C++ FFmpeg4.1版本东京热初体验
我的目的就是通过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;
}
代码不够完善,但是功能已经实现;大家可以拓展;暂停功能就是停止解码,加一个变量控制就可以,停止就是把解码线程停止,也是加一个变量进行控制;