基于 ffmpeg 的解码实现

本文实现了将视频文件解码成YUV文件和音频裸流PCM文件

解码过程:
1、打开音视频文件:avformat_open_input()
2、获取流信息:avformat_find_stream_info()
3、寻找解码器:avcodec_find_decoder()
4、打开解码器:avcodec_open2()
5、循环读取数据包:av_read_frame()
6、解码数据包:avcodec_send_packet()、avcodec_receive_frame()

解码视频流:

创建上下文环境结构体 AVFormatContext,打开视频文件,并获取流信息更新AVFormatContext

    AVFormatContext* afc = avformat_alloc_context();
	if (avformat_open_input(&afc, infile, NULL, NULL) != 0)
	{
		cout << "open video error " << endl;
		return -1;
	}
	if (avformat_find_stream_info(afc, NULL) < 0)
	{
		cout << "find stream error" << endl;
		return -1;
	}

寻找视频流解码器并打开解码器。必须要调用avcodec_parameters_to_context()函数更新解码器上下文的信息,否则解码的时候会出错:picture size 0x0 is invalid

for (int i = 0; i < afc->nb_streams; ++i)
	{
		if (afc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)  //视频
		{
			videoflag = i;
			vdecodec = avcodec_find_decoder(afc->streams[videoflag]->codecpar->codec_id);
			vcc = avcodec_alloc_context3(vdecodec);  //为解码器上下文创建内存并初始化
			avcodec_parameters_to_context(vcc, afc->streams[i]->codecpar); //将流里面的数据更新到解码器上下文
			if (avcodec_open2(vcc, vdecodec, NULL) < 0)
			{
				cout << "open video decodec error" << endl;
				return -1;
			}
		}

 循环读取数据包,并解码。注意这里的avcodec_send_packet()和avcodec_receive_frame()函数不是一对一的关系

int decodec_one_frame_video(AVCodecContext* codec_ctx, AVPacket* pkt, AVFrame* frame, FILE* output)
{
	AVFrame* yuv = av_frame_alloc();
	unsigned char* out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1));
	av_image_fill_arrays(yuv->data, yuv->linesize, out_buffer, AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1); //填充yuv像素数据缓冲区,没有这步会导致sws_scale()失败

	SwsContext* sws = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
		codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
		SWS_BICUBIC, NULL, NULL, NULL);

	int ret = 0;
	if (ret = avcodec_send_packet(codec_ctx, pkt) != 0)  //向解码器发送视频数据包
	{
		cout << "send video packet to decodec error, code:" << ret << endl;
		return -1;
	}
	while (ret >= 0)
	{
		ret = avcodec_receive_frame(codec_ctx, frame);  //接收解码后的数据帧
		if (ret == 0)
		{
			sws_scale(sws, frame->data, frame->linesize, 0, codec_ctx->height, yuv->data, yuv->linesize); //将解码后帧的像素格式转换成YUV420
			int size = codec_ctx->width * codec_ctx->height;
			cout << "fmt = " << yuv->nb_samples << endl; //输出0,为AV_PIX_FMT_YUV420P
			fwrite(yuv->data[0], 1, size, output);      //y
			fwrite(yuv->data[1], 1, size / 4, output);  //u
			fwrite(yuv->data[2], 1, size / 4, output);  //v
			cout << "write yuv one frame succeed" << endl;
		}
		else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) 
		{
			cout << "receive video frame  error, need again" << endl;
			sws_freeContext(sws);
			av_frame_free(&yuv);
			return -2; //接收到的数据无效 需要重新读入
		}
		else
		{
			cout << "avcodec_receive_frame video error  code:" << ret << endl;
			sws_freeContext(sws);
			av_frame_free(&yuv);
			return -3; //解码错误,可选择直接退出程序
		}
	}
	sws_freeContext(sws);
	av_frame_free(&yuv);
	return 0; //解码成功
}

//主函数调用
while (av_read_frame(afc, pkt) >= 0)
{
	if (pkt->stream_index == videoflag) //视频解码
	{
		decodec_one_frame_video(vcc, pkt, frame, yuvfile);
	}
}
//再次解码输出解码器中的数据,否则会丢失部分数据
decodec_one_frame_video(vcc, NULL, frame, yuvfile);/

avcodec_receive_frame()解码后调用了sws_scale()函数将图片格式转换成YUV420然后再写入文件,在调用sws_scale之前必须用sws_getContext初始化上下文环境,并设置转换时的参数。必须使用av_image_fill_arrays()填充目标图像的缓存区,不然会导致sws_scale()转换失败

unsigned char* out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, 
                            vcc->width, vcc->height, 1));
av_image_fill_arrays(yuv->data, yuv->linesize, out_buffer, AV_PIX_FMT_YUV420P, vcc->width, vcc->height, 1); //填充yuv像素数据缓冲区,没有这步会导致sws_scale()失败

SwsContext* ssc = sws_getContext(vcc->width, vcc->height, vcc->pix_fmt,    
		                        vcc->width, vcc->height, AV_PIX_FMT_YUV420P,
		                        SWS_BICUBIC, NULL, NULL, NULL);

音频解码和视频类似,如下的demo中实现了将视频文件解码成YUV文件和音频裸流PCM文件

demo:

/*
* 解码成yvu和pcm
*/
#include <iostream>
#include <cstdio>
using namespace std;

extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
const char* infile = "1.mp4";
const char* yuvout = "out.yuv"; //输出纯视频流文件
const char* pcmout = "out.pcm"; //输出纯音频流文件

int decodec_one_frame_video(AVCodecContext* codec_ctx, AVPacket* pkt, AVFrame* frame, FILE* output)
{
	AVFrame* yuv = av_frame_alloc();
	unsigned char* out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1));
	av_image_fill_arrays(yuv->data, yuv->linesize, out_buffer, AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1); //填充yuv像素数据缓冲区,没有这步会导致sws_scale()失败

	SwsContext* sws = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
		codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
		SWS_BICUBIC, NULL, NULL, NULL);

	int ret = 0;
	if (ret = avcodec_send_packet(codec_ctx, pkt) != 0)  //向解码器发送视频数据包
	{
		cout << "send video packet to decodec error, code:" << ret << endl;
		return -1;
	}
	while (ret >= 0)
	{
		ret = avcodec_receive_frame(codec_ctx, frame);  //接收解码后的数据帧
		if (ret == 0)
		{
			sws_scale(sws, frame->data, frame->linesize, 0, codec_ctx->height, yuv->data, yuv->linesize); //将解码后帧的像素格式转换成YUV420
			int size = codec_ctx->width * codec_ctx->height;
			cout << "fmt = " << yuv->nb_samples << endl; //输出0,为AV_PIX_FMT_YUV420P
			fwrite(yuv->data[0], 1, size, output);      //y
			fwrite(yuv->data[1], 1, size / 4, output);  //u
			fwrite(yuv->data[2], 1, size / 4, output);  //v
			cout << "write yuv one frame succeed" << endl;
		}
		else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) //接收到的数据无效 需要重新读入
		{
			cout << "receive video frame  error, need again" << endl;
			sws_freeContext(sws);
			av_frame_free(&yuv);
			return -2;
		}
		else
		{
			cout << "avcodec_receive_frame video error  code:" << ret << endl;
			sws_freeContext(sws);
			av_frame_free(&yuv);
			return -3;
		}
	}
	sws_freeContext(sws);
	av_frame_free(&yuv);
	return 0;
}

int decodec_one_frame_audio(AVCodecContext *codec_ctx, AVPacket *pkt, AVFrame *frame, FILE* output)
{
	int ret = 0;
	cout << "frame fix = " << frame->format << endl;
	if (ret = avcodec_send_packet(codec_ctx, pkt) != 0)
	{
		cout << "send audio packet to decodec error" << endl;
		return -1;
	}
	while(ret >= 0)
	{
		ret = avcodec_receive_frame(codec_ctx, frame);
		if (ret == 0)
		{
			int audio_buffersize = av_samples_get_buffer_size(NULL, codec_ctx->channels, codec_ctx->frame_size, AV_SAMPLE_FMT_S16P, 1); //获取音频数据大小
			cout << "channels = " << codec_ctx->channels << "rate = " << codec_ctx->sample_rate  << "sample_fmt = " << codec_ctx->sample_fmt << endl; //输出音频的通道数、采样率和采样格式(这里输出8,为AV_SAMPLE_FMT_FLTP)
			fwrite(frame->data, 1, audio_buffersize,output);
			cout << "write pcm one frame succeed" << endl;
		}
		else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) 
		{
			cout << "receive audio frame  error, need again" << endl;
			return -2; //接收到的数据无效 需要重新读入
		}
		else
		{
			cout << "avcodec_receive_frame audio error  code:" << ret << endl;
			return -3; //解码错误,可选择直接退出程序
		}
	}
	return 0; //解码成功
}

int main(int argc, char** argv)
{
	AVFormatContext* afc = avformat_alloc_context(); //初始化上下文结构体AVFormatContext
	if (avformat_open_input(&afc, infile, NULL, NULL) != 0)
	{
		cout << "open video error " << endl;
		return -1;
	}
	if (avformat_find_stream_info(afc, NULL) < 0) //获取流信息,更新上下文结构AVFormatContext
	{
		cout << "find stream error" << endl;
		return -1;
	}
	int videoflag = -1;
	int audioflag = -1;
	AVCodec* vdecodec, * adecodec;
	AVCodecContext* vcc = NULL;
	AVCodecContext* acc = NULL;
	for (int i = 0; i < afc->nb_streams; ++i)
	{
		if (afc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)  //视频
		{
			videoflag = i;
			vdecodec = avcodec_find_decoder(afc->streams[videoflag]->codecpar->codec_id);
			vcc = avcodec_alloc_context3(vdecodec);  //为解码器创建内存
			avcodec_parameters_to_context(vcc, afc->streams[i]->codecpar); //更新解码器参数
			if (avcodec_open2(vcc, vdecodec, NULL) < 0)
			{
				cout << "open video decodec error" << endl;
				return -1;
			}
		}
		else if (afc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)  //音频
		{
			audioflag = i;
			adecodec = avcodec_find_decoder(afc->streams[audioflag]->codecpar->codec_id);
			acc = avcodec_alloc_context3(adecodec);
			avcodec_parameters_to_context(acc, afc->streams[i]->codecpar);
			if (avcodec_open2(acc, adecodec, NULL) < 0)
			{
				cout << "open audio decodec error" << endl;
				return -1;
			}
		}
	}
	if (videoflag == -1)
	{
		cout << "video stream not find" << endl;
		return -1;
	}
	if (audioflag == -1)
	{
		cout << "audio stream not find" << endl;
		return -1;
	}

	AVFrame* frame = av_frame_alloc();//视频解码后的frame
	AVFrame* aframe = av_frame_alloc(); //音频解码后的frame
	AVPacket* pkt = (AVPacket*)av_malloc(sizeof(AVPacket));
	FILE* yuvfile = fopen(yuvout, "wb");
	FILE* pcmfile = fopen(pcmout, "wb");
	
	while (av_read_frame(afc, pkt) >= 0)
	{
		if (pkt->stream_index == videoflag) //视频解码
		{
			decodec_one_frame_video(vcc, pkt, frame, yuvfile);
		}
		else if (pkt->stream_index == audioflag) //音频解码
		{
			decodec_one_frame_audio(acc, pkt, aframe, pcmfile);
		}
	}
	//再次解码输出解码器中的数据,否则会丢失部分数据
	decodec_one_frame_video(vcc, NULL, frame, yuvfile);
	decodec_one_frame_audio(acc, NULL, aframe, pcmfile);
	
	//释放资源
	fclose(yuvfile);
	fclose(pcmfile);
	av_packet_unref(pkt);
	av_frame_free(&aframe);
	av_frame_free(&frame);
	avcodec_free_context(&vcc);
	avcodec_free_context(&acc);
	avformat_close_input(&afc);
	return 0;
}
 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值