跨平台音频IO处理库libsoundio实践

远程桌面应用开发过程中需要实现一个高效率的跨平台的音频播放处理,于是参考了moonlight项目,发现他采用了一个很厉害的库libsoundio,这个库支持多种backend播放服务,可以实现非常高小的音频播放采集应用,于是也做了一个简单的驱动接口,以实现我的远程桌面控制客户端的一个音频处理组件,简洁高效的实现了多平台的音频播放功能,根据rk3588板子的个性还可以实现音频播放设备热拔插识别等功能,推荐给需要的人,这个基础上还可以实现更强大的需求,欢迎感兴趣的朋友交流互鉴,共同成长。你对远程桌面客户端开发有什么想法也可以告诉我,我尽力来实现。

cpp 复制代码
#ifndef SOUNDIO_AUDIO_DRIVER_H
#define SOUNDIO_AUDIO_DRIVER_H

#include <QObject>
#include <QTimer>
#include <QMutex>
#include <QDateTime>
#include <soundio/soundio.h>

struct AudioBuffer {
    char* data;
    int capacity;
    int size;
    int read_pos;
    int write_pos;
};

class SoundIoAudioDriver : public QObject
{
    Q_OBJECT

public:
    explicit SoundIoAudioDriver(QObject *parent = nullptr);
    ~SoundIoAudioDriver();

    bool initialize();
    bool playAudio(const uint8_t* data, int length);
    void stop();
    void setAudioParameters(int sampleRate, int channels);
    QString lastError() const { return m_lastError; }
    bool isInitialized() const { return m_initialized; }

signals:
    void deviceConnected(const QString& deviceName);
    void deviceError(const QString& error);

private:
    struct SoundIo* m_soundio;
    struct SoundIoDevice* m_device;
    struct SoundIoOutStream* m_outstream;
    AudioBuffer* m_audioBuffer;
    
    int m_sampleRate;
    int m_channels;
    SoundIoFormat m_format;
    
    bool m_initialized;
    bool m_streamStarted;
    
    QTimer* m_deviceCheckTimer;
    qint64 m_lastDeviceCheckTime;
    QString m_lastError;
    QMutex m_bufferMutex;

    bool setupOutputStream();
    bool selectBestDevice();
    bool isDesiredHdmiDevice(struct SoundIoDevice *dev);
    void cleanup();
    void checkDeviceStatus();

    static void onDevicesChange(struct SoundIo *soundio);
    static void onBackendDisconnect(struct SoundIo *soundio, int err);
    static void onWriteCallback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max);
    static void onUnderflowCallback(struct SoundIoOutStream *outstream);
};

#endif // SOUNDIO_AUDIO_DRIVER_H
cpp 复制代码
#include "soundio_audio_driver.h"
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <cstring>

SoundIoAudioDriver::SoundIoAudioDriver(QObject *parent)
    : QObject(parent)
    , m_soundio(nullptr)
    , m_device(nullptr)
    , m_outstream(nullptr)
    , m_audioBuffer(nullptr)
    , m_sampleRate(48000)
    , m_channels(2)
    , m_format(SoundIoFormatFloat32NE)
    , m_initialized(false)
    , m_streamStarted(false)
    , m_deviceCheckTimer(nullptr)
    , m_lastDeviceCheckTime(0)
{
}

SoundIoAudioDriver::~SoundIoAudioDriver()
{
    stop();
    cleanup();
}

bool SoundIoAudioDriver::initialize()
{
    if (m_initialized) {
        cleanup();
    }
    
    m_soundio = soundio_create();
    if (!m_soundio) {
        m_lastError = "Failed to create SoundIo instance";
        qWarning() << "[SoundIoAudioDriver]" << m_lastError;
        return false;
    }
    
    m_soundio->userdata = this;
    m_soundio->on_devices_change = onDevicesChange;
    m_soundio->on_backend_disconnect = onBackendDisconnect;
    
    int err = soundio_connect_backend(m_soundio, SoundIoBackendAlsa);
    if (err != SoundIoErrorNone) {
        m_lastError = QString("Failed to connect to ALSA backend: %1").arg(soundio_strerror(err));
        qWarning() << "[SoundIoAudioDriver]" << m_lastError;
        
        err = soundio_connect(m_soundio);
        if (err != SoundIoErrorNone) {
            m_lastError = QString("Failed to connect to any backend: %1").arg(soundio_strerror(err));
            qWarning() << "[SoundIoAudioDriver]" << m_lastError;
            cleanup();
            return false;
        }
    }
    
    qInfo() << "[SoundIoAudioDriver] Connected to backend:" << soundio_backend_name(m_soundio->current_backend);
    
    soundio_flush_events(m_soundio);
    
    if (!selectBestDevice()) {
        m_lastError = "Failed to select audio device";
        qWarning() << "[SoundIoAudioDriver]" << m_lastError;
        cleanup();
        return false;
    }
    
    if (!setupOutputStream()) {
        m_lastError = "Failed to setup output stream";
        qWarning() << "[SoundIoAudioDriver]" << m_lastError;
        cleanup();
        return false;
    }
    
    // 修复:正确初始化音频缓冲区
    m_audioBuffer = new AudioBuffer;
    m_audioBuffer->capacity = m_sampleRate * m_channels * sizeof(float) * 4; // 4秒的缓冲区
    m_audioBuffer->data = new char[m_audioBuffer->capacity];
    m_audioBuffer->size = 0;
    m_audioBuffer->read_pos = 0;
    m_audioBuffer->write_pos = 0;
    
    m_deviceCheckTimer = new QTimer(this);
    connect(m_deviceCheckTimer, &QTimer::timeout, this, &SoundIoAudioDriver::checkDeviceStatus);
    m_deviceCheckTimer->start(5000);
    
    m_initialized = true;
    m_lastDeviceCheckTime = QDateTime::currentMSecsSinceEpoch();
    
    qInfo() << "[SoundIoAudioDriver] Audio driver initialized successfully";
    return true;
}

bool SoundIoAudioDriver::playAudio(const uint8_t* data, int length)
{
    if (!m_initialized || !m_audioBuffer || !data || length <= 0) {
        return false;
    }
    
    QMutexLocker locker(&m_bufferMutex);
    
    if (m_audioBuffer->size + length > m_audioBuffer->capacity) {
        static qint64 lastLogTime = 0;
        qint64 currentTime = QDateTime::currentMSecsSinceEpoch();
        if (currentTime - lastLogTime > 1000) {
            qWarning() << "[SoundIoAudioDriver] Audio buffer overflow, dropping data";
            lastLogTime = currentTime;
        }
        return false;
    }
    
    // 修复:改进的环形缓冲区写入逻辑
    int remaining = length;
    const char* src = reinterpret_cast<const char*>(data);
    
    while (remaining > 0) {
        int available = m_audioBuffer->capacity - m_audioBuffer->write_pos;
        int to_copy = qMin(remaining, available);
        
        memcpy(m_audioBuffer->data + m_audioBuffer->write_pos, src, to_copy);
        
        m_audioBuffer->write_pos = (m_audioBuffer->write_pos + to_copy) % m_audioBuffer->capacity;
        m_audioBuffer->size += to_copy;
        src += to_copy;
        remaining -= to_copy;
    }
    
    return true;
}

void SoundIoAudioDriver::stop()
{
    if (m_streamStarted && m_outstream) {
        soundio_outstream_pause(m_outstream, true);
        m_streamStarted = false;
    }
    
    if (m_deviceCheckTimer) {
        m_deviceCheckTimer->stop();
    }
    
    m_initialized = false;
}

void SoundIoAudioDriver::setAudioParameters(int sampleRate, int channels)
{
    m_sampleRate = sampleRate;
    m_channels = channels;
}

void SoundIoAudioDriver::onDevicesChange(struct SoundIo *soundio)
{
    SoundIoAudioDriver* driver = static_cast<SoundIoAudioDriver*>(soundio->userdata);
    if (!driver) return;
    
    qInfo() << "[SoundIoAudioDriver] Audio devices changed";
    soundio_flush_events(soundio);
    driver->selectBestDevice();
}

void SoundIoAudioDriver::onBackendDisconnect(struct SoundIo *soundio, int err)
{
    SoundIoAudioDriver* driver = static_cast<SoundIoAudioDriver*>(soundio->userdata);
    if (!driver) return;
    
    qWarning() << "[SoundIoAudioDriver] Backend disconnected:" << soundio_strerror(err);
    driver->m_initialized = false;
    driver->m_lastError = QString("Backend disconnected: %1").arg(soundio_strerror(err));
    emit driver->deviceError(driver->m_lastError);
}

void SoundIoAudioDriver::onWriteCallback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max)
{
    SoundIoAudioDriver* driver = static_cast<SoundIoAudioDriver*>(outstream->userdata);
    if (!driver || !driver->m_audioBuffer) return;
    
    struct SoundIoChannelArea *areas;
    int err;
    
    int frames_left = frame_count_max;
    
    for (;;) {
        int frame_count = frames_left;
        if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) {
            qWarning() << "[SoundIoAudioDriver] Unrecoverable stream error in write callback:" << soundio_strerror(err);
            return;
        }
        
        if (!frame_count)
            break;
        
        QMutexLocker locker(&driver->m_bufferMutex);
        AudioBuffer* buffer = driver->m_audioBuffer;
        
        const struct SoundIoChannelLayout *layout = &outstream->layout;
        int bytes_per_frame = soundio_get_bytes_per_frame(outstream->format, layout->channel_count);
        int bytes_needed = frame_count * bytes_per_frame;
        
        if (buffer && buffer->size >= bytes_needed) {
            // 修复:改进的环形缓冲区读取逻辑
            for (int frame = 0; frame < frame_count; frame++) {
                for (int channel = 0; channel < layout->channel_count; channel++) {
                    int byte_offset = (buffer->read_pos + frame * bytes_per_frame) % buffer->capacity;
                    memcpy(areas[channel].ptr, buffer->data + byte_offset, sizeof(float));
                    areas[channel].ptr += areas[channel].step;
                }
            }
            
            buffer->read_pos = (buffer->read_pos + bytes_needed) % buffer->capacity;
            buffer->size -= bytes_needed;
            
        } else {
            // 没有足够数据,填充静音
            for (int frame = 0; frame < frame_count; frame++) {
                for (int channel = 0; channel < layout->channel_count; channel++) {
                    float silence = 0.0f;
                    memcpy(areas[channel].ptr, &silence, sizeof(float));
                    areas[channel].ptr += areas[channel].step;
                }
            }
        }
        
        if ((err = soundio_outstream_end_write(outstream))) {
            if (err == SoundIoErrorUnderflow)
                return;
            qWarning() << "[SoundIoAudioDriver] Unrecoverable stream error in end write:" << soundio_strerror(err);
            return;
        }
        
        frames_left -= frame_count;
        if (frames_left <= 0)
            break;
    }
}

void SoundIoAudioDriver::onUnderflowCallback(struct SoundIoOutStream *outstream)
{
    static int count = 0;
    if (count % 100 == 0) {
        qWarning() << "[SoundIoAudioDriver] Audio underflow" << count;
    }
    count++;
}

bool SoundIoAudioDriver::setupOutputStream()
{
    if (!m_device) return false;
    
    m_outstream = soundio_outstream_create(m_device);
    if (!m_outstream) {
        qWarning() << "[SoundIoAudioDriver] Failed to create output stream";
        return false;
    }
    
    // 使用设备支持的参数而不是硬编码参数
    if (m_device->layouts && m_device->layout_count > 0) {
        m_outstream->layout = m_device->layouts[0];
    } else {
        m_outstream->layout = *soundio_channel_layout_get_default(m_channels);
    }
    
    // 尝试使用设备支持的采样率
    if (m_device->sample_rates && m_device->sample_rate_count > 0) {
        // 查找最接近的采样率
        int best_rate = m_device->sample_rates[0].max;
        int target_rate = m_sampleRate;
        int min_diff = abs(best_rate - target_rate);
        
        for (int i = 0; i < m_device->sample_rate_count; i++) {
            int current_rate = m_device->sample_rates[i].max;
            int diff = abs(current_rate - target_rate);
            if (diff < min_diff) {
                min_diff = diff;
                best_rate = current_rate;
            }
        }
        m_outstream->sample_rate = best_rate;
        qInfo() << "[SoundIoAudioDriver] Using sample rate:" << best_rate;
    } else {
        m_outstream->sample_rate = m_sampleRate;
    }
    
    // 尝试使用设备支持的格式
    if (m_device->formats && m_device->format_count > 0) {
        // 优先选择浮点格式,否则选择第一个支持的格式
        SoundIoFormat preferred_formats[] = {
            SoundIoFormatFloat32NE,
            SoundIoFormatFloat32LE,
            SoundIoFormatS32NE,
            SoundIoFormatS32LE,
            SoundIoFormatS24NE,
            SoundIoFormatS24LE,
            SoundIoFormatS16NE,
            SoundIoFormatS16LE
        };
        
        m_outstream->format = m_device->formats[0]; // 默认选择第一个
        
        for (int i = 0; i < sizeof(preferred_formats) / sizeof(preferred_formats[0]); i++) {
            for (int j = 0; j < m_device->format_count; j++) {
                if (m_device->formats[j] == preferred_formats[i]) {
                    m_outstream->format = preferred_formats[i];
                    break;
                }
            }
        }
        qInfo() << "[SoundIoAudioDriver] Using format:" << soundio_format_string(m_outstream->format);
    } else {
        m_outstream->format = m_format;
    }
    
    m_outstream->userdata = this;
    m_outstream->write_callback = onWriteCallback;
    m_outstream->underflow_callback = onUnderflowCallback;
    
    int err = soundio_outstream_open(m_outstream);
    if (err) {
        qWarning() << "[SoundIoAudioDriver] Failed to open output stream:" << soundio_strerror(err);
        qWarning() << "[SoundIoAudioDriver] Device capabilities:";
        qWarning() << "  - Sample rates:" << m_device->sample_rate_count;
        qWarning() << "  - Formats:" << m_device->format_count;
        qWarning() << "  - Layouts:" << m_device->layout_count;
        
        // 打印更多设备信息用于调试
        if (m_device->sample_rates && m_device->sample_rate_count > 0) {
            for (int i = 0; i < m_device->sample_rate_count; i++) {
                qWarning() << "    Rate" << i << ":" << m_device->sample_rates[i].min << "-" << m_device->sample_rates[i].max;
            }
        }
        
        if (m_device->formats && m_device->format_count > 0) {
            for (int i = 0; i < m_device->format_count; i++) {
                qWarning() << "    Format" << i << ":" << soundio_format_string(m_device->formats[i]);
            }
        }
        
        soundio_outstream_destroy(m_outstream);
        m_outstream = nullptr;
        return false;
    }
    
    // 检查实际使用的参数
    qInfo() << "[SoundIoAudioDriver] Stream opened with parameters:";
    qInfo() << "  - Sample rate:" << m_outstream->sample_rate;
    qInfo() << "  - Format:" << soundio_format_string(m_outstream->format);
    qInfo() << "  - Channels:" << m_outstream->layout.channel_count;
    
    err = soundio_outstream_start(m_outstream);
    if (err) {
        qWarning() << "[SoundIoAudioDriver] Failed to start output stream:" << soundio_strerror(err);
        soundio_outstream_destroy(m_outstream);
        m_outstream = nullptr;
        return false;
    }
    
    m_streamStarted = true;
    qInfo() << "[SoundIoAudioDriver] Output stream started successfully";
    return true;
}

void SoundIoAudioDriver::cleanup()
{
    stop();
    
    if (m_outstream) {
        soundio_outstream_destroy(m_outstream);
        m_outstream = nullptr;
    }
    
    if (m_device) {
        soundio_device_unref(m_device);
        m_device = nullptr;
    }
    
    if (m_soundio) {
        soundio_disconnect(m_soundio);
        soundio_destroy(m_soundio);
        m_soundio = nullptr;
    }
    
    if (m_audioBuffer) {
        delete[] m_audioBuffer->data;
        delete m_audioBuffer;
        m_audioBuffer = nullptr;
    }
    
    if (m_deviceCheckTimer) {
        m_deviceCheckTimer->deleteLater();
        m_deviceCheckTimer = nullptr;
    }
    
    m_initialized = false;
}

bool SoundIoAudioDriver::selectBestDevice()
{
    if (!m_soundio) return false;
    
    struct SoundIoDevice *chosen = nullptr;
    int output_cnt = soundio_output_device_count(m_soundio);
    
    for (int i = 0; i < output_cnt; ++i) {
        struct SoundIoDevice *dev = soundio_get_output_device(m_soundio, i);
        if (!dev) continue;
        
        if (isDesiredHdmiDevice(dev)) {
            chosen = dev;
            qInfo() << "[SoundIoAudioDriver] Found desired HDMI audio device:" << dev->name;
            emit deviceConnected(QString(dev->name));
            break;
        }
        
        soundio_device_unref(dev);
    }
    
    if (!chosen) {
        int default_idx = soundio_default_output_device_index(m_soundio);
        if (default_idx >= 0) {
            chosen = soundio_get_output_device(m_soundio, default_idx);
            if (chosen) {
                qInfo() << "[SoundIoAudioDriver] Using default audio device:" << chosen->name;
                emit deviceConnected(QString(chosen->name));
            }
        }
    }
    
    if (!chosen) {
        qWarning() << "[SoundIoAudioDriver] No suitable audio output device found";
        return false;
    }
    
    if (m_device) {
        soundio_device_unref(m_device);
    }
    
    m_device = chosen;
    qInfo() << "[SoundIoAudioDriver] Selected output device:" << m_device->name << "(id=" << m_device->id << ")";
    
    if (m_device->probe_error) {
        qWarning() << "[SoundIoAudioDriver] Cannot probe device:" << soundio_strerror(m_device->probe_error);
        return false;
    }
    
    return true;
}

bool SoundIoAudioDriver::isDesiredHdmiDevice(struct SoundIoDevice *dev)
{
    if (!dev || !dev->id) return false;
    int card = -1;
    if (sscanf(dev->id, "hw:%d", &card) != 1) return false;
    return (card >= 2 && card <= 5);
}

void SoundIoAudioDriver::checkDeviceStatus()
{
    if (!m_initialized || !m_soundio) return;
    
    qint64 currentTime = QDateTime::currentMSecsSinceEpoch();
    if (currentTime - m_lastDeviceCheckTime > 5000) {
        m_lastDeviceCheckTime = currentTime;
        soundio_flush_events(m_soundio);
    }
}
相关推荐
RTC实战笔记4 天前
实时互动数字人怎么做,才不是一个只会说话的视频?
音视频·数字人·rtc·数字人接入
用户805533698035 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner5 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz10 天前
QML Hello World 入门示例
qt
xcyxiner13 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner14 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner14 天前
DicomViewer (添加模型类)3
qt
xcyxiner15 天前
DicomViewer (目录调整) 2
qt
xcyxiner15 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
RTC实战笔记16 天前
Android 实时音视频接入教程:媒体补充增强信息(SEI)
音视频·媒体·rtc