随着互联网技术的飞速发展,移动端播放视频的需求如日中天,由此也催生了一批开源、闭源的播放器,但是无论这个播放器功能是否强大、兼容性是否优秀,它的基本模块通常都是由以下部分组成:事务处理、数据的接收和解复用、音视频解码以及渲染,其基本框架如下图所示:

直播技术流程
针对各种铺天盖地的播放器项目,选取了比较出众的ijkplayer进行源码剖析。它是一个基于FFPlay的轻量级Android/iOS视频播放器,实现了跨平台的功能,API易于集成;编译配置可裁剪,方便控制安装包大小。

一、总体说明

打开ijkplayer,可看到其主要目录结构如下:

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

二、初始化流程

初始化完成的主要工作就是创建播放器对象,打开ijkplayer/ios/IJKMediaDemo/IJKMediaDemo.xcodeproj工程,可看到IJKMoviePlayerViewController类中viewDidLoad方法中创建了IJKFFMoviePlayerController对象,即iOS平台上的播放器对象。

1
2
3
4
5
6
- (void)viewDidLoad
{
......
self.player = [[IJKFFMoviePlayerController alloc] initWithContentURL:self.url withOptions:options];
......
}

查看ijkplayer/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m文件,其初始化方法具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
- (id)initWithContentURL:(NSURL *)aUrl
withOptions:(IJKFFOptions *)options
{
if (aUrl == nil)
return nil;

// Detect if URL is file path and return proper string for it
NSString *aUrlString = [aUrl isFileURL] ? [aUrl path] : [aUrl absoluteString];

return [self initWithContentURLString:aUrlString
withOptions:options];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (id)initWithContentURLString:(NSString *)aUrlString
withOptions:(IJKFFOptions *)options
{
if (aUrlString == nil)
return nil;

self = [super init];
if (self) {
......
// init player
_mediaPlayer = ijkmp_ios_create(media_player_msg_loop);
......
}
return self;
}

可发现在此创建了IjkMediaPlayer结构体实例_mediaPlayer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
IjkMediaPlayer *ijkmp_ios_create(int (*msg_loop)(void*))
{
IjkMediaPlayer *mp = ijkmp_create(msg_loop);
if (!mp)
goto fail;

mp->ffplayer->vout = SDL_VoutIos_CreateForGLES2();
if (!mp->ffplayer->vout)
goto fail;

mp->ffplayer->pipeline = ffpipeline_create_from_ios(mp->ffplayer);
if (!mp->ffplayer->pipeline)
goto fail;

return mp;

fail:
ijkmp_dec_ref_p(&mp);
return NULL;
}

在该方法中主要完成了三个动作:

1、创建IJKMediaPlayer对象

1
2
3
4
5
6
7
8
9
10
IjkMediaPlayer *ijkmp_create(int (*msg_loop)(void*))
{
IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));
......
mp->ffplayer = ffp_create();
......
mp->msg_loop = msg_loop;
......
return mp;
}

通过ffp_create方法创建了FFPlayer对象,并设置消息处理函数。

2、创建图像渲染对象SDL_Vout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SDL_Vout *SDL_VoutIos_CreateForGLES2()
{
SDL_Vout *vout = SDL_Vout_CreateInternal(sizeof(SDL_Vout_Opaque));
if (!vout)
return NULL;

SDL_Vout_Opaque *opaque = vout->opaque;
opaque->gl_view = nil;
vout->create_overlay = vout_create_overlay;
vout->free_l = vout_free_l;
vout->display_overlay = vout_display_overlay;

return vout;
}

3、创建平台相关的IJKFF_Pipeline对象,包括视频解码以及音频输出部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
IJKFF_Pipeline *ffpipeline_create_from_ios(FFPlayer *ffp)
{
IJKFF_Pipeline *pipeline = ffpipeline_alloc(&g_pipeline_class, sizeof(IJKFF_Pipeline_Opaque));
if (!pipeline)
return pipeline;

IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
opaque->ffp = ffp;
pipeline->func_destroy = func_destroy;
pipeline->func_open_video_decoder = func_open_video_decoder;
pipeline->func_open_audio_output = func_open_audio_output;

return pipeline;
}

至此已经完成了ijkplayer播放器初始化的相关流程,简单来说,就是创建播放器对象,完成音视频解码、渲染的准备工作。

作者金山视频云,首发简书 Jianshu.com