HarmonyOS Sample之AI能力应用之报菜名儿

开发 前端 OpenHarmony
照片拍摄的界面是MainAbilitySlice.java,主要是申请权限的代码,初始化组件然后调用相机工具类实现拍摄功能。

[[437606]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

一、介绍

小的时候就看过一个节目,就报菜名,一直印象很深刻,“蒸羊羔、蒸熊掌、蒸鹿尾儿、烧花鸭、烧雏鸡儿、烧子鹅、卤煮咸鸭、酱鸡、腊肉、松花、小肚儿”,历历在目。

前几天,家里的小朋友在背诵校园童谣,背古诗,也得帮着儿,就想着可以结合AI能力开发一个小的应用,具备以下2个能力:

  • 支持拍照,然后能提取出照片中的文字。(相机功能+通用文字提取能力)
  • 能把提取的文字朗读出来,最后能通过语音进行控制。(Tts能力+Asr语音识别)

使用过程是这样的,

拍摄照片—>预览照片---->提取文字---->朗读内容---->实现语音控制菜单(可选)

二、效果展示

 HarmonyOS Sample 之 AI能力应用之 报菜名儿-鸿蒙HarmonyOS技术社区

三、搭建环境

安装DevEco Studio,详情请参考DevEco Studio下载

设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:

如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。

如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境

下载源码后,

使用DevEco Studio 打开项目,需要在真机上运行,参见 使用远程真机运行应用

四、项目结构

 HarmonyOS Sample 之 AI能力应用之 报菜名儿-鸿蒙HarmonyOS技术社区
 HarmonyOS Sample 之 AI能力应用之 报菜名儿-鸿蒙HarmonyOS技术社区

五、代码讲解

5.1 拍摄照片介绍(相机功能)

照片拍摄的界面是MainAbilitySlice.java,主要是申请权限的代码,初始化组件然后调用相机工具类实现拍摄功能。相机工具类参考了官方给的AI相机的示例代码,CameraUtil.java 提供了拍摄画面预览、拍照、切换前后摄像头、保存照片的基础功能,核心代码如下:

1.初始化相机预览画面

  1. /** 
  2.  * 初始化屏幕 
  3.  * 
  4.  * @param surfaceContainer 
  5.  */ 
  6. public void initSurface(Component surfaceContainer) { 
  7.     HiLog.info(LABEL, "initSurface"); 
  8.     //相机屏幕 
  9.     surfaceProvider = new SurfaceProvider(slice); 
  10.     DirectionalLayout.LayoutConfig params = 
  11.             new DirectionalLayout.LayoutConfig( 
  12.                     ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT); 
  13.  
  14.     surfaceProvider.setLayoutConfig(params); 
  15.     //toTop --指定是否将Surface固定在最上面,值为true表示将Surface放在AGP容器组件的顶层,false表示相反。 
  16.     surfaceProvider.pinToZTop(true); 
  17.  
  18.     surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack()); 
  19.  
  20.     if (surfaceContainer instanceof ComponentContainer) { 
  21.         ((ComponentContainer) surfaceContainer).addComponent(surfaceProvider); 
  22.     } 

2.打开相机预览画面

  1. private void openCamera() { 
  2.     HiLog.info(LABEL, "openCamera"); 
  3.     //图像帧数据接收处理对象 

  4.     imageReceiver = ImageReceiver.create(SCREEN_WIDTH, SCREEN_HEIGHT, ImageFormat.JPEG, IMAGE_RCV_CAPACITY); 
  5.     //新图像接收监听 
  6.     imageReceiver.setImageArrivalListener(this::saveImage); 
  7.     //获取相机组件实例对象 
  8.     CameraKit cameraKit = CameraKit.getInstance(slice.getApplicationContext()); 
  9.     if (cameraKit == null) { 
  10.         // 处理cameraKit获取失败的情况 
  11.         HiLog.error(LABEL, "CameraKit getInstance error"); 
  12.     }else
  13.         //获取相机id列表 
  14.         String[] cameraList = cameraKit.getCameraIds(); 
  15.         //获取前/后相机ID 
  16.         String cameraId = cameraList.length > 1 && isCameraRear ? cameraList[1] : cameraList[0]; 
  17.         //相机状态回调 
  18.         CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl(); 
  19.         //创建相机 
  20.         cameraKit.createCamera(cameraId, cameraStateCallback, creamEventHandler); 
  21.     } 
  22.  
  23. /** 
  24.  * 相机状态回调 
  25.  * CameraStateCallbackImpl 
  26.  * 
  27.  * @since 2021-03-08 
  28.  */ 
  29. class CameraStateCallbackImpl extends CameraStateCallback { 
  30.     CameraStateCallbackImpl() { 
  31.     } 
  32.  
  33.     @Override 
  34.     public void onCreated(Camera camera) { 
  35.         HiLog.info(LABEL, "onCreated"); 
  36.         //获取预览 
  37.         previewSurface = surfaceProvider.getSurfaceOps().get().getSurface(); 
  38.         if (previewSurface == null) { 
  39.             HiLog.info(LABEL, "create camera filed, preview surface is null"); 
  40.             return
  41.         } 
  42.  
  43.         try { 
  44.             Thread.sleep(SLEEP_TIME); 
  45.         } catch (InterruptedException exception) { 
  46.             HiLog.info(LABEL, "Waiting to be interrupted"); 
  47.         } 
  48.  
  49.         //提供配置相机的方法,例如添加或移除表面,以及设置相机运行模式。 
  50.         CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder(); 
  51.         //配置预览的Surfac 
  52.         cameraConfigBuilder.addSurface(previewSurface); 
  53.         //配置拍照的Surface 
  54.         cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface()); 
  55.  
  56.         //相机设备配置 
  57.         camera.configure(cameraConfigBuilder.build()); 
  58.         cameraDevice = camera; 
  59.  
  60.         //相机已准备好,eventId=2,设置拍照/切换摄像头按钮可用 
  61.         handler.sendEvent(2); 
  62.     } 
  63.     ... 
  64.  }  
  65.  
  66. /** 
  67.  * 相机预览画面回调 
  68.  * 提供为表面操作添加或删除回调的方法。 
  69.  */ 
  70. class SurfaceCallBack implements SurfaceOps.Callback { 
  71.     @Override 
  72.     public void surfaceCreated(SurfaceOps callbackSurfaceOps) { 
  73.         if (callbackSurfaceOps != null) { 
  74.             callbackSurfaceOps 
  75.                     .setFixedSize(surfaceProvider.getHeight(), surfaceProvider.getWidth()); 
  76.         } 
  77.         openCamera(); 
  78.     } 
  79.  
  80.     @Override 
  81.     public void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) { 
  82.     } 
  83.  
  84.     @Override 
  85.     public void surfaceDestroyed(SurfaceOps callbackSurfaceOps) { 
  86.     } 

3.实现拍照

  1. /** 
  2.  * 拍照 
  3.  */ 
  4. public void takePicture(Component takePictureImage) { 
  5.     HiLog.info(LABEL, "takePicture"); 
  6.     if (!takePictureImage.isEnabled()) { 
  7.         HiLog.info(LABEL, "takePicture return"); 
  8.         return
  9.     } 
  10.     if (cameraDevice == null || imageReceiver == null) { 
  11.         return
  12.     } 
  13.  
  14.     //添加一个表面作为帧捕获输出。 
  15.     FrameConfig.Builder framePictureConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PICTURE); 
  16.      //添加图像接收Surface 
  17.     framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface()); 
  18.  
  19.     //提供用于帧捕获配置、图像处理和图像输出的方法。 
  20.     FrameConfig pictureFrameConfig = framePictureConfigBuilder.build(); 
  21.  
  22.     //开始单帧捕捉。 
  23.     cameraDevice.triggerSingleCapture(pictureFrameConfig); 

4.保存照片

  1. /** 
  2.  * 保存照片到存储设备 
  3.  * 并复制一份到分布式文件系统 
  4.  * 
  5.  * @param receiver 
  6.  */ 
  7. public void saveImage(ImageReceiver receiver) { 
  8.     //文件起名 
  9.     fileName = IMG_FILE_PREFIX + System.currentTimeMillis() + IMG_FILE_TYPE; 
  10.     //外部存储的文件目录 
  11.     targetFile = new File(slice.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName); 
  12.     try { 
  13.         HiLog.info(LABEL, "filePath is " + targetFile.getCanonicalPath()); 
  14.     } catch (IOException e) { 
  15.         HiLog.error(LABEL, "filePath is error"); 
  16.     } 
  17.  
  18.     ohos.media.image.Image image = receiver.readNextImage(); 
  19.     if (image == null) { 
  20.         return
  21.     } 
  22.     ohos.media.image.Image.Component component = image.getComponent(ImageFormat.ComponentType.JPEG); 
  23.     bytes = new byte[component.remaining()]; 
  24.     component.read(bytes); 
  25.     try (FileOutputStream output = new FileOutputStream(targetFile)) { 
  26.         output.write(bytes); 
  27.         output.flush(); 
  28.  
  29.         //保存参数 
  30.         Map<String, Object> dataMap = new HashMap<>(); 
  31.         dataMap.put("fileName", fileName); 
  32.         dataMap.put("remaining", bytes); 
  33.  
  34.         //发送事件 
  35.         InnerEvent event = InnerEvent.get(EVENT_IMAGESAVING_PROMTING, 1, dataMap); 
  36.         handler.sendEvent(event); 
  37.  
  38.         //复制图片到分布式文件系统目录 
  39.         DistributeFileUtil.copyPicToDistributedDir(slice, targetFile, fileName); 
  40.     } catch (IOException e) { 
  41.         HiLog.info(LABEL, "IOException, Save image failed"); 
  42.     } 

5.相机底部缩略图的处理

用到了MyDrawTask.java工具类,它提供了将像素矩阵绘制到image组件的功能,同时处理了缩略图的大小等样式。

  1. ** 
  2.  * 绘制任务 
  3.  * 绘制一个照片缩略图 
  4.  * @since 2021-03-08 
  5.  */ 
  6. public class MyDrawTask implements Component.DrawTask { 
  7.     private static final HiLogLabel LABEL = new HiLogLabel(LOG_APP, 0x00204, "=>" + MyDrawTask.class.getName()); 
  8.  
  9.     private static final int HALF = 2; 
  10.     private static final int STROKE_WIDTH = 3; 
  11.     private static final int MAX_SIZE = 66; 
  12.     private Component component; 
  13.     private PixelMap pixelMap; 
  14.     private RectFloat rectSrc; 
  15.  
  16.     /** 
  17.      * MyDrawTask 
  18.      * 
  19.      * @param component component 
  20.      */ 
  21.     public MyDrawTask(Component component) { 
  22.         HiLog.info(LABEL, "MyDrawTask"); 
  23.         this.component = component; 
  24.     } 
  25.  
  26.     /** 
  27.      * putPixelMap 
  28.      * 以像素矩阵的形式提供图像 
  29.      * @param pm Provides images in forms of pixel matrices 
  30.      */ 
  31.     public void putPixelMap(PixelMap pm) { 
  32.         if (pm != null) { 
  33.             pixelMap = pm; 
  34.             int halfWidth = pixelMap.getImageInfo().size.width / HALF; 
  35.             int halfHeight = pixelMap.getImageInfo().size.height / HALF; 
  36.             int center = Math.min(halfWidth, halfHeight); 
  37.             rectSrc = new RectFloat(halfWidth - center, halfHeight - center, halfWidth + center, halfHeight + center); 
  38.             component.invalidate(); 
  39.         } 
  40.     } 
  41.  
  42.     @Override 
  43.     public void onDraw(Component component1, Canvas canvas) { 
  44.         int halfWidth = component.getWidth() / HALF; 
  45.         int halfHeight = component.getHeight() / HALF; 
  46.         int center = Math.min(halfWidth, halfHeight); 
  47.         if (center > MAX_SIZE) { 
  48.             center = MAX_SIZE; 
  49.         } 
  50.  
  51.         int radius = center - STROKE_WIDTH / HALF; 
  52.         if (pixelMap != null) { 
  53.             canvas.drawPixelMapHolderCircleShape(new PixelMapHolder(pixelMap), rectSrc, halfWidth, halfHeight, radius); 
  54.         } 
  55.  
  56.         Paint roundPaint = new Paint(); 
  57.         roundPaint.setAntiAlias(true); 
  58.         roundPaint.setStyle(Paint.Style.STROKE_STYLE); 
  59.         roundPaint.setStrokeWidth(STROKE_WIDTH); 
  60.         roundPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP); 
  61.         roundPaint.setColor(Color.WHITE); 
  62.         canvas.drawCircle(halfWidth, halfHeight, radius, roundPaint); 
  63.     } 

工具类中使用 EventHandler对象,实现跨线程通知的目的。

以上需要ohos.permission.CAMERA和ohos.permission.WRITE_MEDIA权限。

5.2 预览照片介绍(PixelMap的使用)

预览照片的界面是ImageAbilitySlice.java 主要是读取本地存储的照片后并做显示。

  1. /** 
  2.  * 本地读取照片并显示 
  3.  * 
  4.  * @param picPath 
  5.  */ 
  6. private void setImage(String picPath) { 
  7.     HiLog.info(LABEL, "setImage"); 
  8.     if (picPath != null && !picPath.isEmpty()) { 
  9.         PixelMap pixelMap = PixelMapUtil.getPixelMapByLocalPath(null, picPath, 1); 
  10.         bigImage.setPixelMap(pixelMap); 
  11.     } 
  1. /** 
  2.  * 将图片转换为PixeMap便于使用 
  3.  */ 
  4. public static PixelMap getPixelMapByLocalPath(byte[] imageBytes, String imagePath, int rotateCount) { 
  5.     ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions(); 
  6.     sourceOptions.formatHint = "image/jpg"
  7.     ImageSource imageSource; 
  8.     if (imagePath != null && !imagePath.isEmpty()) { 
  9.         imageSource = ImageSource.create(imagePath, sourceOptions); 
  10.     } else if (imageBytes != null) { 
  11.         imageSource = ImageSource.create(imageBytes, sourceOptions); 
  12.     } else { 
  13.         return null
  14.     } 
  15.     ImageSource.DecodingOptions decodingOpts = new ImageSource.DecodingOptions(); 
  16.     decodingOpts.desiredSize = new Size(0, 0); 
  17.     // 
  18.     decodingOpts.rotateDegrees = DEGREES_90 * rotateCount; 
  19.     decodingOpts.desiredPixelFormat = PixelFormat.ARGB_8888; 
  20.     return imageSource.createPixelmap(decodingOpts); 

5.3 提取文字介绍(通用文字识别能力)

WordRecognition.java //抽取了通用文字识别能力的工具类

TextDetectorAbilitySlice.java //文字识别能力页

1.init方法,传递要识别的图片路径和其他参数

  1. /** 
  2.  * 设置参数方法 
  3.  * 
  4.  * @param context      页面 
  5.  * @param picturePaths   图片集合 
  6.  * @param eventHandler MyEventHandle对象 
  7.  * @since 2021-01-21 
  8.  */ 
  9. public void init(Context context, String[] picturePaths, EventHandler eventHandler) { 
  10.     slice = context; 
  11.     pictureLists = picturePaths; 
  12.     handler = eventHandler; 

2.开始文字识别

  1. /** 
  2.  * 文字识别方法 
  3.  * 
  4.  * @param picPath picPath 
  5.  * @since 2021-01-21 
  6.  */ 
  7. public void wordRecognition(String picPath) { 
  8.     HiLog.debug(LABEL, "wordRecognition"); 
  9.     photoPath = picPath; 
  10.     // 实例化ITextDetector接口 
  11.     textDetector = VisionManager.getTextDetector(slice); 
  12.  
  13.     // 实例化VisionImage对象image,并传入待检测图片pixelMap 
  14.     pixelMap = PixelMapUtil.getPixelMapByLocalPath(null, picPath, 1); 
  15.  
  16.     VisionImage image = VisionImage.fromPixelMap(pixelMap); 
  17.  
  18.     // 定义VisionCallback<Text>回调,异步模式下用到 
  19.     VisionCallback<Text> visionCallback = getVisionCallback(); 
  20.  
  21.     // 定义ConnectionCallback回调,实现连接能力引擎成功与否后的操作 
  22.     ConnectionCallback connectionCallback = getConnectionCallback(image, visionCallback); 
  23.     // 建立与能力引擎的连接 
  24.     VisionManager.init(slice, connectionCallback); 

3.实现识别结果的回调函数

  1. /** 
  2.  * 提供接收服务状态变化的回调方法, 
  3.  * 当服务状态改变时调用该接口。 
  4.  * @param image 
  5.  * @param visionCallback 
  6.  * @return 
  7.  */ 
  8. private ConnectionCallback getConnectionCallback(VisionImage image, VisionCallback<Text> visionCallback) { 
  9.     return new ConnectionCallback() { 
  10.         @Override 
  11.         public void onServiceConnect() { 
  12.             HiLog.debug(LABEL, "onServiceConnect"); 
  13.             //存储给定图像中文本的检测结果。 
  14.             Text text = new Text(); 
  15.  
  16.             // 通过TextConfiguration配置textDetector()方法的运行参数 
  17.             TextConfiguration.Builder builder = new TextConfiguration.Builder(); 
  18.             //MODE_IN模式表示SDK在调用者进程中直接调用OCR函数。 
  19.             //MODE_OUT模式表示SDK作为客户端向HUAWEI HiAI Engine发送调用请求,由HUAWEI HiAI Engine进行OCR并将结果返回给调用方。 
  20.             builder.setProcessMode(VisionConfiguration.MODE_IN); 
  21.  
  22.             builder.setDetectType(TextDetectType.TYPE_TEXT_DETECT_FOCUS_SHOOT); 
  23.             builder.setLanguage(TextConfiguration.AUTO); 
  24.  
  25.             TextConfiguration config = builder.build(); 
  26.             textDetector.setVisionConfiguration(config); 
  27.  
  28.             // 调用ITextDetector的detect()方法 
  29.             if (IS_SYNC) { 
  30.                 HiLog.debug(LABEL, "IS_SYNC"); 
  31.                 //result不为0,说明当前OCR能力准备失败 
  32.                 result = textDetector.detect(image, text, null); // 同步 
  33.                 HiLog.debug(LABEL, "IS_SYNC,result:" + result); 
  34.                 // 
  35.                 sendResult(text.getValue()); 
  36.  
  37.             } else { 
  38.                 HiLog.debug(LABEL, "!IS_SYNC"); 
  39.                 //result=700:异步调用请求发送成功 
  40.                 result = textDetector.detect(image, null, visionCallback); // 异步 
  41.                 HiLog.debug(LABEL, "!IS_SYNC,result:" + result); 
  42.             } 
  43.         } 
  44.  
  45.         @Override 
  46.         public void onServiceDisconnect() { 
  47.             HiLog.debug(LABEL, "onServiceDisconnect"); 
  48.             // 释放 
  49.             if ((IS_SYNC && result == 0 && textDetector != null)) { 
  50.                 textDetector.release(); 
  51.             } 
  52.             if (pixelMap != null) { 
  53.                 pixelMap.release(); 
  54.                 pixelMap = null
  55.             } 
  56.             //断开HUAWEI HiAI Engine服务。 
  57.             VisionManager.destroy(); 
  58.         } 
  59.     }; 

4.异步模式下的回调函数

  1. /** 
  2.  * 异步模式下回调 
  3.  * 
  4.  * @return 
  5.  */ 
  6. private VisionCallback<Text> getVisionCallback() { 
  7.     return new VisionCallback<Text>() { 
  8.         @Override 
  9.         public void onResult(Text text) { 
  10.             HiLog.debug(LABEL, "onResult"); 
  11.             //异步情况下返回识别结果后发送 
  12.             sendResult(text.getValue()); 
  13.         } 
  14.  
  15.         @Override 
  16.         public void onError(int index) { 
  17.             HiLog.debug(LABEL, "onError"); 
  18.         } 
  19.  
  20.         @Override 
  21.         public void onProcessing(float value) { 
  22.             HiLog.debug(LABEL, "onProcessing"); 
  23.         } 
  24.     }; 

最后是通过EventHandler对象将识别的结果发送给TextDetectorAbilitySlice.java

进行界面展示。

5.4 朗读内容介绍(Tts能力)

Tts能力封装在TtsUtil.java工具类,核心类包括:

  1. import ohos.ai.tts.TtsClient; // TTS客户端 
  2. import ohos.ai.tts.TtsListener; // TTS 监听接口 
  3. import ohos.ai.tts.TtsParams; // TTS参数 
  4. import ohos.ai.tts.constants.TtsEvent; // TTS事件 
  5. import ohos.utils.PacMap; // TTS依赖 

1.初始化参数

  1. /** 
  2.  * 初始化参数 
  3.  * 
  4.  * @param context      Slice对象 
  5.  * @param eventHandler 事件传递对象 
  6.  * @since 2021-01-21 
  7.  */ 
  8. public void init(Context context, EventHandler eventHandler) { 
  9.     slice = context; 
  10.     handler = eventHandler; 
  11.     //创建Tts客户端 
  12.     TtsClient.getInstance().create(slice, ttsListener); 

2.实例化事件监听

  1. /** 
  2.  * 实例化TTS 事件监听 
  3.  */ 
  4. private TtsListener ttsListener = new TtsListener() { 
  5.     @Override 
  6.     public void onEvent(int eventType, PacMap pacMap) { 
  7.         HiLog.info(LABEL, "onEvent...eventType:" + eventType); 
  8.         // 定义TTS客户端创建成功的回调函数 
  9.         if (eventType == TtsEvent.CREATE_TTS_CLIENT_SUCCESS) { 
  10.             TtsParams ttsParams = new TtsParams(); 
  11.             //必填 
  12.             ttsParams.setDeviceId(UUID.randomUUID().toString()); 
  13.             //语速 
  14.             //ttsParams.setSpeed(8); 
  15.             //ttsParams.setSpeaker(SPEAKER_XIAOYI); 
  16.             //ttsParams.setVolume(4); 
  17.  
  18.             //Tts初始化结果 
  19.             createSucc = TtsClient.getInstance().init(ttsParams); 
  20.             //发送初始化成功事件 
  21.             sendResult(createSucc + "", 4); 
  22.         } 
  23.     } 
  24.  
  25.     @Override 
  26.     public void onSpeechStart(String utteranceId) { 
  27.         HiLog.info(LABEL, "onSpeechStart..."); 
  28.         sendResult("开始朗读", 5); 
  29.     } 
  30.  
  31.     @Override 
  32.     public void onSpeechFinish(String utteranceId) { 
  33.         HiLog.info(LABEL, "onSpeechFinish..."); 
  34.         sendResult("朗读完成", 6); 
  35.     } 
  36.  
  37.     @Override 
  38.     public void onStart(String utteranceId) { 
  39.         HiLog.info(LABEL, "onStart..."); 
  40.     } 
  41.  
  42.     @Override 
  43.     public void onProgress(String utteranceId, byte[] audioData, int progress) { 
  44.     } 
  45.  
  46.     @Override 
  47.     public void onFinish(String utteranceId) { 
  48.         HiLog.info(LABEL, "onFinish..."); 
  49.     } 
  50.  
  51.     @Override 
  52.     public void onError(String s, String s1) { 
  53.         HiLog.info(LABEL, "onError...s:" + s + ",s1:" + s1); 
  54.     } 
  55.  
  56.     @Override 
  57.     public void onSpeechProgressChanged(String utteranceId, int progress) { 
  58.     } 
  59. }; 

5.5 语音控制介绍(Asr能力)

自定义ASR工具类AsrUtil.java ,提供了Asr的相关功能,通过语音识别获取指定菜单指令,然后执行相应动作。Asr核心类如下:

  1. import ohos.ai.asr.AsrIntent; // 提供ASR引擎执行时所需要传入的参数类 
  2. import ohos.ai.asr.util.AsrError; // 错误码的定义类 
  3. import ohos.ai.asr.AsrListener; // 加载语音识别Listener 
  4. import ohos.ai.asr.AsrClient; // 提供调用ASR引擎服务接口的类 
  5. import ohos.ai.asr.util.AsrResultKey; // ASR回调结果中的关键字封装类 

1.初始化参数

  1. /** 
  2.  * 初始化参数 
  3.  * 
  4.  * @param context      Slice对象 
  5.  * @param eventHandler 事件传递对象 
  6.  * @since 2021-01-21 
  7.  */ 
  8. public void initAsrClient(Context context, EventHandler eventHandler) { 
  9.     HiLog.debug(LABEL, "initAsrClient"); 
  10.     slice = context; 
  11.     handler = eventHandler; 
  12.     //release(); 
  13.     initAudioCapture(); 
  14.     initAsrClient(); 

2.实例化TTS 事件监听

  1. //初始化 ASR 引擎时,调用者决定如何实现该接口。 
  2. private AsrListener asrListener = new MyAsrListener() { 
  3.     @Override 
  4.     public void onInit(PacMap params) { 
  5.         HiLog.debug(LABEL, "onInit"); 
  6.         super.onInit(params); 
  7.         //打开录音 
  8.         if (!isAsrListener) { 
  9.             HiLog.debug(LABEL, "!isAsrListener"); 
  10.             isAsrListener = true
  11.             //新线程去处理,拾取PCM音频流交给asrClient 
  12.             poolExecutor.submit(new AudioCaptureRunnable()); 
  13.         } 
  14.  
  15.     } 
  16.  
  17.     @Override 
  18.     public void onError(int error) { 
  19.         super.onError(error); 
  20.         HiLog.error(LABEL, "error:" + (error == 6 ? "在设定的时间内没有语音输入时,在回调中会返回的结果码" : error)); 
  21.     } 
  22.  
  23.     @Override 
  24.     public void onIntermediateResults(PacMap pacMap) { 
  25.         super.onIntermediateResults(pacMap); 
  26.         HiLog.debug(LABEL, "onIntermediateResults"); 
  27.         String result = pacMap.getString(AsrResultKey.RESULTS_INTERMEDIATE); 
  28.         //发送识别结果 
  29.         sendResult(result, 6); 
  30.     } 
  31.  
  32.     @Override 
  33.     public void onResults(PacMap results) { 
  34.         super.onResults(results); 
  35.         HiLog.debug(LABEL, "onResults"); 
  36.     } 
  37.  
  38.     @Override 
  39.     public void onEnd() { 
  40.         super.onEnd(); 
  41.         HiLog.debug(LABEL, "onEnd"); 
  42.         if (!recognizeOver) { 
  43.             startListening(); 
  44.         } 
  45.     } 
  46. }; 
  47.  
  48. /** 
  49.  * 获取 PCM 音频流并在录制过程中将它们发送到 ASR 引擎。 
  50.  * 
  51.  * @since 2021-03-08 
  52.  */ 
  53. private class AudioCaptureRunnable implements Runnable { 
  54.     @Override 
  55.     public void run() { 
  56.         byte[] buffers = new byte[BYTES_LENGTH]; 
  57.         //录音开始 
  58.         audioCapturer.start(); 
  59.         HiLog.debug(LABEL, "audioCapturer start!"); 
  60.         while (isAsrListener) { 
  61.             //读取音频输入 
  62.             int ret = audioCapturer.read(buffers, 0, BYTES_LENGTH); 
  63.             if (ret >= 0) { 
  64.                 //将 PCM 音频流写入字节数组并执行语音识别。 
  65.                 asrClient.writePcm(buffers, BYTES_LENGTH); 
  66.             } 
  67.         } 
  68.     } 

3.初始化录音程序

  1. /** 
  2.  * 初始化录音程序 
  3.  */ 
  4. private void initAudioCapture() { 
  5.     HiLog.debug(LABEL, "initAudioCapture"); 
  6.     //初始化线程池 
  7.     poolExecutor = 
  8.             new ThreadPoolExecutor( 
  9.                     POOL_SIZE, 
  10.                     POOL_SIZE, 
  11.                     ALIVE_TIME, 
  12.                     TimeUnit.SECONDS, 
  13.                     new LinkedBlockingQueue<>(CAPACITY), 
  14.                     new ThreadPoolExecutor.DiscardOldestPolicy()); 
  15.     //提供音频流信息 
  16.     AudioStreamInfo audioStreamInfo = 
  17.             new AudioStreamInfo.Builder() 
  18.                     .encodingFormat(AudioStreamInfo.EncodingFormat.ENCODING_PCM_16BIT) 
  19.                     .channelMask(AudioStreamInfo.ChannelMask.CHANNEL_IN_MONO) 
  20.                     .sampleRate(SAMPLE_RATE) 
  21.                     .build(); 
  22.  
  23.     //提供录音所需的参数结构 
  24.     AudioCapturerInfo audioCapturerInfo = new AudioCapturerInfo.Builder().audioStreamInfo(audioStreamInfo).build(); 
  25.     //初始化录音程序 
  26.     audioCapturer = new AudioCapturer(audioCapturerInfo); 

4.初始化 ASR 引擎服务客户端

  1. /** 
  2.  * 初始化 ASR 引擎服务客户端 
  3.  */ 
  4. private void initAsrClient() { 
  5.     HiLog.debug(LABEL, "initAsrClient"); 
  6.     //创建一个 AsrClient 实例 
  7.     asrClient = AsrClient.createAsrClient(slice).orElse(null); 
  8.     //任务分发器 TODO 
  9.     TaskDispatcher taskDispatcher = slice.getMainTaskDispatcher(); 
  10.     taskDispatcher.asyncDispatch( 
  11.             () -> { 
  12.                 if (asrClient != null) { 
  13.                     //初始化 ASR 引擎服务。 
  14.                     asrClient.init(setInitIntent(), asrListener); 
  15.                 } 
  16.             }); 
  17.  
  18. //Asr引擎输入参数 
  19. private AsrIntent setInitIntent() { 
  20.     HiLog.debug(LABEL, "setInitIntent"); 
  21.     //提供用于执行 ASR 引擎方法的输入参数。 
  22.     AsrIntent initIntent = new AsrIntent(); 
  23.     //识别音频流 
  24.     initIntent.setAudioSourceType(AsrIntent.AsrAudioSrcType.ASR_SRC_TYPE_PCM); 
  25.     // 
  26.     initIntent.setEngineType(AsrIntent.AsrEngineType.ASR_ENGINE_TYPE_LOCAL); 
  27.     return initIntent; 

5.开始录音和停止语音识别

  1. public void startListening() { 
  2.     HiLog.debug(LABEL, "startListening"); 
  3.     if (asrClient != null) { 
  4.         //开始阅读和识别语音数据。 
  5.         asrClient.startListening(setStartIntent()); 
  6.     } 
  7.     sendResult("语音控制已开启---->^_^~~", 6); 
  8.     recognizeOver = false
  9.     isAsrListener = true
  10.  
  11. /** 
  12.  * 停止录音和识别 
  13.  */ 
  14. public void stopListening() { 
  15.     HiLog.debug(LABEL, "stopListening"); 
  16.     if (asrClient != null) { 
  17.         // 取消识别 
  18.         asrClient.stopListening(); 
  19.     } 
  20.     //识别结束 
  21.     recognizeOver = true
  22.     //isAsrListener = false
  23.     sendResult("语音控制已关闭---->@_@~~", 6); 

发送事件通知

5.6 NluAbilitySlice.java 代码

  1. /** 
  2.  * 朗读文本 
  3.  */ 
  4. private void speakingText() { 
  5.     try { 
  6.         if (initTtsResult) { 
  7.             HiLog.debug(LABEL, "speakingText,text:" + inputText.getText()); 
  8.             stopSpeaking(); 
  9.             //朗读长文本,文本最大支持长度为100000,speakText最长512个字符 
  10.             TtsClient.getInstance().speakLongText(inputText.getText(), null); 
  11.         } else { 
  12.             new ToastDialog(this).setText("Tts客户端初始化失败").show(); 
  13.         } 
  14.     } catch (Exception e) { 
  15.         HiLog.error(LABEL,"ERROR:"+e.getLocalizedMessage()); 
  16.         e.printStackTrace(); 
  17.     } 
  18.  
  19. /** 
  20.  * 停止朗读 
  21.  */ 
  22. private void stopSpeaking() { 
  23.     TtsClient.getInstance().stopSpeak(); 

匹配语音指令

  1. //命令关键词 
  2. private static final Map<String, Boolean> COMMAND_MAP = new HashMap<>(); 
  3.  
  4. static { 
  5.     COMMAND_MAP.put("朗读菜单"true); 
  6.     COMMAND_MAP.put("关闭语音控制"true); 
  7.     COMMAND_MAP.put("停止朗读"true); 
  8.     COMMAND_MAP.put("获取分词"true); 
  9.     COMMAND_MAP.put("词性标注"true); 
  10.     COMMAND_MAP.put("提取关键字"true); 
  11.     COMMAND_MAP.put("实体识别"true); 
  12. }  
  13.  
  14. /** 
  15.  * 匹配指令 
  16.  * 
  17.  * @param result 
  18.  * @return 
  19.  */ 
  20. private void matchWords(String result) { 
  21.     if (result != null && !result.isEmpty()) { 
  22.         if (result.indexOf('{') == -1) { 
  23.             resultText.setText(result); 
  24.         } else { 
  25.             JSONObject jsonObject = JSONObject.parseObject(result); 
  26.             JSONObject resultObject = new JSONObject(); 
  27.             if (jsonObject.getJSONArray("result").get(0) instanceof JSONObject) { 
  28.                 resultObject = (JSONObject) jsonObject.getJSONArray("result").get(0); 
  29.             } 
  30.             String resultWord = resultObject.getString("ori_word").replace(" """); 
  31.             resultText.setText(resultWord); 
  32.  
  33.             boolean command = COMMAND_MAP.getOrDefault(resultWord, false); 
  34.             HiLog.debug(LABEL, "command:" + command); 
  35.             switch (resultWord) { 
  36.                 case "朗读菜单"
  37.                     HiLog.debug(LABEL, resultWord); 
  38.                     speakingText(); 
  39.                     break; 
  40.                 case "停止朗读"
  41.                     HiLog.debug(LABEL, resultWord); 
  42.                     stopSpeaking(); 
  43.                     break; 
  44.                 case "关闭语音控制"
  45.                     HiLog.debug(LABEL, resultWord); 
  46.                     asrUtil.stopListening(); 
  47.                     break; 
  48.                 case "获取分词"
  49.                     HiLog.debug(LABEL, resultWord); 
  50.                     //分词能力 
  51.                     String requestData = "{\"text\":\"" + inputText.getText() + "\",\"type\":9223372036854775807}"
  52.                     nluClientUtil.wordSegment(requestData); 
  53.                     break; 
  54.             } 
  55.         } 
  56.     } 
  57.  
  58.  
  59. /** 
  60.  * 事件Handle 
  61.  * 
  62.  * @since 2021-01-15 
  63.  */ 
  64. public class MyEventHandler extends EventHandler { 
  65.     MyEventHandler(EventRunner runner) throws IllegalArgumentException { 
  66.         super(runner); 
  67.     } 
  68.  
  69.     @Override 
  70.     protected void processEvent(InnerEvent event) { 
  71.         super.processEvent(event); 
  72.         //事件类型 
  73.         HiLog.debug(LABEL, "processEvent,eventId:" + event.eventId); 
  74.         switch (event.eventId) { 
  75.             //分词事件 
  76.             case 0: 
  77.                 if (event.object instanceof List) { 
  78.                     Optional<List<String>> optionalStringList = TypeUtil.castList(event.object, String.class); 
  79.                     if (optionalStringList.isPresent() && optionalStringList.get().size() > 0 
  80.                             && (!"no keywords".equals(optionalStringList.get().get(0)))) { 
  81.                         //获取分词结果列表 
  82.                         List<String> lists = optionalStringList.get(); 
  83.                         resultText.setText(lists.toString()); 
  84.                     } 
  85.                 } 
  86.                 break; 
  87.             //Tts初始化成功事件 
  88.             case 4: 
  89.                 Boolean success = Boolean.valueOf(event.object + ""); 
  90.                 HiLog.debug(LABEL, "processEvent,initTtsResult:" + success); 
  91.                 initTtsResult = success; 
  92.                 break; 
  93.             case 6: 
  94.                 String audio = String.valueOf(event.object); 
  95.                 //HiLog.debug(LABEL, "processEvent,audio:" + audio); 
  96.                 //匹配命令 
  97.                 matchWords(audio); 
  98.                 break; 
  99.             default
  100.                 HiLog.debug(LABEL, "processEvent,String:"); 
  101.                 String res = String.valueOf(event.object); 
  102.                 HiLog.debug(LABEL, "processEvent,res:" + res); 
  103.                 resultText.setText(res); 
  104.                 break; 
  105.         } 
  106.     } 

六、完整代码

附件可以直接下载

https://harmonyos.51cto.com/resource/1539

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2021-11-23 09:58:35

鸿蒙HarmonyOS应用

2021-08-17 10:20:14

鸿蒙HarmonyOS应用

2021-09-15 14:55:49

鸿蒙HarmonyOS应用

2021-09-17 14:43:54

鸿蒙HarmonyOS应用

2021-09-24 09:25:01

鸿蒙HarmonyOS应用

2021-07-08 09:42:04

鸿蒙HarmonyOS应用

2021-09-22 09:42:41

鸿蒙HarmonyOS应用

2021-11-02 10:10:49

鸿蒙HarmonyOS应用

2021-07-29 14:03:35

鸿蒙HarmonyOS应用

2021-12-10 15:06:56

鸿蒙HarmonyOS应用

2021-12-02 10:11:44

鸿蒙HarmonyOS应用

2021-08-24 15:13:06

鸿蒙HarmonyOS应用

2021-12-24 10:34:11

鸿蒙HarmonyOS应用

2024-01-23 11:16:08

操作系统鸿蒙HarmonyOS

2022-09-09 14:42:17

应用开发ETS

2021-09-23 10:00:57

鸿蒙HarmonyOS应用

2021-05-09 19:02:16

5G3G4G

2009-07-15 09:59:36

MyEclipse使用
点赞
收藏

51CTO技术栈公众号