海思mpp音频编码
音频编码海思MPP AUDIO模块包括音频输入、音频输出、音频编码、音频解码四个子模块。音频输入AI和输出模块AO通过对芯片音频接口的控制实现音频输入输出功能。音频编码和解码模块提供对G711、G726、ADPCM等格式的音频编解码功能并支持录制和播放LPCM格式的原始音频文件。详细音频模块介绍详见《海思MPP 媒体处理软件 V6.0 开发参考》,这里仅列举简单常用点源码链接https://gitee.com/kidwjb/codec_video_audio/tree/master/%E6%B5%B7%E6%80%9D/src原文链接https://kidwjb.top/archives/161音频接口和AI、AO设备音频输入输出接口简称为AIOAudio Input/Output接口用于和Audio Codec对接完成声音的录制和播放。软件中负责抽象音频接口输入功能的单元称之为AI设备负责抽象音频接口输出功能的单元称之为AO设备。对每个输入输出接口软件根据该接口支持的功能分别与AI设备和AO设备建立映射 关系。例如AIP0只支持音频输入则AIP0映射为AiDev0AOP0只支持音频输出 则AOP0映射为AoDev0音频接口时序海思MPP音频接口支持标准的I2S接口时序模式和PCM接口时序模式并提供灵活的配置以支持与多种Audio Codec对接。AI设备支持扩展的多路接收的I2S及PCM接口时序。对接时Codec的时序模式选择、同步时钟、采样位宽等配置必须与AI设备的配置保持一致否则可能采集不到正确的数据。AI/AO设备支持主模式和从模式主模式即AI/AO设备提供时钟从模式即Audio Codec提供时钟注意这里的Audio Codec就是指的外部的音频编码芯片。主模式时如果AI设备与AO设备同时对接同一个Codec则时钟供输入和输出共同使用其他情况没有此限制。而从模式时的输入输出时钟可以分别由外围Audio Codec提供。由于时序的问题在AI/AO设备从模式下建议用户先配置好对接的Codec再配 置AI或AO设备而在AI/AO设备主模式下建议用户先配置好AI或AO设备再配 置对接的Codec。笔者这里选择的是内部编码所以使用第二条方案音频输入输出支持重采样声音质量增强VQE不过这里都未用到在代码中需要设置去初始化音频接口与设备的对应关系芯片集成的AIO内部分两类只支持音频输入的AIP和只支持音频输出的AOP相应的AIO规格详见对应芯片的用户指南。AIO接口中只支持音频输出的AOP0可以配置是否共用帧同步时钟和位流时钟给 AIP0使用。共用时AI设备0和AO设备0的帧同步时钟与位流时钟必须相同即工 作模式必须一致采样频率也必须一致并且位流时钟按二者中的较高频率进行 配置。音频输入输出设备号音频的输入输出设备的通道数范围内置 Audio CodecHi3519DV500/Hi3516CV610提供一个内置的Audio Codec其中Hi3519DV500/ Hi3516CV610采用V751用于对接AIP0/AOP0实现声音的播放和录制。AIP0/AOP0 接口可以选择对接内置的Audio Codec或外接的Audio Codec进行声音的播放及录 制。因为内置Audio Codec不能发送同步时钟所以AIP0/AOP0接口对接内置Codec时 只能配置为I2S时序的主模式用户仍需要正确配置AIP0/AOP0和内置Audio Codec对 接的时序才可接收或发送音频数据。ioctl函数内置Audio Codec的用户态接口以ioctl形式体现其形式如下int ioctl (int fd, unsigned long cmd, …… );但在Audio Codec中实际只需要3个参数。因此其语法形式等同于。但在Audio Codec中实际只需要3个参数。因此其语法形式等同于int ioctl (int fd, unsigned long cmd, CMD_DATA_TYPE *cmddata);其中CMD_DATA_TYPE随参数cmd的变化而变化具体cmd类型详见手册这里只列举几个常用的代码编写音频设备初始化首先进行音频设备初始化调用的海思封装了一层的接口底层调用的hi_mpi_audio_init(前缀hi是进行了又一次封装手册里是ss有些地方是ot)应用程序启动Audio业务前必须完成Audio模块初始化工作。同理应用程序退出Audio业务后也要完成Audio模块去初始化工作释放资源。如果多次初始化仍会返回成功但实际上系统不会对Audio的运行状态有任何影 响。由于音频模块依赖用户态属性故音频不支持多进程操作。对于Hi3519DV500/ Hi3516CV610用户需要保证音频的相关操作和ss_mpi_audio_init在同一个进程 中。/* audio system init */ hi_s32 sample_comm_audio_init(hi_void) { hi_s32 ret; hi_mpi_audio_exit(); ret hi_mpi_audio_init(); if (ret ! HI_SUCCESS) { sample_print(hi_mpi_audio_init failed!\n); return HI_FAILURE; } return HI_SUCCESS; }初始化aac函数笔者最终会把音频流编码为aac文件根据查看例程发现会调用这个函数但是手册没有说也看不到这个函数定义但是保险起见还是加上td_s32 hi_mpi_aenc_aac_init(td_void);初始化aio_attr相关配置AIO设备依赖于hi_aio_attr设置参数所以需要对其进行初始化赋值这里我借鉴海思例程函数修改海思的初始化函数内部参数即可注意后两个参数是aiao设备号static hi_void sample_audio_ai_aenc_init_param(hi_aio_attr *aio_attr, hi_audio_dev *ai_dev, hi_audio_dev *ao_dev) { aio_attr-sample_rate HI_AUDIO_SAMPLE_RATE_48000; aio_attr-bit_width HI_AUDIO_BIT_WIDTH_16; aio_attr-work_mode HI_AIO_MODE_I2S_MASTER; aio_attr-snd_mode HI_AUDIO_SOUND_MODE_STEREO; aio_attr-expand_flag 0; aio_attr-frame_num SAMPLE_AUDIO_AI_USER_FRAME_DEPTH; aio_attr-point_num_per_frame OT_AACLC_SAMPLES_PER_FRAME; aio_attr-chn_cnt 2; /* 2:chn num */ #ifdef OT_ACODEC_TYPE_INNER *ai_dev SAMPLE_AUDIO_INNER_AI_DEV; *ao_dev SAMPLE_AUDIO_INNER_AO_DEV; aio_attr-clk_share 1; aio_attr-i2s_type HI_AIO_I2STYPE_INNERCODEC; #else *ai_dev SAMPLE_AUDIO_EXTERN_AI_DEV; *ao_dev SAMPLE_AUDIO_EXTERN_AO_DEV; aio_attr-clk_share 1; aio_attr-i2s_type HI_AIO_I2STYPE_EXTERN; #endif }可以看到这里我的配置是48k采样率16bit位宽是使用I2S主模式双声道aio_attr中的frame_num项用于配置AI中用于接收音频数据的缓存的音频帧帧数建议配置为5以上否则可能出现采集丢帧等异常。每帧的采样点数为OT_AACLC_SAMPLES_PER_FRAME定义为1024在这里需要注意一点笔者使用的是内部音频编解码所以要定义OT_ACODEC_TYPE_INNER宏否则很多关于内部配置代码会被注释初始化AI在配置完属性变量之后就可以开始初始化AI调用下面海思封装的函数这里ai_chn_cn要等于上面aio_attr-chn_cnt并且如果不需要vqe需要设置vqe参数无效//不使用vqe ai_vqe_param.out_sample_rate HI_AUDIO_SAMPLE_RATE_BUTT; ai_vqe_param.resample_en HI_FALSE; ai_vqe_param.ai_vqe_attr HI_NULL; ai_vqe_param.ai_vqe_type SAMPLE_AUDIO_VQE_TYPE_NONE;/* start ai */ hi_s32 sample_comm_audio_start_ai(hi_audio_dev ai_dev_id, hi_u32 ai_chn_cnt, hi_aio_attr *aio_attr, const sample_comm_ai_vqe_param *ai_vqe_param, hi_audio_dev ao_dev_id) { hi_s32 i; hi_s32 ret; hi_u32 chn_cnt; ret hi_mpi_ai_set_pub_attr(ai_dev_id, aio_attr); if (ret) { printf(%s: hi_mpi_ai_set_pub_attr(%d) failed with %#x\n, __FUNCTION__, ai_dev_id, ret); return ret; } ret hi_mpi_ai_enable(ai_dev_id); if (ret) { printf(%s: hi_mpi_ai_enable(%d) failed with %#x\n, __FUNCTION__, ai_dev_id, ret); return ret; } chn_cnt ai_chn_cnt ((hi_u32)aio_attr-snd_mode); for (i 0; i (hi_s32)chn_cnt; i) { ret hi_mpi_ai_enable_chn(ai_dev_id, i); if (ret) { printf(%s: hi_mpi_ai_enable_chn(%d,%d) failed with %#x\n, __FUNCTION__, ai_dev_id, i, ret); return ret; } if (ai_vqe_param-resample_en HI_TRUE) { ret hi_mpi_ai_enable_resample(ai_dev_id, i, ai_vqe_param-out_sample_rate); if (ret) { printf(%s: hi_mpi_ai_enable_re_smp(%d,%d) failed with %#x\n, __FUNCTION__, ai_dev_id, i, ret); return ret; } } ret sample_comm_audio_start_ai_vqe(ai_dev_id, i, ai_vqe_param, ao_dev_id); if (ret ! HI_SUCCESS) { return ret; } } return HI_SUCCESS; }配置编码参数完成AI初始化之后就可以开始进行AENC的初始化首先需要配置编码参数/* config codec */ hi_s32 sample_comm_audio_cfg_acodec(const hi_aio_attr *aio_attr) { hi_bool codec_cfg HI_FALSE; #ifdef OT_ACODEC_TYPE_ES8388 hi_s32 ret sample_es8388_cfg_audio(aio_attr-work_mode, aio_attr-sample_rate, aio_attr-bit_width); if (ret ! HI_SUCCESS) { printf(%s: sample_es8388_cfg_audio failed\n, __FUNCTION__); return ret; } codec_cfg HI_TRUE; #endif #ifdef OT_ACODEC_TYPE_INNER /* INNER AUDIO CODEC */ hi_s32 ret sample_inner_codec_cfg_audio(aio_attr-sample_rate); if (ret ! HI_SUCCESS) { printf(%s:sample_inner_codec_cfg_audio failed\n, __FUNCTION__); return ret; } codec_cfg HI_TRUE; #endif if (codec_cfg HI_FALSE) { printf(can not find the right codec.\n); return HI_FAILURE; } return HI_SUCCESS; }使能AENC在使能AENC之前需要先设置AENC通道计数aenc_chn_cnt aio_attr.chn_cnt ((hi_u32)aio_attr.snd_mode);这是海思的设计STEREO 模式下左右声道合并为一个 aenc 通道处理chn_cnt snd_mode这个右移运算正是为此设计的MONO0不移位STEREO1右移1位÷2。然后调用AENC初始化函数/* start aenc */ hi_s32 sample_comm_audio_start_aenc(hi_u32 aenc_chn_cnt, const hi_aio_attr *aio_attr, hi_payload_type type) { hi_aenc_chn ae_chn; hi_s32 ret, i, j; hi_aenc_chn_attr aenc_attr; hi_aenc_attr_adpcm adpcm_aenc; hi_aenc_attr_g711 aenc_g711; hi_aenc_attr_g726 aenc_g726; hi_aenc_attr_lpcm aenc_lpcm; hi_aenc_attr_aac aenc_aac; hi_aenc_attr_mp3 aenc_mp3; hi_aenc_attr_opus aenc_opus; /* set AENC chn attr */ aenc_attr.type type; aenc_attr.buf_size 30; /* 30:size */ aenc_attr.point_num_per_frame aio_attr-point_num_per_frame; if (aenc_attr.type HI_PT_ADPCMA) { aenc_attr.value adpcm_aenc; adpcm_aenc.adpcm_type AUDIO_ADPCM_TYPE; } else if ((aenc_attr.type HI_PT_G711A) || (aenc_attr.type HI_PT_G711U)) { aenc_attr.value aenc_g711; } else if (aenc_attr.type HI_PT_G726) { aenc_attr.value aenc_g726; aenc_g726.g726bps G726_BPS; } else if (aenc_attr.type HI_PT_LPCM) { aenc_attr.value aenc_lpcm; } else if (aenc_attr.type HI_PT_AAC) { aenc_attr.value aenc_aac; aenc_attr_aac_get_param(aio_attr, aenc_aac); } else if (aenc_attr.type HI_PT_MP3) { aenc_attr.value aenc_mp3; aenc_attr_mp3_get_param(aio_attr, aenc_mp3); } else if (aenc_attr.type HI_PT_OPUS) { aenc_attr.value aenc_opus; aenc_attr_opus_get_param(aio_attr, aenc_opus); } else { printf(%s: invalid aenc payload type:%d\n, __FUNCTION__, aenc_attr.type); return HI_FAILURE; } for (i 0; i (hi_s32)aenc_chn_cnt; i) { ae_chn i; /* create aenc chn */ ret hi_mpi_aenc_create_chn(ae_chn, aenc_attr); if (ret ! HI_SUCCESS) { printf(%s: hi_mpi_aenc_create_chn(%d) failed with %#x!\n, __FUNCTION__, ae_chn, ret); for (j 0; j (hi_s32)ae_chn; j) { (hi_void)hi_mpi_aenc_destroy_chn((hi_aenc_chn)j); } return ret; } } return HI_SUCCESS; }绑定AI和AENC这里可以选择绑定AI和AENC通道这样AI数据就会硬件自动处理到达AENC当然也可以选择手动获取AI数据然后发送给AENC这里我选择第一种static hi_s32 sample_audio_aenc_bind_ai(hi_audio_dev ai_dev, hi_u32 aenc_chn_cnt) { hi_s32 ret; hi_u32 i, j; hi_ai_chn ai_chn; hi_aenc_chn ae_chn; for (i 0; i aenc_chn_cnt; i) { ae_chn i; ai_chn i; ret sample_comm_audio_aenc_bind_ai(ai_dev, ai_chn, ae_chn); if (ret ! HI_SUCCESS) { sample_print(sample_comm_audio_aenc_bind_ai failed,ret:%d\r\n,ret); goto aenc_bind_err; } printf(ai(%d,%d) bind to aenc_chn:%d ok!\n, ai_dev, ai_chn, ae_chn); } return HI_SUCCESS; aenc_bind_err: for (j 0; j i; j) { ae_chn j; ai_chn j; sample_comm_audio_aenc_unbind_ai(ai_dev, ai_chn, ae_chn); } return HI_FAILURE; }综上音频流的初始化就完成了接下来就是获取数据查看当前状态为了代码健壮性可以查看当前状态使用ioctl接口参数是HI_ACODEC_QUERY_STATUSfd_acodec open(ACODEC_FILE, O_RDWR); if (fd_acodec 0) { printf(%s: cant open audio codec,%s\n, __FUNCTION__, ACODEC_FILE); goto EXIT_AI_AENC; } //检查状态不要也可以但是为了健壮性还是加上也懒得删了 td_u32 status; ret ioctl(fd_acodec, HI_ACODEC_QUERY_STATUS, status); if(ret ! HI_SUCCESS) { sample_print(HI_ACODEC_QUERY_STATUS failed\r\n); goto EXIT_AI_AENC; } printf(acodec status is %d\r\n,status);//为1则表示acodec初始化成功加载acodec驱动后需要一段时间进行初始化Normal模式下为100msFast模式下为10msNice模式下为300ms。该接口的作用是查询ACODEC的当前状态。在未完成初始化时其获取到的状态是0即未使能在已完成初始化时其获取 到的状态是1即已使能。设置输入音量在实际使用时发现编码后的音频音量较小于是笔者在获取音频流前先去调节音量可以调节输入和输出音量这里我只选择调节输入调用HI_ACODEC_SET_INPUT_VOLUME输入总音量控制。此接口依据优先调整模拟增益的原则把用户期望设置的总增益分解成模拟增益和数字增益分别配置到模拟增益控制寄存器和数字增益控制寄存器。Hi3519DV500/Hi3516CV610输入音量范围为[-78, 80]包括模拟增益和数字增益赋值越大音量越大。赋值为80时音量最大为80dB赋值为-78时音量最小为静音。调节时左右声道一起生效。建议调节范围限制为[0, 50]中的偶数值此范围只调节模拟增益这样引入的噪声最小能够更好的保证声音质量。当使用了RecordVQE的HDR功能且HDR功能配置为自动模式时HDR算法会根 据输入音量的大小动态修改acodec的模拟增益。//设置输入音量默认音量只有6声音不大 ret ioctl(fd_acodec, HI_ACODEC_SET_INPUT_VOLUME, set_volume); if(ret ! HI_SUCCESS) { sample_print(HI_ACODEC_SET_INPUT_VOLUME failed\r\n); goto EXIT_AI_AENC; } //查看设置输入音量成功没有 hi_s32 get_volume; ret ioctl(fd_acodec, HI_ACODEC_GET_INPUT_VOLUME, get_volume); if(ret ! HI_SUCCESS) { sample_print(HI_ACODEC_GET_INPUT_VOLUME failed\r\n); goto EXIT_AI_AENC; } printf(volume is %d\r\n,get_volume); if(status ! 1 || get_volume ! set_volume) { goto EXIT_AI_AENC; }启动音频流获取线程在做完上述配置后就可以获取音频流了这里我单独创建一个线程去获取static hi_s32 sample_aenc_normal_start_encode(hi_void) { hi_s32 ret 0; ret pthread_create(tid_aenc, NULL, get_audio, NULL); if (ret 0) { sample_print(create get_audio thread failed !!! ret %d\n, ret); return HI_FAILURE; } // 启动 Qt 事件循环 printf(Starting Qt event loop in main thread...\n); qt_ui_run(); // 阻塞在这里直到收到 CTRLC printf(Qt event loop exited\n); pthread_join(tid_aenc, NULL); return HI_SUCCESS; }通过select机制去等待获取通道的音频数据这里只设置一个fd因为上述说的左右通道合并在一块了音频流的获取是使用接口:hi_mpi_aenc_get_streamhi_s32 hi_mpi_aenc_get_stream(hi_aenc_chn aenc_chn, hi_audio_stream *stream, hi_s32 milli_sec);aenc_chn:通道号。取值范围[0, OT_AENC_MAX_CHN_NUM)stream:获取的音频码流milli_sec:获取数据的超时时间-1表示阻塞模式无数据时一直等待0表示非阻塞模式无数据时则报错返回大于0表示阻塞milli_sec毫秒超时则报错返回。必须创建通道后才可能获取码流否则直接返回失败如果在获取码流过程中销毁通道则会立刻返回失败。milli_sec的值必须大于等于-1等于-1时采用阻塞模式获取数据等于0时采用非阻塞模式获取数据大于0时阻塞milli_sec毫秒后没有数据则返回超时并报错。直接获取AI原始音频数据的方法: 创建一路AENC通道编码协议类型设置为OT_PT_LPCM绑定AI通道后从此 AENC通道获取的音频数据即AI原始数据。获取码流后就可以进行自定义操作这里我把他保存为文件/* save audio stream to file */ (hi_void)fwrite(stream.stream, 1, stream.len, output_audio_fd); (hi_void)fflush(output_audio_fd);在使用完后要释放流hi_s32 hi_mpi_aenc_release_stream(hi_aenc_chn aenc_chn, const hi_audio_stream *stream);