本文参考了100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)
看完这两片文章后觉得可以不用SDL,也可以实现一个播放器。
只要将帧读出解码成bmp后就可以直接显示在mfc的对话框上。
实现如下:
开发平台:vs2013
新建一个mfc对话框工程,叫做ASimplePlayer
到对话框里拉一个按钮,用默认的参数就行。
双击button1,vs2013会自动关联到对话框的类CASimplePlayerDlg。
在CASimplePlayerDlg里,增加一个函数,int MainFun();在里面加上如下代码
int CASimplePlayerDlg::MainFun(){
AVFormatContext * pFormatCtx;
int i, videoStream, screen_w, screen_h, PictureSize, ret, got_picture, frameFinished;
AVCodecContext * pCodecCtx;
AVCodec * pCodec;
AVFrame * pFrame, *pFrameRGB;
AVPacket packet;
struct SwsContext * pSwsCtx;
FILE * fp_yuv;
uint8_t* OutBuff;
char filepath[] = "bigbuckbunny_480x272.h265";
//char filepath[] = "1.mp4";
//char filepath[] = "test.wmv";
av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();
//打开视频文件
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0){
MessageBox(L"cannot open file",L"错误",MB_OK);
return -1;
}
//发现视频流上下文
if (avformat_find_stream_info(pFormatCtx, NULL) < 0){
MessageBox(L"cannot find stream info\n", L"错误", MB_OK);
return -1;
}
//在视频流群中找到类型为video的视频流
videoStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
videoStream = i;
break;
}
}
if (videoStream == -1){
MessageBox(L"codec not found",L"错误",MB_OK);
return -1;
}
//在video视频流中找到编解码器上下文
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
//根据编解码器上下文找到编解码器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL){
MessageBox(L"codec not found\n",L"错误",MB_OK);
return -1;
}
//打开编解码器
avcodec_open2(pCodecCtx, pCodec, NULL);
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
PictureSize = avpicture_get_size(PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
OutBuff = (uint8_t*)av_malloc(PictureSize);
//填充帧数据
avpicture_fill((AVPicture *)pFrameRGB, OutBuff, PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
//设置图像转换上下文
pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height,
PIX_FMT_BGR24,
SWS_BICUBIC, NULL, NULL, NULL);
m_iWid = pCodecCtx->width;
m_iHei = pCodecCtx->height;
int iRet = 0;
//读入一个包
while (av_read_frame(pFormatCtx, &packet) >= 0){
if (packet.stream_index == videoStream){
//解码
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if (frameFinished){
//反转图像 ,否则生成的图像是上下调到的
pFrame->data[0] += pFrame->linesize[0] * (pCodecCtx->height - 1);
pFrame->linesize[0] *= -1;
pFrame->data[1] += pFrame->linesize[1] * (pCodecCtx->height / 2 - 1);
pFrame->linesize[1] *= -1;
pFrame->data[2] += pFrame->linesize[2] * (pCodecCtx->height / 2 - 1);
pFrame->linesize[2] *= -1;
//转换图像格式,将解压出来的YUV420P的图像转换为BRG24的图像
iRet = sws_scale(pSwsCtx, pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pFrameRGB->data, pFrameRGB->linesize);
//SaveAsBMP(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i++, 24);
ShowInDlg(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i++, 24);
Sleep(40);
}
av_free_packet(&packet);
}
}
sws_freeContext(pSwsCtx);
av_free(pFrame);
av_free(pFrameRGB);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}
在MainFun中,当把帧解压出来后,要用ShowInDlg来将图片显示在对话框上
实现如下:
int CSimplestFfmpegDlgDlg::ShowInDlg(AVFrame *pFrameRGB, int width, int height, int index, int bpp){
char buf[5] = { 0 };
BITMAPFILEHEADER bmpheader;
BITMAPINFOHEADER bmpinfo;
FILE *fp;
bmpheader.bfType = 0x4d42;
bmpheader.bfReserved1 = 0;
bmpheader.bfReserved2 = 0;
bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp / 8;
bmpinfo.biSize = sizeof(BITMAPINFOHEADER);
bmpinfo.biWidth = width;
bmpinfo.biHeight = height;
bmpinfo.biPlanes = 1;
bmpinfo.biBitCount = bpp;
bmpinfo.biCompression = BI_RGB;
bmpinfo.biSizeImage = (width*bpp + 31) / 32 * 4 * height;
bmpinfo.biXPelsPerMeter = 100;
bmpinfo.biYPelsPerMeter = 100;
bmpinfo.biClrUsed = 0;
bmpinfo.biClrImportant = 0;
if (m_hbitmap)
::DeleteObject(m_hbitmap);
CClientDC dc(NULL);
m_hbitmap = CreateDIBitmap(dc.GetSafeHdc(), //设备上下文的句柄
(LPBITMAPINFOHEADER)&bmpinfo, //位图信息头指针
(long)CBM_INIT, //初始化标志
pFrameRGB->data[0], //初始化数据指针
(LPBITMAPINFO)&bmpinfo, //位图信息指针
DIB_RGB_COLORS);
CRect rt(0, 0, width, height);
InvalidateRect(&rt, FALSE);
//Invalidate();
return 0;
}
在ShowInDlg中,调用Invalidate后,将会响应OnPaint,还要在其中加上如下代码
void CSimplestFfmpegDlgDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CPaintDC dc(this); // 用于绘制的设备上下文
if (m_hbitmap)
{
CRect rcClient;
GetClientRect(rcClient);
CDC memDc;
memDc.CreateCompatibleDC(&dc);
memDc.SelectObject(m_hbitmap);
SetStretchBltMode(dc.m_hDC, HALFTONE);
dc.StretchBlt(rcClient.left, rcClient.top, m_iWid, m_iHei, &memDc, 0, 0, m_iWid, m_iHei, SRCCOPY);
memDc.DeleteDC();
}
}
}
不过只是编译成功还不够,还要添加按钮响应。在OnBnClickedButton1中添加一个线程,代码如下:
DWORD WINAPI ShowVideoThread(LPVOID param){
CASimplePlayerDlg * pClass = (CASimplePlayerDlg*)param;
pClass->MainFun();
return 0;
}
void CASimplePlayerDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
DWORD dwId;
CreateThread(NULL, NULL, ShowVideoThread, (LPVOID)this, NULL, &dwId);
}
这样点击按钮就会去调用MainFum了。只有用了线程,在显示视频时,才不会卡顿。
对于要显示的视频,笔者是直接写死在MainFun中的filepath变量中了。只要改变该变量,或者设置一个对话框来选择要显示的视频,就可以显示不同的视频。经测试,该程序可以显示h264,wmp,mp4三种格式的视频,其他格式的未测试。
如果出现“无法解析的外部符号”的错,是lib库的设置没弄好。
在附加依赖项中添加“avcodec.lib;avformat.lib;avutil.lib;avdevice.lib;avfilter.lib;postproc.lib;swresample.lib;swscale.lib;”
vs2013就会帮我们去链接相关库
如果按本文所述未能实现播放器的,可以直接参考样例工程:
http://download.csdn.net/detail/sspdfn/9828826