我的目的就是通过 FFmpeg 来对 h264 文件进行解码播放;
开始骚操作;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
第一步,打开 http://ffmpeg.org/
找到 Download
找到 Windows Builds,点击进入
需要下载两个版本(如果你编写的程序是 32 位程序就下载 32 位的,如果你程序是 64 位的就要下载 64 位的)
1、Shared(dll 文件)
2、Dev(包含了头文件、lib、示例代码)
下载完后,等待被使用;
第二步,新建 MFC 程序,然后运行一下(生成 debug 文件夹);
从刚刚下载的 FFmpeg-dev 里面拷贝 lib 和 include 文件夹到项目路径(我放在 cpp 文件的上一层目录,这个看你自己对项目架构的设计)
目目录的 debug 文件夹
一共有 8 个 dll 文件,注意目录;其实这个目录就是 exe 所在的目录;
第三步,配置 FFmpeg
1、配置 include
右键项目属性,配置属性 ->C/C++-> 常规 -> 附加包含目录 -> 键入..include(因为我的刚刚拷贝 include 目录的时候,我放在 cpp 文件的上一层,所以是.include)
2、配置 lib
右键项目属性,配置属性 -> 链接器 -> 常规 -> 附加库目录 ->..lib(因为我的刚刚拷贝 lib 目录的时候,我放在 cpp 文件的上一层,所以是.lib)
3、配置 lib 文件
右键项目属性,配置属性 -> 链接器 -> 输入 -> 附加依赖项 -> 输入所有 FFmpeg lib 目录下的 lib 文件名
打开 cpp 文件包含 FFmpeg 头文件
define __STDC_CONSTANT_MACROS
extern “C”{
include <libavcodec/avcodec.h>
include <libavformat/avformat.h>
include <libswscale/swscale.h>
include <libavutil/imgutils.h>
include <libswresample/swresample.h>
}
注 1:#define __STDC_CONSTANT_MACROS 这个需要定义在 include 的前面
注 2:因为已经在第三步第三项中输入了所有 lib,所以不需要通过 #pragma comment(lib,”xxx.lib”) 来链接。
然后编译运行测试一下,编译时有什么错误就百度解决一下;没有错误就最好了,祝顺利。
第四步,拖界面
因为之前看过雷神的教程,所以界面就按这个拖出来的;
File button 为选择文件的按钮,选着文件后,把路径在 Edit Control 控件中显示,可以自行百度如何实现;
第五步,实现代码
按照上面的界面,实现逻辑;
大概逻辑:选择文件路径 -> 点击播放 -> 解码文件 -> 在 Picture control 中显示;
重点我们放在视频解码
点击播放的响应事件
// 点击播放按钮响应时间
void CMFCFFmpeg41Player1Dlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
if(mVideoPath.IsEmpty()){
MessageBox(TEXT(“ 请选择视频文件路径 ”));
return;
}
AfxBeginThread(Thread_Play,this);
}
mVideoPath 是 Edit Control 控件的 Value 关联对象;先判断是否为空,然后消息提示;如果不为空,开启线程进行解码, 并把 this 指针传递给了线程函数;
看 Thread_Play
注:Thread_Play 的定义要遵循特定的格式
UINT Thread_Play(LPVOID lpParam){
Play_H264_File(lpParam);
cout << “ 播放文件线程结束 ” << endl;
return 0;
}
在线程函数中调用了 Play_H264_File 函数;
重点就是 Play_H264_File 函数
我们看看:
AVFormatContext*pFormatCtx; // 解码上下文
AVCodecContext*pCodecCtx; // 解码器上下文
AVCodec*pCodec; // 加码器
AVFrame*pFrame; // 解码后的数据结构体
AVFrame*pFrameYUV; // 解码后的数据再处理的结构体
AVPacket*packet; // 解码前的数据结构体
uint8_t*out_buffer; // 数据缓存
int v_index; // 视频流的轨道下标
int v_size; // 一帧数据的大小
CMFCFFmpeg41Player1Dlg *dlg; //
int dely_time; // 需要暂停的毫秒数
/////////////////////// 先这么解释,结合代码再看 /////////////////////////////
// 先把刚刚传递进来的 this 指针,转换成可调用的对象
dlg = (CMFCFFmpeg41Player1Dlg *)lpParam;
// 获取指定的视频路径
char filepath[250]={0};
GetWindowTextA(dlg->mVideoEdit,(LPSTR)filepath,250); //mVideoEdit 是文件路径对象 Edit Control 控件关联的 Control 关联的对象
pFormatCtx = avformat_alloc_context(); // 获取解码上下文
// 解码上下文关联文件
if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
cout << “ 视频文件打开失败 ” << endl;
return;
}
// 打开文件输入输出流
if(avformat_find_stream_info(pFormatCtx,NULL)<0){
cout << “ 视频文件不可读 ” <<endl;
return;
}
// 打印
av_dump_format(pFormatCtx, -1, filepath, NULL);
// 寻找视频帧的下标(视频文件存在视频、音频、字幕等轨道)
v_index = -1;
for(int i=0;i<pFormatCtx->nb_streams;i++){
if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){
v_index = i;
break;
}
}
// 判断是否存在视频帧
if(v_index < 0){
cout << “ 目标不是视频文件 ” <<endl;
return;
}
// 计算需要 delay 的毫秒数
int fps=pFormatCtx->streams[v_index]->avg_frame_rate.num/pFormatCtx->streams[v_index]->avg_frame_rate.den;// 每秒多少帧
dely_time = 1000/fps;
// 获取解码器上下文对象
pCodecCtx = avcodec_alloc_context3(NULL);
// 根据解码上下文初始化解码器上下文
if (avcodec_parameters_to_context(pCodecCtx , pFormatCtx->streams[v_index]->codecpar) < 0)
{
cout << “ 拷贝解码器数据失败 ” << endl;
return;
}
// 获取解码器对象
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
// 打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) != 0) {
cout << “ 解码器打开失败 ” << endl;
return;
}
// 确认上下文和解码器都没有问题后,申请解码所需要的结构体空间
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
packet = av_packet_alloc();
// 根据 YUV 数据格式,计算解码后图片的大小
v_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
// 申请缓存对象
out_buffer = (uint8_t *)av_malloc(v_size);
// 将自定义缓存空间绑定到输出的 AVFrame 中
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
while (av_read_frame(pFormatCtx, packet) >= 0) {// 读取一个帧数据
if (packet->stream_index == v_index) {// 判断是否是视频帧
if (avcodec_send_packet(pCodecCtx, packet) != 0){// 发送数据进行解码
cout << “ 发送解码数据出错 ” << endl;
return;
}
if (avcodec_receive_frame(pCodecCtx, pFrame) != 0)
{
cout << “ 接受解码数据出错,解码时发生错误 ”;
return;
}
// 解码完成,pFrame 为解码后的数据
}
}
解码的流程,就是这样;但是里面没有说如何显示?还有一些变量没有用?
嗯,接下来,我们使用 SDL 来进行视频数据的显示;
打开 http://www.libsdl.org/,然后下载文件
配置 SDL,跟配置 FFmpge 一样;需要把把头文件、lib、dll 进行配置,参考上面的配置,不在赘述;
在 cpp 中包含 SDL 头文件
define SDL_MAIN_HANDLED
include <SDL2/SDL.h>
include <SDL2/SDL_main.h>
然后加入到 Play_H264_File 函数中使用,我贴完整代码了;
void Play_H264_File(LPVOID lpParam){
////////////////////FFmpeg/////////////////////
AVFormatContext*pFormatCtx;
AVCodecContext*pCodecCtx;
AVCodec*pCodec;
AVFrame*pFrame;
AVFrame*pFrameYUV;
AVPacket*packet;
uint8_t*out_buffer;
int v_index;
int v_size;
CMFCFFmpeg41Player1Dlg *dlg;
int dely_time;
///////////////////////////SDL////////////////////////
SDL_Window *mSDL_Window;
SDL_Renderer *mSDL_Renderer;
SDL_Texture *mSDL_Texture;
struct SwsContext *mSwsContext;
int screenW;
int screenH;
SDL_Rect mSDL_Rect;
dlg = (CMFCFFmpeg41Player1Dlg *)lpParam;
///////////////////////////SDL////////////////////////
if(SDL_Init(SDL_INIT_VIDEO)) {
cout << “SDL 初始化失败 ” <<endl;
return;
}
/////////////////////////FFmpeg///////////////////////////
char filepath[250]={0};
GetWindowTextA(dlg->mVideoEdit,(LPSTR)filepath,250);
pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
cout << “ 视频文件打开失败 ” << endl;
return;
}
if(avformat_find_stream_info(pFormatCtx,NULL)<0){
cout << “ 视频文件不可读 ” <<endl;
return;
}
av_dump_format(pFormatCtx, -1, filepath, NULL);
v_index = -1;
for(int i=0;i<pFormatCtx->nb_streams;i++){
if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){
v_index = i;
break;
}
}
if(v_index < 0){
cout << “ 目标不是视频文件 ” <<endl;
return;
}
int fps=pFormatCtx->streams[v_index]->avg_frame_rate.num/pFormatCtx->streams[v_index]->avg_frame_rate.den;// 每秒多少帧
dely_time = 1000/fps;
cout << “ 视频 FPS = ” << fps << endl;
cout << “dely_time = ” << dely_time << endl;
pCodecCtx = avcodec_alloc_context3(NULL);
//pCodecCtx = pFormatCtx->streams[videoindex]->codec;
if (avcodec_parameters_to_context(pCodecCtx , pFormatCtx->streams[v_index]->codecpar) < 0)
{
cout << “ 拷贝解码器失败 ” << endl;
return;
}
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (avcodec_open2(pCodecCtx, pCodec, NULL) != 0) {
cout << “ 解码器打开失败 ” << endl;
return;
}
//sw = SDL_CreateWindow(“video”, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 680, 540, SDL_WINDOW_OPENGL);
mSDL_Rect.w = pCodecCtx->width;
mSDL_Rect.h = pCodecCtx->height;
mSDL_Window = SDL_CreateWindowFrom(dlg->GetDlgItem(IDC_VIDEO_SURFACE)->GetSafeHwnd());
mSDL_Renderer = SDL_CreateRenderer(mSDL_Window, -1, 0);
mSDL_Texture = SDL_CreateTexture(mSDL_Renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
mSwsContext = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
packet = av_packet_alloc();
v_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
out_buffer = (uint8_t *)av_malloc(v_size);
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
while (av_read_frame(pFormatCtx, packet) >= 0) {
if (packet->stream_index == v_index) {
if (avcodec_send_packet(pCodecCtx, packet) != 0){
cout << “ 发送解码数据出错 ” << endl;
return;
}
if (avcodec_receive_frame(pCodecCtx, pFrame) != 0)
{
cout << “ 接受解码数据出错 ”;
return;
}
sws_scale(mSwsContext, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
SDL_UpdateTexture(mSDL_Texture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
SDL_RenderClear(mSDL_Renderer);
SDL_RenderCopy(mSDL_Renderer, mSDL_Texture, NULL, NULL);
SDL_RenderPresent(mSDL_Renderer);
Sleep(dely_time);
}
}
av_free(out_buffer);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
av_packet_free(&packet);
sws_freeContext(mSwsContext);
SDL_DestroyTexture(mSDL_Texture);
SDL_DestroyRenderer(mSDL_Renderer);
SDL_DestroyWindow(mSDL_Window);
SDL_Quit();
avcodec_free_context(&pCodecCtx);
avformat_close_input(&pFormatCtx);
avformat_free_context(pFormatCtx);
cout << “ 视频播放完成 ” << endl;
return;
}
代码不够完善,但是功能已经实现;大家可以拓展;暂停功能就是停止解码,加一个变量控制就可以,停止就是把解码线程停止,也是加一个变量进行控制;
原文链接:https://blog.csdn.net/dong923…