环境
背景:在系统性学习FFmpeg时,发现官方推荐教程还是15年的,不少接口已经弃用,大版本也升了一级,所以在这里记录下FFmpeg4.0 SDL2.0的学习过程。
win10,VS2019,FFmpeg4.3.2,SDL2.0.14
原文地址:http://dranger.com/ffmpeg/tutorial02.html
SDL与Video
为了播放视频,我们将使用SDL库。SDL即Simple Direct Layer,它是非常优秀且跨平台的多媒体库,不少项目都有所使用。
SDL可以渲染多种图像格式,其中包括YUV格式。粗略讲一下YUV,与RGB类似,YUV是储存原始图像的一种格式,Y是亮度(luma),UV是色度。
SDL2.0现在支持7种YUV格式,包括ffmpeg解码出的YUV420P格式。420表示图像按4:2:0的比例采样,也就是每4个亮度样本(Y)对应1个色度样本(UV),总共6字节,相比RGB24需要4*3=12字节,YUV节省了一半带宽,并且人眼感知不到其中的差别。P表示YUV数据按planar格式存储——即Y,U,V分别存放在3个数组里。
首先来看看如何使用SDL库。第一步,初始化,注意main函数必须写成SDL库要求的形式int main(int argc, char* argv[]),否则编译不通过。另外SDL1.0里不少接口和组件已被弃用,参考:https://wiki.libsdl.org/MigrationGuide#SDL_1.2_to_2.0_Migration_Guide
代码语言:javascript复制extern "C" {
#include <SDL.h>
#include <SDL_thread.h>
}
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
cout << "SDL_Init failed: " << SDL_GetError() << endl;
return -1;
}
}
创建窗口
SDL1.0里的SDL_SetVideoMode已被弃用,这里我们使用SDL_Window,SDL_Renderer和SDL_Texture,SDL_CreateWindow会创建对应宽高的窗口,SDL_CreateRenderer创建该窗口的渲染器,SDL_CreateTexture创建渲染器的Texture好让我们放置YUV数据。
代码语言:javascript复制 SDL_Window* pScreen;
SDL_Renderer* pRenderer;
SDL_Texture* pTexture;
//创建窗口
pScreen = SDL_CreateWindow("SDLPlayer",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
pVideoCodecCtx->width,
pVideoCodecCtx->height,
0);
if (pScreen == nullptr) {
cout << "SDL_CreateWindow failed:" << SDL_GetError() << endl;
return -1;
}
//创建渲染Context
pRenderer = SDL_CreateRenderer(pScreen, -1, 0);
if (pRenderer == nullptr) {
cout << "SDL_CreateRenderer failed:" << SDL_GetError() << endl;
return -1;
}
//初始化成白色
SDL_SetRenderDrawColor(pRenderer, 255, 255, 255, 255);
SDL_RenderClear(pRenderer);
SDL_RenderPresent(pRenderer);
//创建Render所需的Texture,用于放置YUV数据
pTexture = SDL_CreateTexture(pRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pVideoCodecCtx->width, pVideoCodecCtx->height);
if (pTexture == nullptr) {
cout << "SDL_CreateTexture failed:" << SDL_GetError() << endl;
return -1;
}
渲染图像
渲染图像只需把YUV数据送给SDL_Texture,再送给SDL_Renderer,调用SDL_RenderPresent就行了。
代码语言:javascript复制 int count = 0;
//读取视频包并解码,转换,保存文件
while (av_read_frame(pFormatCtx, &packet) >= 0) {
if (packet.stream_index == iVideoStream) {
avcodec_send_packet(pVideoCodecCtx, &packet);
if (avcodec_receive_frame(pVideoCodecCtx, pFrame) == 0) {
//将YUV更新至texture,然后渲染
SDL_UpdateYUVTexture(pTexture, NULL,
pFrame->data[0], pFrame->linesize[0], //Y
pFrame->data[1], pFrame->linesize[1], //U
pFrame->data[2], pFrame->linesize[2]); //V
SDL_RenderClear(pRenderer);
SDL_RenderCopy(pRenderer, pTexture, NULL, NULL);
SDL_RenderPresent(pRenderer);
cout << "presented frame" << count << endl;
}
//取消packet引用的内存,原先的av_free_packet弃用
av_packet_unref(&packet);
}
}
运行程序便看到视频开始播放了!但是有个问题,整个视频像开了超级加速一样,没两下就播完了,后面我们会解决视频同步问题。
代码:https://github.com/onlyandonly/ffmpeg_sdl_player