HarmonyOS 音视频之音频采集实战
背景
应用开发过程中很多场景都有音频采集需求,比如聊天功能的发送语音功能,实时语音转文本功能,实时语音通话,实时视频通话等。在Android和iOS端,系统提供了两种形式:
- 实时音频流采集
- 音频文件录制
系统还提供了不同形式的API,比如Android:
- AudioRecorder Java接口
- MediaRecorder Java接口
- OpenSLES C++接口
- AAudio C++接口
在鸿蒙化适配的过程中也有音频采集的需求,本文我们一步一步实现音频采集功能。
音频录制接口介绍
HarmonyOS 提供了TS与C++两种音频采集接口:
- AudioCapture
- OHAudio
分别介绍这两种语言的API。
AudioCapture
使用AudioCapturer录制音频涉及到AudioCapturer实例的创建、音频采集参数的配置、采集的开始与停止、资源的释放等,下面官方给出的状态示意图将方法和状态切换标记的很清晰:
createAudioCapture
创建capture主要涉及到参数配置:
import { audio } from '@kit.AudioKit';
let audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
channels: audio.AudioChannel.CHANNEL_2, // 通道
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
};
let audioCapturerInfo: audio.AudioCapturerInfo = {
source: audio.SourceType.SOURCE_TYPE_MIC,
capturerFlags: 0
};
let audioCapturerOptions: audio.AudioCapturerOptions = {
streamInfo: audioStreamInfo,
capturerInfo: audioCapturerInfo
};
audio.createAudioCapturer(audioCapturerOptions, (err, data) => {
if (err) {
} else {
let audioCapturer = data;
}
});
参数包含两大块:
- AudioStreamInfo:音频格式配置信息
- samplingRate:采样率
- channels:声道数
- sampleFormat:采样格式
- encodingType:音频编码类型,目前只支持PCM的ENCODING_TYPE_RAW配置
- AudioCapturerInfo:采集配置信息
- source:音源类型,包含:
- SOURCE_TYPE_INVALID:无效的音频源
- SOURCE_TYPE_MIC:Mic音频源
- SOURCE_TYPE_VOICE_RECOGNITION:语音识别源
- SOURCE_TYPE_PLAYBACK_CAPTURE:播放音频流(内录)录制音频源
- SOURCE_TYPE_VOICE_COMMUNICATION:语音通话场景的音频源
- SOURCE_TYPE_VOICE_MESSAGE:短语音消息的音频源
- capturerFlags:音频采集器标志,0代表音频采集器
- source:音源类型,包含:
on(‘readData’)
on(‘readData’)方法用来订阅监听音频数据读入回调:
let readDataCallback = (buffer: ArrayBuffer) => {
//处理音频流
}
audioCapturer.on('readData', readDataCallback);
start
start方法用来开始录制:
import { BusinessError } from '@kit.BasicServicesKit';
audioCapturer.start((err: BusinessError) => {
if (err) {
} else {
}
});
stop
stop用来停止录制:
import { BusinessError } from '@kit.BasicServicesKit';
audioCapturer.stop((err: BusinessError) => {
if (err) {
} else {
}
});
release
release销毁实例,释放资源
import { BusinessError } from '@kit.BasicServicesKit';
audioCapturer.release((err: BusinessError) => {
if (err) {
} else {
}
});
OHAudio
OHAudio是系统在API version 10中引入的一套C API,此API在设计上实现归一,同时支持普通音频通路和低时延通路。仅支持PCM格式,适用于依赖Native层实现音频输入功能的场景。很多音频编码库都是C/C++实现的,在迁移到鸿蒙平台后,采集侧也使用OHAudio C++接口,可以减少数据在TS层与C++层传递的消耗,提高效率。
OHAudio依赖libohaudio.so动态库,通过引入<native_audiostreambuilder.h>
和<native_audiocapturer.h)>头文件,使用音频录制相关API。
创建构造器
OH_AudioStreamBuilder* builder;
OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_CAPTURER);
配置音频流参数
可参考如下示例:
// 设置音频采样率
OH_AudioStreamBuilder_SetSamplingRate(builder, 48000);
// 设置音频声道
OH_AudioStreamBuilder_SetChannelCount(builder, 2);
// 设置音频采样格式
OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE);
// 设置音频流的编码类型
OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_RAW);
// 设置输入音频流的工作场景
OH_AudioStreamBuilder_SetCapturerInfo(builder, AUDIOSTREAM_SOURCE_TYPE_MIC);
参数作用于AudioCapture类似。
设置音频回调函数
// 自定义写入数据函数
int32_t MyOnReadData(
OH_AudioCapturer* capturer,
void* userData,
void* buffer,
int32_t length)
{
// 从buffer中取出length长度的录音数据
return 0;
}
// 自定义音频流事件函数
int32_t MyOnStreamEvent(
OH_AudioCapturer* capturer,
void* userData,
OH_AudioStream_Event event)
{
// 根据event表示的音频流事件信息,更新播放器状态和界面
return 0;
}
// 自定义音频中断事件函数
int32_t MyOnInterruptEvent(
OH_AudioCapturer* capturer,
void* userData,
OH_AudioInterrupt_ForceType type,
OH_AudioInterrupt_Hint hint)
{
// 根据type和hint表示的音频中断信息,更新录制器状态和界面
return 0;
}
// 自定义异常回调函数
int32_t MyOnError(
OH_AudioCapturer* capturer,
void* userData,
OH_AudioStream_Result error)
{
// 根据error表示的音频异常信息,做出相应的处理
return 0;
}
OH_AudioCapturer_Callbacks callbacks;
// 配置回调函数
callbacks.OH_AudioCapturer_OnReadData = MyOnReadData;
callbacks.OH_AudioCapturer_OnStreamEvent = MyOnStreamEvent;
callbacks.OH_AudioCapturer_OnInterruptEvent = MyOnInterruptEvent;
callbacks.OH_AudioCapturer_OnError = MyOnError;
// 设置音频输入流的回调
OH_AudioStreamBuilder_SetCapturerCallback(builder, callbacks, nullptr);
通过OH_AudioStreamBuilder_SetCapturerCallback函数配置回调函数。
构造录制音频流
OH_AudioCapturer* audioCapturer;
OH_AudioStreamBuilder_GenerateCapturer(builder, &audioCapturer);
使用音频流
- OH_AudioStream_Result OH_AudioCapturer_Start(OH_AudioCapturer* capturer):开始录制
- OH_AudioStream_Result OH_AudioCapturer_Pause(OH_AudioCapturer* capturer):暂停录制
- OH_AudioStream_Result OH_AudioCapturer_Stop(OH_AudioCapturer* capturer):停止录制
- OH_AudioStream_Result OH_AudioCapturer_Flush(OH_AudioCapturer* capturer):释放缓存数据
- OH_AudioStream_Result OH_AudioCapturer_Release(OH_AudioCapturer* capturer):释放录制实例
释放构造器
OH_AudioStreamBuilder_Destroy(builder);
音频录制最佳实践
我们以录制MP3为例来实现音频采集的全流程实践。
权限申请
音频采集需要动态申请权限,现在module.json5中声明权限:
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when": "inuse"
}
}
],
动态申请权限:
function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
} // 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
在aboutToAppera中调用申请权限方法,在授权成功后启动录音
const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
reqPermissionsFromUser(permissions, context);
}
配置C++项目
创建C++模块后,配置ohaudio动态库依赖:
cmake_minimum_required(VERSION 3.5.0)
project(audiorecorderdemo)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(capture SHARED napi_init.cpp)
target_link_libraries(capture PUBLIC libace_napi.z.so)
target_link_libraries(capture PUBLIC libohaudio.so)
配置napi方法:
static napi_value start(napi_env env, napi_callback_info info)
{
return nullptr;
}
static napi_value stop(napi_env env, napi_callback_info info)
{
return nullptr;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
{ "start", nullptr, start, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "stop", nullptr, stop, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
实现启动录制
// 自定义写入数据函数
int32_t MyOnReadData(
OH_AudioCapturer* capturer,
void* userData,
void* buffer,
int32_t length)
{
//TODO 从buffer中取出length长度的录音数据
return 0;
}
// 自定义音频流事件函数
int32_t MyOnStreamEvent(
OH_AudioCapturer* capturer,
void* userData,
OH_AudioStream_Event event)
{
//TODO 根据event表示的音频流事件信息,更新播放器状态和界面
return 0;
}
// 自定义音频中断事件函数
int32_t MyOnInterruptEvent(
OH_AudioCapturer* capturer,
void* userData,
OH_AudioInterrupt_ForceType type,
OH_AudioInterrupt_Hint hint)
{
//TODO 根据type和hint表示的音频中断信息,更新录制器状态和界面
return 0;
}
// 自定义异常回调函数
int32_t MyOnError(
OH_AudioCapturer* capturer,
void* userData,
OH_AudioStream_Result error)
{
//TODO 根据error表示的音频异常信息,做出相应的处理
return 0;
}
static napi_value start(napi_env env, napi_callback_info info)
{
OH_AudioStreamBuilder* builder;
OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_CAPTURER);
// 设置音频采样率
OH_AudioStreamBuilder_SetSamplingRate(builder, 48000);
// 设置音频声道
OH_AudioStreamBuilder_SetChannelCount(builder, 2);
// 设置音频采样格式
OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE);
// 设置音频流的编码类型
OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_RAW);
// 设置输入音频流的工作场景
OH_AudioStreamBuilder_SetCapturerInfo(builder, AUDIOSTREAM_SOURCE_TYPE_MIC);
OH_AudioCapturer_Callbacks callbacks;
// 配置回调函数
callbacks.OH_AudioCapturer_OnReadData = MyOnReadData;
callbacks.OH_AudioCapturer_OnStreamEvent = MyOnStreamEvent;
callbacks.OH_AudioCapturer_OnInterruptEvent = MyOnInterruptEvent;
callbacks.OH_AudioCapturer_OnError = MyOnError;
// 设置音频输入流的回调
OH_AudioStreamBuilder_SetCapturerCallback(builder, callbacks, nullptr);
OH_AudioCapturer* audioCapturer;
OH_AudioStreamBuilder_GenerateCapturer(builder, &audioCapturer);
return nullptr;
}
最佳实践一:
为了避免不可预期的行为,在设置音频回调函数时,请确保OH_AudioCapturer_Callbacks的每一个回调都被自定义的回调方法或空指针初始化,比如:
OH_AudioCapturer_Callbacks callbacks;
// 配置回调函数,如果需要监听,则赋值
callbacks.OH_AudioCapturer_OnReadData = MyOnReadData;
callbacks.OH_AudioCapturer_OnInterruptEvent = MyOnInterruptEvent;
// (必选)如果不需要监听,使用空指针初始化
callbacks.OH_AudioCapturer_OnStreamEvent = nullptr;
callbacks.OH_AudioCapturer_OnError = nullptr;
最佳实践二:
对于支持低延时模式的设备,对于延时要求比较高的场景(比如语音通话)可以使用低时延模式创建音频录制构造器,获得更高质量的音频体验:
OH_AudioStream_LatencyMode latencyMode = AUDIOSTREAM_LATENCY_MODE_FAST;
OH_AudioStreamBuilder_SetLatencyMode(builder, latencyMode);
音频文件处理
在音频回调中我们对音频数据就行处理,可以交给ASR也可以直接写入文件,下一篇我们实现编码成mp3并写入文件的实践。
停止播放销毁实例
OH_AudioCapturer_Stop(builder, &audioCapturer);
OH_AudioStreamBuilder_Destroy(builder);
总结
本文介绍了HarmonyOS 提供的两种音频采集方式:TS层的AudioCapture和C++层的OHAudio,并以OHAudio接口实现了实时音频采集功能。