【问题标题】:FFMPEG: multiplexing streams with different durationFFMPEG:多路复用不同持续时间的流
【发布时间】:2018-08-26 19:09:01
【问题描述】:

我正在复用视频和音频流。视频流来自生成的图像数据。音频流来自 aac 文件。一些音频文件比我设置的总视频时间长,所以我的策略是在其时间大于总视频时间(我通过编码视频帧数控制的最后一个)时停止音频流复用器。

我不会把整个设置代码放在这里,但它类似于来自最新 FFMPEG 存储库的muxing.c 示例。唯一的区别是,正如我所说,我使用来自文件的音频流,而不是来自综合生成的编码帧。我很确定问题出在复用器循环期间我的错误同步。这就是我所做的:

void AudioSetup(const char* audioInFileName)
{
    AVOutputFormat* outputF = mOutputFormatContext->oformat;
    auto audioCodecId = outputF->audio_codec;

    if (audioCodecId == AV_CODEC_ID_NONE) {
        return false;
    }

    audio_codec = avcodec_find_encoder(audioCodecId);

    avformat_open_input(&mInputAudioFormatContext,
    audioInFileName, 0, 0);
    avformat_find_stream_info(mInputAudioFormatContext, 0);

    av_dump_format(mInputAudioFormatContext, 0, audioInFileName, 0);


    for (size_t i = 0; i < mInputAudioFormatContext->nb_streams; i++) {
        if (mInputAudioFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            inAudioStream = mInputAudioFormatContext->streams[i];

            AVCodecParameters *in_codecpar = inAudioStream->codecpar;
            mAudioOutStream.st = avformat_new_stream(mOutputFormatContext, NULL);
            mAudioOutStream.st->id = mOutputFormatContext->nb_streams - 1;
            AVCodecContext* c = avcodec_alloc_context3(audio_codec);
            mAudioOutStream.enc = c;
            c->sample_fmt = audio_codec->sample_fmts[0];
            avcodec_parameters_to_context(c, inAudioStream->codecpar);
            //copyparams from input to autput audio stream:
            avcodec_parameters_copy(mAudioOutStream.st->codecpar, inAudioStream->codecpar);

            mAudioOutStream.st->time_base.num = 1;
            mAudioOutStream.st->time_base.den = c->sample_rate;

            c->time_base = mAudioOutStream.st->time_base;

            if (mOutputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) {
                c->flags |= CODEC_FLAG_GLOBAL_HEADER;
            }
            break;
        }
    }
}

void Encode()
{
    int cc = av_compare_ts(mVideoOutStream.next_pts, mVideoOutStream.enc->time_base,
    mAudioOutStream.next_pts, mAudioOutStream.enc->time_base);

    if (mAudioOutStream.st == NULL || cc <= 0) {
        uint8_t* data = GetYUVFrame();//returns ready video YUV frame to work with
        int ret = 0;
        AVPacket pkt = { 0 };
        av_init_packet(&pkt);
        pkt.size = packet->dataSize;
        pkt.data = data;
        const int64_t duration = av_rescale_q(1, mVideoOutStream.enc->time_base, mVideoOutStream.st->time_base);

        pkt.duration = duration;
        pkt.pts = mVideoOutStream.next_pts;
        pkt.dts = mVideoOutStream.next_pts;
        mVideoOutStream.next_pts += duration;

        pkt.stream_index = mVideoOutStream.st->index;
        ret = av_interleaved_write_frame(mOutputFormatContext, &pkt);
    } else
    if(audio_time <  video_time) {
        //5 -  duration of video in seconds
        AVRational r = {  60, 1 };

        auto cmp= av_compare_ts(mAudioOutStream.next_pts, mAudioOutStream.enc->time_base, 5, r);
        if (cmp >= 0) {
            mAudioOutStream.next_pts = (int64_t)std::numeric_limits<int64_t>::max();
            return true; //don't mux audio anymore
        }

        AVPacket a_pkt = { 0 };
        av_init_packet(&a_pkt);

        int ret = 0;
        ret = av_read_frame(mInputAudioFormatContext, &a_pkt);
        //if audio file is shorter than stop muxing when at the end of the file
        if (ret == AVERROR_EOF) {
            mAudioOutStream.next_pts = (int64_t)std::numeric_limits<int64_t>::max(); 
            return true;
        }
        a_pkt.stream_index = mAudioOutStream.st->index;

        av_packet_rescale_ts(&a_pkt, inAudioStream->time_base, mAudioOutStream.st->time_base);
        mAudioOutStream.next_pts += a_pkt.pts;

        ret = av_interleaved_write_frame(mOutputFormatContext, &a_pkt);
    }
}

现在,视频部分完美无缺。但是,如果音轨比视频持续时间长,我的总视频长度会增加大约 5% - 20%,很明显,音频在其中起到了作用,因为视频帧正好在应该在的地方完成。

我最接近的'hack'是这部分:

AVRational r = {  60 ,1 };
auto cmp= av_compare_ts(mAudioOutStream.next_pts, mAudioOutStream.enc->time_base, 5, r);
if (cmp >= 0) {
    mAudioOutStream.next_pts = (int64_t)std::numeric_limits<int64_t>::max();
    return true;
} 

在这里,我试图将音频流的next_pts 与为视频文件设置的总时间(5 秒)进行比较。通过设置r = {60,1},我将这些秒数转换为音频流的time_base。至少那是我相信我正在做的事情。有了这个 hack,当使用标准 AAC 文件时,我与正确的电影长度的偏差非常小,即 44100 的采样率,立体声。但是,如果我使用更多有问题的样本进行测试,例如 AAC 采样率 16000,单声道 - 那么视频文件的大小几乎会增加一整秒。 如果有人能指出我在这里做错了什么,我将不胜感激。

重要提示:我没有为任何上下文设置持续时间。我控制多路复用会话的终止,它基于视频帧数。音频输入流当然有持续时间,但它对我没有帮助,因为视频持续时间是定义电影长度的因素。

更新:

这是第二次赏金尝试。

更新 2:

实际上,我的 {den,num} 音频时间戳是错误的,而 {1,1} 确实是要走的路,正如答案所解释的那样。阻止它工作的是这一行中的一个错误(我的错):

     mAudioOutStream.next_pts += a_pkt.pts;

必须是:

     mAudioOutStream.next_pts = a_pkt.pts;

该错误导致 pts 呈指数增加,这导致非常早地到达流的末尾(以 pts 而言),因此导致音频流比预期的更早终止。

【问题讨论】:

  • 奖给谁?真的没有人回答不了这个问题吗?
  • 截断 - 不是一个选项。第二件事——这就是我想要做的。看看我的代码。
  • “我不熟悉 FFMPEG API”——拜托,我对 API 非常熟悉,这不是一个小问题。我不会因为简单的事情放弃 250 分。顺便说一句,我故意翻转那个时基,因为它是唯一给我一些“相对”合理的东西的变体......在那个例子中使用 {1,1} 在我的情况下不会返回任何有意义的东西。我想那是因为我的音频流不像示例中使用的那样合成。文件中音频流的时基由编解码器上下文设置,可能看起来很奇怪。
  • “请”是粗鲁的吗?来吧伙计,如果你帮不上忙,别怪我。为了不了解 FFMPEG 文档,我陷入这个错误太久了。
  • 简而言之,用 C++ 代码实现类似于stackoverflow.com/questions/13041061/… 的东西?

标签: c++ audio video ffmpeg libavformat


【解决方案1】:

问题是你告诉它比较给定的音频时间和560 seconds per tick 的滴答声。实际上我很惊讶它在某些情况下有效,但我想这真的取决于给定音频流的特定 time_base

让我们假设音频有一个1/25time_base 并且流在6 秒,这超出了您的预期,因此您希望av_compare_ts 返回01。鉴于这些条件,您将获得以下值:

mAudioOutStream.next_pts = 150
mAudioOutStream.enc->time_base = 1/25

因此您使用以下参数调用av_compare_ts

ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 60/1

现在我们来看看av_compare_ts的实现:

int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b)
{
    int64_t a = tb_a.num * (int64_t)tb_b.den;
    int64_t b = tb_b.num * (int64_t)tb_a.den;
    if ((FFABS(ts_a)|a|FFABS(ts_b)|b) <= INT_MAX)
        return (ts_a*a > ts_b*b) - (ts_a*a < ts_b*b);
    if (av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b)
        return -1;
    if (av_rescale_rnd(ts_b, b, a, AV_ROUND_DOWN) < ts_a)
        return 1;
    return 0;
}

给定上面的值,你得到:

a = 1 * 1 = 1
b = 60 * 25 = 1500

然后使用这些参数调用av_rescale_rnd

a = 150
b = 1
c = 1500
rnd = AV_ROUND_DOWN

给定我们的参数,我们实际上可以将整个函数av_rescale_rnd 剥离到以下行。 (av_rescale_rnd 的函数体比较长,我不会复制整个函数体,但你可以看一下here。)

return (a * b) / c;

这将返回(150 * 1) / 1500,即0

因此av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) &lt; ts_b 将解析为true,因为0 小于ts_b (5),所以av_compare_ts 将返回-1,这完全不是你想要的。

如果您将r 更改为1/1,它应该可以工作,因为现在您的5 实际上将被视为5 seconds

ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 1/1

av_compare_ts 我们现在得到:

a = 1 * 1 = 1
b = 1 * 25 = 25

然后使用这些参数调用av_rescale_rnd

a = 150
b = 1
c = 25
rnd = AV_ROUND_DOWN

这将返回(150 * 1) / 25,即6

6大于5,条件失败,再次调用av_rescale_rnd,这次是:

a = 5
b = 25
c = 1
rnd = AV_ROUND_DOWN

这将返回(5 * 25) / 1,即125。这比150 小,因此1 被返回,你的问题就解决了。

如果 step_size 大于 1

如果您的音频流的step_size 不是1,您需要修改您的r 以解决这个问题,例如step_size = 1024:

r = { 1, 1024 };

让我们快速回顾一下现在发生的事情:

大约 6 秒:

mAudioOutStream.next_pts = 282
mAudioOutStream.enc->time_base = 1/48000

av_compare_ts 获取以下参数:

ts_a = 282
tb_a = 1/48000
ts_b = 5
tb_b = 1/1024

因此:

a = 1 * 1024 = 1024
b = 1 * 48000 = 48000

av_rescale_rnd:

a = 282
b = 1024
c = 48000
rnd = AV_ROUND_DOWN

(a * b) / c 将给出(282 * 1024) / 48000 = 288768 / 480006

使用r={1,1},您将再次获得0,因为它会计算出(281 * 1) / 48000

【讨论】:

  • 好的,让我试试这个。看起来很有希望。
  • 不,它不起作用。在这种情况下,音频流在 1-2 秒后到达末尾。现在我记得这就是我没有使用 {1,1} 时间戳的原因。这是音频流的详细信息: timebase: {1.48000} 也许我的 next_pts calc 是错误的?每个 PTS step 是 1024 ,codec timestamp 和 stream timestamp 也是相等的。
  • 啊,您需要将r 中的分母乘以步长。你需要r={1,1024}。我会更新我的答案。
  • 放弃我的评论。我有另一个错误导致 {1,1} 无法正常工作。我会把它放在我的问题中。谢谢!
  • 好吧,至少你的编辑解释了为什么 60,1 有时有效。
猜你喜欢
  • 2016-06-10
  • 2012-05-25
  • 1970-01-01
  • 1970-01-01
  • 2016-12-17
  • 2016-06-16
  • 2021-05-08
  • 1970-01-01
  • 2020-04-11
相关资源
最近更新 更多