在FFmpeg中,时间基(timebase)、PTS(Presentation Time Stamp)、DTS(Decoding Time Stamp)和duration是处理视频和音频流时非常重要的概念。下面我将详细解释这些概念,并提供一个使用FFmpeg库处理这些时间相关信息的C代码示例。
时间基(Time Base)
时间基是FFmpeg中表示时间的基本单位,它是一个有理数(分数),通常由分子和分母组成。时间基定义了时间戳(PTS、DTS)和时长(duration)的精度。
PTS(Presentation Time Stamp)
PTS是数据包(或帧)应该被显示给用户的时间点,它是以时间基为单位的。对于视频流,PTS决定了帧的显示顺序。
DTS(Decoding Time Stamp)
DTS是数据包(或帧)应该被解码的时间点,也是以时间基为单位的。对于视频流,DTS决定了帧的解码顺序。
Duration
Duration是数据包(或帧)的时长,同样是以时间基为单位的。它表示从当前数据包(或帧)的开始到下一个数据包(或帧)的开始之间的时间间隔。
C代码示例
下面是一个使用FFmpeg库读取视频文件并打印每帧的PTS、DTS和duration的C代码示例:
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <input file>\n", argv[0]);
return 1;
}
const char *input_filename = argv[1];
AVFormatContext *fmt_ctx = NULL;
int video_stream_index = -1;
AVPacket pkt;
AVFrame *frame = NULL;
AVRational time_base;
// Initialize libavformat and register all muxers, demuxers and protocols.
av_register_all();
// Open input file and read header.
if (avformat_open_input(&fmt_ctx, input_filename, NULL, NULL) != 0) {
fprintf(stderr, "Could not open input file '%s'.\n", input_filename);
return 1;
}
// Retrieve stream information.
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
fprintf(stderr, "Could not find stream information.\n");
avformat_close_input(&fmt_ctx);
return 1;
}
// Find the first video stream.
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_index = i;
time_base = fmt_ctx->streams[i]->time_base;
break;
}
}
if (video_stream_index == -1) {
fprintf(stderr, "Could not find a video stream.\n");
avformat_close_input(&fmt_ctx);
return 1;
}
// Allocate video frame.
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate video frame.\n");
avformat_close_input(&fmt_ctx);
return 1;
}
// Read frames from the file.
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == video_stream_index) {
// Decode video frame.
int response = avcodec_send_packet(fmt_ctx->streams[video_stream_index]->codec, &pkt);
if (response < 0) {
fprintf(stderr, "Error sending packet for decoding.\n");
break;
}
while (response >= 0) {
response = avcodec_receive_frame(fmt_ctx->streams[video_stream_index]->codec, frame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
} else if (response < 0) {
fprintf(stderr, "Error receiving frame from decoder.\n");
goto end;
}
// Print PTS, DTS, and duration.
printf("Frame PTS: %"PRId64", DTS: %"PRId64", Duration: %"PRId64"\n",
av_rescale_q(frame->pts, fmt_ctx->streams[video_stream_index]->time_base, AV_TIME_BASE),
av_rescale_q(pkt.dts, fmt_ctx->streams[video_stream_index]->time_base, AV_TIME_BASE),
av_rescale_q(pkt.duration, fmt_ctx->streams[video_stream_index]->time_base, AV_TIME_BASE));
}
}
// Free the packet.
av_packet_unref(&pkt);
}
end:
// Free the frame.
av_frame_free(&frame);
// Close the input file.
avformat_close_input(&fmt_ctx);
return 0;
}
注意事项
- 时间基转换:在打印PTS、DTS和duration时,我使用了
av_rescale_q
函数将它们从流的时间基转换为AV_TIME_BASE
(通常是1/1000000
,即微秒)。这是为了更容易理解和比较这些时间值。 - 错误处理:代码中的错误处理是简化的,实际使用中可能需要更详细的错误检查和恢复逻辑。
- 解码器:这个示例代码假设了视频流有一个可用的解码器,并且解码器能够成功解码帧。在实际应用中,可能需要检查解码器的配置和状态。
- 内存管理:FFmpeg使用了自己的内存管理机制,因此请确保在不再需要时释放所有分配的内存和资源。
- FFmpeg版本:这个示例代码基于FFmpeg的C API,并且可能需要根据您使用的FFmpeg版本进行调整。特别是,FFmpeg的API在不同版本之间可能会有所变化。
- 编译:要编译这个示例代码,您需要链接FFmpeg库。这通常涉及到设置正确的编译器和链接器标志,以及确保FFmpeg的开发文件(头文件和库文件)在您的构建环境中可用。