1

C/C++ FFmpeg トランスコーダーに奇妙な問題があります。これは、入力 MP4 (さまざまな入力コーデック) を受け取り、MP4 (x264、ベースライン & AAC LC @44100 サンプル レート、libfdk_aac) を生成して出力します。

結果として得られる mp4 ビデオには、きれいな画像 (x264) があり、オーディオ (AAC LC) も正常に機能しますが、ビデオのちょうど半分までしか再生されません。

オーディオの速度が遅くなったり、引き伸ばされたり、途切れたりすることはありません。動画の途中で止まるだけです。

1 つのヒントは、入力ファイルのサンプル レートが 22050 で、22050/44100 が 0.5 であることかもしれませんが、なぜこれが半分の時間でサウンドを停止させるのか、私にはよくわかりません。このようなエラーが原因で、音が間違った速度になることが予想されます。44100 を強制しようとせず、代わりに受信する sample_rate を使用するだけで、すべてがうまく機能します。

もう 1 つの推測は、pts の計算が機能しないことです。しかし、オーディオは(停止するまで)問題なく聞こえます。ビデオ部分についてもまったく同じことを行い、問題なく動作します。「まさに」、同じコードのようですが、「オーディオ」変数が「ビデオ」変数に置き換えられています。

FFmpeg は、プロセス全体でエラーを報告しません。また、入力からのすべてのパッケージの読み取りが完了した後、decoders/encoders/interleaved_writing をフラッシュします。ビデオではうまく機能するので、私の一般的なアプローチに大きな誤りがあるとは思えません。

これが私のコードの機能です(エラー処理と他のクラスのものを取り除きました):

AudioCodecContext のセットアップ

outContext->_audioCodec = avcodec_find_encoder(outContext->_audioTargetCodecID);
outContext->_audioStream = 
        avformat_new_stream(outContext->_formatContext, outContext->_audioCodec);
outContext->_audioCodecContext = outContext->_audioStream->codec;
outContext->_audioCodecContext->channels = 2;
outContext->_audioCodecContext->channel_layout = av_get_default_channel_layout(2);
outContext->_audioCodecContext->sample_rate = 44100;
outContext->_audioCodecContext->sample_fmt = outContext->_audioCodec->sample_fmts[0];
outContext->_audioCodecContext->bit_rate = 128000;
outContext->_audioCodecContext->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
outContext->_audioCodecContext->time_base = 
        (AVRational){1, outContext->_audioCodecContext->sample_rate};
outContext->_audioStream->time_base = (AVRational){1, outContext->_audioCodecContext->sample_rate};
int retVal = avcodec_open2(outContext->_audioCodecContext, outContext->_audioCodec, NULL);

リサンプラのセットアップ

outContext->_audioResamplerContext = 
        swr_alloc_set_opts( NULL, outContext->_audioCodecContext->channel_layout,
                            outContext->_audioCodecContext->sample_fmt,
                            outContext->_audioCodecContext->sample_rate,
                            _inputContext._audioCodecContext->channel_layout,
                            _inputContext._audioCodecContext->sample_fmt,
                            _inputContext._audioCodecContext->sample_rate,
                            0, NULL);
int retVal = swr_init(outContext->_audioResamplerContext);

デコード

decodedBytes = avcodec_decode_audio4(   _inputContext._audioCodecContext, 
                                        _inputContext._audioTempFrame, 
                                        &p_gotAudioFrame, &_inputContext._currentPacket);

変換中 (もちろん、デコードによってフレームが生成された場合のみ)

int retVal = swr_convert(   outContext->_audioResamplerContext, 
                            outContext->_audioConvertedFrame->data, 
                            outContext->_audioConvertedFrame->nb_samples, 
                            (const uint8_t**)_inputContext._audioTempFrame->data, 
                            _inputContext._audioTempFrame->nb_samples);

エンコーディング(もちろん、デコーディングによってフレームが生成された場合のみ)

outContext->_audioConvertedFrame->pts = 
        av_frame_get_best_effort_timestamp(_inputContext._audioTempFrame);

// Init the new packet
av_init_packet(&outContext->_audioPacket);
outContext->_audioPacket.data = NULL;
outContext->_audioPacket.size = 0;

// Encode
int retVal = avcodec_encode_audio2( outContext->_audioCodecContext, 
                                    &outContext->_audioPacket, 
                                    outContext->_audioConvertedFrame,
                                    &p_gotPacket);


// Set pts/dts time stamps for writing interleaved
av_packet_rescale_ts(   &outContext->_audioPacket, 
                        outContext->_audioCodecContext->time_base,
                        outContext->_audioStream->time_base);
outContext->_audioPacket.stream_index = outContext->_audioStream->index;

書き込み(もちろん、エンコードによってパケットが生成された場合のみ)

int retVal = av_interleaved_write_frame(outContext->_formatContext, &outContext->_audioPacket);

何がそのような行動を引き起こすのかについて、私はまったく考えていません。

4

1 に答える 1

1

それで、私はついに自分で物事を理解することができました。

問題は確かに sample_rate の違いにありました。私が行ったように、swr_convert() を呼び出すと、オーディオ フレームを変換するために必要なすべてのサンプルが得られると思われます。もちろん、それは簡単すぎるでしょう。

代わりに、swr_convert (潜在的に)をフレームごとに複数回呼び出し、必要に応じてその出力をバッファリングする必要があります。次に、バッファから単一のフレームを取得する必要があり、それをエンコードする必要があります。

ここに私の新しい convertAudioFrame 関数があります:

// Calculate number of output samples
int numOutputSamples = av_rescale_rnd(  
    swr_get_delay(outContext->_audioResamplerContext, _inputContext._audioCodecContext->sample_rate) 
    + _inputContext._audioTempFrame->nb_samples, 
    outContext->_audioCodecContext->sample_rate, 
    _inputContext._audioCodecContext->sample_rate, 
    AV_ROUND_UP);
if (numOutputSamples == 0) 
{
    return;
}

uint8_t* tempSamples;
av_samples_alloc(   &tempSamples, NULL, 
                    outContext->_audioCodecContext->channels, numOutputSamples,
                    outContext->_audioCodecContext->sample_fmt, 0);

int retVal = swr_convert(   outContext->_audioResamplerContext, 
                            &tempSamples, 
                            numOutputSamples, 
                            (const uint8_t**)_inputContext._audioTempFrame->data, 
                            _inputContext._audioTempFrame->nb_samples);

// Write to audio fifo
if (retVal > 0)
{
    retVal = av_audio_fifo_write(outContext->_audioFifo, (void**)&tempSamples, retVal);
}
av_freep(&tempSamples);

// Get a frame from audio fifo
int samplesAvailable = av_audio_fifo_size(outContext->_audioFifo);
if (samplesAvailable > 0)
{
    retVal = av_audio_fifo_read(outContext->_audioFifo, 
                                (void**)outContext->_audioConvertedFrame->data,
                                outContext->_audioCodecContext->frame_size);

    // We got a frame, so also set its pts
    if (retVal > 0)
    {
        p_gotConvertedFrame = 1;

        if (_inputContext._audioTempFrame->pts != AV_NOPTS_VALUE)
        {
            outContext->_audioConvertedFrame->pts = _inputContext._audioTempFrame->pts;
        }
        else if (_inputContext._audioTempFrame->pkt_pts != AV_NOPTS_VALUE)
        {
            outContext->_audioConvertedFrame->pts = _inputContext._audioTempFrame->pkt_pts;
        }
    }
}

この関数は基本的に、オーディオ fifo バッファーにフレームがなくなるまで呼び出します。

つまり、デコードしたフレーム数だけエンコードしたため、オーディオの長さは半分しかありませんでした。sample_rate が 2 倍であるため、実際には 2 倍のフレームをエンコードする必要がありました。

于 2015-08-17T08:36:00.273 に答える