上一次发了QT音频采集篇,本次是它的后续内容。
未读过前篇的可以到此链接:Linux系统 QT+Faac实时音频采集编码(QT音频采集篇)
Linux下编译FAAC库:linux下编译faac库
一:FAAC使用流程图:
二:项目代码
本次项目基于上次的QT音频采集,把采集到的PCM数据存到本地文件,同时进行实时编码,再保存一个编码后的AAC文件,把两者进行对比,可以看到编码的效果。
.pro 文件
包含libfaac库文件位置,看安装在哪
LIB+= /usr/local/lib/libfaac.so\
.h 头文件
/*本程序实现简单的QT音频录音并实时写入PCM文件*/
/*同时初始化FAAC将原始音频编码实时写入AAC文件*/
/* [email protected] 姓值钱的金三岁*/
#ifndef MYRECORD_H
#define MYRECORD_H
#include "myserver.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef unsigned long ULONG;
typedef unsigned int UINT;
typedef unsigned char BYTE;
typedef char _TCHAR;
class myRecord : public QMainWindow
{
Q_OBJECT
public:
explicit myRecord(QWidget *parent = 0);
void FAAC(); //FAAC初始化封装函数
void StartRecord(); //开始录音
void StopRecord(); //结束录音
void FaacEncode(); //编码
FILE* fout = NULL; //输出PCM文件
FILE* AACFILE = NULL; //输出AAC文件
MyServer* myserver;
int temp;
int end; //偏移位 尾部 从麦克风读数据到DataPool时用到
int offset; //偏移位 首部 从麦克风读数据到DataPool时用到
int PoolSize = 4096*10; //内存空间大小,可扩容
char *DataPool = new char[PoolSize]; //内存空间,用于存储读到的PCM数据
int DataReadAll; //从麦克风中读到的PCM数据总量
int DataEncode; //已送入编码器PCM数据总量
int mark; //编码器从DataPool取数据用到的标志位
signals:
public slots:
void captureDataFromDevice(); //数据处理槽函数
void on_Record_stop(); //停止录音 连接一个UI按钮
void Start_Record(); //开始录音 连接一个UI按钮
private:
QAudioInput *audioInput;
QIODevice *device;
QAudioFormat format;
/*AAC编码参数*/
ULONG nSampleRate; //原始数据的采样率
UINT nChannels; //原始数据的通道数
UINT nPCMBitSize; //原始数据的采样位数
ULONG nInputSamples; //一次送入编码的样本大小
ULONG nMaxOutputBytes; //一次最大AAC数据输出大小
faacEncHandle hEncoder; //编码器句柄
faacEncConfigurationPtr pConfiguration; //编码器设置句柄
char* pbPCMBuffer; //用于存储输入的PCM数据
BYTE* pbAACBuffer; //用于存储输出的AAC数据
int nBytesRead; //从麦克风一次读到的字节数
int nPCMBufferSize; //一次送入编码器的数据的大小
int nRect; //编码后的返回值
};
#endif // MYRECORD_H
.cpp 源文件
#include "myrecord.h"
myRecord::myRecord(QWidget *parent) :
DataReadAll(0),
DataEncode(0),
nBytesRead(0),
end(0),
offset(0),
mark(0),
nInputSamples(0),
nMaxOutputBytes(0),
QMainWindow(parent)
{
//myserver = MyServer::getInstance();
/*音频采集设置*/
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);
}
/*编码器参数设置 必须和上面录音的设置一致*/
nSampleRate = 16000; //采样率
nChannels = 1; //声道
nPCMBitSize = 16; //采样位数
}
void myRecord::FAAC()
{
/* 打开FAAC编码器 */
/*传入采样率(nSampleRate)和通道数(nChannels),编码器会自动设置后两个参数大小。不要自己去改*/
hEncoder = faacEncOpen(nSampleRate,nChannels,&nInputSamples,&nMaxOutputBytes);
if(hEncoder == NULL)
{
printf("FAAC open failed\n");
}
/*设置每帧输入数据大小*/
nPCMBufferSize = nInputSamples * nPCMBitSize / 8;
pbPCMBuffer = new char[nPCMBufferSize]; //一帧PCM数据存放在此,再送入编码器
pbAACBuffer = new BYTE[nMaxOutputBytes]; //编码器内编码后的AAC数据,暂存在此
/*获得当前编码器设置*/
pConfiguration = faacEncGetCurrentConfiguration(hEncoder);
/*原始(Raw)的AAC音频不能直接不放,需要解码。加了ADTS头的AAC音频可以直接播放,也可解码播放*/
pConfiguration -> outputFormat = 1; //输出数据的类型 0=原始AAC,1=加ADTS头的AAC
pConfiguration -> inputFormat = FAAC_INPUT_16BIT; //输入音频数据的采样位数(要与采集设置参数一致)
pConfiguration -> useTns = 1; //瞬时噪声定形滤波器
pConfiguration -> allowMidside = 0; //mid/side coding
pConfiguration -> aacObjectType=LOW; //MAIN=1,LOW=2,SSR=3,LTP=4
//其他一些参数:
//mpegVersion :mpeg版本, MPEG2/MPEG4
//useLfe:低频增强
//bitRate :码率
//bandWidth:占用的带宽
/*应用编码器设置*/
nRect = faacEncSetConfiguration(hEncoder,pConfiguration);
}
void myRecord::Start_Record()
{
StartRecord();
}
void myRecord::StartRecord()
{
fout = fopen("/home/pi/Desktop/New/Record/_pcm 16000 16.pcm","wb");
AACFILE = fopen("/home/pi/Desktop/New/Record/_aac 16000 16.aac","wb");
FAAC();
audioInput = new QAudioInput(format,this);
device = audioInput->start();
qDebug() << "Start record !";
connect(device,SIGNAL(readyRead()),this,SLOT(captureDataFromDevice())); //录音时新数据传入,信号触发
QTimer::singleShot(30000,this,SLOT(on_Record_stop())); //定时器设置录音时间
}
/*停止录音后,编码器内部还有几帧的缓存没出来*/
/*要等完全取出再结束编码,否则音频末尾丢几帧*/
void myRecord::StopRecord()
{
audioInput->stop();
while (DataReadAll > DataEncode)
{
int rest = DataReadAll - DataEncode; //剩下未编码的数据大小
std::cout << "----------rest : " << rest << std::endl;
if (rest > nPCMBufferSize)
{
FaacEncode();
}
else //把最后剩下不足一帧的数据进行编码
{
if (mark >= PoolSize)
mark = 0;
memset(pbPCMBuffer, 0, nPCMBufferSize);
memcpy(pbPCMBuffer, DataPool + mark, rest);
nInputSamples = nPCMBufferSize / (nPCMBitSize / 8);
/*Encode*/
nRect = faacEncEncode(hEncoder, (int32_t*)pbPCMBuffer, nInputSamples, pbAACBuffer, nMaxOutputBytes);
std::cout << "Encode : " << nRect << std::endl;
fwrite(pbAACBuffer, 1, nRect, AACFILE); //写AAC文件
DataEncode += nPCMBufferSize;
mark += rest;
//myserver->SendMessage(pbAACBuffer,nRect);
}
}
/*循环不断往编码器写入0数据,如果返回为13(输入为0时编码出的返回值),说明有音频的部分已经编完了,可以退出了*/
while (nRect != 13)
{
memset(pbPCMBuffer, 0, nPCMBufferSize);
nRect = faacEncEncode(hEncoder, (int32_t*)pbPCMBuffer, nInputSamples, pbAACBuffer, nMaxOutputBytes);
std::cout << "Encode : " << nRect << std::endl;
fwrite(pbAACBuffer, 1, nRect, AACFILE);
}
std::cout << "Stop Record" << std::endl;
fclose(fout);
fclose(AACFILE);
nRect = faacEncClose(hEncoder); //关闭编码器
}
void myRecord::on_Record_stop()
{
StopRecord();
}
/*不断从麦克风中读取数据,存到DataPool中*/
void myRecord::captureDataFromDevice()
{
temp = end - offset;
if(end <= PoolSize)
{
if(end+4096read(DataPool+end,4096);
offset += temp;
end += nBytesRead;
/*写PCM文件*/
fwrite(DataPool+offset,1,(end - offset),fout);
}
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);
}
else
{
memcpy(DataPool+end,fin,nBytesRead);
offset += temp;
end += nBytesRead;
fwrite(DataPool+offset,1,end - offset,fout);
}
delete fin;
}
DataReadAll+=nBytesRead;
std::cout<<"DataReadAll :"<< DataReadAll </*不断从DataPool中取数据,由于nPCMBufferSize是PoolSize的倍数,因此只用设置一个mark标志位就可以循环读数据了*/
void myRecord::FaacEncode()
{
if(DataReadAll - DataEncode > nPCMBufferSize)
{
memset(pbPCMBuffer,0,nPCMBufferSize);
memcpy(pbPCMBuffer,DataPool+mark,nPCMBufferSize); //从DataPool取数据到pbPCMBuffer
nInputSamples = nPCMBufferSize / (nPCMBitSize/8);//一帧样本大小通过该计算得到
/*Encode*/
/*传pbPCMBuffer和长度nPCMBufferSize,编码后的数据会存到pbAACBuffer,编码后的数据大小为nRect*/
nRect = faacEncEncode(hEncoder,(int32_t*)pbPCMBuffer,nInputSamples,pbAACBuffer,nMaxOutputBytes);
std::cout<<"Encode : " << nRect <//写AAC文件
DataEncode+=nPCMBufferSize;
mark+=nPCMBufferSize;
std::cout<<"DataEncode : " << DataEncode <//mark到了DataPool末尾,再回到头继续
if(mark>=PoolSize)
mark = 0;
//myserver->SendMessage(pbAACBuffer,nRect); 可以用TCP发送数据
}
}
三:注意事项
(1)Faac编码器自带缓存,类似于队列的结构。前几次编码会发现nRect的返回值为0,不要觉得是编码失败,原因是内部队列一开始是空的,还没轮到对新传进去的数据进行编码。同样,在采集结束后,编码器内部缓存仍有数据在编,不要急着关闭编码器,否则音频最后几帧会丢失。
(2)faacEncOpen(unsigned long sampleRate,
unsigned int numChannels,
unsigned long *inputSamples,
unsigned long *maxOutputBytes)
该接口前两个参数是采样率(sampleRate)和通道数(numChannels),要和采集时的设置一致。后面两个参数是编码器自动返回给你的,不要去手动改它的大小。
(3)每次送入编码的数据长度要为nPCMBufferSize一致,大小由下面计算得到:
nPCMBufferSize = nInputSamples * nPCMBitSize / 8;
如果送入数据长度不足,可能会导致音频断层或者乱帧。
(4)想用网络实时传出去的朋友,在编码完可以直接用TCP发送。我在代码中注释的SendMessage()就是基于Libevent的网络传输功能。自己在另外一端接收到数据并写个AAC文件,没有数据丢失。
四:运行结果
在Linux下用QT运行该项目,然后用音频分析软件(Audacity)打开进行对比。上面为编码后的AAC音频,下面为原始PCM音频。数据对其一致。
由于树莓派没有安装截屏,就用手机拍了。画质渣请见谅。