Linux_22:音频AAC编码

1.音频编码的原理

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

2.AAC 编码的特点

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

1. ADIF 格式

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

2. ADTS 格式

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

1. adts_fixed_header 的参数

采样率下标和采样率关系

2 .adts_variable_header 的参数

3.RV1126的 AENC 模块

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

1.RV1126 AENC 模块参数

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

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

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

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

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

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

u32Channels **:**编码通道数

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

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

u32Channels **:**编码通道数

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

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

u32Channels **:**编码通道数

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

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

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

u32Channels **:**编码通道数

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

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

2.设置 AENC API 属性

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

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

4.代码

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

1.RV1126 多线程获取音频编码 AAC 码流的流程

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

2.多线程获取每一帧 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码流

}​​​​​​​

1.每个AAC码流添加ADTSHeader头部

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

(adts_fixed_header)

(adts_variable_header)

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

#define AAC_PROFILE_LOW 1

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 头部

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

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

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

3.代码

cs 复制代码
#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_aenc.aac","w+");
    MEDIA_BUFFER mb;
    RK_U8  Adts_Header[7];

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

        printf("RK_MPI_SYS_GetMediaBuffer Success.\n");
        GetAdtsHeader(Adts_Header,48000,2,RK_MPI_MB_GetSize(mb));
        fwrite(Adts_Header,1,7,aac_file);
        fwrite(RK_MPI_MB_GetPtr(mb),1,RK_MPI_MB_GetSize(mb),aac_file);

        RK_MPI_MB_ReleaseBuffer(mb);
    }
    

    return NULL;
}

int main(int argc, char *argv[])
{
    int ret;
    RK_MPI_SYS_Init();

    AI_CHN_ATTR_S ai_chn_attr;

    ai_chn_attr.pcAudioNode = AUDIO_PATH;
    ai_chn_attr.enSampleFormat = RK_SAMPLE_FMT_S16;
    ai_chn_attr.u32Channels = 2;
    ai_chn_attr.u32SampleRate = 48000;
    ai_chn_attr.u32NbSamples = 1024;
    ai_chn_attr.enAiLayout = AI_LAYOUT_NORMAL;

    ret = RK_MPI_AI_SetChnAttr(AI_CHN,&ai_chn_attr);
    if(ret)
    {
        printf("RK_MPI_AI_SetChnAttr Failed\n");
        return 0 ;
    }
    else
    {
        printf("RK_MPI_AI_SetChnAttr Success\n");
    }

    ret = RK_MPI_AI_EnableChn(AI_CHN);
    if(ret)
    {
        printf("RK_MPI_AI_EnableChn Failed\n");
        return 0 ;
    }
    else
    {
        printf("RK_MPI_AI_EnableChn Success\n");
    }

    AENC_CHN_ATTR_S aenc_chn_attr;
    aenc_chn_attr.enCodecType = RK_CODEC_TYPE_AAC;
    aenc_chn_attr.u32Quality = 1;
    aenc_chn_attr.u32Bitrate = 64000;
    aenc_chn_attr.stAencAAC.u32Channels = 2;
    aenc_chn_attr.stAencAAC.u32SampleRate = 48000;

    ret = RK_MPI_AENC_CreateChn(AENC_CHN,&aenc_chn_attr);
    if(ret)
    {
        printf("K_MPI_AENC_CreateChn Failed\n");
        return 0 ;
    }
    else
    {
        printf("K_MPI_AENC_CreateChn Success\n");
    }

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

    MPP_CHN_S aenc_chn;
    aenc_chn.enModId = RK_ID_AENC;
    aenc_chn.s32ChnId = AENC_CHN;

    ret = RK_MPI_SYS_Bind(&ai_chn,&aenc_chn);
    if(ret)
    {
        printf("RK_MPI_SYS_Bind Failed\n");
        return 0 ;
    }
    else
    {
        printf("RK_MPI_SYS_Bind Success\n");
    }

    pthread_t aac_pid;

    pthread_create(&aac_pid,NULL,get_audio_aenc_thread,NULL);

    while (1)
    {
        sleep(2);
    }

    RK_MPI_SYS_UnBind(&ai_chn,&aenc_chn);
    RK_MPI_AI_DisableChn(AI_CHN);
    
    return 0;
}
相关推荐
HAPPY酷2 小时前
C++ 音视频项目与 UE5 渲染与电影制作的关系
c++·ue5·音视频
听麟2 小时前
HarmonyOS 6.0+ PC端分布式并行计算引擎开发实战:边缘协同场景下的异构资源调度与任务优化
分布式·华为·音视频·harmonyos·政务
人机与认知实验室3 小时前
Seedance:字节跳动的AI视频生成技术突破与行业变革
人工智能·音视频
爱打代码的小林3 小时前
基于 Lucas-Kanade 光流法实现视频特征点追踪
opencv·计算机视觉·音视频
Knight_AL3 小时前
如何用 FFmpeg 处理 PCM 音频 & 判断 PCM 文件到底是什么格式
ffmpeg·音视频·pcm
集成显卡1 天前
前端视频播放方案选型:主流 Web 播放器对比 + Vue3 实战
前端·vue·音视频
爱吃番茄鼠骗1 天前
回顾ESP32S3系列---音频开发
音视频
ViiTor_AI1 天前
AI 在线字幕去除工具:一键无损删除视频硬字幕与软字幕
人工智能·音视频
愚公搬代码1 天前
【愚公系列】《AI短视频创作一本通》027-AI 短视频创作的注意事项及未来展望(AI短视频的技术展望)
人工智能·音视频