FFMpeg API源码解读——av_interleaved_write_frame

av_interleaved_write_frame的主要作用是将编码后的数据写入输出文件,并保证数据的正确排序,这个函数会在写入数据时根据时间戳对数据进行排序,确保数据的正确性和同步性,这次我们就针对这个函数进行源码的解析。

一、流程解析

首先我们先给出av_interleaved_write_frame的源码:

cpp 复制代码
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt)
{
    int ret, flush = 0;

    ret = prepare_input_packet(s, pkt);
    if (ret < 0)
        goto fail;

    if (pkt) {
        AVStream *st = s->streams[pkt->stream_index];

        ret = do_packet_auto_bsf(s, pkt);
        if (ret == 0)
            return 0;
        else if (ret < 0)
            goto fail;

        if (s->debug & FF_FDEBUG_TS)
            av_log(s, AV_LOG_TRACE, "av_interleaved_write_frame size:%d dts:%s pts:%s\n",
                pkt->size, av_ts2str(pkt->dts), av_ts2str(pkt->pts));

#if FF_API_COMPUTE_PKT_FIELDS2 && FF_API_LAVF_AVCTX
        if ((ret = compute_muxer_pkt_fields(s, st, pkt)) < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
            goto fail;
#endif

        if (pkt->dts == AV_NOPTS_VALUE && !(s->oformat->flags & AVFMT_NOTIMESTAMPS)) {
            ret = AVERROR(EINVAL);
            goto fail;
        }
    } else {
        av_log(s, AV_LOG_TRACE, "av_interleaved_write_frame FLUSH\n");
        flush = 1;
    }

    for (;; ) {
        AVPacket opkt;
        int ret = interleave_packet(s, &opkt, pkt, flush);
        if (pkt) {
            memset(pkt, 0, sizeof(*pkt));
            av_init_packet(pkt);
            pkt = NULL;
        }
        if (ret <= 0) //FIXME cleanup needed for ret<0 ?
            return ret;

        ret = write_packet(s, &opkt);
        if (ret >= 0)
            s->streams[opkt.stream_index]->nb_frames++;

        av_packet_unref(&opkt);

        if (ret < 0)
            return ret;
        if(s->pb && s->pb->error)
            return s->pb->error;
    }
fail:
    av_packet_unref(pkt);
    return ret;
}

我们可以发现av_interleaved_write_frame主要逻辑为:

  1. prepare_input_packet :准备输入的数据包,包括复制数据包,以及根据需要调整数据包的时间戳和持续时间
  2. do_packet_auto_bsf:自动应用比特流过滤器(BitStream Filter),在某些情况下,我们需要对比特流进行一些额外的处理,比如添加SPS/PPS头,这个函数就是用来完成这些处理的。
  3. compute_muxer_pkt_fields:这个函数主要是计算复用器(Muxer)的数据包字段,包括PTS(Presentation Time Stamp)、DTS(Decoding Time Stamp)等。这些字段对于视频的同步播放非常重要
  4. interleave_packet:主要作用是将数据包插入到一个队列中,并且保证队列中的数据包按照DTS排序,这是为了保证数据的正确性和同步性。
  5. write_packet:这个函数的作用是将数据包写入到输出文件中,它会调用底层的I/O函数,将数据实际写入到文件中。
  6. av_packet_unref:这个函数的作用是释放数据包,在数据包被处理完毕后,我们需要调用这个函数来释放函数包占用内存。

二、解析prepare_input_packet

首先贴出源码

cpp 复制代码
static int prepare_input_packet(AVFormatContext *s, AVPacket *pkt)
{
    int ret;

    ret = check_packet(s, pkt);
    if (ret < 0)
        return ret;

#if !FF_API_COMPUTE_PKT_FIELDS2 || !FF_API_LAVF_AVCTX
    /* sanitize the timestamps */
    if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) {
        AVStream *st = s->streams[pkt->stream_index];

        /* when there is no reordering (so dts is equal to pts), but
         * only one of them is set, set the other as well */
        if (!st->internal->reorder) {
            if (pkt->pts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE)
                pkt->pts = pkt->dts;
            if (pkt->dts == AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE)
                pkt->dts = pkt->pts;
        }

        /* check that the timestamps are set */
        if (pkt->pts == AV_NOPTS_VALUE || pkt->dts == AV_NOPTS_VALUE) {
            av_log(s, AV_LOG_ERROR,
                   "Timestamps are unset in a packet for stream %d\n", st->index);
            return AVERROR(EINVAL);
        }

        /* check that the dts are increasing (or at least non-decreasing,
         * if the format allows it */
        if (st->cur_dts != AV_NOPTS_VALUE &&
            ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && st->cur_dts >= pkt->dts) ||
             st->cur_dts > pkt->dts)) {
            av_log(s, AV_LOG_ERROR,
                   "Application provided invalid, non monotonically increasing "
                   "dts to muxer in stream %d: %" PRId64 " >= %" PRId64 "\n",
                   st->index, st->cur_dts, pkt->dts);
            return AVERROR(EINVAL);
        }

        if (pkt->pts < pkt->dts) {
            av_log(s, AV_LOG_ERROR, "pts %" PRId64 " < dts %" PRId64 " in stream %d\n",
                   pkt->pts, pkt->dts, st->index);
            return AVERROR(EINVAL);
        }
    }
#endif

    return 0;
}

对照上述源码我们可以发现,其首先调用check_packet检查:

  1. AVPacket是否为空、其Stream流的index是否不合法(index本身大于0,且不超过AVFormatContext流的总数)
  2. 检查AVFormatContext里AVStream流对应的编解码器是否有问题

然后会对数据包的时间戳进行检查处理,也就是如下的代码:

cpp 复制代码
#if !FF_API_COMPUTE_PKT_FIELDS2 || !FF_API_LAVF_AVCTX
    /* sanitize the timestamps */
    if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) {
        AVStream *st = s->streams[pkt->stream_index];

        /* when there is no reordering (so dts is equal to pts), but
         * only one of them is set, set the other as well */
        if (!st->internal->reorder) {
            if (pkt->pts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE)
                pkt->pts = pkt->dts;
            if (pkt->dts == AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE)
                pkt->dts = pkt->pts;
        }

        /* check that the timestamps are set */
        if (pkt->pts == AV_NOPTS_VALUE || pkt->dts == AV_NOPTS_VALUE) {
            av_log(s, AV_LOG_ERROR,
                   "Timestamps are unset in a packet for stream %d\n", st->index);
            return AVERROR(EINVAL);
        }

        /* check that the dts are increasing (or at least non-decreasing,
         * if the format allows it */
        if (st->cur_dts != AV_NOPTS_VALUE &&
            ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && st->cur_dts >= pkt->dts) ||
             st->cur_dts > pkt->dts)) {
            av_log(s, AV_LOG_ERROR,
                   "Application provided invalid, non monotonically increasing "
                   "dts to muxer in stream %d: %" PRId64 " >= %" PRId64 "\n",
                   st->index, st->cur_dts, pkt->dts);
            return AVERROR(EINVAL);
        }

        if (pkt->pts < pkt->dts) {
            av_log(s, AV_LOG_ERROR, "pts %" PRId64 " < dts %" PRId64 " in stream %d\n",
                   pkt->pts, pkt->dts, st->index);
            return AVERROR(EINVAL);
        }
    }
#endif

对照上述代码我们可以发现其会判断输出格式是需要时间戳的(即没有设置AVFMT_NOTIMESTAMPS标志),那么就进行以下操作:

  1. 获取数据包对应的流(AVStream)
  2. 检查是否需要对数据包进行重排序。如果不需要重排序(即DTS等于PTS),但是只设置了其中一个时间戳,那么就将另一个时间戳也设置为相同的值(st->internal->reorder是什么)
  3. 检查数据包的PTS和DTS是否都已设置。如果有任意没有设置,则会打印报错日志并返回错误
  4. 检查数据包的DTS是否是递增的(或者至少是非递减的,如果输出格式允许)。如果不是,则会打印报错日志并返回错误
  5. 检查数据包的PTS是否大于DTS。如果不是,则会打印报错日志并返回错误

这段代码的主要目的是确保数据包的时间戳符合要求,因为时间戳对于数据的正确播放非常重要。如果时间戳有任何问题,那么可能会导致数据的播放顺序混乱,或者无法正确播放。

三、解析do_packet_auto_bsf

首先我们贴出实现源码

cpp 复制代码
static int do_packet_auto_bsf(AVFormatContext *s, AVPacket *pkt) {
    AVStream *st = s->streams[pkt->stream_index];
    int i, ret;

    if (!(s->flags & AVFMT_FLAG_AUTO_BSF))
        return 1;

    if (s->oformat->check_bitstream) {
        if (!st->internal->bitstream_checked) {
            if ((ret = s->oformat->check_bitstream(s, pkt)) < 0)
                return ret;
            else if (ret == 1)
                st->internal->bitstream_checked = 1;
        }
    }

#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
    if (st->internal->nb_bsfcs) {
        ret = av_packet_split_side_data(pkt);
        if (ret < 0)
            av_log(s, AV_LOG_WARNING, "Failed to split side data before bitstream filter\n");
    }
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    for (i = 0; i < st->internal->nb_bsfcs; i++) {
        AVBSFContext *ctx = st->internal->bsfcs[i];
        // TODO: when any bitstream filter requires flushing at EOF, we'll need to
        // flush each stream's BSF chain on write_trailer.
        if ((ret = av_bsf_send_packet(ctx, pkt)) < 0) {
            av_log(ctx, AV_LOG_ERROR,
                    "Failed to send packet to filter %s for stream %d\n",
                    ctx->filter->name, pkt->stream_index);
            return ret;
        }
        // TODO: when any automatically-added bitstream filter is generating multiple
        // output packets for a single input one, we'll need to call this in a loop
        // and write each output packet.
        if ((ret = av_bsf_receive_packet(ctx, pkt)) < 0) {
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                return 0;
            av_log(ctx, AV_LOG_ERROR,
                    "Failed to send packet to filter %s for stream %d\n",
                    ctx->filter->name, pkt->stream_index);
            return ret;
        }
    }
    return 1;
}

对照上述代码我们会发现,其逻辑是:

  1. AVStream *st = s->streams[pkt->stream_index];:获取数据包对应的流AVStream
  2. 判断AVStream流里的Flag是否为AVFMT_FLAG_AUTO_BSF,如果没有设置自动比特流过滤器的标志,那么就直接返回
  3. if (s->oformat->check_bitstream) {...}:如果存在检查比特流的函数,那么就调用这个函数进行检查,如果检查出错,那么就返回错误。如果检查成功,那么就设置比特流为已经被检查的标志。下次就不会再检查了
  4. if (st->internal->nb_bsfcs) {...}:如果存在比特流过滤器,那么就分离数据包的边际数据。如果分离出错,那么就会打印错误告警信息。
  5. for (i = 0; i < st->internal->nb_bsfcs; i++) {...}:对每一个比特流过滤器,都将数据包送入过滤器进行处理。如果送入过滤器出错,那么就打印错误信息并返回错误。然后,从过滤器接受处理后的数据包。如果接受出错,那么就打印错误信息并返回错误。如果接受成功,那么就继续处理下一个过滤器
  6. return 1:如果所有的过滤器都处理成功,那么就返回1,表示处理成功。

这个函数的主要作用是对数据包应用比特流过滤器。比特流过滤器可以对数据包进行一些额外的处理,比如添加SPS/PPS头,或者进行其他的一些转换。这个函数就是用来完成这些处理的。

四、解析compute_muxer_pkt_fields

执行compute_muxer_pkt_fields会先做如下的检查:

cpp 复制代码
#if FF_API_COMPUTE_PKT_FIELDS2 && FF_API_LAVF_AVCTX
    ret = compute_muxer_pkt_fields(s, s->streams[pkt->stream_index], pkt);

    if (ret < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
        return ret;
#endif

结合上述代码,它会检查FF_API_COMPUTE_PKT_FIELDS2FF_API_LAVF_AVCTX是否被定义,如果执行compute_muxer_pkt_fields出现问题且当前s->oformat->flags是需要时间戳的时候,会返回错误。接下来我们浅浅分析下compute_muxer_pkt_fields的源码:

cpp 复制代码
static int compute_muxer_pkt_fields(AVFormatContext *s, AVStream *st, AVPacket *pkt)
{
    int delay = FFMAX(st->codecpar->video_delay, st->internal->avctx->max_b_frames > 0);
    int num, den, i;
    int frame_size;

    if (!s->internal->missing_ts_warning &&
        !(s->oformat->flags & AVFMT_NOTIMESTAMPS) &&
        (!(st->disposition & AV_DISPOSITION_ATTACHED_PIC) || (st->disposition & AV_DISPOSITION_TIMED_THUMBNAILS)) &&
        (pkt->pts == AV_NOPTS_VALUE || pkt->dts == AV_NOPTS_VALUE)) {
        av_log(s, AV_LOG_WARNING,
               "Timestamps are unset in a packet for stream %d. "
               "This is deprecated and will stop working in the future. "
               "Fix your code to set the timestamps properly\n", st->index);
        s->internal->missing_ts_warning = 1;
    }

    if (s->debug & FF_FDEBUG_TS)
        av_log(s, AV_LOG_TRACE, "compute_muxer_pkt_fields: pts:%s dts:%s cur_dts:%s b:%d size:%d st:%d\n",
            av_ts2str(pkt->pts), av_ts2str(pkt->dts), av_ts2str(st->cur_dts), delay, pkt->size, pkt->stream_index);

    if (pkt->duration < 0 && st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
        av_log(s, AV_LOG_WARNING, "Packet with invalid duration %"PRId64" in stream %d\n",
               pkt->duration, pkt->stream_index);
        pkt->duration = 0;
    }

    /* duration field */
    if (pkt->duration == 0) {
        ff_compute_frame_duration(s, &num, &den, st, NULL, pkt);
        if (den && num) {
            pkt->duration = av_rescale(1, num * (int64_t)st->time_base.den * st->codec->ticks_per_frame, den * (int64_t)st->time_base.num);
        }
    }

    if (pkt->pts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE && delay == 0)
        pkt->pts = pkt->dts;

    //XXX/FIXME this is a temporary hack until all encoders output pts
    if ((pkt->pts == 0 || pkt->pts == AV_NOPTS_VALUE) && pkt->dts == AV_NOPTS_VALUE && !delay) {
        static int warned;
        if (!warned) {
            av_log(s, AV_LOG_WARNING, "Encoder did not produce proper pts, making some up.\n");
            warned = 1;
        }
        pkt->dts =
//        pkt->pts= st->cur_dts;
            pkt->pts = st->priv_pts->val;
    }

    //calculate dts from pts
    if (pkt->pts != AV_NOPTS_VALUE && pkt->dts == AV_NOPTS_VALUE && delay <= MAX_REORDER_DELAY) {
        st->pts_buffer[0] = pkt->pts;
        for (i = 1; i < delay + 1 && st->pts_buffer[i] == AV_NOPTS_VALUE; i++)
            st->pts_buffer[i] = pkt->pts + (i - delay - 1) * pkt->duration;
        for (i = 0; i<delay && st->pts_buffer[i] > st->pts_buffer[i + 1]; i++)
            FFSWAP(int64_t, st->pts_buffer[i], st->pts_buffer[i + 1]);

        pkt->dts = st->pts_buffer[0];
    }

    if (st->cur_dts && st->cur_dts != AV_NOPTS_VALUE &&
        ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) &&
          st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE &&
          st->codecpar->codec_type != AVMEDIA_TYPE_DATA &&
          st->cur_dts >= pkt->dts) || st->cur_dts > pkt->dts)) {
        av_log(s, AV_LOG_ERROR,
               "Application provided invalid, non monotonically increasing dts to muxer in stream %d: %s >= %s\n",
               st->index, av_ts2str(st->cur_dts), av_ts2str(pkt->dts));
        return AVERROR(EINVAL);
    }
    if (pkt->dts != AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE && pkt->pts < pkt->dts) {
        av_log(s, AV_LOG_ERROR,
               "pts (%s) < dts (%s) in stream %d\n",
               av_ts2str(pkt->pts), av_ts2str(pkt->dts),
               st->index);
        return AVERROR(EINVAL);
    }

    if (s->debug & FF_FDEBUG_TS)
        av_log(s, AV_LOG_TRACE, "av_write_frame: pts2:%s dts2:%s\n",
            av_ts2str(pkt->pts), av_ts2str(pkt->dts));

    st->cur_dts = pkt->dts;
    st->priv_pts->val = pkt->dts;

    /* update pts */
    switch (st->codecpar->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        frame_size = (pkt->flags & AV_PKT_FLAG_UNCODED_FRAME) ?
                     ((AVFrame *)pkt->data)->nb_samples :
                     av_get_audio_frame_duration(st->codec, pkt->size);

        /* HACK/FIXME, we skip the initial 0 size packets as they are most
         * likely equal to the encoder delay, but it would be better if we
         * had the real timestamps from the encoder */
        if (frame_size >= 0 && (pkt->size || st->priv_pts->num != st->priv_pts->den >> 1 || st->priv_pts->val)) {
            frac_add(st->priv_pts, (int64_t)st->time_base.den * frame_size);
        }
        break;
    case AVMEDIA_TYPE_VIDEO:
        frac_add(st->priv_pts, (int64_t)st->time_base.den * st->time_base.num);
        break;
    }
    return 0;
}

结合上述源码,我们可以发现其流程如下:

  1. FFMAX(st->codecpar->video_delay, st->internal->avctx->max_b_frames > 0)计算延迟,取视频延迟和B帧数量大于0的最大值
  2. 一系列检查:
    1. 检查时间戳的设置:如果数据包的pts和dts都没有设置,并且输出格式需要时间戳,那么就会打印一条告警信息,并设置missing_ts_warning标志
    2. 如果开启了时间戳调试,那么就打印一些调试信息,包括pts、dts、当前dts、延迟、数据包大小和流索引
  3. 计算数据包的持续时间
    1. 如果数据包的持续时间小于0,并且编码类型不是字幕,那么就打印一条告警信息,并将持续时间设置为0
    2. 计算数据包的持续时间,如果持续时间为0,那么就调用ff_compute_frame_duration函数计算持续时间,并根据计算结果更新数据包的持续时间
  4. 校正数据包的pts(Presentation Time Stamp,显示时间戳)和dts(Decoding TimeStamp,解码时间戳)
    1. 如果数据包的pts没有设置,但dts设置了,并且延迟为0,那么就将pts设置成dts。
    2. 如果pts没有设置,并且dts也没有设置,并且延迟为0,则打印一条警告信息,并将pts和dts设置成当前dts
    3. 然后继续检查数据包(pkt)的pts和dts,如果pts存在,而dts不存在,且延迟小于等于最大重排序延迟,那么就会计算dts。计算方式为,先将pts存入缓冲区,然后根据延迟和持续时间计算出后续的dts,最后将缓冲区的数据将进行排序,取出最小的作为dts。
  5. 然后会检查当前dts(st->cur_dts)是否有效,以及是否满足一定的条件。如果不满足,就会记录错误日志,并返回错误码
  6. 在之后就会检查数据包的pts和dts是否都存在,以及pts是否小于dts。如果pts小于dts,那么就会记录错误日志,并返回错误码
  7. 接着,如果开启了时间戳调试,就会记录pts和dts的日志
  8. 然后,更新当前的dts和私有pts
  9. 最后,会根据编解码器的类型,更新pts。对于音频数据,会根据帧大小和时间基准来计算pts。对于视频数据,会根据时间基准来计算pts

五、解析write_packet

首先贴出源码,然后我们一步一步拆解

cpp 复制代码
static int write_packet(AVFormatContext *s, AVPacket *pkt)
{
    int ret, did_split;
    int64_t pts_backup, dts_backup;

    pts_backup = pkt->pts;
    dts_backup = pkt->dts;

    // If the timestamp offsetting below is adjusted, adjust
    // ff_interleaved_peek similarly.
    if (s->output_ts_offset) {
        AVStream *st = s->streams[pkt->stream_index];
        int64_t offset = av_rescale_q(s->output_ts_offset, AV_TIME_BASE_Q, st->time_base);

        if (pkt->dts != AV_NOPTS_VALUE)
            pkt->dts += offset;
        if (pkt->pts != AV_NOPTS_VALUE)
            pkt->pts += offset;
    }

    if (s->avoid_negative_ts > 0) {
        AVStream *st = s->streams[pkt->stream_index];
        int64_t offset = st->mux_ts_offset;
        int64_t ts = s->internal->avoid_negative_ts_use_pts ? pkt->pts : pkt->dts;

        if (s->internal->offset == AV_NOPTS_VALUE && ts != AV_NOPTS_VALUE &&
            (ts < 0 || s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO)) {
            s->internal->offset = -ts;
            s->internal->offset_timebase = st->time_base;
        }

        if (s->internal->offset != AV_NOPTS_VALUE && !offset) {
            offset = st->mux_ts_offset =
                av_rescale_q_rnd(s->internal->offset,
                                 s->internal->offset_timebase,
                                 st->time_base,
                                 AV_ROUND_UP);
        }

        if (pkt->dts != AV_NOPTS_VALUE)
            pkt->dts += offset;
        if (pkt->pts != AV_NOPTS_VALUE)
            pkt->pts += offset;

        if (s->internal->avoid_negative_ts_use_pts) {
            if (pkt->pts != AV_NOPTS_VALUE && pkt->pts < 0) {
                av_log(s, AV_LOG_WARNING, "failed to avoid negative "
                    "pts %s in stream %d.\n"
                    "Try -avoid_negative_ts 1 as a possible workaround.\n",
                    av_ts2str(pkt->pts),
                    pkt->stream_index
                );
            }
        } else {
            av_assert2(pkt->dts == AV_NOPTS_VALUE || pkt->dts >= 0 || s->max_interleave_delta > 0);
            if (pkt->dts != AV_NOPTS_VALUE && pkt->dts < 0) {
                av_log(s, AV_LOG_WARNING,
                    "Packets poorly interleaved, failed to avoid negative "
                    "timestamp %s in stream %d.\n"
                    "Try -max_interleave_delta 0 as a possible workaround.\n",
                    av_ts2str(pkt->dts),
                    pkt->stream_index
                );
            }
        }
    }

#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
    did_split = av_packet_split_side_data(pkt);
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    if (!s->internal->header_written) {
        ret = s->internal->write_header_ret ? s->internal->write_header_ret : write_header_internal(s);
        if (ret < 0)
            goto fail;
    }

    if ((pkt->flags & AV_PKT_FLAG_UNCODED_FRAME)) {
        AVFrame *frame = (AVFrame *)pkt->data;
        av_assert0(pkt->size == UNCODED_FRAME_PACKET_SIZE);
        ret = s->oformat->write_uncoded_frame(s, pkt->stream_index, &frame, 0);
        av_frame_free(&frame);
    } else {
        ret = s->oformat->write_packet(s, pkt);
    }

    if (s->pb && ret >= 0) {
        flush_if_needed(s);
        if (s->pb->error < 0)
            ret = s->pb->error;
    }

fail:
#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
    if (did_split)
        av_packet_merge_side_data(pkt);
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    if (ret < 0) {
        pkt->pts = pts_backup;
        pkt->dts = dts_backup;
    }

    return ret;
}

我们会发现:

  1. 首先,它备份了数据的dts和pts值,pts是presentation timestamp,表示这个数据包应该何时被播放;dts是decoding timestamp,表示这个数据包何时被解码
  2. 如果设置了output_ts_offset,那么将这个偏移量加到pts和dts上。
  3. 如果设置了avoid_negative_ts,那么会尝试避免pts和dts的值为负。如果pts和dts的值为负,那么会计算一个offset值,并将其加到pts和dts上。如果加上offset后pts或dts仍然为负,那么会打印一条警告日志。
  4. 然后,如果数据包包含了未编码的帧,那么会调用write_uncoded_frame函数将这个帧写入输出;否则,会调用write_packet函数将数据包写入输出。
  5. 如果在写入过程中出现了错误,那么会恢复数据包的pts和dts为备份的值,并返回错误码。
相关推荐
cuijiecheng20184 小时前
音视频入门基础:RTP专题(10)——FFmpeg源码中,解析RTP header的实现
ffmpeg·音视频
aaon223571 天前
ubuntu ffmpeg 安装踩坑
linux·ubuntu·ffmpeg
音视频牛哥1 天前
深度解析大牛直播SDK在RTSP播放器中的集成与优化实践
音视频开发·视频编码·直播
m0_748245171 天前
SpringCloud-使用FFmpeg对视频压缩处理
spring·spring cloud·ffmpeg
iummature2 天前
FFmpeg命令
ffmpeg
渔舟唱晚@2 天前
FFmpeg+WebSocket+JsMpeg实时视频流实现方案
websocket·网络协议·ffmpeg
xcg3401232 天前
关于视频抽帧调用虹软人脸识别的BufferedImage读取优化策略
ffmpeg·音视频·视频抽帧
繁依Fanyi3 天前
使用 FFmpeg 剪辑视频指南
java·服务器·开发语言·ffmpeg·音视频
关键帧Keyframe3 天前
音视频面试题集锦第 19 期 | 读取纹理数据
ios·图像识别·音视频开发
音视频牛哥4 天前
RTSP|RTMP直播播放器实时截图使用场景和技术实现
音视频开发·视频编码·直播