>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎转载,转载请注明出处-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>