FFMPEG+SDL实现视频播放器
一. 前言
基于学习ffmpeg和sdl,写一个视频播放器是个不错的练手项目。
视频播放器的原理很多人的博客都有讲过,这里出于自己总结的目的,还是会做一些概况。
二. 视频播放器基本原理

2.1 解封装
视频文件基本上都是将编码好的音频和视频数据封装在一起形成的,因此拿到视频文件的第一步就是先将它解封装,分为视频流和音频流压缩编码数据。常见的封装格式有MP4、MKV、FLV、AVI、RMVB、TS等。例如,FLV格式的文件经过解封装后,可能得到H.264编码的视频码流和AAC编码的音频码流。
在FFMPEG中,解封装的流程如下:

这一步最重要的是得到解封装器的上下文结构体"AVFormatContext *m_pFormatCtx", 以及接下来我们要解码的音视频流索引。
2.2 解码
原始数据基本上都是经过压缩编码后的数据,解码过程就是将H.264、AAC等压缩后的数据解码成非压缩的音频/视频原始数据,视频一般是YUV或者RGB数据,音频一般是PCM抽样数据。
解码过程可以总结如下:

2.3 SDL2播放视频数据
我们都知道视频其实都是由连续的一帧帧图像快速播放形成的动态效果,一般视频都设置成了25帧,即1s内播放25幅图片。
我们使用SDL2库来播放视频。这和我之前的SDL2学习(一): 显示一张图片中写到的SDL2显示一张图片就关联了起来,不过这里更加复杂点。
在视频解码完后,我们在avcodec_receive_frame得到的AVFrame对象,就是视频的一帧数据。我们要做的是将这一帧的数据显示到SDL的Render中。总体流程如下:

首先我们需要对得到的AVFrame数据进行大小格式的变换,这里使用sws_scale函数实现,之后就是更新SDL中的Texture和Render了。下面是关键代码:
AVFrame *frame = m_videoFrameQueue.front();
m_videoFrameQueue.pop();
AVFrame *frameYUV = av_frame_alloc();
int ret = av_image_alloc(frameYUV->data, frameYUV->linesize, m_sdlRect.w, m_sdlRect.h, AV_PIX_FMT_YUV420P, 1);
//Convert image
if (m_imgConvertCtx)
{
sws_scale(m_imgConvertCtx, frame->data, frame->linesize, 0, m_videoCodecParams.height, frameYUV->data, frameYUV->linesize);
SDL_UpdateYUVTexture(m_sdlTexture, NULL, frameYUV->data[0], frameYUV->linesize[0], frameYUV->data[1], frameYUV->linesize[1], frameYUV->data[2], frameYUV->linesize[2]);
SDL_RenderClear(m_sdlRender);
SDL_RenderCopy(m_sdlRender, m_sdlTexture, NULL, &m_sdlRect);
// Present picture
SDL_RenderPresent(m_sdlRender);
}
2.4 SDL2播放音频数据
对于音频数据,avcodec_receive_frame后得到的AVFrame是音频的pcm数据,但是它不向视频那样表示"一帧",它可能包含很多的sample,即多次的采样数据。
播放音频,同样需要对音频数据进行格式转换,以支持音频设备的播放。音频格式转换主要通过swr_convert函数完成。转换后的音频数据可以放到一个公共缓冲区中。
播放音频使用SDL_OpenAudio函数,它需要闯入一个SDL_AudioSpec结构体用于设置播放参数,其中需要设置一个callback用于音频设备取数据时执行,因此我们需要在这个回调里向音频设备"喂"数据:
SDL_AudioSpec m_sdlAudioSpec;
auto audioCtx = m_audioDecoder.GetCodecContext();
m_sdlAudioSpec.freq = audioCtx->sample_rate; //根据你录制的PCM采样率决定
m_sdlAudioSpec.format = AUDIO_S16SYS;
m_sdlAudioSpec.channels = audioCtx->channels;
m_sdlAudioSpec.silence = 0;
m_sdlAudioSpec.samples = SDL_AUDIO_BUFFER_SIZE;
m_sdlAudioSpec.callback = &SDLVideoPlayer::ReadAudioData;
m_sdlAudioSpec.userdata = NULL;
int re = SDL_OpenAudio(&m_sdlAudioSpec, NULL);
if (re < 0)
{
std::cout << "can't open audio: " << GetErrorInfo(re);
}
else
{
//Start play audio
SDL_PauseAudio(0);
}
void SDLVideoPlayer::ReadAudioData(void *udata, Uint8 *stream, int len) {
SDL_memset(stream, 0, len);
//需要向stream中填充len长度的音频数据
...
SDL_MixAudio(stream, m_audioPcmDataBuf, len, g_volum);
}
2.5 音视频同步的设计
用两个线程分别播放音频和视频,音频的话可以直接在所设置的回调中喂数据即可,而视频则需要我们自己来控制播放速度,这就涉及到两者播放速度的统一问题。
音视频同步的基本方式就是确定一个时钟作为主时钟,播放过程中,主时钟作为同步基准,不断判断当前流的播放时间和主时钟的差异,以调节自身的播放速度。按照主时钟的不同种类,可以分为:
- 音频同步到视频,视频时钟作为主时钟;
- 视频同步到音频,音频时钟作为主时钟;
- 音视频都同步到外部时钟。
由于音频播放时往往都是送很多数据到设备缓存中,而且音频播放效果对人的敏感度更高,因此以音频时钟为主是比较合理且简单的办法。具体实现就是:
- 在每次喂音频数据的时候,记录送入数据的起始pts时间戳,表示当前音频的播放进度;
- 每次刷新图片时,记录当前图片帧的pts时间戳;
- 在记录当前音频pts的同时,根据记录的图片pts,记录两者间的延时delay;
- 刷新图片时,根据delay值判断,当前视频如果比音频快,那么一次性调整视频等待时间为正常两帧间隔加音视频之间的延时,之后将delay置0;如果音频比视频快,那么直接丢弃当前的视频帧,直到和音频时间一致。
2.6 快进和快退
快进和快退,或者一些播放器直接拖动进度条,实现思路都是一样的,即使用av_seek_frame实现:
av_seek_frame(m_pFormatCtx, -1, pts * AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);
因此关键就是获取要跳转的时间戳,这个在做音视频同步处理后,这个时间戳就很容易拿到。
2.7 SDL事件处理
对于窗口大小更改、暂停、快进快退等,都是需要交互的,这个可以通过SDL的事件机制来实现。
监听事件:
SDL_Event event;
SDL_WaitEvent(&event);
if (event.type == SDL_WINDOWEVENT) {
...
}
...
除了预定义的事件,比如窗口事件、鼠标事件、按键事件等,你也可以自己触发或定义新的事件:
SDL_Event event;
event.type = SFM_REFRESH_PIC_EVENT;
SDL_PushEvent(&event);
我这里就是使用SDL事件来通知视频播放线程来进行下一帧的播放。
FFMPEG+SDL实现视频播放器的更多相关文章
- 最简单的基于FFMPEG+SDL的视频播放器 ver2 (採用SDL2.0)
===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...
- 最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)
===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...
- 音视频处理之FFmpeg+SDL+MFC视频播放器20180411
一.FFmpeg+SDL+MFC视频播放器 1.MFC知识 1).创建MFC工程的方法 打开VC++ 文件->新建->项目->MFC应用程序 应用程序类型->基于对话框 取消勾 ...
- 基于<最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)>的一些个人总结
最近因为项目接近收尾阶段,所以变的没有之前那么忙了,所以最近重新拿起了之前的一些FFMPEG和SDL的相关流媒体播放器的例子在看. 同时自己也用FFMPEG2.01,SDL2.01结合MFC以及网上罗 ...
- 用JavaCV改写“100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ”
FFMPEG的文档少,JavaCV的文档就更少了.从网上找到这篇100行代码实现最简单的基于FFMPEG+SDL的视频播放器.地址是http://blog.csdn.net/leixiaohua102 ...
- 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】
转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] ...
- 最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器
===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...
- 【转】100行代码实现最简单的基于FFMPEG+SDL的视频播放器
FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我把自己做项目过程中实现的一个非常简单的视频播放器 ...
- FFMPEG学习----使用SDL构建视频播放器
#include <stdio.h> #include <string.h> extern "C" { #include "libavcodec/ ...
随机推荐
- c# 第40节 密封类、密封方法
本节内容: 1:密封的存在意义 2:密封方法的实现 1:密封的存在意义 2:密封方法的实现 class2::父类 class Class2 { public virtual void show() { ...
- QQ第三方登录-python_web开发_django框架
准备工作 1. 成为QQ互联的开发者 参考链接: <http://wiki.connect.qq.com/%E6%88%90%E4%B8%BA%E5%BC%80%E5%8F%91%E8%80%8 ...
- MongoDB图形化工具(三)
一.安装 下载地址:https://www.mongodbmanager.com/download 注意:在下载的时候需要对应上自己安装的mongodb版本. 双击安装 选择“Full install ...
- MySQL 字符集与比较规则
MySQL 字符集与比较规则 由于 MySQL 客户端与服务端之间通信时需要将字符串编码传输,所以不可避免会产生编码转换 字符集 MySQL 中 utf8 就是 utf8mb3,只使用 1-3 个字节 ...
- 10.12 csp-s模拟测试70 木板+打扫卫生+骆驼
T1 木板 求$[\sqrt{n},n)$间有多少个数的平方是n的倍数 通过打表可以发现(我没带脑子我看不出来),符合条件的数构成一个等差数列,公差为首项 而首项就是将n质因数分解后每个质因数出现次数 ...
- 10.1 csp-s模拟测试(b) X国的军队+排列组合+回文
T1 X国的军队 贪心,按$b-a$的大小降序排序,然后就贪心吧 #include<iostream> #include<cstdio> #include<algorit ...
- windows上安装python和python开发工具
一.python安装: 登录python官网,下载需要的安装包:https://www.python.org/downloads/windows/.,一般就下载 executable installe ...
- 这几款我私藏的Markdown编辑器,今天分享给你
相信很多人都使用 Markdown 来编写文章,Markdown 语法简洁,使用起来很是方便,而且各大平台几乎都已支持 Markdown 语法 那么,如何选择一款趁手的 Markdown 编辑器,就是 ...
- 连接树莓派中的MySQL服务器
今天用笔记本连接树莓派的 MySQL ,结果连接不上.就直接连接到树莓派上进行操作.其实以前也知道远程访问 MySQL 需要进行配置,可以直接 mysql.user 表,也可以直接使用授权的 SQL ...
- MAT 4378 – MAT 5317, Analysis of categorical
MAT 4378 – MAT 5317, Analysis of categorical data, Assignment 3 1MAT 4378 – MAT 5317, Analysis of ca ...