欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

利用av_parser_parser2函数,解码h264文件

程序员文章站 2022-06-16 19:54:12
1、解码原始的图像数据是非常庞大的,本示例的视频20s,分辨率1280*720,原始的yuv420p数据大概在1.4G,如果是2个小时的电影、分辨率为1920*1080,原始的yuv数据可想而知会有多大,所以无论是音频还是视频数据都会经过编码,以降低多媒体mp4、mov等视频的容量。利用av_parser_parser2解码,输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)。2、解码流程懒的画图了,直接手写...

1、解码

原始的图像数据是非常庞大的,本示例的视频20s,分辨率1280*720,原始的yuv420p数据大概在1.4G,如果是2个小时的电影、分辨率为1920*1080,原始的yuv数据可想而知会有多大,所以无论是音频还是视频数据都会经过编码,以降低多媒体mp4、mov等视频的容量。

利用av_parser_parser2解码,输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)。

2、解码流程

懒的画图了,直接手写了一下

利用av_parser_parser2函数,解码h264文件

同样在windows上和linux上都实现了解码的代码,如果要获取整个工程,直接访问我的github,希望大家给个星星,感谢。

visual studio环境:https://github.com/liupengh3c/goffmpeg

linux环境:https://github.com/liupengh3c/myffmpeg

linux的代码,直接在build目录下,执行:sh build.sh即可编译

3、代码

linux环境下代码:

/*
	本函数以解码h264文件为例,解码为yuv420p
	没有使用av_read_frame函数,输入必须是只包含视频编码数据“裸流”(例如h264、HEVC码流文件)
	而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)。
*/
#define __STDC_CONSTANT_MACROS
#include "iostream"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"

#define INBUF_SIZE 4096
    int decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt, FILE *f)
    {
        int ret = 0;
        ret = avcodec_send_packet(dec_ctx, pkt);
        if (ret < 0)
        {
            std::cout << "error sending a packet for decoding." << std::endl;
            return -1;
        }
        while (ret >= 0)
        {
            ret = avcodec_receive_frame(dec_ctx, frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            {
                return 0;
            }
            else if (ret < 0)
            {
                return -2;
            }
            // 写Y分量
            for (size_t i = 0; i < frame->height; i++)
            {
                fwrite(frame->data[0] + frame->linesize[0] * i, 1, frame->width, f);
            }

            // 写U分量
            for (size_t i = 0; i < frame->height / 2; i++)
            {
                fwrite(frame->data[1] + frame->linesize[1] * i, 1, frame->width / 2, f);
            }

            // 写V分量
            for (size_t i = 0; i < frame->height / 2; i++)
            {
                fwrite(frame->data[2] + frame->linesize[2] * i, 1, frame->width / 2, f);
            }
        }
        return 0;
    }
    int decode_video(std::string input_filename, std::string output_filename)
    {
        int ret = 0;

        AVCodec* codec = NULL;
        AVCodecContext* avcodec_ctx = NULL;
        AVPacket *pkt = NULL;
        AVFrame* frame = NULL;
        // 解析器上下文
        AVCodecParserContext* parser = NULL;

        FILE* f_in = NULL;
        FILE* f_out = NULL;

        uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
        uint8_t* data;
        size_t   data_size;

        // alloc an avpacket && frame
        pkt = av_packet_alloc();
        frame = av_frame_alloc();
        f_out = fopen(output_filename.data(), "wb+");

        // 1、找解码器
        codec = avcodec_find_decoder(AV_CODEC_ID_H264);
        if (!codec)
        {
            std::cout << "codec not found." << std::endl;
            return -1;
        }

        // 2、初始化parser
        parser = av_parser_init(codec->id);
        if (!parser)
        {
            std::cout << "parser not found." << std::endl;
            return -2;
        }

        // 3、分配解码上下文 alloc codec context
        avcodec_ctx = avcodec_alloc_context3(codec);
        if (!avcodec_ctx)
        {
            std::cout << "could not allocate avcodec_ctx." << std::endl;
            return -3;
        }

        // 4、打开解码器
        ret = avcodec_open2(avcodec_ctx, codec, NULL);
        if (ret < 0)
        {
            std::cout << "could not open codec,ret=" << ret << std::endl;
            return -4;
        }

        // 5、打开h264文件
        f_in = fopen(input_filename.data(), "rb");

        // 6、开始解码
        while (!feof(f_in))
        {
            data_size = fread(inbuf, 1, INBUF_SIZE, f_in);
            if (data_size <= 0)
            {
                break;
            }
            data = inbuf;
            while (data_size > 0)
            {
                // 输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)。
                ret = av_parser_parse2(parser, avcodec_ctx, &pkt->data, &pkt->size, data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
                std::cout << "ret=" << ret << std::endl;
                data += ret;
                data_size -= ret;
                
                std::cout << "pkt_size=" << pkt->size << std::endl;
                if (pkt->size)
                {
                    decode(avcodec_ctx, frame, pkt, f_out);
                }
            }
        }

        /* flush the decoder */
        decode(avcodec_ctx, frame, pkt, f_out);

        fclose(f_in);
        fclose(f_out);

        av_parser_close(parser);
        avcodec_free_context(&avcodec_ctx);
        av_frame_free(&frame);
        av_packet_free(&pkt);
        return 0;
    }
}

visual studio环境代码:

#pragma warning(disable : 4996);
#include "DecodeVideo.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}
/*
	本函数以解码h264文件为例,解码为yuv420p
	没有使用av_read_frame函数,输入必须是只包含视频编码数据“裸流”(例如h264、HEVC码流文件)
	而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)。
*/

#define INBUF_SIZE 4096

int DecodeVideo::decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt, FILE *f)
{
	int ret = 0;
	ret = avcodec_send_packet(dec_ctx, pkt);
	if (ret < 0)
	{
		std::cout << "error sending a packet for decoding." << std::endl;
		return -1;
	}
	while (ret >= 0)
	{
		ret = avcodec_receive_frame(dec_ctx, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
		{
			return 0;
		}
		else if (ret < 0)
		{
			return -2;
		}
		// 写Y分量
		for (size_t i = 0; i < frame->height; i++)
		{
			fwrite(frame->data[0] + frame->linesize[0] * i, 1, frame->width, f);
		}

		// 写U分量
		for (size_t i = 0; i < frame->height / 2; i++)
		{
			fwrite(frame->data[1] + frame->linesize[1] * i, 1, frame->width / 2, f);
		}

		// 写V分量
		for (size_t i = 0; i < frame->height / 2; i++)
		{
			fwrite(frame->data[2] + frame->linesize[2] * i, 1, frame->width / 2, f);
		}
	}
	return 0;
}

int DecodeVideo::decode_video(std::string input_filename, std::string output_filename)
{
	int ret = 0;

	AVCodec* codec = NULL;
	AVCodecContext* avcodec_ctx = NULL;
	AVPacket *pkt = NULL;
	AVFrame* frame = NULL;
	// 解析器上下文
	AVCodecParserContext* parser = NULL;

	FILE* f_in = NULL;
	FILE* f_out = NULL;

	uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
	uint8_t* data;
	size_t   data_size;

	//AVFormatContext* fmt_ctx = NULL;

	// alloc an avpacket && frame
	pkt = av_packet_alloc();
	frame = av_frame_alloc();
	f_out = fopen(output_filename.data(), "wb+");

	/*avformat_open_input(&fmt_ctx, input_filename.data(), NULL, NULL);
	avformat_find_stream_info(fmt_ctx, NULL);
	av_dump_format(fmt_ctx, 0, input_filename.data(), 0);*/

	// 1、找解码器
	codec = avcodec_find_decoder(AV_CODEC_ID_H264);
	if (!codec)
	{
		std::cout << "codec not found." << std::endl;
		return -1;
	}

	// 2、初始化parser
	parser = av_parser_init(codec->id);
	if (!parser)
	{
		std::cout << "parser not found." << std::endl;
		return -2;
	}

	// 3、分配解码上下文 alloc codec context
	avcodec_ctx = avcodec_alloc_context3(codec);
	if (!avcodec_ctx)
	{
		std::cout << "could not allocate avcodec_ctx." << std::endl;
		return -3;
	}

	// 4、打开解码器
	ret = avcodec_open2(avcodec_ctx, codec, NULL);
	if (ret < 0)
	{
		std::cout << "could not open codec,ret=" << ret << std::endl;
		return -4;
	}

	// 5、打开h264文件
	f_in = fopen(input_filename.data(), "rb");

	// 6、开始解码
	while (!feof(f_in))
	{
		data_size = fread(inbuf, 1, INBUF_SIZE, f_in);
		if (data_size <= 0)
		{
			break;
		}
		data = inbuf;
		while (data_size > 0)
		{
			 // 输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)。
			ret = av_parser_parse2(parser, avcodec_ctx, &pkt->data, &pkt->size, data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
			std::cout << "ret=" << ret << std::endl;
			data += ret;
			data_size -= ret;
			
			std::cout << "pkt_size=" << pkt->size << std::endl;
			if (pkt->size)
			{
				decode(avcodec_ctx, frame, pkt, f_out);
			}
		}
	}

	/* flush the decoder */
	decode(avcodec_ctx, frame, pkt, f_out);

	fclose(f_in);
	fclose(f_out);

	av_parser_close(parser);
	avcodec_free_context(&avcodec_ctx);
	av_frame_free(&frame);
	av_packet_free(&pkt);
	return 0;
}

4、执行

以linux环境为例说明吧,windows下的很简单,直接运行即可,

在build目录,运行sh build.sh进行编译,运行可执行文件,会有如下打印

利用av_parser_parser2函数,解码h264文件

h264的文件可以通过第2步解码代码中toy3.mp4来获的h264。

输入3来解码文件获得yuv420p的数据。

利用av_parser_parser2函数,解码h264文件

之后直接回车即可。

5、播放

该视频文件分辨率为1280*720,使用ffmplay可以进行播放,播放命令为(我的windows环境):

./study/ffmpeg-20200617-0b3bd00-win64-static/bin/ffplay.exe -f rawvideo -video_size 1280*720 study/goffmpeg/goffmpeg/toy3.yuv

利用av_parser_parser2函数,解码h264文件

觉着这篇文章对自己有益的土豪朋友可以扫描屏幕下方二维码金额随意,感谢大家支持,增加写作动力。

利用av_parser_parser2函数,解码h264文件

本文地址:https://blog.csdn.net/liupenglove/article/details/107303453