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_frame
或av_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);
}