由于我是移植到arm-linux环境(海思HI3521A),H264裸流直接从海思的编码模块VENC获取。
H264数据流序列: SPS, PPS, SEI, I, P, P, ... P, P, SPS, PPS, SEI, I, P, P, ... P, P, ...
源码如下:
#include
#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
#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();