本文参考了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