FFmpeg之timebase和timestamp

FFmpeg之timebase和timestamp

以下姑且将timebase翻译为时间基,timestamp翻译为时间戳。时间戳是以时间基为单位的具体时间表示。

在音视频编解码时,经常会遇到时间基和时间戳的问题,导致音频视频解码播放出现问题。因此有必要做一个总结,以免下次再犯同样的错误。

一、timebase

ffmpeg存在多个时间基准(time_base),对应不同的阶段(结构体),每个time_base具体的值不一样,ffmpeg提供函数在各个time_base中进行切换。

1、AVStream 

typedef struct AVStream {
    /**
     * This 
is the fundamental unit of time (in seconds) in terms
     * of which frame timestamps are represented.
     *
     * decoding: 
set by libavformat
     * encoding: May be 
set by the caller before avformat_write_header() to
     *           provide a hint 
to the muxer about the desired timebase. In
     *           avformat_write_header(), the muxer will overwrite this field
     *           
with the timebase that will actually be used for the timestamps
     *           written into the file (which may 
or may not be related to the
     *           user-provided one, depending 
on the format).
     */
    AVRational time_base;

}

上面英文说得很清楚了,AVStream->time_base是1S对应的时间单位,在 avformat_write_header()前可以设置AVStream->time_base,根据封装格式不一样,avformat_write_header()可能修改AVStream->time_base,比如mpegts修改为90000,flv修改为1000,mp4根据设置time_base,如果小于10000,会将time_base*2的幂直到大于10000。

2、AVCodecContext

typedef struct AVCodecContext {
    /**
     * This 
is the fundamental unit of time (in seconds) in terms
     * of which frame timestamps are represented. 
For fixed-fps content,
     * timebase should be 1/framerate 
and timestamp increments should be
     * identically 1.
     * - encoding: MUST be 
set by user.
     * - decoding: 
Set by libavcodec.
     */
    AVRational time_base;

}

AVCodecContext  ->time_base是1S对应的时间单位,一般以帧率为作为timebase。

 

一、timestamp

Timestamp有PTS和DTS,一般在有B帧编码的情况下两者都会用到,没有B帧时,两者一般保持一样。

PTS(Presentation timestamp)即显示时间戳,就是一副图片或音频帧显示或播放的时间。

DTS(Decompressiontimestamp)即解码时间戳,就是一副图片或音频帧解码的时间。

1、 AVPacket

typedef struct 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;

}

一帧原始数据压缩后的数据用AVPacket表示,pts指示帧显示时间,dts指示帧解码时间。

2、 AVFrame

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

    /**
     * PTS copied from the AVPacket that was decoded 
to produce this frame.
     */
    int64_t pkt_pts;

    /**
     * DTS copied from the AVPacket that triggered returning this frame. (
if frame threading isn't used)
     * This is also the Presentation time of this AVFrame calculated from
     * only AVPacket.dts values without pts values.
     */
    int64_t pkt_dts;

}

根据上面的解释,ptsframe的时间戳,在解码时pkt_ptspkt_dts是复制的AVPacket中对应的pkt_ptspkt_dts。需要说明的是pts在解码时是没有赋值的,需要调用pts  =av_frame_get_best_effort_timestamp(p_frame)获取,这个函数是一个宏定义,实际上是读取的AVframe->best_effort_timestamp。编码时需要带上pts

 

三、不同timebase之间timestamp的转换

FFmpeg提供一系列的函数用于此目的。

1、av_rescale_q

int64_t av_rescale_q(int64_ta, AVRational bq, AVRational cq)

{

   return av_rescale_q_rnd(a, bq, cq, AV_ROUND_NEAR_INF);

}

2、av_rescale_q_rnd

int64_t av_rescale_q_rnd(int64_t a,AVRational bq, AVRational cq,

                         enum AVRounding rnd)

{

   int64_t b = bq.num * (int64_t)cq.den;

   int64_t c = cq.num * (int64_t)bq.den;

   return av_rescale_rnd(a, b, c, rnd);

}

3、av_rescale_rnd

int64_t av_rescale_rnd(int64_ta, int64_t b, int64_t c, enum AVRounding rnd)

{

   int64_t r = 0;

   av_assert2(c > 0);

   av_assert2(b >=0);

   av_assert2((unsigned)(rnd&~AV_ROUND_PASS_MINMAX)<=5 &&(rnd&~AV_ROUND_PASS_MINMAX)!=4);

 

   if (c <= 0 || b < 0 || !((unsigned)(rnd&~AV_ROUND_PASS_MINMAX)<=5&& (rnd&~AV_ROUND_PASS_MINMAX)!=4))

       return INT64_MIN;

 

   if (rnd & AV_ROUND_PASS_MINMAX) {

       if (a == INT64_MIN || a == INT64_MAX)

           return a;

       rnd -= AV_ROUND_PASS_MINMAX;

    }

 

   if (a < 0 && a != INT64_MIN)

       return -av_rescale_rnd(-a, b, c, rnd ^ ((rnd >> 1) & 1));

 

   if (rnd == AV_ROUND_NEAR_INF)

       r = c / 2;

   else if (rnd & 1)

       r = c - 1;

 

   if (b <= INT_MAX && c <= INT_MAX) {

       if (a <= INT_MAX)

           return (a * b + r) / c;

       else

           return a / c * b + (a % c * b + r) / c;

    }else {

#if 1

       uint64_t a0  = a & 0xFFFFFFFF;

       uint64_t a1  = a >> 32;

       uint64_t b0  = b & 0xFFFFFFFF;

       uint64_t b1  = b >> 32;

       uint64_t t1  = a0 * b1 + a1 * b0;

       uint64_t t1a = t1 << 32;

       int i;

 

       a0  = a0 * b0 + t1a;

       a1  = a1 * b1 + (t1 >> 32) +(a0 < t1a);

       a0 += r;

       a1 += a0 < r;

 

       for (i = 63; i >= 0; i--) {

           a1 += a1 + ((a0 >> i) & 1);

           t1 += t1;

           if (c <= a1) {

                a1 -= c;

                t1++;

           }

       }

       return t1;

    }

#else

       AVInteger ai;

       ai = av_mul_i(av_int2i(a), av_int2i(b));

       ai = av_add_i(ai, av_int2i(r));

 

       return av_i2int(av_div_i(ai, av_int2i(c)));

    }

#endif

}

这几个函数说简单点就是实现a*b/c,对尾数有一些舍入方法。如下

enum AVRounding {

   AV_ROUND_ZERO     = 0, ///<Round toward zero.

   AV_ROUND_INF      = 1, ///<Round away from zero.

   AV_ROUND_DOWN     = 2, ///< Round toward -infinity.

   AV_ROUND_UP       = 3, ///<Round toward +infinity.

   AV_ROUND_NEAR_INF = 5, ///< Round to nearest and halfway cases awayfrom zero.

   AV_ROUND_PASS_MINMAX = 8192, ///< Flag to pass INT64_MIN/MAX throughinstead of rescaling, this avoids special cases for AV_NOPTS_VALUE

};

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
下面是使用 C++ 和 FFmpeg 创建 RTSP 服务器的示例代码: ```c++ #include <iostream> #include <thread> #include <chrono> #include <sstream> #include <cstring> #include <cstdio> #include <cstdlib> #include <ctime> extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/avutil.h> #include <libavutil/opt.h> #include <libavutil/time.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> #include <libswresample/swresample.h> #include <libavutil/audio_fifo.h> #include <libavutil/audioconvert.h> } #define WIDTH 640 #define HEIGHT 480 #define FPS 25 static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt, FILE *outfile) { int ret; /* send the frame to the encoder */ ret = avcodec_send_frame(enc_ctx, frame); if (ret < 0) { std::cerr << "Error sending a frame for encoding" << std::endl; exit(1); } /* receive packet from encoder */ while (ret >= 0) { ret = avcodec_receive_packet(enc_ctx, pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) return; else if (ret < 0) { std::cerr << "Error during encoding" << std::endl; exit(1); } /* write the compressed frame to the media file */ fwrite(pkt->data, 1, pkt->size, outfile); av_packet_unref(pkt); } } static void encode_thread(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt, FILE *outfile) { while (true) { encode(enc_ctx, frame, pkt, outfile); } } int main(int argc, char *argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " output_url" << std::endl; return 1; } const char *output_url = argv[1]; // Initialize FFmpeg av_register_all(); avcodec_register_all(); // Create format context AVFormatContext *format_ctx = nullptr; int ret = avformat_alloc_output_context2(&format_ctx, nullptr, "rtsp", output_url); if (ret < 0) { std::cerr << "Error creating output context" << std::endl; return 1; } // Create video stream AVStream *video_stream = avformat_new_stream(format_ctx, nullptr); if (video_stream == nullptr) { std::cerr << "Error creating video stream" << std::endl; return 1; } // Set codec parameters AVCodecParameters *codec_params = video_stream->codecpar; codec_params->codec_id = AV_CODEC_ID_H264; codec_params->codec_type = AVMEDIA_TYPE_VIDEO; codec_params->width = WIDTH; codec_params->height = HEIGHT; codec_params->format = AV_PIX_FMT_YUV420P; codec_params->bit_rate = 1000000; codec_params->fps_num = FPS; codec_params->fps_den = 1; // Find codec AVCodec *codec = avcodec_find_encoder(codec_params->codec_id); if (codec == nullptr) { std::cerr << "Error finding codec" << std::endl; return 1; } // Create codec context AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); if (codec_ctx == nullptr) { std::cerr << "Error creating codec context" << std::endl; return 1; } // Set codec options av_opt_set(codec_ctx->priv_data, "preset", "ultrafast", 0); // Open codec ret = avcodec_open2(codec_ctx, codec, nullptr); if (ret < 0) { std::cerr << "Error opening codec" << std::endl; return 1; } // Copy codec parameters to codec context ret = avcodec_parameters_to_context(codec_ctx, codec_params); if (ret < 0) { std::cerr << "Error copying codec parameters" << std::endl; return 1; } // Allocate frame AVFrame *frame = av_frame_alloc(); if (frame == nullptr) { std::cerr << "Error allocating frame" << std::endl; return 1; } // Set frame options frame->format = codec_ctx->pix_fmt; frame->width = codec_ctx->width; frame->height = codec_ctx->height; // Allocate frame buffer ret = av_frame_get_buffer(frame, 0); if (ret < 0) { std::cerr << "Error allocating frame buffer" << std::endl; return 1; } // Open output file ret = avio_open(&format_ctx->pb, output_url, AVIO_FLAG_WRITE); if (ret < 0) { std::cerr << "Error opening output file" << std::endl; return 1; } // Write format header ret = avformat_write_header(format_ctx, nullptr); if (ret < 0) { std::cerr << "Error writing format header" << std::endl; return 1; } // Create packet AVPacket *pkt = av_packet_alloc(); if (pkt == nullptr) { std::cerr << "Error creating packet" << std::endl; return 1; } // Create encoding thread std::thread encode_thread(encode_thread, codec_ctx, frame, pkt, format_ctx->pb); // Generate video frames AVFrame *rgb_frame = av_frame_alloc(); if (rgb_frame == nullptr) { std::cerr << "Error allocating RGB frame" << std::endl; return 1; } uint8_t *buffer = reinterpret_cast<uint8_t *>(av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB24, WIDTH, HEIGHT, 1))); if (buffer == nullptr) { std::cerr << "Error allocating buffer" << std::endl; return 1; } av_image_fill_arrays(rgb_frame->data, rgb_frame->linesize, buffer, AV_PIX_FMT_RGB24, WIDTH, HEIGHT, 1); for (int i = 0; i < 1000; ++i) { // Generate random image for (int y = 0; y < HEIGHT; ++y) { for (int x = 0; x < WIDTH; ++x) { rgb_frame->data[0][y * rgb_frame->linesize[0] + x * 3] = rand() % 256; rgb_frame->data[0][y * rgb_frame->linesize[0] + x * 3 + 1] = rand() % 256; rgb_frame->data[0][y * rgb_frame->linesize[0] + x * 3 + 2] = rand() % 256; } } // Convert RGB to YUV SwsContext *sws_ctx = sws_getContext(WIDTH, HEIGHT, AV_PIX_FMT_RGB24, WIDTH, HEIGHT, AV_PIX_FMT_YUV420P, 0, nullptr, nullptr, nullptr); sws_scale(sws_ctx, rgb_frame->data, rgb_frame->linesize, 0, HEIGHT, frame->data, frame->linesize); sws_freeContext(sws_ctx); // Set frame timestamp frame->pts = i * (codec_ctx->time_base.den) / (codec_ctx->time_base.num * FPS); // Encode frame encode(codec_ctx, frame, pkt, format_ctx->pb); std::this_thread::sleep_for(std::chrono::milliseconds(1000 / FPS)); } // Flush encoder encode(codec_ctx, nullptr, pkt, format_ctx->pb); // Write format trailer av_write_trailer(format_ctx); // Join encoding thread encode_thread.join(); // Free resources av_frame_free(&frame); av_frame_free(&rgb_frame); av_packet_free(&pkt); avcodec_free_context(&codec_ctx); avformat_free_context(format_ctx); return 0; } ``` 在上面的示例,我们使用了 FFmpeg 库来生成随机视频帧并将其编码为 H.264 格式,并使用 RTSP 协议将其流式传输到给定的 URL。首先,我们使用 `avformat_alloc_output_context2` 函数创建一个输出上下文,并将其格式设置为 RTSP。然后,我们创建一个视频流并设置其编解码器参数。接下来,我们使用 `avcodec_find_encoder` 函数查找 H.264 编解码器,并使用 `avcodec_alloc_context3` 函数创建编解码器上下文。然后,我们设置编解码器的选项并打开它。接下来,我们将编解码器参数复制到编解码器上下文,并使用 `av_frame_alloc` 函数分配一个帧。然后,我们设置帧的选项并使用 `av_frame_get_buffer` 函数分配其缓冲区。接下来,我们使用 `avio_open` 函数打开输出文件,并使用 `avformat_write_header` 函数写入格式头。然后,我们创建一个数据包并启动一个编码线程。接下来,我们使用 `av_frame_alloc` 函数分配一个 RGB 帧,并使用 `av_malloc` 函数分配一个缓冲区。然后,我们生成随机 RGB 像素,并使用 `sws_getContext` 函数创建一个缩放上下文。然后,我们使用 `sws_scale` 函数将 RGB 帧转换为 YUV 帧。接下来,我们在帧上设置时间戳,并使用 `encode` 函数将其编码为 H.264 格式并写入输出文件。最后,我们使用 `av_write_trailer` 函数写入格式尾,并等待编码线程完成。在退出之前,我们释放所有使用的资源,并返回 0。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sliaowalker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值