> 技术文档 > 使用Qt下QAudioOutput播放声音

使用Qt下QAudioOutput播放声音


导读

        本项目目的是使用QAudioOutput播放声音 ,音频数据来源为ffmpeg解码后的音频数据。

Qt音频播放类说明 

QAudioFormat

QAudioFormat是Qt多媒体框架中用于定义音频格式的核心类,用于设置音频数据的参数,确保与硬件设备兼容。其主要功能和参数如下:
一,采样率(Sample Rate)
定义每秒采集或播放的样本数,单位为Hz。常用值:
44,100 Hz(CD音质)
48,000 Hz(高清音频)

二,通道数(Channel Count)
定义音频声道数量:
1:单声道(Mono)
2:立体声(Stereo)

三,样本大小(Sample Size)
定义每个样本的位数(量化精度),常见值为16位

四,编码类型(Codec)
指定音频编码格式,通常为PCM原始数据

五,字节序(Byte Order)
定义数据存储顺序:
QAudioFormat::LittleEndian(小端序,Intel架构常用)
QAudioFormat::BigEndian(大端序)

六,样本类型(Sample Type)
定义样本数据类型:
QAudioFormat::SignedInt(有符号整数)
QAudioFormat::UnSignedInt(无符号整数)

QAudioOutput

QAudioOutput 是 Qt 多媒体框架中用于音频输出的核心类,支持将 PCM 原始音频数据发送到音频设备(如扬声器)。以下是其核心特性和使用要点:
一,功能定位
音频输出控制
管理音频播放状态(播放/暂停/停止)和音频数据流传输,适用于低延迟实时音频场景。
对比高级类 QMediaPlayer,它更底层且灵活性强,但需手动处理原始 PCM 数据。‌

二,核心方法
方法 功能说明
start(QIODevice*)    绑定输入设备(如 QFile 或自定义 QIODevice)并开始播放音频数据。
stop()    停止播放并释放资源。
suspend()    暂停播放(保留资源)。
resume()    从暂停状态恢复播放。
setVolume(float)    设置音量(0.0 静音 ~ 1.0 最大)。
setBufferSize(int)    设置缓冲区大小(需在 start() 前调用生效)。

三,关键信号
stateChanged(QAudio::State)
播放状态变更时触发,状态包括:
ActiveState(正在播放)
SuspendedState(暂停)
StoppedState(停止)
IdleState(数据耗尽或未启动)

QIODevice

QIODevice是Qt框架中所有输入/输出设备的核心抽象基类,为文件、内存缓冲区和网络通信等设备提供了统一的I/O接口。其主要特性如下:

一,设备抽象与继承结构
作为所有Qt I/O设备的基类,定义了通用读写接口,无法直接实例化。
具体子类包括:
QFile(文件操作)
QBuffer(内存缓冲区)
QTcpSocket/QTcpServer(网络通信)
QProcess(进程通信)
QSerialPort(串口通信)

二,设备类型区分
随机访问设备(如文件):支持seek()定位和pos()获取当前位置 
顺序设备(如网络套接字):不支持随机定位,数据必须一次性读取
通过isSequential()判断设备类型

音频播放代码关键实现

初始化qt音频相关对象 

int FFPlayer::initQtAudio(const AVCodecParameters *codecPar){ QAudioFormat format; format.setSampleRate(codecPar->sample_rate); format.setChannelCount(codecPar->channels); format.setSampleSize(16); // 统一转换为16位输出 format.setCodec(\"audio/pcm\"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); m_audioOutput = new QAudioOutput(format); m_audioDevice = m_audioOutput->start(); // 初始化重采样器 swr_ctx = swr_alloc_set_opts(NULL,  av_get_default_channel_layout(codecPar->channels),  AV_SAMPLE_FMT_S16,  codecPar->sample_rate,  av_get_default_channel_layout(audio_codec_ctx->channels),  audio_codec_ctx->sample_fmt,  audio_codec_ctx->sample_rate,  0, NULL); logger()->info(\"swr_alloc_set_opts()sample_fmt:%d \",audio_codec_ctx->sample_fmt); if(!swr_ctx || swr_init(swr_ctx)info(\"重采样初始化失败\"); return -1; } //初始化音频播放线程 _audioPlayThread = std::thread(playAudioFunc,this); return 0;}

音频解码线程实现

void decodeAudioFunc(void*ctx){ FFPlayer* ptx = (FFPlayer*)ctx; if(ptx == nullptr) return; while(!ptx->quit){ AVPacket pkt; if(ptx->_audioPktList.size()>0){ std::lock_guard lock(ptx->audioQueueMutex); pkt = ptx->_audioPktList.front(); ptx->_audioPktList.pop_front(); }else{  std::this_thread::sleep_for(std::chrono::milliseconds(20));  continue; }#if 1 //解码 int ret = 0; ret = avcodec_send_packet(ptx->audio_codec_ctx, &pkt); if (ret != 0) { logger()->info(\"avcodec_send_packet error: %d\", ret); return; } while(avcodec_receive_frame(ptx->audio_codec_ctx, ptx->audioFrame)>=0){ // 重采样 uint8_t* output; int out_samples = swr_get_out_samples(ptx->swr_ctx, ptx->audioFrame->nb_samples); av_samples_alloc(&output, NULL, ptx->audio_codec_ctx->channels, out_samples, AV_SAMPLE_FMT_S16, 0); out_samples = swr_convert(ptx->swr_ctx, &output, out_samples, (const uint8_t**)ptx->audioFrame->data, ptx->audioFrame->nb_samples); if(out_samples info(\"swr_convert failed %d\",out_samples); } // 填充音频缓冲区 int aDataSize = 0; //两种计算重采样后音频数据大小的方法 #if 0 aDataSize = out_samples * ptx->audio_codec_ctx->channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);#else aDataSize = av_samples_get_buffer_size( NULL, // 行大小(可忽略) ptx->audio_codec_ctx->channels, // 声道数 out_samples,  // 实际样本数 AV_SAMPLE_FMT_S16, // 目标格式 0 // 字节对齐 );#endif //将重采样后的音频数据加入队列 { QByteArray aData((char*)output,aDataSize); {  std::lock_guard lck(ptx->_audioDataMutex);  ptx->_audioDataList.push_back(aData); } } av_freep(&output); }#endif av_packet_unref(&pkt); } logger()->info(\"quit decodeAudioFunc\");}

音频播放线程实现

void playAudioFunc(void*ctx){ FFPlayer* player = (FFPlayer*)ctx; if(player == nullptr) return; QByteArray aData; while(!player->quit){ if(!player->_audioDataList.empty()){ std::lock_guard lock(player->_audioDataMutex); aData = player->_audioDataList.front(); player->_audioDataList.pop_front(); }else{ std::this_thread::sleep_for(std::chrono::milliseconds(2)); continue; } if(player->m_audioDevice){ //将pcm音频数据写入声卡 player->m_audioDevice->write((const char*)aData.data(),  aData.length()); } }}