網(wǎng)站編輯專題怎么做東莞百度seo推廣公司
實(shí)現(xiàn)功能主要思路:在網(wǎng)頁(yè)端進(jìn)行語(yǔ)音輸入,PC機(jī)可以實(shí)時(shí)接收并播放語(yǔ)音流。
此時(shí),Qt程序做客戶端,Web端做服務(wù)器,使用QWebSocket進(jìn)行通訊,實(shí)時(shí)播放接收的語(yǔ)音流。
功能實(shí)現(xiàn)
想要實(shí)現(xiàn)該功能,需要完成以下兩大部分。
第一部分:QWebSocket通訊實(shí)現(xiàn)。(簡(jiǎn)單)
第二部分:語(yǔ)音流實(shí)時(shí)播放功能。(稍微有點(diǎn)難度)
接下來(lái)對(duì)于該功能實(shí)現(xiàn)進(jìn)行具體的講解。
1:建立通訊
1.1:創(chuàng)建QWebSocket通訊
添加頭文件
#include <QWebSocketServer>
#include <QWebSocket>
聲明WebSocket對(duì)象并響應(yīng)消息
m_pWebClient = new QWebSocket;
connect(m_pWebClient, &QWebSocket::connected, this, &QWebSocketManager::MsgRecievd_Server_Connected);
connect(m_pWebClient, &QWebSocket::disconnected, this, &QWebSocketManager::MsgRecievd_Server_Disconnected);
connect(m_pWebClient, &QWebSocket::textMessageReceived, this, &QWebSocketManager::MsgRecievd_Server_TextMessageReceived);
connect(m_pWebClient, &QWebSocket::binaryMessageReceived, this, &QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived);
分別響應(yīng)了:連接、斷開(kāi)、接收字符串內(nèi)容、接收二進(jìn)制內(nèi)容。
1.2:建立心跳包
一說(shuō)到通訊,首先想到的應(yīng)該是心跳包機(jī)制。在與Web通訊也是如此,為了防止掉線,程序中也需要設(shè)定一個(gè)心跳包機(jī)制。
為了保證心跳包有連接,但不頻繁發(fā)送,可以采用在無(wú)數(shù)據(jù)發(fā)送時(shí),采用3秒~10秒之間發(fā)送一條。
使用方法:QTimer進(jìn)行心跳包發(fā)送。
在程序使用過(guò)程中,不需要精確發(fā)送時(shí)間,只要在指定時(shí)間范圍內(nèi)(3s~10s)發(fā)送就可以了。
定義時(shí)間更新變量
DWORD m_dwReciveTime;//接收到WebSocket消息的時(shí)間
每次接收到web服務(wù)端發(fā)送數(shù)據(jù)時(shí),實(shí)時(shí)更新接收時(shí)間。
void QWebSocketManager::MsgRecievd_Server_TextMessageReceived(const QString &message)
{qDebug() << QStringLiteral("接收內(nèi)容:") << message;m_dwReciveTime = GetTickCount(); //更新接收時(shí)間
}
void QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived(const QByteArray &message)
{qDebug() << QStringLiteral("接收內(nèi)容:") << message;m_dwReciveTime = GetTickCount(); //更新接收時(shí)間
}
在項(xiàng)目中重寫了兩個(gè)接收消息,所以都需要實(shí)時(shí)更新接收時(shí)間。
此時(shí)需要開(kāi)啟定時(shí)器,假設(shè)每間隔3秒訪問(wèn)一次,定時(shí)器核心代碼,如下:
DWORD dwCalc = GetTickCount() - pThis->m_dwReciveTime; //時(shí)間差 = 最新時(shí)間 - 模擬人上傳數(shù)據(jù)時(shí)間
if (dwCalc < g_nWebSocektHeartTime)
{//時(shí)間差 < 最小心跳包
}
else if ((dwCalc > g_nWebSocektHeartTime) && (dwCalc < g_nWebSocketLostConnectTime))
{//發(fā)送心跳包協(xié)議
}
else if(dwCalc > g_nWebSocketLostConnectTime)
{qDebug() << QStringLiteral("連接超時(shí)!");
}
注意:這是我在通訊過(guò)程中進(jìn)行了一點(diǎn)點(diǎn)小小優(yōu)化,大家也可以采用喲~
每次觸發(fā)定時(shí)器時(shí),并沒(méi)有直接發(fā)送心跳包,而是當(dāng)間隔超過(guò)10秒后代表斷開(kāi)連接了。
1.3:接收web端音頻流
在1.1中實(shí)現(xiàn)了QWebSocket的兩個(gè)消息數(shù)據(jù)接收:textMessageReceived、binaryMessageReceived
具體使用哪個(gè)消息,需要對(duì)應(yīng)服務(wù)端是如何發(fā)送的,一般而言,音頻流采用二進(jìn)制流的方式比較安全。
接收語(yǔ)音流數(shù)據(jù),實(shí)例代碼如下:
void QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived(const QByteArray &message)
{//qDebug() << QStringLiteral("MsgRecievd_Server_BinaryMessageReceived,內(nèi)容:") << message;
}
接收到音頻流以后,該如何進(jìn)行播放呢?
接下來(lái)就需要進(jìn)行第二步重要功能:語(yǔ)音流實(shí)時(shí)播放功能
2:語(yǔ)音流實(shí)時(shí)播放功能
在這里我用的是:QAudioOutput類,使用該類方便操作。
2.1:初始化輸出音頻參數(shù)
QAudioFormat audio_out_format;
//設(shè)置錄音的格式
audio_out_format.setSampleRate(8000); //采樣率
audio_out_format.setChannelCount(1); //通道數(shù)
audio_out_format.setSampleSize(16);
audio_out_format.setCodec("audio/pcm"); //編碼格式
audio_out_format.setByteOrder(QAudioFormat::LittleEndian); //樣本是小端字節(jié)順序
audio_out_format.setSampleType(QAudioFormat::SignedInt); //樣本類型QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());m_pAudioOutput = new QAudioOutput(audio_out_format);
m_pStreamOutput = m_pAudioOutput->start();
m_nPeriodSize = m_pAudioOutput->periodSize();
代碼分析:
錄音的格式要與服務(wù)端輸入的音頻流格式才能保證客戶端接收到清晰完整的音頻流。
此時(shí),需要注意的是最后一行代碼:m_nPeriodSize = m_pAudioOutput->periodSize();
這是實(shí)現(xiàn)播放音頻流的核心之一!
2.2:播放接收的音頻流
針對(duì)這部分實(shí)現(xiàn)方式,我經(jīng)歷了以下幾個(gè)步驟,已踩坑,希望對(duì)大家有用!
簡(jiǎn)單有問(wèn)題的實(shí)現(xiàn)方式
void QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived(const QByteArray &message)
{//qDebug() << QStringLiteral("MsgRecievd_Server_BinaryMessageReceived,內(nèi)容:") << message;m_dwReciveTime = GetTickCount(); //更新接收時(shí)間m_pStreamOutput->write(array); //播放音頻流
}
接收到音頻流就直接播放。使用這種方法會(huì)發(fā)現(xiàn),音頻是可以播放,但是嘰里呱啦的,每次智能聽(tīng)到說(shuō)話的第一個(gè)字,其余的全都聽(tīng)不到了。
此時(shí),你會(huì)懷疑是不是服務(wù)端傳入的音頻流不正確呢?因?yàn)榭蛻舳丝梢圆シ怕曇?。如果你沿著這條路走,那你就錯(cuò)了。
原因:之所以只能聽(tīng)到說(shuō)話的第一個(gè)字是因?yàn)?#xff0c;頻繁地接收數(shù)據(jù),上一次接收的音頻流還未播放完畢就立刻播放下一條音頻流,所以會(huì)出現(xiàn)這種問(wèn)題了。
那么,該如何解決這種問(wèn)題呢?
在這里就用到了初始化時(shí)我所說(shuō)的核心代碼了。
m_nPeriodSize 是每次播放一條完整音頻格式的大小,服務(wù)端傳入的數(shù)據(jù)大小我們無(wú)法控制,但是可以在播放時(shí),每次取m_nPeriodSize 大小的數(shù)據(jù)進(jìn)行播放,就能保證數(shù)據(jù)的完整性。
那么,如何知道上一次播放的音頻流已經(jīng)完成了呢?
使用m_pAudioOutput->bytesFree(),循環(huán)進(jìn)行判斷,只有當(dāng)釋放的緩存數(shù)小于m_nPeriodSize 才能夠繼續(xù)播放音頻流
下面為大家展示有效地實(shí)現(xiàn)方法。
復(fù)雜有效的實(shí)現(xiàn)方式
void QWebSocketManager::MsgRecievd_Server_BinaryMessageReceived(const QByteArray &message)
{//qDebug() << QStringLiteral("MsgRecievd_Server_BinaryMessageReceived,內(nèi)容:") << message;m_dwReciveTime = GetTickCount(); //更新接收時(shí)間{std::lock_guard<std::mutex> lck(m_mutexPcm); //C11用法m_ArrayAudio.append(message);}if (m_bRunningAudio == false){m_bRunningAudio = true; //開(kāi)啟數(shù)據(jù)處理線程m_threadAudio = std::thread(&QWebSocketManager::ThreadProcessingPCMData, this, this);}
}
代碼解析:
當(dāng)接收到第一條音頻數(shù)據(jù)時(shí),開(kāi)啟線程,將音頻播放處理放到線程中進(jìn)行判斷,只有把上一次播放的音頻緩存釋放完成后,才能夠從緩存m_ArrayAudio中獲取m_nPeriodSize大小的數(shù)據(jù)
線程實(shí)現(xiàn)代碼,如下:
unsigned int QWebSocketManager::ThreadProcessingPCMData(void* pParam)
{QWebSocketManager* pThis = reinterpret_cast<QWebSocketManager*>(pParam);while (pThis->m_bRunningAudio == true){//只有滿足一個(gè)完整包數(shù)據(jù)時(shí),才需要處理if (pThis->m_ArrayAudio.count() >= m_nPeriodSize){if (m_pAudioOutput->bytesFree() < m_nPeriodSize){Sleep(5);continue; //當(dāng)前音頻釋放大小 < 固定大小時(shí),不處理}std::lock_guard<std::mutex> lck(m_mutexPcm); //C11用法QByteArray array = pThis->m_ArrayAudio.mid(0, m_nPeriodSize);pThis->m_pStreamOutput->write(array);pThis->m_ArrayAudio.remove(0, m_nPeriodSize);qDebug() << QStringLiteral("處理一次完整的音頻,此時(shí)剩余大小 = ") << pThis->m_ArrayAudio.count();}else{Sleep(1000);}}return 0;
}
以上就是核心的實(shí)現(xiàn)流程了,如果需要查看原始代碼的,請(qǐng)看下面鏈接
Qt中使用QWebSocket與Web進(jìn)行通訊,實(shí)時(shí)語(yǔ)音通話
我是糯諾諾米團(tuán),一名C++開(kāi)發(fā)程序媛~