4

OGG ビデオ (コーデックとして theora & vorbis) をデコードしていて、そのサウンドを再生しながら (Ogre 3D を使用して) 画面に表示したいと考えています。画像ストリームを問題なくデコードでき、ビデオは正しいフレームレートなどで完全に再生されます。

ただし、OpenAL ではサウンドをまったく再生できません。

編集:再生音をビデオの実際のオーディオに少なくともある程度似せることができました. サンプルコードを更新しました。

編集 2:「ほぼ」正しいサウンドを得ることができました。STEREO16 だけでなく、AL_FORMAT_STEREO_FLOAT32 (拡張機能の初期化後) を使用するように OpenAL を設定する必要がありました。現在、サウンドは非常に高いピッチで途切れ途切れになっているだけですが、正しい速度です。

オーディオ パケットをデコードする方法は次のとおりです (バックグラウンド スレッドでは、ビデオ ファイルのイメージ ストリームに対して同等の機能が正常に機能します)。

//------------------------------------------------------------------------------
int decodeAudioPacket(  AVPacket& p_packet, AVCodecContext* p_audioCodecContext, AVFrame* p_frame,
                        FFmpegVideoPlayer* p_player, VideoInfo& p_videoInfo)
{
    // Decode audio frame
    int got_frame = 0;
    int decoded = avcodec_decode_audio4(p_audioCodecContext, p_frame, &got_frame, &p_packet);
    if (decoded < 0) 
    {
        p_videoInfo.error = "Error decoding audio frame.";
        return decoded;
    }

    // Frame is complete, store it in audio frame queue
    if (got_frame)
    {
        int bufferSize = av_samples_get_buffer_size(NULL, p_audioCodecContext->channels, p_frame->nb_samples, 
                                                    p_audioCodecContext->sample_fmt, 0);

        int64_t duration = p_frame->pkt_duration;
        int64_t dts = p_frame->pkt_dts;

        if (staticOgreLog)
        {
            staticOgreLog->logMessage("Audio frame bufferSize / duration / dts: " 
                    + boost::lexical_cast<std::string>(bufferSize) + " / "
                    + boost::lexical_cast<std::string>(duration) + " / "
                    + boost::lexical_cast<std::string>(dts), Ogre::LML_NORMAL);
        }

        // Create the audio frame
        AudioFrame* frame = new AudioFrame();
        frame->dataSize = bufferSize;
        frame->data = new uint8_t[bufferSize];
        if (p_frame->channels == 2)
        {
            memcpy(frame->data, p_frame->data[0], bufferSize >> 1);
            memcpy(frame->data + (bufferSize >> 1), p_frame->data[1], bufferSize >> 1);
        }
        else
        {
            memcpy(frame->data, p_frame->data, bufferSize);
        }
        double timeBase = ((double)p_audioCodecContext->time_base.num) / (double)p_audioCodecContext->time_base.den;
        frame->lifeTime = duration * timeBase;

        p_player->addAudioFrame(frame);
    }

    return decoded;
}

ご覧のとおり、フレームをデコードし、それを memcpy して独自の構造体 AudioFrame にします。サウンドが再生されると、これらのオーディオ フレームを次のように使用します。

    int numBuffers = 4;
    ALuint buffers[4];
    alGenBuffers(numBuffers, buffers);
    ALenum success = alGetError();
    if(success != AL_NO_ERROR)
    {
        CONSOLE_LOG("Error on alGenBuffers : " + Ogre::StringConverter::toString(success) + alGetString(success));
        return;
    }

    // Fill a number of data buffers with audio from the stream
    std::vector<AudioFrame*> audioBuffers;
    std::vector<unsigned int> audioBufferSizes;
    unsigned int numReturned = FFMPEG_PLAYER->getDecodedAudioFrames(numBuffers, audioBuffers, audioBufferSizes);

    // Assign the data buffers to the OpenAL buffers
    for (unsigned int i = 0; i < numReturned; ++i)
    {
        alBufferData(buffers[i], _streamingFormat, audioBuffers[i]->data, audioBufferSizes[i], _streamingFrequency);

        success = alGetError();
        if(success != AL_NO_ERROR)
        {
            CONSOLE_LOG("Error on alBufferData : " + Ogre::StringConverter::toString(success) + alGetString(success)
                            + " size: " + Ogre::StringConverter::toString(audioBufferSizes[i]));
            return;
        }
    }

    // Queue the buffers into OpenAL
    alSourceQueueBuffers(_source, numReturned, buffers);
    success = alGetError();
    if(success != AL_NO_ERROR)
    {
        CONSOLE_LOG("Error queuing streaming buffers: " + Ogre::StringConverter::toString(success) + alGetString(success));
        return;
    }
}

alSourcePlay(_source);

OpenAL に与える形式と周波数は、AL_FORMAT_STEREO_FLOAT32 (これはステレオ サウンド ストリームであり、FLOAT32 拡張を初期化しました) と 48000 (オーディオ ストリームの AVCodecContext のサンプル レート) です。

再生中は、次のようにして OpenAL のバッファを補充します。

ALint numBuffersProcessed;

// Check if OpenAL is done with any of the queued buffers
alGetSourcei(_source, AL_BUFFERS_PROCESSED, &numBuffersProcessed);
if(numBuffersProcessed <= 0)
    return;

// Fill a number of data buffers with audio from the stream
std::vector<AudiFrame*> audioBuffers;
std::vector<unsigned int> audioBufferSizes;
unsigned int numFilled = FFMPEG_PLAYER->getDecodedAudioFrames(numBuffersProcessed, audioBuffers, audioBufferSizes);

// Assign the data buffers to the OpenAL buffers
ALuint buffer;
for (unsigned int i = 0; i < numFilled; ++i)
{
    // Pop the oldest queued buffer from the source, 
    // fill it with the new data, then re-queue it
    alSourceUnqueueBuffers(_source, 1, &buffer);

    ALenum success = alGetError();
    if(success != AL_NO_ERROR)
    {
        CONSOLE_LOG("Error Unqueuing streaming buffers: " + Ogre::StringConverter::toString(success));
        return;
    }

    alBufferData(buffer, _streamingFormat, audioBuffers[i]->data, audioBufferSizes[i], _streamingFrequency);

    success = alGetError();
    if(success != AL_NO_ERROR)
    {
        CONSOLE_LOG("Error on re- alBufferData: " + Ogre::StringConverter::toString(success));
        return;
    }

    alSourceQueueBuffers(_source, 1, &buffer);

    success = alGetError();
    if(success != AL_NO_ERROR)
    {
        CONSOLE_LOG("Error re-queuing streaming buffers: " + Ogre::StringConverter::toString(success) + " "
                    + alGetString(success));
        return;
    }
}

// Make sure the source is still playing, 
// and restart it if needed.
ALint playStatus;
alGetSourcei(_source, AL_SOURCE_STATE, &playStatus);
if(playStatus != AL_PLAYING)
    alSourcePlay(_source);

ご覧のとおり、かなり厳しいエラー チェックを行っています。しかし、OpenAL からも FFmpeg からもエラーは発生しません。 編集:私が聞いたのは、ビデオの実際の音声にいくらか似ていますが、非常に高いピッチで、非常に吃音があります. また、テレビのノイズに乗っかって再生しているようです。非常に奇妙な。さらに、正しいオーディオよりもはるかに遅く再生されます。 編集: 2 AL_FORMAT_STEREO_FLOAT32 を使用した後、サウンドは正しい速度で再生されますが、まだ非常に高いピッチで途切れています (ただし、以前よりは少なくなります)。

ビデオ自体は壊れていません。どのプレーヤーでも問題なく再生できます。OpenAL は *.way ファイルも同じアプリケーションで問題なく再生できるため、動作しています。

ここで何が間違っているのか、またはこれを正しく行う方法はありますか?

私の唯一の推測は、どういうわけか、FFmpeg のデコード関数は OpenGL が読み取れるデータを生成しないということです。しかし、これは FFmpeg のデコード例に関する限りであるため、何が欠けているのかわかりません。私が理解しているように、decode_audio4 関数はフレームを生データにデコードします。そして、OpenAL は RAW データを処理できるはずです (というか、それ以外では機能しません)。

4

1 に答える 1

2

それで、私はついにそれを行う方法を考え出しました。ええ、なんて混乱しています。libav-users メーリング リストのユーザーからのヒントで、私は正しいパスにたどり着きました。

ここに私の間違いがあります:

  1. alBufferData 関数で間違った形式を使用しています。私は AL_FORMAT_STEREO16 を使用しました (これは、OpenAL を使用したすべてのストリーミングの例で使用されているものです)。私がストリーミングするビデオは Ogg で、vorbis は浮動小数点で格納されているため、AL_FORMAT_STEREO_FLOAT32 を使用する必要がありました。また、swr_convert を使用して AV_SAMPLE_FMT_FLTP から AV_SAMPLE_FMT_S16 に変換すると、クラッシュするだけです。理由はわかりません。

  2. デコードされたオーディオ フレームをターゲット形式に変換するために、swr_convert を使用しない。swr_convert を使用して FLTP から S16 に変換しようとしたところ、理由もなくクラッシュしたため、壊れていると思いました。しかし、最初の間違いを見つけた後、FLTP から FLT (非平面) に変換して再試行したところ、うまくいきました! そのため、OpenAL は平面ではなくインターリーブ形式を使用します。知っておくと良い。

だから、これはOggビデオ、vorbisオーディオストリームで私のために働いているdecodeAudioPacket関数です:

int decodeAudioPacket(  AVPacket& p_packet, AVCodecContext* p_audioCodecContext, AVFrame* p_frame,
                        SwrContext* p_swrContext, uint8_t** p_destBuffer, int p_destLinesize,
                        FFmpegVideoPlayer* p_player, VideoInfo& p_videoInfo)
{
    // Decode audio frame
    int got_frame = 0;
    int decoded = avcodec_decode_audio4(p_audioCodecContext, p_frame, &got_frame, &p_packet);
    if (decoded < 0) 
    {
        p_videoInfo.error = "Error decoding audio frame.";
        return decoded;
    }

    if(decoded <= p_packet.size)
    {
        /* Move the unread data to the front and clear the end bits */
        int remaining = p_packet.size - decoded;
        memmove(p_packet.data, &p_packet.data[decoded], remaining);
        av_shrink_packet(&p_packet, remaining);
    }

    // Frame is complete, store it in audio frame queue
    if (got_frame)
    {
        int outputSamples = swr_convert(p_swrContext, 
                                        p_destBuffer, p_destLinesize, 
                                        (const uint8_t**)p_frame->extended_data, p_frame->nb_samples);

        int bufferSize = av_get_bytes_per_sample(AV_SAMPLE_FMT_FLT) * p_videoInfo.audioNumChannels
                            * outputSamples;

        int64_t duration = p_frame->pkt_duration;
        int64_t dts = p_frame->pkt_dts;

        if (staticOgreLog)
        {
            staticOgreLog->logMessage("Audio frame bufferSize / duration / dts: " 
                    + boost::lexical_cast<std::string>(bufferSize) + " / "
                    + boost::lexical_cast<std::string>(duration) + " / "
                    + boost::lexical_cast<std::string>(dts), Ogre::LML_NORMAL);
        }

        // Create the audio frame
        AudioFrame* frame = new AudioFrame();
        frame->dataSize = bufferSize;
        frame->data = new uint8_t[bufferSize];
        memcpy(frame->data, p_destBuffer[0], bufferSize);
        double timeBase = ((double)p_audioCodecContext->time_base.num) / (double)p_audioCodecContext->time_base.den;
        frame->lifeTime = duration * timeBase;

        p_player->addAudioFrame(frame);
    }

    return decoded;
}

そして、コンテキストと宛先バッファーを初期化する方法は次のとおりです。

// Initialize SWR context
SwrContext* swrContext = swr_alloc_set_opts(NULL, 
            audioCodecContext->channel_layout, AV_SAMPLE_FMT_FLT, audioCodecContext->sample_rate,
            audioCodecContext->channel_layout, audioCodecContext->sample_fmt, audioCodecContext->sample_rate, 
            0, NULL);
int result = swr_init(swrContext);

// Create destination sample buffer
uint8_t** destBuffer = NULL;
int destBufferLinesize;
av_samples_alloc_array_and_samples( &destBuffer,
                                    &destBufferLinesize,
                                    videoInfo.audioNumChannels,
                                    2048,
                                    AV_SAMPLE_FMT_FLT,
                                    0);
于 2014-01-28T11:42:40.093 に答える