Qt 采集麦克风PCM数据并实时翻译

对着背影说爱祢 提交于 2019-11-26 16:39:57

一,需求 

采集麦克风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个参数:

  1. Sample Rate : 采样频率。8kHz(电话)、44.1kHz(CD)、48kHz(DVD)。
  2. Sample Size : 量化位数。通常该值为16-bit。
  3. Number of Channels : 通道个数。常见的音频有立体声(stereo)和单声道(mono)两种类型,立体声包含左声道和右声道。另外还有环绕立体声等其它不太常用的类型。
  4. Sign : 表示样本数据是否是有符号位,比如用一字节表示的样本数据,有符号的话表示范围为-128 ~ 127,无符号是0 ~ 255。
  5. Byte Ordering : 字节序。字节序是little-endian还是big-endian。通常均为little-endian。字节序说明见第4节。
  6. 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简介

https://www.cnblogs.com/cangqinglang/p/8331120.html

 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);
        }
    }
}

四,效果

存在翻译不准确的问题,跟环境和和语速有一定关系。 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!