自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

鴻蒙AI能力之語音識別

開發(fā)
文章旨在幫助大家開發(fā)錄音及語音識別時少踩一點(diǎn)坑。AI語音識別不需要任何權(quán)限,但此處使用到麥克風(fēng)錄制音頻,就需要申請麥克風(fēng)權(quán)限。

[[441758]]

想了解更多內(nèi)容,請訪問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

文章旨在幫助大家開發(fā)錄音及語音識別時少踩一點(diǎn)坑。

效果

鴻蒙AI能力之語音識別-鴻蒙HarmonyOS技術(shù)社區(qū)

左側(cè)為簡易UI布局及識別成果,右側(cè)為網(wǎng)易云播放的測試音頻。

開發(fā)步驟

IDE安裝、項目創(chuàng)建等在此略過。App采用SDK版本為API 6,使用JS UI。

1.權(quán)限申請

AI語音識別不需要任何權(quán)限,但此處使用到麥克風(fēng)錄制音頻,就需要申請麥克風(fēng)權(quán)限。

在config.json配置文件中添加權(quán)限:

  1. "reqPermissions": [ 
  2.       { 
  3.         "name""ohos.permission.MICROPHONE" 
  4.       } 
  5.     ] 

 在MainAbility中顯示申明麥克風(fēng)權(quán)限:

  1. @Override 
  2.     public void onStart(Intent intent) { 
  3.         super.onStart(intent); 
  4.         requestPermission(); 
  5.     } 
  6.  
  7.     //獲取權(quán)限 
  8.     private void requestPermission() { 
  9.         String[] permission = { 
  10.                 "ohos.permission.MICROPHONE"
  11.         }; 
  12.         List<String> applyPermissions = new ArrayList<>(); 
  13.         for (String element : permission) { 
  14.             if (verifySelfPermission(element) != 0) { 
  15.                 if (canRequestPermission(element)) { 
  16.                     applyPermissions.add(element); 
  17.                 } 
  18.             } 
  19.         } 
  20.         requestPermissionsFromUser(applyPermissions.toArray(new String[0]), 0); 
  21.     } 

2.創(chuàng)建音頻錄制的工具類

首先創(chuàng)建音頻錄制的工具類AudioCaptureUtils。

而音頻錄制需要用到AudioCapturer類,而在創(chuàng)建AudioCapture類時又會用到AudioStreamInfo類及AudioCapturerInfo類,所以我們分別申明以上3個類的變量。

  1. private AudioStreamInfo audioStreamInfo; 
  2.    private AudioCapturer audioCapturer; 
  3.    private AudioCapturerInfo audioCapturerInfo; 

 在語音識別時對音頻的錄制是由限制的,限制如下:

鴻蒙AI能力之語音識別-鴻蒙HarmonyOS技術(shù)社區(qū)

所以我們在錄制音頻時需要注意:

1.采樣率16000HZ

2.聲道為單聲道

3.僅支持普通話

作為工具類,為了使AudioCaptureUtils能多處使用,我們在創(chuàng)建構(gòu)造函數(shù)時,提供聲道與頻率的參數(shù)重載,并在構(gòu)造函數(shù)中初始化AudioStreamInfo類及AudioCapturerInfo類。

  1. //channelMask 聲道 
  2. //SampleRate 頻率 
  3.   public AudioCaptureUtils(AudioStreamInfo.ChannelMask channelMask, int SampleRate) { 
  4.         this.audioStreamInfo = new AudioStreamInfo.Builder() 
  5.                 .encodingFormat(AudioStreamInfo.EncodingFormat.ENCODING_PCM_16BIT) 
  6.                 .channelMask(channelMask) 
  7.                 .sampleRate(SampleRate) 
  8.                 .build(); 
  9.         this.audioCapturerInfo = new AudioCapturerInfo.Builder().audioStreamInfo(audioStreamInfo).build(); 
  10.     } 

 在init函數(shù)中進(jìn)行audioCapturer的初始化,在初始化時對音效進(jìn)行設(shè)置,默認(rèn)為降噪模式。

  1. //packageName 包名 
  2.  public void init(String packageName) { 
  3.         this.init(SoundEffect.SOUND_EFFECT_TYPE_NS,packageName ); 
  4.     } 
  5. //soundEffect 音效uuid 
  6. //packageName 包名 
  7.    public void init(UUID soundEffect, String packageName) { 
  8.         if (audioCapturer == null || audioCapturer.getState() == AudioCapturer.State.STATE_UNINITIALIZED) 
  9.             audioCapturer = new AudioCapturer(this.audioCapturerInfo); 
  10.         audioCapturer.addSoundEffect(soundEffect, packageName); 
  11.     } 

 初始化后提供start、stop和destory方法,分別開啟音頻錄制、停止音頻錄制和銷毀,此處都是調(diào)用AudioCapturer類中對應(yīng)函數(shù)。

  1. public void stop(){ 
  2.        this.audioCapturer.stop(); 
  3.    } 
  4.  
  5.    public void destory(){ 
  6.        this.audioCapturer.stop(); 
  7.        this.audioCapturer.release(); 
  8.    } 
  9.  
  10.    public Boolean start() { 
  11.        if (audioCapturer == null
  12.            return false
  13.        return audioCapturer.start(); 
  14.    } 

 提供一個讀取音頻流的方法及獲取AudioCapturer實(shí)例的方法。

  1. //buffers 需要寫入的數(shù)據(jù)流 
  2. //offset 數(shù)據(jù)流的偏移量 
  3. //byteslength 數(shù)據(jù)流的長度 
  4.  public int read(byte[] buffers, int offset, int bytesLength){ 
  5.         return audioCapturer.read(buffers,offset,bytesLength); 
  6.     } 
  7.  
  8. //獲取AudioCapturer的實(shí)例audioCapturer 
  9.   public AudioCapturer get(){ 
  10.         return this.audioCapturer; 
  11.     } 

3.創(chuàng)建語音識別的工具類

在上面我們已經(jīng)創(chuàng)建好一個音頻錄制的工具類,接下來在創(chuàng)建一個語音識別的工具類 AsrUtils。

我們再回顧一下語音識別的約束與限制:

鴻蒙AI能力之語音識別-鴻蒙HarmonyOS技術(shù)社區(qū)

在此補(bǔ)充一個隱藏限制,PCM流的長度只允許640與1280兩種長度,也就是我們音頻讀取流時只能使用640與1280兩種長度。

接下來我們定義一些基本常量:

  1. //采樣率限定16000HZ 
  2.    private static final int VIDEO_SAMPLE_RATE = 16000; 
  3. //VAD結(jié)束時間 默認(rèn)2000ms 
  4.    private static final int VAD_END_WAIT_MS = 2000; 
  5. //VAD起始時間 默認(rèn)4800ms  
  6. //這兩參數(shù)與識別準(zhǔn)確率有關(guān),相關(guān)信息可百度查看,在此使用系統(tǒng)默認(rèn) 
  7.    private static final int VAD_FRONT_WAIT_MS = 4800; 
  8. //輸入時常 20000ms 
  9.    private static final int TIMEOUT_DURATION = 20000; 
  10.  
  11. //PCM流長度僅限640或1280 
  12.    private static final int BYTES_LENGTH = 1280; 
  13. //線程池相關(guān)參數(shù) 
  14.    private static final int CAPACITY = 6; 
  15.    private static final int ALIVE_TIME = 3; 
  16.    private static final int POOL_SIZE = 3; 

 因?yàn)橐诤笈_持續(xù)錄制音頻,所以需要開辟一個新的線程。此處用到j(luò)ava的ThreadPoolExecutor類進(jìn)行線程操作。

定義一個線程池實(shí)例以及其它相關(guān)屬性如下:

  1. //錄音線程 
  2.    private ThreadPoolExecutor poolExecutor;  
  3.  /* 自定義狀態(tài)信息 
  4.     **  錯誤:-1 
  5.     **  初始:0 
  6.     **  init:1 
  7.     **  開始輸入:2 
  8.     **  結(jié)束輸入:3 
  9.     **  識別結(jié)束:5 
  10.     **  中途出識別結(jié)果:9 
  11.     **  最終識別結(jié)果:10 
  12.     */ 
  13.    public int state = 0; 
  14.    //識別結(jié)果 
  15.    public String result; 
  16. //是否開啟語音識別 
  17. //當(dāng)開啟時才寫入PCM流 
  18.    boolean isStarted = false
  19.  
  20. //ASR客戶端 
  21.    private AsrClient asrClient; 
  22. //ASR監(jiān)聽對象 
  23.    private AsrListener listener; 
  24.    AsrIntent asrIntent; 
  25. //音頻錄制工具類 
  26.    private AudioCaptureUtils audioCaptureUtils; 

在構(gòu)造函數(shù)中初始化相關(guān)屬性:

  1. public AsrUtils(Context context) { 
  2.         //實(shí)例化一個單聲道,采集頻率16000HZ的音頻錄制工具類實(shí)例 
  3.         this.audioCaptureUtils = new AudioCaptureUtils(AudioStreamInfo.ChannelMask.CHANNEL_IN_MONO, VIDEO_SAMPLE_RATE); 
  4.         //初始化降噪音效 
  5.         this.audioCaptureUtils.init("com.panda_coder.liedetector"); 
  6.         //結(jié)果值設(shè)為空 
  7.         this.result = ""
  8.         //給錄音控件初始化一個新的線程池 
  9.         poolExecutor = new ThreadPoolExecutor( 
  10.                 POOL_SIZE, 
  11.                 POOL_SIZE, 
  12.                 ALIVE_TIME, 
  13.                 TimeUnit.SECONDS, 
  14.                 new LinkedBlockingQueue<>(CAPACITY), 
  15.                 new ThreadPoolExecutor.DiscardOldestPolicy()); 
  16.  
  17.         if (asrIntent == null) { 
  18.             asrIntent = new AsrIntent(); 
  19.             //設(shè)置音頻來源為PCM流 
  20.             //此處也可設(shè)置為文件             
  21.             asrIntent.setAudioSourceType(AsrIntent.AsrAudioSrcType.ASR_SRC_TYPE_PCM); 
  22.             asrIntent.setVadEndWaitMs(VAD_END_WAIT_MS); 
  23.             asrIntent.setVadFrontWaitMs(VAD_FRONT_WAIT_MS); 
  24.             asrIntent.setTimeoutThresholdMs(TIMEOUT_DURATION); 
  25.         } 
  26.  
  27.         if (asrClient == null) { 
  28.             //實(shí)例化AsrClient 
  29.             asrClient = AsrClient.createAsrClient(context).orElse(null); 
  30.         } 
  31.         if (listener == null) { 
  32.             //實(shí)例化MyAsrListener 
  33.             listener = new MyAsrListener(); 
  34.             //初始化AsrClient 
  35.             this.asrClient.init(asrIntent, listener); 
  36.         } 
  37.      
  38.     } 
  39.  
  40. //夠建一個實(shí)現(xiàn)AsrListener接口的類MyAsrListener  
  41.  class MyAsrListener implements AsrListener { 
  42.  
  43.         @Override 
  44.         public void onInit(PacMap pacMap) { 
  45.             HiLog.info(TAG, "====== init"); 
  46.             state = 1; 
  47.         } 
  48.  
  49.         @Override 
  50.         public void onBeginningOfSpeech() { 
  51.             state = 2; 
  52.         } 
  53.  
  54.         @Override 
  55.         public void onRmsChanged(float v) { 
  56.  
  57.         } 
  58.  
  59.         @Override 
  60.         public void onBufferReceived(byte[] bytes) { 
  61.  
  62.         } 
  63.  
  64.         @Override 
  65.         public void onEndOfSpeech() { 
  66.             state = 3; 
  67.         } 
  68.  
  69.         @Override 
  70.         public void onError(int i) { 
  71.             state = -1; 
  72.             if (i == AsrError.ERROR_SPEECH_TIMEOUT) { 
  73.                 //當(dāng)超時時重新監(jiān)聽 
  74.                 asrClient.startListening(asrIntent); 
  75.             } else { 
  76.                 HiLog.info(TAG, "======error code:" + i); 
  77.                 asrClient.stopListening(); 
  78.             } 
  79.         } 
  80.  
  81.         //注意與onIntermediateResults獲取結(jié)果值的區(qū)別 
  82.         //pacMap.getString(AsrResultKey.RESULTS_RECOGNITION); 
  83.         @Override 
  84.         public void onResults(PacMap pacMap) { 
  85.             state = 10; 
  86.             //獲取最終結(jié)果  
  87.             //{"result":[{"confidence":0,"ori_word":"你 好 ","pinyin":"NI3 HAO3 ","word":"你好。"}]} 
  88.             String results = pacMap.getString(AsrResultKey.RESULTS_RECOGNITION); 
  89.             ZSONObject zsonObject = ZSONObject.stringToZSON(results); 
  90.             ZSONObject infoObject; 
  91.             if (zsonObject.getZSONArray("result").getZSONObject(0) instanceof ZSONObject) { 
  92.                 infoObject = zsonObject.getZSONArray("result").getZSONObject(0); 
  93.                 String resultWord = infoObject.getString("ori_word").replace(" """); 
  94.                 result += resultWord; 
  95.             } 
  96.         } 
  97.  
  98.         //中途識別結(jié)果 
  99.         //pacMap.getString(AsrResultKey.RESULTS_INTERMEDIATE) 
  100.         @Override 
  101.         public void onIntermediateResults(PacMap pacMap) { 
  102.             state = 9; 
  103. //            String result = pacMap.getString(AsrResultKey.RESULTS_INTERMEDIATE); 
  104. //            if (result == null
  105. //                return
  106. //            ZSONObject zsonObject = ZSONObject.stringToZSON(result); 
  107. //            ZSONObject infoObject; 
  108. //            if (zsonObject.getZSONArray("result").getZSONObject(0) instanceof ZSONObject) { 
  109. //                infoObject = zsonObject.getZSONArray("result").getZSONObject(0); 
  110. //                String resultWord = infoObject.getString("ori_word").replace(" """); 
  111. //                HiLog.info(TAG, "=========== 9 " + resultWord); 
  112. //            } 
  113.         } 
  114.  
  115.  
  116.         @Override 
  117.         public void onEnd() { 
  118.             state = 5; 
  119.             //當(dāng)還在錄音時,重新監(jiān)聽 
  120.             if (isStarted) 
  121.                 asrClient.startListening(asrIntent); 
  122.         } 
  123.  
  124.         @Override 
  125.         public void onEvent(int i, PacMap pacMap) { 
  126.  
  127.         } 
  128.  
  129.         @Override 
  130.         public void onAudioStart() { 
  131.             state = 2; 
  132.  
  133.         } 
  134.  
  135.         @Override 
  136.         public void onAudioEnd() { 
  137.             state = 3; 
  138.         } 
  139.     } 

開啟識別與停止識別的函數(shù):

  1. public void start() { 
  2.      if (!this.isStarted) { 
  3.          this.isStarted = true
  4.          asrClient.startListening(asrIntent); 
  5.          poolExecutor.submit(new AudioCaptureRunnable()); 
  6.      } 
  7.  } 
  8.  
  9.  public void stop() { 
  10.      this.isStarted = false
  11.      asrClient.stopListening(); 
  12.      audioCaptureUtils.stop(); 
  13.  } 
  14.  
  15. //音頻錄制的線程 
  16.  private class AudioCaptureRunnable implements Runnable { 
  17.      @Override 
  18.      public void run() { 
  19.          byte[] buffers = new byte[BYTES_LENGTH]; 
  20. //開啟錄音 
  21.          audioCaptureUtils.start(); 
  22.          while (isStarted) { 
  23.     //讀取錄音的PCM流 
  24.              int ret = audioCaptureUtils.read(buffers, 0, BYTES_LENGTH); 
  25.              if (ret <= 0) { 
  26.                  HiLog.error(TAG, "======Error read data"); 
  27.              } else { 
  28.         //將錄音的PCM流寫入到語音識別服務(wù)中 
  29.         //若buffer的長度不為1280或640時,則需要手動處理成1280或640 
  30.                  asrClient.writePcm(buffers, BYTES_LENGTH); 
  31.              } 
  32.          } 
  33.      } 
  34.  } 

識別結(jié)果是通過listener的回調(diào)獲取的結(jié)果,所以我們在處理時是將結(jié)果賦值給result,通過getresult或getResultAndClear函數(shù)獲取結(jié)果。

  1. public String getResult() { 
  2.        return result; 
  3.    } 
  4.  
  5.    public String getResultAndClear() { 
  6.        if (this.result == ""
  7.            return ""
  8.        String results = getResult(); 
  9.        this.result = ""
  10.        return results; 
  11.    } 

4.創(chuàng)建一個簡易的JS UI,并通過JS調(diào)ServerAbility的能力調(diào)用Java

hml代碼:

  1. <div class="container"
  2.     <div> 
  3.         <button class="btn" @touchend="start">開啟</button> 
  4.         <button class="btn" @touchend="sub">訂閱結(jié)果</button> 
  5.         <button class="btn" @touchend="stop">關(guān)閉</button> 
  6.     </div> 
  7.     <text class="title"
  8.         語音識別內(nèi)容: {{ text }} 
  9.     </text> 
  10. </div> 

樣式代碼:

  1. .container { 
  2.     flex-direction: column
  3.     justify-content: flex-start; 
  4.     align-items: center; 
  5.     width: 100%; 
  6.     height: 100%; 
  7.     padding: 10%; 
  8.  
  9. .title { 
  10.     font-size: 20px; 
  11.     color: #000000; 
  12.     opacity: 0.9; 
  13.     text-align: left
  14.     width: 100%; 
  15.     margin: 3% 0; 
  16.  
  17. .btn{ 
  18.     padding: 10px 20px; 
  19.     margin:3px; 
  20.     border-radius: 6px; 

 js邏輯控制代碼:

  1. //js調(diào)Java ServiceAbility的工具類 
  2. import { jsCallJavaAbility } from '../../common/JsCallJavaAbilityUtils.js'
  3.  
  4. export default { 
  5.     data: { 
  6.         text: "" 
  7.     }, 
  8.     //開啟事件 
  9.     start() { 
  10.         jsCallJavaAbility.callAbility("ControllerAbility",100,{}).then(result=>{ 
  11.             console.log(result) 
  12.         }) 
  13.     }, 
  14. //關(guān)閉事件 
  15.     stop() { 
  16.         jsCallJavaAbility.callAbility("ControllerAbility",101,{}).then(result=>{ 
  17.             console.log(result) 
  18.         }) 
  19.         jsCallJavaAbility.unSubAbility("ControllerAbility",201).then(result=>{ 
  20.             if (result.code == 200) { 
  21.                 console.log("取消訂閱成功"); 
  22.             } 
  23.         }) 
  24.     }, 
  25. //訂閱Java端結(jié)果事件 
  26.     sub() { 
  27.         jsCallJavaAbility.subAbility("ControllerAbility", 200, (data) => { 
  28.             let text = data.data.text 
  29.             text && (this.text += text) 
  30.         }).then(result => { 
  31.             if (result.code == 200) { 
  32.                 console.log("訂閱成功"); 
  33.             } 
  34.         }) 
  35.     } 

ServerAbility:

  1. public class ControllerAbility extends Ability { 
  2.     AnswerRemote remote = new AnswerRemote(); 
  3.     AsrUtils asrUtils; 
  4.     //訂閱事件的委托 
  5.     private static HashMap<Integer, IRemoteObject> remoteObjectHandlers = new HashMap<Integer, IRemoteObject>(); 
  6.  
  7.     @Override 
  8.     public void onStart(Intent intent) { 
  9.         HiLog.error(LABEL_LOG, "ControllerAbility::onStart"); 
  10.         super.onStart(intent); 
  11.     //初始化語音識別工具類 
  12.         asrUtils = new AsrUtils(this); 
  13.     } 
  14.  
  15.  
  16.     @Override 
  17.     public void onCommand(Intent intent, boolean restart, int startId) { 
  18.     } 
  19.  
  20.     @Override 
  21.     public IRemoteObject onConnect(Intent intent) { 
  22.         super.onConnect(intent); 
  23.         return remote.asObject(); 
  24.     } 
  25.  
  26.     class AnswerRemote extends RemoteObject implements IRemoteBroker { 
  27.         AnswerRemote() { 
  28.             super(""); 
  29.         } 
  30.  
  31.         @Override 
  32.         public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) { 
  33.             Map<String, Object> zsonResult = new HashMap<String, Object>(); 
  34.             String zsonStr = data.readString(); 
  35.             ZSONObject zson = ZSONObject.stringToZSON(zsonStr); 
  36.             switch (code) { 
  37.                 case 100: { 
  38.             //當(dāng)js發(fā)送code為100時,開啟語音識別 
  39.                     asrUtils.start(); 
  40.                     break; 
  41.                 } 
  42.                 case 101: { 
  43.             //當(dāng)js發(fā)送code為101時,關(guān)閉語音識別 
  44.                     asrUtils.stop(); 
  45.                     break; 
  46.                 } 
  47.                 case 200: { 
  48.             //當(dāng)js發(fā)送code為200時,訂閱獲取識別結(jié)果事件 
  49.                     remoteObjectHandlers.put(200 ,data.readRemoteObject()); 
  50.             //定時獲取語音識別結(jié)果并返回JS UI                     
  51.                 getAsrText(); 
  52.                     break; 
  53.                 } 
  54.                 default: { 
  55.                     reply.writeString("service not defined"); 
  56.                     return false
  57.                 } 
  58.             } 
  59.             reply.writeString(ZSONObject.toZSONString(zsonResult)); 
  60.             return true
  61.         } 
  62.  
  63.         @Override 
  64.         public IRemoteObject asObject() { 
  65.             return this; 
  66.         } 
  67.     } 
  68.  
  69.     public void getAsrText() { 
  70.         new Thread(() -> { 
  71.             while (true) { 
  72.                 try { 
  73.                     Thread.sleep(1 * 500); 
  74.                     Map<String, Object> zsonResult = new HashMap<String, Object>(); 
  75.                     zsonResult.put("text",asrUtils.getResultAndClear()); 
  76.                     ReportEvent(200, zsonResult); 
  77.  
  78.                 } catch (RemoteException | InterruptedException e) { 
  79.                     break; 
  80.                 } 
  81.             } 
  82.         }).start(); 
  83.     } 
  84.  
  85.     private void ReportEvent(int remoteHandler, Object backData) throws RemoteException { 
  86.         MessageParcel data = MessageParcel.obtain(); 
  87.         MessageParcel reply = MessageParcel.obtain(); 
  88.         MessageOption option = new MessageOption(); 
  89.         data.writeString(ZSONObject.toZSONString(backData)); 
  90.         IRemoteObject remoteObject = remoteObjectHandlers.get(remoteHandler); 
  91.         remoteObject.sendRequest(100, data, reply, option); 
  92.         reply.reclaim(); 
  93.         data.reclaim(); 
  94.     } 
  95.  

至此簡易的語音識別功能完畢。

相關(guān)演示:https://www.bilibili.com/video/BV1E44y177hv/ 

完整代碼開源:https://gitee.com/panda-coder/harmonyos-apps/tree/master/AsrDemo

想了解更多內(nèi)容,請訪問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

 

責(zé)任編輯:jianghua 來源: 鴻蒙社區(qū)
相關(guān)推薦

2021-05-06 11:13:06

人工智能語音識別

2021-05-06 11:18:23

人工智能語音識別

2022-02-17 17:19:31

鴻蒙語音識別語音播報

2011-05-31 16:38:47

Android 實(shí)現(xiàn)語音

2016-02-17 10:39:18

語音識別語音合成語音交互

2023-07-06 08:41:20

TTS?Mac?系統(tǒng)

2021-11-23 09:58:35

鴻蒙HarmonyOS應(yīng)用

2009-08-21 15:28:23

C#英文

2011-01-18 11:52:25

Linux語音識別

2009-07-21 15:28:06

Windows Emb

2020-09-21 07:00:00

語音識別AI人工智能

2022-12-01 07:03:22

語音識別人工智能技術(shù)

2014-05-04 13:39:15

人臉識別算法

2017-10-27 16:19:23

語音識別CNN

2021-11-17 10:37:39

語音識別技術(shù)人工智能

2019-10-12 17:42:33

2015-10-23 13:41:20

android源碼科大訊飛語音識別

2017-03-20 10:14:03

語音識別匹配算法模型

2024-01-08 19:30:15

AI開源語音識別
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號