C++ FFmpeg4.1版本东京热初体验

我的目的就是通过FFmpeg来对h264文件进行解码播放;

开始骚操作;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

第一步,打开http://ffmpeg.org/

找到Download

C++ FFmpeg4.1版本东京热初体验
找到Windows Builds,点击进入

C++ FFmpeg4.1版本东京热初体验
需要下载两个版本(如果你编写的程序是32位程序就下载32位的,如果你程序是64位的就要下载64位的)

1、Shared(dll文件)

2、Dev(包含了头文件、lib、示例代码)

C++ FFmpeg4.1版本东京热初体验
下载完后,等待被使用;

第二步,新建MFC程序,然后运行一下(生成debug文件夹);

从刚刚下载的FFmpeg-dev里面拷贝lib和include文件夹到项目路径(我放在cpp文件的上一层目录,这个看你自己对项目架构的设计)

C++ FFmpeg4.1版本东京热初体验
目目录的debug文件夹

C++ FFmpeg4.1版本东京热初体验
一共有8个dll文件,注意目录;其实这个目录就是exe所在的目录;

第三步,配置FFmpeg

1、配置include

右键项目属性,配置属性->C/C++->常规->附加包含目录->键入..include(因为我的刚刚拷贝include目录的时候,我放在cpp文件的上一层,所以是.include)

2、配置lib

右键项目属性,配置属性->链接器->常规->附加库目录->..lib(因为我的刚刚拷贝lib目录的时候,我放在cpp文件的上一层,所以是.lib)

3、配置lib文件

右键项目属性,配置属性->链接器->输入->附加依赖项->输入所有FFmpeg lib目录下的lib文件名

C++ FFmpeg4.1版本东京热初体验
打开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")来链接。

然后编译运行测试一下,编译时有什么错误就百度解决一下;没有错误就最好了,祝顺利。

第四步,拖界面

因为之前看过雷神的教程,所以界面就按这个拖出来的;

C++ FFmpeg4.1版本东京热初体验

C++ FFmpeg4.1版本东京热初体验
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/,然后下载文件

C++ FFmpeg4.1版本东京热初体验
配置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...