音频模块适配 原创

游戏技术分享
发布于 2025-5-7 16:02
浏览
0收藏

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播放流程如下:

  1. 创建Renderer(设置streamBuilder参数等)
  2. 设置RendererCallback
  3. 状态控制
  4. 资源释放

音频模块适配-鸿蒙开发者社区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 音频录制

音频录制开发流程

音频录制开发流程和音频播放开发流程基本上一致:

  1. 创建Capturer(设置streamBuilder参数等)
  2. 设置CapturerCallback
  3. 状态控制
  4. 资源释放

音频模块适配-鸿蒙开发者社区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 参考文档


更多问题可关注:

鸿蒙游戏官方网站:​​已有游戏移植-鸿蒙游戏-华为开发者联盟​

公开课:​​华为开发者学堂​

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
收藏
回复
举报
回复
    相关推荐