100行代码实现最简单的基于FFMPEG+SDL的视频播放器
该播放器虽然简单,但是几乎包含了使用FFMPEG播放一个视频所有必备的API,并且使用SDL显示解码出来的视频。
并且支持流媒体等多种视频输入,处于简单考虑,没有音频部分,同时视频播放采用直接延时40ms的方式
对比SDL1.2的流程图,发现变化还是很大的。几乎所有的API都发生了变化。但是函数和变量有一定的对应关系:
SDL_SetVideoMode()————SDL_CreateWindow()
SDL_Surface————SDL_Window
SDL_CreateYUVOverlay()————SDL_CreateTexture()
SDL_Overlay————SDL_Texture
简单解释各个变量的作用:
- SDL_Window就是使用SDL的时候弹出的那个窗口。在SDL1.x版本中,只可以创建一个一个窗口。在SDL2.0版本中,可以创建多个窗口。
- SDL_Texture用于显示YUV数据。一个SDL_Texture对应一帧YUV数据。
- SDL_Renderer用于渲染SDL_Texture至SDL_Window。
- SDL_Rect用于确定SDL_Texture显示的位置。注意:一个SDL_Texture可以指定多个不同的
- SDL_Rect,这样就可以在SDL_Window不同位置显示相同的内容(使用SDL_RenderCopy()函数)。
它们的关系如下图所示:
下图举了个例子,指定了4个SDL_Rect,可以实现4分屏的显示。
simplest_ffmpeg_player(标准版)代码
最基础的版本,学习的开始。
1 |
|
simplest_ffmpeg_player_su(SU版)代码
标准版的基础之上引入了 SDL 的 Event。
效果如下:
- SDL弹出的窗口可以移动了
- 画面显示是严格的40ms一帧
代码:
1 |
|
av_read_packet()
通过 av_read_packet()
,读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况。
以 ts 流为例,是读取一个完整的 PES 包(一个完整 pes 包包含若干视频或音频 es 包),读取完毕后,通过 av_parser_parse2()
分析出视频一帧(或音频若干帧),返回,下次进入循环的时候,如果上次的数据没有完全取完,则 st = s->cur_st
; 不会是NULL,即再此进入 av_parser_parse2()
流程,而不是下面的 av_read_packet()
流程.
这样就保证了,如果读取一次包含了 N 帧视频数据(以视频为例),则调用 av_read_frame()
N 次都不会去读数据,而是返回第一次读取的数据,直到全部解析完毕。
函数调用结构图:
FFmpeg 源码分析
av_register_all()
av_register_all()
- ffmpeg注册复用器,编码器
该函数在所有基于ffmpeg的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器等。
1 | // 可见解复用器注册都是用 |
看一下宏的定义,这里以解复用器为例:
1 |
|
我们以 REGISTER_DEMUXER (AAC, aac)
为例,则它等效于
1 | extern AVInputFormat ff_aac_demuxer; |
从上面这段代码我们可以看出,真正注册的函数是 av_register_input_format(&ff_aac_demuxer)
,那我就看看这个和函数的作用,查看一下 av_register_input_format()
的代码:
1 | void av_register_input_format(AVInputFormat *format) |
这段代码是比较容易理解的,首先先提一点,first_iformat 是个什么东东呢?其实它是 Input Format 链表的头部地址,是一个全局静态变量,定义如下:
1 | /** head of registered input format linked list */ |
由此我们可以分析出 av_register_input_format()
的含义,一句话概括就是:
- 遍历链表并把当前的 Input Format 加到链表的尾部。
至此 REGISTER_DEMUXER (X, x)
分析完毕。
同理,复用器道理是一样的,只是注册函数改为 av_register_output_format()
;
既有解复用器又有复用器的话,有一个宏定义:
1 |
可见是分别注册了复用器和解复用器。
此外还有网络协议的注册,注册函数为 ffurl_register_protocol()
,在此不再详述。
下面贴出它的源代码(allformats.c)
1 |
|
整个代码没太多可说的,首先确定是不是已经初始化过了(initialized),如果没有,就调用 avcodec_register_all()
注册编解码器(这个先不分析),然后就是注册,注册,注册…直到完成所有注册。
PS:曾经研究过一阵子 RTMP 协议,以及对应的开源工程 librtmp。在这里发现有一点值得注意,ffmpeg自带了 RTMP 协议的支持,只有使用 rtmpt://, rtmpe://, rtmpte://
等的时候才会使用 librtmp 库。
函数调用关系图如下图所示。av_register_all()
调用了 avcodec_register_all()
。 avcodec_register_all()
注册了和编解码器有关的组件:硬件加速器,解码器,编码器,Parser,Bitstream Filter。av_register_all()
除了调用 avcodec_register_all()
之外,还注册了复用器,解复用器,协议处理器。
下面附上复用器,解复用器,协议处理器的代码。
注册复用器的函数是 av_register_output_format()
。
1 | void av_register_output_format(AVOutputFormat *format) |
注册解复用器的函数是 av_register_input_format()
。
1 | void av_register_input_format(AVInputFormat *format) |
注册协议处理器的函数是 ffurl_register_protocol()
。
1 | int ffurl_register_protocol(URLProtocol *protocol) |
avcodec_register_all()
ffmpeg注册编解码器等的函数 avcodec_register_all()
(注意不是 av_register_all()
,那是注册所有东西的)。该函数在所有基于ffmpeg的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用编解码器等。
其实注册编解码器和注册复用器解复用器道理是差不多的,重复的内容不再多说。
1 | // 编码器的注册是: |
我们来看一下宏的定义,这里以编解码器为例:
1 |
|
在这里,我发现其实编码器和解码器用的注册函数都是一样的:avcodec_register()
以 REGISTER_DECODER (H264, h264)
为例,就是等效于
1 | extern AVCodec ff_h264_decoder; |
下面看一下 avcodec_register()
的源代码:
1 | //注册所有的AVCodec |
这段代码是比较容易理解的。首先先提一点,first_avcdec 是就是 AVCodec 链表的头部地址,是一个全局静态变量,定义如下:
1 | /* encoder management */ |
由此我们可以分析出avcodec_register()的含义,一句话概括就是:遍历链表并把当前的AVCodec加到链表的尾部。
同理,Parser,BSF(bitstream filters,比特流滤镜),HWACCEL(hardware accelerators,硬件加速器)的注册方式都是类似的。不再详述。
下面贴出它的原代码:
1 |
|
整个代码的过程就是首先确定是不是已经初始化过了(initialized),如果没有,就注册,注册,注册…直到完成所有注册。
函数的调用关系图如下图所示。av_register_all()
调用了 avcodec_register_all()
。因此如果调用过 av_register_all()
的话就不需要再调用 avcodec_register_all()
了。
下面附上硬件加速器,编码器/解码器,parser,Bitstream Filter的注册代码。
硬件加速器注册函数是 av_register_hwaccel()
。
1 | void av_register_hwaccel(AVHWAccel *hwaccel) |
编解码器注册函数是 avcodec_register()
。
1 | av_cold void avcodec_register(AVCodec *codec) |
parser注册函数是 av_register_codec_parser()
。
1 | void av_register_codec_parser(AVCodecParser *parser) |
Bitstream Filter注册函数是 av_register_bitstream_filter()
。
1 | void av_register_bitstream_filter(AVBitStreamFilter *bsf) |
后两个函数中的 avpriv_atomic_ptr_cas()
定义如下。
1 | void *avpriv_atomic_ptr_cas(void * volatile *ptr, void *oldval, void *newval) |