ffmpeg实现将H264裸流封装成.mp4或.avi文件

 ffmpeg学习历程

由于我是移植到arm-linux环境(海思HI3521A),H264裸流直接从海思的编码模块VENC获取。

H264数据流序列:    SPS, PPS, SEI, I, P, P, ... P, P,  SPS, PPS, SEI, I, P, P, ... P, P, ...

源码如下:

#include <stdio.h>
#include "mpi_venc.h"

#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif

extern "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}

class video_file
{
private:
	AVFormatContext *m_fmtCtx;
	bool m_bNeedGlobalHeader;
	bool m_bHadFillGlobalHeader;
	bool m_bFoundFirstIDR;
	int m_frameNum;
	int m_videoIndex;

public:
	video_file();	
	~video_file();
	int create_and_open_file(const char* fileName);
	int close_file();
	int write_packet(VENC_STREAM_S *vencStream);
};

#include <stdio.h>
#include "video_file.h"

video_file::video_file()
{
	m_fmtCtx = NULL;
	m_bNeedGlobalHeader = false;
	m_bHadFillGlobalHeader = false;
	m_bFoundFirstIDR = false;
	m_frameNum = 0;
	m_videoIndex = 0;
}

video_file::~video_file()
{
	close_file();
}

int video_file::create_and_open_file(const char* fileName)
{
	int ret, i;
	AVOutputFormat *ofmt = NULL;
	AVCodec *codec = NULL;
	AVStream *stream = NULL;
	AVCodecContext *codecCtx = NULL;

	if(m_fmtCtx != NULL)
	{
		printf("Another open video file is not closed\n");
		return -1;
	}
	
	av_register_all();
	
	ret = avformat_alloc_output_context2(&m_fmtCtx, NULL, NULL, fileName);
	if(ret < 0)
	{
		printf("Failed to alloc output format context [fileName: %s]\n", fileName);
		return -1;
	}
	
	ofmt = m_fmtCtx->oformat;	
	codec = avcodec_find_encoder(ofmt->video_codec);
	if(codec == NULL)
	{
		printf("No encoder[%s] found\n", avcodec_get_name(ofmt->video_codec));
		goto fail;
	}
	
	stream = avformat_new_stream(m_fmtCtx, codec);
	if(stream == NULL)
	{
	   printf("Failed to alloc stream\n");
	   goto fail;
	}

	m_videoIndex = stream->index;
	codecCtx = stream->codec;
	//宽高,帧率,编码格式
	codecCtx->codec_id = AV_CODEC_ID_H264;
	codecCtx->width = 1920;
	codecCtx->height = 1080;
	codecCtx->time_base.den = 30;
	codecCtx->time_base.num = 1;
	//codecCtx->gop_size = 30;
	//codecCtx->bit_rate = 0;
	//codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

	//MP4格式需要全局头信息,AVI不需要	
	m_bNeedGlobalHeader = (m_fmtCtx->oformat->flags & AVFMT_GLOBALHEADER);
	if(m_bNeedGlobalHeader)
	{
		codecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
		//extradata空间大小只需能装下SPS,PPS,SEI等即可
		codecCtx->extradata = (uint8_t*)av_malloc(1024);
		codecCtx->extradata_size = 0;
		m_bHadFillGlobalHeader = false;
	}
	
	printf("==========Output Information==========\n");
	av_dump_format(m_fmtCtx, 0, fileName, 1);

	if(!(ofmt->flags & AVFMT_NOFILE))
	{
		ret = avio_open(&m_fmtCtx->pb, fileName, AVIO_FLAG_WRITE);
		if (ret < 0)
		{
			printf("could not open %s\n", fileName);
			goto fail;
		}
	}

	ret = avformat_write_header(m_fmtCtx, NULL);
	if (ret < 0)
	{
		printf("Error occurred when opening output file \n");
		goto fail;
	}

	m_frameNum = 0;
	m_bFoundFirstIDR = false;
	return 0;

fail:
	if(stream && stream->codec->extradata)
		av_free(stream->codec->extradata);
	if (m_fmtCtx && !(m_fmtCtx->flags & AVFMT_NOFILE))
		avio_close(m_fmtCtx->pb);		
	avformat_free_context(m_fmtCtx);
	m_fmtCtx = NULL;
	return -1;
}

//分析源码可知, 释放AVFormatContext是,会自动释放旗下的若干AVStream, 而AVStream释放时也会自动释放旗下的extradata
int video_file::close_file()
{
	if(m_fmtCtx != NULL)
	{
		av_write_trailer(m_fmtCtx);
		if (m_fmtCtx && !(m_fmtCtx->flags & AVFMT_NOFILE))
			avio_close(m_fmtCtx->pb);
		/*AVStream *stream = m_fmtCtx->streams[m_videoIndex];
		if(stream && stream->codec->extradata)
		{
			av_free(stream->codec->extradata);
			stream->codec->extradata = NULL;
		}*/
		avformat_free_context(m_fmtCtx);
		m_fmtCtx = NULL;
	}
	return 0;
}

/* 海思的VENC的裸流NAL序列如下:
*	SPS, PPS, SEI, I, P, P, ..., P, P,   SPS, PPS, SEI, I, P, P, ..., P, P
*/
int video_file::write_packet(VENC_STREAM_S *vencStream)
{
	int ret;
	unsigned int i; 
	AVStream *stream = NULL;
	AVPacket pkt;
	bool bIsIDR;

	if(m_fmtCtx == NULL)
	{
		printf("Video file is not opened\n");
		return -1;
	}

	if(vencStream == NULL)
	{
		printf("Input stream is NULL\n");
		return -1;
	}

	stream = m_fmtCtx->streams[m_videoIndex];
	bIsIDR = (vencStream->u32PackCount > 1);
	//写入封装器的第一帧必须是IDR帧
	if(!m_bFoundFirstIDR)
	{
		if(!bIsIDR)
			return 0;
		else
			m_bFoundFirstIDR = true;
	}

	for(i = 0 ; i < vencStream->u32PackCount; i++)
	{
		//当需要global header时,将SPS, PPS, SEI填入CodecContext的extradata
		if(bIsIDR && m_bNeedGlobalHeader && (i < 3))
		{
			if(!m_bHadFillGlobalHeader)
			{
				memcpy(stream->codec->extradata + stream->codec->extradata_size, vencStream->pstPack[i].pu8Addr + vencStream->pstPack[i].u32Offset, vencStream->pstPack[i].u32Len - vencStream->pstPack[i].u32Offset);
				stream->codec->extradata_size += vencStream->pstPack[i].u32Len - vencStream->pstPack[i].u32Offset;
				if(i == 2)
					m_bHadFillGlobalHeader = true;
			}
			continue;
		}

		av_init_packet(&pkt);	
		pkt.flags |= bIsIDR ? AV_PKT_FLAG_KEY : 0;
		pkt.stream_index = stream->index;
		pkt.data = (unsigned char*)(vencStream->pstPack[i].pu8Addr + vencStream->pstPack[i].u32Offset);
		pkt.size = vencStream->pstPack[i].u32Len - vencStream->pstPack[i].u32Offset;		
		pkt.pts = av_rescale_q(m_frameNum, stream->codec->time_base, stream->time_base);
		pkt.dts = pkt.pts;
		pkt.duration = 0;	//0 if unknown.
		pkt.pos = -1;		//-1 if unknown
						  
		ret = av_interleaved_write_frame(m_fmtCtx, &pkt);
		if(ret < 0)
		{
		 	 printf("av_interleaved_write_frame failed\n");
		 	 return -1;
		}
		
		m_frameNum++;
	}
		
	return 0;
}

使用方法:

video_file mp4File;
mp4File.create_and_open_file("output.mp4");
mp4File.write_packet(&vencStream);
mp4File.close_file();

 

  • 3
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值