将MP4视频和MP4音频合并成MP4文件_mp4v encod-程序员宅基地

技术标签: 音视频  

由于项目需要,需要将一个mp4文件中的视频和另一个mp4文件的音频合成为一个mp4文件。因此试着将合成过程中解决问题的方法记录下来,以便以后进行查看。
合成中需要处理的问题:
1.当视频和音频时长不一致时,如何处理?
以视频的时长为标准,音频时长比视频短时,添加静音帧作为补充;
开始时需要处理的问题:
第一种情况:视频的开始时间要比音频晚,因此要过滤掉一部分音频,等到达视频开始时间时,才开始将视频和音频数据写入MP4文件。
第一种情况
第二种情况:音频的开始时间比视频晚,因此为了保证同步,用静音帧来填补音频数据的缺失。
第二种情况
结尾时需要处理的问题:
结尾也同样有两种情况需要进行处理。
第一种情况:音频先于视频结束,为了与视频对齐,因此需要添加静音帧来弥补音频时长过短的问题。
第一种情况
第二种情况:音频结束时间比视频晚,此时与视频进行看齐,丢失多余的音频帧数据。
第二种情况
只要在合并视频和音频之前,建立好相应的规则,才能更有效保证合成后的文件中,音频和视频是同步的。

2.是否需要重新对音视频进行解码和编码?
考虑到现有的文件中视频为H264编码,音频为AAC编码,为了节约时间,提高合并效率,不再对视频、音频进行重新解码和编码。具体可以参考最简单的基于FFmpeg的封装格式处理:视音频复用器(muxer这篇文章。
由于不需要进行编解码,只需要将原文件中的视频和音频数据直接写入到新文件中。为了能够只获取纯数据,因此分别采用了"h264_mp4toannexb"和"aac_adtstoasc"来得到原始的纯视频和音频数据,其用法,在参考文章中讲解的也很清楚,此处不再细述。

3.新生成文件中音频和视频时间戳如何写?
此部分也是比较重要的部分,因为这直接关系到合成文件中视频和音频是否同步。在实际操作过程中,为了更好的实现音频和视频的同步,分别采用变帧率采集音频和视频,这样就可以避免采集的视频和音频由于设备不一致的原因而导致的音视频本身不同步。至于视频和音频采集的方法可以参考RTSP流封装成MP4方法总结中的源码,里面提供了变帧率和固定帧率两种录制方式。
经过实验验证,新文件采用变帧率的录制方式进行,同时新生成视频文件的时间戳按照原来视频和音频中的时间戳来写所得到的最终文件音视频同步效果较好,这里用到了MP4v2对H264视频和AAC音频进行封装,主要是由于个人原因(MP4v2调用方便以及生成的视频文件质量较高)。因此需要对MP4v2库有一个很好的理解,这部分可以参考利用ffmpeg录制rtsp流的方法总结(三)
注意:这部分是从原始文件中获取时间戳的,因此MP4WriteSample()函数中的duration设置有些不太一样,此处的duration为两连续帧之间的时间戳即可,不需要除以timeScale,这个原因,只需要明白duration的含义就懂了。

4.如何生成静音帧?
由于一帧AAC音频格式frame_size为1024,因此静音帧中的frame_size也设置为1024,AVFrame中的其它参数和输入的音频中参数保持一致,其初始化代码如下:

	int ret=NO_VALUE;
	int frame_size_out_encode = output_codec_context->frame_size;
    printf("frame_size_out_encode %d \n", frame_size_out_encode);
    if(!frame_size_out_encode)
        frame_size_out_encode = 1024;
    (*frame)->nb_samples     = frame_size_out_encode;
    (*frame)->channel_layout = output_codec_context->channel_layout;  
    (*frame)->channels = av_get_channel_layout_nb_channels((*frame)->channel_layout);
    (*frame)->format         = output_codec_context->sample_fmt;  
    (*frame)->sample_rate    = output_codec_context->sample_rate;  
    ret=av_frame_get_buffer((*frame), 0);
	if(ret<0)
		return AV_FRAME_GET_BUFFER_ERROR;
    //根据参数填充静音帧数据,常说的PCM数据
    av_samples_set_silence((*frame)->data, 0, (*frame)->nb_samples, (*frame)->channels, (enum AVSampleFormat)(*frame)->format);

对静音frame初始好后,后面只需要调用aac音频编码器进行编码,时间戳按照1024递增。其编码代码如下:

	av_init_packet(&output_packet);
	output_packet.data=NULL;
	output_packet.size=0;
	if((!frame)||(!out_codec_ctx))
		return AV_NO_FRAME;
	frame->pts = pts;
	pts += frame->nb_samples;
	ret=avcodec_send_frame(out_codec_ctx, frame);
	if (ret == AVERROR_EOF) 
	{
		ret=0;
		goto end;
	}else if(ret<0)
	{
		av_packet_unref(&output_packet);
		return AV_AUDIO_SEND_FRAME_ERROR;
	}
	ret=avcodec_receive_packet(out_codec_ctx,&output_packet);
	if (ret==AVERROR(EAGAIN))
	{
		ret=0;
		goto end;
	}else if(ret==AVERROR_EOF)
	{
		ret=0;
		goto end;
	}else if(ret<0)
	{
		av_packet_unref(&output_packet);
		return AUDIO_ENCODE_DATA_ERROR;
	}
	nowvoltime_audio=1024;
	if(mp4encod.MP4WriteAACSlient(output_packet.data,output_packet.size,nowvoltime_audio)<0)   
	{
		av_packet_unref(&output_packet);
		return WRITE_AUDIO_DATA_ERROR;
	}

MP4WriteAACSlient()函数代码为:

int MP4Encord::MP4WriteAACSlient(const uint8_t *sData, int nSize,uint64_t audio_cur_pts)
{
	bool result = false;
	if(nSize<0||sData==NULL)
		return -1;
	result = MP4WriteSample(m_hFile, m_audioTrack, sData, nSize,audio_cur_pts,0,true);
	if (!result)
	{
		printf("write aac frame error!\n");
		return -1;
	}
	return 0;
}

5.这里给出了视频开始时间早于音频开始时间,并且视频结束时间要晚于音频结束时间的示例:

if(video_start_time<audio_start_time)//视频起始时间早于音频起始时间
	{
		delay_time=audio_start_time-video_start_time;
		delay_time=delay_time*out_audio_ctx->sample_rate/1024;

		while(merge_state)
		{
			av_init_packet(&packet);
			packet.data=NULL;
			packet.size=0;

			if(av_compare_ts(cur_pts_v,(*ic_video)->streams[video_index]->time_base,cur_pts_a,(*ic_audio)->streams[audio_index]->time_base) <= 0)
			{
				//写视频
				ret=av_read_frame(*ic_video, &packet);
				if(ret>=0)
				{
					do
					{
						if(packet.stream_index==video_index)
						{
							ret=av_bitstream_filter_filter(h264bsfc,in_video_ctx,NULL,&packet.data,&packet.size,packet.data,packet.size,0);
							if(ret<0)
							{
								ret=AV_BITSTREAM_FILTER_ERROR;
								goto end;
							}
							ret=Wrtie_H264_MP4v2(&packet,mp4encord);
							if(ret<0)
								goto end;
							cur_pts_v=packet.pts;
							break;
						}
					}while(av_read_frame(*ic_video, &packet)>= 0);

				}
				else if(ret==AVERROR_EOF)
				{
					ret=0;
					break;
				}
				else 
					break;
			}
			else //音频快于视频
			{
				//音频前delay_time为静音
				if(num_frame_start<delay_time)
				{
					ret=Encode_Silent_Audio_Frame(Silent_Frame,ofmt,audio_stream,out_audio_ctx,(*ic_audio),mp4encord,pts);
					if(ret<0)
						break;
					num_frame_start++;
					cur_pts_a=num_frame_start*1024;
				}
				else
				{
					ret=av_read_frame(*ic_audio, &packet);
					if(ret>=0)
					{
						do
						{
							if(packet.stream_index==audio_index)
							{
								ret=av_bitstream_filter_filter(aacbsfc,in_audio_ctx,NULL,&packet.data,&packet.size,packet.data,packet.size,0);
								if(ret<0)
								{
									ret=AV_BITSTREAM_FILTER_ERROR;
									break;
								}
							
								ret=mp4encord.MP4WriteAACData(packet.data,packet.size,packet.pts);
								if(ret<0)
								{
									ret=WRITE_AUDIO_DATA_ERROR;
									av_packet_unref(&packet);
									goto end;
								}

								cur_pts_a=packet.pts+num_frame_start*1024;
								last_audio_pts=cur_pts_a;
								break;
							}
						}while(av_read_frame(*ic_audio, &packet)>= 0);

					}
					else if(ret==AVERROR_EOF)//判断音频文件结束后,添加静音帧
					{
						ret=Encode_Silent_Audio_Frame(Silent_Frame,ofmt,audio_stream,out_audio_ctx,(*ic_audio),mp4encord,pts);
						if(ret<0)
							break;
						num_frame_end++;
						cur_pts_a=last_audio_pts+num_frame_end*1024;
					}
					else
						break;
			
				}
			}
			av_packet_unref(&packet);
		}
	}

av_compare_ts()函数起着关键作用,主要对当前的视频和音频的时间戳进行判断,如果视频时间戳小于音频时间戳,就应该读取视频数据;否则读取音频数据。
注意:在这里需要留意的是,当音频结束后,需要记录下最后一帧的音频时间戳,以方便后面的静音帧在此基础上进行叠加,保证新生成MP4文件的完整性。如果时间戳设置有问题,会出现音视频不同步,视频播放时,进度条显示速度不正常。
为了使用方便,将原始方法封装成了MFC可执行文件。其简要界面为
在这里插入图片描述
该部分下载链接为:可执行程序下载测试视频下载

如需完整源码,请前往下载

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/unfound/article/details/100051861

智能推荐

海康威视网络摄像头开发流程(五)------- 直播页面测试_ezuikit 测试的url-程序员宅基地

文章浏览阅读3.8k次。1、将下载好的萤石js插件,添加到SoringBoot项目中。位置可参考下图所示。(容易出错的地方,在将js插件在html页面引入时,发生路径错误的问题)所以如果对页面中引入js的路径不清楚,可参考下图所示存放路径。2、将ezuikit.js引入到demo-live.html中。(可直接将如下代码复制到你创建的html页面中)<!DOCTYPE html><html lan..._ezuikit 测试的url

如何确定组态王与多动能RTU的通信方式_组态王ua-程序员宅基地

文章浏览阅读322次。第二步,在弹出的对话框选择,设备驱动—>PLC—>莫迪康—>ModbusRTU—>COM,根据配置软件选择的协议选期期,这里以此为例,然后点击“下一步”。第四步,把使用虚拟串口打勾(GPRS设备),根据需要选择要生成虚拟口,这里以选择KVCOM1为例,然后点击“下一步”设备ID即Modbus地址(1-255) 使用DTU时,为下485接口上的设备地址。第六步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“第五步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“_组态王ua

npm超详细安装(包括配置环境变量)!!!npm安装教程(node.js安装教程)_npm安装配置-程序员宅基地

文章浏览阅读9.4k次,点赞22次,收藏19次。安装npm相当于安装node.js,Node.js已自带npm,安装Node.js时会一起安装,npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西_npm安装配置

火车头采集器AI伪原创【php源码】-程序员宅基地

文章浏览阅读748次,点赞21次,收藏26次。大家好,小编来为大家解答以下问题,python基础训练100题,python入门100例题,现在让我们一起来看看吧!宝子们还在新手村练级的时候,不单要吸入基础知识,夯实自己的理论基础,还要去实际操作练练手啊!由于文章篇幅限制,不可能将100道题全部呈现在此除了这些,下面还有我整理好的基础入门学习资料,视频和讲解文案都很齐全,用来入门绝对靠谱,需要的自提。保证100%免费这不,贴心的我爆肝给大家整理了这份今天给大家分享100道Python练习题。大家一定要给我三连啊~

Linux Ubuntu 安装 Sublime Text (无法使用 wget 命令,使用安装包下载)_ubuntu 安装sumlime text打不开-程序员宅基地

文章浏览阅读1k次。 为了在 Linux ( Ubuntu) 上安装sublime,一般大家都会选择常见的教程或是 sublime 官网教程,然而在国内这种方法可能失效。为此,需要用安装包安装。以下就是使用官网安装包安装的教程。打开 sublime 官网后,点击右上角 download, 或是直接访问点击打开链接,即可看到各个平台上的安装包。选择 Linux 64 位版并下载。下载后,打开终端,进入安装..._ubuntu 安装sumlime text打不开

CrossOver for Mac 2024无需安装 Windows 即可以在 Mac 上运行游戏 Mac运行exe程序和游戏 CrossOver虚拟机 crossover运行免安装游戏包-程序员宅基地

文章浏览阅读563次,点赞13次,收藏6次。CrossOver24是一款类虚拟机软件,专为macOS和Linux用户设计。它的核心技术是Wine,这是一种在Linux和macOS等非Windows操作系统上运行Windows应用程序的开源软件。通过CrossOver24,用户可以在不购买Windows授权或使用传统虚拟机的情况下,直接在Mac或Linux系统上运行Windows软件和游戏。该软件还提供了丰富的功能,如自动配置、无缝集成和实时传输等,以实现高效的跨平台操作体验。

随便推点

一个用聊天的方式让ChatGPT写的线程安全的环形List_为什么gpt一写list就卡-程序员宅基地

文章浏览阅读1.7k次。一个用聊天的方式让ChatGPT帮我写的线程安全的环形List_为什么gpt一写list就卡

Tomcat自带的设置编码Filter-程序员宅基地

文章浏览阅读336次。我们在前面的文章里曾写过Web应用中乱码产生的原因和处理方式,旧文回顾:深度揭秘乱码问题背后的原因及解决方式其中我们提到可以通过Filter的方式来设置请求和响应的encoding,来解..._filterconfig selectencoding

javascript中encodeURI和decodeURI方法使用介绍_js encodeur decodeurl-程序员宅基地

文章浏览阅读651次。转自:http://www.jb51.net/article/36480.htmencodeURI和decodeURI是成对来使用的,因为浏览器的地址栏有中文字符的话,可以会出现不可预期的错误,所以可以encodeURI把非英文字符转化为英文编码,decodeURI可以用来把字符还原回来_js encodeur decodeurl

Android开发——打包apk遇到The destination folder does not exist or is not writeable-程序员宅基地

文章浏览阅读1.9w次,点赞6次,收藏3次。前言在日常的Android开发当中,我们肯定要打包apk。但是今天我打包的时候遇到一个很奇怪的问题Android The destination folder does not exist or is not writeable,大意是目标文件夹不存在或不可写。出现问题的原因以及解决办法上面有说报错的中文大意是:目标文件夹不存在或不可写。其实问题就在我们的打包界面当中图中标红的Desti..._the destination folder does not exist or is not writeable

Eclipse配置高大上环境-程序员宅基地

文章浏览阅读94次。一、配置代码编辑区的样式 <1>打开Eclipse,Help —> Install NewSoftware,界面如下: <2>点击add...,按下图所示操作: name:随意填写,Location:http://eclipse-color-th..._ecplise高大上设置

Linux安装MySQL-5.6.24-1.linux_glibc2.5.x86_64.rpm-bundle.tar_linux mysql 安装 mysql-5.6.24-1.linux_glibc2.5.x86_6-程序员宅基地

文章浏览阅读2.8k次。一,下载mysql:http://dev.mysql.com/downloads/mysql/; 打开页面之后,在Select Platform:下选择linux Generic,如果没有出现Linux的选项,请换一个浏览器试试。我用的谷歌版本不可以,换一个别的浏览器就行了,如果还是不行,需要换一个翻墙的浏览器。 二,下载完后解压缩并放到安装文件夹下: 1、MySQL-client-5.6.2_linux mysql 安装 mysql-5.6.24-1.linux_glibc2.5.x86_64.rpm-bundle