一,需求
采集麦克风PCM数据,并利用有道实时语音翻译API进行显示
(1),麦克风数据采集
(2),调用有道API进行实时翻译
二,数据采集
根据有道 接口要求,数据采样率8k、16bit、wav(PCM编码)(https://irma.youdao.com/html/%E5%AE%9E%E6%97%B6%E8%AF%AD%E9%9F%B3%E7%BF%BB%E8%AF%91/API%E6%96%87%E6%A1%A3/%E5%AE%9E%E6%97%B6%E8%AF%AD%E9%9F%B3%E7%BF%BB%E8%AF%91%E6%9C%8D%E5%8A%A1/%E5%AE%9E%E6%97%B6%E8%AF%AD%E9%9F%B3%E7%BF%BB%E8%AF%91%E6%9C%8D%E5%8A%A1-API%E6%96%87%E6%A1%A3.html)
传输方式 | WSS |
字符编码 | 统一使用UTF-8编码 |
响应格式 | 统一采用JSON格式 |
语音格式 | wav(不压缩、pcm编码) |
语音采样率 | 8k或者16k。推荐16k |
语音编码 | 16bit位深的单声道 |
什么是PCM?
PCM(Pulse Code Modulation,脉冲编码调制)音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准数字音频数据。
描述PCM数据的6个参数:
- Sample Rate : 采样频率。8kHz(电话)、44.1kHz(CD)、48kHz(DVD)。
- Sample Size : 量化位数。通常该值为16-bit。
- Number of Channels : 通道个数。常见的音频有立体声(stereo)和单声道(mono)两种类型,立体声包含左声道和右声道。另外还有环绕立体声等其它不太常用的类型。
- Sign : 表示样本数据是否是有符号位,比如用一字节表示的样本数据,有符号的话表示范围为-128 ~ 127,无符号是0 ~ 255。
- Byte Ordering : 字节序。字节序是little-endian还是big-endian。通常均为little-endian。字节序说明见第4节。
- Integer Or Floating Point : 整形或浮点型。大多数格式的PCM样本数据使用整形表示,而在一些对精度要求高的应用方面,使用浮点类型表示PCM样本数据。
什么是WAV
WAV:wav是一种无损的音频文件格式,WAV符合 PIFF(Resource Interchange File Format)规范。所有的WAV都有一个文件头,这个文件头音频流的编码参数。WAV对音频流的编码没有硬性规定,除了PCM之外,还有几乎所有支持ACM规范的编码都可以为WAV的音频流进行编码。
WAV和PCM的关系
WAV可以使用多种音频编码来压缩其音频流,不过我们常见的都是音频流被PCM编码处理的WAV,但这不表示WAV只能使用PCM编码,MP3编码同样也可以运用在WAV中,和AVI一样,只要安装好了相应的Decode,就可以欣赏这些WAV了。在Windows平台下,基于PCM编码的WAV是被支持得最好的音频格式,所有音频软件都能完美支持,由于本身可以达到较高的音质的要求,因此,WAV也是音乐编辑创作的首选格式,适合保存音乐素材。因此,基于PCM编码的WAV被作为了一种中介的格式,常常使用在其他编码的相互转换之中,例如MP3转换成WMA。
简单来说:pcm是无损wav文件中音频数据的一种编码方式,但wav还可以用其它方式编码。
利用Qt进行音频数据采集
设置参数:
m_format.setSampleRate(16000);//设置采样率 每秒钟取得声音样本的次数
m_format.setChannelCount(1);//设定声道数目,mono(平声道)的声道数目是1;stero(立体声)的声道数目是2
m_format.setSampleSize(16);//设置采样大小,一般为8位或16位
m_format.setCodec("audio/pcm");//编码器
m_format.setByteOrder(QAudioFormat::LittleEndian);//设定高低位,LittleEndian(低位优先)/LargeEndian(高位优先)
m_format.setSampleType(QAudioFormat::SignedInt);//设置样本数据类型
每隔200ms 读取一次数据
m_timeID = startTimer(200,Qt::PreciseTimer);
m_audioInput = new QAudioInput(m_format,this);
QObject::connect(m_audioInput,&QAudioInput::stateChanged,this,&OBSAudio::onStateChanged);
m_streamIn = m_audioInput->start();
读取的数据存入内容中
int len1 = m_audioInput->bytesReady();
if (len1 <6400)
{
qDebug("len1 too small");
return;
}
QByteArray byteArray(len1,0);
qint64 len2 = m_streamIn->read(byteArray.data(), len1);
三,将PCM数据利用有道API进行上传
有道接口采用wss 协议进行传输
websocket简介
Qt可以使用websockets 类,来试下webSocket协议
建立连接,因为此连接最好只有一个,所以可以用单例模式进行封装
m_webSocket.open(QUrl(url));
连接相关槽函数
QObject::connect(&m_webSocket, &QWebSocket::connected, this, &OBSWSSSingleton::onConnected);
QObject::connect(&m_webSocket, QOverload<const QList<QSslError>&>::of(&QWebSocket::sslErrors),
this, &OBSWSSSingleton::onSslErrors);
QObject::connect(&m_webSocket,&QWebSocket::disconnected,this, &OBSWSSSingleton::onDisconnected);
发送数据,支持文本和二进制
void OBSWSSSingleton::sendBinaryMessage(const QByteArray &data)
{
if(!m_isConnected)
return;
qint64 len = m_webSocket.sendBinaryMessage(data);
//qDebug()<<"sendBinaryMessage:"<<len;
}
将上一步每200ms采集的数据 利用wss协议进行发送,此处有坑,开始发送的第一帧数据,需要封装wav头信息,以后则直接发送PCM裸流即可
void OBSAudio::timerEvent(QTimerEvent *)
{
if(!m_streamIn||!m_streamIn->isOpen())
return;
int len1 = m_audioInput->bytesReady();
if (len1 <6400)
{
qDebug("len1 too small");
return;
}
//第一帧 需要封装wav 头信息
if(OBSWSSSingleton::GetInstance()->getm_isConnected()){
QByteArray byteArray(len1,0);
qint64 len2 = m_streamIn->read(byteArray.data(), len1);
//qDebug()<<"timerEvent:"<<len1<<len2;
if(!m_fistData){
static WAVHEADER wavHeader;
qstrcpy(wavHeader.RiffName,"RIFF");
qstrcpy(wavHeader.WavName,"WAVE");
qstrcpy(wavHeader.FmtName,"fmt ");
qstrcpy(wavHeader.DATANAME,"data");
wavHeader.nFmtLength = 16;
int nAudioFormat = 1;
wavHeader.nAudioFormat = nAudioFormat;
wavHeader.nBitsPerSample = 16;
wavHeader.nChannleNumber = 1;
wavHeader.nSampleRate = 16000;
wavHeader.nBytesPerSample = wavHeader.nChannleNumber * wavHeader.nBitsPerSample / 8;
wavHeader.nBytesPerSecond = wavHeader.nSampleRate * wavHeader.nChannleNumber * wavHeader.nBitsPerSample / 8;
wavHeader.nRiffLength = len1 - 8 + sizeof(WAVHEADER);
wavHeader.nDataLength = len1;
QByteArray byte;
QBuffer buffer;
buffer.setBuffer(&byte);
buffer.open(QIODevice::WriteOnly);
buffer.seek(0);
buffer.write(reinterpret_cast<char*>(&wavHeader),sizeof(WAVHEADER));
buffer.seek(sizeof(WAVHEADER));
buffer.write(byteArray.data(),len1);
buffer.close();
m_fistData=true;
OBSWSSSingleton::GetInstance()->sendBinaryMessage(byte);
}else {
OBSWSSSingleton::GetInstance()->sendBinaryMessage(byteArray);
}
}
}
四,效果
存在翻译不准确的问题,跟环境和和语速有一定关系。
来源:https://blog.csdn.net/weixin_38416696/article/details/98953072