ffmpeg中的时间单位以及时间转换函数(av_q2d av_rescale_q)

Posted 泡沫o0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ffmpeg中的时间单位以及时间转换函数(av_q2d av_rescale_q)相关的知识,希望对你有一定的参考价值。


相关概念


tbr: 是我们通常所说的帧率。time base of rate
tbn: 视频流的时间基。 time base of stream
tbc: 视频解码的时间基。time base of codec
tbn = the time base in AVStream that has come from the container
tbc = the time base in AVCodecContext for the codec used for a particular stream
tbr = tbr is guessed from the video stream and is the value users want to see when they look for the video frame rate.

ffmpeg内部时间基

ffmpeg中的内部计时单位(时间基),ffmepg中的所有时间都是于它为一个单位,
比如AVStream中的duration,即这个流的长度为duration个AV_TIME_BASE.

#define AV_TIME_BASE 1000000
它还有一种分数所表式法:
#define AV_TIME_BASE_Q (AVRational)1, AV_TIME_BASE
在 ffmpeg中进行换算,将不同时间基的值转成按秒为单位的值计算如下:
timestamp(秒) = pts * av_q2d(time_base)


相关定义

AVRatioal的定义如下:

typedef struct AVRational
int num; //numerator
int den; //denominator
 AVRational;
 

AVRatioal结构转double的函数:

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

av_get_time_base_q函数

返回内部时基的小数表示.

/**
 * Return the fractional representation of the internal time base.
 */
AVRational av_get_time_base_q(void);

av_rescale_q函数

av_rescale_q(a,b,c)的作用是,把时间戳从一个时基调整到另外一个时基时候用的函数。其中,a 表式要换算的值;b 表式原来的时间基;c表式要转换的时间基。其计算公式为 a * b / c。

/**  * Rescale a 64-bit integer by 2 rational numbers.  
** The operation is mathematically equivalent to `a * bq / cq`.  
**  This function is equivalent to av_rescale_q_rnd() with #AV_ROUND_NEAR_INF. 
*  * @see av_rescale(), av_rescale_rnd(), av_rescale_q_rnd()  */
 int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;

用例

用于计算Packet中的pts

        if (pkt->pts != AV_NOPTS_VALUE) 
            pkt->pts = av_rescale_q(pkt->pts, mCtx->streams[pkt->stream_index]->time_base, av_get_time_base_q());
        

        if (pkt->dts != AV_NOPTS_VALUE) 
            pkt->dts = av_rescale_q(pkt->dts, mCtx->streams[pkt->stream_index]->time_base, av_get_time_base_q());
        

根据pts来计算一桢在整个视频中的时间位置

timestamp() = pts * av_q2d(st->time_base) 

计算视频长度的方法

time() = st->duration * av_q2d(st->time_base)

这里的st是一个AVStream对象指针。


时间基转换公式

timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒)
time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)

示例

int64_t timestamp = N * AV_TIME_BASE; 
av_seek_frame(fmtctx, index_of_video, timestamp, AVSEEK_FLAG_BACKWARD);
//ffmpeg同样为我们提供了不同时间基之间的转换函数:
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

//时间戳转秒
    time_in_seconds = av_q2d(AV_TIME_BASE_Q) * timestamp
//秒转时间戳
    timestamp = AV_TIME_BASE * time_in_seconds

ffmpeg av_seek_frame

【中文标题】ffmpeg av_seek_frame【英文标题】: 【发布时间】:2010-10-05 00:34:07 【问题描述】:

我正在尝试使用 ffmpeg 的 av_seek_frame 方法在电影中搜索,但是我在确定如何生成要搜索的时间戳时遇到了最大的麻烦。假设我想向前或向后寻找 x 帧,并且我知道电影当前在哪一帧,我该怎么做?

【问题讨论】:

不能用帧率计算时间偏移吗? 据我了解,时间偏移量需要以 time_base 单位为单位,但我不确定如何将其转换为这些单位(或者即使这是我需要做的)。如果这是我需要做的,我不确定 time_base 的单位是什么(秒、帧、每秒帧数)。 【参考方案1】:

不确定这是否超级准确,但以下内容非常简单并且似乎有效:

int n_seconds = 10; // seek forward 10 seconds
// time_base is in seconds, eg. the time base may be 1/1000th of a second,
// so just multiply by the reciprocal (den = denominator, num = numerator)
int64_t ts = av_rescale(
    n_seconds,
    format_ctx->streams[video_stream_index]->time_base.den,
    format_ctx->streams[video_stream_index]->time_base.num
);
// even though it mentions in docs that you shouldn't use this because it is a
// work in progress, it's been around for more than a decade now, ffplay/ffmpeg/ffprobe
// all use it...it is the most consistent and easiest to use. the way I am using
// it here is to seek to the nearest keyframe (not frame!). I would not recommend
// using it in any other way:
// eg. AVSEEK_FLAG_ANY/FRAME/BACKWARD (BACKWARD is ignored anyways)
// 0 as flag seeks to keyframes only. I have set the max timestamp to the same value so
// that we only look for nearest keyframes behind us
int err = avformat_seek_file(pFormatContext, video_stream_index, 0, ts, ts, 0);

这会寻找最近的关键帧!这可能离你想要的很远。但是,它只会落后于目标时间戳,因此您可以使用 av_read_frame 直到到达您想要的位置,使用 AVframe->pts * AVStream->timebase 来计算帧的时间(使用 av_rescale 来执行此操作)。

另外请注意,如果您需要向后搜索(即您已经使用av_read_frame 阅读的帧后面的帧),或者您将在一个帧上多次调用av_read_frame,您必须发送/receive the packet/frame with avcodec_send_packetavcodec_receive_frame 分别,否则编解码器上下文将不同步(我认为这是问题所在?)。您不能只是空白地读取数据包。在您寻找到您正在阅读的位置后面的新位置之后,您还应该avcodec_flush_buffers(您可能应该在每次寻找时都调用它,但我不确定性能)。

文档参考:

int avformat_seek_file (..., int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags)

【讨论】:

谢谢!发现重置到流的开头实际上需要3个步骤,比如avcodec_send_packet(video_dec_ctx, NULL) + av_seek_frame(fmt_ctx, video_stream_idx, 0, AVSEEK_FLAG_ANY) +avcodec_flush_buffers(video_dec_ctx);【参考方案2】:

我是这样做的:

// Duration of one frame in AV_TIME_BASE units
int64_t timeBase;

void open(const char* fpath)
    ...
    timeBase = (int64_t(pCodecCtx->time_base.num) * AV_TIME_BASE) / int64_t(pCodecCtx->time_base.den);
    ...


bool seek(int frameIndex)

    if(!pFormatCtx)
        return false;

    int64_t seekTarget = int64_t(frameIndex) * timeBase;

    if(av_seek_frame(pFormatCtx, -1, seekTarget, AVSEEK_FLAG_ANY) < 0)
        mexErrMsgTxt("av_seek_frame failed.");


AVSEEK_FLAG_ANY 可以搜索每一帧,而不仅仅是关键帧。

【讨论】:

【参考方案3】:

简单的答案:你应该有一个 AVFormatContext 对象。它的duration 属性告诉您文件的时间长度,即时间戳乘以 1000 可以在 av_seek_frame 中使用,因此将其视为 100%。然后,您可以计算出您想要在视频中搜索多远。

如果你想前进一帧,只需调用 av_read_frame 和 avcodec_decode_video 直到它用非零值填充 got_picture_ptr。在调用 avcodec_decode_video 之前,请确保来自 av_read_frame 的数据包来自视频流。然后 avcodec_decode_video 将填充 AVFrame 结构,您可以使用它来做任何事情。

【讨论】:

以上是关于ffmpeg中的时间单位以及时间转换函数(av_q2d av_rescale_q)的主要内容,如果未能解决你的问题,请参考以下文章

FFMpeg中seek函数解析

ffmpeg av_seek_frame

FFMPEG 无法丢弃媒体中的无效数据流。为啥它会留下来以及如何丢弃它?

FFmpeg的使用——PHP转换视频截取视频以及JW Player播放器控制

FFMPEG:将当前时间(以毫秒为单位)嵌入到视频中

如何在ffmpeg中使用字节而不是文件路径?