ffmpeg 时基timebase、时间戳pts/dts、延时控制delay

1、基本概念

ffmpeg中在不同是层(封装、编解码、数据)的采样率不同,为精确描述该其数值,使用结构AVRational来描述时基这一概念。

typedef struct AVRational{
    int num; ///< numerator
    int den; ///< denominator
} AVRational;

实际是使用一个分数来描述采样率,例如,一个视频流是帧率是25帧, 那么其r_frame_rate=AVRational(25,1),或者AVRational(50,2)。

在ffmpeg中,时间的单位是微妙,那么标准的时基为 (AVRational){1, 1000000},其中ffmpeg定义了两个宏

#define AV_TIME_BASE            1000000
#define AV_TIME_BASE_Q          (AVRational){1, AV_TIME_BASE}

一个时间戳在不同的时基下进行变化,获取精确延时(av_usleep函数),多数是在不同帧率情况下进行重新封装时用的较多。

2、示例说明

先介绍2个函数

  • double av_q2d(AVRational a)
    将AVRational 对象转换为小数,便于转换
  • int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)
    int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq, enum AVRounding)
    将一个时间戳a从时基bq转换到时基cq下

例如, 在时基bq = {1,1200000}有时间戳 a1 = 48000,需要转换到时基 cq ={1,1000000}下的时间戳a2,
计算过程应该为 a2 = 48000 *(1/1200000)/ (1/1000000} = 40000,使用ffmpeg的对应接口计算函数为 a2 = av_rescale_q(a1, bq, cq);

在实际使用时,一个解封后数据包packet重新封装时,面临时基不同的情况,需要进行转换。另外,若数据包packet来源于裸流(如h264文件,非MP4、avi等封装文件),可能不存在时间戳,还需要进行额外处理。

2.1 解封的packet时间处理

当使用 av_read_frame 获取一个pkt后,pkt中存在pts、dts和duration,这三个时间戳输入流时基下的值,当封装到到输出流中时,需要进行时间戳转换。

(1) 时间戳处理

直接给出转换的代码

in_stream  = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
/* copy packet */
//Convert PTS/DTS
VRounding rnd = (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, rnd);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, rnd);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;

之后就可以使用av_write_frameav_interleaved_write_frame写入都输出流。

(2) 延时处理

正常情况下还要做延时处理,否则对于一个本地文件来说,将以本机性能最快的的速度将所有速度发送到服务端,只有对于服务器是吃不消的。所以需要计算延迟时间。

ffmpeg时间刻度、延时函数都是以微妙为单位,因此还需要通过packet的pts计算前后两帧的系统时间的微妙差。

if(pkt.stream_index==videoindex){
	AVRational time_base = ifmt_ctx->streams[videoindex]->time_base;
	AVRational time_base_q = {1,AV_TIME_BASE}; // AV_TIME_BASE_Q;
	int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
	int64_t now_time = av_gettime() - start_time;
	if (pts_time > now_time)
		av_usleep(pts_time - now_time);
}

2.2 裸流packet时间处理

对于读到是裸流packet,其pts、dts没有“值”(未设置),duration也能没有。因此需要根据裸流的帧率来直接设定其pts、dts和duration的值。这里要采用输入流的时基,也就是要将系统时基的时间转换到输入流时基时间。

AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
//Duration between 2 frames (us)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
//Parameters
pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts=pkt.pts;
pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);

首先根据帧率计算时间间隔(微秒),再做时间戳转换。前面使用 a2 = av_rescale_q(a1, bq, cq);,这里的计算方式也是等效的。

当前 bq = {1, AV_TIME_BASE}cq = time_base1。计算过程如下,a2 = a1 * (1 / AV_TIME_BASE) / (time_base1.num / time_base1.den) = a1 / ( av_q2d(time_base1)*AV_TIME_BASE )

对每一个packet进行修改pts、dts和duration后,可将其当做正常封装文件得到的packet正常流程做延时、转换都输出流时间戳,并写出到输出流。

3 时间转换的完整代码

这里仅给出输入、输出时间的处理代码,仅做封装转换未设计到编解码,也未给出相关变量的创建过程。代码如下:

start_time=av_gettime();
while (1) {
	AVStream *in_stream, *out_stream;
	//Get an AVPacket
	ret = av_read_frame(ifmt_ctx, &pkt);
	if (ret < 0)
		break;
	//FIX:No PTS (Example: Raw H.264)
	//Simple Write PTS
	if(pkt.pts==AV_NOPTS_VALUE){
		//Write PTS
		AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
		//Duration between 2 frames (us)
		int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
		//Parameters
		pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
		pkt.dts=pkt.pts;
		pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
	}
	//Important:Delay
	if(pkt.stream_index==videoindex){
		AVRational time_base = ifmt_ctx->streams[videoindex]->time_base;
		AVRational time_base_q = {1,AV_TIME_BASE}; // AV_TIME_BASE_Q;
		int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
		int64_t now_time = av_gettime() - start_time;
		if (pts_time > now_time)
			av_usleep(pts_time - now_time);
	}

	in_stream  = ifmt_ctx->streams[pkt.stream_index];
	out_stream = ofmt_ctx->streams[pkt.stream_index];
	/* copy packet */
	//Convert PTS/DTS
    AVRounding rnd = (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
	pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, rnd);
	pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, rnd);
	pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
	pkt.pos = -1;
	//Print to Screen
	if(pkt.stream_index==videoindex){
		printf("Send %8d video frames to output URL\n",frame_index);
		frame_index++;
	}
	//ret = av_write_frame(ofmt_ctx, &pkt);
	ret = av_interleaved_write_frame(ofmt_ctx, &pkt);

	if (ret < 0) {
		printf( "Error muxing packet\n");
		break;
	}
		
	av_free_packet(&pkt);	
}
  • 8
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aworkholic

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值