ffplay是ffmpeg源码中一个自带的开源播放器实例,同时支持本地视频文件的播放以及在线流媒体播放,功能非常强大。
FFplay: FFplay is a very simple and portable media player using the FFmpeg libraries and the SDL library. It is mostly used as a testbed for the various FFmpeg APIs.
ffplay中的代码充分调用了ffmpeg中的函数库,因此,想学习ffmpeg的使用,或基于ffmpeg开发一个自己的播放器,ffplay都是一个很好的切入点。
由于ffmpeg本身的开发文档比较少,且ffplay播放器源码的实现相对复杂,除了基础的ffmpeg组件调用外,还包含视频帧的渲染、音频帧的播放、音视频同步策略及线程调度等问题。
因此,这里我们以ffmpeg官网推荐的一个ffplay播放器简化版本的开发例程为基础,在此基础上循序渐进由浅入深,最终探讨实现一个视频播放器的完整逻辑。
在上篇文章中介绍了如果搭建一个基于ffmpeg的播放器框架
本文在上篇文章的基础上,继续讨论如何将ffmpeg解码出的视频帧进行渲染显示
1、视频帧渲染
上篇文章中介绍了如何基于ffmpeg搭建一个视频播放器框架,运行程序后可以看到,除了生成几张图片外,程序好像什么也做不了。
这是因为ffmpeg通过其封装的api及组件,为我们屏蔽了不同视频封装格式及编码格式的差异,以统一的api接口提供给开发者使用,开发者不需要了解每种编码方式及封装方式具体的技术细节,只需要调用ffmpeg提供的api就可以完成解封装和解码的操作了。
至于视频帧的渲染及音频帧的播放,ffmpeg就无能为力了,因此需要借助类似sdl库等其他第三方组件来完成。
这里讲述如何使用sdl库完成视频帧的渲染,sdl在底层封装了opengl图形库,sdl提供的api简化了opengl的绘图操作,为开发者提供了很多便利的操作,当然,你也可以采用其他系统支持的图形库来绘制视频帧。
1.1 渲染环境搭建
一个视频帧在显示前,需要准备一个用于显示视频的窗口对象,以及附着在窗口上的画布对象
创建SDL窗口,并指定图像尺寸及像素个数
// 创建SDL窗口,并指定图像尺寸
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
创建画布对象
// 创建画布对象
bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen);
1.2 视频帧渲染
在窗口和画布对象创建完成后,就可以开始视频帧的渲染显示了。
在对画布对象操作前,需要对其加线程锁保护,避免其他线程对画布中的内容进行竞争性访问(后面的内容很快会涉及到多线程环境的开发)。对线程操作不熟悉的同学可以了解一下在多线程环境下,多个线程对临界区资源的竞争性访问与线程同步操作。
SDL_LockYUVOverlay(bmp);//locks the overlay for direct access to pixel data
向画布注入解码后的视频帧
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pict.data, pict.linesize);
在画布对象的视频帧填充操作完成后,释放sdl线程锁。
//Unlocks a previously locked overlay. An overlay must be unlocked before it can be displayed
SDL_UnlockYUVOverlay(bmp);
对视频帧的渲染
SDL_DisplayYUVOverlay(bmp, &rect);//图像渲染
可以看到,由于借助了sdl封装的api绘图接口,视频帧的渲染还是非常容易的,如果直接采用opengl绘图,绘制过程会相对复杂些,例程主要的目的是为了介绍ffmpeg的使用,因此,这里采用sdl简化了渲染流程。
1.3 项目源码编译
本例程和上篇文章中用到的编译方法完全一样
tutorial02: tutorial02.c
gcc -o tutorial02 -g3 tutorial02.c -I${FFMPEG_INCLUDE} -I${SDL_INCLUDE} \
-L${FFMPEG_LIB} -lavut