ffmpeg(4.0.2)解码H264

版权声明:凡本人原创,转发请注明出处,谢谢! https://blog.csdn.net/qq_41248872/article/details/83544825

接口变更:

AVStream的codec参数被codecpar参数所替代

AVCodecContext *codec变为AVCodecParameters *codecpar

av_register_all被弃用

添加av_demuxer_iterate()

const AVInputFormat *av_demuxer_iterate(void **opaque);

解码接口变更:

视频解码接口avcodec_decode_video2和avcodec_decode_audio4音频解码deprecated,两个接口做了合并,使用统一的接口。将音视频解码步骤分为两步:

第一步:avcodec_send_packet()    发送编码数据包

第二步:avcodec_receive_frame()  接收解码后数据

函数原型:

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

参数:

AVCodecContext *avctx:视频解码的上下文,包含解码器。

const AVPacket *avpkt: 编码的音视频帧数据

返回值:

成功返回0

为什么要传递空的avpkt?

这里有一个说明是可以传递NULL,什么情况下需要传递NULL,你平时看一些视频播放器,播放经常会少最后几帧,很多情况就是因为没有处理好缓冲帧的问题,ffmpeg内部会缓冲几帧,要想取出来就需要传递空的AVPacket进去。

函数原型:

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

参数:

AVCodecContext *avctx:视频解码的上下文,包含解码器。

AVFrame *frame:解码后的视频帧数据。

返回值:

成功返回0

*******空间申请和释放问题*********

     解码后图像空间由函数内部申请,你所做的只需要分配 AVFrame 对象空间,如果你每次调用avcodec_receive_frame传递同一个对象,接口内部会判断空间是否已经分配,如果没有分配会在函数内部分配。

avcodec_send_packet和avcodec_receive_frame调用关系并不一定是一对一的,比如一些音频数据一个AVPacket中包含了1秒钟的音频,调用一次avcodec_send_packet之后,可能需要调用25次 avcodec_receive_frame才能获取全部的解码音频数据,所以要做如下处理:

int ret = avcodec_send_packet(codec, pkt);

if (ret != 0)

{

    return;

}

while( avcodec_receive_frame(codec, frame) == 0)

{

    //读取到一帧音频或者视频

    //处理解码后音视频 frame

}
 

 源代码:

/**基于雷神(雷霄骅)博文修改
 * 基于FFmpeg的视频解码器
 * Simplest FFmpeg Decoder Pure
 *
 * 本程序实现了视频码流H.264解码为YUV数据。
 * 通过学习本例子可以了解FFmpeg的解码流程。
 */
 
#include <stdio.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
 
#define __STDC_CONSTANT_MACROS
 

 
int main(int argc, char* argv[])
{
	AVCodec *pCodec;
    
	const int in_buffer_size=4096;
	uint8_t in_buffer[4096 + 460800]={0};
	uint8_t *cur_ptr;
	int cur_size;
	int ret,i,got_picture;
	int y_size;
    AVFrame	*pFrame;     //AVFrame存储一帧解码后的像素数据
    AVPacket packet;    //存储一帧(一般情况下)压缩编码数据
 
	enum AVCodecID codec_id=AV_CODEC_ID_H264;
	char filepath_in[]="yuv_640x480.h264";
	char filepath_out[]="decode_yuv420p_640x480.yuv";
	int first_time=1;
 	
	//注册所有的编解码器
	void *opaque = NULL;
	av_demuxer_iterate(&opaque);
	//av_register_all();  被弃用
	
	//打开多媒体文件
	AVFormatContext *pFormatCtx = NULL;
	//为AVFormatContext分配内存
	pFormatCtx=avformat_alloc_context();
	if(avformat_open_input(&pFormatCtx, filepath_in, NULL, NULL) != 0){
		return -1;   // Couldn't open file
	}
	//独立的解码上下文
	//AVCodecContext视频解码的上下文,为AVCodecContext分配内存
	AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx){
        printf("Could not allocate video codec context\n");
        return -1;
    }

	//循环遍历所有流,找到视频流
	int videoStream = -1; 
	for(i = 0; i < pFormatCtx->nb_streams; i++) { 
	if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { 
			videoStream = i; 
			break; 
		} 
	}

	//将配置参数复制到AVCodecContext中
	avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar);

	//查找视频解码器
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);  
    if (!pCodec) {
        printf("Codec not found\n");
        return -1;
    }

    //打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec\n");
        return -1;
    }
    	
	//初始化AVCodecParserContext
	AVCodecParserContext *pCodecParserCtx=NULL;
	pCodecParserCtx=av_parser_init(codec_id);
	if (!pCodecParserCtx){
		printf("Could not allocate video parser context\n");
		return -1;
	}

	//Input File
    FILE *fp_in = fopen(filepath_in, "rb");
    if (!fp_in) {
        printf("Could not open input stream\n");
        return -1;
    }

	//Output File
	FILE *fp_out = fopen(filepath_out, "wb");
	if (!fp_out) {
		printf("Could not open output YUV file\n");
		return -1;
	}
	
	//为AVFrame分配内存
    pFrame = av_frame_alloc();
	//初始化AVPacket
	av_init_packet(&packet);
 
	int n_frame=0;
	while (1) {
        cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);
        if (cur_size == 0)
            break;
        cur_ptr=in_buffer;
 
        while (cur_size>0){
 
            //解析获得一个Packet
			int len = av_parser_parse2(
				pCodecParserCtx, pCodecCtx,
				&packet.data, &packet.size,
				cur_ptr , cur_size ,
				AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
 
			cur_ptr += len;
			cur_size -= len;
 
			if(packet.size==0)
				continue;
 
			//Some Info from AVCodecParserContext
			printf("[Packet]Size:%6d\t",packet.size);
			switch(pCodecParserCtx->pict_type){
				case AV_PICTURE_TYPE_I: printf("Type:I\t");break;
				case AV_PICTURE_TYPE_P: printf("Type:P\t");break;
				case AV_PICTURE_TYPE_B: printf("Type:B\t");break;
				default: printf("Type:Other\t");break;
			}
			printf("Number:%4d\n",pCodecParserCtx->output_picture_number);

			//解码一帧数据
			//avcodec_send_packet()向解码器提供输入AVPacket
			ret = avcodec_send_packet(pCodecCtx,  &packet); 
			if (ret != 0)
			{
				return;
			}
            //avcodec_receive_frame()接收解码的帧AVFrame
			while( avcodec_receive_frame(pCodecCtx, pFrame) == 0)
			{
				got_picture=1;
				if(got_picture){
					//读取到一帧视频,处理解码后视频frame
					if(first_time){
						printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);
						printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);
						first_time=0;
					}
					//Y, U, V
					int i;
					for(i=0;i<pFrame->height;i++){
						fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
					}
					for(i=0;i<pFrame->height/2;i++){
						fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
					}
					for(i=0;i<pFrame->height/2;i++){
						fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);
					}

					n_frame++;
					printf("Succeed to decode %d frame!\n",n_frame);			
				}
			}
		}
 
    }
 
	//Flush Decoder
    packet.data = NULL;
    packet.size = 0;
	while(1){
		ret = avcodec_receive_frame(pCodecCtx, pFrame);
		if (ret < 0) {
			printf("Decode Error.\n");
			return ret;
		}
		if (!got_picture){
			break;
		}else {
			//Y, U, V
			int i;
			for(i=0;i<pFrame->height;i++){
				fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
			}
			for(i=0;i<pFrame->height/2;i++){
				fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
			}
			for(i=0;i<pFrame->height/2;i++){
				fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);
			}
 
			printf("Flush Decoder: Succeed to decode 1 frame!\n");
		}
	}
 
    fclose(fp_in);
	fclose(fp_out);
 
 
	//释放AVFormatContext和它所有的流
    avformat_free_context(pFormatCtx);

	av_parser_close(pCodecParserCtx);


	av_frame_free(&pFrame);
	avcodec_close(pCodecCtx);
	av_free(pCodecCtx);
 
	return 0;
}

解码出来,yuv文件大小同压缩前一致,代表解压成功。但是发现最后一帧丢失,代码中已做了缓冲刷新,具体原因还未查清。待有时间后续再改进,先做记录。

猜你喜欢

转载自blog.csdn.net/qq_41248872/article/details/83544825