导读
本项目目的是使用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)<0){logger()->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<std::mutex> 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 < 0){logger()->info("swr_convert failed %d",out_samples);}// 填充音频缓冲区int aDataSize = 0;//两种计算重采样后音频数据大小的方法
#if 0aDataSize = out_samples * ptx->audio_codec_ctx->channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
#elseaDataSize = 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<mutex> lck(ptx->_audioDataMutex);ptx->_audioDataList.push_back(aData);}}av_freep(&output);}
#endifav_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<std::mutex> 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());}}
}