FFmpeg 3.0 计算视频时长

作者: 熊皮皮 | 来源:发表于2016-04-09 21:33 被阅读3520次

    1、问题:AVFormatContextAVStream都有duration: int64_t字段,调用av_dump_format()打印的多媒体文件时长是怎么算出来的呢?

    2、av_dump_format输出示例

      Metadata:
        major_brand     : qt  
        minor_version   : 0
        compatible_brands: qt  
        creation_time   : 2014-01-26 20:14:21
      Duration: 00:00:04.00, start: 0.063733, bitrate: 7987 kb/s
    

    3、AVStream对于duration的描述如下:

    /**
     * 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.
     */
    int64_t duration;
    

    4、AVFormatContext对于duration的描述如下:

    /**
     * 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;
    

    5、int av_read_frame(AVFormatContext *s, AVPacket *pkt);有如下描述:

     * pkt->pts, pkt->dts and pkt->duration are always set to correct
     * values in AVStream.time_base units (and guessed if the format cannot
     * provide them). pkt->pts can be AV_NOPTS_VALUE if the video format
     * has B-frames, so it is better to rely on pkt->dts if you do not
     * decompress the payload.
     *
     * @return 0 if OK, < 0 on error or end of file
     */
    int av_read_frame(AVFormatContext *s, AVPacket *pkt);
    

    6、avformat.h的描述

     * AVPacket.pts, AVPacket.dts and AVPacket.duration timing information will be
     * set if known. They may also be unset (i.e. AV_NOPTS_VALUE for
     * pts/dts, 0 for duration) if the stream does not provide them. The timing
     * information will be in AVStream.time_base units, i.e. it has to be
     * multiplied by the timebase to convert them to seconds.
    

    7、验证

    从前述可知,duration以AV_TIME_BASE分数形式表示。若写入文件时不设置AVFormatContext的duration字段值,则从AVStream对应的字段中推断得出。那么,我理解的计算方式为:

    IF AVFormatContext.duration != AV_NOPTS_VALUE
        time = AVFormatContext.duration * (1.0 * AVStream.num / AVStream.den)
    THEN
        time = AVStream.duration * (1.0 * AVStream.num / AVStream.den)
    

    time 的组成为123...n,从右往左数三个位为毫秒,剩余值为秒。

    简单设计一段粗暴的验证代码:

    AVStream *videoStream = fmtCtx->streams[videoStreamIndex];
    int den = videoStream->time_base.den;
    int num = videoStream->time_base.num;
    printf("\nfmtContext->duration = %lld, stream->duration = %lld",
           fmtCtx->duration == AV_NOPTS_VALUE ? -1 : fmtCtx->duration ,
           videoStream->duration == AV_NOPTS_VALUE ? -1 : videoStream->duration);
    printf("\nstream->numerator = %d, stream->denominator = %d", num, den);
    if (fmtCtx->duration != AV_NOPTS_VALUE) {
        printf("\nfmtContext->duration = %lf, fmtContext->duration = %d(s)%3d(ms)",
               fmtCtx->duration * (num * 1.0 / den),
               (int)(fmtCtx->duration * (num * 1.0 / den) / 1000),
               ((int)(fmtCtx->duration * (num * 1.0 / den))) % 1000);
    } else {
        printf("\nstream->duration = %lf",
               videoStream->duration * (num * 1.0 / den));
    }
    

    输入案例:

    • 样本1:
      Duration: 00:00:16.16
      fmtContext->duration = 16160000, stream->duration = -1
      stream->numerator = 1, stream->denominator = 1000
      fmtContext->duration = 16160.000000, fmtContext->duration = 16(s)160(ms)
    • 样本2:
      Duration: 00:03:01.39
      fmtContext->duration = 181389622, stream->duration = 16325066
      stream->numerator = 1, stream->denominator = 90000
      // AVFormatContext的计算结果
      fmtContext->duration = 2015.440244, fmtContext->duration = 2(s)15(ms)
      // AVStream的计算结果
      stream->duration = 181.389622
    • 样本3:
      Duration: 00:00:04.00
      fmtContext->duration = 4004967, stream->duration = 123123
      stream->numerator = 1, stream->denominator = 30000
      // AVFormatContext的计算结果
      fmtContext->duration = 133.498900, fmtContext->duration = 0(s)133(ms)
      // AVStream的计算结果
      stream->duration = 4.104100

    从样本2、3可知,当AVStream.duration有值时,AVStream.duration的计算结果近似FFmpeg的计算结果。

    8、FFmpeg源码

    int hours, mins, secs, us;
    int64_t duration = fmtCtx->duration + (fmtCtx->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,
           (100 * us) / AV_TIME_BASE);
    

    源码以AV_TIME_BASE为基准进行处理,定义如下。

    /**
     * Internal time base represented as integer
     */
    #define AV_TIME_BASE            1000000
    

    那么,AVFormatContext.duration的描述可能是我理解错了。

    9、AV_TIME_BASE、AV_TIME_BASE_Q和pts

    AV_TIME_BASE_Q定义如下:

    /**
     * Internal time base represented as fractional value
     */
    #define AV_TIME_BASE_Q          (AVRational){1, AV_TIME_BASE}
    

    AVRatioal定义如下:

    /**
     * rational number numerator/denominator
     */
    typedef struct AVRational{
        int num; ///< numerator
        int den; ///< denominator
    } AVRational;
    

    分数表示形式不方便计算,就如我前面写了很多冗余代码一样,FFmpeg提供了一个便捷函数。

    /**
     * Convert rational to double.
     * @param a rational to convert
     * @return (double) a
     */
    static inline double av_q2d(AVRational a){
        return a.num / (double) a.den;
    }
    

    那么,步骤7验证中计算视频时长可简化为

    double totalSeconds = videoStream->duration * av_q2d(videoStream->time_base);
    printf("totalSeconds = %lf\n", totalSeconds);
    

    执行结果为totalSeconds = 181.389622。

    相关文章

      网友评论

      本文标题:FFmpeg 3.0 计算视频时长

      本文链接:https://www.haomeiwen.com/subject/cztvlttx.html