FFmpeg时间戳详解摘要

3.1 时间基与时间戳的概念

在 FFmpeg 中,时间基(time_base)是时间戳(timestamp)的单位,时间戳值乘以时间基,可以得到实际的时刻值(以秒等为单位)。例如,如果一个视频帧的 dts 是 40,pts 是 160,其 time_base 是 1/1000 秒,那么可以计算出此视频帧的解码时刻是 40 毫秒(40/1000),显示时刻是 160 毫秒(160/1000)。FFmpeg 中时间戳(pts/dts)的类型是 int64_t 类型,把一个 time_base 看作一个时钟脉冲,则可把 dts/pts 看作时钟脉冲的计数。

音频按采样点播放,所以解码后的原始音频帧时间基为 1/sample_rate 。
视频按帧播放,所以解码后的原始视频帧时间基为 1/framerate。

3.4 时间值形式转换

av_q2d()将时间从 AVRational 形式转换为 double 形式.AVRational 是分数类型,double 是双精度浮点数类型,转换的结果单位是秒.转换前后的值基于同一时间基,仅仅是数值的表现形式不同而已。

av_q2d()实现如下:

static inline double av_q2d(AVRational a){
    return a.num / (double) a.den;
}

av_q2d()使用方法如下:

AVStream stream;
AVPacket packet;
packet 播放时刻值:timestamp(单位秒) = packet.pts × av_q2d(stream.time_base);
packet 播放时长值:duration(单位秒) = packet.duration × av_q2d(stream.time_base);

notes:av_q2d实现pts和秒的转换;  
AVRational {1,90000};
pkt_pts=160 pkt_pts_time=0.160000;pkt_pts=14400 pkt_pts_time=0.160000

3.5 时间基转换函数 (av_rescale_q 封装av_rescale_rnd 两者是一回事)

av_rescale_q()是time_base转换函数,用于将时间值从一种时间基转换为另一种时间基。

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;

av_packet_rescale_ts()用于将 AVPacket 中各种时间值从一种时间基转换为另一种时间基。

void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);

notes: av_rescale_rnd() 是计算 “a * b / c” 的值并分五种方式来取整
 "时钟基c" 表示的 数值a 转换成以 "时钟基b" 来表示
比如:flv 封装格式的 time_base 为{1,1000},ts 封装格式的 time_base 为{1,90000}
看第一帧的时间戳,计算关系:80×{1,1000} == 7200×{1,90000} == 0.080000 

a=pts=10949117256,b=90000, c=1000000

3.6 转封装过程中的时间基转换

AVStream.time_base 是 AVPacket 中 pts 和 dts 的时间单位,输入流与输出流中 time_base 按如下方式确定:
对于输入流:打开输入文件后,调用 avformat_find_stream_info()可获取到每个流中的 time_base
对于输出流:打开输出文件后,调用 avformat_write_header()可根据输出文件封装格式确定每个流的 time_base 并写入输出文件中

不同封装格式具有不同的时间基,在转封装(将一种封装格式转换为另一种封装格式)过程中,时间基转换相关代码如下:

av_read_frame(ifmt_ctx, &pkt);
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);

notes: 这段代码是每个转封装需要用的代码,转换pts.比如:pts=160 ,实际时间0.160000.
av_q2d实现pts和秒的转换; 

下面的代码具有和上面代码相同的效果:

// 从输入文件中读取 packet
av_read_frame(ifmt_ctx, &pkt);
// 将 packet 中的各时间值从输入流封装格式时间基转换到输出流封装格式时间基
av_packet_rescale_ts(&pkt, in_stream->time_base, out_stream->time_base);

这里流里的时间基in_stream->time_baseout_stream->time_base,是容器中的时间基,就是 3.2 节中的 tbn。

例如,flv 封装格式的 time_base 为{1,1000},ts 封装格式的 time_base 为{1,90000}
我们编写程序将 flv 封装格式转换为 ts 封装格式,抓取原文件(flv)的前四帧显示时间戳:

think@opensuse> ffprobe -show_frames -select_streams v tnmil3.flv | grep pkt_pts  
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, flv, from 'tnmil3.flv':
  Metadata:
    encoder         : Lavf58.20.100
  Duration: 00:00:03.60, start: 0.017000, bitrate: 513 kb/s
    Stream #0:0: Video: h264 (High), yuv420p(progressive), 784x480, 25 fps, 25 tbr, 1k tbn, 50 tbc
    Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 128 kb/s
pkt_pts=80
pkt_pts_time=0.080000
pkt_pts=120
pkt_pts_time=0.120000
pkt_pts=160
pkt_pts_time=0.160000
pkt_pts=200
pkt_pts_time=0.200000

再抓取转换的文件(ts)的前四帧显示时间戳:

think@opensuse> ffprobe -show_frames -select_streams v tnmil3.ts | grep pkt_pts  
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, mpegts, from 'tnmil3.ts':
  Duration: 00:00:03.58, start: 0.017000, bitrate: 619 kb/s
  Program 1 
    Metadata:
      service_name    : Service01
      service_provider: FFmpeg
    Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(progressive), 784x480, 25 fps, 25 tbr, 90k tbn, 50 tbc
    Stream #0:1[0x101]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 44100 Hz, stereo, fltp, 127 kb/s
pkt_pts=7200
pkt_pts_time=0.080000
pkt_pts=10800
pkt_pts_time=0.120000
pkt_pts=14400
pkt_pts_time=0.160000
pkt_pts=18000
pkt_pts_time=0.200000

可以发现,对于同一个视频帧,它们时间基(tbn)不同因此时间戳(pkt_pts)也不同,但是计算出来的时刻值(pkt_pts_time)是相同的。
看第一帧的时间戳,计算关系:
80×{1,1000} == 7200×{1,90000} == 0.080000

3.7 转码过程中的时间基转换

根据注释中的建议,实际使用时,在视频解码过程中,我们不使用 AVCodecContext.time_base,而用帧率倒数作时间基,在视频编码过程中,我们将 AVCodecContext.time_base 设置为帧率的倒数

3.7.1 视频流

视频按帧播放,所以解码后的原始视频帧时间基为 1/framerate。

视频编码过程中的时间基转换处理:

AVFormatContext *ofmt_ctx;
AVStream *out_stream;
AVCodecContext *dec_ctx;
AVCodecContext *enc_ctx;
AVPacket packet;
AVFrame *frame;

// 编码
avcodec_send_frame(enc_ctx, frame);
avcodec_receive_packet(enc_ctx, packet);

// 时间基转换
packet.stream_index = out_stream_idx;
enc_ctx->time_base = av_inv_q(dec_ctx->framerate);
av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_base);

// 将编码帧写入输出媒体文件
av_interleaved_write_frame(o_fmt_ctx, &packet);

解码同理

3.7.2 音频流

音频按采样点播放,所以解码后的原始音频帧时间基为 1/sample_rate

音频编码过程中的时间基转换处理:

和视频处理类似,只需要framerate改成samplerate即可。
enc_ctx->time_base = av_inv_q(dec_ctx->sample_rate);

4 其他

​​​​​​​//计算一桢在整个视频中的时间位置
timestamp(秒) = pts * av_q2d(st->time_base);
//计算视频长度的方法:
time(秒) = st->duration * av_q2d(st->time_base);

//ffmpeg内部的时间与标准的时间转换方法
timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒)

 当需要把视频跳转到N秒的时候可以使用下面的方法:

av_seek_frame(fmt_ctx, index_of_video, N * AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);

ts格式文件中3600间隔是什么意思?

它是25fps帧率的ts媒体文件,每个视频帧的间隔时间。
ts文件的封装时基是90kHz为单位,timebase是AVRational{1,90000},简单的理解就是把1秒分成了90000等分,拿25帧率ts文件来分析
按标准时间来计算每帧的间隔:
公式为:1 / 25 = 0.04(秒) = 40毫秒
按ffmpeg中的1秒(即90000)来计算每帧的间隔(单位好像没有明确的定义,暂且使用ffmpeg吧):
90000 / 25 = 3600(ffmpeg)
用时间转换公式可能会更清楚一些:
1(s) = 90000(ffmpeg)
40(ms) = 3600(ffmpeg)

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值