av_packet_rescale_ts是FFmpeg中用于将AVPacket时间戳从一个时间基(timebase)转换到另一个时间基的函数。
一、函数原型
void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational dst_tb);
参数说明
-
pkt: 要转换时间戳的AVPacket指针
-
tb_src: 源时间基(当前packet使用的时间基)
-
dst_tb: 目标时间基(要转换到的时间基)
二、工作原理
2.1 时间基转换公式
新时间戳 = 旧时间戳 × (src_timebase / dst_timebase)
2.2 内部实现逻辑
// 简化的av_packet_rescale_ts实现
void av_packet_rescale_ts_impl(AVPacket *pkt, AVRational src_tb, AVRational dst_tb) {
if (pkt->pts != AV_NOPTS_VALUE)
pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
if (pkt->dts != AV_NOPTS_VALUE)
pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
if (pkt->duration > 0)
pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb);
if (pkt->duration < 0)
pkt->duration = 0;
}
三、基本用法
3.1 基本时间戳转换
#include <libavcodec/avcodec.h>
#include <stdio.h>
void basic_rescale_example() {
AVPacket *pkt = av_packet_alloc();
if (!pkt) {
return;
}
// 模拟一个packet
pkt->pts = 1000;
pkt->dts = 900;
pkt->duration = 100;
// 定义时间基
// 源时间基: 1/1000 (毫秒)
AVRational src_tb = {1, 1000};
// 目标时间基: 1/90000 (常见于MPEG-TS)
AVRational dst_tb = {1, 90000};
printf("转换前:\n");
printf(" PTS: %ld\n", pkt->pts);
printf(" DTS: %ld\n", pkt->dts);
printf(" Duration: %ld\n", pkt->duration);
printf(" 源时间基: %d/%d\n", src_tb.num, src_tb.den);
printf(" 目标时间基: %d/%d\n", dst_tb.num, dst_tb.den);
// 转换时间戳
av_packet_rescale_ts(pkt, src_tb, dst_tb);
printf("\n转换后:\n");
printf(" PTS: %ld\n", pkt->pts);
printf(" DTS: %ld\n", pkt->dts);
printf(" Duration: %ld\n", pkt->duration);
// 验证转换结果
double src_pts_seconds = pkt->pts * (double)src_tb.num / src_tb.den;
double dst_pts_seconds = pkt->pts * (double)dst_tb.num / dst_tb.den;
printf(" PTS转换验证: %.3fs (应保持不变)\n", dst_pts_seconds);
av_packet_free(&pkt);
}
3.2 在流之间转换时间戳
#include <libavformat/avformat.h>
int rescale_between_streams_example(const char *input_filename,
const char *output_filename) {
AVFormatContext *input_ctx = NULL;
AVFormatContext *output_ctx = NULL;
AVPacket *pkt = NULL;
int ret = 0;
// 打开输入文件
if ((ret = avformat_open_input(&input_ctx, input_filename, NULL, NULL)) < 0) {
fprintf(stderr, "无法打开输入文件: %s\n", av_err2str(ret));
return ret;
}
// 获取流信息
if ((ret = avformat_find_stream_info(input_ctx, NULL)) < 0) {
fprintf(stderr, "无法获取流信息: %s\n", av_err2str(ret));
goto cleanup;
}
// 创建输出格式上下文
if ((ret = avformat_alloc_output_context2(&output_ctx, NULL, NULL, output_filename)) < 0) {
fprintf(stderr, "无法创建输出上下文: %s\n", av_err2str(ret));
goto cleanup;
}
pkt = av_packet_alloc();
if (!pkt) {
ret = AVERROR(ENOMEM);
goto cleanup;
}
printf("输入文件信息:\n");
printf(" 格式: %s\n", input_ctx->iformat->name);
printf(" 时长: %.2f秒\n", (double)input_ctx->duration / AV_TIME_BASE);
// 读取并转换packet
int packet_count = 0;
while (1) {
av_packet_unref(pkt);
ret = av_read_frame(input_ctx, pkt);
if (ret < 0) {
if (ret == AVERROR_EOF) {
printf("文件读取完成,共 %d 个packet\n", packet_count);
ret = 0;
} else {
fprintf(stderr, "读取错误: %s\n", av_err2str(ret));
}
break;
}
packet_count++;
// 获取输入流的时间基
AVStream *in_stream = input_ctx->streams[pkt->stream_index];
AVRational src_tb = in_stream->time_base;
// 定义目标时间基(例如转换为毫秒精度)
AVRational dst_tb = {1, 1000}; // 1/1000,毫秒级
// 保存原始时间戳
int64_t original_pts = pkt->pts;
int64_t original_dts = pkt->dts;
int64_t original_duration = pkt->duration;
// 转换时间戳
av_packet_rescale_ts(pkt, src_tb, dst_tb);
// 计算时间(秒)
double src_pts_seconds = original_pts * av_q2d(src_tb);
double dst_pts_seconds = pkt->pts * av_q2d(dst_tb);
printf("Packet #%d:\n", packet_count);
printf(" 原始: PTS=%ld (%.3fs), DTS=%ld, Duration=%ld\n",
original_pts, src_pts_seconds, original_dts, original_duration);
printf(" 转换后: PTS=%ld (%.3fs), DTS=%ld, Duration=%ld\n",
pkt->pts, dst_pts_seconds, pkt->dts, pkt->duration);
// 验证转换准确性
if (fabs(src_pts_seconds - dst_pts_seconds) > 0.001) {
printf(" 警告: 时间戳转换可能存在误差!\n");
}
}
cleanup:
av_packet_free(&pkt);
if (input_ctx) {
avformat_close_input(&input_ctx);
}
if (output_ctx) {
avformat_free_context(output_ctx);
}
return ret;
}
四、实际应用场景
4.1 转封装时的应用
#include <libavformat/avformat.h>
int remux_with_rescale(const char *input_file, const char *output_file) {
AVFormatContext *in_ctx = NULL, *out_ctx = NULL;
AVPacket *pkt = av_packet_alloc();
int ret = 0;
int *stream_mapping = NULL;
int stream_mapping_size = 0;
if (!pkt) {
return AVERROR(ENOMEM);
}
// 1. 打开输入文件
if ((ret = avformat_open_input(&in_ctx, input_file, NULL, NULL)) < 0) {
fprintf(stderr, "无法打开输入文件: %s\n", av_err2str(ret));
goto end;
}
if ((ret = avformat_find_stream_info(in_ctx, NULL)) < 0) {
fprintf(stderr, "无法获取流信息: %s\n", av_err2str(ret));
goto end;
}
// 2. 创建输出上下文
avformat_alloc_output_context2(&out_ctx, NULL, NULL, output_file);
if (!out_ctx) {
fprintf(stderr, "无法创建输出上下文\n");
ret = AVERROR_UNKNOWN;
goto end;
}
stream_mapping_size = in_ctx->nb_streams;
stream_mapping = av_malloc_array(stream_mapping_size, sizeof(*stream_mapping));
if (!stream_mapping) {
ret = AVERROR(ENOMEM);
goto end;
}
// 3. 创建输出流
for (int i = 0; i < in_ctx->nb_streams; i++) {
AVStream *in_stream = in_ctx->streams[i];
AVStream *out_stream = NULL;
if (in_stream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_stream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
in_stream->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
stream_mapping[i] = -1;
continue;
}
out_stream = avformat_new_stream(out_ctx, NULL);
if (!out_stream) {
fprintf(stderr, "无法创建输出流\n");
ret = AVERROR(ENOMEM);
goto end;
}
// 复制编解码器参数
ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
if (ret < 0) {
fprintf(stderr, "无法复制编解码器参数\n");
goto end;
}
out_stream->codecpar->codec_tag = 0;
stream_mapping[i] = i;
}
// 4. 打开输出文件
if (!(out_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&out_ctx->pb, output_file, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "无法打开输出文件: %s\n", av_err2str(ret));
goto end;
}
}
// 5. 写入头部
ret = avformat_write_header(out_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "写入文件头失败: %s\n", av_err2str(ret));
goto end;
}
// 6. 读取、转换并写入packet
while (1) {
AVStream *in_stream, *out_stream;
ret = av_read_frame(in_ctx, pkt);
if (ret < 0) {
break; // 文件结束或错误
}
in_stream = in_ctx->streams[pkt->stream_index];
int stream_idx = stream_mapping[pkt->stream_index];
if (stream_idx < 0) {
av_packet_unref(pkt);
continue;
}
pkt->stream_index = stream_idx;
out_stream = out_ctx->streams[pkt->stream_index];
// 关键:转换时间戳
av_packet_rescale_ts(pkt, in_stream->time_base, out_stream->time_base);
// 写入packet
ret = av_interleaved_write_frame(out_ctx, pkt);
if (ret < 0) {
fprintf(stderr, "写入frame错误: %s\n", av_err2str(ret));
break;
}
av_packet_unref(pkt);
}
// 7. 写入尾部
av_write_trailer(out_ctx);
printf("转封装完成\n");
end:
av_packet_free(&pkt);
if (in_ctx) {
avformat_close_input(&in_ctx);
}
if (out_ctx && !(out_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&out_ctx->pb);
}
if (out_ctx) {
avformat_free_context(out_ctx);
}
av_free(stream_mapping);
return ret;
}
4.2 处理B帧的时间戳
void handle_b_frames_with_rescale(AVPacket *pkt, AVStream *stream) {
// 检查是否是B帧
int is_b_frame = 0;
if (pkt->pts != AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE) {
is_b_frame = (pkt->pts < pkt->dts);
}
if (is_b_frame) {
printf("检测到B帧: PTS=%ld, DTS=%ld\n", pkt->pts, pkt->dts);
// 保存原始时间戳
int64_t original_pts = pkt->pts;
int64_t original_dts = pkt->dts;
// 转换为目标时间基
AVRational dst_tb = {1, 90000}; // 常见的目标时间基
av_packet_rescale_ts(pkt, stream->time_base, dst_tb);
printf("转换后: PTS=%ld, DTS=%ld (时间基: %d/%d)\n",
pkt->pts, pkt->dts, dst_tb.num, dst_tb.den);
// 计算时间差
double pts_seconds = pkt->pts * av_q2d(dst_tb);
double dts_seconds = pkt->dts * av_q2d(dst_tb);
double time_diff = pts_seconds - dts_seconds;
printf(" 时间差: PTS-DTS = %.3f秒\n", time_diff);
// 验证B帧属性仍然保持
if (pkt->pts < pkt->dts) {
printf(" B帧属性保持\n");
}
}
}
五、时间基转换详解
5.1 常见的时间基
void common_timebases_example() {
printf("常见时间基:\n");
// 1. 微秒级 (1/1,000,000)
AVRational us_tb = {1, 1000000};
printf("1. 微秒: %d/%d\n", us_tb.num, us_tb.den);
// 2. 毫秒级 (1/1,000)
AVRational ms_tb = {1, 1000};
printf("2. 毫秒: %d/%d\n", ms_tb.num, ms_tb.den);
// 3. MPEG-TS 时钟 (1/90,000)
AVRational mpegts_tb = {1, 90000};
printf("3. MPEG-TS: %d/%d\n", mpegts_tb.num, mpegts_tb.den);
// 4. 毫秒的1.001倍 (用于NTSC)
AVRational ntsc_tb = {1001, 30000};
printf("4. NTSC: %d/%d\n", ntsc_tb.num, ntsc_tb.den);
// 5. 采样率作为时间基 (音频常见)
AVRational audio_tb = {1, 44100}; // 44.1kHz
printf("5. 音频(44.1kHz): %d/%d\n", audio_tb.num, audio_tb.den);
}
5.2 时间基转换工具函数
#include <math.h>
// 将时间戳转换为秒
double timestamp_to_seconds(int64_t ts, AVRational time_base) {
if (ts == AV_NOPTS_VALUE) {
return NAN;
}
return ts * av_q2d(time_base);
}
// 将秒转换为时间戳
int64_t seconds_to_timestamp(double seconds, AVRational time_base) {
if (isnan(seconds)) {
return AV_NOPTS_VALUE;
}
return (int64_t)(seconds / av_q2d(time_base));
}
// 手动转换时间戳(不使用av_packet_rescale_ts)
void manual_rescale_example() {
AVPacket *pkt = av_packet_alloc();
pkt->pts = 1000;
pkt->dts = 900;
pkt->duration = 100;
AVRational src_tb = {1, 1000}; // 毫秒
AVRational dst_tb = {1, 90000}; // MPEG-TS
printf("手动转换示例:\n");
// 1. 先将时间戳转换为秒
double pts_seconds = timestamp_to_seconds(pkt->pts, src_tb);
double dts_seconds = timestamp_to_seconds(pkt->dts, src_tb);
double duration_seconds = timestamp_to_seconds(pkt->duration, src_tb);
printf(" 转换为秒: PTS=%.3fs, DTS=%.3fs, Duration=%.3fs\n",
pts_seconds, dts_seconds, duration_seconds);
// 2. 再将秒转换为目标时间基
int64_t new_pts = seconds_to_timestamp(pts_seconds, dst_tb);
int64_t new_dts = seconds_to_timestamp(dts_seconds, dst_tb);
int64_t new_duration = seconds_to_timestamp(duration_seconds, dst_tb);
printf(" 转换为目标时间基: PTS=%ld, DTS=%ld, Duration=%ld\n",
new_pts, new_dts, new_duration);
// 3. 使用av_packet_rescale_ts验证
av_packet_rescale_ts(pkt, src_tb, dst_tb);
printf(" av_packet_rescale_ts结果: PTS=%ld, DTS=%ld, Duration=%ld\n",
pkt->pts, pkt->dts, pkt->duration);
av_packet_free(&pkt);
}
六、高级应用
6.1 批量转换时间戳
void batch_rescale_packets(AVPacket **packets, int count,
AVRational src_tb, AVRational dst_tb) {
printf("批量转换 %d 个packet的时间戳\n", count);
printf("从时间基 %d/%d 到 %d/%d\n",
src_tb.num, src_tb.den, dst_tb.num, dst_tb.den);
for (int i = 0; i < count; i++) {
if (packets[i]) {
int64_t original_pts = packets[i]->pts;
int64_t original_dts = packets[i]->dts;
av_packet_rescale_ts(packets[i], src_tb, dst_tb);
printf("Packet[%d]: PTS %ld -> %ld, DTS %ld -> %ld\n",
i, original_pts, packets[i]->pts,
original_dts, packets[i]->dts);
}
}
}
6.2 时间戳对齐
typedef struct {
int64_t start_pts;
AVRational time_base;
} StreamTiming;
int align_stream_timestamps(StreamTiming *streams, int num_streams,
AVPacket *pkt, int stream_index) {
if (stream_index < 0 || stream_index >= num_streams) {
return AVERROR(EINVAL);
}
StreamTiming *stream = &streams[stream_index];
// 如果是第一个packet,设置起始PTS
if (stream->start_pts == AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE) {
stream->start_pts = pkt->pts;
printf("流 %d 起始PTS设置为: %ld\n", stream_index, stream->start_pts);
}
// 调整PTS使其从0开始
if (pkt->pts != AV_NOPTS_VALUE && stream->start_pts != AV_NOPTS_VALUE) {
pkt->pts -= stream->start_pts;
}
// 调整DTS
if (pkt->dts != AV_NOPTS_VALUE && stream->start_pts != AV_NOPTS_VALUE) {
pkt->dts -= stream->start_pts;
}
return 0;
}
七、常见问题与解决方案
7.1 时间戳溢出
void handle_timestamp_overflow() {
AVPacket *pkt = av_packet_alloc();
AVRational src_tb = {1, 1}; // 秒
AVRational dst_tb = {1, 90000}; // MPEG-TS
// 非常大的时间戳
pkt->pts = INT64_MAX / 2; // 很大的值
pkt->dts = pkt->pts - 1000;
printf("处理大时间戳:\n");
printf("原始PTS: %ld (可能溢出)\n", pkt->pts);
// 使用av_rescale_q手动转换,检查溢出
int64_t new_pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
int64_t new_dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
// 检查是否溢出
if (new_pts < 0 || new_dts < 0) {
printf("警告: 时间戳转换可能溢出!\n");
}
// 使用av_packet_rescale_ts
av_packet_rescale_ts(pkt, src_tb, dst_tb);
printf("转换后PTS: %ld\n", pkt->pts);
av_packet_free(&pkt);
}
7.2 负时间戳处理
void handle_negative_timestamps() {
AVPacket *pkt = av_packet_alloc();
AVRational src_tb = {1, 1000}; // 毫秒
AVRational dst_tb = {1, 90000}; // MPEG-TS
// 负时间戳(某些情况下可能出现)
pkt->pts = -100; // -0.1秒
pkt->dts = -200; // -0.2秒
pkt->duration = 100;
printf("处理负时间戳:\n");
printf("转换前: PTS=%ld, DTS=%ld\n", pkt->pts, pkt->dts);
// 转换
av_packet_rescale_ts(pkt, src_tb, dst_tb);
printf("转换后: PTS=%ld, DTS=%ld\n", pkt->pts, pkt->dts);
// 验证转换结果
if (pkt->pts < 0 || pkt->dts < 0) {
printf("注意: 时间戳仍为负值\n");
// 如果需要,可以偏移到0
int64_t offset = -pkt->dts; // 使用DTS作为偏移
pkt->pts += offset;
pkt->dts += offset;
printf("偏移后: PTS=%ld, DTS=%ld\n", pkt->pts, pkt->dts);
}
av_packet_free(&pkt);
}
八、性能优化
8.1 重用时间基转换结果
typedef struct {
AVRational src_tb;
AVRational dst_tb;
double conversion_factor; // 转换因子
} RescaleContext;
void init_rescale_context(RescaleContext *ctx,
AVRational src_tb, AVRational dst_tb) {
ctx->src_tb = src_tb;
ctx->dst_tb = dst_tb;
ctx->conversion_factor = av_q2d(src_tb) / av_q2d(dst_tb);
}
int64_t fast_rescale(RescaleContext *ctx, int64_t timestamp) {
if (timestamp == AV_NOPTS_VALUE) {
return AV_NOPTS_VALUE;
}
return (int64_t)(timestamp * ctx->conversion_factor);
}
void fast_batch_rescale(RescaleContext *ctx, AVPacket **packets, int count) {
for (int i = 0; i < count; i++) {
if (packets[i]) {
packets[i]->pts = fast_rescale(ctx, packets[i]->pts);
packets[i]->dts = fast_rescale(ctx, packets[i]->dts);
if (packets[i]->duration > 0) {
packets[i]->duration = fast_rescale(ctx, packets[i]->duration);
}
}
}
}
九、最佳实践
9.1 完整的时间戳处理流程
int process_packet_with_timestamps(AVPacket *pkt, AVStream *stream,
AVRational target_timebase) {
int ret = 0;
// 1. 检查时间戳有效性
if (pkt->pts == AV_NOPTS_VALUE) {
fprintf(stderr, "警告: PTS未设置\n");
// 可以使用DTS或估计值
}
if (pkt->dts == AV_NOPTS_VALUE) {
fprintf(stderr, "警告: DTS未设置\n");
// 如果PTS有效,可以使用PTS
if (pkt->pts != AV_NOPTS_VALUE) {
pkt->dts = pkt->pts;
}
}
// 2. 验证时间戳顺序
if (pkt->pts != AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE) {
if (pkt->dts > pkt->pts) {
printf("注意: DTS > PTS,可能是B帧\n");
}
}
// 3. 转换时间戳
av_packet_rescale_ts(pkt, stream->time_base, target_timebase);
// 4. 验证转换结果
if (pkt->pts != AV_NOPTS_VALUE && pkt->pts < 0) {
printf("注意: 转换后PTS为负值\n");
}
if (pkt->dts != AV_NOPTS_VALUE && pkt->dts < 0) {
printf("注意: 转换后DTS为负值\n");
}
// 5. 记录时间信息
double pts_seconds = timestamp_to_seconds(pkt->pts, target_timebase);
double dts_seconds = timestamp_to_seconds(pkt->dts, target_timebase);
printf("处理完成: PTS=%.3fs, DTS=%.3fs\n", pts_seconds, dts_seconds);
return ret;
}
十、总结
-
功能: 转换AVPacket中的pts、dts、duration到新的时间基
-
使用场景: 转封装、时间基标准化、多流同步等
-
注意事项:
-
会自动处理AV_NOPTS_VALUE
-
使用av_rescale_q避免溢出
-
只转换有效的duration (duration > 0)
-
-
最佳实践:
-
总是在写入输出前转换时间戳
-
检查时间戳的有效性
-
处理负时间戳和溢出的情况
-
记录时间基转换的日志
-
-
性能考虑: 批量处理时可以使用缓存转换因子
// 标准使用模式
void standard_usage(AVPacket *pkt, AVStream *src_stream, AVStream *dst_stream) {
// 保存原始时间戳(用于调试)
int64_t original_pts = pkt->pts;
int64_t original_dts = pkt->dts;// 转换时间戳 av_packet_rescale_ts(pkt, src_stream->time_base, dst_stream->time_base); // 记录转换 printf("时间戳转换: PTS %ld->%ld, DTS %ld->%ld\n", original_pts, pkt->pts, original_dts, pkt->dts);}