Files
Yosuga_rk3566/src/Handle/AudioHandle/Src/AudioOutput.cpp
T
2026-06-14 15:54:35 +08:00

227 lines
6.8 KiB
C++

//
// Created by Administrator on 2025/1/17.
//
#include "AudioOutput.h"
#include <QMediaDevices>
#include <QDebug>
#include <QCoreApplication>
#include <QDataStream>
// Static Helpers (WAV Header Parser)
static bool hasWavHeader(const QByteArray& data) {
if (data.size() < 44) return false;
return data.startsWith("RIFF") && data.mid(8, 4) == "WAVE";
}
// StreamAudioWorker 实现
StreamAudioWorker::~StreamAudioWorker() {
// 确保析构时资源释放
stop();
}
void StreamAudioWorker::start(int sampleRate, int channelCount, int bitDepth) {
if (m_sink) {
m_sink->stop();
m_sink.reset();
}
m_firstChunk = true;
// 配置音频格式
QAudioFormat format;
format.setSampleRate(sampleRate);
format.setChannelCount(channelCount);
if (bitDepth == 8) format.setSampleFormat(QAudioFormat::UInt8);
else if (bitDepth == 16) format.setSampleFormat(QAudioFormat::Int16);
else if (bitDepth == 32) format.setSampleFormat(QAudioFormat::Float); // 32位通常为Float
else format.setSampleFormat(QAudioFormat::Int16); // 默认回退
// 检查设备是否支持
auto device = QMediaDevices::defaultAudioOutput();
if (!device.isFormatSupported(format)) {
qWarning() << "[Worker] Device does not support format, using preferred format.";
format = device.preferredFormat();
}
// 创建 Sink (必须在 Worker 线程中创建)
m_sink.reset(new QAudioSink(device, format));
// 监听状态
connect(m_sink.data(), &QAudioSink::stateChanged, this, [this](QAudio::State state){
if (state == QAudio::IdleState) {
emit playbackFinished();
}
else if (state == QAudio::StoppedState) {
if (m_sink->error() != QAudio::NoError) {
emit errorOccurred("Audio Sink Error: " + QString::number(m_sink->error()));
}
}
});
// 启动,获取 IO 设备
m_ioDevice = m_sink->start();
if (!m_ioDevice) {
emit errorOccurred("Failed to start audio device");
} else {
qDebug() << "[Worker] Stream started:" << sampleRate << "Hz";
}
}
void StreamAudioWorker::processChunk(const QByteArray& chunk) {
if (chunk.isEmpty() || !m_ioDevice || !m_sink) return;
QByteArray dataToWrite = chunk;
// 智能处理 WAV 头
if (m_firstChunk) {
if (hasWavHeader(chunk)) {
qDebug() << "[Worker] Detected WAV header, stripping 44 bytes.";
dataToWrite = chunk.mid(44);
}
m_firstChunk = false;
}
// 写入音频设备 (QAudioSink 内部有缓冲区,这里直接 write 即可)
// 如果数据量过大,write 可能会阻塞,但在独立线程中这是可以接受的
qint64 written = m_ioDevice->write(dataToWrite);
if (written != dataToWrite.size()) {
qWarning() << "[Worker] Incomplete write:" << written << "/" << dataToWrite.size();
}
}
void StreamAudioWorker::stop() {
if (m_sink) {
m_sink->stop();
m_sink.reset(); // 删除对象
}
m_ioDevice = nullptr;
qDebug() << "[Worker] Stream stopped";
}
// AudioOutput 主类实现
QScopedPointer<AudioOutput> AudioOutput::m_instance;
QMutex AudioOutput::m_mutex;
AudioOutput* AudioOutput::getInstance() {
if (m_instance.isNull()) {
QMutexLocker locker(&m_mutex);
if (m_instance.isNull()) {
m_instance.reset(new AudioOutput());
}
}
return m_instance.data();
}
void AudioOutput::destroy() {
QMutexLocker locker(&m_mutex);
if (!m_instance.isNull()) {
m_instance.reset();
}
}
AudioOutput::AudioOutput(QObject *parent) : QObject(parent) {
// 初始化文件播放器
m_player = new QMediaPlayer(this);
m_audioOutput = new QAudioOutput(this);
m_player->setAudioOutput(m_audioOutput);
connect(m_player, &QMediaPlayer::mediaStatusChanged, this, [this](QMediaPlayer::MediaStatus status){
if (status == QMediaPlayer::EndOfMedia) emit playbackFinished();
});
// 预先初始化流式 Worker 线程
// 保持一个常驻线程Worker,通过信号控制
m_streamWorker = new StreamAudioWorker(); // 不能指定 parent,因为要 moveToThread
m_workerThread = new QThread(this);
m_streamWorker->moveToThread(m_workerThread);
// 连接信号槽
// 主线程 -> Worker
connect(this, &AudioOutput::sigOperateStreamStart, m_streamWorker, &StreamAudioWorker::start);
connect(this, &AudioOutput::sigOperateStreamChunk, m_streamWorker, &StreamAudioWorker::processChunk);
connect(this, &AudioOutput::sigOperateStreamStop, m_streamWorker, &StreamAudioWorker::stop);
// Worker -> 主线程
connect(m_streamWorker, &StreamAudioWorker::errorOccurred, this, &AudioOutput::errorOccurred);
// 线程启动
m_workerThread->start();
}
AudioOutput::~AudioOutput() {
stopPlayback();
// 清理线程
if (m_workerThread) {
m_workerThread->quit();
m_workerThread->wait(3000); // 等待退出
delete m_streamWorker;
}
}
// 对外接口
void AudioOutput::stopPlayback() {
// 停止文件播放
if (m_player->playbackState() != QMediaPlayer::StoppedState) {
m_player->stop();
}
// 停止流播放
if (m_isStreaming) {
stopStream();
}
}
void AudioOutput::setVolume(int volume) {
if (m_audioOutput) m_audioOutput->setVolume(volume / 100.0);
// 注意:流式播放的音量控制需要在 Worker 内单独实现
}
bool AudioOutput::isPlaying() const {
return (m_player->playbackState() == QMediaPlayer::PlayingState) || m_isStreaming;
}
// 文件播放
void AudioOutput::playUrl(const QUrl& url) {
stopPlayback(); // 互斥,播放新文件前停止旧的
m_player->setSource(url);
m_player->play();
}
void AudioOutput::playData(const QByteArray& data) {
// 这个方法对于 QMediaPlayer 比较麻烦,需要自定义 QIODevice
// 建议直接走 stream 接口,或者使用 QBuffer + StreamWorker 的一次性模式
// 为了简单,这里将 buffer 视为 stream 播放
stopPlayback();
startStream(44100, 2, 16); // 假设默认 wav 格式,Worker 会自动解析头
pushStreamData(data);
// 不需要显式 stopStream,让它播完
}
// 流式播放
void AudioOutput::startStream(int sampleRate, int channelCount, int bitDepth) {
stopPlayback(); // 确保干净的状态
m_isStreaming = true;
// 通过信号跨线程调用 Worker 的 start
emit sigOperateStreamStart(sampleRate, channelCount, bitDepth);
}
void AudioOutput::pushStreamData(const QByteArray& chunk) {
if (!m_isStreaming) return;
// 直接发射信号,Qt 会把 chunk copy 到子线程事件队列
emit sigOperateStreamChunk(chunk);
}
void AudioOutput::stopStream() {
if (!m_isStreaming) return;
m_isStreaming = false;
emit sigOperateStreamStop();
}