【问题标题】:QAudioOutput buffer underflowQAudioOutput 缓冲区下溢
【发布时间】:2017-04-17 03:24:37
【问题描述】:

收到消息“出现缓冲区下溢!”每次写完这个简单的程序。

Beep.hpp:

#pragma once

#include <QTimer>
#include <QAudioOutput>

class Beep: public QObject
{
    Q_OBJECT

public:
    explicit Beep();
    virtual ~Beep();

    void onTimer();

private:
    QAudioOutput m_out;
    QIODevice *m_outDev;
    QTimer m_timer;
};

Beep.cpp:

#include "Beep.hpp"

int ms = 100;

const QAudioFormat defaultAudioFormat = []()
{
    QAudioFormat format;
    format.setSampleRate(8000);
    format.setChannelCount(1);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::SignedInt);
    return format;
}();

Beep::Beep() :
        m_out(defaultAudioFormat),
        m_outDev()
{
    m_out.setBufferSize(16 * ms);
    m_outDev = m_out.start();

    QObject::connect(&m_timer, &QTimer::timeout, this, &Beep::onTimer);

    m_timer.setSingleShot(false);
    m_timer.start(ms);
}

Beep::~Beep()
{
}

void Beep::onTimer()
{
    std::vector<uint8_t> samples(16 * ms);
    m_outDev->write((char*) &samples.front(), samples.size());
}

main.cpp:

#include <QCoreApplication>

#include "Beep.hpp"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    Beep beep;
    return app.exec();
}

这个测试程序只是用零写入缓冲区。有了真实数据,就会有爆裂声。

写入更多数据或更改时间会使情况变得更糟。这段代码有什么问题?

【问题讨论】:

    标签: qt audio qt5


    【解决方案1】:

    使用定时器是错误的做法。

    使用 notify() 信号

    void AudioManager::init_audio(AudioManager *mgr) {
        if (mgr->stream_id == -1) return;
    
        mgr->audio_format.setSampleRate(mgr->context->time_base.den);
        mgr->audio_format.setSampleSize(16);
        mgr->audio_format.setChannelCount(2);
        mgr->audio_format.setCodec("audio/pcm");
        mgr->audio_format.setSampleType(QAudioFormat::SignedInt);
        QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
        if (!info.isFormatSupported(mgr->audio_format)) {
            mgr->audio_format = info.nearestFormat(mgr->audio_format);
        }
    
        mgr->audio_out = new QAudioOutput(mgr->audio_format, nullptr);
        mgr->audio_out->setNotifyInterval(15);
        mgr->audio_out->setBufferSize(mgr->context->time_base.den * 4); // 1 second worth of stereo data
    
        connect(mgr->audio_out, SIGNAL(notify()), mgr, SLOT(audio_out_notify()));
        connect(mgr->audio_out, SIGNAL(stateChanged(QAudio::State)), mgr, SLOT(audio_out_state_changed(QAudio::State)));
        qreal volume_out = (qreal)parent->volume / 100.0f;
        mgr->audio_out->setVolume(volume_out);
        mgr->audio_out_device = mgr->audio_out->start();
    }
    

    当音频播放需要更多数据时会调用它

    void AudioManager::audio_out_notify() {
        qDebug() << "Audio notify";
        check_audio_playback();
    }
    

    下面的大部分代码都无关紧要,但也称为音频已停止播放。

    void AudioManager::check_audio_playback() {
        if (stream_id == -1) return;
    
        pthread_mutex_lock(&audio_mutex);
    
        if (!audio_out->state() == QAudio::State::IdleState) {
            pthread_mutex_unlock(&audio_mutex);
            return;
        }
    
        if (parent->pts_start_time < 0.0) {
            if (parent->Video.stream_id == -1 && decode_pos > 65) { // start playback
                parent->pts_start_time = buffers[0].frame_time;
                parent->sys_start_time = (double)parent->timer.elapsed() / 1000.0;
                qDebug() << "Audio playback started";
            } else {
                pthread_mutex_unlock(&audio_mutex);
                return;
            }
        }
    
        if (playback_pos == decode_pos) {
            pthread_mutex_unlock(&audio_mutex);
            return;
        }
    
    
    
        AudioBuffer *buffer = nullptr;
        double current_sys_time = ((double)parent->timer.elapsed() / 1000.0) - parent->sys_start_time;
    
        bool bounds = false;
        int skipped = 0;
    
        while (!bounds) {
            if (playback_pos == decode_pos) bounds = true;
            else {
                AudioBuffer *temp_buffer = &buffers[playback_pos];
                double temp_time = temp_buffer->frame_time - parent->pts_start_time;
    
                if (temp_time < current_sys_time ) {
                    if (buffer) {
                        buffer->used = false;
                        skipped++;
                    }
                    buffer = temp_buffer;
                    playback_pos++; playback_pos %= MAX_AUD_BUFFERS;
                } else {
                    bounds = true;
                }
            }
        }
    
        if (skipped > 0) qDebug("Skipped %d audio buffers on playback", skipped);
    
        if (buffer) {
            audio_out_device->write((const char *)buffer->data, buffer->buffer_size);
            buffer->used = false;
        }
    
        pthread_mutex_unlock(&audio_mutex);
    }
    

    Qt 网站上的示例一开始http://qt.apidoc.info/5.1.1/qtmultimedia/audiooutput.html 并没有那么明显,但是当我将它放入测试时,它并不算太糟糕。

    【讨论】:

    • Notify 在这种情况下没有用。我有一个外部计时器,所以它是“拉模式”(根据audiooutput.html 示例)。
    【解决方案2】:

    原因是音频数据的来源不是“生产质量模块”(它是一个虚拟测试类):计时器在漂移,因为它的实际间隔是 10 毫秒加上处理时间。

    其他观察:

    • QAudioOutput::setBufferSize()更大
    • 在大小与QAudioInput::periodSize()QAudioOutput::periodSize() 匹配的块中执行QAudioInput::read()QAudioOutput::write()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-16
      • 1970-01-01
      • 2010-11-11
      相关资源
      最近更新 更多