first
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
//
|
||||
// Created by Administrator on 2025/1/17.
|
||||
//
|
||||
|
||||
#include "AudioInput.h"
|
||||
#include <QDebug>
|
||||
#include <QtMath>
|
||||
#include <QtEndian> // 用于处理字节序
|
||||
|
||||
AudioInput *AudioInput::instance = nullptr;
|
||||
AudioInput *AudioInput::getInstance()
|
||||
{
|
||||
// 懒汉式 依旧单线程无需加锁
|
||||
if (instance == nullptr) {
|
||||
instance = new AudioInput();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
AudioInput::AudioInput(QObject *parent) : QObject(parent)
|
||||
{
|
||||
// new一些必要的对象
|
||||
// 初始化定时器
|
||||
m_timer = new QTimer(this);
|
||||
m_silenceTimer = new QTimer(this);
|
||||
m_thresholdTimer = new QTimer(this);
|
||||
m_thresholdTimer->setSingleShot(true);
|
||||
|
||||
// 连接定时器信号
|
||||
connect(m_timer, &QTimer::timeout, this, &AudioInput::onTimeout); // 录音超时槽函数
|
||||
connect(m_silenceTimer, &QTimer::timeout, this, &AudioInput::stopAudio); // 录音超时槽函数
|
||||
connect(m_thresholdTimer, &QTimer::timeout, this, &AudioInput::thresholdTimeout); // 阈值检测超时槽函数
|
||||
|
||||
// 初始化默认设备和格式
|
||||
m_currentDevice = QMediaDevices::defaultAudioInput();
|
||||
setAudioSettings(); // 使用默认参数
|
||||
}
|
||||
|
||||
AudioInput::~AudioInput()
|
||||
{
|
||||
stopAudio(); // 停止录音
|
||||
if (m_audioSource) {
|
||||
delete m_audioSource;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioInput::setAudioSettings(const int rate, const int channels)
|
||||
{
|
||||
m_format.setSampleRate(rate);
|
||||
m_format.setChannelCount(channels);
|
||||
// 重要:Qt6 默认可能是 float,为了生成标准 WAV 且方便计算 RMS,强制设为 Int16
|
||||
m_format.setSampleFormat(QAudioFormat::Int16);
|
||||
|
||||
// 检查设备是否支持该格式,不支持则使用最接近的
|
||||
if (!m_currentDevice.isFormatSupported(m_format)) {
|
||||
qWarning() << "Requested format not supported, using preferred format.";
|
||||
m_format = m_currentDevice.preferredFormat();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioInput::setAudioPath(const QString &path, const QString &fileName)
|
||||
{
|
||||
this->m_outputFilePath = path + fileName;
|
||||
}
|
||||
|
||||
|
||||
void AudioInput::startAudio()
|
||||
{
|
||||
// 每次开始前重新创建 QAudioSource,确保状态重置
|
||||
if (m_audioSource) {
|
||||
delete m_audioSource;
|
||||
m_audioSource = nullptr;
|
||||
}
|
||||
|
||||
m_audioSource = new QAudioSource(m_currentDevice, m_format, this);
|
||||
|
||||
// 调大缓冲区以避免溢出
|
||||
m_audioSource->setBufferSize(128000);
|
||||
|
||||
// start() 返回一个 QIODevice,我们可以从中读取数据
|
||||
m_ioDevice = m_audioSource->start();
|
||||
|
||||
if (m_ioDevice) {
|
||||
connect(m_ioDevice, &QIODevice::readyRead, this, &AudioInput::onReadyRead);
|
||||
qDebug() << "Started recording with device:" << m_currentDevice.description();
|
||||
} else {
|
||||
qCritical() << "Failed to start audio recording.";
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInput::stopAudio()
|
||||
{
|
||||
if (m_audioSource) {
|
||||
m_audioSource->stop();
|
||||
// 注意:不要立即 delete m_audioSource,某些情况下可能导致 crash,停止即可
|
||||
}
|
||||
|
||||
// 停止所有定时器
|
||||
m_timer->stop();
|
||||
m_silenceTimer->stop();
|
||||
m_thresholdTimer->stop();
|
||||
|
||||
// 生成 WAV 数据
|
||||
QByteArray wavData;
|
||||
if (!m_rawPCMData.isEmpty()) {
|
||||
wavData = generateWavHeader(m_rawPCMData.size());
|
||||
wavData.append(m_rawPCMData);
|
||||
|
||||
// 如果需要保存文件
|
||||
if (!m_outputFilePath.isEmpty()) {
|
||||
QFile file(m_outputFilePath);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(wavData);
|
||||
file.close();
|
||||
qDebug() << "Saved WAV to:" << m_outputFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
m_rawPCMData.clear();
|
||||
}
|
||||
|
||||
isAutoRecording = false;
|
||||
isAutoThreshold = false;
|
||||
|
||||
emit recordingFinished();
|
||||
emit recordingFinished_Byte(wavData);
|
||||
qDebug() << "Recording stopped.";
|
||||
}
|
||||
|
||||
// 阈值检测超时槽函数
|
||||
void AudioInput::onReadyRead()
|
||||
{
|
||||
if (!m_ioDevice) return;
|
||||
|
||||
// 读取当前所有可用的音频数据
|
||||
QByteArray data = m_ioDevice->readAll();
|
||||
if (data.isEmpty()) return;
|
||||
|
||||
// 1. 保存原始 PCM 数据
|
||||
m_rawPCMData.append(data);
|
||||
|
||||
// 2. 计算 RMS (仅用于分析,取最后一小段或者整体计算,这里计算当前块的RMS)
|
||||
m_rmsValue = calculateRMS(data);
|
||||
|
||||
// 3. 自动停止逻辑 (VAD)
|
||||
if (isAutoRecording) {
|
||||
// 输出 RMS 用于调试
|
||||
// qDebug() << "RMS:" << m_rmsValue;
|
||||
|
||||
if (m_rmsValue < m_silenceThreshold) {
|
||||
// 静音状态
|
||||
if (!m_silenceTimer->isActive()) {
|
||||
m_silenceTimer->start(m_silenceDuration);
|
||||
}
|
||||
} else {
|
||||
// 有声音,重置定时器
|
||||
m_silenceTimer->stop();
|
||||
m_silenceTimer->start(m_silenceDuration);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 自动阈值计算逻辑
|
||||
if (isAutoThreshold) {
|
||||
m_rmsValues.push_back(m_rmsValue);
|
||||
emit rmsRealValue(m_rmsValue);
|
||||
}
|
||||
}
|
||||
|
||||
qreal AudioInput::calculateRMS(const QByteArray& buffer)
|
||||
{
|
||||
if (buffer.isEmpty()) return 0;
|
||||
|
||||
// 假设是 Int16 格式 (16位深)
|
||||
// 如果是 Stereo,数据排列是 L R L R...
|
||||
// 简单的 RMS 计算可以将所有通道数据视为一个长序列
|
||||
|
||||
const qint16 *data = reinterpret_cast<const qint16*>(buffer.constData());
|
||||
const int sampleCount = buffer.size() / sizeof(qint16); // 样本数量
|
||||
|
||||
if (sampleCount == 0) return 0;
|
||||
|
||||
qreal sumSquared = 0;
|
||||
for (int i = 0; i < sampleCount; ++i) {
|
||||
const qreal sample = static_cast<qreal>(data[i]);
|
||||
sumSquared += sample * sample;
|
||||
}
|
||||
|
||||
return qSqrt(sumSquared / sampleCount);
|
||||
}
|
||||
|
||||
// 启动带时长的录音
|
||||
void AudioInput::startAudioWithDuration(int duration)
|
||||
{
|
||||
startAudio();
|
||||
m_timer->start(duration * 1000);
|
||||
}
|
||||
|
||||
void AudioInput::onTimeout()
|
||||
{
|
||||
stopAudio();
|
||||
qDebug() << "Recording stopped by duration timeout.";
|
||||
}
|
||||
|
||||
// 获取所有音频输入设备
|
||||
QList<QString> AudioInput::getAvailableAudioInputDevices()
|
||||
{
|
||||
QList<QString> list;
|
||||
const auto devices = QMediaDevices::audioInputs();
|
||||
for (const auto &device : devices) {
|
||||
list.append(device.description());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// 设置当前录音设备
|
||||
void AudioInput::setAudioInputDevice(const QString &deviceName)
|
||||
{
|
||||
const auto devices = QMediaDevices::audioInputs();
|
||||
for (const auto &device : devices) {
|
||||
if (device.description() == deviceName) {
|
||||
m_currentDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 启动自动录音 (VAD)
|
||||
void AudioInput::startAutoStopAudio(const qreal silenceThreshold, const int silenceDuration)
|
||||
{
|
||||
isAutoRecording = true;
|
||||
m_silenceThreshold = silenceThreshold;
|
||||
m_silenceDuration = silenceDuration;
|
||||
|
||||
startAudio();
|
||||
|
||||
// 延迟启动静音检测,给一点缓冲时间
|
||||
QTimer::singleShot(200, this, [this](){
|
||||
m_silenceTimer->start(m_silenceDuration);
|
||||
});
|
||||
}
|
||||
|
||||
// 启动阈值计算
|
||||
void AudioInput::startAutoThresholdClu(int Duration)
|
||||
{
|
||||
isAutoThreshold = true;
|
||||
m_rmsValues.clear();
|
||||
startAudio();
|
||||
m_thresholdTimer->start(Duration);
|
||||
}
|
||||
|
||||
void AudioInput::thresholdTimeout()
|
||||
{
|
||||
isAutoThreshold = false;
|
||||
stopAudio(); // 内部会处理 stop
|
||||
|
||||
if (!m_rmsValues.empty()) {
|
||||
const double sum = std::accumulate(m_rmsValues.begin(), m_rmsValues.end(), 0.0);
|
||||
const double avg = sum / m_rmsValues.size();
|
||||
m_silenceThreshold = avg + 500.0; // 这里的 500 是经验值,可以根据需要调整
|
||||
emit thresholdCalculated(m_silenceThreshold);
|
||||
} else {
|
||||
emit thresholdCalculated(0);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray AudioInput::generateWavHeader(const quint32 dataSize) const {
|
||||
// WAV头结构定义
|
||||
struct WavHeader {
|
||||
char riff[4] = {'R','I','F','F'};
|
||||
quint32 chunkSize;
|
||||
char wave[4] = {'W','A','V','E'};
|
||||
char fmt[4] = {'f','m','t',' '};
|
||||
quint32 fmtSize = 16;
|
||||
quint16 audioFormat = 1; // PCM
|
||||
quint16 numChannels;
|
||||
quint32 sampleRate;
|
||||
quint32 byteRate;
|
||||
quint16 blockAlign;
|
||||
quint16 bitsPerSample;
|
||||
char data[4] = {'d','a','t','a'};
|
||||
quint32 dataSize;
|
||||
} header;
|
||||
|
||||
header.numChannels = static_cast<quint16>(m_format.channelCount());
|
||||
header.sampleRate = static_cast<quint32>(m_format.sampleRate());
|
||||
header.bitsPerSample = 16; // 我们强制使用了 Int16
|
||||
|
||||
header.byteRate = header.sampleRate * header.numChannels * (header.bitsPerSample / 8);
|
||||
header.blockAlign = header.numChannels * (header.bitsPerSample / 8);
|
||||
header.dataSize = dataSize;
|
||||
header.chunkSize = 36 + dataSize;
|
||||
|
||||
return QByteArray(reinterpret_cast<const char*>(&header), sizeof(WavHeader));
|
||||
}
|
||||
|
||||
void AudioInput::setSilenceThreshold(const qreal silenceThreshold)
|
||||
{
|
||||
this->m_silenceThreshold = silenceThreshold;
|
||||
}
|
||||
|
||||
qreal AudioInput::getSilenceThreshold() const
|
||||
{
|
||||
return this->m_silenceThreshold;
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
//
|
||||
// Created by Administrator on 2025/1/17.
|
||||
//
|
||||
|
||||
#include "AudioOutput.h"
|
||||
#include <QMediaDevices>
|
||||
#include <QDataStream>
|
||||
|
||||
AudioOutput *AudioOutput::instance = nullptr;
|
||||
|
||||
AudioOutput *AudioOutput::getInstance()
|
||||
{
|
||||
// 懒汉式(单线程播放,无需考虑加锁)
|
||||
if (instance == nullptr) {
|
||||
instance = new AudioOutput();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
AudioOutput::AudioOutput(QObject *parent) : QObject(parent), mediaPlayer(nullptr), audioOutput(nullptr), audioSink(nullptr), audioBuffer(nullptr)
|
||||
{
|
||||
audioBuffer = new QBuffer(this); // 初始化缓冲区
|
||||
// 初始化 QMediaPlayer 用于文件播放
|
||||
mediaPlayer = new QMediaPlayer(this);
|
||||
// 初始化QAudioOutput 用于控制 QMediaPlayer 的音量与设备
|
||||
audioOutput = new QAudioOutput(QMediaDevices::defaultAudioOutput(), this);
|
||||
mediaPlayer->setAudioOutput(audioOutput); // 将 QMediaPlayer 与 QAudioOutput 关联起来
|
||||
|
||||
// 监听 QMediaPlayer 状态变化
|
||||
connect(mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, [this](QMediaPlayer::MediaStatus state) {
|
||||
if (state == QMediaPlayer::EndOfMedia) {
|
||||
emit playbackFinished(); // 触发播放完成信号
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化 默认的QAudioSink 和 QBuffer 用于字节流播放
|
||||
format.setSampleRate(44100); // 采样率
|
||||
format.setChannelCount(2); // 播放通道数
|
||||
format.setSampleFormat(QAudioFormat::Int16); // 采样格式
|
||||
audioSink = new QAudioSink(QMediaDevices::defaultAudioOutput(), format, this);
|
||||
audioBuffer = new QBuffer(this);
|
||||
|
||||
}
|
||||
|
||||
|
||||
AudioOutput::~AudioOutput()
|
||||
{
|
||||
if (mediaPlayer->playbackState() != QMediaPlayer::StoppedState) {
|
||||
mediaPlayer->stop();
|
||||
}
|
||||
if (audioSink->state() != QAudio::StoppedState) {
|
||||
audioSink->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutput::playAudio(const QUrl& url) const {
|
||||
mediaPlayer->setSource(url);
|
||||
mediaPlayer->play();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字节数组中播放音频
|
||||
* @param wavData
|
||||
*/
|
||||
void AudioOutput::playFromByteArray(const QByteArray &wavData) {
|
||||
// 确保数据有效
|
||||
if (wavData.isEmpty()) {
|
||||
qWarning() << "尝试播放空音频数据";
|
||||
return;
|
||||
}
|
||||
// 停止 QMediaPlayer 播放,防止冲突
|
||||
if (mediaPlayer->playbackState() != QMediaPlayer::StoppedState) {
|
||||
mediaPlayer->stop();
|
||||
}
|
||||
// 停止并释放旧的 QAudioSink 这里因为QAudioSink 不支持运行时修改播放音频格式,因此只能这么做
|
||||
if (audioSink) {
|
||||
audioSink->stop();
|
||||
delete audioSink;
|
||||
audioSink = nullptr;
|
||||
}
|
||||
// 解析新的音频格式
|
||||
const QAudioFormat newFormat = getWavFormat(wavData);
|
||||
if (!newFormat.isValid()) {
|
||||
qWarning() << "音频格式解析失败,停止播放。";
|
||||
return;
|
||||
}
|
||||
// 创建一个新的 QAudioSink 实例
|
||||
audioSink = new QAudioSink(QMediaDevices::defaultAudioOutput(), newFormat, this);
|
||||
// 准备 QBuffer:只包含音频原始数据 (跳过 44 字节的 WAV 头部)
|
||||
const QByteArray rawAudioData = wavData.mid(44);
|
||||
|
||||
// 重置缓冲区
|
||||
audioBuffer->close();
|
||||
audioBuffer->setData(rawAudioData);
|
||||
audioBuffer->open(QIODevice::ReadOnly);
|
||||
|
||||
// 监听 QAudioSink 状态变化以触发 playbackFinished 信号
|
||||
connect(audioSink, &QAudioSink::stateChanged, this, [this](const QAudio::State state) {
|
||||
if (state == QAudio::StoppedState) {
|
||||
if (audioSink->error() == QAudio::NoError) {
|
||||
emit playbackFinished();
|
||||
} else {
|
||||
qWarning() << "QAudioSink 播放错误:" << audioSink->error();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 将 QBuffer (QIODevice) 传递给 QAudioSink,并开始播放
|
||||
audioSink->start(audioBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从完整的 WAV 字节流中解析 QAudioFormat
|
||||
* @param wavData 完整的 WAV 文件字节流
|
||||
* @return QAudioFormat 如果解析成功,返回正确的格式;否则返回默认格式
|
||||
*/
|
||||
QAudioFormat AudioOutput::getWavFormat(const QByteArray &wavData) const {
|
||||
QAudioFormat format_;
|
||||
|
||||
// 完整的 WAV 文件至少有 44 字节的头部
|
||||
if (wavData.size() < 44) {
|
||||
qWarning() << "WAV 数据太短,无法解析头部";
|
||||
return this->format; // 返回一个默认格式
|
||||
}
|
||||
|
||||
// 使用 QDataStream 以小端模式读取二进制数据
|
||||
QDataStream stream(wavData);
|
||||
stream.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
// 跳过 RIFF 和 FORMAT 块 (共 20 字节)
|
||||
stream.skipRawData(20);
|
||||
|
||||
// 读取声道数 (2 bytes)
|
||||
quint16 channels = 0;
|
||||
stream >> channels;
|
||||
format_.setChannelCount(channels);
|
||||
|
||||
// 读取采样率 (4 bytes)
|
||||
quint32 sampleRate = 0;
|
||||
stream >> sampleRate;
|
||||
format_.setSampleRate(sampleRate);
|
||||
|
||||
// 跳过 ByteRate 和 BlockAlign (共 6 字节)
|
||||
stream.skipRawData(6);
|
||||
|
||||
// 读取位深 (2 bytes)
|
||||
quint16 bitsPerSample = 0;
|
||||
stream >> bitsPerSample;
|
||||
|
||||
// 根据位深设置 QAudioFormat 的 SampleFormat
|
||||
if (bitsPerSample == 8) {
|
||||
format_.setSampleFormat(QAudioFormat::UInt8);
|
||||
} else if (bitsPerSample == 16) {
|
||||
format_.setSampleFormat(QAudioFormat::Int16);
|
||||
} else if (bitsPerSample == 32) {
|
||||
format_.setSampleFormat(QAudioFormat::Float); // 32位通常是浮点数
|
||||
} else {
|
||||
qWarning() << "不支持的位深:" << bitsPerSample;
|
||||
return this->format; // 返回一个默认格式
|
||||
}
|
||||
|
||||
qDebug() << "WAV Format - Rate:" << sampleRate << ", Channels:" << channels << ", Bits:" << bitsPerSample;
|
||||
return format_;
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停播放
|
||||
*/
|
||||
void AudioOutput::pauseAudio() const {
|
||||
mediaPlayer->pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止播放
|
||||
*/
|
||||
void AudioOutput::stopAudio() const {
|
||||
mediaPlayer->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置播放速度
|
||||
* @param speed
|
||||
*/
|
||||
void AudioOutput::setPlaySpeed(const double speed) const {
|
||||
mediaPlayer->setPlaybackRate(speed);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取播放速度
|
||||
* @return double
|
||||
*/
|
||||
double AudioOutput::getPlaySpeed() const {
|
||||
return mediaPlayer->playbackRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置播放音量
|
||||
* @param volume
|
||||
*/
|
||||
void AudioOutput::setPlayVolume(const int volume) const {
|
||||
// 将 0-100 转换为 0.0 - 1.0
|
||||
const qreal newVolume = static_cast<qreal>(volume) / 100.0;
|
||||
audioOutput->setVolume(static_cast<float>(newVolume));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取播放音量
|
||||
* @return int
|
||||
*/
|
||||
int AudioOutput::getPlayVolume() const {
|
||||
// 将 0.0 - 1.0 转换为 0-100
|
||||
return qRound(audioOutput->volume() * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前播放位置(毫秒)
|
||||
* @return qint64
|
||||
*/
|
||||
qint64 AudioOutput::getPlayPosition() const {
|
||||
// 检查 QMediaPlayer 是否处于播放状态,且当前是否有源在播放
|
||||
if (mediaPlayer->playbackState() == QMediaPlayer::PlayingState) {
|
||||
return mediaPlayer->position();
|
||||
}
|
||||
// 否则返回 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取音频时长(毫秒)
|
||||
* @return qint64
|
||||
*/
|
||||
qint64 AudioOutput::getMediaDuration() const {
|
||||
if(mediaPlayer->playbackState() == QMediaPlayer::PlayingState){
|
||||
return mediaPlayer->duration();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前播放位置,返回播放进度百分比
|
||||
* @return double
|
||||
*/
|
||||
double AudioOutput::getPlayProgress() const {
|
||||
if(mediaPlayer->playbackState() != QMediaPlayer::PlayingState){
|
||||
return 0;
|
||||
}
|
||||
return static_cast<double>(this->getPlayPosition()) / static_cast<double>(this->getMediaDuration());
|
||||
}
|
||||
|
||||
void AudioOutput::setAudioFormat(const int sampleRate, const int channels, const QAudioFormat::SampleFormat sampleType) {
|
||||
this->format.setSampleRate(sampleRate);
|
||||
this->format.setChannelCount(channels);
|
||||
this->format.setSampleFormat(sampleType);
|
||||
}
|
||||
|
||||
void AudioOutput::setAudioFormat(const QAudioFormat &format_) {
|
||||
this->format = format_;
|
||||
}
|
||||
|
||||
QAudioFormat AudioOutput::getAudioFormat() const {
|
||||
return this->format;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取播放状态
|
||||
* QMediaPlayer::StoppedState、QMediaPlayer::PlayingState、QMediaPlayer::PausedState
|
||||
* @return QMediaPlayer::State
|
||||
*/
|
||||
QMediaPlayer::MediaStatus AudioOutput::getState() const {
|
||||
return mediaPlayer->mediaStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误类型
|
||||
* NoError,
|
||||
ResourceError,
|
||||
FormatError,
|
||||
NetworkError,
|
||||
AccessDeniedError,
|
||||
ServiceMissingError,
|
||||
MediaIsPlaylist
|
||||
* @return QMediaPlayer::Error
|
||||
*/
|
||||
QMediaPlayer::Error AudioOutput::getError() const {
|
||||
return mediaPlayer->error();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误描述
|
||||
* @return QString
|
||||
*/
|
||||
QString AudioOutput::getErrorString() const {
|
||||
return mediaPlayer->errorString();
|
||||
}
|
||||
Reference in New Issue
Block a user