跨平台音频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);
    }
}
相关推荐
ajassi20004 小时前
开源 C++ QT QML 开发(二十)多媒体--摄像头拍照
c++·qt·开源
_OP_CHEN4 小时前
C++基础:(十二)list类的基础使用
开发语言·数据结构·c++·stl·list类·list核心接口·list底层原理
ONE_PUNCH_Ge7 小时前
Go 语言变量
开发语言
幼稚园的山代王7 小时前
go语言了解
开发语言·后端·golang
晚风残7 小时前
【C++ Primer】第六章:函数
开发语言·c++·算法·c++ primer
满天星83035777 小时前
【C++】AVL树的模拟实现
开发语言·c++·算法·stl
weixin_456904277 小时前
基于.NET Framework 4.0的串口通信
开发语言·c#·.net
ss2737 小时前
手写MyBatis第107弹:@MapperScan原理与SqlSessionTemplate线程安全机制
java·开发语言·后端·mybatis
麦麦鸡腿堡8 小时前
Java的动态绑定机制(重要)
java·开发语言·算法