概述
在这里使用的版本是SDL2。实际上SDL本身并不提供视音频播放的功能,它只是封装了视音频播放的底层API。在Windows平台下,SDL封装了Direct3D这类的API用于播放视频;封装了DirectSound这类的API用于播放音频。因为SDL的编写目的就是简化视音频播放的开发难度,所以使用SDL播放视频(YUV/RGB)和音频(PCM)数据非常的容易。
SDL简介
SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。目前SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。用下面这张图可以很明确地说明SDL的用途。
SDL实际上并不限于视音频的播放,它将功能分成下列数个子系统(subsystem):
Video(图像):图像控制以及线程(thread)和事件管理(event)。
Audio(声音):声音控制
Joystick(摇杆):游戏摇杆控制
CD-ROM(光盘驱动器):光盘媒体控制
Window Management(视窗管理):与视窗程序设计集成
Event(事件驱动):处理事件驱动
在Windows下,SDL与DirectX的对应关系如下。
SDL | DirectX |
SDL_Video、SDL_Image | DirectDraw、Direct3D |
SDL_Audio、SDL_Mixer | DirectSound |
SDL_Joystick、SDL_Base | DirectInput |
SDL_Net | DirectPlay |
SDL流程图及主要数据结构
就像FFmpeg一样,SDL2也有固定的执行逻辑,其流程图如下所示:
C++代码及解释
class CMySDL2
{
public:
CMySDL2();
~CMySDL2();
int Init(int width, int height);
int UnInit();
void Tick_Render(AVFrame *pFrame);
// 消息处理
void Tick_Event();
private:
int m_nW;
int m_nH;
SDL_Window *m_pWindow;
SDL_Renderer *m_pRenderer;
SDL_Texture *m_pTexture;
SDL_Rect m_sdlRect;
};
int CMySDL2::Init(int width, int height)
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER))
{
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
//SDL 2.0 Support for multiple windows
m_nW = width;
m_nH = height;
m_pWindow = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
m_nW, m_nH, SDL_WINDOW_OPENGL);
if (!m_pWindow)
{
printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
return -1;
}
m_pRenderer = SDL_CreateRenderer(m_pWindow, -1, 0);
m_pTexture = SDL_CreateTexture(m_pRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, m_nW, m_nH);
m_sdlRect.x = 0;
m_sdlRect.y = 0;
m_sdlRect.w = m_nW;
m_sdlRect.h = m_nH;
return 0;
}
int CMySDL2::UnInit()
{
SDL_Quit();
return 0;
}
void CMySDL2::Tick_Render(AVFrame *pFrame)
{
SDL_UpdateTexture(m_pTexture, NULL, pFrame->data[0], m_nW);
m_sdlRect.x = 0;
m_sdlRect.y = 0;
m_sdlRect.w = m_nW;
m_sdlRect.h = m_nH;
SDL_RenderClear(m_pRenderer);
//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
SDL_RenderCopy(m_pRenderer, m_pTexture, NULL, &m_sdlRect);
SDL_RenderPresent(m_pRenderer);
}
void CMySDL2::Tick_Event()
{
static SDL_Event event;
SDL_PollEvent(&event);
switch (event.type) {
case SDL_QUIT:
SDL_Quit();
exit(0);
break;
default:
break;
}
}
// 函数 函数调用
int main(int argc, char **argv)
{
my::InitFFmpegLibrary();
CMyFFmpeg my;
my.Init("video\\yu_15s.mp4");
my.Log_StructInfo();
CMyDecoder dec;
dec.Init(&my, AV_PIX_FMT_YUV420P);
AVFrame *pFrame;
CMySDL2 mySDL;
mySDL.Init(my.GetWidth(), my.GetHeight());
std::ofstream ofile;
ofile.open("my.yuv", std::ios::binary);
while (true)
{
pFrame = dec.GetFrame();
if (pFrame)
{
mySDL.Tick_Render(pFrame);
}
mySDL.Tick_Event();
Sleep(10);
}
mySDL.UnInit();
return 0;
}
总结
最后总结下编写代码中遇到的两个问题:SDL绘图崩溃、视频花屏。相关代码可以查看GIT的历史记录。
SDL绘图崩溃:avcodec_decode_video2解码出来的数据必须sws_scale后才能使用:因为解出来的可能有黑边,长宽不一定就是视频的长宽。
视频花屏:av_frame_unref(pFrame);只能在程序运行结束后调用。可以查看CMyDecoder的Init和Uninit相关代码。