理解ffmpeg中的pts,dts,time_base

本文详细介绍了FFmpeg中PTS(Presentation Time Stamp)和DTS(Decode Time Stamp)的概念,以及time_base的作用。PTS表示帧的显示时间,DTS表示解码时间,而time_base用于量化时间。通过pts * av_q2d(time_base)可以计算帧的显示时间戳。文章还探讨了时间基转换的必要性,以及如何计算视频长度和音频PTS。

首先介绍下概念:
PTS:Presentation Time Stamp。PTS主要用于度量解码后的视频帧什么时候被显示出来
DTS:Decode Time Stamp。DTS主要是标识读入内存中的bit流在什么时候开始送入解码器中进行解码

也就是pts反映帧什么时候开始显示,dts反映数据流什么时候开始解码

怎么理解这里的“什么时候”呢?如果有某一帧,假设它是第10秒开始显示。那么它的pts是多少呢。是10?还是10s?还是两者都不是。

为了回答这个问题,先引入FFmpeg中时间基的概念,也就是time_base。它也是用来度量时间的。
如果把1秒分为25等份,你可以理解就是一把尺,那么每一格表示的就是1/25秒。此时的time_base={1,25}
如果你是把1秒分成90000份,每一个刻度就是1/90000秒,此时的time_base={1,90000}。

所谓时间基表示的就是每个刻度是多少秒
pts的值就是占多少个时间刻度(占多少个格子)。它的单位不是秒,而是时间刻度。只有pts加上time_base两者同时在一起,才能表达出时间是多少。

好比我只告诉你,某物体的长度占某一把尺上的20个刻度。但是我不告诉你,这把尺总共是多少厘米的,你就没办法计算每个刻度是多少厘米,你也就无法知道物体的长度。
pts=20个刻度
time_base={1,10} 每一个刻度是1/10厘米
所以物体的长度=pts * time_base=20 *1/10 厘米

在ffmpeg中。av_q2d(time_base)=每个刻度是多少秒
此时你应该不难理解 pts*av_q2d(time_base)才是帧的显示时间戳。

下面理解时间基的转换,为什么要有时间基转换。
首先,不同的封装格式,timebase是不一样的。另外,整个转码过程,不同的数据状态对应的时间基也不一致。拿mpegts封装格式25fps来说(只说视频,音频大致一样,但也略有不同)。非压缩时候的数据(即YUV或者其它),在ffmpeg中对应的结构体为AVFrame,它的时间基为AVCodecContext 的time_base ,AVRational{1,25}。
压缩后的数据(对应的结构体为AVPacket)对应的时间基为AVStream的time_base,AVRational{1,90000}。
因为数据状态不同,时间基不一样,所以我们必须转换,在1/25时间刻度下占10格,在1/90000下是占多少格。这就是pts的转换。

根据pts来计算一桢在整个视频中的时间位置:
timestamp(秒) = pts * av_q2d(st->time_base)

duration和pts单位一样,duration表示当前帧的持续时间占多少格。或者理解是两帧的间隔时间是占多少格。一定要理解单位。
pts:格子数
av_q2d(st->time_base): 秒/格

计算视频长度:
time(秒) = st->duration * av_q2d(st->time_base)

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

av_rescale_q(int64_t a, AVRational bq, AVRational cq)函数
这个函数的作用是计算a*bq / cq来把时间戳从一个时间基调整到另外一个时间基。在进行时间基转换的时候,应该首先这个函数,因为它可以避免溢出的情况发生。
函数表示在bq下的占a个格子,在cq下是多少。

关于音频pts的计算:
音频sample_rate:samples per second,即采样率,表示每秒采集多少采样点。
比如44100HZ,就是一秒采集44100个sample.
即每个sample的时间是1/44100秒

一个音频帧的AVFrame有nb_samples个sample,所以一个AVFrame耗时是nb_samples(1/44100)秒
即标准时间下duration_s=nb_samples(1/44100)秒,
转换成AVStream时间基下
duration=duration_s / av_q2d(st->time_base)
基于st->time_base的num值一般等于采样率,所以duration=nb_samples.
pts=n* duration=n *nb_samples

补充:
next_pts-current_pts=current_duration,根据数学等差公式an=a1+(n-1)* d可得pts=n *d

<think>我们正在讨论FFmpeg中的两个重要概念:pkt_pts_time和pkt_dts_time。这两个时间戳在视频处理中非常重要,用于确定帧的显示和解码时间。 下面我们将详细解释这两个概念的区别,并通过代码示例展示如何获取它们。 1. PTS (Presentation Time Stamp):表示显示时间戳,即这一帧数据应该被显示的时间。 2. DTS (Decoding Time Stamp):表示解码时间戳,即这一帧数据应该被解码的时间。 在视频流中,由于存在B帧(双向预测帧),解码顺序和显示顺序可能不同。例如,一个典型的帧序列可能是: 解码顺序:I0, P3, B1, B2, P6, B4, B5, ... 显示顺序:I0, B1, B2, P3, B4, B5, P6, ... 因此,DTSPTS可能会不同。对于没有B帧的情况,DTSPTS通常是相同的。 在FFmpeg的工具(如ffprobe)中,我们可以获取这些时间戳。在代码中,我们通常使用FFmpeg的API来访问这些值。 下面是一个使用FFmpeg的ffprobe命令行工具来查看这些时间戳的例子,以及一个使用libavcodec的C代码示例。 但由于您可能更关心在Python中如何获取,我将提供一个使用ffprobe在Python中解析帧信息的示例。 注意:我们通常使用FFmpeg的API(C语言)或者通过调用ffprobe并解析其输出来获取这些信息。在Python中,我们可以使用subprocess调用ffprobe,或者使用专门的包装库(如ffmpeg-python)。 以下是两种方法的示例: </think># FFmpeg时间戳详解:PTSDTS的区别与应用 在FFmpeg中,`pkt_pts_time`和`pkt_dts`是视频处理中的核心概念,理解它们的区别对于正确处理视频流至关重要。 ## PTSDTS的基本概念 ### PTS (Presentation Time Stamp) - **含义**:显示时间戳,表示帧应该**显示**的时间点 - **作用**:决定视频帧在播放器中何时呈现给观众 - **单位**:秒(当表示为`pkt_pts_time`时)或时间基单位 ### DTS (Decoding Time Stamp) - **含义**:解码时间戳,表示帧应该**解码**的时间点 - **作用**:指导解码器何时处理该帧数据 - **单位**:时间基单位(需要转换成秒) ## 核心区别 | 特性 | PTS (显示时间戳) | DTS (解码时间戳) | |------|-----------------|-----------------| | **作用** | 控制帧的显示时机 | 控制帧的解码时机 | | **顺序** | 显示顺序 | 解码顺序 | | **B帧影响** | 受B帧影响,顺序可能乱序 | 不受B帧影响,保持解码顺序 | | **典型单位** | 秒(`pkt_pts_time`) | 时间基单位(需转换) | | **应用场景** | 播放同步、帧显示 | 解码器处理顺序 | ## 为什么需要两个时间戳? 主要原因是**B帧(Bidirectional frames)**的存在: 1. B帧需要参考前后的帧才能解码 2. 解码顺序与显示顺序不同 **典型解码和显示顺序**: ``` 解码顺序(DTS): I0, P3, B1, B2, P6, B4, B5, ... 显示顺序(PTS): I0, B1, B2, P3, B4, B5, P6, ... ``` ## 代码示例:提取并对比PTS/DTS 以下Python脚本使用FFmpeg的ffprobe工具提取视频帧的PTSDTS信息: ```python import subprocess import json import matplotlib.pyplot as plt def extract_pts_dts(video_path): """提取视频帧的PTSDTS信息""" cmd = [ 'ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_frames', '-select_streams', 'v:0', video_path ] result = subprocess.run(cmd, capture_output=True, text=True) data = json.loads(result.stdout) frames = [] for frame in data['frames']: if frame['media_type'] == 'video': pts = float(frame.get('pkt_pts_time', -1)) dts = float(frame.get('pkt_dts_time', frame.get('pkt_dts', -1))) frame_type = frame.get('pict_type', '?') # 如果dts是时间基单位,换算成秒 if dts != -1 and 'pkt_dts_time' not in frame: time_base = frame.get('time_base', '1/1000').split('/') if len(time_base) == 2: dts /= float(time_base[0]) / float(time_base[1]) frames.append({ 'frame_num': len(frames), 'pts': pts, 'dts': dts, 'type': frame_type, 'offset': pts - dts if pts != -1 and dts != -1 else 0 }) return frames def analyze_time_stamps(frames): """分析时间戳分布并可视化""" # 分离PTSDTS pts_values = [f['pts'] for f in frames if f['pts'] != -1] dts_values = [f['dts'] for f in frames if f['dts'] != -1] frame_types = [f['type'] for f in frames] # 创建图表 plt.figure(figsize=(14, 8)) # PTSDTS折线图 plt.subplot(2, 1, 1) plt.plot(pts_values, 'b-', label='PTS (显示时间)') plt.plot(dts_values, 'r--', label='DTS (解码时间)') plt.title('PTSDTS时间线对比') plt.xlabel('帧序号') plt.ylabel('时间(秒)') plt.legend() plt.grid(True) # 时间差柱状图 plt.subplot(2, 1, 2) offsets = [f['offset'] for f in frames if f['pts'] != -1 and f['dts'] != -1] plt.bar(range(len(offsets)), offsets, color='g') plt.title('PTSDTS时间差') plt.xlabel('帧序号') plt.ylabel('时间差(秒)') plt.grid(True) plt.tight_layout() plt.savefig('pts_dts_analysis.png') plt.show() # 输出统计信息 print(f"总帧数: {len(frames)}") print(f"最大PTS-DTS差: {max(offsets):.4f}秒") print(f"最小PTS-DTS差: {min(offsets):.4f}秒") print(f"平均PTS-DTS差: {sum(offsets)/len(offsets):.4f}秒") print("帧类型分布:") print(f" I帧: {frame_types.count('I')}") print(f" P帧: {frame_types.count('P')}") print(f" B帧: {frame_types.count('B')}") # 使用示例 if __name__ == "__main__": video_file = "input_video.mp4" print(f"分析视频文件: {video_file}") frames_data = extract_pts_dts(video_file) if not frames_data: print("未提取到帧数据,请检查视频文件路径和格式") else: analyze_time_stamps(frames_data) ``` ## 关键代码解析 ### 1. 提取帧信息 ```python cmd = [ 'ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_frames', '-select_streams', 'v:0', video_path ] ``` 使用ffprobe命令提取视频流的所有帧信息,输出为JSON格式以便解析。 ### 2. 时间基转换 ```python if dts != -1 and 'pkt_dts_time' not in frame: time_base = frame.get('time_base', '1/1000').split('/') if len(time_base) == 2: dts /= float(time_base[0]) / float(time_base[1]) ``` 当DTS以时间基单位给出时,将其转换为秒。时间基通常表示为分数(如1/90000)。 ### 3. 可视化分析 ```python plt.subplot(2, 1, 1) plt.plot(pts_values, 'b-', label='PTS (显示时间)') plt.plot(dts_values, 'r--', label='DTS (解码时间)') ``` 生成两个子图: 1. PTSDTS的时间线对比 2. PTSDTS差值的柱状图 ## 实际应用场景 ### 1. 视频同步 ```python # 计算音视频同步 audio_pts = get_audio_pts() video_pts = get_video_pts() sync_diff = video_pts - audio_pts if abs(sync_diff) > SYNC_THRESHOLD: adjust_playback_speed(sync_diff) ``` ### 2. 帧精确编辑 ```python def cut_video_precisely(input_path, output_path, start_pts, end_pts): """基于PTS的精确视频切割""" cmd = [ 'ffmpeg', '-i', input_path, '-vf', f"select='between(pts,{start_pts},{end_pts})'", '-af', f"aselect='between(pts,{start_pts},{end_pts})'", '-vsync', '0', output_path ] subprocess.run(cmd) ``` ### 3. B帧处理 ```python def reorder_b_frames(frames): """为B帧帧重新排序""" # 按DTS排序获取解码顺序 decode_order = sorted(frames, key=lambda x: x['dts']) # 按PTS排序获取显示顺序 display_order = sorted(frames, key=lambda x: x['pts']) # 处理B帧依赖关系 for frame in decode_order: if frame['type'] == 'B': # 确保参考帧已解码 ensure_reference_frames_decoded(frame) ``` ## 常见问题解决方案 ### 问题1:PTS/DTS值为NOPTS(-1) **解决方案**: ```python if pts == -1: # 使用前一帧的PTS加上估计的帧持续时间 estimated_pts = prev_pts + frame_duration ``` ### 问题2:时间戳跳变 **解决方案**: ```python def detect_pts_jumps(frames, threshold=1.0): """检测异常的PTS跳变""" for i in range(1, len(frames)): diff = frames[i]['pts'] - frames[i-1]['pts'] if diff > threshold: print(f"警告: 帧{i}处检测到PTS跳变: {diff:.3f}秒") # 自动校正或标记问题区域 ``` ### 问题3:音视频同步问题 **解决方案**: ```python def calculate_av_drift(audio_frames, video_frames): """计算音视频漂移""" audio_pts = [f['pts'] for f in audio_frames] video_pts = [f['pts'] for f in video_frames] # 使用线性回归计算漂移 slope, intercept = np.polyfit(video_pts, audio_pts, 1) print(f"音视频漂移: 斜率={slope:.6f}, 截距={intercept:.3f}") # 校正处理 if abs(intercept) > 0.1 or abs(slope - 1.0) > 0.001: apply_av_correction(slope, intercept) ``` ## 高级话题:时间基(Time Base)转换 FFmpeg使用时间基来表示时间戳的精度: ```c // FFmpeg中的时间基结构 typedef struct AVRational{ int num; // 分子 int den; // 分母 } AVRational; ``` 转换公式: ``` 秒 = 时间戳 &times; (时间基分子 / 时间基分母) ``` Python转换函数: ```python def timestamp_to_seconds(ts, time_base): """将时间戳转换为秒""" if isinstance(time_base, str): num, den = map(int, time_base.split('/')) else: num, den = time_base.num, time_base.den return ts * (num / den) def seconds_to_timestamp(seconds, time_base): """将秒转换为时间戳""" if isinstance(time_base, str): num, den = map(int, time_base.split('/')) else: num, den = time_base.num, time_base.den return int(seconds / (num / den)) ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值