鸿蒙应用开发-录音并使用WebSocket实现实时语音识别

这篇具有很好参考价值的文章主要介绍了鸿蒙应用开发-录音并使用WebSocket实现实时语音识别。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

功能介绍:

录音并实时获取RAW的音频格式数据,利用WebSocket上传数据到服务器,并实时获取语音识别结果,参考文档使用AudioCapturer开发音频录制功能(ArkTS),更详细接口信息请查看接口文档:AudioCapturer8+和@ohos.net.webSocket (WebSocket连接)。

知识点:

  1. 熟悉使用AudioCapturer录音并实时获取RAW格式数据。
  2. 熟悉使用WebSocket上传音频数据并获取识别结果。
  3. 熟悉对敏感权限的动态申请方式,本项目的敏感权限为MICROPHONE
  4. 关于如何搭建实时语音识别服务,可以参考我的另外一篇文章:《识别准确率竟如此高,实时语音识别服务》。

使用环境:

  • API 9
  • DevEco Studio 4.0 Release
  • Windows 11
  • Stage模型
  • ArkTS语言

所需权限:

  1. ohos.permission.MICROPHONE

效果图:
鸿蒙应用app播放webrtc流,鸿蒙应用开发,harmonyos,websocket,语音识别,鸿蒙系统,华为

核心代码:

src/main/ets/utils/Permission.ets是动态申请权限的工具:

import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';

async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
  let atManager = abilityAccessCtrl.createAtManager();
  let grantStatus: abilityAccessCtrl.GrantStatus;

  // 获取应用程序的accessTokenID
  let tokenId: number;
  try {
    let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
  } catch (err) {
    console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
  }

  // 校验应用是否被授予权限
  try {
    grantStatus = await atManager.checkAccessToken(tokenId, permission);
  } catch (err) {
    console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);
  }

  return grantStatus;
}

export async function checkPermissions(permission: Permissions): Promise<boolean> {
  let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permission);

  if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
    return true
  } else {
    return false
  }
}

src/main/ets/utils/Recorder.ets是录音工具类,进行录音和获取录音数据。

import audio from '@ohos.multimedia.audio';
import { delay } from './Utils';

export default class AudioCapturer {
  private audioCapturer: audio.AudioCapturer | undefined = undefined
  private isRecording: boolean = false
  private audioStreamInfo: audio.AudioStreamInfo = {
    samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000, // 音频采样率
    channels: audio.AudioChannel.CHANNEL_1, // 录音通道数
    sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
    encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW  // 音频编码类型
  }
  private audioCapturerInfo: audio.AudioCapturerInfo = {
    // 音源类型,使用SOURCE_TYPE_VOICE_RECOGNITION会有减噪功能,如果设备不支持,该用普通麦克风:SOURCE_TYPE_MIC
    source: audio.SourceType.SOURCE_TYPE_VOICE_RECOGNITION,
    capturerFlags: 0 // 音频采集器标志
  }
  private audioCapturerOptions: audio.AudioCapturerOptions = {
    streamInfo: this.audioStreamInfo,
    capturerInfo: this.audioCapturerInfo
  }

  // 初始化,创建实例,设置监听事件
  constructor() {
    // 创建AudioCapturer实例
    audio.createAudioCapturer(this.audioCapturerOptions, (err, capturer) => {
      if (err) {
        console.error(`创建录音器失败, 错误码:${err.code}, 错误信息:${err.message}`)
        return
      }
      this.audioCapturer = capturer
      console.info('创建录音器成功')
    });
  }

  // 开始一次音频采集
  async start(callback: (state: number, data?: ArrayBuffer) => void) {
    // 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动采集
    let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED]
    if (stateGroup.indexOf(this.audioCapturer.state) === -1) {
      console.error('启动录音失败')
      callback(audio.AudioState.STATE_INVALID)
      return
    }
    // 启动采集
    await this.audioCapturer.start()
    this.isRecording = true
    let bufferSize = 1920
    // let bufferSize: number = await this.audioCapturer.getBufferSize();

    while (this.isRecording) {
      let buffer = await this.audioCapturer.read(bufferSize, true)
      if (buffer === undefined) {
        console.error('读取录音数据失败')
      } else {
        callback(audio.AudioState.STATE_RUNNING, buffer)
      }
    }
    callback(audio.AudioState.STATE_STOPPED)
  }

  // 停止采集
  async stop() {
    this.isRecording = false
    // 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止
    if (this.audioCapturer.state !== audio.AudioState.STATE_RUNNING && this.audioCapturer.state !== audio.AudioState.STATE_PAUSED) {
      console.warn('Capturer is not running or paused')
      return
    }
    await delay(200)
    // 停止采集
    await this.audioCapturer.stop()
    if (this.audioCapturer.state.valueOf() === audio.AudioState.STATE_STOPPED) {
      console.info('录音停止')
    } else {
      console.error('录音停止失败')
    }
  }

  // 销毁实例,释放资源
  async release() {
    // 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能release
    if (this.audioCapturer.state === audio.AudioState.STATE_RELEASED || this.audioCapturer.state === audio.AudioState.STATE_NEW) {
      return
    }
    // 释放资源
    await this.audioCapturer.release()
  }
}

还需要一些其他的工具函数src/main/ets/utils/Utils.ets,这个主要用于睡眠等待:

// 睡眠
export function delay(milliseconds : number) {
  return new Promise(resolve => setTimeout( resolve, milliseconds));
}

还需要在src/main/module.json5添加所需要的权限,注意是在module中添加,关于字段说明,也需要在各个的string.json添加:

    "requestPermissions": [
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:record_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "always"
        }
      }
    ]

页面代码如下:文章来源地址https://www.toymoban.com/news/detail-858148.html

import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common';
import webSocket from '@ohos.net.webSocket';
import AudioCapturer from '../utils/Recorder';
import promptAction from '@ohos.promptAction';
import { checkPermissions } from '../utils/Permission';
import audio from '@ohos.multimedia.audio';

// 需要动态申请的权限
const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
// 获取程序的上下文
const context = getContext(this) as common.UIAbilityContext;

@Entry
@Component
struct Index {
  @State recordBtnText: string = '按下录音'
  @State speechResult: string = ''
  private offlineResult = ''
  private onlineResult = ''
  // 语音识别WebSocket地址
  private asrWebSocketUrl = "ws://192.168.0.100:10095"
  // 录音器
  private audioCapturer?: AudioCapturer;
  // 创建WebSocket
  private ws;

  // 页面显示时
  async onPageShow() {
    // 判断是否已经授权
    let promise = checkPermissions(permissions[0])
    promise.then((result) => {
      if (result) {
        // 初始化录音器
        if (this.audioCapturer == null) {
          this.audioCapturer = new AudioCapturer()
        }
      } else {
        this.reqPermissionsAndRecord(permissions)
      }
    })
  }

  // 页面隐藏时
  async onPageHide() {
    if (this.audioCapturer != null) {
      this.audioCapturer.release()
    }
  }

  build() {
    Row() {
      RelativeContainer() {
        Text(this.speechResult)
          .id("resultText")
          .width('95%')
          .maxLines(10)
          .fontSize(18)
          .margin({ top: 10 })
          .alignRules({
            top: { anchor: '__container__', align: VerticalAlign.Top },
            middle: { anchor: '__container__', align: HorizontalAlign.Center }
          })
        // 录音按钮
        Button(this.recordBtnText)
          .width('90%')
          .id("recordBtn")
          .margin({ bottom: 10 })
          .alignRules({
            bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
            middle: { anchor: '__container__', align: HorizontalAlign.Center }
          })
          .onTouch((event) => {
            switch (event.type) {
              case TouchType.Down:
                console.info('按下按钮')
              // 判断是否有权限
                let promise = checkPermissions(permissions[0])
                promise.then((result) => {
                  if (result) {
                    // 开始录音
                    this.startRecord()
                    this.recordBtnText = '录音中...'
                  } else {
                    // 申请权限
                    this.reqPermissionsAndRecord(permissions)
                  }
                })
                break
              case TouchType.Up:
                console.info('松开按钮')
                // 停止录音
                this.stopRecord()
                this.recordBtnText = '按下录音'
                break
            }
          })
      }
      .height('100%')
      .width('100%')
    }
    .height('100%')
  }

  // 开始录音
  startRecord() {
    this.setWebSocketCallback()
    this.ws.connect(this.asrWebSocketUrl, (err) => {
      if (!err) {
        console.log("WebSocket连接成功");
        let jsonData = '{"mode": "2pass", "chunk_size": [5, 10, 5], "chunk_interval": 10, ' +
          '"wav_name": "HarmonyOS", "is_speaking": true, "itn": false}'
        // 要发完json数据才能录音
        this.ws.send(jsonData)
        // 开始录音
        this.audioCapturer.start((state, data) => {
          if (state == audio.AudioState.STATE_STOPPED) {
            console.info('录音结束')
            // 录音结束,要发消息告诉服务器,结束识别
            let jsonData = '{"is_speaking": false}'
            this.ws.send(jsonData)
          } else if (state == audio.AudioState.STATE_RUNNING) {
            // 发送语音数据
            this.ws.send(data, (err) => {
              if (err) {
                console.log("WebSocket发送数据失败,错误信息:" + JSON.stringify(err))
              }
            });
          }
        })
      } else {
        console.log("WebSocket连接失败,错误信息: " + JSON.stringify(err));
      }
    });
  }

  // 停止录音
  stopRecord() {
    if (this.audioCapturer != null) {
      this.audioCapturer.stop()
    }
  }

  // 绑定WebSocket事件
  setWebSocketCallback() {
    // 创建WebSocket
    this.ws = webSocket.createWebSocket();
    // 接收WebSocket消息
    this.ws.on('message', (err, value: string) => {
      console.log("WebSocket接收消息,结果如下:" + value)
      // 解析数据
      let result = JSON.parse(value)
      let is_final = result['is_final']
      let mode = result['mode']
      let text = result['text']
      if (mode == '2pass-offline') {
        this.offlineResult = this.offlineResult + text
        this.onlineResult = ''
      } else {
        this.onlineResult = this.onlineResult + text
      }
      this.speechResult = this.offlineResult + this.onlineResult
      // 如果是最后的数据就关闭WebSocket
      if (is_final) {
        this.ws.close()
      }
    });
    // WebSocket关闭事件
    this.ws.on('close', () => {
      console.log("WebSocket关闭连接");
    });
    // WebSocket发生错误事件
    this.ws.on('error', (err) => {
      console.log("WebSocket出现错误,错误信息: " + JSON.stringify(err));
    });
  }

  // 申请权限
  reqPermissionsAndRecord(permissions: Array<Permissions>): void {
    let 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) {
          // 用户授权,可以继续访问目标操作
          console.info('授权成功')
          if (this.audioCapturer == null) {
            this.audioCapturer = new AudioCapturer()
          }
        } else {
          promptAction.showToast({ message: '授权失败,需要授权才能录音' })
          return;
        }
      }
    }).catch((err) => {
      console.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`);
    })
  }
}

到了这里,关于鸿蒙应用开发-录音并使用WebSocket实现实时语音识别的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【鸿蒙应用ArkTS开发系列】- 选择图片、文件和拍照功能实现

    在使用App的时候,我们经常会在一些社交软件中聊天时发一些图片或者文件之类的多媒体文件,那在鸿蒙原生应用中,我们怎么开发这样的功能呢? 本文会给大家对这个功能点进行讲解,我们采用的是拉起系统组件来进行图片、文件的选择,拉起系统相机进行拍照的这样一种

    2024年02月04日
    浏览(38)
  • arkTS开发鸿蒙OS应用(登录页面实现,连接数据库)

    喜欢的朋友可在抖音、小红书、微信公众号、哔哩哔哩搜索“淼学派对”。知乎搜索“编程淼”。

    2024年03月24日
    浏览(35)
  • 【鸿蒙应用ArkTS开发系列】- 导航栏Tab组件使用讲解

    现在市场上的大部分应用,主页都是才用底部导航栏菜单作为页面主体框架来展示, 在鸿蒙中是使用Tabs组件实现,下面我们开始讲解Tab组件的使用。 Tabs是一个通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图,它仅可包含子组件TabContent,同时搭配 TabsCo

    2024年01月16日
    浏览(90)
  • HarmonyOS鸿蒙应用开发( 四、重磅组件List列表组件使用详解)

    List列表组件,是一个非常常用的组件。可以说在一个应用中,它的身影无处不在。它包含一系列相同宽度的列表项,适合连续、多行呈现同类数据,如商品列表、图片列表和和文本列表等。ArkUI 框架采用 List 容器组件创建列表(类似 Android 的 RecycleView、Compose 的 LazyColumn)。

    2024年01月24日
    浏览(44)
  • 【鸿蒙应用ArkTS开发系列】- http网络库使用讲解和封装

    现在网上的应用,基本都是网络应用,需要进行联网获取数据,而常用的联网获取数据的方式有http、socket、websocket等。 在鸿蒙应用、服务中,stage模式开发下,鸿蒙官方为我们提供了一个网络组件库 http ,我们通过 import http from ‘@ohos.net.http’; 即可以完成引用。 @ohos.net.http

    2024年02月15日
    浏览(44)
  • 【鸿蒙应用ArkTS开发系列】- Har包中子组件中监听生命周期实现

    在鸿蒙应用开发中,有时候我们会创建HAR 模块封装一些SDK能力提供给第三方APP进行集成。 鸿蒙的har 包并不支持定义page页面对外导出,也不支持配置路由信息,因此我们多是在har包中提供组件,通过导出组件的形式,提供给App引用使用。 在鸿蒙中,非@Entry装饰的组件,只能

    2024年02月14日
    浏览(47)
  • 鸿蒙应用开发学习:改进小鱼动画实现按键一直按下时控制小鱼移动和限制小鱼移出屏幕

    一、前言 近期我在学习鸿蒙应用开发,跟着B站UP主黑马程序员的视频教程做了一个小鱼动画应用,UP主提供的小鱼动画源代码仅仅实现了移动组件的功能,还存在一些问题,如默认进入页面是竖屏而页面适合横屏显示;真机测试发现手机的状态栏影响到了返回键对按键事件的

    2024年02月01日
    浏览(34)
  • HarmonyOS 鸿蒙应用开发(十一、面向鸿蒙开发的JavaScript基础)

    ArkTS 是HarmonyOS(鸿蒙操作系统)原生应用开发的首选语言。它是用于构建用户界面的一种TypeScript方言,扩展了TypeScript以适应HarmonyOS生态系统的UI开发需求。ArkTS 融合了TypeScript的静态类型系统和现代UI框架的设计理念,为开发者提供了一种更安全高效的方式来编写HarmonyOS应用。

    2024年02月20日
    浏览(40)
  • 鸿蒙实战:ArkTs 开发一个鸿蒙应用

    学习过的 ArkTs 知识点,一步一步开发一个小的鸿蒙应用示例,涉及到  ArkTs 语法、注解 @Entry 、 @Component 、 @state 、路由、生命周期、 @Prop 、 @Link 、常用组件的使用等等知识点。 要开发一个鸿蒙应用,首先我们需要知道 系统是如何找到页面的启动入口 。 鸿蒙如何启动应用

    2024年02月22日
    浏览(42)
  • 【HarmonyOS】开发一个可以看小姐姐的鸿蒙应用 鸿蒙开发入门

    先整张效果图,丑点是丑点,但可以用,买不起鸿蒙系统手机的我,只配用虚拟机。 要说目前最火的手机操作系统,要我来看的话那必然是鸿蒙无疑。16号刚刚结束了第五次鸿蒙内测,在看到这次的内测名单之后,居然有970的机器,这是不是说明俺这手里奋战了三年的荣耀

    2024年02月15日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包