视频播放主要涉及解码流程,主要分为硬解码和软解码。在Android设备硬件支持的情况下优先使用Android设备的硬件解码,减少CPU的占用,更加省电。

软件解码:通过软件让CPU来对视频进行解码处理;

软解码需要对大量的视频信息进行运算,所以对CPU处理性能的要求非常高。巨大的运算量就会造成转换效率低,发热量高等问题。不过,软解码不需要过多的硬件支持,兼容性非常高。而且软解码拥有丰富的滤镜,字幕,画面处理优化等效果。

硬件解码:是将原来全部交由CPU来处理的视频数据的部分交由GPU来做。

硬解码效率非常高,这样不但能够减轻CPU的负担,还有着低功耗,发热少等特点。但是,由于硬解码起步比较晚,软件和驱动对他的支持度很低,往往会出现兼容性不好的问题。此外,硬解码的滤镜、字幕、画质方面都做的不够理想。

1 Android系统原生播放器MediaPlayer,以及将MediaPlayer和SurfaceView封装在一起的VideoView, 两者都只是使用硬解播放,基本上只支持本地和HTTP协议的视频播放,扩展性都很差,只适合最简单的视频播放需求。

2 最最常见的视频软解码开源库就是FFmpeg。目前基于FFmpeg的开源播放器有B站的ijkplayer    https://github.com/bilibili/ijkplayer

3 MediaCodec是安卓自带的视频编解码接口,由于使用的是硬解码,其效率相对FFMPEG高出来不少。而MediaCodec就很好拓展,我们可以根据流媒体的协议和设备硬件本身来自定义硬件解码,代表播放器就是Google的ExoPlayer       https://github.com/google/ExoPlayer

基本流程:

  • 1.创建和配置MediaCodec对象
  • 2.进行以下循环:
  • 如果一个输入缓冲区准备好:
  • 读取部分数据,复制到缓冲区
  • 如果一个输出缓冲区准备好:
  • 复制到缓冲区
  • 3.销毁MediaCodec对象
接口:
//根据视频编码创建解码器,这里是解码AVC编码的视频
MediaCodec mediaCodec =MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
//创建视频格式信息
MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, width, height);
//配置
mediaCodec.configure(mediaFormat, surfaceView.getHolder().getSurface(), null, 0);
mediaCodec.start();
//停止解码,此时可以再次调用configure()方法
mediaCodec.stop();
//释放内存
mediaCodec.release();
//一下是循环解码接口
getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组
queueInputBuffer:输入流入队列
dequeueInputBuffer:从输入流队列中取数据进行编码操作
getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组
dequeueOutputBuffer:从输出队列中取出编码操作之后的数据
releaseOutputBuffer:处理完成,释放ByteBuffer数据 

图1 视频播放流程

一 MediaPlayer

1 MediaPlayer方法

1 MediaPlayer创建
可以直接调用构造函数,利用setDataSource()方法设置资源。MediaPlayer mp = new MediaPlayer();
// path可以是本地路径,也可以是网络地址
mp.setDataSource(String path);也可以调用create()方法,create()方法会直接调用prepare()方法。
MediaPlayer.create(Context context, int resid)
MediaPlayer.create(Context context, Uri uri)2 MediaPlayer的控制方法prepare()/prepareAsync(),提供了同步和异步两种方式设置播放器进入prepare状态start(),启动播放器播放pause(),暂停播放器播放stop(),停止播放器播放getCurrentPosition(),当前播放器的时间seekTo(int),从指定位置开始播放release(),释放播放器暂用资源reset(),是播放器重回到Idle状态setLooping(boolean),设置是否循环播放3 MediaPlayer的监听器OnPreparedListener,prepare/prepareAsync结束时调用OnCompletionListener,播放结束时调用OnSeekCompleteListener,seekTo(int)结束时调用

2 播放音频

@Override
protected void onCreate(Bundle savedInstanceState) {... ...mMediaPlayer = new MediaPlayer();MediaPlayerListener listener = new MediaPlayerListener();mMediaPlayer.setOnPreparedListener(listener);mMediaPlayer.setOnCompletionListener(listener);
}@Override
protected void onDestroy() {super.onDestroy();mMediaPlayer.release();mMediaPlayer = null;
}private void createMediaPlayer() {mMediaPlayer.reset();try {AssetFileDescriptor fd = getAssets().openFd("demo.mp3");mMediaPlayer.setDataSource(fd.getFileDescriptor(),fd.getStartOffset(), fd.getLength());mMediaPlayer.prepare();mMediaPlayer.start();} catch (IOException e) {LogTool.loge(LOG_TAG, e);}
}private void startMediaPlayer() {try {mMediaPlayer.start();} catch (IllegalStateException e) {LogTool.loge(LOG_TAG, e);}
}private void pauseMediaPlayer() {try  {mMediaPlayer.pause();} catch (IllegalStateException e) {LogTool.loge(LOG_TAG, e);}
}public void stopMediaPlayer() {try {mMediaPlayer.stop();} catch (IllegalStateException e) {LogTool.loge(LOG_TAG, e);}
}

3播放视频

MediaPlayer要和SurfaceView配合播放视频,控制的方法和音频相同。
在SurfaceView的SurfaceHolder中添加一个回调类Callback,在Callback.surfaceCreated(SurfaceHolder)方法中调用MediaPlayer的setDisplay(SurfaceHolder)方法。

@Override
protected void onCreate(Bundle savedInstanceState) {... ...SurfaceHolder holder = mSurfaceView.getHolder();holder.addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(SurfaceHolder holder) {// createMediaPlayer方法必须要等待Surface被创建以后调用createMediaPlayer();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {}});mMediaPlayer = new MediaPlayer();MediaPlayerListener listener = new MediaPlayerListener();mMediaPlayer.setOnPreparedListener(listener);mMediaPlayer.setOnCompletionListener(listener);
}private void createMediaPlayer() {mMediaPlayer.reset();try {AssetFileDescriptor fd = getAssets().openFd("video.3gp");mMediaPlayer.setDataSource(fd.getFileDescriptor(),fd.getStartOffset(), fd.getLength());mMediaPlayer.prepare();mMediaPlayer.setDisplay(mSurfaceView.getHolder());mMediaPlayer.start();} catch (IOException e) {}
}

二 MediaCodec

MediaCodec 是Android 4.1(api 16)版本引入的低层编解码接口,同时支持音视频的编码和解码。通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,能够编解码诸如H.264、H.265、AAC、3gp等常见的音视频格式。MediaCodec在编解码的过程中使用了一组输入/输出缓存区来同步或异步处理数据。

图 客户端调用MediaCodec流程

1 数据格式

mediacodec的作用是处理输入的数据生成输出数据,有两种输入输出模式:
 1)surface模式-------输入/输出以surface作为源
 2)ByteBuffer模式--------输入/输出时以ByteBuffer作为源

mediacodec接受三种数据格式:压缩数据,原始音频数据和原始视频数据。

这三种数据都可以使用ByteBuffer作为载体传输给mediacodec来处理。但是当使用原视频数据时,最好采用Surface作为输入源来替代ByteBuffer,这样效率更高,效果更好,因为surface使用的更底层的视频数据,不会映射或者复制到ByteBuffer缓冲区。在使用surface作为输入源时,开发者不能访问到到原始视频数据,但是可以使用ImageReader来获取到原始未加密的视频数据,这个地方我理解的是imagereader的工作流程是接受自己的surface数据来生成image,将imagereader的surface传给mediacodec作为解码器的输出surface,就可以访问解码的数据,但是必须是未加密的,这种方式同样比使用ByteBuffer更快,因为native缓冲区会直接映射到directbytebuffer区域,这是一块native和java共享的缓冲区。当使用ByteBuffer模式时可以使用Image来获取原始视频数据,mediacodec提供了两个方法,getInput/OutputImage(int)

例如mediacodec解码H264数据,我们必须将分割符和NALU单元作为一个完整的数据帧传给解码器才能正确解码,除非是标记了BUFFER_FLAG_PARTIAL_FRAME的数据,这种方式不常用。

注:客户端处理完数据后,必须手动释放output缓冲区,否则将会导致MediaCodec输出缓冲被占用,无法继续解码。

图 MediaCodec状态

2 MediaCodec状态图,整体上分为三个大的状态:Sotpped、Executing、Released。

  • Stoped:包含了3个小状态:Error、Uninitialized、Configured。

首先,新建MediaCodec后,会进入Uninitialized状态;

其次,调用configure方法配置参数后,会进入Configured;

  • Executing:同样包含3个小状态:Flushed、Running、End of Stream。

再次,调用start方法后,MediaCodec进入Flushed状态;
接着,调用dequeueInputBuffer方法后,进入Running状态;
最后,当解码/编码结束时,进入End of Stream(EOF)状态。

这时,一个视频就处理完成了。

  • Released:最后,如果想结束整个数据处理过程,可以调用release方法,释放所有的资源。

那么,Flushed是什么状态呢?

从图中我们可以看到,在Running或者End of Stream状态时,都可以调用flush方法,重新进入Flushed状态。
当我们在解码过程中,进入了End of Stream后,解码器就不再接收输入了,这时候,需要调用flush方法,重新进入接收数据状态。
或者,我们在播放视频过程中,想进行跳播,这时候,我们需要Seek到指定的时间点,这时候,也需要调用flush方法,清除缓冲,否则解码时间戳会混乱。

图 MediaCodec解码流程

3 MediaCodec用法

Android的硬解码接口MediaCodec只能接收Annex-B格式的H.264数据,而iOS平台的VideoToolBox则相反,只支持AVCC格式。这就导致:

在Android平台硬解播放flv/mp4/mkv等封装的视频时,需要将AVCC格式的extradata以及NALU数据转为Annex-B格式;

在iOS平台播放ts或ts切片的hls视频时,需要将Annex-B格式的SPS/PPS NALU转为AVCC格式的extradata,以及将其他以size方式分割的NALU转为start code方式。

初始化解码器,除了配置输入视频流的的编码格式、宽高以及输出格式之外,还需要配置一些额外的信息。 对于H.264视频,需要填充Annex-B格式的SPS/PPS信息。

MediaCodec API:
getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组
queueInputBuffer:输入流入队列
dequeueInputBuffer:从输入流队列中取数据进行编码操作
getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组
dequeueOutputBuffer:从输出队列中取出编码操作之后的数据
releaseOutputBuffer:处理完成,释放ByteBuffer数据 
创建编/解码器MediaCodec主要提供了createEncoderByType(String type)、createDecoderByType(String type)两个方法来创建编解码器,它们均需要传入一个MIME类型多媒体格式。常见的MIME类型多媒体格式如下:
● “video/x-vnd.on2.vp8” - VP8 video (i.e. video in .webm)
● “video/x-vnd.on2.vp9” - VP9 video (i.e. video in .webm)
● “video/avc” - H.264/AVC video
● “video/mp4v-es” - MPEG4 video
● “video/3gpp” - H.263 video
● “audio/3gpp” - AMR narrowband audio
● “audio/amr-wb” - AMR wideband audio
● “audio/mpeg” - MPEG1/2 audio layer III
● “audio/mp4a-latm” - AAC audio (note, this is raw AAC packets, not packaged in LATM!)
● “audio/vorbis” - vorbis audio
● “audio/g711-alaw” - G.711 alaw audio
● “audio/g711-mlaw” - G.711 ulaw audioMediaCodec还提供了createByCodecName (String name)方法,支持使用组件的具体名称来创建编解码器。但是该方法使用起来有些麻烦,且官方是建议最好是配合MediaCodecList使用,因为MediaCodecList记录了所有可用的编解码器。
 配置编/解码器编解码器配置使用的是MediaCodec的configure方法,该方法首先对MediaFormat存储的数据map进行提取,然后调用本地方法native-configure实现对编解码器的配置工作。在配置时,configure方法需要传入format、surface、crypto、flags参数,其中format为MediaFormat的实例,它使用”key-value”键值对的形式存储多媒体数据格式信息;surface用于指明解码器的数据源来自于该surface;crypto用于指定一个MediaCrypto对象,以便对媒体数据进行安全解密;flags指明配置的是编码器(CONFIGURE_FLAG_ENCODE)。MediaFormat mFormat = MediaFormat.createVideoFormat("video/avc", 640 ,480);     // 创建MediaFormat
mFormat.setInteger(MediaFormat.KEY_BIT_RATE,600);       // 指定比特率
mFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30);  // 指定帧率
mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,mColorFormat);  // 指定编码器颜色格式
mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); // 指定关键帧时间间隔
mVideoEncodec.configure(mFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); //码率控制模式有三种:CQ  表示完全不控制码率,尽最大可能保证图像质量;CBR 表示编码器会尽量把输出码率控制为设定值,即我们前面提到的“不为所动”;VBR 表示编码器会根据图像内容的复杂度(实际上是帧间变化量的大小)来动态调整输出码率,图像复杂则码率高,图像简单则码率低;// extradata中是Annex-B格式的SPS、PPS NALU数据
//SPS设为"csd-0", PPS设为"csd-1"
mediaFormat.setByteBuffer("csd-0", extradata);// ...mediaCodec.configure(mediaFormat, surface, 0, 0);// ...对于mp4/flv/mkv等封装,我们得到的是AVCC格式的extradata,需要先将该extradata转换为Annex-B格式的两个NALU, 然后用startcode进行分割。
///如果编解码音频数据,则调用MediaFormat的createAudioFormat(String mime, int sampleRate,int channelCount)的方法Camera预览采集的图像流通常为NV21或YV12,那么编码器需要指定相应的颜色格式,否则编码得到的数据可能会出现花屏、叠影、颜色失真等现象。MediaCodecInfo.CodecCapabilities.存储了编码器所有支持的颜色格式,常见颜色格式映射如下:原始数据 编码器
NV12(YUV420sp) ———> COLOR_FormatYUV420PackedSemiPlanar
NV21 ———-> COLOR_FormatYUV420SemiPlanar
YV12(I420) ———-> COLOR_FormatYUV420Planar 
启动编/解码器
当编解码器配置完毕后,就可以调用MediaCodec的start()方法,该方法会调用低层native_start()方法来启动编码器,并调用低层方法ByteBuffer[] getBuffers(input)来开辟一系列输入、输出缓存区。start()方法源码如下:public final void start() {native_start();synchronized(mBufferLock) {cacheBuffers(true /* input */);cacheBuffers(false /* input */);}}
数据处理
MediaCodec支持两种模式编解码器,即同步synchronous、异步asynchronous。本文主要介绍用得较多的同步编解码。当编解码器被启动后,每个编解码器都会拥有一组输入和输出缓存区,但是这些缓存区暂时无法被使用,只有通过MediaCodec的dequeueInputBuffer/dequeueOutputBuffer方法获取输入输出缓存区授权,通过返回的ID来操作这些缓存区。下面我们通过一段官方提供的代码,进行扩展分析:MediaCodec codec = MediaCodec.createByCodecName(name);codec.configure(format, …);MediaFormat outputFormat = codec.getOutputFormat(); // option Bcodec.start();for (;;) {int inputBufferId = codec.dequeueInputBuffer(timeoutUs);if (inputBufferId >= 0) {ByteBuffer inputBuffer = codec.getInputBuffer(…);// fill inputBuffer with valid data…codec.queueInputBuffer(inputBufferId, …);}int outputBufferId = codec.dequeueOutputBuffer(…);if (outputBufferId >= 0) {ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A// bufferFormat is identical to outputFormat// outputBuffer is ready to be processed or rendered.…codec.releaseOutputBuffer(outputBufferId, …);} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {// Subsequent data will conform to new format.// Can ignore if using getOutputFormat(outputBufferId)outputFormat = codec.getOutputFormat(); // option B}}codec.stop();codec.release();从上面代码可知,当编解码器start后,会进入一个for(;;)循环,该循环是一个死循环,以实现不断地去从编解码器的输入缓存池中获取包含数据的一个缓存区,然后再从输出缓存池中获取编解码好的输出数据。

三 ExoPlayer

谷歌出品,推荐使用的播放器。同RecyclerView一样定制化程度非常高。
优点:

支持动态的自适应流HTTP(DASH) 和 平滑流,任何目前MediaPlayer支持的视频格式(同时它还支持HTTP直播了(HLS),MP4,MP3,WebM,M4A,MPEG-TS 和 AAC).
    支持高级的HLS特性,例如正确处理 EXT-X-DISCONTINUITY 标签。
    无缝合并、连接和循环媒体的能力。
    支持自定义和扩治你的使用场景。ExoPlayer专门为此设计,它允许将许多组件替换为自定义实现,它提供了低等级的媒体API,例如:MediaCodec,AudioTrack,MediaDrm,可以用于建立自定义媒体播放的解决方案。。
    以第三方依赖的方式集成,可以随应用升级版本
    更少的适配性问题,更少的设备特定的问题和更少的行为变化, 在不同的设备和android的版本。
    可以接入ffmpeg

缺点:

相对于MediaPlayer更耗电:但是Android Q以开发audio affload,可以减低功耗。
    最低API16
    早期版本不支持自动检查需要播放的媒体格式,后续的版本已经支持。
    TextureView比SurfaceView 耗电增加30%,所以SurfaceView能满足需求,尽量使用SurfaceView。

android播放器:MediaPlayer ExoPlayer ijkplayer相关推荐

  1. android音乐播放器实现,Android实现简单音乐播放器(MediaPlayer)

    Android实现简单音乐播放器(MediaPlayer),供大家参考,具体内容如下 开发工具:Andorid Studio 1.3 运行环境:Android 4.4 KitKat 工程内容 实现一个 ...

  2. ijk基于exo_github上十二款最著名的Android播放器开源项目

    1.ijkplayer 介绍:Ijkplayer 是Bilibili发布的基于 FFplay 的轻量级 Android/iOS 视频播放器.实现了跨平台功能,API 易于集成:编译配置可裁剪,方便控制 ...

  3. github上十二款最著名的Android播放器开源项目

    1.ijkplayer 项目地址: https://github.com/Bilibili/ijkplayer 介绍:Ijkplayer 是Bilibili发布的基于 FFplay 的轻量级 Andr ...

  4. android 音乐视频播放器(github上十二款最著名的Android播放器开源项目)

    1.ijkplayer 项目地址: https://github.com/Bilibili/ijkplayer 介绍:Ijkplayer 是Bilibili发布的基于 FFplay 的轻量级 Andr ...

  5. android 著名播放器,【精华】十二大最著名的Android播放器开源项目

    1.ExoPlayer https://github.com/google/ExoPlayer ExpPlayer是一个开源的,App等级的媒体API,它的开源项目包含了library和示例: Dem ...

  6. Android播放器开源项目,github常用视频音频播放器

    需求: 搜集到的github常用的视频和音频播放器,其中前三个是比较常用的播放器. 第四个和第五个是比较直白的用法. 1.ijkplayer 项目地址: https://github.com/Bili ...

  7. android开源库 droidlib,十二大最著名的Android播放器开源项目

    1.ExoPlayer https://github.com/google/ExoPlayer ExpPlayer是一个开源的,App等级的媒体API,它的开源项目包含了library和示例: - 这 ...

  8. 安卓java音乐播放器下一曲_Android实现简单音乐播放器(MediaPlayer)

    Android实现简单音乐播放器(MediaPlayer),供大家参考,具体内容如下 开发工具:Andorid Studio 1.3 运行环境:Android 4.4 KitKat 工程内容 实现一个 ...

  9. Android-音频播放器—MediaPlayer

    音频播放器-MediaPlayer 1.日常生活中听到的数码声音即音频,主要格式有(MP3,3GPP,Ogg和,WAWE)等,通常看到的视频主要有(3GP 和mpeg-4) 2.再Android中提供 ...

最新文章

  1. 有多个重载参数pow_面试深刨——150分面重载
  2. 被“嫌弃”的AI药物设计
  3. 百货中心供应链管理系统
  4. VTK:Filtering之ImplicitBooleanDemo
  5. 15行代码让苹果设备崩溃,最新的iOS 12也无法幸免
  6. 杭电1262--寻找素数对(打表)
  7. 深度学习自动编译和优化技术调研
  8. 如何添加时区 java_如何在Windows(非Java)应用程序中使用Java时区ID?
  9. 4、Windows2008 R2安装Vcenter5.0
  10. 插槽作用域渲染按钮开关 ~ 满满的干货哦
  11. cmake之系统头文件(六)
  12. Wave Arts Tube Saturator for Mac(电子管模拟效果器插件)
  13. python pygame实战1: 小球碰撞balls collision
  14. 在计算机领域黑箱,计算机模拟电学黑箱
  15. 简单概括一下《金字塔原理》的主要内容?
  16. 【转发】Cortex-M3 处理器
  17. HTML常用的标签:
  18. elang mnesia 数据库操作
  19. (免费分享)基于jsp,javaweb银行柜员业务绩效考核系统(带论文)
  20. Apifox 测试工具

热门文章

  1. MyBatis-Plus快速入门-(干货满满+超详细)
  2. Mac安装单机版K8S
  3. Unity 多重材质球替换、多重材质球特定贴图替换、Materials替换
  4. 无边框透明窗口设置鼠标穿透与不穿透功能
  5. 鸿蒙智能家居市场,荣耀智慧屏杀入彩电市场 重在智能家居 不把红海做成血海...
  6. 机器学期第一学期小结
  7. 浅谈linux - samba实现linux与windows文件共享
  8. java 连接 firebird
  9. 日用品电商销售数据分析
  10. ros--rosbag