当前位置:   article > 正文

海思HI3531D使用ffmpeg实时封装多路H264视频+AAC音频为MP4_海思 ffmpeg 录制mp4

海思 ffmpeg 录制mp4

零、前提:

2020-3-7
实时封装多路H264裸码流和AAC音频到mp4,不涉及编码。编码我都用海思做了,没研究过ffmpeg的编码。
大部分代码都在注释里面写的很清楚了。
第一次做视音频方面项目,以此文做个笔记,也希望能帮到你,毕竟我也是从这里获得了很多帮助。

参考了雷神写的许多ffmpeg封装的文章。
同时参考了https://blog.csdn.net/zhenglie110/article/details/88030925的实时封装,他的代码已经实现了大部分主要功能,我也是在项目中拿他的代码进行加工的,连函数名都没变,够懒的,但不代表没有认真解读过,也做了些改动。
1,增加了音频写入函数。
2,由于我使用的是ffmpeg-4.x版本,新版本已经弃用了AVStream结构体中的AVCodec codec,改用AVCodecParameters codecpar代替,所以这里顺便给改了,不改的话在封装的时候会出警告,不影响功能。我看不惯警告,消灭掉。
3,增加了SPS和PPS信息的写入,不然mp4文件没有封面缩略图。

H264裸码流是通过海思提供的HI_MPI_VENC_GetStream获取到VENC_PACK_S结构体中。
AAC音频是HI_MPI_AENC_GetStream获取到AUDIO_STREAM_S结构体中。

一、实现代码:

代码在HI3531D上运行通过,适当调整即可
  • 1

0、自定义的一个结构体:

typedef struct
{
	AVFormatContext* g_OutFmt_Ctx[VENC_MAX_CHN_NUM];	//每个通道的AVFormatContext
	int vi[VENC_MAX_CHN_NUM];	//视频流索引号
	int ai[VENC_MAX_CHN_NUM];	//音频流索引号
	HI_BOOL b_First_IDR_Find[VENC_MAX_CHN_NUM];	//第一帧是I帧标志
	long int VptsInc[VENC_MAX_CHN_NUM];	//用于视频帧递增计数
	long int AptsInc[VENC_MAX_CHN_NUM];	//音频帧递增
	HI_U64 Audio_PTS[VENC_MAX_CHN_NUM];	//音频PTS
	HI_U64 Video_PTS[VENC_MAX_CHN_NUM];	//视频PTS
	HI_U64 Afirst[VENC_MAX_CHN_NUM];	//是文件第一帧音频标志
	HI_U64 Vfirst[VENC_MAX_CHN_NUM];	//视频第一帧标志
	HI_BOOL state;	
	long int moov_pos[VENC_MAX_CHN_NUM];	//moov的pos,未使用
	int moov_flags[VENC_MAX_CHN_NUM];	//moov前置标志,未使用
	int file_flags[VENC_MAX_CHN_NUM];	
	char filename[VENC_MAX_CHN_NUM][1024];	//文件名
}FfmpegConf;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

1、创建mp4文件:

 每个通道的文件名不一样,几通道就调用几次。
 这个函数我并没有往AVFormatContext里面添加视音频流以及视音频参数设置,我是等第一个I帧来了之后,
 根据SPS和PPS信息添加视频流。海思的I帧包含4个数据包(NormalP模式):IDR,SEI,SPS,PPS。
 也可以在这个函数里面直接添加流,需要事先获取SPS和PPS。
 根据我的测试,SPS和PPS是固定的,只要海思编码设置确定后。
 但是只要变了,就要重新写SPS、PPS,所以,我是在读取到I帧的时候,读取SPS、PPS信息来添加流。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
//MP4创建函数:初始化,写文件头。
//参数:通道号,文件名,fc,
int HI_PDT_CreateMp4(VENC_CHN VeChn, char *pfile, FfmpegConf *fc)
{
    int ret = 0; 
    char pszFileName[256] = {0};	//保存文件名
    AVOutputFormat *pOutFmt = NULL;	//输出Format指针
    #ifdef FFMPEG_MUXING	
    AVCodec *audio_codec;
    #endif
	sprintf(pszFileName,"%s.mp4",pfile);
  //  av_register_all();	//已弃用
    avformat_alloc_output_context2(&(fc->g_OutFmt_Ctx[VeChn]), NULL, NULL, pszFileName);//初始化输出视频码流的AVFormatContext。
	if (NULL == fc->g_OutFmt_Ctx[VeChn])	//失败处理
    {	
        ADD_LOG("Muxing:Could not deduce output format from file extension: using mp4. \n");//添加日志
        avformat_alloc_output_context2(&(fc->g_OutFmt_Ctx[VeChn]), NULL, "mp4", pszFileName);
		if (NULL == fc->g_OutFmt_Ctx[VeChn])
    	{
    		ADD_LOG("Muxing:avformat_alloc_output_context2 failed\n");
        	return -1;
    	}
    }
//	set_mov_moov_ahead(	fc->g_OutFmt_Ctx[VeChn] );	//前置moov
    pOutFmt = fc->g_OutFmt_Ctx[VeChn]->oformat;		//获取输出Format指针
    if (pOutFmt->video_codec == AV_CODEC_ID_NONE)	//检查视频编码器
    {
        ADD_LOG("Muxing:add_video_stream ID failed\n"); 
		goto exit_outFmt_failed;
	}
	if (pOutFmt->audio_codec == AV_CODEC_ID_NONE)	//检查音频编码器
    {
        ADD_LOG("Muxing:add_audio_stream ID failed\n"); 
		goto exit_outFmt_failed;
	}
    if (!(pOutFmt->flags & AVFMT_NOFILE))	//应该是判断文件IO是否打开
    {
        ret = avio_open(&(fc->g_OutFmt_Ctx[VeChn]->pb), pszFileName, AVIO_FLAG_WRITE);	//创建并打开mp4文件
        if (ret < 0)
        {
        	#ifdef DEBUG
        	printf("pszFileName=%s\n",pszFileName);
        	#endif
            ADD_LOG("Muxing:could not create video file\n");
            goto exit_avio_open_failed;
        }
    }
	//初始化一些参数
	fc->Video_PTS[VeChn]=0;	
	fc->Audio_PTS[VeChn]=0;
	fc->Vfirst[VeChn]=0;
	fc->Afirst[VeChn]=0;
	fc->vi[VeChn]=-1;
	fc->ai[VeChn]=-1;
	fc->b_First_IDR_Find[VeChn] = 0;
	return HI_SUCCESS;
//错误处理
exit_avio_open_failed:	
	if (fc->g_OutFmt_Ctx[VeChn] && !(fc->g_OutFmt_Ctx[VeChn]->flags & AVFMT_NOFILE))
		avio_close(fc->g_OutFmt_Ctx[VeChn]->pb);
exit_outFmt_failed:
	if(NULL != fc->g_OutFmt_Ctx[VeChn])
		avformat_free_context(fc->g_OutFmt_Ctx[VeChn]);
	return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

2、写视频帧:

 	直接从 VENC_STREAM_S 中提取视频帧数据写入MP4。
  • 1
HI_S32 HI_PDT_WriteVideo(VENC_CHN VeChn, VENC_STREAM_S *pstStream, FfmpegConf *fc)
{
	unsigned int i=0;	//
	unsigned char* pPackVirtAddr = NULL;	//码流首地址
	unsigned int u32PackLen = 0;	//码流长度
    int ret = 0;
    AVStream *Vpst = NULL; //视频流指针
    AVPacket pkt;	//音视频包结构体,这个包不是海思的包,填充之后,用于最终写入数据
    uint8_t sps_buf[32];	//
    uint8_t pps_buf[32];
    uint8_t sps_pps_buf[64];	
    unsigned int pps_len=0;
    unsigned int sps_len=0;
	if(NULL == pstStream)	//裸码流有效判断
	{
		return HI_SUCCESS;
	}
	//u32PackCount是海思中记录此码流结构体中码流包数量,一般含I帧的是4个包,P帧1个
    for (i = 0 ; i < pstStream->u32PackCount; i++)	
    {
    	//从海思码流包中获取数据地址,长度
        pPackVirtAddr = pstStream->pstPack[i].pu8Addr + pstStream->pstPack[i].u32Offset;	
        u32PackLen = pstStream->pstPack[i].u32Len -pstStream->pstPack[i].u32Offset;	
		av_init_packet(&pkt);	//初始化AVpack包,
		pkt.flags=AV_PKT_FLAG_KEY;	//默认是关键帧,关不关键好像都没问题
		switch(pstStream->pstPack[i].DataType.enH264EType)
		{
			case H264E_NALU_SPS:	//如果这个包是SPS
				pkt.flags =   0;	//不是关键帧
				if(fc->b_First_IDR_Find[VeChn] == 2)	//如果不是第一个SPS帧
				{
					continue;	//不处理,丢弃
					//我只要新建文件之后的第一个SPS PPS信息,后面都是一样的,只要第一个即可
				}
				else //如果是第一个SPS帧
				{
					sps_len = u32PackLen;
					memcpy(sps_buf, pPackVirtAddr, sps_len);
					if(fc->b_First_IDR_Find[VeChn] ==1)	//如果PPS帧已经收到
					{
						memcpy(sps_pps_buf, sps_buf, sps_len);	//复制sps
						memcpy(sps_pps_buf+sps_len, pps_buf, pps_len);	//加上pps
						//去添加视频流,和SPS PPS信息,这步之后才开始写入视频帧
						ret = HI_ADD_SPS_PPS(VeChn, sps_pps_buf, sps_len+pps_len, fc);
						if(ret<0)return HI_FAILURE;
					}
					fc->b_First_IDR_Find[VeChn]++;	//
				}
				continue; //继续
				//break; 
			case H264E_NALU_PPS:
				pkt.flags = 0;	//不是关键帧
				if(fc->b_First_IDR_Find[VeChn] == 2)	//如果不是第一个PPS帧
				{
					continue;
				}
				else //是第一个PPS帧
				{
					pps_len = u32PackLen;
					memcpy(pps_buf, pPackVirtAddr, pps_len);	//复制
					if(fc->b_First_IDR_Find[VeChn] ==1)	//如果SPS帧已经收到
					{
						memcpy(sps_pps_buf, sps_buf, sps_len);
						memcpy(sps_pps_buf+sps_len, pps_buf, pps_len);
						//这里和SPS那里互斥,只有一个会执行,主要是看SPS和PPS包谁排在后面
						ret = HI_ADD_SPS_PPS(VeChn, sps_pps_buf, sps_len+pps_len, fc); 
						if(ret<0)return HI_FAILURE;
					}
					fc->b_First_IDR_Find[VeChn]++;
				}
				continue;
			case H264E_NALU_SEI:	//增强帧
				continue;	//不稀罕这个帧
			case H264E_NALU_PSLICE:		//P帧
			case H264E_NALU_IDRSLICE:	//I帧
				if(fc->b_First_IDR_Find[VeChn] !=2)	//如果这个文件还没有收到过sps和pps帧
				{
					continue;	//跳过,不处理这帧
				}
				break;
			default:
				break;
		}
		
		if(fc->vi[VeChn]<0)	//流索引号,如果g_OutFmt_Ctx里面还没有新建视频流,也就是说还没收到I帧
		{
			#ifdef DEBUG
			printf("vi less than 0 \n");
			#endif
			return HI_SUCCESS;
		}

		if(fc->Vfirst[VeChn]==0)	//如果是文件的第一帧视频
		{
			fc->Vfirst[VeChn]=1;	
			#ifdef USING_SEQ	//使用帧序号计算PTS
			fc->Video_PTS[VeChn] = pstStream->u32Seq; //记录初始序号
			#endif
			#ifdef USING_PTS	//直接使用海思的PTS
			fc->Video_PTS[VeChn] = pstStream->pstPack[i].u64PTS;	//记录开始时间戳
			#endif
		}
		
		Vpst = fc->g_OutFmt_Ctx[VeChn]->streams[fc->vi[VeChn]];	//根据索引号获取视频流地址
	    pkt.stream_index = Vpst->index;	//视频流的索引号 赋给 包里面的流索引号,表示这个包属于视频流
		
		//以下,时间基转换,PTS很重要,涉及音视频同步问题
		#if 0	//原博主的,可以用
			pkt.pts = av_rescale_q_rnd((fc->VptsInc[VeChn]++), Vpst->codec->time_base,Vpst->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
			pkt.dts = av_rescale_q_rnd(pkt.pts, Vpst->time_base,Vpst->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
		#endif
		#if 1	//我用的
			#ifdef USING_SEQ	
				//跟原博主差不多,我怕中间丢帧,导致不同步,所以用序号来计算
				pkt.pts = av_rescale_q_rnd(pstStream->u32Seq - fc->Video_PTS[VeChn], (AVRational){1, STREAM_FRAME_RATE},Vpst->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
				pkt.dts = pkt.pts;	//只有I、P帧,相等就行了
			#endif
			#ifdef USING_PTS	
				//海思的PTS是us单位,所以将真实世界的1000000us转成90000Hz频率的时间
				pkt.pts = pkt.dts =(int64_t)((pstStream->pstPack[i].u64PTS - fc->Video_PTS[VeChn]) *0.09+0.5);
			#endif
		#endif
		//一秒25帧,一帧40ms,好像写0也行,ffmpeg内部处理了?
		//按理说,新建流的时候,给了codepar帧率参数,ffmpeg是可以计算的
		pkt.duration=40;	
		pkt.duration = av_rescale_q(pkt.duration, Vpst->time_base, Vpst->time_base);
		pkt.pos = -1;	//默认
		
		//最重要的数据要给AVpack包
		pkt.data = pPackVirtAddr ;	//接受视频数据NAUL
   		pkt.size = u32PackLen;	//视频数据长度
		//把AVpack包写入mp4
		ret = av_interleaved_write_frame(fc->g_OutFmt_Ctx[VeChn], &pkt);
		if (ret < 0)
		{
		    ADD_LOG("Muxing:cannot write video frame\n");
		    return HI_FAILURE;
		}
    }
	return HI_SUCCESS;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141

2.1、写视频函数的子函数:

在收到第一帧SPS、PPS后被调用。
调用HI_PDT_Add_Stream()向 AVFormatContext* g_OutFmt_Ctx 中创建音视频流并添加流信息,
然后写入文件头
  • 1
  • 2
  • 3
HI_S32 HI_ADD_SPS_PPS(VENC_CHN VeChn, uint8_t *buf, uint32_t size, FfmpegConf *fc)
{
	HI_S32 ret;
	ret = HI_PDT_Add_Stream(VeChn,fc);	//创建一个新流并添加到当前AVFormatContext中
	if(ret<0)
	{
		ADD_LOG("Muxing:HI_PDT_Add_Stream faild\n");
		goto Add_Stream_faild;
	}
	
	fc->g_OutFmt_Ctx[VeChn]->streams[fc->vi[VeChn]]->codecpar->extradata_size = size;
	fc->g_OutFmt_Ctx[VeChn]->streams[fc->vi[VeChn]]->codecpar->extradata = (uint8_t*)av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
	memcpy(fc->g_OutFmt_Ctx[VeChn]->streams[fc->vi[VeChn]]->codecpar->extradata, buf, size);	//写入SPS和PPS

	ret = avformat_write_header(fc->g_OutFmt_Ctx[VeChn], NULL);		//写文件头
	
	if(ret<0)
	{
		ADD_LOG("Muxing:avformat_write_header faild\n");
		goto write_header_faild;
	}	
    return HI_SUCCESS;
    
write_header_faild:
	if (fc->g_OutFmt_Ctx[VeChn] && !(fc->g_OutFmt_Ctx[VeChn]->flags & AVFMT_NOFILE))
		avio_close(fc->g_OutFmt_Ctx[VeChn]->pb);
	
Add_Stream_faild:
    if(NULL != fc->g_OutFmt_Ctx[VeChn])
		avformat_free_context(fc->g_OutFmt_Ctx[VeChn]);
	fc->vi[VeChn] = -1;
	fc->ai[VeChn] = -1;		//AeChn
	fc->VptsInc[VeChn]=0;
	fc->AptsInc[VeChn]=0;
	fc->b_First_IDR_Find[VeChn] = 0;	//sps,pps帧标志清除
	return HI_FAILURE;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

2.2、添加流,被HI_ADD_SPS_PPS()调用:

static int HI_PDT_Add_Stream(VENC_CHN VeChn, FfmpegConf *fc)
{
    AVOutputFormat *pOutFmt = NULL;	//用于获取AVFormatContext->Format
//    AVCodecContext *vAVCodecCtx = NULL ;	//用于获取弃用的AVStream->Codec
    AVCodecParameters *vAVCodecPar=NULL;	//新替代参数AVStream->CodecPar
//    AVCodecContext *aAVCodecCtx = NULL; 
    AVCodecParameters *aAVCodecPar=NULL;

    AVStream *vAVStream = NULL;	//用于指向新建的视频流
    AVStream *aAVStream = NULL;	//用于指向新建的音频流

	AVCodec *vcodec = NULL;	//用于指向视频编码器
	AVCodec *acodec = NULL;	//用于指向音频编码器

	pOutFmt = fc->g_OutFmt_Ctx[VeChn]->oformat;	//输出Format
	
    vcodec = avcodec_find_encoder(pOutFmt->video_codec);	//查找视频编码器,默认就是H264了
//	vcodec = avcodec_find_encoder(AV_CODEC_ID_H264);	//查找一个H264编码器
    if (NULL == vcodec)
    {
        ADD_LOG("Muxing:could not find video encoder H264\n");
        return -1;
    }
	acodec = avcodec_find_encoder(pOutFmt->audio_codec);	//查找音频编码器,默认AAC
//	acodec = avcodec_find_encoder(AV_CODEC_ID_AAC);  //查找一个AAC编码器
	if (NULL == acodec)
    {
        ADD_LOG("Muxing:could not find audio encoder AAC\n");
        return -1;
    }
	
	//根据视频编码器信息(H264),在AVFormatContext里新建视频流通道
    vAVStream = avformat_new_stream(fc->g_OutFmt_Ctx[VeChn], vcodec);	
    if (NULL == vAVStream)
    {
       ADD_LOG("Muxing:could not allocate vcodec stream \n");
       return -1;
    }
    //给新建的视频流一个ID,0
    vAVStream->id = fc->g_OutFmt_Ctx[VeChn]->nb_streams-1;	//nb_streams是当前AVFormatContext里面流的数量
    
	//根据音频编码器信息(AAC),在AVFormatContext里新建音频流通道
    aAVStream = avformat_new_stream(fc->g_OutFmt_Ctx[VeChn], acodec);
    if (NULL == aAVStream)
    {
       ADD_LOG("Muxing:could not allocate acodec stream \n");
       return -1;
    }
    //给新建的音频流一个ID,1
    aAVStream->id = fc->g_OutFmt_Ctx[VeChn]->nb_streams-1;	
    
	fc->vi[VeChn] = vAVStream->index;	//获取视频流的索引号
	fc->ai[VeChn] = aAVStream->index;	//获取音频流的索引号
	
//    vAVCodecCtx = vAVStream->codec;	//弃用
    vAVCodecPar = vAVStream->codecpar;	//
//    aAVCodecCtx = aAVStream->codec;	//弃用
    aAVCodecPar = aAVStream->codecpar;	
//    avcodec_parameters_to_context(aAVCodecCtx, aAVCodecPar);	//可以不用这个函数,太麻烦,还要复制回去
	if(vcodec->type == AVMEDIA_TYPE_VIDEO)	//编码器是视频编码器
    {
    	//对视频流的参数设置
		vAVCodecPar->codec_type = AVMEDIA_TYPE_VIDEO;	
        vAVCodecPar->codec_id = AV_CODEC_ID_H264;
        vAVCodecPar->bit_rate = 2000;	//kbps,好像不需要
        vAVCodecPar->width = 1280;	//像素
        vAVCodecPar->height = 720;
        vAVStream->time_base = (AVRational){1, STREAM_FRAME_RATE};	//时间基
        vAVCodecPar->format = AV_PIX_FMT_YUV420P;
        //下面就是SPS+PPS信息,可以在这里直接添加,那么就可以在创建mp4时调用这个添加流的函数
//        vAVCodecPar->extradata = (uint8_t*)av_malloc(27 + AV_INPUT_BUFFER_PADDING_SIZE);
//        memcpy(vAVCodecPar->extradata, sps_pps, 27);	//sps_pps事先记录下来
//		vAVCodecPar->extradata_size =27;
    }
    
	if(acodec->type ==AVMEDIA_TYPE_AUDIO)	//音频
    {
        aAVCodecPar->codec_type = AVMEDIA_TYPE_AUDIO;
        aAVCodecPar->codec_id = AV_CODEC_ID_AAC;	//AAC
        aAVCodecPar->format = acodec->sample_fmts ? (acodec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;		//AAC只能用浮点,默认也是
        aAVCodecPar->bit_rate = 48000;	//bps
        aAVCodecPar->sample_rate = AUDIO_RATE;	//宏 16000采样率
        aAVCodecPar->channel_layout = AV_CH_LAYOUT_MONO;	//单声道
        aAVCodecPar->channels =1;  //通道数1
        aAVCodecPar->frame_size = 1024;	//实测好像不影响,一般AAC是1024字节
        aAVCodecPar->profile = FF_PROFILE_AAC_LOW;	//AAC等级设置
    	aAVStream->time_base = (AVRational){1, aAVCodecPar->sample_rate};	//时间基
    }
    if (fc->g_OutFmt_Ctx[VeChn]->oformat->flags & AVFMT_GLOBALHEADER)
    {
        vAVCodecPar->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        vAVCodecPar->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }
    return HI_SUCCESS;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

3、写音频帧:

HI_S32 HI_PDT_WriteAudio(AENC_CHN AeChn, AUDIO_STREAM_S *pstStream, FfmpegConf *fc)
{
	unsigned char* pPackVirtAddr = NULL;	//码流数据首地址
	unsigned int u32PackLen = 0;	//码流数据长度
    int ret = 0;
    AVStream *Apst = NULL;	//用于指向音频流
    AVPacket pkt;	//定义一个包
	if(NULL == pstStream)
	{
		return HI_SUCCESS;
	}
	if(fc->ai[AeChn]<0)	//如果文件里面没有音频流
	{
		#ifdef DEBUG
		printf("ai[%d] less than 0 \n",AeChn);
		#endif
		return HI_SUCCESS;
	}
	//获取音频码流信息
	pPackVirtAddr = pstStream->pStream;	//带7字节ADTS头
	u32PackLen = pstStream->u32Len;
	av_init_packet(&pkt);	//初始化AVpack包
	Apst = fc->g_OutFmt_Ctx[AeChn]->streams[fc->ai[AeChn]];	//ai[AeChn]代表音频流索引号
	if(fc->Afirst[AeChn]==0)	//如果是第一个音频帧
	{
		fc->Afirst[AeChn]=1;
		#ifdef USING_SEQ
		fc->Audio_PTS[AeChn] = pstStream->u32Seq;		//记录下初始序列号
		#endif
		#ifdef USING_PTS
		fc->Audio_PTS[AeChn] = pstStream->u64TimeStamp;	//记录下开始时间戳
		#endif
	}

	#ifdef USING_SEQ
//	pkt.pts = pkt.dts =(pstStream->u32Seq - fc->Audio_PTS[AeChn]) * 1024;	//也可以
	pkt.pts = pkt.dts =av_rescale_q_rnd(pstStream->u32Seq - fc->Audio_PTS[AeChn], 
										(AVRational){1000, 15625},	//16000/1024*1000=15625
										Apst->time_base,
										(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
	#endif
	#ifdef USING_PTS 
	pkt.pts = pkt.dts = (pstStream->u64TimeStamp - fc->Audio_PTS[AeChn])/(1000000/AUDIO_RATE);	//时间基转换
	#endif
	pkt.duration=64;	//64ms
	pkt.duration = av_rescale_q(pkt.duration, Apst->time_base, Apst->time_base); //转换

	pkt.pos = -1;
	pkt.stream_index = Apst->index;	//音频流的索引号 赋给 包里面流索引号,表示这个包属于音频流
	pkt.data = pPackVirtAddr ;	//接受音频数据
   	pkt.size = u32PackLen;	//音频数据长度
   	//写入一帧
	ret = av_interleaved_write_frame(fc->g_OutFmt_Ctx[AeChn], &pkt);
	if (ret < 0)
	{
	    ADD_LOG("Muxing:cannot write audio frame\n");
	    return HI_FAILURE;
	}
	return HI_SUCCESS;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

4、写入文件尾,关闭文件:

void HI_PDT_CloseMp4(VENC_CHN VeChn, FfmpegConf *fc)
{
	int ret;
    if (fc->g_OutFmt_Ctx[VeChn])
    {
       ret = av_write_trailer(fc->g_OutFmt_Ctx[VeChn]);	//写文件尾
       if(ret<0)
       {
       		#ifdef DEBUG
       		printf("av_write_trailer faild\n");
       		#endif
       }
    }
    if (fc->g_OutFmt_Ctx[VeChn] && !(fc->g_OutFmt_Ctx[VeChn]->oformat->flags & AVFMT_NOFILE)) //文件状态检测
	{
		ret = avio_close(fc->g_OutFmt_Ctx[VeChn]->pb);	//关闭文件
		if(ret<0)
       {
       		#ifdef DEBUG
       		printf("avio_close faild\n");
       		#endif
       }
	}
	if (fc->g_OutFmt_Ctx[VeChn])
    {
        avformat_free_context(fc->g_OutFmt_Ctx[VeChn]);	//释放结构体
        fc->g_OutFmt_Ctx[VeChn] = NULL;
    }
    //清除相关标志
	fc->vi[VeChn] = -1;
	fc->ai[VeChn] = -1;		
	fc->VptsInc[VeChn]=0;
	fc->AptsInc[VeChn]=0;
	fc->Afirst[VeChn]=0;
	fc->Vfirst[VeChn]=0;
	fc->b_First_IDR_Find[VeChn] = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

二、封装流程

1、定义FfmpegConf结构体实体,并memset。
2、HI_PDT_CreateMp4() 创建mp4文件,一次创建一个通道的。
3、在海思的while中调用写入函数,音频来了就调音频HI_PDT_WriteAudio(),视频来了就调视频HI_PDT_WriteVideo()写入。
4、出循环之后,调用HI_PDT_CloseMp4(),一次关闭一个通道的。
备注:所有函数都需要传入VeChn或者AeChn通道号,表示当前要操作的是哪个通道的。
  • 1
  • 2
  • 3
  • 4
  • 5

三、过程中遇到的一些问题总结:

1、mp4文件必须有头有尾才能播放,写入音视频中途是无法播放的。如果设备正在写入,突然断电,视频将无法播放。
	【所以后来放弃了ffmpeg封装mp4,用了mpeg-ps(节目流)封装,代码https://download.csdn.net/download/qq_30659437/12338349】
2、ffmpeg过于强大,我使用了ffmpeg的API之后,编译出来的执行程序约19M,去掉ffmpeg后1M。
3、音视频同步问题PTS,主要就是一个转换概念,真实世界1s=1000000us,播放器采用的是90000hz,也就是1000000对应90000,
   视频一秒25帧,每帧的时间递增就是40000us,那么播放器就是40000/1000000=PTS/90000。
4、还有一些遗留问题,虽然解决了,但是没有理解其原理。。。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

能力有限,希望指正!

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号