基于ffmpeg的播放器sdk
简介
网上开源的音视频sdk很多,如比较出名的有vlc、ijkplayer、exoplayer、gstreamer等等,当然各大公司他们也会有自己的专属播放器sdk,可能基于ffmpeg,也可能完全自己实现。
因此,基于个人兴趣我写了一个简单的播放器sdk,名为PPlayer
github地址:https://github.com/KaiLuQiu/PPlayer
ffmpeg的简单介绍
ffmpeg是一个全球领先的多媒体框架,跨平台,提供视频的解码、编码、转码、demuxer、muxer、流媒体、过滤等支持。因此,ffmpeg中其实包好着好几库,如:
1.libavcode(它是音视频的编解码库)
2.libavformat(它实现了多种流媒体协议,各种容器的解封装,I/O访问等)、libavutil(它相当于是一个使用的工具程序库)
3.libavfilter(它是一个通用的音视频后处理库)
4.libavdevice
5.libswresample
6.libswscale
PPlayer的简单介绍
首先它是基于ffmpeg开发的,参考ffplay的一套跨平台播放器sdk。以下是其目录结构:
播放器内部源码简单分析
首先是demuxer线程,demuxer线程是对应各种不同媒体容器的解封装过程。如常见的媒体容器有mp4、mkv、flv、avi、ts等码流。不同容器的解封装过程当然不同。首先不同容器的解封装可以看不同的容器的spec里面对媒体文件格式的解析很详细。同时具体的解封装源码可以看ffmpeg libavformat库中对应的源码。
大致流程如下:读取一定的视频文件做probe过程,主要是通过probe对一个未知的视频源进行分析。首先可以通过文件的后缀名初步判断其文件容器格式,但这仅仅只是得到一定score值。如果这个文件可以支持read_probe,那么就会遍历所有容器的probe函数,比如,mp4可能就会去probe码流中是否存在ftyp这样字段信息。因此遍历所有容器的probe,并取最高分来确定当前播放的容器的格式。
当我们获取到媒体容器格式之后,我们就可以映射到相应容器的解封装源码。比如我的媒体容器格式是flv,那么av_read_fream最终就会映射到对应flv.c文件的read函数。seek函数也一样,同样也是映射到具体容器的seek过程(题外话:像有的容器,其内部带有index索引,则很方便seek。如果不带index索引的话,如ts码流就不带这种索引,那就可以通过类2分法方式进行seek操作。同时seek有by time or by byte等等,这一块就不展开讨论了)。在扯一个题外话(就是有些奇怪的码流,一开audio的数据就和video的数据离得很远远,那这种格式的码流的话,如果正常的一包一包的parse就会导致parse出来的某一种包过多,其实遇到这种case还是有方式可以对demuxer进行优化的)总之这一块的坑还是蛮多的,但是强大的ffmpeg都为我们封装好了。
解封装完毕后,接下来我们就会得到视频音频字幕两路不同的数据包了,我这边的做法就是参考ffplay 将数据送入到对应的ringbuffer中也就是一个packetQueue中。应该来说正常的播放器器audioringbuffer和videoringbuffer中其能够容纳的大小是需要一个限制的,主要根据当前视频的格式来进行区分,如4k那么其容纳限制应该高点,我这边只是简单的设置 #define MAX_SIZE (15 * 512 *1024) 15M的大小。我这边的ringbuffer开始播放和seek之后都应该插入一个flush包,这个包得作用也很简单,相当的一个分割线的作用。
接下来解码,解码主要调用ffmpeg对应的接口,将对应的pkt包丢下去通过软解的方式进行解码,解码后输出一个frame格式的数据。当我们获得对应的frame的时候,我们可对其pts做一个转换,也就是通过time_base将pts转换成秒,方便后面avsync 同步计算,其实也可以不转。最后将解码的数据送入到对应FrameQueue循环队列中(注意:这边Queue大小设置为3帧左右,目的是不让frameQueue中存放太多的frame,原因我会选择在videothread 和 videoOut线程中进行丢帧,如果FrameQueue存放太多帧的话,就会导致两边最新一笔的数据的pts相差太多。那么就会影响到avsync的丢帧地方)
音视频输出,视频输出采用的是GLView进行输出,audio 采用SDL库通过callback进行输出。
音视频同步,PPlayer目前只是简单的使用video同步audio的方式进行的。原因很简单,使用audio同步video,丢失数据的话,影响会比video丢数据更加明显,且更影响播放效果。同步方面参考ffplay,对应的流都有一套pts,根据pts的计算是否需要丢帧,根据系统时间判断是否要delay线程等。(此处在有遇到几个坑,一个是audioClock更新的时候一定要记住SDL采用的是双buffer的设计,一定要将那帧的audio frame pts减去两个buffer中缓存的数据以及减去当前这笔剩余的数据(因为每一次SDL库callback并不是读满一整samples的),这样子才是真正播放的pts,具体你可以看下源码,注释还是写的蛮清楚的)。其次在callback时一定要设置SDL_memset(stream, 0, len);,否则会出现声音异常。 SDL_SetMainReady();还有这个否者SDL_OpenAudio的时候就会失败。
总结
本人毕业两年多了还是第一次写简书惭愧惭愧,写的不好的地方还望指出。目的也是对以前所学的内容做一个复习与总结。之后将会持续更新自己所学的一些知识!
PPlayer是我空余时间基于兴趣写的一个简单的播放器SDK(内部某些实现参考ffplay),里面肯定有很多地方写的不是很好,如果遇大佬还望给予更多的意见。