音频
概述
音频框架是一个完整的音频架构,旨在为应用程序提供不同层次的音频接口。这些接口涉及音频硬件抽象层(Audio HAL)和音频框架(Audio Framework)。
音频硬件抽象层:定义统一的音频硬件接口。它与音频驱动交互以进行音频流或音频设置。
音频框架:提供音频流、音量和其他设置的接口。
音频框架提供两种架构以满足不同的音频需求。不同的架构有不同的实现,但接口是相同的。
音频混音架构:支持音频录制和音频播放。对于音频播放,该架构提供音频混音功能,并将格式、采样率和声道转换为统一格式以进行混音。
音频直通架构:支持音频录制和音频播放。该架构没有音频混音功能,同一时刻只允许一个音频播放。
音频混音器概述
音频接口和整体实现如下所示。
音频混音架构
整个音频混音架构包括以下子模块:
应用接口
RTAudioTrack 提供播放音频流接口。
RTAudioRecord 提供录音接口。
RTAudioControl 提供控制接口,包括音量,静音控制等。
音频框架接口
音频框架为音频流播放提供音频格式转换、重采样、音量和混音功能。
音频硬件抽象层接口
AudioHwManager 管理声卡驱动程序的接口,使得用户获取各个声卡信息,并根据需求打开相应的声卡。
AudioHwCard 提供管理声卡功能的接口,包括初始化端口、创建输出流和输入流。
AudioHwControl 提供接口给RTAudioControl使用,并向音频驱动程序发送音频设置命令。
AudioHwStreamOut 从上层获取数据,并将数据流传递给音频驱动程序。
AudioHwStreamIn 从音频驱动程序录音数据,并将数据传送到上层接口。
音频驱动
AUDIO_SP 提供接口,配置音频SPORT。
AUDIO_CODEC 提供接口,配置音频CODEC。
音频直通概述
音频接口和整个实现如下所示。
音频直通架构
与音频混音器架构相比,音频直通架构没有音频框架层,其他部分几乎相同。
架构选择
以上部分描述了两种音频架构:混音器和直通。用户可以根据项目要求选择合适的架构。以下是这两种架构的对比:
架构 |
内存占用情况 |
代码大小 |
播放功能 |
录音功能 |
||
---|---|---|---|---|---|---|
基础功能 |
混音功能 |
播放延迟 |
||||
混音架构 |
相对较多 |
相对较多 |
相近 |
支持 |
相对较大 |
相同 |
直通架构 |
相对较少 |
相对较少 |
相近 |
不支持 |
相对较小 |
相同 |
在录音实现方面,这两种架构是相同的。两种架构都可以满足用户的录音功能需求。
在播放实现方面,这两种架构是不同的。如果用户需要同时播放两个声音,应选择混音器架构,因为只有混音器架构可以进行混音。
混音器架构占用更多的内存,并且代码体积更大。如果用户希望节省内存和代码体积,并且没有音频混音的需求,应选择直通架构。
音频术语
在本章中,一些常用音频术语的含义列出如下。
术语 |
简介 |
---|---|
PCM |
脉冲编码调制(Pulse Code Modulation,PCM)是一种音频信号的数字表示方法。 |
声道 |
声道是指在不同位置录音或播放的独立音频信号,因此声道的数量就是声源的数量。 |
mono |
单声道。 |
stereo |
双声道。 |
bit depth |
位深表示在音频信号处理过程中使用的有效位数。 采样深度表示声音的分辨率。值越大,分辨率越高。 |
采样点 |
音频采样点是指从连续的模拟音频信号中,按照固定的时间间隔取样得到的离散数据点。 |
采样率 |
音频采样率指的是每秒对信号进行采样的帧数。采样率越高,音频品质越好。 |
帧 |
帧是一个声音单元,其长度是样本长度乘以通道数量。 |
增益 |
音频信号增益控制用于调整信号幅度。 |
交错的 |
这是一种音频数据的录制方法。在交错模式下,数据以连续的方式存储,首先存储第一帧的所有通道样本,然后存储第二帧的数据。 |
延迟 |
信号通过整个系统时的时间延迟。 |
overrun |
缓冲区过满,使得缓冲区生产者无法写入更多数据。 |
underrun |
缓冲区生产者写入数据的速度太慢,导致当消费者想要消费数据时缓冲区是空的。 |
xrun |
overrun 或者 underrun。 |
音量 |
声音强度和响度。 |
硬件音量 |
音频codec的音量。 |
软件音量 |
软件算法中的音量。 |
重采样 |
音频采样率转换。 |
格式转换 |
音频位深转换。 |
混音 |
将多个音频流混音在一起。用户可以听到多个音频流同时播放。 |
音频codec |
芯片内部的数模转换器(DAC)和模数转换器(ADC)控制器。 |
音频格式
本节描述了音频框架和音频硬件抽象层(HAL)支持的数据格式。音频框架和音频HAL的共同部分将在这里进行描述,而不同的部分将在各自的章节中进行描述。
音频框架和音频硬件抽象层(HAL)都支持交错的流数据。
两声道交错数据示例如下: 两声道交错,四声道交错数据的示例如下: 四声道交错。
两声道交错
四声道交错
音频架构
播放架构
音频混音器播放架构的框图如下所示。
播放混音架构
音频直通播放架构的框图如下所示。
播放直通架构
音频播放架构包括以下子模块:
音频框架层 (仅针对混音架构)
音频框架负责音频播放声音混音。
在混音之前,所有声音将被转换为一种统一的音频格式,默认是16位,44100Hz,2声道。音频框架中还有音量模块,用于调整不同音频类型的音量,例如,音乐和语音可能有不同的音量。
音频框架支持从8k到96k的采样率,单声道/立体声,格式包括8位、16位、24位和32位浮点。
硬件抽象层
音频HAL从音频框架获取播放数据,并将数据发送到音频驱动程序。
音频驱动
音频驱动程序从音频HAL获取播放数据,并将数据发送到音频硬件。
录音架构
音频混音和直通架构具有相同的录音架构。音频录制的框图如下所示。
录音架构
音频录制架构包含以下子模块:
RTAudioRecord: 从音频HAL录音,并将数据提供给需要录制数据的音频应用程序。
Audio HAL: 从音频驱动程序获取录制数据,并将数据发送到 RTAudioRecord。
Audio Driver: 从音频硬件获取录制数据,并将数据发送到音频HAL。
控制架构
音频混音和直通具有相同的控制架构。音频控制的框图如下所示。
控制架构
音频控制架构包括以下子模块:
RTAudioControl: 由应用程序调用,并与HAL交互进行音频控制设置。
Audio HAL: 通过调用驱动程序API,进行音频控制设置。
Audio Driver: 控制音频CODEC硬件。
RTAudioControl 提供用于设置和获取硬件音量的接口。
#include "audio/audio_control.h"
int32_t RTAudioControl_SetHardwareVolume(float left_volume, float right_volume)
用户将左右声道的音量设置为0.0到1.0,这一设定线性映射到-65.625到最大分贝。
音频配置
菜单配置
如果用户想要使用音频接口,请选择下列音频配置,并根据该章节选择合适的音频架构: 架构选择。
音频配置菜单
框架配置
音频配置文件在: {SDK}/component/audio/configs/ameba_audio_mixer_usrcfg.cpp
。
如果用户想要更改音频HAL周期缓冲区大小,或音频混音器的缓冲策略,根据.h的描述 {SDK}/component/audio/configs/include/ameba_audio_mixer_usrcfg.h
,修改 kPrimaryAudioConfig 。
配置 out_min_frames_stage 仅支持 RTAUDIO_OUT_MIN_FRAMES_STAGE1 和 RTAUDIO_OUT_MIN_ FRAMES_STAGE2。
RTAUDIO_OUT_MIN_FRAMES_STAGE1 意味着单次有更多的数据写入音频HAL。
RTAUDIO_OUT_MIN_FRAMES_STAGE2 意味着单次有较少的数据写入音频HAL。
RTAUDIO_OUT_MIN_FRAMES_STAGE2 使用该配置可以减少音频框架层的延迟,但是可能会因为用户送数据速度跟不上,更容易xrun引入noise. 用户可根据实验结果选择适合的配置。
HAL配置
硬件配置文件在: {SDK}/component/soc/amebaxx/usrcfg/include/ameba_audio_hw_usrcfg.h
。
不同的板子有不同的配置。例如,有些板子需要使用放大器,而有些则不需要。不同的板子可能使用不同的引脚来启用放大器;不同放大器的启动时间也有所不同。此外,每个板子使用的数字麦克风 (DMIC) 引脚可能不同,数字麦克风的稳定时间也可能不同。所有这些信息都需要在配置文件中进行配置。
文件 ameba_audio_hw_usrcfg.h
当中有各个配置的描述,请根据描述进行配置。
音频接口
音频组件提供了三层接口。
接口层级 |
介绍 |
---|---|
音频驱动接口 |
提供音频硬件接口。 |
音频硬件抽象层接口 |
隔离操作系统和硬件设备,使得应用程序可以通过统一的接口访问音频硬件资源。 |
音频框架层接口 |
用户层接口,用来播放音频流,录制音频流,控制音频音量等。 |
接口层如下所示。
音频接口
驱动接口
SPORT接口,请参考:
{SDK}/component/soc/amebadplus/fwlib/include/ameba_sport.h
。CODEC接口,请参考:
{SDK}/component/soc/amebadplus/fwlib/include/ameba_audio.h
。
I2S PLL APIs
有两种方式来生成I2S PLL:
如果系统时钟是98.304M或者45.1584M的整数倍,我们可以在*SocClk_Info*数组中添加时钟,然后在
bootloader_km4.c
修改*SocClk_Info*数组中的索引. 如果您想要高品质的音频,您需要选择这种做法。如果系统时钟不是98.304M或者45.1584M的整数倍,在这种情况下,我们可以自动获取98.304M或者45.1584M。
具体细节如图所示。
I2S PLL接口使用流程
SPORT接口,请参考:
{SDK}/component/soc/amebalite/fwlib/include/ameba_sport.h
.CODEC接口,请参考:
{SDK}/component/soc/amebalite/fwlib/include/ameba_audio.h
.
SPORT接口,请参考:
{SDK}/component/soc/amebalite/fwlib/include/ameba_sport.h
.CODEC接口,请参考:
{SDK}/component/soc/amebalite/fwlib/include/ameba_audio.h
.
SPORT接口,请参考:
{SDK}/component/soc/amebasmart/fwlib/include/ameba_sport.h
.CODEC接口,请参考:
{SDK}/component/soc/amebasmart/fwlib/include/ameba_audio.h
.
HAL接口
音频HAL提供AudioHwStreamOut/AudioHwStreamIn/AudioHwControl接口来控制音频硬件。接口在于: {SDK}/component/audio/interfaces/hardware/audio
。
这些接口中包含具体描述,使用前请阅读。
AudioHwStreamOut: 从上层接收PCM数据,通过音频驱动写入数据以将PCM数据发送到硬件,并提供关于音频输出硬件驱动的信息。
AudioHwStreamIn: 通过音频驱动接收PCM数据并发送到上层。
AudioHwControl: 接收来自上层的控制调用,并将控制信息设置给驱动程序。
AudioHwStreamOut/AudioHwStreamIn由AudioHwCard接口管理. 他们负责创建/销毁 AudioHwStreamOut/AudioHwStreamIn实例。 AudioHwCard是用于处理音频流的声卡。它包含一组端口和设备,具体如图所示。
Port – 声卡的输入/输出流称作:port。
Device – 声卡的输入/输出设备称作:device。
AudioHwCard 示例
AudioHwManager管理系统中所有的AudioHwCard,并根据给定的音频卡描述符打开指定的声卡驱动。
使用 AudioHwStreamOut
用户可以查看AudioHwStreamOut的示例,详情请见 {SDK}/component/example/audio/audio_hal_render
。
以下描述了如何使用音频HAL接口播放音频原始数据(PCM格式):
使用
CreateAudioHwManager()
获取AudioHwManager句柄:struct AudioHwManager *audio_manager = CreateAudioHwManager();
使用
GetCards()
获取声卡描述符:int32_t cards_size = audio_manager->GetCardsCount(audio_manager); struct AudioHwCardDescriptor *card_descs = audio_manager->GetCards(audio_manager);
选择一个特定的声卡来播放音频(当前音频管理器仅支持主音频卡):
struct AudioHwCardDescriptor *audio_card_desc; for (int32_t index = 0; index < cards_size; index++) { struct AudioHwCardDescriptor *desc = &card_descs[index]; for (uint32_t port = 0; (desc != NULL && port < desc->port_num); port++) { printf("check for audio port \n"); if (desc->ports[port].role == AUDIO_HW_PORT_ROLE_OUT && (audio_card = audio_manager->OpenCard(audio_manager, desc))) { audio_port = desc->ports[port]; audio_card_desc = desc; break; } } }
根据采样率,声道,格式,和AudioHwPathDescriptor,创建AudioHwConfig,然后使用
CreateStreamOut()
来基于选中的声卡创建AudioHwStreamOut:struct AudioHwConfig audio_config; audio_config.sample_rate = 48000; audio_config.channel_count = 2; audio_config.format = AUDIO_HW_FORMAT_PCM_16_BIT; struct AudioHwPathDescriptor path_desc; path_desc.port_index = audio_port.port_index; path_desc.devices = AUDIO_HW_DEVICE_OUT_SPEAKER; path_desc.flags = AUDIO_HW_INPUT_FLAG_NONE; audio_stream_out = audio_card->CreateStreamOut(audio_card, &path_desc, &audio_config);
重复写入PCM数据到AudioHwStreamOut。用户可以自定义写入的size大小。用户需要确保 size/frame_size 是整数。
int32_t bytes = audio_stream_out->Write(audio_stream_out, buffer, size, true);
使用
DestroyStreamOut()
来关闭AudioHwStreamOut,以结束播放:audio_card->DestroyStreamOut(audio_card, audio_stream_out);
使用
CloseCard()
来销毁AudioHwCard,并使用DestoryAudioHwManager来释放AudioHwManager句柄:audio_manager->CloseCard(audio_manager, audio_card, audio_card_desc); DestoryAudioHwManager(audio_manager);
使用 AudioHwStreamIn
用户可以在如下目录找到AudioHwStreamOut的使用范例: {SDK}/component/example/audio/audio_hal_capture
。
以下是关于如何使用音频HAL接口录制音频原始数据的描述:
使用
CreateAudioHwManager()
来获取AudioHwManager实例:struct AudioHwManager *audio_manager = CreateAudioHwManager();
使用
GetCards()
来获取所有声卡描述符:int32_t cards_size = audio_manager->GetCardsCount(audio_manager); struct AudioHwCardDescriptor *card_descs = audio_manager->GetCards(audio_manager);
选择特定的录音卡进行录音(当前音频管理器仅支持主音频卡):
struct AudioHwCardDescriptor *audio_card_in_desc = NULL; for (int32_t index = 0; index < cards_size; index++) { struct AudioHwCardDescriptor *desc = &card_descs[index]; for (uint32_t port = 0; (desc != NULL && port < desc->port_num); port++) { if (desc->ports[port].role == AUDIO_HW_PORT_ROLE_IN && (audio_card_in = audio_manager->OpenCard(audio_manager, desc))) { audio_port_in = desc->ports[port]; audio_card_in_desc = desc; break; } } }
根据采样率、通道、格式和AudioHwPathDescriptor创建AudioHwConfig。然后使用
CreateStreamIn()
来基于声卡创建AudioHwStreamIn:struct AudioHwConfig audio_config; audio_config.sample_rate = 48000; audio_config.channel_count = 2; audio_config.format = AUDIO_HW_FORMAT_PCM_16_BIT; struct AudioHwPathDescriptor path_desc_in; path_desc_in.port_index = audio_port_in.port_index; path_desc_in.devices = AUDIO_HW_DEVICE_IN_MIC; path_desc_in.flags = AUDIO_HW_INPUT_FLAG_NONE; audio_stream_in = audio_card_in->CreateStreamIn(audio_card_in, &path_desc_in, &audio_config);
重复从AudioHwStreamIn中读取PCM数据。此大小可以由用户定义。用户需要确保 size/frame_size 是整数。
audio_stream_in->Read(audio_stream_in, buffer, size);
使用
DestroyStreamIn()
来关闭AudioHwStreamIn,以结束录音:audio_card_in->DestroyStreamIn(audio_card_in, audio_stream_in);
使用
CloseCard()
来释放AudioHwCard,并调用函数DestoryAudioHwManager()
释放AudioHwManager句柄。audio_manager->CloseCard(audio_manager, audio_card_in, audio_card_in_desc); DestoryAudioHwManager(audio_manager);
使用 AudioHwControl
以下是一个示例,展示了如何使用音频 HAL 接口来控制音频CODEC:
AudioHwControl 始终是线程安全的,调用非常方便。要使用 AudioHwControl,函数调用的第一个参数应该始终是 GetAudioHwControl()
。
以PLL clock设定为例:
GetAudioHwControl()->AdjustPLLClock(GetAudioHwControl(), rate, ppm, action);
音频框架接口
音频流接口
音频流接口包括 RTAudioTrack 和 RTAudioRecord 接口。这些接口位于: {SDK}/component/audio/interfaces/audio
. 这些接口中有具体的说明,请在使用前阅读。
RTAudioTrack: 初始化框架中播放数据流的格式,从应用程序接收PCM数据,并将数据写入音频框架(混音器)或音频HAL(直通)。
RTAudioRecord: 初始化框架中录制数据流的格式,从音频HAL接收PCM数据,并将数据发送到应用程序。
使用 RTAudioTrack
RTAudioTrack RTAudioTrack 支持播放多种常见的音频原始格式类型,以便音频可以轻松集成到应用程序中。
音频框架具有以下音频播放流类型。应用程序可以使用这些类型来初始化 RTAudioTrack。框架获取流类型并根据这些类型进行音量混合。
RTAUDIO_CATEGORY_MEDIA - 如果应用程序想要播放音乐,那么其类型为 RTAUDIO_CATEGORY_MEDIA,可以使用此类型来初始化RTAudioTrack. 音频框架会识别它的类型,并将其与媒体音量混合。
RTAUDIO_CATEGORY_COMMUNICATION - 如果应用程序想要进行电话通话并输出通话声音,声音的类型应为 RTAUDIO_CATEGORY_COMMUNICATION。
RTAUDIO_CATEGORY_SPEECH - 输出语音声音。
RTAUDIO_CATEGORY_BEEP - 如果声音是按键音或其他哔声,则其类型为**RTAUDIO_CATEGORY_BEEP**。
RTAudioTrack的测试demo在:{SDK}/component/example/audio/audio_track
。
以下是一个演示如何播放音频原始数据的示例:
在使用RTAudioTrack之前,RTAudioService需要先进行初始化:
RTAudioService_Init();
创建RTAudioTrack来播放音频:
struct RTAudioTrack* audio_track = RTAudioTrack_Create();
应用程序可以使用音频配置 API 提供有关特定音频播放源的详细音频信息,包括流类型(播放源类型)、格式、声道数、采样率和 RTAudioTrack环形缓冲区大小。语法如下:
typedef struct { uint32_t category_type; uint32_t sample_rate; uint32_t channel_count; uint32_t format; uint32_t buffer_bytes; } RTAudioTrackConfig;
这里:
- category_type:
定义播放数据源的流类型。
- sample_rate:
播放源原始数据的采样率。
- channel_count:
播放源原始数据的声道数。
- format:
播放源原始数据的位深。
- buffer_bytes:
RTAudioTrack的环形缓冲区大小,以避免xrun。
备注
RTAudioTrackConfig 中的 buffer_bytes 非常重要。缓冲区大小应始终大于音频框架计算出的最小缓冲区大小,否则将会发生溢出。
使用该接口获取最小的 RTAudioTrack 缓冲区字节数,并以此作为参考来定义 RTAudioTrack 的缓冲区大小:
例如,您可以将最小缓冲区大小*4作为缓冲区大小。 您使用的缓冲区越大,播放就会越流畅,但可能会导致较高的延迟。缓冲区大小由您自行决定。
int track_buf_size = RTAudioTrack_GetMinBufferBytes(audio_track, type, rate, format, channels) * 4;
使用此缓冲区大小和其他音频参数来创建 RTAudioTrackConfig 对象,以下是一个示例:
RTAudioTrackConfig track_config; track_config.category_type = RTAUDIO_CATEGORY_MEDIA; track_config.sample_rate = rate; track_config.format = format; track_config.buffer_bytes = track_buf_size; track_config.channel_count = channel_count;
通过 RTAudioTrackConfig 对象,我们可以初始化 RTAudioTrack。在此步骤中,将根据缓冲区字节数创建一个环形缓冲区。
RTAudioTrack_Init(audio_track, &track_config);
当所有准备工作完成后,启动 RTAudioTrack 并检查是否成功启动。
if(RTAudioTrack_Start(audio_track) != 0){ //track start fail return; }
RTAudioTrack 的默认音量是最大值 1.0,您可以使用以下 API 调整音量。
RTAudioTrack_SetVolume(audio_track, 1.0, 1.0);
备注
在混音器架构中,此 API 设置当前 audio_track 的软件音量。
在直通架构中,此 API 不支持。
将音频数据写入框架。用户可以自定义write_size,但需要确保 write_size/frame_size 为整数。
RTAudioTrack_Write(audio_track, buffer, write_size, true);
如果用户想要暂停并停止写入数据,可以调用以下 API 来通知框架执行暂停操作:
RTAudioTrack_Pause(audio_track); RTAudioTrack_Flush(audio_track);
如果用户想要停止播放音频,可以停止写入数据,然后调用API
RTAudioTrack_Stop()
。RTAudioTrack_Stop(audio_track);
在audio_track指针无用时将其删除。
RTAudioTrack_Destroy(audio_track);
使用 RTAudioRecord
RTAudioRecord 支持多种常见的音频原始格式类型,因此您可以轻松地将录音集成到应用程序中。
RTAudioRecord 支持以下音频输入源:
RTDEVICE_IN_MIC - 如果应用程序想要从麦克风录音,请选择此输入源。
RTDEVICE_IN_I2S - 如果应用程序想要从I2S录音,请选择此输入源。
RTAudioRecord测试范例在: {SDK}/component/example/audio/audio_record
。
以下是一个展示如何录制音频原始数据的示例:
创建RTAudioRecord
struct RTAudioRecord *audio_record = RTAudioRecord_Create();
应用程序可以使用音频配置API来提供有关特定音频记录源的详细音频信息,包括记录设备源、格式、声道数量和采样率。
语法如下:
typedef struct { uint32_t sample_rate; uint32_t channel_count; uint32_t format; uint32_t device; uint32_t buffer_bytes; } RTAudioRecordConfig;
这里
- sample_rate:
原始数据的采样率。
- channel_count:
原始数据的声道数。
- format:
原始数据的位深。
- device:
音频输入设备。
- buffer_bytes:
框架中的音频缓冲字节数。设置为0使用默认值。用户也可以设置其他值,较大的 buffer_bytes 意味着较大的延迟。
以下是一个展示如何创建 RTAudioRecord 的 RTAudioRecordConfig 对象的示例:
RTAudioRecordConfig record_config; record_config.sample_rate = rate; record_config.format = RTAUDIO_FORMAT_PCM_16_BIT; record_config.channel_count = channels; record_config.device = RTDEVICE_IN_MIC; record_config.buffer_bytes = 0;
创建 RTAudioRecordConfig 对象后,初始化 RTAudioRecord。在此步骤中,将根据音频输入设备源打开音频HAL的AudioHwCard:
RTAudioRecord_Init(audio_record, &record_config);
当所有准备工作完成后,开始音频录制:
RTAudioRecord_Start(audio_record);
读取音频麦克风数据。读取的大小可以由用户定义。用户需要确保 size/frame_size 是整数。
RTAudioRecord_Read(audio_record, buffer, size, true);
当录音结束时,停止录音:
RTAudioRecord_Stop(audio_record);
当 audio_record 不再使用时,销毁它以避免内存泄漏:
RTAudioRecord_Destroy(audio_record);
音频控制接口
音频控制接口包括 RTAudioControl 接口,用于与音频控制 HAL 交互。
RTAudioControl 提供了用于设置和获取硬件音量、设置输出设备等的接口。这些接口位于 SDK/component/audio/interfaces/audio/audio_control.h
这些接口有具体的描述,在使用前请阅读。
使用 RTAudioControl
要使用 RTAudioControl,请调用RTAudioControl设置DAC的音频硬件音量:
RTAudioControl_SetHardwareVolume(0.5, 0.5);
对于播放和录音情况,大多数 RTAudioControl API 可以随时随地调用,它们可以直接工作。 只有 RTAudioControl_SetPlaybackDevice()
混音架构中需要在函数 RTAudioService_Init()
之前调用,直通架构需要在 RTAudioTrack_Start()
函数之前调用。