欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

FFMPEG的AVFormatContext、AVStream、AVPacket、AVFrame时间戳分析

程序员文章站 2022-05-02 15:06:01
...

这里FFMPEG版本用的3.3.3

时间戳描述

  • AVFormatContext
  /**
   * Duration of the stream, in AV_TIME_BASE fractional
   * seconds. Only set this value if you know none of the individual stream
   * durations and also do not set any of them. This is deduced from the
   * AVStream values if not set.
   *
   * Demuxing only, set by libavformat.
   */
  int64_t duration;

duration表示整个数据流的时长,获取正常时长的时候要除以AV_TIME_BASE,得到的结果单位是秒,另外如果这个值没有被libavformat设置的时候,则通过AVStream中的相关时间值推导而来。


  • AVStream
 /**
  * Decoding: duration of the stream, in stream time base.
  * If a source file does not specify a duration, but does specify
  * a bitrate, this value will be estimated from bitrate and file size.
  *
  * Encoding: May be set by the caller before avformat_write_header() to
  * provide a hint to the muxer about the estimated duration.
  */
 int64_t duration;

这里的duration也表示整个数据流的时长,这里的数据流分视频流、音频流、字幕流等,获取正常的时长时要乘以av_q2d(time_base),具体计算下面测试用例会提到,计算出来的时长值单位也是秒,如果一个源文件没设置这个时长值,但是有指定对应的码率的时候,则这个时长可以通道码率和文件大小来推导。


  • AVPacket
  /**
   * Presentation timestamp in AVStream->time_base units; the time at which
   * the decompressed packet will be presented to the user.
   * Can be AV_NOPTS_VALUE if it is not stored in the file.
   * pts MUST be larger or equal to dts as presentation cannot happen before
   * decompression, unless one wants to view hex dumps. Some formats misuse
   * the terms dts and pts/cts to mean something different. Such timestamps
   * must be converted to true pts/dts before they are stored in AVPacket.
   */
  int64_t pts;

 /**
  * Decompression timestamp in AVStream->time_base units; the time at which
  * the packet is decompressed.
  * Can be AV_NOPTS_VALUE if it is not stored in the file.
  */
 int64_t dts;

pts是以对应数据流中AVStream->time_base为计算单位,表示这个解压包何时显示。
dts也是以对应数据流中AVStream->time_base为计算单位,表示这个包何时被用来解包出一帧数据。
这里对pts和dts就不做具体的介绍,后面文章会有专门的介绍。


  • AVFrame
  /**
   * Presentation timestamp in time_base units (time when frame should be shown to user).
   */
  int64_t pts;

这里的pts也是以对应数据流包中的AVStream->time_base为计算单位的,表示这一帧何时用来解码显示。


时间戳测试

  • AVFormatContext 、AVStream
    测试验证代码:
printf("***********************************************\n");
        av_dump_format(pFormatCtx, 0, 0, 0);
        printf("***********************************************\n");

        printf("fmtContext->duration = %lf\n",(pFormatCtx->duration *1.0 / AV_TIME_BASE) * 1000);
        printf("videoStream->duration = %lf\n", objVideoStream->duration * av_q2d(objVideoStream->time_base) * 1000);
        printf("audioStream->duration = %lf\n", objAudioStream->duration * av_q2d(objAudioStream->time_base)* 1000);

下面贴出几组测试结果图:
FFMPEG的AVFormatContext、AVStream、AVPacket、AVFrame时间戳分析


FFMPEG的AVFormatContext、AVStream、AVPacket、AVFrame时间戳分析


FFMPEG的AVFormatContext、AVStream、AVPacket、AVFrame时间戳分析

从上面的测试结果来看av_dump_format在Duration一行输出了整个视频文件的时长,这里咱们看下av_dump_format内部对Duration值输出实现代码:

void av_dump_format(AVFormatContext *ic, int index,
                    const char *url, int is_output)
{
        ...省略其它处理代码

        av_log(NULL, AV_LOG_INFO, "  Duration: ");
        if (ic->duration != AV_NOPTS_VALUE) 
        {
            int hours, mins, secs, us;
            int64_t duration = ic->duration + (ic->duration <= INT64_MAX - 5000 ? 5000 : 0);
            secs  = duration / AV_TIME_BASE;
            us    = duration % AV_TIME_BASE;
            mins  = secs / 60;
            secs %= 60;
            hours = mins / 60;
            mins %= 60;
            av_log(NULL, AV_LOG_INFO, "%02d:%02d:%02d.%02d", hours, mins, secs,
        }

        ...省略其它处理代码
}

从上面可以看出av_dump_format对整个文件时长的计算就是用的AVFormatContext里面的duration计算的,计算是都是除以AV_TIME_BASE的。另外输出的测试结果对每条流的时长计算方式也是如我们上诉所写的一样。


  • AVPacket
    测试验证代码:
objPacket.pts*av_q2d(pFormatCtx->streams[stream_index]->time_base) * 1000
  • AVFrame:
av_frame_get_best_effort_timestamp(frame)*av_q2d(pFormatCtx->streams[stream_index]->time_base) * 1000

这里说明下乘以1000是为了得到毫秒,另外AVFrame的时间计算是用av_frame_get_best_effort_timestamp来获取 AVFrame中的best_effort_timestamp来进行计算的,这里为什么不用AVFrame中的pts来直接计算呢?其实大多数情况下AVFrame的pts和best_effort_timestamp值是一样的,但是用best_effort_timestamp为什么好些呢,看下面的介绍就知道了。

 /**
  * frame timestamp estimated using various heuristics, in stream time base
  * - encoding: unused
  * - decoding: set by libavcodec, read by user.
  */
 int64_t best_effort_timestamp;

这里表示一帧时间戳计算会通过各种方式推导得到。另外在看下AVFrame中的best_effort_timestamp是如何得到的:

int attribute_align_arg avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    ...省略其它代码

      if (avctx->codec->receive_frame) {
        if (avctx->internal->draining && !(avctx->codec->capabilities & AV_CODEC_CAP_DELAY))
            return AVERROR_EOF;
        ret = avctx->codec->receive_frame(avctx, frame);
        if (ret >= 0) {
            if (av_frame_get_best_effort_timestamp(frame) == AV_NOPTS_VALUE) {
                av_frame_set_best_effort_timestamp(frame,
                guess_correct_pts(avctx, frame->pts, frame->pkt_dts));
            }
        }
        return ret;
    }

    ...省略其它代码
}

其实通道上面的代码我们可以看到 best_effort_timestamp值的得到通过guess_correct_pts(avctx, frame->pts, frame->pkt_dts)来计算推导,基本大多数情况下用的是frame->pts。


总结:

通过上面我们的介绍我们基本对ffmpeg的时间戳有了个大概的了解,另外对于一些做视频媒体播放的设计,对时间进度条的操作,最好是音频流的pts来操作,通过上面的介绍相信大家都知道原因。

相关标签: ffmpeg 时间戳