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文件的上一层目录,这个看你自己对项目架构的设计)

再拷贝FFmpeg-shared版本下bin目录的所有dll文件到项目目录的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;
}

代码不够完善,但是功能已经实现;大家可以拓展;暂停功能就是停止解码,加一个变量控制就可以,停止就是把解码线程停止,也是加一个变量进行控制;

猜你喜欢

转载自blog.csdn.net/dong923700243/article/details/86518955