重封装剪切中间10秒视频并重新计算pts_dts

封装视频

重封装剪切中间10秒视频并重新计算pts_dts_第1张图片

封装MP4 创建上下文和流

重封装剪切中间10秒视频并重新计算pts_dts_第2张图片

avio_open 打开输出

重封装剪切中间10秒视频并重新计算pts_dts_第3张图片

avformat_write_header 

PTS计算 

重封装剪切中间10秒视频并重新计算pts_dts_第4张图片

av_write_frame 写入帧 

重封装剪切中间10秒视频并重新计算pts_dts_第5张图片

控制播放进度 av_seek_frame 

重封装剪切中间10秒视频并重新计算pts_dts_第6张图片

 

重新封装 截断后10秒

重封装剪切中间10秒视频并重新计算pts_dts_第7张图片

代码示例 

123_test_remux.cpp

#include 

using namespace std;

extern "C"  // 指定函数是 C 语言函数,函数目标名不包含重载标识,C++ 中调用 C 函数需要使用 extern "C"
{
	// 引用 ffmpeg 头文件
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}

// 预处理指令导入库
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")

#define CERR(err)  if(err != 0)				\
					{						\
						PrintError(err);	\
						return -1;			\
					}

static void PrintError(int err)
{
	char buf[1024] = { 0 };

	av_strerror(err, buf, sizeof(buf) - 1);
	cerr << buf << endl;
}

int main()
{
	int ret = 0;
	AVFormatContext* ic = nullptr;  // 解封装输入上下文
	const char* url = "v1080.mp4";
	AVPacket packet;
	AVStream* vs = nullptr;  //视频流
	AVStream* as = nullptr;  //音频流
	AVFormatContext* oc = nullptr;
	const char* out_url = "test_remux.mp4";
	AVStream* mvs = nullptr;
	AVStream* mas = nullptr;
	double begin_sec = 10.0;
	double end_sec = 20.0;
	long long begin_video_pts = 0;
	long long begin_audio_pts = 0;
	long long end_video_pts = 0;

	ret = avformat_open_input(&ic, url,
		nullptr,   // 封装器格式 nullptr 自动探测 根据后缀名或者文件头 
		nullptr	   // 参数设置,rtsp需要设置
	);
	CERR(ret);

	// 获取媒体信息 无头部格式
	ret = avformat_find_stream_info(ic, nullptr);
	CERR(ret);

	// 打印封装信息
	av_dump_format(ic, 0, url,
		0	// 0表示上下文是输入 1 输出
	);

	for (int i = 0; i < ic->nb_streams; i++)
	{
		if (ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			vs = ic->streams[i];
			// cout << "video: " << "width = " << vs->codecpar->width << " hwight = " << vs->codecpar->height << " bitrate = " << vs->codecpar->bit_rate << endl;
		}
		else if (ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			as = ic->streams[i];
			// cout << "audio: " << "sample rate = " << as->codecpar->sample_rate << endl;
		}
	}

	ret = avformat_alloc_output_context2(&oc, nullptr, nullptr,
		out_url  // 根据文件名推测封装格式
	);
	CERR(ret);

	// 添加视频流、音频流
	mvs = avformat_new_stream(oc, nullptr);
	mas = avformat_new_stream(oc, nullptr);

	// 打开输出IO
	ret = avio_open(&oc->pb, out_url, AVIO_FLAG_WRITE);
	CERR(ret);

	// 设置编码音视频流参数
	if (vs)
	{
		mvs->time_base = vs->time_base;  // 时间基数与原视频一致
		avcodec_parameters_copy(mvs->codecpar, vs->codecpar);  //从解封装复制参数
	}

	if (as)
	{
		mas->time_base = as->time_base;
		avcodec_parameters_copy(mas->codecpar, as->codecpar);
	}

	// 写入文件头
	ret = avformat_write_header(oc, nullptr);
	CERR(ret);

	//打印输出上下文
	av_dump_format(oc, 0, out_url, 1);

	
	/// 截取10 ~ 20 秒之间的音频视频 取多不取少
	// 假定 9 11秒有关键帧 我们取第9秒

	//换算成pts 换算成输入ic的pts,以视频流为准
	if (vs && (vs->time_base.num > 0))
	{
		double t = (double)vs->time_base.den / vs->time_base.num;

		begin_video_pts = begin_sec * t;
		end_video_pts = end_sec * t;
	}

	if (as && (as->time_base.num > 0))
	{
		double t = (double)as->time_base.den / as->time_base.num;

		begin_audio_pts = begin_sec * t;
	}

	// seek输入媒体 移动到第十秒的关键帧位置
	if (vs)
	{
		ret = av_seek_frame(ic, vs->index, begin_video_pts,
			AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD		// 向后关键帧
		);
	}

	CERR(ret);

	AVStream* in_stream = nullptr;
	AVStream* out_stream = nullptr;
	long long offset_pts = 0;

	while (1)
	{
		ret = av_read_frame(ic, &packet);  // 此函数不会是否 packet 中原先的 buf 空间,需要手动调用 av_packet_unref 来释放 buf 空间

		in_stream = ic->streams[packet.stream_index];

		if (ret != 0)
		{
			break;
		}

		if (vs && (packet.stream_index == vs->index))
		{
			// cout << "video ";

			out_stream = oc->streams[0];
			offset_pts = begin_video_pts;

			// 超过第20秒退出,只存10~20秒
			if (packet.pts > end_video_pts)
			{
				av_packet_unref(&packet);
				break;
			}
		}
		else if (as && (packet.stream_index == as->index))
		{
			// cout << "audio ";

			out_stream = oc->streams[1];
			offset_pts = begin_audio_pts;
		}

		// cout << packet.pts << " : " << packet.dts << " " << packet.duration << endl;

		//重新计算pts dts duration
		//`a * bq(输入basetime) / cq(输出basetime)`
		if (in_stream && out_stream)
		{
			packet.pts = av_rescale_q_rnd(packet.pts - offset_pts, in_stream->time_base, out_stream->time_base,
				static_cast(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
			);
			packet.dts = av_rescale_q_rnd(packet.dts - offset_pts, in_stream->time_base, out_stream->time_base,
				static_cast(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)
			);
			packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base);
		}

		packet.pos = -1;

		ret = av_interleaved_write_frame(oc, &packet);  // 写入音视频帧 会清理pkt

		if (ret != 0)
		{
			break;
		}
	}

	// 写入结尾 包含文件偏移索引
	ret = av_write_trailer(oc);
	CERR(ret);

	avio_close(oc->pb);
	avformat_free_context(oc);
	oc = nullptr;
	avformat_close_input(&ic);

	return 0;
}

我们从 v1080.mp4 文件中截取 10s-20s 的音视频,在 seek 的时候,需要找到关键帧,我们的策略是找到向后关键帧,所以截取出来的音视频会大于10s。

测试结果如下图所示:

截取出来的音视频比预取长了3s。

你可能感兴趣的:(音视频,音视频)