3.音频

3.1PCM音频原始数据

一.PCM数据的生成过程

音频原始数据PCM,PCM全称是脉冲编码调制数据。PCM数据是未经过压缩的音频数据,它是由模拟信号经过采样、编码等步骤转换成标准的数字信号,下面是PCM生成的总流程。从下面这个流程图,我们可以看出来,音频模拟信号转换成数字信号需要经过采样量化编码三大步骤会变成PCM音频数字信号

1.采样:

采样是我们音频最重要的知识点之一,它指的是把一段连续的模拟信号转换成离散的数字信号。而采样率就指的是每秒钟采样的个数,而根据奈奎斯特采样公式:当采样率大于等于连续信号的2倍时,采样信号就能够无差别还原出原始的信号。比方说人类的听觉频率是20HZ-20KHZ,而采样率就需要达到40KHZ以上才能够保证数据的完整性。

        

                          (音频模拟信号)                                                (音频数字信号

2.量化:

量化指的是在坐标轴上,把每一个离散数据进行数字化操作。换言之就是把我们每一个采样的点都按照数字化表示出来,如下图

从这张图我们可以看出,量化的过程就是把刚才我们转换成的数字信号一个一个点用竖线显示出来,这样的话我们在数字化的时候就方便很多。

3.编码

把每一个量化的采样点存储起来,并以二进制的形式表现出来的过程就是编码。下图就是存储的表格:

把上图所有的数据存储起来,就是一段连续的PCM数据。

PCM数据,二进制形式:

011011110111101……

对于一个音频的原始数字信号的生成:

采样->量化->编码: PCM数据

二. PCM数据有以下重要的参数

2.1. 采样率:指的是每秒钟采样的个数,换言之就是1S钟采集声音的频率,比方说48000HZ就相当于一秒钟PCM采集48000个采样点,总体来说采样率越高,音频的质量越好。PCM数据常用的采样率有:

192000HZ:192KHZ(蓝光、高清电影DVD)

96000HZ 96KHZ(蓝光、高清电影DVD)

48000HZ48KHZ(数字电视、DVD)  (最常用)

44100HZ:44.1KHZ(CD音质)

22000HZ:22KHZ(无线广播)

2.2. 采样深度:位深度(Bit Depth),即每个采样点用多少位二进制数表示,决定了音频的动态范围(音量的最大与最小值差距),每次采样的大小,比方说如果采样深度是16BIT,那声音就有2的16次方的振幅,而32bit就相当于有2的32次方个振幅。声音振幅越多,声音的质量就会越高在PCM中,有三种常见的采样大小:8 BIT16BIT32BIT这三种采样格式

2.3. 通道数PCM一般有四种通道数,分别是单通道、双声道、四声道、5.1声道

2.3.1.单声道:

指的是只有一个声音的通道,比方说电话、喇叭之类的

2.3.2.双声道(立体声)

双声道指的是有两个声音的通道,声音在录制的过程中分配到两个独立的声道,这让人听起来就有立体的感觉。

2.3.3.四声道:

四声道指的是前左、前右、后左、后右四个发声通道。观众听起来,则像被声音包围了一样。

2.3.4. 5.1声道:

5.1声道广泛运用在家庭影院,因为开发的时候用的较少,所以这里就不多介绍。

2.4. PCM比特率

比特率指的是每秒传输的比特数(bit),一般PCM的比特率计算公式是:采样率*采样深度*通道数。比方说采样率是48000,采样深度是16BIT,通道数2,那它PCM比特率的计算公式就等于48000 * 16 * 2 = 1536000bit

2.5. PCM文件大小计算:

假设一个PCM音频设备采样率48000、采样精度是16bit、2通道,大概采集10分钟数据,那它的大小:采样率 * 采样深度 * 通道数 * 时长 = 48000 * 16 * 2 * 10 * 60 = 921600000bit,然后再把bit转换成字节(BYTE) 921 600 000/8/1024/1024 = 109

2.3. PCM存储格式

上图是PCM单双声道的存储布局,这里我们来重点讲解一下双声道的布局。一般双声道的存储有两种存储方式,一种是交错模式、另外一种是非交错模式。交错模式:首先记录第一帧的左声道样本和右声道样本;非交错模式:首先先记录一个周期内所有帧的左声道样本、再记录所有右声道样本

双声道的存储模式:

交错模式:L R L R L R L R

非交错模式:L L L L L R R R

3.2RV1126 音频AI模块的详解

  • 什么是音频AI模块

RV1126的AI模块指的是音频输入模块,它的作用是通过内置芯片读取麦克风等音频的模拟信号,然后把音频模拟信号转换成数字信号。在RV1126里面,音频AI模块是所有音频输入的入口。下面是AI模块和麦克风等音频输入模块的关系

  • AI模块结构体的重要参数

下面是AI_CHN_ATTR_S结构体的成员变量

2.1. pcAudioNodeAUDIO音频节点,默认是default

2.2. enSampleFormat:采样格式,下面是RV1126的提供的采样格式,下面好多种采样格式,我们来分别讲解一下:

2.2.1.RK_SAMPLE_FMT_U8:无符号整型8位采样格式,它是8BIT采样格式的一种,这里的U是unsigned的缩写

2.2.2.RK_SAMPLE_FMT_S16:整型16位采样格式,它是16IT采样格式,S是signed的缩写

2.2.3.RK_SAMPLE_FMT_S32:整型32位采样格式,它是32IT采样格式,S是signed的缩写

2.2.4.RK_SAMPLE_FMT_FLT:利用float格式去采样,它是用浮点型格式进行采样。取值范围是[-1.0, 1.0]。

2.2.5.RK_SAMPLE_FMT_U8P:无符号整型8位采样平面格式,这里的P表示的是平面格式,平面格式指的是非交错模式,如:LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL

2.2.6.RK_SAMPLE_FMT_S16P:整型16位采样平面格式,这里的P表示的是平面格式,平面格式指的是非交错模式,如:LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL

2.2.7.RK_SAMPLE_FMT_S32P:整型32位采样平面格式,这里的P表示的是平面格式,平面格式指的是非交错模式,如:LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRLR

2.2.8.RK_SAMPLE_FMT_FLTP:浮点型采样平面格式,这里的P表示的是平面格式,平面格式指的是非交错模式,如:LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL

2.2.9.RK_SAMPLE_FMT_G711A:G711A是标准的ITU-T推荐的标准之一采用8位精度进行采样,它主要运用在电话领域。A表示的是将一个13bit的PCM样本压缩成8bit样本

RK_SAMPLE_FMT_G711U:G711U是标准的ITU-T推荐的标准之一采用8位精度进行采样,它主要运用在电话领域。U表示的是将一个14bit的PCM样本压缩成8bit样本。

2.3. u32Channels:音频通道数,音频默认通道数是2

2.4. u32SampleRate:音频采样率,在RV1126里面常用的采样率是16000、44100、48000三种。目前在这个开发中,我们用的是48000,因为48000采样率的音频效果最好。

2.5. u32NbSample:每一帧的采样个数,这个参数要和对应的音频编码格式来设置。AAC音频编码格式对应的采样个数是1024,MP3音频编码格式对应的采样个数是1152,其实还有很多。

2.6. enAiLayout:音频输入布局类型,它提供了三个成员变量选择:AI_LAYOUT_NORMALAI_LAYOUT_MIC_REFAI_LAYOUT_REF_MIC它默认是用的是AI_LAYOUT_NORMAL类型默认是AI_LAYOUT_NORMAL

  • 设置AI模块的API

3.1. RK_MPI_AI_SetChnAttr的功能:

RK_MPI_AI_SetChnAttr功能主要是设置AI通道属性

第一个参数:AI模块的通道号ID,取值范围是[0, AI_MAX_CHN_NUM]

第二个参数:AI_CHN_ATTR_S结构体指针

3.2. RK_MPI_AI_EnableChn的功能:

RK_MPI_AI_EnableChn功能主要是打开AI通道

第一个参数:AI模块的通道号ID,这里填的值和设置的通道号一致

3.3. RK_MPI_AI_StartStream的功能:

RK_MPI_AI_StartStream功能主要是启动AI音频流

第一个参数:AI模块的通道号ID,这里填的值和设置的通道号一致

3.3多线程获取音频AI的PCM数据

通过RKMEDIA的API获取音频的输入的PCM数据并且保存起来。

一、RV1126多线程获取音频PCM数据的流程

RV1126多线程采集AI模块的数据,一般分为三个步骤:分别是初始化AI模块、启动AI模块开启采集、开启多线程采集AI数据并保存到本地。

1.1.初始化AI模块:

AI模块的初始化实际上就是对AI_CHN_ATTR_S的参数进行设置、然后调用RK_MPI_AI_SetChnAttr设置AI模块并使能RK_MPI_AI_EnableChn,伪代码如下:

AI_CHN_ATTR_S ai_chn_s;

ai_chn_s.pcAudioNode = AUDIO_PATH;

ai_chn_s.u32Channels = 2;

ai_chn_s.u32NbSamples = 1024;

ai_chn_s.u32SampleRate = 48000;

ai_chn_s.enAiLayout = AI_LAYOUT_NORMAL;

ai_chn_s.enSampleFormat = RK_SAMPLE_FMT_S16;

ret = RK_MPI_AI_SetChnAttr(AI_CHN, &ai_chn_s);

if(ret)

{

   printf("RK_MPI_AI_SetChnAttr Failed...\n");

}

ret = RK_MPI_AI_EnableChn(AI_CHN);

if(ret)

{

     printf("RK_MPI_AI_EnableChn Failed...\n");

}

1.2.启动AI模块:

设置完上面的AI模块后,就要开启AI模块的工作,使用的API是RK_MPI_AI_StartStream伪代码如下:

....................................................................

ret = RK_MPI_AI_StartStream(AI_CHN);

 if(ret)

 {

        printf("RK_MPI_AI_StartStream Failed...\n");

 }

.......................................................................

1.3.开启多线程获取PCM音频数据:

开启一个线程去采集每一帧VI模块的数据,使用的API是RK_MPI_SYS_GetMediaBuffer, 模块ID是RK_ID_AI,通道号ID是AI创建的通道ID号这个API的具体作用已经在之前的获取AI数据,我们直接上伪代码

........................................

while(1)

{

  .........................

  mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_AI, s32_chn_id, -1);

  fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, pcm_file);

.......................

}

  • 开始写代码

collect_audio_stream文件放到共享文件夹,然后linux打开复制到项目中,用vscode打开项目中的文件夹,写代码和makefile

代码

#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#include "rkmedia_api.h"
#define AUDIO_PATH "default"
#define AI_CHN 0

//获取PCM数据的线程
void * get_ai_pcm_thread(void * args)
{
    pthread_detach(pthread_self());
    FILE * pcm_file = fopen("test_ai.pcm", "w+");
    MEDIA_BUFFER mb ;

    while (1)
    {
        //获取PCM数据的线程
        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_AI, AI_CHN, -1);
        if(!mb)
        {
            printf("RK_MPI_SYS_GetMediaBuffer Failed....\n");
            break;
        }
        
        printf("get_pcm_data success...\n");
        fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, pcm_file);
        RK_MPI_MB_ReleaseBuffer(mb);
    }
}


int main(int argc, char *argv[])
{
    int ret;
    AI_CHN_ATTR_S ai_chn_s;
    ai_chn_s.pcAudioNode = AUDIO_PATH; //音频路径,默认default
    ai_chn_s.u32SampleRate = 48000; //音频采样率
    ai_chn_s.u32Channels = 2;   //音频通道数
    ai_chn_s.u32NbSamples = 1024; //音频采样个数,这里固定1024
    ai_chn_s.enSampleFormat = RK_SAMPLE_FMT_S16;
    ai_chn_s.enAiLayout = AI_LAYOUT_NORMAL;  //NORMAL
    ret = RK_MPI_AI_SetChnAttr(AI_CHN, &ai_chn_s);  //设置AI模块
    if(ret)
    {
        printf("Set Ai_Chn_Attr Failed....\n");
    }
    else
    {
        printf("Set Ai_Chn_Attr Success....\n");
    }

    ret = RK_MPI_AI_EnableChn(AI_CHN);  //使能AI模块
    if(ret)
    {
        printf("Enable Ai_Chn_Attr Failed....\n");
    }

    ret = RK_MPI_AI_StartStream(AI_CHN); //启动音频AI模块
    if(ret)
    {
        printf("RK_MPI_AI_StartStream Failed....\n");
    }

    pthread_t pid;
    pthread_create(&pid, NULL, get_ai_pcm_thread, NULL); 

    while (1)
    {
        sleep(2);
    }

    RK_MPI_AI_DisableChn(AI_CHN);


    return 0;
}

makefile

hide := @
ECHO := echo

G++ := /opt/rv1126_rv1109_linux_sdk_v1.8.0_20210224/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++
CFLAGS := -I./include/rkmedia \
			-I./include/rkaiq/common \
			-I./include/rkaiq/xcore \
			-I./include/rkaiq/uAPI \
			-I./include/rkaiq/algos \
			-I./include/rkaiq/iq_parser \
			-I./rknn_rockx_include \
			-I./im2d_api          \
			-I./arm_libx264/include \
			-I./arm32_ffmpeg_srt/include  

LIB_FILES := -L./rv1126_lib  

LD_FLAGS := -lpthread -leasymedia -ldrm -lrockchip_mpp \
	        -lavformat -lavcodec -lswresample -lavutil \
			-lasound -lv4l2 -lv4lconvert -lrga \
			-lRKAP_ANR -lRKAP_Common -lRKAP_3A \
			-lmd_share -lrkaiq -lod_share   	
			
CFLAGS += -DRKAIQ

all:
	$(G++) rv1126_ai_stream.cpp $(CFLAGS) $(LIB_FILES) $(LD_FLAGS) -o rv1126_ai_stream
	$(hide)$(ECHO) "Build Done ..."

编译完的文件,复制到共享文件夹,从共享文件夹复制到桌面,打开软件放入板子的/tmp/,改权限,执行程序。把得到的音频结果,放到ffmpeg的bin中,然后命令行切换到ffmpeg目录,执行ffplay -ar 48000 -channels 2 -f s16le -i test_sdi_origin.pcm

  • ffplay播放pcm数据需要的命令:

ffplay -ar 采样率 -channels 通道数 -f 采样格式(s16le) -i pcm文件

3.4音频AAC编码的讲解

  • 音频编码的原理

音频为什么要进行编码压缩?我们就以PCM原始数据为例,假设这个PCM数据采样率为:48000、采样深度:16bit、声道数:2。对应的码率是:48000 * 16bit * 2 = 1536000bps ~=1.46M,若传输一分钟那就是1.46M * 60S~ = 87.6M。这个数据量是非常大,若在网络传输上这个音频的数据量很容易造成网络的负载压力。所以此时我们就需要对音频进行编码压缩,音频编码压缩格式分很多种,比方说:MP3、AAC、OGG格式。我们的课程重点来说AAC编码格式,因为AAC编码在网络传输中质量最好,并且AAC的压缩比高达1:18,是所有音频编码技术中压缩比最高。

  • AAC编码的特点

每个AAC音频帧包含了多个音频采样的压缩数据,AAC的一个音频帧包含1024个采样值。由于原始数据块它是以帧的形式存在,我们称之为原始帧。在AAC中一般有两种方式来封装,一种是ADIF,另外一种是ADTS。

2.1. ADIF格式

音频数据交换格式,这种格式必须在定义的音频数据流进行处理,基本上用于存储磁盘文件中

2.2. ADTS格式

音频数据传输流,这种格式是最常用的格式。它的特点是会同步字的比特流,并且允许在音频数据流任意帧解码。换言之,就是它的每一帧都有信息头,一个是AAC原始数据长度是可变,对原始帧加上ADTS头进行封装就生成ADTS帧。AAC的每一帧数据由ADTS Header和AAC Audio Data组成,其中ADTS Header占有7个字节-9个字节,ADTS Header由两部分组成分别是:固定头部信息(adts_fixed_header)和可变头信息(adts_variable_header),固定头信息指的是数据每一帧都是相同的,它主要定义了音频的采样率、声道数、帧长度等信息;可变头信息则主要描述帧和帧之间的可变。下面是adts帧的结构

2.2.1. adts_fixed_header的参数:

图二:采样率下标和采样率关系

2.2.2 .adts_variable_header的参数:

3.5RV1126的AENC模块的讲解

  • RV1126的AENC模块的介绍

RV1126的AENC模块是音频编码模块,主要是对AI模块进来的数据进行音频编码压缩处理,并输出对应的音频压缩码流,下面是AENC模块在RV1126里面和AI模块的关系。

  • RV1126的AENC模块参数

设置AENC模块的是AENC_CHN_ATTR_S结构体,下面我们重点看看这个结构体属性的具体定义:

2.1. enCodecType:音频编码协议类型,下面是AENC支持的音频编码格式

2.2. u32Bitrate:音频编码比特率,音频编码每秒传输的数据量。AAC编码协议推荐使用64kpbs(64000bps);G711A、G711U编码协议推荐使用64kps(64000bps);G726推荐使用32kpbs(32000bps)。

2.3. u32Quality: 音频编码质量,默认是1

2.4.stAencAAC、stAencMp2、stAencG711A、stAencG711U、stAencG726:这几个结构体是不同的音频编码器的专门协议属性结构体

2.4.1.stAencAAC:它是AAC编码协议属性

u32Channels:编码通道数

u32SampleRate:音频采样率,AAC的采样率范围是7350-96000

2.4.2.stAencMp2:它是MP2编码协议属性

u32Channels:编码通道数

u32SampleRate:音频采样率,MP2的采样率范围是0-48000

2.4.3.stAencG711A:它是G711A编码协议属性

u32Channels:编码通道数

u32SampleRate:音频采样率,G711A的采样率是8KHZ

u32NbSamples:采样个数,默认的采样个数是320

2.4.4.stAencG711U:它是G711U编码协议属性

u32Channels:编码通道数

u32SampleRate:音频采样率,G711U的采样率是8KHZ

u32NbSamples:采样个数,默认的采样个数是320

  • 设置AENC的API属性

第一个参数:AENC的通道号,取值范围是[0,16]

第二个参数:AENC_CHN_ATTR的结构体指针

3.6通过多线程获取RV1126的AAC码流

通过多线程的方式获取AAC码流并保存。

一、RV1126多线程获取音频编码AAC码流的流程

上面是RV1126多线程获取AAC码流的流程,分为六步:AI模块的初始化并使能、AENC模块的初始化、绑定AI模块和AENC模块、创建多线程获取AAC码流、向每个AAC码流添加ADTSHeader头部(这个是重点)、写入具体每一帧AAC的ES码流。

1.AI模块的初始化并使能

AI模块的初始化实际上就是对AI_CHN_ATTR_S的参数进行设置、然后调用RK_MPI_AI_SetChnAttr设置AI模块并使能RK_MPI_AI_EnableChn,伪代码如下:

AI_CHN_ATTR_S ai_chn_s;

ai_chn_s.pcAudioNode = AUDIO_PATH;

ai_chn_s.u32Channels = 2;

ai_chn_s.u32NbSamples = 1024;

ai_chn_s.u32SampleRate = 48000;

ai_chn_s.enAiLayout = AI_LAYOUT_NORMAL;

ai_chn_s.enSampleFormat = RK_SAMPLE_FMT_S16;

ret = RK_MPI_AI_SetChnAttr(AI_CHN, &ai_chn_s);

if(ret)

{

   printf("RK_MPI_AI_SetChnAttr Failed...\n");

}

ret = RK_MPI_AI_EnableChn(AI_CHN);

if(ret)

{

     printf("RK_MPI_AI_EnableChn Failed...\n");

}

​​​​​​​2.AENC模块的初始化

AENC模块的初始化实际上就是对AI_CHN_ATTR_S的参数进行设置、然后调用RK_MPI_AENC_CreateChn设置AENC模块伪代码如下:

AENC_CHN_ATTR_S aenc_attr;

aenc_attr.enCodecType = RK_CODEC_TYPE_AAC;

aenc_attr.u32Bitrate = 64000;

aenc_attr.u32Quality = 1;

aenc_attr.stAencAAC.u32Channels = 2;

aenc_attr.stAencAAC.u32SampleRate = 48000;

ret = RK_MPI_AENC_CreateChn(AENC_CHN, &aenc_attr);

if (ret)

{

        printf("Create AENC[0] failed! ret=%d\n", ret);

        return -1;

 }else{

        printf("Create AENC[0] success!\n");

}

​​​​​​​3.绑定AI模块和AENC模块

分别创建两个MPP_CHN_S结构体,分别是AI模块的MPP_CHN_S和AENC模块的MPP_CHN_S,创建完成之后则用RK_MPI_SYS_Bind对两个模块进行绑定

MPP_CHN_S ai_mpp_chn_s;

ai_mpp_chn_s.enModId = RK_ID_AI;

ai_mpp_chn_s.s32ChnId = 0;

MPP_CHN_S aenc_mpp_chn_s;

aenc_mpp_chn_s.enModId = RK_ID_AENC;

aenc_mpp_chn_s.s32ChnId = 0;

ret = RK_MPI_SYS_Bind(&ai_mpp_chn_s, &aenc_mpp_chn_s);

if (ret)

 {

        printf("RK_MPI_SYS_Bind Failed....\n");

 }else{

        printf("RK_MPI_SYS_Bind Success....\n");

}

​​​​​​​4.多线程获取每一帧AAC码流

开启一个线程去采集每一帧AENC模块的数据,使用的API是RK_MPI_SYS_GetMediaBuffer, 模块ID是RK_ID_AENC,通道号ID是AENC创建的通道ID号。这里需要注意的是,要进行两层写入。第一层要进行adts header头部的写入,第二层则需要进行adts es流的写入

....................................................

While(1)

{

 mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_AENC, AENC_CHN, -1);

 if (!mb)

 {

        printf("Get Aenc_Buffer break...\n");

        break;

 }

 fwrite(aac_header, 1, 7, aac_file); //对每一帧AAC码流写入adts_header头部

 fwrite(RK_MPI_MB_GetPtr(mb), 1, RK_MPI_MB_GetSize(mb), aac_file); //写入每一帧AAC的ES码流

}

​​​​​​​5.每个AAC码流添加ADTSHeader头部

在写入AACES码流前需要对其进行ADTS Header的封装,adts header头部分为:adts_fixed_header和adts_variable_header。在对其写入的时候需要把这两个头部都一并写到码流上面。具体的写入格式以下面两张表(adts_fixed_header和adts_variable_header)作为参考

根据上面两张表的信息,RK官方封装了一套写入AAC的方法,下面就是封装的具体方法。

typedef struct AacFreqIdx_

{

    RK_S32 u32SmpRate;

    RK_U8 u8FreqIdx;

} AacFreqIdx;

AacFreqIdx AacFreqIdxTbl[13] = {{96000, 0}, {88200, 1}, {64000, 2}, {48000, 3}, {44100, 4}, {32000, 5}, {24000, 6}, {22050, 7}, {16000, 8}, {12000, 9}, {11025, 10}, {8000, 11}, {7350, 12}};

static void GetAdtsHeader(RK_U8 *pu8AdtsHdr, RK_S32 u32SmpleRate, RK_U8 u8Channel,RK_U32 u32DataLen)

{

    RK_U8 u8FreqIdx = 0;

    for (int i = 0; i < 13; i++)

    {

        if (u32SmlpRate == AacFreqIdxTbl[i].u32SmpRate)

        {

            u8FreqIdx = AacFreqIdxTbl[i].u8FreqIdx;

            break;

        }

    }

    RK_U32 u32PacketLen = u32DataLen + 7;

    pu8AdtsHdr[0] = 0xFF;    //主要是写入syncword同步字节的前8位

    pu8AdtsHdr[1] = 0xF1;   //主要是写入syncword同步字节的后4位,并且设置ID号、layer、protection_absent

    pu8AdtsHdr[2] = ((AAC_PROFILE_LOW) << 6) + (u8FreqIdx << 2) + (u8Channel >> 2); //设置音频profile、sample_rate_index、声道数

    pu8AdtsHdr[3] = (((u8Channel & 3) << 6) + (u32PacketLen >> 11)); //设置声道数,original_copy,home,copyright_identification_bit、copyright_identification_start、aac_frame_length

pu8AdtsHdr[4] = ((u32PacketLen & 0x7FF) >> 3);  //设置aac_frame_length+adts_buffer_fullness

    pu8AdtsHdr[5] = (((u32PacketLen & 7) << 5) + 0x1F);//设置adts_buffer_fullness + number_of_raw_data_blocks_in_frame

    pu8AdtsHdr[6] = 0xFC; //设置 number_of_raw_data_blocks_in_frame

}

上面这个方法需要传入四个参数,分别是:

第一个参数pu8AdtsHdr:需要处理输出的字符串

第二个参数u32SampleRate音频的采样率(根据音频采样率去查找对应的采样率索引,这里的索引是AacFreqIdx)

第三个参数u8Channel音频通道数

第四个参数u32DataLen每一帧aac码流的长度(这里需要注意的是,在写入AAC码流的时候需要把每个AAC长度+7,因为头部是adts的头部是7个字节)

设置完成之后,就要对每个码流进行7个字节头部的写入。

fwrite(aac_header, 1, 7, aac_file); //对每一帧AAC码流写入adts_header头部

​​​​​​​6.写入具体每一帧AAC的ES码流

在写入AAC头部之后,就可以写入每一帧具体的AAC的ES码流

..................................................

fwrite(RK_MPI_MB_GetPtr(mb), 1, RK_MPI_MB_GetSize(mb), aac_file); //写入每一帧AAC的ES码流

...................................................

总结:

一个对每个码流生成头部的函数(7个数组(7个字节),每个8位,一共56位,正好两个头部56位)在每一帧ACC里写入了头部,然后再给每一帧ACC里写入具体的ES码流。

每一帧包含两部分,两部分分别写入之后,一帧一帧的输出。

collect_audio_stream文件放入共享文件夹,linux里打开复制到项目中,在vscode里打开项目中的文件夹,写程序和maekfile

#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#include "include/rkmedia/rkmedia_api.h"
#include "include/rkmedia/rkmedia_buffer.h"
#include "include/rkmedia/rkmedia_common.h"
#include "rkmedia_api.h"
#define AUDIO_PATH "default"
#define AI_CHN 0
#define AENC_CHN 0

#define AAC_PROFILE_LOW 1

typedef struct AacFreqIdx_   //索引
{
    RK_S32 u32SampleRate;
    RK_U8 u8FreqIdx;
} AacFreqIdx;

AacFreqIdx AacFreqIdxTbl[13] = {{96000, 0}, {88200, 1}, {64000, 2}, {48000, 3}, {44100, 4}, {32000, 5}, {24000, 6}, {22050, 7}, {16000, 8}, {12000, 9}, {11025, 10}, {8000, 11}, {7350, 12}};                  //索引

static void GetAdtsHeader(RK_U8 *pu8AdtsHdr, RK_S32 u32SmpleRate, RK_U8 u8Channel,
                          RK_U32 u32DataLen)
{
    RK_U8 u8FreqIdx = 0;
    for (int i = 0; i < 13; i++)
    {
        if (u32SmpleRate == AacFreqIdxTbl[i].u32SampleRate)
        {
            u8FreqIdx = AacFreqIdxTbl[i].u8FreqIdx;
            break;
        }
    }

    RK_U32 u32PacketLen = u32DataLen + 7;
    pu8AdtsHdr[0] = 0xFF;                                                           // 主要是写入syncword同步字节的前8位
    pu8AdtsHdr[1] = 0xF1;                                                           // 主要是写入syncword同步字节的后4位,并且设置ID号、layer、protection_absent
    pu8AdtsHdr[2] = ((AAC_PROFILE_LOW) << 6) + (u8FreqIdx << 2) + (u8Channel >> 2); // 设置音频profile、sample_rate_index、声道数
    pu8AdtsHdr[3] = (((u8Channel & 3) << 6) + (u32PacketLen >> 11));                // 设置声道数,original_copy,home,copyright_identification_bit、copyright_identification_start、aac_frame_length
    pu8AdtsHdr[4] = ((u32PacketLen & 0x7FF) >> 3);                                  // 设置aac_frame_length+adts_buffer_fullness
    pu8AdtsHdr[5] = (((u32PacketLen & 7) << 5) + 0x1F);                             // 设置adts_buffer_fullness + number_of_raw_data_blocks_in_frame
    pu8AdtsHdr[6] = 0xFC;                                                           // 设置 number_of_raw_data_blocks_in_frame
}

void *get_audio_aenc_thread(void *args)
{
    pthread_detach(pthread_self());
    FILE *aac_file = fopen("test_capture.aac", "w+");
    MEDIA_BUFFER mb = NULL;
    RK_U8 aac_header[7];

    while (1)
    {
        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_AENC, AENC_CHN, -1);
        if (!mb)
        {
            printf("RK_MPI_SYS_GetMediaBuffer break....\n");
            break;
        }

        printf("Get AAC_Buffer Success...\n");

        //获取AENC的AAC码流头部
        GetAdtsHeader(aac_header, 48000, 2, RK_MPI_MB_GetSize(mb));
        fwrite(aac_header, 1, 7, aac_file); //写入7个字节的头部
        fwrite(RK_MPI_MB_GetPtr(mb),1, RK_MPI_MB_GetSize(mb), aac_file); //写入AAC的ES码流
        RK_MPI_MB_ReleaseBuffer(mb);
    }
}

int main(int argc, char *argv[])
{
    int ret;
    AI_CHN_ATTR_S ai_chn_s;
    ai_chn_s.pcAudioNode = AUDIO_PATH; //音频采样路径
    ai_chn_s.u32SampleRate = 48000;     //音频采样率
    ai_chn_s.u32NbSamples = 1024;       //音频采样个数
    ai_chn_s.u32Channels = 2;           //音频采样通道
    ai_chn_s.enSampleFormat = RK_SAMPLE_FMT_S16; //采样格式
    ai_chn_s.enAiLayout = AI_LAYOUT_NORMAL;    //采样布局
    ret = RK_MPI_AI_SetChnAttr(AI_CHN, &ai_chn_s); //设置AI属性
    if (ret)
    {
        printf("RK_MPI_AI_SetChnAttr failed...\n");
    }
    else
    {
        printf("RK_MPI_AI_SetChnAttr success...\n");
    }

    ret = RK_MPI_AI_EnableChn(AI_CHN);  //使能AI模块
    if (ret)
    {
        printf("RK_MPI_AI_EnableChn failed...\n");
    }
    else
    {
        printf("RK_MPI_AI_EnableChn success...\n");
    }

    AENC_CHN_ATTR_S aenc_chn_attrs;  //AENC
    aenc_chn_attrs.enCodecType = RK_CODEC_TYPE_AAC;//AENC模块的编码协议
    aenc_chn_attrs.u32Bitrate = 64000; //音频编码比特率,64kbps
    aenc_chn_attrs.u32Quality = 1;     //音频质量
    aenc_chn_attrs.stAencAAC.u32Channels = 2; //音频通道数
    aenc_chn_attrs.stAencAAC.u32SampleRate = 48000; //音频编码采样率,这里要和AI模块的采样率一致
    ret = RK_MPI_AENC_CreateChn(AENC_CHN, &aenc_chn_attrs); //创建AENC模块
    if (ret)
    {
        printf("RK_MPI_AENC_CreateChn failed....\n");
    }
    else
    {
        printf("RK_MPI_AENC_CreateChn success....\n");
    }

    MPP_CHN_S ai_mpp_chn_s;
    ai_mpp_chn_s.enModId = RK_ID_AI;
    ai_mpp_chn_s.s32ChnId = AI_CHN;

    MPP_CHN_S aenc_mpp_chn_s;
    aenc_mpp_chn_s.enModId = RK_ID_AENC;
    aenc_mpp_chn_s.s32ChnId = AENC_CHN;
    ret = RK_MPI_SYS_Bind(&ai_mpp_chn_s, &aenc_mpp_chn_s);  //绑定AI模块和AENC模块
    if (ret)
    {
        printf("RK_MPI_SYS_Bind failed....\n");
    }
    else
    {
        printf("RK_MPI_SYS_Bind success....\n");
    }

    pthread_t pid;
    pthread_create(&pid, NULL, get_audio_aenc_thread, NULL); //创建线程获取AENC码流

    while (1)
    {
        sleep(2);
    }

    RK_MPI_SYS_UnBind(&ai_mpp_chn_s, &aenc_mpp_chn_s);
    RK_MPI_AENC_DestroyChn(AENC_CHN);
    RK_MPI_AI_DisableChn(AI_CHN);

    return 0;
}

makefile

hide := @
ECHO := echo

G++ := /opt/rv1126_rv1109_linux_sdk_v1.8.0_20210224/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++
CFLAGS := -I./include/rkmedia \
			-I./include/rkaiq/common \
			-I./include/rkaiq/xcore \
			-I./include/rkaiq/uAPI \
			-I./include/rkaiq/algos \
			-I./include/rkaiq/iq_parser \
			-I./rknn_rockx_include \
			-I./im2d_api          \
			-I./arm_libx264/include \
			-I./arm32_ffmpeg_srt/include  

LIB_FILES := -L./rv1126_lib  

LD_FLAGS := -lpthread -leasymedia -ldrm -lrockchip_mpp \
	        -lavformat -lavcodec -lswresample -lavutil \
			-lasound -lv4l2 -lv4lconvert -lrga \
			-lRKAP_ANR -lRKAP_Common -lRKAP_3A \
			-lmd_share -lrkaiq -lod_share   	
			
CFLAGS += -DRKAIQ

all:
	$(G++) rv1126_ai_stream.cpp $(CFLAGS) $(LIB_FILES) $(LD_FLAGS) -o rv1126_ai_stream
	$(G++) rv1126_aenc_stream.cpp $(CFLAGS) $(LIB_FILES) $(LD_FLAGS) -o rv1126_aenc_stream
	$(hide)$(ECHO) "Build Done ..."

编译完的文件,复制到共享文件夹,从共享文件夹复制到桌面,打开软件放入板子的/tmp/,改权限,执行程序。把得到的音频结果,放到ffmpeg的bin中,然后命令行切换到ffmpeg目录,执行ffplay.exe test_capture.acc

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐