你的浏览器不支持canvas

做你害怕做的事情,然后你会发现,不过如此。

小程序录音silk格式转码,并识别音频内容 java代码(windows和linux系统)

时间: 作者: 黄运鑫

本文章属原创文章,未经作者许可,禁止转载,复制,下载,以及用作商业用途。原作者保留所有解释权。


前言

  • 注:新版的小程序录音已经支持MP3格式,所以本文中的silk-v3-decoder可以不使用
  • 项目需求:小程序录音后,识别出录音的文字内容
  • 需要下载github的开源项目:silk-v3-decoder
    此大牛的blog地址: https://kn007.net
  • windows和linux系统只是在silk转码wav时有区别,windows调用的是silk_v3_decoder.exe文件,而linux调用的是converter.sh脚本文件
  • 电脑需安装gcc和ffmpeg
  • 本文为原创,转载请注明:https://blog.xinpapa.com/2017/10/30/silk-wav/

内容

1.首先安装gcc和ffmpeg,并下载github中的项目silk-v3-decoder

  • 安装gcc和ffmpeg网上例子很多,安装后在cmd执行命令 gcc -v 和 ffmpeg -version ,查看是否安装成功
  • 将下载好的silk-v3-decoder-master.zip解压到当前文件夹,得到silk-v3-decoder-master文件夹
  • 将silk-v3-decoder-master放到到D盘根目录(目录自定)

2.将silk音频转码成讯飞可识别的wav音频(windows系统)

  • 确保路径下存在silk文件
  • java代码中调用 silk_v3_decoder.exe 文件并传入参数,代码如下
    public static void main(String[] args) {
      File silk = new File("D:/silk.silk");//silk文件
      File pcm = new File("D:/result.pcm");//转码后的pcm文件
      try {
          //silk转pcm,,silk.getAbsolutePath()是要转码的文件路径,pcm.getAbsolutePath()是转码后得到的文件路径
          String cmd = "cmd.exe /c " + "D:/silk-v3-decoder-master/windows/silk_v3_decoder.exe " + silk.getAbsolutePath() + " " + pcm.getAbsolutePath();
          Runtime.getRuntime().exec(cmd);
      } catch (Exception e) {
          e.printStackTrace();
      }
    }
    
  • 代码执行后,得到result.pcm文件;
    但是讯飞识别音频时发现不能正确识别,查看result.pcm文件发现原来是音频采样率的问题,讯飞或百度要求音频采样率为8000或16000,而转码后的文件采样率是24000
  • 查看 https://kn007.net 中的文章发现博主提供了修改采样率的方法
  • 在执行转码命令结尾增加” -Fs_API 16000”,就可以设置输入音频的采样率
  • 修改代码如下:
    public static void main(String[] args) {
      File silk = new File("D:/silk.silk");//silk文件
      File pcm = new File("D:/result.pcm");//转码后的pcm文件
      try {
          //silk转pcm
          String cmd = "cmd.exe /c " + "D:/silk-v3-decoder-master/windows/silk_v3_decoder.exe " + silk.getAbsolutePath() + " " + pcm.getAbsolutePath() + " -Fs_API 16000";
          Runtime.getRuntime().exec(cmd);
      } catch (Exception e) {
          e.printStackTrace();
      }
    }
    
  • 执行后即可得到16000采样率的pcm文件,并且讯飞和百度语音都可以正常识别,8000采样率同理
  • 需要注意的是,得到的pcm文件可以被讯飞识别,但是播放器不能播放,如果想要播放器播放,则需要用ffmpeg转码成wav或其他格式音频,代码如下:
    public static void main(String[] args) {
      File pcm = new File("D:/result.pcm");//需要转码的pcm文件
      File wav= new File("D:/result.wav");//转码后的wav文件
      try {
          //pcm转wav或其它格式
          String cmd = "cmd /c ffmpeg.exe -y -f s16le -ar 16000 -ac 1 -i " + pcm.getAbsolutePath() + " " + wav.getAbsolutePath();
          Runtime.getRuntime().exec(cmd);
      } catch (Exception e) {
          e.printStackTrace();
      }
    }
    
  • 得到的 result.wav 文件,即可以被讯飞、百度语音识别,也能用播放器播放

3.将silk音频转码成讯飞可识别的wav音频(linux系统)

  • 首先将解压到的silk-v3-decoder-master文件夹放在 usr/ 目录下(目录自定)
  • 将测试用的silk文件 silk.silk 放在 tmp/ 目录下
  • 执行文件夹中 converter.sh 脚本进行转码,代码如下:
    public static void main(String[] args) {
      String temp = File.separator + "tmp";//linux临时文件夹路径
      File silk = new File(temp + File.separator + "silk" + File.separator + fileName + ".silk");//silk文件
      //执行converter.sh,silk转wav,这里执行后,会在tmp/silk/目录下生成wav音频文件
      Process exec = Runtime.getRuntime().exec("sh " + File.separator + "usr" + File.separator + "silk" + File.separator + "converter.sh " + silk.getAbsolutePath() + " wav");
      exec.waitFor();
    }
    
  • 执行例子代码后,会在tmp/silk/目录下生成wav音频文件,但是发现得到的wav音频和windows一样,都是24000采样率,所以需要更改 converter.sh 脚本才能输出16000采样率的音频
  • 用文本编辑器打开脚本文件,修改文件中的 ffmpeg 命令,本例中代码在第70行,代码如下:
    ffmpeg -y -f s16le -ar 24000 -ac 1 -i "$1.pcm" "${1%.*}.$2" > /dev/null 2>&1
    
  • 可以看到,默认输出的文件是24000采样率,想要输出16000采样率,修改代码如下(输出8000采样率同理):
    ffmpeg -y -f s16le -ar 24000 -ac 1 -i "$1.pcm" -ar 16000 "${1%.*}.$2" > /dev/null 2>&1
    
  • 修改后保存文件,再执行转码,得到的就是16000采样率的wav文件
  • 因为sh脚本执行过程是:silk解码成pcm,pcm再转码输出为wav音频,所以得到的wav音频即可以被讯飞识别,也可以被播放器播放
  • 因为不了解sh脚本代码,所以网上查找了些资料,参考了这篇博客 小程序语音与讯飞语音识别踩坑过程

4.使用讯飞或百度语音识别音频内容

  • 百度语音只能识别30秒以内的16000采样率音频,如果想识别60秒以内的音频,则需要将音频的采样率设为8000
  • 讯飞则相对友好,采样率8000和16000的音频,时长限制都是60秒
  • 只要音频格式和采样率正确,识别的代码很简单,具体请看讯飞或百度语音官方文档
  • 这里之给出百度语音的代码:
    public class Sample extends Encoder {
    
      private static final String serverURL = "http://vop.baidu.com/server_api";
      private static String token = "";
      //put your own params here
      private static final String apiKey = "请前往百度语音获取";
      private static final String secretKey = "请前往百度语音获取";
      private static final String cuid = "请填写你的cuid";
      private static final Pattern FORMAT_PATTERN = Pattern.compile("^\\s*([D ])([E ])\\s+([\\w,]+)\\s+.+$");
      private static final Pattern ENCODER_DECODER_PATTERN = Pattern.compile("^\\s*([D ])([E ])([AVS]).{3}\\s+(.+)$", 2);
      private static final Pattern PROGRESS_INFO_PATTERN = Pattern.compile("\\s*(\\w+)\\s*=\\s*(\\S+)\\s*", 2);
      private static final Pattern SIZE_PATTERN = Pattern.compile("(\\d+)x(\\d+)", 2);
      private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("([\\d.]+)\\s+(?:fps|tb\\(r\\))", 2);
      private static final Pattern BIT_RATE_PATTERN = Pattern.compile("(\\d+)\\s+kb/s", 2);
      private static final Pattern SAMPLING_RATE_PATTERN = Pattern.compile("(\\d+)\\s+Hz", 2);
      private static final Pattern CHANNELS_PATTERN = Pattern.compile("(mono|stereo)", 2);
      private static final Pattern SUCCESS_PATTERN = Pattern.compile("^\\s*video\\:\\S+\\s+audio\\:\\S+\\s+global headers\\:\\S+.*$", 2);
      private FFMPEGLocator locator;
    
      public static void main(String[] args) throws Exception {
          getToken();
          method();
      }
    
      private static void getToken() throws Exception {
          String getTokenURL = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials" +
                  "&client_id=" + apiKey + "&client_secret=" + secretKey;
          HttpURLConnection conn = (HttpURLConnection) new URL(getTokenURL).openConnection();
          token = new JSONObject(printResponse(conn)).getString("access_token");
      }
    
      private static void method() throws Exception {
          File pcmFile = new File("D://result.wav");//需要识别的音频文件
          HttpURLConnection conn = (HttpURLConnection) new URL(serverURL).openConnection();
    
          // 识别的参数
          JSONObject params = new JSONObject();
          params.put("format", "wav");
          params.put("rate", 16000);
          params.put("channel", "1");
          params.put("token", token);
          params.put("cuid", cuid);
          params.put("len", pcmFile.length());
          params.put("speech", DatatypeConverter.printBase64Binary(loadFile(pcmFile)));
    
          // add request header
          conn.setRequestMethod("POST");
          conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
    
          conn.setDoInput(true);
          conn.setDoOutput(true);
    
          // send request
          DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
          wr.writeBytes(params.toString());
          wr.flush();
          wr.close();
    
          printResponse(conn);
      }
    
      private static String printResponse(HttpURLConnection conn) throws Exception {
          if (conn.getResponseCode() != 200) {
              // request error
              return "";
          }
          InputStream is = conn.getInputStream();
          BufferedReader rd = new BufferedReader(new InputStreamReader(is));
          String line;
          StringBuffer response = new StringBuffer();
          while ((line = rd.readLine()) != null) {
              response.append(line);
              response.append('\r');
          }
          rd.close();
          System.out.println(new JSONObject(response.toString()).toString(4));
          return response.toString();
      }
    
      private static byte[] loadFile(File file) throws IOException {
          InputStream is = new FileInputStream(file);
    
          long length = file.length();
          byte[] bytes = new byte[(int) length];
    
          int offset = 0;
          int numRead = 0;
          while (offset < bytes.length
                  && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
              offset += numRead;
          }
    
          if (offset < bytes.length) {
              is.close();
              throw new IOException("Could not completely read file " + file.getName());
          }
    
          is.close();
          return bytes;
      }
    }
    

对于本文内容有问题或建议的小伙伴,欢迎在文章底部留言交流讨论。