其他分享
首页 > 其他分享> > ijkplayer剖析

ijkplayer剖析

作者:互联网

ijkplayer 是一款比较出众的开源 Android/IOS 跨平台播放器,基于 ffplay,API 易于集成,可定制编译控制体积。

本文基于 0.8.8 版本的 ijkplayer ,对其源码进行剖析,涉及到不同平台下的封装接口或处理方式时,均以 Android 为例。

ijkplayer android 集成了三种播放器实现:

一般而言, ijkplayer 就是指 IjkMediaPlayer,本文分析的对象就是 IjkMediaPlayer.

目录结构

1
2
3
4
5
6
7
8
9
ijkplayer(项目文件夹)
├──tools - 初始化项目工程脚本
├──config - 编译ffmpeg使用的配置文件
├──extra - 存放编译ijkplayer所需的依赖源文件, 如ffmpeg、openssl等
├──ijkmedia - 核心代码
├──ijkplayer - 播放器数据下载及解码相关
├──ijksdl - 音视频数据渲染相关
├──android - android平台上的上层接口封装以及平台相关方法
├──ios - iOS平台上的上层接口封装以及平台相关方法

功能实现的平台差异

iOS和Android平台的差异主要表现在

Platform Hardware Codec Video Render Audio Output
Android MediaCodec OpenGL ES、MediaCodec OpenSL ES、AudioTrack
iOS VideoToolBox OpenGL ES AudioQueue

IjkMediaPlayer和Native交互

播放控制相关的 start、pause、stop 等,调用 对应的 native 方法

底层状态信息的上报(比如底层的播放状态回调)相关的 postEventFromNative 等,这些方法由底层主动调用(有@CalledByNative 注解)

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class  extends AbstractMediaPlayer {
private void initPlayer(IjkLibLoader libLoader) {
loadLibrariesOnce(libLoader);
initNativeOnce();

Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}


* Native setup requires a weak reference to our object. It's easier to
* create it here than in C++.
*/
native_setup(new WeakReference<IjkMediaPlayer>(this));
}
}

initPlayer 方法,共做了四件事:

初始化底层

c代码

ijkplayer/android/ijkplayer/ijkplayer-armv7a/src/main/jni/ijkmedia/ijkplayer/android/

主要逻辑位于

ijkpalyer_android.cijkmp_android_create 方法

1
2
3
4
5
6
7
8
// 创建底层播放器对象,设置消息处理函数
IjkMediaPlayer *mp = ijkmp_create(msg_loop, saveMode, hard_mux);

// 创建图像渲染对象
mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();

// 初始化视频解码器(软/硬)、音频输出设备(opensles/audioTrack)
mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);

软硬解码选择

跟踪 pipeline/ffpipeline_android.cffpipeline_create_from_android 方法

发现是 用函数指针记录 类 IjkMediaPlayer.setOption() 设置的属性

配置

初始化后 IjkMediaPlayer 后,可以对其进行一系列配置,例如:

1
2
3
4
5
// 设置硬解码
mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);

// 设置 opensles 方式输出音频
mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1);

setOption() 会调用到底层 ff_ffplay.c 的 ffp_set_option() 方法

播放

播放器必然是通过多线程同时进行解封装、解码、视频渲染等工作的,对于 Ijkplayer 来说,开辟的线程如下:

当对播放器设置视频源路径、解码方式、输出模式等播放选项后,就可以开始播放了, 播放入口方法为 ffp_prepare_async_l,此方法中调用了比较重要的两个方法:

1
2
3
4
5
// 打开音频输出设备
ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);
...
// 创建音/视频数据解码前/后队列, 创建解封装和视频渲染线程
VideoState *is = stream_open(ffp, file_name, NULL);

stream_open 方法则相当重要了,梳理一下该方法中涉及到的关键方法:

音视频同步

对于播放器来说,音视频同步是一个关键点,同时也是一个难点,同步效果的好坏,直接决定着播放器的质量。

通常音视频同步的解决方案就是选择一个参考时钟,播放时读取音视频帧上的时间戳,同时参考当前时钟参考时钟上的时间来安排播放。如下图所示:

如果音视频帧的播放时间大于当前参考时钟上的时间,则不急于播放该帧,直到参考时钟达到该帧的时间戳;如果音视频帧的时间戳小于当前参考时钟上的时间,则需要“尽快”播放该帧或丢弃,以便播放进度追上参考时钟。

参考时钟的选择

有多种方式:

考虑人对视频、和音频的敏感度,在存在音频的情况下,优先选择音频作为主时钟源。

ijkplayer在默认情况下也是使用音频作为参考时钟源

事件处理

在播放过程中,某些行为的完成或者变化,如prepare完成,开始渲染等,需要以事件形式通知到外部,以便上层作出具体的业务处理。

播放器底层上报事件时,实际上就是将待发送的消息放入消息队列,另外有一个线程会不断从队列中取出消息,上报给外部

参考&扩展

原文:大专栏  ijkplayer剖析


标签:播放器,IjkMediaPlayer,ijkplayer,剖析,android,播放,时钟
来源: https://www.cnblogs.com/sanxiandoupi/p/11642317.html