HarmonyOS 音视频之音频采集实战

news2024/9/25 13:26:10

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代表音频采集器
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接口实现了实时音频采集功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1981451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图+代码 | Bloom Filter实现及应用

什么是布隆过滤器&#xff08;Bloom Filter&#xff09;&#xff1f; 布隆过滤器是一种空间复杂度很低的概率型数据结构&#xff0c;用于判断一个元素是否在一个集合中。它有两种可能的返回结果&#xff1a; 元素可能在集合中&#xff1a;这可能是一个真阳性&#xff08;确实…

使用Variadic Templates(可变参数模板)实现printf

最近学习了C2.0版本的一些新的特性&#xff0c;利用Variadic Templates&#xff08;可变参数模板&#xff09;实现printf函数。 语言环境 Dev-C 5.11 并需要自己的环境是支持C11的&#xff0c;例如:Dev-C 5.11可以通过以下步骤进行修改&#xff1a; 源码 #include <io…

编程与AI:保持竞争力的策略

人工智能时代&#xff0c;程序员如何保持核心竞争力&#xff1f; 随着AIGC&#xff08;如chatgpt、midjourney、claude等&#xff09;大语言模型接二连三的涌现&#xff0c;AI辅助编程工具日益普及&#xff0c;程序员的工作方式正在发生深刻变革。有人担心AI可能取代部分编程工…

顶刊、顶会、水刊的论文读哪个,如何做一个称职的学术裁缝_来自B站水论文的程序猿

系列文章目录 文章目录 系列文章目录一、顶刊、顶会要看二、水刊&#xff1a;SCI4区&#xff0c;部分3区 看水刊&#xff0c;发现新大陆1、数据集2、性能3、复现 三、如何做一个称职的学术裁缝1、缝合2、编故事 一、顶刊、顶会要看 通过顶刊、顶会知道好的东西长什么样子即可&…

【从零开始一步步学习VSOA开发】创建VSOA的client端

创建VSOA的client端 创建工程 参考 hellovsoa 工程&#xff0c;创建 client 工程&#xff0c;工程源码修改如下&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/select.h> #include "vsoa_client.h&q…

全球情绪检测与识别市场规划预测:2030年市场规模将接近3166亿元,未来六年CAGR为22.6%

一、引言 随着人工智能和机器学习技术的飞速发展&#xff0c;情绪检测与识别在多个领域的应用日益广泛&#xff0c;其市场潜力日益凸显。本文旨在探索情绪检测与识别行业的发展趋势、潜在商机及其未来展望。 二、市场趋势 全球情绪检测与识别市场的增长主要受人工智能和机器学…

【mathtype】word中如何输入4×4的矩阵,甚至阶数更多

在写论文或者使用word操作的时候&#xff0c;我们可能会使用矩阵插入我们所写的word中&#xff0c;今天小编就分享一下如何在word中输入矩阵。首先&#xff0c;我们word中需要安装mathtype的插件。 ①打开word&#xff0c;鼠标点击mathtype&#xff0c;再点击内联 ② 出现以下…

C++学习记录

C学习 1.const,mutable,初始化列表,this指针2.static使用类友元作用友元全局函数友元类友元成员函数 运算符重载左移运算符 <<下标运算符赋值运算符括号运算符一元运算符 自动类型转换隐式类型转换 转换函数 继承继承方式构造函数继承名字遮蔽和类作用域名字覆盖类作用域…

大气能见度测量仪的功能优势:安心守护

大气能见度是衡量空气质量与交通安全的重要指标之一&#xff0c;它直接关系到人们的出行安全、航空航行的顺畅以及环境监测的准确性。为此&#xff0c;大气能见度测量仪应运而生&#xff0c;以其独特的功能优势&#xff0c;成为现代气象观测与交通管理中不可或缺的高科技设备。…

数据资源:机遇与挑战并存的新时代

数据资源&#xff1a;机遇与挑战并存的新时代 2023年全国数据生产总量达到32.85泽字节&#xff08;ZB&#xff09;&#xff0c;同比增长22.44%&#xff0c;数据生产规模持续扩大&#xff0c;其中非结构数据增长尤为突出&#xff0c;这得益于5G、AI技术的发展和智能设备的应用。…

ME31K-创建合同

ME31K创建合同 合同分为&#xff1a;数量合同和价值合同 CMK代表数量合同。CWK代表价值合同。 输入对应数量或价值后&#xff0c;点击保存。 点击保存后。系统会带出一个合同号。 可以引用这个合同号&#xff0c;去创建采购订单或者计划协议。

【从零开始一步步学习VSOA开发】创建VSOA的server端

创建VSOA的server端 创建工程 参考 hellovsoa 工程&#xff0c;创建 server 工程&#xff0c;工程源码修改如下&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #…

zabbix7.0TLS-02-客户端zabbix-agent部署和配置

文章目录 1 介绍2 工作模式2 部署2.1 RPM 包方式部署 3 配置3.1 配置文件位置3.2 主配置文件布局3.2.1 常规参数3.2.2 高级参数 3.3 部分配置参数详解3.3.1 通用参数3.3.2 被动模式参数3.3.3 主动模式参数 4 测试4.1 Agent 本机测试4.2 在服务端 Zabbix-server 测试和 Agent 的…

Opengl 安装

安装Cmake&#xff1a;Download CMake 安装GLFW&#xff08;source package&#xff09;:An OpenGL library | GLFW glad.dav1d.de 下载glad&#xff0c;选择version3.3 Core --Generate生成。然后点击glad.zip文件下载。 在cmake配置glfw&#xff1a;config-generate 构建GLF…

场外个股期权也可以随时平仓?场外期权行权是什么时候?

今天带你了解场外个股期权也可以随时平仓&#xff1f;场外期权行权是什么时候&#xff1f;A股场外个股有期权&#xff0c;主要对应的标的是沪深交易所上市的个股&#xff0c;除了st、次新股等受到限制&#xff0c;90%的股票都可以购买。 场外个股期权是否可以随时平仓 场外个…

MySQL数据库管理系统在Linux上安装部署

1. MySQL 5.7版本安装 1.1 安装 &#xff08;1&#xff09; 配置yum仓库 # 更新密钥 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 # 安装Mysql yum库 rpm -Uvh http://repo.mysql.com/mysql57-community-release-el7-7.noarch.rpm 注意&#xff1a; 由于M…

OpenCV及rembg去除图像背景

OpenCV去除图像背景 去除图像背景&#xff0c;需要综合使用二值化&#xff08;thresholding&#xff09;、腐蚀&#xff08;erosion&#xff09;、膨胀&#xff08;dilation&#xff09;以及位运算&#xff08;bitwise operations&#xff09;&#xff0c;代码如下&#xff1a…

【独家原创RIME-CNN-LSSVM】基于霜冰优化算法优化卷积神经网络(CNN)结合最小二乘向量机(LSSVM)的数据回归预测

【独家原创RIME-CNN-LSSVM】基于霜冰优化算法优化卷积神经网络(CNN)结合最小二乘向量机(LSSVM)的数据回归预测 目录 【独家原创RIME-CNN-LSSVM】基于霜冰优化算法优化卷积神经网络(CNN)结合最小二乘向量机(LSSVM)的数据回归预测效果一览基本介绍程序设计参考资料 效果一览 基本…

成品油综合监管云平台:系统功能全方位解析

成品油综合监管云平台&#xff0c;作为集数据收集、统计分析、监管、预警应急安全等功能于一体的信息化监管系统&#xff0c;正逐步成为政府监管部门提升管理效率、保障市场秩序的重要工具。 本文将详细解析成品油综合监管云平台的多项核心功能&#xff0c;展现其在现代成品油…

习题2.31

先上代码 (defn square [x](* x x)) (defn square-tree[tree](tree-map square tree) )(defn tree-map [op tree](cond (not (seq? tree)) (op tree)(empty? tree) nil:else (cons (tree-map op (first tree)) (tree-map op (rest tree)))) )题目实际上是想让我们将树的遍历…