導讀
? ? ? ? 本項目目的是使用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());}}
}
????????