1. 引言

在 RV1126 音视频开发中,AENC(Audio Encoder,音频编码模块)是瑞芯微多媒体处理链路中的核心组件之一。它负责将采集到的原始音频数据(如 PCM)编码为指定的压缩格式(如 AAC、G711、G726 等),以便于后续的存储或网络传输。

本文将结合 RV1126 的 RKMPP(Rockchip Media Process Platform)框架,详细介绍 AENC 模块的初始化、配置、编码流程以及实际开发中的注意事项,帮助开发者快速上手。

2. AENC 模块概述

2.1 什么是 AENC

AENC 是 RKMPP 中负责音频编码的功能模块。与视频编码(VENC)类似,AENC 通过 MPI(Media Process Interface)对外提供统一的编程接口,屏蔽了底层硬件编解码器的差异。

2.2 支持的编码格式

RV1126 的 AENC 模块通常支持以下音频编码格式:

  • AAC:常用于网络流媒体,压缩率高,音质好。
  • G711(A/U Law):PCM 压缩格式,广泛用于 VoIP 和安防监控。
  • G726:低码率语音编码,常用于对讲系统。

2.3 在音视频链路中的位置

典型的音频处理链路如下:

音频采集 AI

音频处理 AENC

封装 MUX

网络传输/存储

3. 开发环境准备

3.1 硬件平台

  • 主控芯片:RV1126
  • 音频输入:I2S 接口麦克风阵列或 Line In

3.2 软件环境

  • SDK:Rockchip Linux SDK(含 RKMPP 库)
  • 交叉编译工具链:arm-rockchip830-linux-uclibcgnueabihf
  • 头文件路径:include/rkaiq/include/rk_mpi_audio.h

4. AENC 模块开发流程

4.1 代码执行流程总结

上述示例代码的整体执行流程可以概括为以下几个步骤:

  1. 初始化 AI 音频采集通道:设置采样率(48000Hz)、采样格式(S16)、声道数(2)等参数,并通过 RK_MPI_AI_SetChnAttr 配置、RK_MPI_AI_EnableChn 使能 AI 通道。
  2. 创建 AENC 编码通道:配置编码类型为 AAC、码率 64kbps、采样率与 AI 通道保持一致,通过 RK_MPI_AENC_CreateChn 创建编码通道。
  3. 绑定 AI 与 AENC 通道:通过 RK_MPI_SYS_Bind 将 AI 采集通道与 AENC 编码通道绑定,使 PCM 数据自动从 AI 流向 AENC,无需手动搬运数据。
  4. 创建线程获取编码码流:启动 get_audio_aenc_thread 线程,循环调用 RK_MPI_SYS_GetMediaBuffer 从 AENC 通道获取编码后的 AAC 数据。
  5. 写入 ADTS 头并保存文件:为每帧 AAC 裸流添加 7 字节的 ADTS 头(包含采样率、声道数、帧长度等信息),然后写入 test_capture.aac 文件。
  6. 释放缓冲区并循环:写入完成后调用 RK_MPI_MB_ReleaseBuffer 释放缓冲区,继续获取下一帧数据。
  7. 主线程保持运行:主线程通过 while(1) sleep(2) 保持进程常驻,等待编码线程持续工作。

配置并使能 AI 采集通道

创建 AENC 编码通道

RK_MPI_SYS_Bind 绑定 AI 与 AENC

创建线程获取编码码流

获取 AAC 数据 RK_MPI_SYS_GetMediaBuffer

添加 ADTS 头并写入文件

释放缓冲区 RK_MPI_MB_ReleaseBuffer

4.2 ADTS 参数介绍

在上一节的流程中,我们为每帧 AAC 裸流添加了 7 字节的 ADTS(Audio Data Transport Stream)头。ADTS 是 AAC 音频的一种封装格式,用于在传输或存储时标识每一帧的起始位置和参数信息,使解码器能够正确解析码流。

ADTS 头固定为 7 字节,其各字节的位域含义如下:

字节 位域 长度(bit) 说明
0 syncword 12 同步字,固定为 0xFFF,用于标识一帧 ADTS 数据的开始
1 ID 1 MPEG 标识,0 表示 MPEG-4,1 表示 MPEG-2
1 layer 2 固定为 00
1 protection_absent 1 是否关闭 CRC 校验,1 表示无 CRC
2 profile 2 AAC 档次,01 表示 AAC-LC(Low Complexity)
2 sampling_frequency_index 4 采样率索引,对应关系见下表
2 private_bit 1 私有位,通常为 0
2 channel_configuration 3 声道数配置,2 表示双声道
3 original_copy 1 原始/拷贝标识,通常为 0
3 home 1 家庭标识,通常为 0
3 copyright_identification_bit 1 版权标识位,通常为 0
3 copyright_identification_start 1 版权标识起始位,通常为 0
3-4 aac_frame_length 13 当前帧总长度(含 ADTS 头),单位字节
5 adts_buffer_fullness 11 码率控制状态,0x7FF 表示码率可变(VBR)
5-6 number_of_raw_data_blocks_in_frame 2 当前帧包含的原始数据块数,通常为 00

其中 sampling_frequency_index 与采样率的对应关系如下:

sampling_frequency_index 采样率(Hz)
0 96000
1 88200
2 64000
3 48000
4 44100
5 32000
6 24000
7 22050
8 16000
9 12000
10 11025
11 8000
12 7350

对照示例代码中的 GetAdtsHeader 函数:

  • pu8AdtsHdr[0] = 0xFFpu8AdtsHdr[1] = 0xF1:写入同步字 0xFFF,并设置 ID、layer、protection_absent 等字段。
  • pu8AdtsHdr[2]:通过 (AAC_PROFILE_LOW << 6) + (u8FreqIdx << 2) + (u8Channel >> 2) 设置 profile、采样率索引和声道数的高位。
  • pu8AdtsHdr[3]pu8AdtsHdr[4]pu8AdtsHdr[5]:写入声道数低位、帧长度 aac_frame_lengthadts_buffer_fullness
  • pu8AdtsHdr[6] = 0xFC:设置 number_of_raw_data_blocks_in_frame0,表示每帧只包含一个原始数据块。

4.3 AENC模块完整代码

#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;
}

Logo

一站式 AI 云服务平台

更多推荐