
音频模块适配 原创
1 概述
介绍如何使用HarmonyOS NEXT自带的OHAudio音频模块开发音频播放和录音功能
2 依赖
在CMake脚本中链接动态库
target_link_libraries(sample PUBLIC libohaudio.so)
添加头文件
#include <ohaudio/native_audiorenderer.h>
#include <ohaudio/native_audiostreambuilder.h>
3 音频播放
音频播放开发流程
一个简单的OHAudio播放流程如下:
- 创建Renderer(设置streamBuilder参数等)
- 设置RendererCallback
- 状态控制
- 资源释放
cke_10446.png
第一步:创建Renderer
OH_AudioRenderer *audioRenderer = nullptr;
OH_AudioStreamBuilder *ohaudioBuilder = nullptr;
void OHAudio_t::RendererInit(){
OH_AudioStreamBuilder *ohaudioBuilder;
OH_AudioStream_Result result;
// 创建StreamBuilder
result = OH_AudioStreamBuilder_Create(&ohaudioBuilder, AUDIOSTREAM_TYPE_RENDERER);
CHECK_RESULT(result);
// 设置StreamBuilder参数
result = OH_AudioStreamBuilder_SetSamplingRate(ohaudioBuilder, SAMPLING_RATE);
CHECK_RESULT(result);
result = OH_AudioStreamBuilder_SetChannelCount(ohaudioBuilder, NUM_CHANNELS);
CHECK_RESULT(result);
result = OH_AudioStreamBuilder_SetSampleFormat(ohaudioBuilder, AUDIOSTREAM_SAMPLE_S16LE);
CHECK_RESULT(result);
result = OH_AudioStreamBuilder_SetEncodingType(ohaudioBuilder, AUDIOSTREAM_ENCODING_TYPE_RAW);
CHECK_RESULT(result);
// 低时延模式
result = OH_AudioStreamBuilder_SetLatencyMode(ohaudioBuilder, AUDIOSTREAM_LATENCY_MODE_FAST);
CHECK_RESULT(result);
// audio使用场景
result = OH_AudioStreamBuilder_SetRendererInfo(ohaudioBuilder, AUDIOSTREAM_USAGE_GAME);
CHECK_RESULT(result);
result = OH_AudioStreamBuilder_SetFrameSizeInCallback(ohaudioBuilder, 5);
CHECK_RESULT(result);
// 设置RendererCallback
{
OH_AudioRenderer_Callbacks callbacks = {
render_callback,
stream_event_callback,
interrupt_event_callback,
error_callback
};
result = OH_AudioStreamBuilder_SetRendererCallback(ohaudioBuilder, callbacks, NULL);
CHECK_RESULT(result);
}
// 创建Renderer
{
assert(stream == NULL);
result = OH_AudioStreamBuilder_GenerateRenderer(ohaudioBuilder, &audioRenderer);
CHECK_RESULT(result);
stream = stream;
}
}
第二步:设置RendererCallback
设置的回调方法通过OH_AudioStreamBuilder_SetRendererCallback接口绑定到StreamBuilder。
FILE *g_file = "/data/storage/el2/base/haps/entry/files/audio/xxxx.pcm";
bool g_readEnd = false;
int32_t OHAudio_t::render_callback(OH_AudioRenderer *capturer, void *userData, void *buffer, int32_t bufferLen) {
size_t readCount = fread(buffer, bufferLen, 1, g_file);
if (!readCount) {
if (ferror(g_file)) {
LOGI("MYOHAUDIO Error reading myfile");
} else if (feof(g_file)) {
LOGI("MYOHAUDIO EOF found");
g_readEnd = true;
}
}
return 0;
}
// 自定义音频流事件函数
int32_t stream_event_callback(OH_AudioRenderer* renderer, void* userData, OH_AudioStream_Event event)
{
// 根据event表示的音频流事件信息,更新播放器状态和界面
LOGI("MYOHAUDIO MyOnStreamEvent FUNCTION START...");
OH_AudioStream_Result result;
if (stream != NULL) {
if (hint == AUDIOSTREAM_INTERRUPT_HINT_RESUME) {
result = OH_AudioRenderer_Start(renderer);
} else if (hint == AUDIOSTREAM_INTERRUPT_HINT_PAUSE) {
result = OH_AudioRenderer_Pause(renderer);
}
}
return 0;
}
// 自定义音频中断事件函数
int32_t interrupt_event_callback(OH_AudioRenderer* renderer, void* userData, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint)
{
// 根据type和hint表示的音频中断信息,更新播放器状态和界面
LOGI("MYOHAUDIO MyOnInterruptEvent FUNCTION START...");
return 0;
}
// 自定义异常回调函数
int32_t error_callback(OH_AudioRenderer* renderer, void* userData, OH_AudioStream_Result error)
{
// 根据error表示的音频异常信息,做出相应的处理
LOGI("MYOHAUDIO MyOnError FUNCTION START...");
return 0;
}
第三步:状态控制
void OHAudio_t::play(){
LOGD("MYOHAUDIO OHAudio_t play ...");
OH_AudioRenderer_Start(audioRenderer);
}
void OHAudio_t::pause(){
LOGD("MYOHAUDIO OHAudio_t pause ...");
OH_AudioRenderer_Pause(audioRenderer);
}
void OHAudio_t::stop(){
LOGD("MYOHAUDIO OHAudio_t stop ...");
OH_AudioRenderer_Stop(audioRenderer);
}
第四步:释放Renderer等对象
OHAudio_t::~OHAudio_t(){
LOGD("MYOHAUDIO OHAudio_t delete ...");
fclose(g_file);
g_file = nullptr;
OH_AudioRenderer_Release(audioRenderer);
OH_AudioStreamBuilder_Destroy(ohaudioBuilder);
}
音频播放状态控制
音频播放状态控制如下图:
cke_13868.png
开发过程中常见问题
低时延模式支持
开启低时延模式后,支持的音频采样率为48Khz或是96Khz.若播放音频的采样率不满足要求,可能出现杂音。
OH_AudioRenderer_OnWriteData回调函数中bufferLen如何调整
可通过OH_AudioStreamBuilder_SetFrameSizeInCallback()设置,其中frameSize和bufferLen换算规则如下:
bufferLen = 音频采样率 * frameSize * 声道数 * 音频流采样格式(OH_AudioStream_SampleFormat) / 8
eg: 低时延模式下(默认frameSize为5ms,可自行调整),采样率为48Khz,双声道16比特采样格式对应的bufferLen计算如下
48K*5ms*2*16/8 = 960
OHAudio播放暂停延迟问题如何分析
播放暂停延迟问题一般为OH_AudioRenderer_OnWriteData回调函数中设置的bufferLen过大导致,一般可以在日志中见到如下内容:
Client handle callback too slow, cost 5610us...
...
Client handle callback too slow, cost 5592us...
...
Client handle callback too slow, cost 5722us...
这种情况的解决方法需要将bufferLen调小。或是优化OH_AudioRenderer_OnWriteData中逻辑。减少一次回调所需时间。正常情况一次回调时间需控制在3000us之内。
OHAudio播放打断及恢复
在通话、短信通知或是闹铃等高优先级音频响起时,OHAudio会被打断,随后等待这些音频结束后,恢复OHAudio音频播放。需要实现上述效果需要通过OH_AudioRenderer_OnInterruptEvent实现。实现方式如下:
int32_t OH_AudioRenderer_OnInterruptEvent(OH_AudioRenderer* renderer, void* userData, OH_AudioStream_Event event)
{
// 根据event表示的音频流事件信息,更新播放器状态和界面
LOGI("MYOHAUDIO MyOnStreamEvent FUNCTION START...");
OH_AudioStream_Result result;
if (stream != NULL) {
if (hint == AUDIOSTREAM_INTERRUPT_HINT_RESUME) {
result = OH_AudioRenderer_Start(renderer);
} else if (hint == AUDIOSTREAM_INTERRUPT_HINT_PAUSE) {
result = OH_AudioRenderer_Pause(renderer);
}
}
return 0;
}
使用播放过程中如何修改使用场景
场景:语言通话功能,切换听筒(AUDIOSTREAM_USAGE_VOICE_COMMUNICATION)和扬声器(AUDIOSTREAM_USAGE_VIDEO_COMMUNICATION)OH_AudioStreamBuilder_SetRendererInfo接口用于设置音频使用场景,但是在Renderer创建完成之后,已创建的Renderer的使用场景便不可以修改(目前无可调整接口),需要重新生成一个不同场景的Renderer替换之前的Renderer。
音频文件播放完毕之后能否自动改变Renderer状态为stop
不能,Renderer只能一个播放通道,并没有播放资源完成的概念。需要手动控制,如Renderer长时间不stop可能存在功耗问题。
4 音频录制
音频录制开发流程
音频录制开发流程和音频播放开发流程基本上一致:
- 创建Capturer(设置streamBuilder参数等)
- 设置CapturerCallback
- 状态控制
- 资源释放
cke_17634.png
第一步:创建Capturer
string pcmFilePath("/data/storage/el2/base/haps/entry/files/audio/record.pcm");
FILE *recordFile = fopen(pcmFilePath.c_str(), "w+b");
OH_AudioCapturer* audioCapturer = nullptr;
OH_AudioStreamBuilder *builder;
void OHAudio_t::CapturerInit(){
OH_AudioStream_Result result = OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_CAPTURER);
CHECK_RESULT(result);
// 设置音频采样率
result = OH_AudioStreamBuilder_SetSamplingRate(builder, samplingRate);
CHECK_RESULT(result);
// 设置音频声道
result = OH_AudioStreamBuilder_SetChannelCount(builder, channelCount);
CHECK_RESULT(result);
result = OH_AudioStreamBuilder_SetLatencyMode(builder, AUDIOSTREAM_LATENCY_MODE_NORMAL);
CHECK_RESULT(result);
result = OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE);
CHECK_RESULT(result);
// 设置输入音频流的工作场景
result = OH_AudioStreamBuilder_SetCapturerInfo(builder, AUDIOSTREAM_SOURCE_TYPE_MIC);
CHECK_RESULT(result);
OH_AudioCapturer_Callbacks callbacks;
// 配置回调函数
callbacks.OH_AudioCapturer_OnReadData = MyOnReadData;
callbacks.OH_AudioCapturer_OnStreamEvent = MyOnStreamEvent;
callbacks.OH_AudioCapturer_OnInterruptEvent = MyOnInterruptEvent;
callbacks.OH_AudioCapturer_OnError = MyOnError;
// 设置CapturerCallback
result = OH_AudioStreamBuilder_SetCapturerCallback(builder, callbacks, recordFile); // 第三个参数是userdata
CHECK_RESULT(result);
// 生成Capturer
result = OH_AudioStreamBuilder_GenerateCapturer(builder, &audioCapturer);
CHECK_RESULT(result);
}
第二步:设置CapturerCallback
int32_t mRecordFileSize = 0;
int32_t mMaxFileSize = 102400;
// 自定义写入数据函数
int32_t MyOnReadData(OH_AudioCapturer *capturer, void *userData, void *buffer, int32_t length) {
FILE *recordFile = static_cast<FILE *>(userData);
// 从buffer中取出length长度的录音数据
size_t count = 1;
long writeBytesLen = length * count;
if (fwrite(buffer, length, count, recordFile) != count) {
LOGE("buffer fwrite err");
}
// 判断是否录制达到允许的最大值,自行定义
mRecordFileSize = mRecordFileSize + writeBytesLen;
if (mRecordFileSize >= mMaxFileSize) {
LOGW("record reached max file size, now need stop record");
// 达到最大值后停止录制
stopRecord();
}
return 0;
}
// 自定义音频流事件函数
int32_t MyOnStreamEvent(OH_AudioCapturer *capturer, void *userData, OH_AudioStream_Event event) {
// 根据event表示的音频流事件信息,更新播放器状态和界面
LOGD("MyOnStreamEvent >>>> received");
return 0;
}
// 自定义音频中断事件函数
int32_t MyOnInterruptEvent(OH_AudioCapturer *capturer, void *userData, OH_AudioInterrupt_ForceType type,
OH_AudioInterrupt_Hint hint) {
// 根据type和hint表示的音频中断信息,更新录制器状态和界面
LOGD("MyOnInterruptEvent >>>> received");
return 0;
}
// 自定义异常回调函数
int32_t MyOnError(OH_AudioCapturer *capturer, void *userData, OH_AudioStream_Result error) {
// 根据error表示的音频异常信息,做出相应的处理
LOGD("MyOnError >>>> received, now recycle record data %{public}d", error);
return 0;
}
第三步:状态控制
// 开始录制
void OHAudio_t::startRecord(){
OH_AudioCapturer_Start(audioCapturer);
}
// 暂停录制
void OHAudio_t::pauseRecord(){
OH_AudioCapturer_Pause(audioCapturer);
}
// 清除录制数据
void OHAudio_t::flushRecord(){
OH_AudioCapturer_Flush(audioCapturer);
}
// 停止录制
void OHAudio_t::stopRecord(){
OH_AudioCapturer_Stop(audioCapturer);
}
第四步:资源释放
OHAudio_t::~OHAudio_t(){
LOGD("MYOHAUDIO OHAudio_t delete ...");
fclose(recordFile);
recordFile = nullptr;
OH_AudioCapturer_Release(audioCapturer);
OH_AudioStreamBuilder_Destroy(builder);
}
开发过程中常见问题
创建Capturer不成功
音频录制功能需要依赖手机的麦克风,如需使用麦克风需要提前申请权限ohos.permission.MICROPHONE。未申请麦克风权限的情况下OH_AudioStreamBuilder_GenerateCapturer接口不会报错和闪退,但是OH_AudioStreamBuilder_GenerateCapturer接口返回值不是AUDIOSTREAM_SUCCESS。需要通过该返回值来判断Capturer是否创建成功,避免Capturer创建不成功导致调用野指针(Capturer)的方法出现的不可预测问题。
连接耳机时,不想使用耳机麦克风进行收音如何调整
OH_AudioStreamBuilder_SetCapturerInfo接口设置为AUDIOSTREAM_SOURCE_TYPE_VOICE_COMMUNICATION时,使用的时耳机麦克风收音,其他配置均使用机器本身的麦克风收音。
5 参考文档
更多问题可关注:
鸿蒙游戏官方网站:已有游戏移植-鸿蒙游戏-华为开发者联盟
公开课:华为开发者学堂
