Linux系统 QT+Faac实时音频采集编码(QT音频采集篇)

一:前言

刚毕业不久,接到的第一个项目就是音频采集。

要求在树莓派Linux系统上用QT进行音频采集,然后实时编码发送给window上的一个程序。

完成项目期间搜集了许多相关资料,苦于网上信息太过零散,大多都是存成文件传输,几乎没有实时性相关的完整的流程代码。FAAC编码器相关的资料也寥寥无几,在使用期间踩了不少坑。

因此在这里分享一下自己项目中的一些要点和部分代码,以及一些注意事项。可能代码比较稚嫩,有什么问题希望大佬指点。


二:音频采集的流程图如下:



三:具体代码

因为本次只讲采集,所以自己写了一个QT采集的代码,为了更好理解,文件不包含FAAC编码。编码将在下次博客中加入。

下面程序能实现实时采集PCM的数据,并存到文件中。

定义了一个DataPool内存空间,用于存放音频数据。如果存满了,从首地址开始将新数据覆盖原来数据。在槽函数中,一边从设备读取数据存到DataPool,一边从DataPool拿来写入文件。

其实想写文件完全没必要这么复杂,也可以这么操作:

QFile inputFile;  //创建QFile文件句柄

inputFile.setFileName("/home/pi/DeskTop/New.Record/PCM 16000 16.PCM"); //设置文件存储位置

inputFile.open(QIODevice::WriteOnly | QIODevice::Truncate); //以只写的形式打开。

audioInput->start(&inputFile);

然后运行,就能开始启动录音并直接写到文件里,连槽函数都用不到,和下面程序功能完全一样!

下面这么做的意义,是因为后续音频编码要从DataPool不断取数据,干脆在这里提前贴上,让大家更好地理解槽函数内的操作。


.h 头文件


/*本程序实现简单的QT音频录音并实时写入文件*/
/*通过学习本例可以学习QT音频实时采集的流程*/
/*  [email protected]    姓值钱的金三岁 */

#ifndef MYRECORD_H
#define MYRECORD_H


#include "myserver.h"
#include <QMainWindow>
#include <QWidget>
#include <QAudioRecorder>
#include <QAudioEncoderSettings>
#include <QtMultimedia/QAudioInput>
#include <QFile>
#include <QTimer>
#include <QIODevice>
#include <QBuffer>
#include <faac.h>
#include <pthread.h>
#include <iostream>

class myRecord : public QMainWindow
{
    Q_OBJECT
public:
    explicit myRecord(QWidget *parent = 0);
    void StartRecord();                    //开始录音

    int temp;
    int end;                               //标志位 尾部 用于标志DataPool数据尾部
    int offset;                            //标志位 头部
    int PoolSize = 4096*10;                //开辟的内存大小
    char *DataPool = new char[PoolSize];   //创建内存空间 用于暂时存放音频原始数据
    int DataReadAll;                       //读到的数据总量
    int DataWrite;                         //已写入文件的数据量
    int nBytesRead;                        //从设备中一次读到的数据长度

signals:

public slots:
    void captureDataFromDevice();          //槽函数  数据处理函数(包括后期 采集 编码 传输 等都在此函数中实现)
    void on_Record_stop();                 //停止录音   连接了一个ui按钮
    void Start_Record();                   //开始录音   连接了一个ui按钮


private:
    QAudioInput         *audioInput;       //音频输入
    QIODevice           *device;           //音频IO设备
    QAudioFormat        format;            //音频采集设置

};

#endif // MYRECORD_H


.cpp 源文件


#include "myrecord.h"

myRecord::myRecord(QWidget *parent) :
QMainWindow(parent)
{
	/*音频采集设置*/
	format.setSampleRate(16000);
	format.setChannelCount(1);
	format.setSampleSize(16);
	format.setCodec("audio/pcm");
	format.setByteOrder(QAudioFormat::LittleEndian);
	format.setSampleType(QAudioFormat::SignedInt);

	QAudioDeviceInfo info(QAudioDeviceInfo::defaultInputDevice());
	if (!info.isFormatSupported(format))
	{
		qWarning() << "Error";
		format = info.nearestFormat(format);
	}

	DataReadAll = 0;
	nBytesRead = 0;
	temp = 0;
	end = 0;
	offset = 0;
	DataWrite = 0;

}


void myRecord::Start_Record()
{
	StartRecord();
}


void myRecord::StartRecord()
{
	fout = fopen("/home/pi/Desktop/New/Record/_pcm 16000 16.pcm", "wb"); //打开文件位置
	audioInput = new QAudioInput(format, this);                          //创建音频
	device = audioInput->start();                                        //开始录音
	qDebug() << "Start record !";
	connect(device, SIGNAL(readyRead()), this, SLOT(captureDataFromDevice()));  //连接信号和槽
	QTimer::singleShot(10000, this, SLOT(on_Record_stop()));             //设置定时器,十秒后结束录音
}


void myRecord::on_Record_stop()   //停止录音  关闭文件  删除内存空间  关闭音频
{
	audioInput->stop();
	fclose(fout);
	delete DataPool;
	delete audioInput;
	std::cout << "Stop Record" << std::endl;
}


void myRecord::captureDataFromDevice()
{
	temp = end - offset;
	int Readable = audioInput->bytesReady();              //bytesReady()用于查看可以麦克风中可读的数据量
	std::cout << "Readable :" << Readable << std::endl;
	if (end <= PoolSize)
	{
		if (end + 4096<PoolSize)
		{
			nBytesRead = device->read(DataPool + end, 4096);
			offset += temp;
			end += nBytesRead;
			fwrite(DataPool + offset, 1, (end - offset), fout);
			DataWrite += (end - offset);
		}
/*DataPool内存大小有限,如果出现末尾空间不够读入的情况,先创建一个内存fin暂存数据,
把fin中一部分数据写入DataPool尾部剩余空间,剩下数据覆盖DataPool头部的原数据*/
		else
		{
			char* fin = new char[4096];
			nBytesRead = device->read(fin, 4096);
			int remain = PoolSize - end;
			if (remain < nBytesRead)
			{
				int i = end;
				memcpy(DataPool + end, fin, remain);
				memcpy(DataPool, fin + remain, nBytesRead - remain);
				offset = 0;
				end = nBytesRead - remain;
				fwrite(DataPool + i, 1, remain, fout);
				fwrite(DataPool + offset, 1, end, fout);
				DataWrite += (remain + end);
			}
			else
			{
				memcpy(DataPool + end, fin, nBytesRead);
				offset += temp;
				end += nBytesRead;
				fwrite(DataPool + offset, 1, end - offset, fout);
				DataWrite += (end - offset);
			}
			delete fin;
		}
		DataReadAll += nBytesRead;

		std::cout << "DataReadAll :" << DataReadAll << std::endl;
		std::cout << "DataWrite :" << DataWrite << std::endl;
		std::cout << "nBytesRead :" << nBytesRead << std::endl;
		std::cout << "Temp :" << temp << std::endl;
	}
}

四:注意事项

(1)麦克风有它自带的缓存空间。数据没读的话会存在那里。但是空间非常小,如果满了,新数据会覆盖原数据。

(2)  .pro文件中  QT+=multimedia  记得写好,否则一些头识别不了。

(3) 想把数据存到文件,文件的打开方式必须是二进制方式打开(b),否则录下来的音频都是噪音。

(4)  readyRead()信号,解释为“每次麦克风有新数据传入,便触发一次”,因此槽函数是不断刷新的(而不是只执行一次)。在新的信号来之前,槽函数内的代码必须运行完。我在树莓派上,槽函数内采集+写文件+编码+传输,完全来得及。所以大部分情况下不用担心来不及处理。

(5)audioInput->bytesReady()可以看麦克风缓存中可读多少数据。但是有一个很奇怪的现象,我在槽函数外开启录音,另外一个线程调用bytesReady(),返回值为0。但是如果这个线程读一点数据,返回值就有了。

(6)device->read()中第二个参数,是设置读数据的长度。但是不知道是我QT版本问题还是其他问题,这个参数并不有效。比如我设置为2048,bytesReady()中表明麦克风中有4096个数据,最后读来的数据只有1367而不是2048。

这个问题困扰我很久,本来只要读够一帧4096长度数据直接送到编码器就行了,现在这个办法无效。这也是为什么我开DataPool存数据的原因。

猜你喜欢

转载自blog.csdn.net/azazssaz2/article/details/80827740
今日推荐