如果使用百度AI的话,需要依赖ffmpeg这个工具。所以要提前安装这个,
以Mac为例:
brew install ffmpeg
然后就等着,时间不会短了,保证电脑不要休眠。
安装完成后,输入ffmpeg,输入版本号,代表安装成功
首先我们需要去百度AI官网上注册一个自己的账号,创建一个程序来获取APP_ID,APP_KEY和APP_SECRET。
<!--百度的SDK-->
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<version>4.3.2</version>
</dependency>
<!--音频视频工具包jave(Linux)-->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
<version>2.4.6</version>
</dependency>
<!--音频视频工具包jave(Windows)-->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-native-win64</artifactId>
<version>2.4.6</version>
</dependency>
转换类:
import com.baidu.aip.speech.AipSpeech;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class WavToWords {
static private Logger logger = LoggerFactory.getLogger(WavToWords.class);
/**
* 音频文件频率8k转16k。必须要转,因为不转百度识别不出来,错误信息是音质太差
*
* @param sourceFile
* @return
*/
public static String cover8xTo16x(File sourceFile) {
String targetPath = null;
//ffmpeg -y -i 16k.wav -acodec pcm_s16le -f s16le -ac 1 -ar 16000 16k.pcm
//ffmpeg -y -i aidemo.mp3 -acodec pcm_s16le -f s16le -ac 1 -ar 16000 16k.pcm
-acodec pcm_s16le pcm_s16le 16bits 编码器
-f s16le 保存为16bits pcm格式
-ac 1 单声道
-ar 16000 16000采样率
try {
File ffmpegPath = new File("/usr/local/Cellar/ffmpeg/4.2.2_2/bin/ffmpeg"); //存放ffmpeg程序的目录
targetPath = sourceFile.getAbsolutePath().replaceAll(".wav", "_16x.pcm");
// File ffmpegPatha = new File(targetPath);
// if(ffmpegPatha.exists()){
// ffmpegPatha.delete();
// }
// ffmpeg.exe -i source.wav -ar 16000 target.wav
List<String> wavToPcm = new ArrayList<String>();
wavToPcm.add(ffmpegPath.getAbsolutePath());
wavToPcm.add("-y");
wavToPcm.add("-i");
wavToPcm.add(sourceFile.getAbsolutePath());
wavToPcm.add("-acodec");
wavToPcm.add("pcm_s16le");
wavToPcm.add("-f");
wavToPcm.add("s16le");
wavToPcm.add("-ac");
wavToPcm.add("1");
wavToPcm.add("-ar");
wavToPcm.add("16000");
wavToPcm.add(targetPath);
ProcessBuilder builder = new ProcessBuilder();
builder.command(wavToPcm);
builder.redirectErrorStream(true);
Process process = builder.start();
process.waitFor();
} catch (Exception e) {
logger.error("录音文件8k转化16k失败" + e.getMessage());
e.printStackTrace();
return null;
}
File ffmpegPatha = new File(targetPath);
if (ffmpegPatha.exists()) {
return targetPath;
}
logger.error("传入的文件路径有误");
return null;
}
public static void test() {
File file = new File("/Users/xxx/gitHome/https/demo/record-cut_50_100.wav");
String resultPath = cover8xTo16x(file);
AipSpeech client = new AipSpeech(APP_ID, APP_KEY, APP_SECRET);
client.setConnectionTimeoutInMillis(2000);
client.setSocketTimeoutInMillis(60000);
JSONObject res = client.asr(resultPath, "wav", 16000, null);
System.out.println(res);
}
public static void main(String[] args) {
int a = 0;
for (int i = 0; i < 1000; i++) {
File file = new File("/Users/xxx/gitHome/https/demo/record-cut_" + a + "_" + (a + 50) + ".wav");
if (!file.exists()) {
break;
}
String resultPath = cover8xTo16x(file);
if(StringUtils.isBlank(resultPath)){
break;
}
AipSpeech client = new AipSpeech(APPID, APP_KEY, APP_SECRET);
client.setConnectionTimeoutInMillis(2000);
client.setSocketTimeoutInMillis(60000);
JSONObject res = client.asr(resultPath, "pcm", 16000, null);
if (res.getString("err_msg").equals("success.")) {
JSONArray aaa = (JSONArray) res.get("result");
System.out.println("第" + i + "段:" + aaa.get(0));
file = new File("结果.docx");
try {
Thread.sleep(1000);
//if file doesnt exists, then create it
if (!file.exists()) {
file.createNewFile();
}
//true = append file
FileWriter fileWritter = new FileWriter(file.getName(), true);
fileWritter.write(aaa.get(0)+"");
fileWritter.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
} else {
logger.warn(res.toString());
}
a = a + 50;
}
}
}
安装ffmpeg就是为了获取音频的一些信息,并且将音频文件频率8k转16k。
要不然百度API识别不出来。
1.调用API语音识别时返回文件太长的错误信息?
所以我们需要将文件切割成一个一个的小文件(我50秒切割一次),循环着调用接口去转文字,然后手动组装返回的文字信息。
2.调用API后只返回开始一句话,或者只返回一小段文字?
因为没有把音频文件频率转成16k。
3.使用jave获取文件时长时,报错找不到ffmpeg启动程序?
因为如果我们不指定ffmpeg的启动程序时,它会去默认的路径下找,找不到就会报错,所以我们需要手动指定ffmpeg的启动程序,然后引入我们自定义的类。
/**
* 自定义MyFFMPEGExecute,手动指定ffmpeg的运行文件
*/
public static class MyFFMPEGExecute extends FFMPEGLocator {
protected String getFFMPEGExecutablePath() {
// String path = MyFFMPEGExecute.class.getResource("/usr/local/Cellar/ffmpeg/4.2.2_2/bin/ffmpeg").getPath();
return "/usr/local/Cellar/ffmpeg/4.2.2_2/bin/ffmpeg";
}
}
/**
* 获取音频文件总时长
* @param file 文件路径
* @return
*/
public static long getTimeLen(File file) {
long tlen = 3600l;
if(file!=null && file.exists()){
//创建媒体对象
MultimediaObject multimediaObject = new MultimediaObject(file,new MyFFMPEGExecute());
//创建媒体信息对象
MultimediaInfo multimediaInfo = null;
try {
multimediaInfo = multimediaObject.getInfo();
tlen = multimediaInfo.getDuration();
return tlen;
} catch (ws.schild.jave.EncoderException e) {
e.printStackTrace();
}
}
return tlen;
}
import ws.schild.jave.FFMPEGLocator;
import ws.schild.jave.MultimediaInfo;
import ws.schild.jave.MultimediaObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class ReadAudio {
public static void main(String[] args) {
int start = 0;
int end = 0;
int count = 50;
String sourcefile = "/Users/xxx/gitHome/https/demo/123.mp3";
long time = getTimeLen(new File(sourcefile));
System.out.println(time);
int newTime = (int)time;
int internal = newTime - end;
while(internal > 0) {
if(internal < 10) {
cut(sourcefile, "./record-cut_" + start + "_" + (int)time +".mp3", start, (int)time);
end += count;
internal = newTime - end;
}else {
end += count;
cut(sourcefile, "./record-cut_" + start + "_" + end +".mp3", start, end);
start += count;
internal = newTime - end;
}
}
}
/**
* 截取wav音频文件
* @param sourcefile 源文件地址
* @param targetfile 目标文件地址
* @param start 截取开始时间(秒)
* @param end 截取结束时间(秒)
*
* return 截取成功返回true,否则返回false
*/
public static boolean cut(String sourcefile, String targetfile, int start, int end) {
try{
if(!sourcefile.toLowerCase().endsWith(".mp3") || !targetfile.toLowerCase().endsWith(".wav")){
return false;
}
File wav = new File(sourcefile);
if(!wav.exists()){
return false;
}
long t1 = getTimeLen(wav); //总时长(秒)
if(start<0 || end<=0 || start>=t1 || end>t1 || start>=end){
return false;
}
FileInputStream fis = new FileInputStream(wav);
long wavSize = wav.length()-44; //音频数据大小(44为128kbps比特率wav文件头长度)
long splitSize = (wavSize/t1)*(end-start); //截取的音频数据大小
long skipSize = (wavSize/t1)*start; //截取时跳过的音频数据大小
int splitSizeInt = Integer.parseInt(String.valueOf(splitSize));
int skipSizeInt = Integer.parseInt(String.valueOf(skipSize));
ByteBuffer buf1 = ByteBuffer.allocate(4); //存放文件大小,4代表一个int占用字节数
buf1.putInt(splitSizeInt+36); //放入文件长度信息
byte[] flen = buf1.array(); //代表文件长度
ByteBuffer buf2 = ByteBuffer.allocate(4); //存放音频数据大小,4代表一个int占用字节数
buf2.putInt(splitSizeInt); //放入数据长度信息
byte[] dlen = buf2.array(); //代表数据长度
flen = reverse(flen); //数组反转
dlen = reverse(dlen);
byte[] head = new byte[44]; //定义wav头部信息数组
fis.read(head, 0, head.length); //读取源wav文件头部信息
for(int i=0; i<4; i++){ //4代表一个int占用字节数
head[i+4] = flen[i]; //替换原头部信息里的文件长度
head[i+40] = dlen[i]; //替换原头部信息里的数据长度
}
byte[] fbyte = new byte[splitSizeInt+head.length]; //存放截取的音频数据
for(int i=0; i<head.length; i++){ //放入修改后的头部信息
fbyte[i] = head[i];
}
byte[] skipBytes = new byte[skipSizeInt]; //存放截取时跳过的音频数据
fis.read(skipBytes, 0, skipBytes.length); //跳过不需要截取的数据
fis.read(fbyte, head.length, fbyte.length-head.length); //读取要截取的数据到目标数组
fis.close();
File target = new File(targetfile);
if(target.exists()){ //如果目标文件已存在,则删除目标文件
target.delete();
}
FileOutputStream fos = new FileOutputStream(target);
fos.write(fbyte);
fos.flush();
fos.close();
}catch(IOException e){
e.printStackTrace();
return false;
}
return true;
}
/**
* 获取音频文件总时长
* @param file 文件路径
* @return
*/
public static long getTimeLen(File file) {
long tlen = 3600l;
if(file!=null && file.exists()){
//创建媒体对象
MultimediaObject multimediaObject = new MultimediaObject(file,new MyFFMPEGExecute());
//创建媒体信息对象
MultimediaInfo multimediaInfo = null;
try {
multimediaInfo = multimediaObject.getInfo();
tlen = multimediaInfo.getDuration();
return tlen;
} catch (ws.schild.jave.EncoderException e) {
e.printStackTrace();
}
}
return tlen;
}
/**
* 自定义MyFFMPEGExecute,手动指定ffmpeg的运行文件
*/
public static class MyFFMPEGExecute extends FFMPEGLocator {
protected String getFFMPEGExecutablePath() {
// String path = MyFFMPEGExecute.class.getResource("/usr/local/Cellar/ffmpeg/4.2.2_2/bin/ffmpeg").getPath();
return "/usr/local/Cellar/ffmpeg/4.2.2_2/bin/ffmpeg";
}
}
/**
* 数组反转
* @param array
*/
public static byte[] reverse(byte[] array){
byte temp;
int len=array.length;
for(int i=0;i<len/2;i++){
temp=array[i];
array[i]=array[len-1-i];
array[len-1-i]=temp;
}
return array;
}
}