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

上一次发了QT音频采集篇,本次是它的后续内容。

未读过前篇的可以到此链接:Linux系统 QT+Faac实时音频采集编码(QT音频采集篇)

Linux下编译FAAC库:linux下编译faac库


一:FAAC使用流程图:



二:项目代码

本次项目基于上次的QT音频采集,把采集到的PCM数据存到本地文件,同时进行实时编码,再保存一个编码后的AAC文件,把两者进行对比,可以看到编码的效果。


.pro 文件

扫描二维码关注公众号,回复: 2478922 查看本文章

包含libfaac库文件位置,看安装在哪

LIB+= /usr/local/lib/libfaac.so\


.h 头文件

/*本程序实现简单的QT音频录音并实时写入PCM文件*/
/*同时初始化FAAC将原始音频编码实时写入AAC文件*/
/*  [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 <iostream>

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+4096<PoolSize)
        {
            nBytesRead = device->read(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 <<std::endl;
        std::cout<<"nBytesRead :"<< nBytesRead <<std::endl;
    }
    FaacEncode();
}


/*不断从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 <<std::endl;

        fwrite(pbAACBuffer,1,nRect,AACFILE);  //写AAC文件

        DataEncode+=nPCMBufferSize;
        mark+=nPCMBufferSize;

        std::cout<<"DataEncode :  " << DataEncode <<std::endl;
        std::cout<<"mark :  " << mark <<std::endl;

	//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音频。数据对其一致。

由于树莓派没有安装截屏,就用手机拍了。画质渣请见谅。





猜你喜欢

转载自blog.csdn.net/azazssaz2/article/details/80842297