简述
将MP4视频内的音频提取出来,该工具类的解决思路如下:
1、提取视频音频pcm数据
2、将pcm数据封装音频头保存。
Maven依赖
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.5.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> <version>1.5.5</version> </dependency>
说明一下,这里用到了hutools工具包,因为可以大幅简化代码。如果有自己的读取文件流的工具类,可以不使用。
代码
上代码
import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.UUID; import lombok.extern.slf4j.Slf4j; import org.bytedeco.javacpp.Loader; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; /** @Author huyi @Date 2021/10/20 12:11 @Description: mp4提取音频工具类 */ @Slf4j public class Mp4ExtractAudioUtils { /** * 获取视频的文件pcm文件地址 * * @param url MP4 * @return * @throws Exception */ public static String getMp4Pcm(String url, String tmpDir) throws Exception { Optional<String> pcmPath = Optional.empty(); try { pcmPath = convertMP4toPCM(Paths.get(url), Paths.get(tmpDir)); } catch (Exception exception) { exception.printStackTrace(); throw new Exception("转换pcm异常:" + exception.getMessage()); } if (pcmPath.isPresent()) { return pcmPath.get(); } else { throw new Exception("视频转换音频失败"); } } /** * 将单个PM4文件进行片头和片尾歌曲删除后,转换为PCM文件 * * @param mp4Path * @param pcmDir * @return 转换完成后的pcm文件路径 */ public static Optional<String> convertMP4toPCM(Path mp4Path, Path pcmDir) { String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class); // 基于ffmpeg进行pcm转换 // 基于输入路径的md5值来命名,也可以基于系统时间戳来命名 String pcmFile = pcmDir.resolve(UUID.randomUUID() + ".pcm").toString(); ProcessBuilder pcmBuilder = new ProcessBuilder( ffmpeg, "-y", "-i", mp4Path.toAbsolutePath().toString(), "-vn", "-acodec", "pcm_s16le", "-f", "s16le", "-ac", "1", "-ar", "16000", pcmFile); try { // inheritIO是指将 子流程的IO与当前java流程的IO设置为相同 pcmBuilder.inheritIO().start().waitFor(); } catch (InterruptedException | IOException e) { log.error("ffmpeg将mp4转换为pcm时出错", e); return Optional.empty(); } // 返回pcm文件路径 return Optional.of(pcmFile); } /** * 根据PCM文件构建wav的header字段 * * @param srate Sample rate - 8000, 16000, etc. * @param channel Number of channels - Mono = 1, Stereo = 2, etc.. * @param format Number of bits per sample (16 here) * @throws IOException */ public static byte[] buildWavHeader(int dataLength, int srate, int channel, int format) throws IOException { byte[] header = new byte[44]; long totalDataLen = dataLength + 36; long bitrate = srate * channel * format; header[0] = 'R'; header[1] = 'I'; header[2] = 'F'; header[3] = 'F'; header[4] = (byte) (totalDataLen & 0xff); header[5] = (byte) ((totalDataLen >> 8) & 0xff); header[6] = (byte) ((totalDataLen >> 16) & 0xff); header[7] = (byte) ((totalDataLen >> 24) & 0xff); header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E'; header[12] = 'f'; header[13] = 'm'; header[14] = 't'; header[15] = ' '; header[16] = (byte) format; header[17] = 0; header[18] = 0; header[19] = 0; header[20] = 1; header[21] = 0; header[22] = (byte) channel; header[23] = 0; header[24] = (byte) (srate & 0xff); header[25] = (byte) ((srate >> 8) & 0xff); header[26] = (byte) ((srate >> 16) & 0xff); header[27] = (byte) ((srate >> 24) & 0xff); header[28] = (byte) ((bitrate / 8) & 0xff); header[29] = (byte) (((bitrate / 8) >> 8) & 0xff); header[30] = (byte) (((bitrate / 8) >> 16) & 0xff); header[31] = (byte) (((bitrate / 8) >> 24) & 0xff); header[32] = (byte) ((channel * format) / 8); header[33] = 0; header[34] = 16; header[35] = 0; header[36] = 'd'; header[37] = 'a'; header[38] = 't'; header[39] = 'a'; header[40] = (byte) (dataLength & 0xff); header[41] = (byte) ((dataLength >> 8) & 0xff); header[42] = (byte) ((dataLength >> 16) & 0xff); header[43] = (byte) ((dataLength >> 24) & 0xff); return header; } /** * 默认写入的pcm数据是16000采样率,16bit,可以按照需要修改 * * @param filePath * @param pcmData */ public static String writeToFile(String filePath, byte[] pcmData) { BufferedOutputStream bos = null; try { bos = new BufferedOutputStream(new FileOutputStream(filePath)); byte[] header = buildWavHeader(pcmData.length, 16000, 1, 16); bos.write(header, 0, 44); bos.write(pcmData); bos.close(); return filePath; } catch (Exception e) { e.printStackTrace(); return filePath; } finally { if (bos != null) { try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 提取音频 * @param mp4Path MP4地址 * @param tmpDir 临时文件夹 * @param resultPath 最终结果音频地址 * @return 音频地址 * @throws Exception 异常 */ public static String extractAudio(String mp4Path, String tmpDir, String resultPath) throws Exception { String pcmPath = getMp4Pcm(mp4Path, tmpDir); return writeToFile(resultPath, FileUtil.readBytes(pcmPath)); } public static void main(String[] args) throws Exception { System.out.println( extractAudio( "C:\\Users\\huyi\\Desktop\\测试.mp4", "C:\\Users\\huyi\\Desktop", "C:\\Users\\huyi\\Desktop\\测试.wav")); } }
执行结果:
可以看到生成一个临时的pcm文件,可以自己调整工具类将临时文件删除。
生成音频成功。
总结
这里有几点需要说明。
1、hutools工具主要是为了使用一些方便的组件,可以用自定义的,不一定要引入。
2、关于音频头文件的参数,在另一篇文章我有详细描述,可以参考:生成自定义时长的静音音频 | Java工具类_阿良的博客-CSDN博客
3、里面用到了ffmpeg进行pcm转换,需要本地环境有ffmpeg环境,具体说明在我另一篇有描述,可以参考:java 音频转为wav格式标准音频 | Java工具类_阿良的博客-CSDN博客
4、该工具类部分参数可以调整为入参模式,如果有疑问的话,可以私信我。
如果本文对你有帮助,请点个赞支持一下吧。