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