草庐IT

c++ - FFmpeg + OpenAL - 无法播放视频中的流式声音

coder 2024-02-24 原文

我正在解码 OGG 视频(theora 和 vorbis 作为编解码器)并希望在播放声音的同时在屏幕上显示它(使用 Ogre 3D)。我可以很好地解码图像流,视频以正确的帧速率完美播放,等等。

但是,我根本无法使用 OpenAL 播放声音。

编辑:我设法让播放的声音至少在某种程度上类似于视频中的实际音频。更新了示例代码。

编辑 2:我现在能够获得“几乎”正确的声音。我必须将 OpenAL 设置为使用 AL_FORMAT_STEREO_FLOAT32(在初始化扩展后),而不仅仅是 STEREO16。现在声音“只是”非常高的音调和断断续续的,但速度正确。

下面是我解码音频数据包的方法(在后台线程中,等效的方法适用于视频文件的图像流):

//------------------------------------------------------------------------------
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;
}

因此,如您所见,我解码了帧,将其存储到我自己的结构 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 数据(或者更确切地说,不能处理其他任何数据)。

最佳答案

所以,我终于想出了办法。哎呀,真是一团糟。这是一个hint来自 libav-users 邮件列表上的用户,使我走上了正确的道路。

这是我的错误:

  1. 在 alBufferData 函数中使用了错误的格式。我使用了 AL_FORMAT_STEREO16(因为这是每个带有 OpenAL 的流媒体示例所使用的格式)。我应该使用 AL_FORMAT_STEREO_FLOAT32,因为我流式传输的视频是 Ogg,而 vorbis 是以浮点形式存储的。使用 swr_convert 从 AV_SAMPLE_FMT_FLTP 转换为 AV_SAMPLE_FMT_S16 只是崩溃。不知道为什么。

  2. 不使用 swr_convert 将解码的音频帧转换为目标格式。在我尝试使用 swr_convert 将 FLTP 转换为 S16 之后,它会在没有给出原因的情况下崩溃,我以为它坏了。但是在弄清楚我的第一个错误之后,我再次尝试,从 FLTP 转换为 FLT(非平面)然后它成功了!所以 OpenAL 使用交错格式,而不是平面格式。很高兴知道。

下面是 decodeAudioPacket 函数,它适用于 Ogg 视频、vorbis 音频流:

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);

关于c++ - FFmpeg + OpenAL - 无法播放视频中的流式声音,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21386135/

有关c++ - FFmpeg + OpenAL - 无法播放视频中的流式声音的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  5. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  6. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  7. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

    我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

  8. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  9. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  10. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

随机推荐