web-dev-qa-db-ja.com

FFMPEG:異なる持続時間のストリームの多重化

ビデオストリームとオーディオストリームを多重化しています。ビデオストリームは、生成された画像データから取得されます。オーディオストリームは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%長くなります。ビデオフレームが本来あるべき場所で正確に終了するため、オーディオがそれに貢献していることは明らかです。

私が持ってきた最も近い「ハック」はこの部分です:

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で変換しています。少なくともそれは私がしていると私が信じていることです。このハックでは、標準のAACファイルを使用すると、正しいムービーの長さからの偏差が非常に小さくなります。これは、サンプルレート44100、ステレオです。しかし、AACサンプルレート16000、monoなど、より問題のあるサンプルでテストすると、ビデオファイルのサイズがほぼ1秒長くなります。ここで私が間違っていることを誰かが指摘していただければ幸いです。

重要な注意:どのコンテキストでも期間をオンに設定していません。ビデオフレーム数に基づいて、多重化セッションの終了を制御します。もちろん、オーディオ入力ストリームには長さがありますが、ビデオの長さが映画の長さを定義するので、役に立ちません。

更新:

これは2回目の報奨金の試みです。

更新2:

実際、私の音声タイムスタンプ{den、num}は間違っていましたが、答えで説明されているように、{1,1}が実際に進むべき道です。それが機能するのを妨げていたのは、この行のバグでした(私の悪い):

     mAudioOutStream.next_pts += a_pkt.pts;

どちらである必要があります:

     mAudioOutStream.next_pts = a_pkt.pts;

このバグにより、ptsが指数関数的に増加し、ストリームの終わりに非常に早く到達し(ptsに関して)、オーディオストリームが想定よりもはるかに早く終了しました。

22
Michael IV

問題は、指定されたオーディオ時間を560 seconds per tickティックと比較するように指示することです。場合によっては機能することに実際は驚いていますが、実際には、特定のオーディオストリームの特定のtime_baseに依存していると思います。

オーディオのtime_base1/25で、ストリームが6秒であると仮定します。これは必要以上に長いため、av_compare_ts0または1を返すようにします。これらの条件が与えられると、次の値になります。

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の関数本体全体はかなり長いのでコピーしませんが、それを見ることができます ここ 。)

return (a * b) / c;

これにより、(150 * 1) / 1500、つまり0が返されます。

したがって、0ts_b5)よりも小さいため、av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_btrueに解決されます。したがって、av_compare_ts-1を返しますが、これはまさにあなたが望むものではありません。

r1/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が返されます。

65より大きい場合、条件は失敗し、av_rescale_rndが再度呼び出されますが、今回は次のようになります。

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

これは(5 * 25) / 1、つまり125を返します。これは150よりも小さいため、1が返され、問題は解決されます。

step_sizeが1より大きい場合

オーディオストリームのstep_size1でない場合は、それを考慮して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 / 48000を与えます。これは6です。

r={1,1}を使用すると、0が計算されるため、再び(281 * 1) / 48000を取得できます。

3
Max Vollmer