Web系统中Mic设备的应用实例

时间:2025-04-17 20:04:07

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎转载,转载请注明出处-VirgoArt,www.cnblogs.com

感谢xiangyuecn同学在GitHub上提供的音频操作组件,点击传送!

一、客户端使用音频设备

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html>
<head>
<title>HTML5采集麦克风音频</title>
<meta charset="UTF-8">
<meta name="content-type" content="text/html; charset=UTF-8">
<script src="http://static.runoob.com/assets/jquery-validation-1.14.0/lib/jquery.js"></script>
<script type="text/javascript" src="recorder-core.js"></script>
<script type="text/javascript" src="wav.js"></script>
<script type="text/javascript">
var index = 1; //录音后行的索引(table) function hasGetUserMedia() {
return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
} if (!hasGetUserMedia()) {
alert('您的浏览器不支持录音');
}
// 录音配置
set = {
type: "wav" //输出类型:mp3,wav等,使用一个类型前需要先引入对应的编码引擎
, bitRate: 16 //比特率 wav(位):16、8,MP3(单位kbps):8kbps时文件大小1k/s,16kbps 2k/s,录音文件很小
, sampleRate: 16000 //采样率,wav格式文件大小=sampleRate*时间;mp3此项对低比特率文件大小有影响,高比特率几乎无影响。wav任意值,mp3取值范围:48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000
, bufferSize: 4096 //AudioContext缓冲大小。会影响onProcess调用速度,相对于AudioContext.sampleRate=48000时,4096接近12帧/s
}
var rec = Recorder(set); function start() {
//打开麦克风授权获得相关资源
rec.open(function () {
rec.start();//开始录音 setTimeout(function () {
rec.stop(function (blob, duration) {//到达指定条件停止录音,拿到blob对象想干嘛就干嘛:立即播放、上传
addInfo(blob);
rec.close();//释放录音资源
}, function (msg) {
alert.log("录音失败:" + msg);
});
}, 1000 * $("#audiotime").val()); }
, function (msg) {//未授权或不支持
alert.log("无法录音:" + msg);
}
);
} var autioList = []; function addInfo(blob) {
var table = $("#table");
var ts = Date.parse(new Date());
var dom = "<tr>";
dom += "<td>" + index + "</td>";
dom += "<td>" + ts + ".wav</td>";
dom += "<td>" + "<video height='40px' width='300px' controls = 'controls' name = 'video' src = '" + URL.createObjectURL(blob) + "'></video>" + "</td>";
dom += "<td>" + "<a download='audio' href='" + URL.createObjectURL(blob) + "'>下载</a>" + "</td>";
dom += "<td>" + "<button onclick='upload(this," + index + ")'>上传</button>" + "</td>";
dom += "<td>未上传</td>";
dom += "</tr>";
table.append(dom); autioList.push({filename: ts + ".wav", blob: blob});
index++;
} function upload(my, index) {
var formData = new FormData();
formData.append("audioData", autioList[index - 1].blob, autioList[index - 1].filename);
var xhr = new XMLHttpRequest();
xhr.open("POST", "/upload");
xhr.send(formData);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
//若响应完成且请求成功
my.parentElement.nextElementSibling.textContent = xhr.responseText;
}
}
} function recognition() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/recognition");
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
//若响应完成且请求成功
alert(xhr.responseText);
}
}
}
</script>
</head>
<body>
Web音频采集 <br>
<button id="start" onclick="start()"> 点击录音</button>
<span>录音时长,默认为三秒</span><input type="text" value="3" id="audiotime">
<button id="confirm" onclick="recognition()">确认</button>
<hr/>
<table id="table">
<tr>
<td>序号</td>
<td>音频文件</td>
<td>播放</td>
<td>下载</td>
<td>上传</td>
<td>状态</td>
</tr>
</table>
</body>
</html>

  其中,项目需要引用xiangyuecn/Recorder工程中的recorder-core.js、wav.js(个人测试只用到Wav格式,如有其他需求,参见工程ReadMe)。

二、后端数据通信

package cn.virgo.audio.controller;

import cn.virgo.audio.utils.RemoteShellExecutor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List; @Controller
public class IndexController { @Value("${system.audiofile.path}")
private String AUDIO_FILES_PATH;
@Value("${system.ffmpeg.path}")
private String AUDIO_FFMPEG_PATH;
@Value("${system.ffmpeg.splittime}")
private String AUDIO_SPLIT_TIME; /**
* 首页跳转
*
* @param request
* @return
*/
@RequestMapping(path = {"/index"}, method = RequestMethod.GET)
public ModelAndView defaultPage1(HttpServletRequest request) throws IOException {
ModelAndView modelAndView = new ModelAndView("/mic");
return modelAndView;
}
/**
* 音频文件上传
*
* @param file
* @return
*/
@RequestMapping(path = {"/upload"}, method = RequestMethod.POST)
@ResponseBody
public String upload1(@RequestParam("audioData") MultipartFile file) {
if (file.isEmpty()) {
return "Error";
}
try {
Files.createDirectories(Paths.get(AUDIO_FILES_PATH));
byte[] bytes = file.getBytes();
Path path = Paths.get(AUDIO_FILES_PATH + file.getOriginalFilename());
Files.write(path, bytes);
} catch (IOException e) {
e.printStackTrace();
}
return "Success";
}
/**
* 音频文件上传
*
* @param file
* @return
*/
@RequestMapping(path = {"/upload2"}, method = RequestMethod.POST)
@ResponseBody
public String upload2(@RequestParam("audioData") MultipartFile file) {
if (file.isEmpty()) {
return "Error";
}
try {
Files.createDirectories(Paths.get(AUDIO_FILES_PATH));
byte[] bytes = file.getBytes();
Path path = Paths.get(AUDIO_FILES_PATH + file.getOriginalFilename());
Files.write(path, bytes);
File srcFile = path.toFile();
//TODO:上传完成后,调用FFMPEG将音频分片,并删除源文件
run_exe(AUDIO_FFMPEG_PATH + "ffmpeg.exe -i " + AUDIO_FILES_PATH + file.getOriginalFilename() + " -f segment -segment_time " + AUDIO_SPLIT_TIME + " -c copy " + AUDIO_FILES_PATH + "out%03d.wav");
srcFile.delete();
} catch (IOException e) {
e.printStackTrace();
}
return "Success";
}
/**
* 准备就绪
*
* @return
*/
@RequestMapping(path = {"/recognition"}, method = RequestMethod.GET)
@ResponseBody
public List<String> recognition1() {
//TODO:return null;
}
/**
* 获取到项目路径
*
* @return
*/
public static String getRootPath() {
return ClassUtils.getDefaultClassLoader().getResource("").getPath();
}
/**
* 调用EXE
*
* @param cmd
*/
public static void run_exe(String cmd) {
System.out.println("-------------Cmd info-------------");
System.out.println("CMD:" + cmd);
String s1;
String s2;
StringBuilder sb1 = new StringBuilder();
sb1.append("ProcessInfo:");
StringBuilder sb2 = new StringBuilder();
sb2.append("ErrorInfo:");
Process proc = null;
try {
// 使用绝对路径
proc = Runtime.getRuntime().exec(cmd);
InputStream pInfo = proc.getInputStream();
BufferedReader bufferedReader1 = new BufferedReader(new InputStreamReader(pInfo));
while ((s1 = bufferedReader1.readLine()) != null) {
sb1.append(s1);
}
bufferedReader1.close();
System.out.println(sb1.toString());
InputStream pErr = proc.getErrorStream();
BufferedReader bufferedReader2 = new BufferedReader(new InputStreamReader(pErr));
while ((s2 = bufferedReader2.readLine()) != null) {
sb2.append(s2);
}
bufferedReader2.close();
System.out.println(sb2.toString());
System.out.println("ExitCode:" + proc.waitFor());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

  其中,项目中upload1对上传的音频不做二次加工,upload2对上传的音频使用FFMPEG进行二次加工。

三、备注

  项目实例基于SpringBoot项目,需引用pom为:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>cn.study</groupId>
<artifactId>audio</artifactId>
<version>1.0-SNAPSHOT</version> <name>audio</name>
<description>Demo project for audio</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties> <dependencies>
<!--Spring Web 相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除默认的日志框架-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency> <!--第三方工具-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<!--log4j2 日志框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.audio.App</mainClass>
<layout>JAR</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>