微信小程序語音同步智能識別的實現案例

一、背景

在小程序的一些應用場景中,會有語音轉文字的需求。原有的做法一般是先通過小程序的錄音功能錄下語音文件,然後再通過調用語音智能識別WebApi(比如百度雲AI平臺,科大訊飛平臺)將語音文件轉成文字信息,以上的做法比較繁瑣且用戶的體驗性較差。
爲解決此問題,微信直接開放了同聲傳譯的插件,小程序作者可以直接使用該插件進行語音同聲傳譯的開發。此文章將通過前後端整合應用的完整案例完成語音的實時轉換,並將語音上傳到服務端後臺備份。

二、同聲傳譯插件介紹

微信同聲傳譯由微信智聆語音團隊、微信翻譯團隊與公衆平臺聯合推出的同傳開放接口,首期開放語音轉文字、文本翻譯、語音合成接口,爲開發者賦能。

1、 微信小程序後臺添加插件

進入微信小程序後臺–>進入設置–>第三方設置–>添加插件->搜索同聲傳譯–>完成添加。
在這裏插入圖片描述
在這裏插入圖片描述

2、 微信小程序啓用插件

在小程序app.json文件中增加插件版本等信息:

"plugins": {
    "WechatSI": {
      "version": "0.3.3",
      "provider": "wx069ba97219f66d99"
    }
  },

在頁面程序文件中引入插件:

/* index.js */

const plugin = requirePlugin("WechatSI")

// 獲取**全局唯一**的語音識別管理器**recordRecoManager**
const manager = plugin.getRecordRecognitionManager()

recordRecoManager 對象的方法列表:

方法 參數 說明
start options 開始識別
stop 結束識別
onStart callback 正常開始錄音識別時會調用此事件
onRecognize callback 有新的識別內容返回,則會調用此事件
onStop callback 識別結束事件
onError callback 識別錯誤事件

官方開發文檔:插件的語音識別管理器

三、語音同步轉換的前端實現

1、界面UI與操作

UI參考微信官方的DEMO:長按按鈕進行錄音,鬆開按鈕實時將錄音轉換爲文字。
在這裏插入圖片描述

用戶可對同步轉換的文字進行編輯,同時可將原始語音文件與文字上傳後臺服務端。
在這裏插入圖片描述

2、代碼實現

語音同步轉換的主要代碼:

//導入插件
const plugin = requirePlugin("WechatSI");
// 獲取**全局唯一**的語音識別管理器**recordRecoManager**
const manager = plugin.getRecordRecognitionManager();

/**
   * 加載進行初始化
   */
 onLoad: function () {
 	//獲取錄音權限
	app.getRecordAuth();
	//初始化語音識別回調
    this.initRecord();
  },

 ...
 
/**
   * 初始化語音識別回調
   * 綁定語音播放開始事件
   */
  initRecord: function () {
    //有新的識別內容返回,則會調用此事件
    manager.onRecognize = (res) => {
      let currentData = Object.assign({}, this.data.currentTranslate, {
        text: res.result,
      });
      this.setData({
        currentTranslate: currentData,
      });
      this.scrollToNew();
    };

    // 識別結束事件
    manager.onStop = (res) => {
      let text = res.result;

      console.log(res.tempFilePath);

      if (text == "") {
        this.showRecordEmptyTip();
        return;
      }

      let lastId = this.data.lastId + 1;

      let currentData = Object.assign({}, this.data.currentTranslate, {
        text: res.result,
        translateText: "正在識別中",
        id: lastId,
        voicePath: res.tempFilePath,
        duration: res.duration
      });

      this.setData({
        currentTranslate: currentData,
        recordStatus: 1,
        lastId: lastId,
      });
      //將當前識別內容與語音文件加入列表
      this.addRecordFile(currentData, this.data.dialogList.length);
      //刷新列表
	  this.scrollToNew();
    };

    // 識別錯誤事件
    manager.onError = (res) => {
      this.setData({
        recording: false,
        bottomButtonDisabled: false,
      });
    };

  },

  /**
   * 按住按鈕開始語音識別
   */
  streamRecord: function (e) {
    let detail = e.detail || {};
    let buttonItem = detail.buttonItem || {};
    //開始中文錄音
    manager.start({
      lang: buttonItem.lang,
    });

    this.setData({
      recordStatus: 0,
      recording: true,
      currentTranslate: {
        // 當前語音輸入內容
        create: util.recordTime(new Date()),
        text: "正在聆聽中",
        lfrom: buttonItem.lang,
        lto: buttonItem.lto,
      },
    });
    //刷新列表
    this.scrollToNew();
  },

  /**
   * 鬆開按鈕結束語音識別
   */
  streamRecordEnd: function (e) {
    let detail = e.detail || {}; // 自定義組件觸發事件時提供的detail對象
    let buttonItem = detail.buttonItem || {};

    // 防止重複觸發stop函數
    if (!this.data.recording || this.data.recordStatus != 0) {
      console.warn("has finished!");
      return;
    }

    manager.stop();

    this.setData({
      bottomButtonDisabled: true,
    });
  },

編輯識別文字並完上傳的主要代碼:

 /**
   * 頁面的初始數據
   */
  data: {
    edit_text_max: 200,
    remain_length: 200,
    edit_text: "",
    is_focus: false,
    tips: "",
    index: -1,
    voicePath: "",
    
  },

/**
   * 加載初始化
   */
 onLoad: function (options) {
    //根據傳入的文字內容填充編輯框
    this.setEditText(options.content)
    
    this.setData({
        index: index,
        oldText:options.content,
        voicePath: options.voicePath
    })
    
  },

 /**
   * 編輯文字
   */
  editInput: function (event) {
    console.log(event)
    if (event.detail.value.length > this.getEditTextMax()) {

    } else {
      this.data.edit_text = event.detail.value
      this.updateRemainLength(this.data.edit_text)
    }
  },

 /**
   * 上傳文字與語音文件
   */
  editConfirm: function (event) {
    let json=this.data.edit_text
    //調用微信上傳文件api將信息上傳至服務端webApi
    wx.uploadFile({
      url: api.wxFileUploadUrl,
      filePath: this.data.voicePath,
      name: "file",
      header: {
        Authorization: wx.getStorageSync("loginFlag"),
        "Content-Type": "multipart/form-data",
      },
      formData: {
        openId: app.globalData.userInfo.openId,
        realName: "語音文件",
        json: JSON.stringify(json),
      },
      success: (result) => {
        console.log("success:", result);
        if (result.statusCode == "200") {
          let data = JSON.parse(result.data);
          console.log("data", data);
          if (data.success == true) {
            let module = data.module;
            console.log("module", module);
            app.showInfo("上傳成功");            
            setTimeout( ()=>{
              wx.navigateBack();
            }, 2000)
                      
          } else {
            app.showInfo("異常錯誤" + data.errMsg + ",請重新進入");
            wx.navigateTo({
              url: "/pages/index/index",
            });
          }
        } else {
          app.showInfo("訪問後臺異常,重新進入系統");
          wx.navigateTo({
            url: "/pages/index/index",
          });
        }
      },
      fail: (result) => {
        console.log("fail", result);
        wx.navigateTo({
          url: "/pages/index/index",
        });
      },
      complete: () => {},
    });

  },

四、後端SpringBoot實現語音文件上傳webApi

1、SpringBoot項目API相關結構樹

在這裏插入圖片描述

2、文件上傳工具類的實現

tools工具類包中主要存文件通用的文件上傳工具類,該工具類會將文件上傳至配置指定的文件夾下,並將文件信息寫入upload_file表中。

  • 文件信息實體類:與數據庫中表upload_file對應;
  • 文件存儲倉庫類:通過Spring Data JPA接口實現數據的CRUD;
  • 文件上傳工具接口:對外統一封裝文件上傳方法;
  • 文件上傳工具實現類:實現文件上傳方法接口。

文件信息實體類:UploadFile.java

/**
 * 文件信息表
 *
 * @author zhuhuix
 * @date 2020-04-20
 */
@Entity
@Getter
@Setter
@Table(name = "upload_file")
public class UploadFile {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @NotNull(groups = Update.class)
    private Long id;

    /**
     * 文件實際名稱
     */
    @Column(name = "real_name")
    private String realName;

    /**
     * 文件名
     */
    @NotNull
    @Column(name = "file_name")
    private String fileName;

    /**
     * 文件主名稱
     */
    @NotNull
    @Column(name = "primary_name")
    private String primaryName;

    /**
     * 文件擴展名
     */
    @NotNull
    private String extension;

    /**
     * 存放路徑
     */
    @NotNull
    private String path;

    /**
     * 文件類型
     */
    private String type;

    /**
     * 文件大小
     */
    private Long size;

    /**
     * 上傳人
     */
    private String uploader;

    @JsonIgnore
    @Column(name = "create_time")
    @CreationTimestamp
    private Timestamp createTime;

    public UploadFile(String realName, @NotNull String fileName, @NotNull String primaryName, @NotNull String extension, @NotNull String path, String type, Long size, String uploader) {
        this.realName = realName;
        this.fileName = fileName;
        this.primaryName = primaryName;
        this.extension = extension;
        this.path = path;
        this.type = type;
        this.size = size;
        this.uploader = uploader;
    }

    @Override
    public String toString() {
        return "UploadFile{" +
                "fileName='" + fileName + '\'' +
                ", uploader='" + uploader + '\'' +
                ", createTime=" + createTime +
                '}';
    }
}

文件存儲倉庫類:UploadFileRepository.java

/**
 * 上傳文件DAO接口層
 *
 * @author zhuhuix
 * @date 2020-04-03
 */
public interface UploadFileRepository extends JpaRepository<UploadFile, Long>, JpaSpecificationExecutor<UploadFile> {
//該接口繼承JpaRepository及CrudRepository接口,已實現瞭如findById,save,delete等CRUD方法
}

UploadFileRepository 接口繼承JpaRepository及CrudRepository接口,已實現瞭如findById,save,delete等CRUD方法
在這裏插入圖片描述
文件上傳工具接口:UploadFileTool.java

/**
 * 文件上傳接口定義
 *
 * @author zhuhuix
 * @date 2020-04-20
 */
public interface UploadFileTool {

    /**
     * 文件上傳
     * @param multipartFile 文件
     * @return 上傳信息
     */
   UploadFile upload(String uploader,String realName,MultipartFile multipartFile);
}

文件上傳工具實現類:UploadFileToolImpl.java

/**
 * 文件上傳實現類
 *
 * @author zhuhuix
 * @date 2020-04-20
 */
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class UploadFileToolImpl implements UploadFileTool {

    private final UploadFileRepository uploadFileRepository;

    @Value("${uploadFile.path}")
    private String path;

    @Value("${uploadFile.maxSize}")
    private long maxSize;

    public UploadFileToolImpl(UploadFileRepository uploadFileRepository) {
        this.uploadFileRepository = uploadFileRepository;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UploadFile upload(String uploader, String realName, MultipartFile multipartFile) {
        //檢查文件大小
        if (multipartFile.getSize() > maxSize * Constant.MB) {
            throw new RuntimeException("超出文件上傳大小限制" + maxSize + "MB");
        }
        //獲取上傳文件的主文件名與擴展名
        String primaryName = FileUtil.mainName(multipartFile.getOriginalFilename());
        String extension = FileUtil.extName(multipartFile.getOriginalFilename());
        //根據文件擴展名得到文件類型
        String type = getFileType(extension);
        //給上傳的文件加上時間戳
        LocalDateTime date = LocalDateTime.now();
        DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMddhhmmssS");
        String nowStr = "-" + date.format(format);
        String fileName = primaryName + nowStr + "." + extension;

        try {
            String filePath = path + type + File.separator + fileName;
            File dest = new File(filePath).getCanonicalFile();
            if (!dest.getParentFile().exists()) {
                dest.getParentFile().mkdirs();
            }
            multipartFile.transferTo(dest);
            if (ObjectUtil.isNull(dest)) {
                throw new RuntimeException("上傳文件失敗");
            }

            UploadFile uploadFile = new UploadFile(realName, fileName, primaryName, extension, dest.getPath(), type, multipartFile.getSize(), uploader);
            return uploadFileRepository.save(uploadFile);

        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }

    }

    /**
     * 根據文件擴展名給文件類型
     *
     * @param extension 文件擴展名
     * @return 文件類型
     */
    private static String getFileType(String extension) {
        String document = "txt doc pdf ppt pps xlsx xls docx csv";
        String music = "mp3 wav wma mpa ram ra aac aif m4a";
        String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
        String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
        if (image.contains(extension)) {
            return "image";
        } else if (document.contains(extension)) {
            return "document";
        } else if (music.contains(extension)) {
            return "music";
        } else if (video.contains(extension)) {
            return "video";
        } else {
            return "other";
        }
    }
}

注意,該程序代碼中用到了@Value註解獲取配置文件中的uploadFile.path及uploadFile.maxsize參數,一般在項目靜態配置文件中按如下書寫(yml配置文件)。

# 測試環境文件存儲路徑
uploadFile:
  path: C:\startup\file\
  # 文件大小 /M
  maxSize: 50
3、小程序上傳文件接口的實現

wx-miniprogram包定義了小程序CRM webApi的接口,小程序調用webApi實現文件的上傳及其他功能。

  • 微信小程序 webApi:對外提供小程序上傳文件webApi;
  • 微信小程序服務接口:封裝小程序上傳文件服務接口;
  • 微信小程序服務實現:小程序上傳文件服務的實現,該服務實現中會調用tools包中的UploadFile接口進行文件的上傳。

微信小程序CRM webApi:WxMiniCrmController.java

/**
 * 微信小程序Crm webApi
 *
 * @author zhuhuix
 * @date 2020-03-30
 */
@Slf4j
@RestController
@RequestMapping("/api/wx-mini")
@Api(tags = "微信小程序Crm接口")
public class WxMiniCrmController {

    private final WxMiniCrm wxMiniCrm;

    public WxMiniCrmController(WxMiniCrm wxMiniCrm) {
        this.wxMiniCrm = wxMiniCrm;
    }

    @ApiOperation(value = "微信小程序端上傳文件")
    @PostMapping(value = "/fileUpload")
    public ResponseEntity fileUpload(HttpServletRequest request) {
        MultipartHttpServletRequest req = (MultipartHttpServletRequest) request;

        MultipartFile multipartFile = req.getFile("file");
        String openId = req.getParameter("openId");
        String realName = req.getParameter("realName");
        String json = req.getParameter("json");

        return ResponseEntity.ok(wxMiniCrm.uploadFile(json, openId,realName, multipartFile));

    }
}

微信小程序CRM服務接口:WxMiniCrm.java

/**
 * 微信小程序CRM服務接口定義
 *
 * @author zhuhuix
 * @date 2020-04-20
 */
public interface WxMiniCrm {

    /**
     * 將微信小程序傳入的json對象寫入數據庫,並同時將文件上傳至服務端
     *
     * @param json          微信端傳入json對象
     * @param openId        上傳人
     * @param realName      文件實際名稱
     * @param multipartFile 上傳文件
     * @return 返回上傳信息
     */
    Result<UploadFile> uploadFile(String  json, String openId, String realName,MultipartFile multipartFile);
}

微信小程序CRM服務實現:WxMiniCrmImpl.java

/**
 * 微信小程序CRM實現類
 *
 * @author zhuhuix
 * @date 2020-04-20
 */
@Slf4j
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class WxMiniCrmImpl implements WxMiniCrm {

    private final UploadFileTool uploadFileTool;

    public WxMiniCrmImpl(UploadFileTool uploadFileTool) {
        this.uploadFileTool = uploadFileTool;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<UploadFile> uploadFile(String  json, String openId,String realName, MultipartFile multipartFile) {
        return new Result<UploadFile>().ok(uploadFileTool.upload(openId,realName, multipartFile));
    }
}
4、小程序上傳文件接口的查看

訪問Swagger2可查看該接口,Swagger2與SpringBoot的集成可參考SpringBoot JWT認證機制項目集成Swagger2
在這裏插入圖片描述

五、實際測試

語音測試正常
在這裏插入圖片描述
上傳文件至後臺:
在這裏插入圖片描述
上傳的日誌信息查看:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章