目录
一、音频的拆分,将mp3拆分为左右声道的wav文件
-
首先准备一个mp3文件到已知目录
-
读取文件,并拆分
/**
* 提取单声道录音,并分别上传
*/
@ResponseBody
@RequestMapping("/extDoubleAudio.action")
public void extDoubleAudio(){
String originPath = "E:/audio/visit/origin/少侠不用刀_(剑网3九周年翻唱丐太ver)-洛少爷-70600494.mp3";
String leftPath = "E:/audio/visit/left";
String rightPath = "E:/audio/visit/right";
//获取单声道录音
try {
AudioUtils.doubleChannelSplit(originPath,leftPath,rightPath);
}catch (Exception e){
logger.error("提取单声道录音出错e:{}",e);
}
}
/**
* 提取左右声道的单声道录音
* @param originPath
* @param leftFilePath
* @param rightFilePath
* @return
*/
public static File[] doubleChannelSplit(String originPath, String leftFilePath, String rightFilePath) {
String name = UUID.randomUUID().toString();
File leftFile = new File(leftFilePath);
File rightFile = new File(rightFilePath);
if (!leftFile.exists()) {
leftFile.mkdir();
}
if (!rightFile.exists()) {
rightFile.mkdir();
}
leftFilePath = leftFilePath + "/" + name + "left.wav";
rightFilePath = rightFilePath + "/" + name + "right.wav";
FFMPEGExecutor ffmpeg = locator.createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(originPath);
ffmpeg.addArgument("-map_channel");
ffmpeg.addArgument("0.0.0");
ffmpeg.addArgument(leftFilePath);
ffmpeg.addArgument("-map_channel");
ffmpeg.addArgument("0.0.1");
ffmpeg.addArgument(rightFilePath);
BufferedReader br = null;
try {
ffmpeg.execute();
br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()));
String line;
while ((line = br.readLine()) != null) {
logger.info(line);
}
return new File[]{leftFile, rightFile};
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
-
结果
既然我们拆分了左右声道,我们可以去判断是否分隔成功单声道
二、对音频文件的各项参数的判断
-
文件我们已经准备好,可以直接写代码,来获取参数
/**
* 查看音频各项参数
* @throws IOException
* @throws ReadOnlyFileException
* @throws TagException
* @throws InvalidAudioFrameException
*/
@ResponseBody
@RequestMapping("/checkAudioParam.action")
public void checkAudioParam() throws IOException, ReadOnlyFileException, TagException, InvalidAudioFrameException {
File originPath = new File("E:/audio/visit/origin/少侠不用刀_(剑网3九周年翻唱丐太ver)-洛少爷-70600494.mp3");
File leftPath = new File("E:/audio/visit/left/91fdaae1-6010-4f60-836d-d20db78d986fleft.wav");
File rightPath = new File("E:/audio/visit/right/91fdaae1-6010-4f60-836d-d20db78d986fright.wav");
RandomAccessFile rdfOrigin = null;
RandomAccessFile rdfLeft = null;
RandomAccessFile rdfRight = null;
// rdfOrigin = new RandomAccessFile(originPath,"r");
rdfLeft = new RandomAccessFile(leftPath,"r");
rdfRight = new RandomAccessFile(rightPath,"r");
MP3File file = new MP3File(originPath);
MP3AudioHeader header = file.getMP3AudioHeader();
System.out.println("时长: " + header.getTrackLength()); //获得时长
System.out.println("比特率: " + header.getBitRate()); //获得比特率
System.out.println("音轨长度: " + header.getTrackLength()); //音轨长度
System.out.println("格式: " + header.getFormat()); //格式,例 MPEG-1
System.out.println("声道: " + header.getChannels()); //声道
System.out.println("采样率: " + header.getSampleRate()); //采样率
System.out.println("MPEG: " + header.getMpegLayer()); //MPEG
System.out.println("MP3起始字节: " + header.getMp3StartByte()); //MP3起始字节
System.out.println("精确的音轨长度: " + header.getPreciseTrackLength()); //精确的音轨长度
// System.out.println("声音尺寸: " + toInt(read(rdfOrigin, 4, 4))); // 声音尺寸
// System.out.println("音频格式 1 = PCM: " + toShort(read(rdfOrigin, 20, 2))); // 音频格式 1 = PCM
// System.out.println("声道: " + toShort(read(rdfOrigin, 22, 2))); // 1 单声道 2 双声道
// System.out.println("采样率: " + toInt(read(rdfOrigin, 24, 4))); // 采样率、音频采样级别 8000 = 8KHz
// System.out.println("每秒波形数量: " + toInt(read(rdfOrigin, 28, 4))); // 每秒波形的数据量
// System.out.println("采样帧大小: " + toShort(read(rdfOrigin, 32, 2))); // 采样帧的大小
// System.out.println("采样位数: " + toShort(read(rdfOrigin, 34, 2))); // 采样位数
System.out.println("---------------------------------------------------Origin-left数据分割线-----------------------------------------------");
System.out.println("声音尺寸: " + toInt(read(rdfLeft, 4, 4))); // 声音尺寸
System.out.println("音频格式 1 = PCM: " + toShort(read(rdfLeft, 20, 2))); // 音频格式 1 = PCM
System.out.println("声道: " + toShort(read(rdfLeft, 22, 2))); // 1 单声道 2 双声道
System.out.println("采样率: " + toInt(read(rdfLeft, 24, 4))); // 采样率、音频采样级别 8000 = 8KHz
System.out.println("每秒波形数量: " + toInt(read(rdfLeft, 28, 4))); // 每秒波形的数据量
System.out.println("采样帧大小: " + toShort(read(rdfLeft, 32, 2))); // 采样帧的大小
System.out.println("采样位数: " + toShort(read(rdfLeft, 34, 2))); // 采样位数
System.out.println("---------------------------------------------------left-right数据分割线-----------------------------------------------");
System.out.println("声音尺寸: " + toInt(read(rdfRight, 4, 4))); // 声音尺寸
System.out.println("音频格式 1 = PCM: " + toShort(read(rdfRight, 20, 2))); // 音频格式 1 = PCM
System.out.println("声道: " + toShort(read(rdfRight, 22, 2))); // 1 单声道 2 双声道
System.out.println("采样率: " + toInt(read(rdfRight, 24, 4))); // 采样率、音频采样级别 8000 = 8KHz
System.out.println("每秒波形数量: " + toInt(read(rdfRight, 28, 4))); // 每秒波形的数据量
System.out.println("采样帧大小: " + toShort(read(rdfRight, 32, 2))); // 采样帧的大小
System.out.println("采样位数: " + toShort(read(rdfRight, 34, 2))); // 采样位数
// rdfOrigin.close();
rdfLeft.close();
rdfRight.close();
}
public static int toInt(byte[] b) {
return (((b[3] & 0xff) << 24) + ((b[2] & 0xff) << 16) + ((b[1] & 0xff) << 8) + ((b[0] & 0xff) << 0));
// return ((b[3] << 24) + (b[2] << 16) + (b[1] << 8) + (b[0] << 0));
}
public static short toShort(byte[] b) {
return (short)(((b[1] & 0xff) << 8) + ((b[0] & 0xff) << 0));
// return (short)((b[1] << 8) + (b[0] << 0));
}
public static byte[] read(RandomAccessFile rdf, int pos, int length) throws IOException {
rdf.seek(pos);
byte result[] = new byte[length];
for (int i = 0; i < length; i++) {
result[i] = rdf.readByte();
}
return result;
}
注:因为mp3格式的使用富文本编辑器会出现数据不准的情况,所以,我们使用了MP3AudioHeader
- 结果
声道类型直通车 注:Joint Stereo代表立体声 ,而1代表了单声道
既然我们可以拆分音频,那么我们可不可以合并了,当然是可以了,我们来写代码
三、对音频的合并,将wav合并为wav、map3格式
/**
* 合并单声道录音为双声道
* @throws Exception
*/
@ResponseBody
@RequestMapping("/mergeAudioDoubts.action")
public void mergeAudioDoubts() throws Exception {
byte[] left = Files.readAllBytes(Paths.get("E:/audio/visit/left", "91fdaae1-6010-4f60-836d-d20db78d986fleft.wav"));
byte[] right = Files.readAllBytes(Paths.get("E:/audio/visit/right", "91fdaae1-6010-4f60-836d-d20db78d986fright.wav"));
String originPathWav = "E:/audio/visit/mergeDoubts/少侠不用刀.wav";
String originPathMp3 = "E:/audio/visit/mergeDoubts/少侠不用刀.mp3";
AudioUtils.merge(left, right, originPathWav);
AudioUtils.merge(left, right, originPathMp3);
}
/**
* 下面的整个部分都属于合并接口
* @param head
* @return
*/
private static boolean isWav(byte[] head) {
return ("RIFF".equals(new String(head, 0, 4, ISO_8859_1)) &&
"WAVE".equals(new String(head, 8, 4, ISO_8859_1)));
}
private static void fileTooSmall(byte[] file) throws Exception {
if (file.length < HEAD_LENGTH + FORMAT_LENGTH) {
logger.warn("file is too small, size if {}.", file.length);
throw new Exception();
}
}
private static int headSize() {
return HEAD_LENGTH + FORMAT_LENGTH;
}
public static int fileSize(byte[] file) throws Exception {
fileTooSmall(file);
byte[] head = copyOfRange(file, 0, HEAD_LENGTH);
if (isWav(head)) {
return ByteBuffer.wrap(copyOfRange(head, 4, 8))
.order(LITTLE_ENDIAN)
.getInt() + 8;
} else {
logger.warn("file format error: expected {}, actual {}.",
"[82, 73, 70, 70, *, *, *, *, 87, 65, 86, 69]",
head);
throw new Exception();
}
}
public static AudioFormat fileFormat(byte[] file) throws Exception {
fileTooSmall(file);
byte[] head = copyOfRange(file, 0, HEAD_LENGTH);
if (isWav(head)) {
byte[] format = copyOfRange(file, 12, HEAD_LENGTH + FORMAT_LENGTH);
String chuckID = new String(format, 0, 4, ISO_8859_1);
int chunkSize = ByteBuffer.wrap(copyOfRange(format, 4, 8))
.order(LITTLE_ENDIAN).getInt();
int audioFmt = ByteBuffer.wrap(copyOfRange(format, 8, 10))
.order(LITTLE_ENDIAN).getShort();
int channels = ByteBuffer.wrap(copyOfRange(format, 10, 12))
.order(LITTLE_ENDIAN).getShort();
int sampleRate = ByteBuffer.wrap(copyOfRange(format, 12, 16))
.order(LITTLE_ENDIAN).getInt();
int byteRate = ByteBuffer.wrap(copyOfRange(format, 16, 20))
.order(LITTLE_ENDIAN).getInt();
int frameSize = ByteBuffer.wrap(copyOfRange(format, 20, 22))
.order(LITTLE_ENDIAN).getShort();
int sampleSizeInBits = ByteBuffer.wrap(copyOfRange(format, 22, 24))
.order(LITTLE_ENDIAN).getShort();
return new AudioFormat(PCM_SIGNED, sampleRate,
sampleSizeInBits, channels, frameSize, sampleRate, false);
} else {
logger.warn("file is not a wav.");
throw new Exception();
}
}
public static void merge(final byte[] left, final byte[] right, final String path) throws Exception {
int leftSize = fileSize(left);
int rightSize = fileSize(right);
int mergeSize = mergeSizeField(leftSize, rightSize);
int mergeDataSize = mergeDataSize(leftSize, rightSize);
try (RandomAccessFile file = new RandomAccessFile(path, "rw")) {
file.write(mergeHead(left, mergeSize));
file.write(dataChunkHead(mergeDataSize));
int max = Math.max(leftSize, rightSize);
for (int i = headSize() + 8; i < max + 8; i += 2) {
file.write(read(left, i));
file.write(read(right, i));
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static byte[] read(final byte[] content, int offset) {
if (content.length > offset) {
return copyOfRange(content, offset, offset + 2);
} else {
return "\0\0".getBytes(ISO_8859_1);
}
}
private static int mergeSizeField(int left, int right) {
int max = Math.max(left - 8, right - 8);
return max * 2;
}
private static int mergeDataSize(int left, int right) {
int max = Math.max(left - headSize() - 8, right - headSize() - 8);
return max * 2;
}
private static byte[] mergeHead(final byte[] left, final int mergeSize) throws Exception {
AudioFormat format = fileFormat(left);
ByteBuffer size = ByteBuffer.allocate(4).order(LITTLE_ENDIAN).putInt(mergeSize);
ByteBuffer channels = ByteBuffer.allocate(2).order(LITTLE_ENDIAN).putShort((short) 2);
ByteBuffer sampleRate = ByteBuffer.allocate(4).order(LITTLE_ENDIAN)
.putInt((int) format.getSampleRate());
ByteBuffer byteRate = ByteBuffer.allocate(4).order(LITTLE_ENDIAN)
.putInt((int) format.getSampleRate() * 2 * format.getSampleSizeInBits() / 8);
ByteBuffer blockAlign = ByteBuffer.allocate(2).order(LITTLE_ENDIAN)
.putShort((short) (2 * format.getSampleSizeInBits() / 8));
ByteBuffer bitsPerSample = ByteBuffer.allocate(2).order(LITTLE_ENDIAN)
.putShort((short) format.getSampleSizeInBits());
ByteBuffer head = ByteBuffer.allocate(headSize());
head.put(left, 0, 4);
head.put(size.array());
head.put(left, 8, 14);
head.put(channels.array());
head.put(sampleRate.array());
head.put(byteRate.array());
head.put(blockAlign.array());
head.put(bitsPerSample.array());
return head.array();
}
private static byte[] dataChunkHead(final int length) {
ByteBuffer head = ByteBuffer.allocate(8);
head.put("data".getBytes(ISO_8859_1));
ByteBuffer size = ByteBuffer.allocate(4).order(LITTLE_ENDIAN).putInt(length);
head.put(size.array());
return head.array();
}
- 结果
可以看出,mp3的文件并没有合并成功,声道类型依旧是单声道,也就是,我们如果需要mp3格式的音频文件,还需要从,wav去转换
结果:
四、wav双声道格式转换为mp3格式
/**
* 音频格式 wav---mp3
*/
@ResponseBody
@RequestMapping("/changeAudioFormat.action")
public void changeAudioFormat(){
String wavFilePath = "E:/audio/visit/mergeDoubts/少侠不用刀.wav";
String mp3FilePath = "E:/audio/visit/mergeDoubts/少侠不用刀-change.mp3";
AudioUtils.wavChange2Mp3(wavFilePath,mp3FilePath);
}
public static void wavChange2Mp3(String wavFilePath,String mp3FilePath){
try {
File wavFile = new File(wavFilePath);
File mp3File = new File(mp3FilePath);
AudioAttributes audio = new AudioAttributes();
audio.setCodec("libmp3lame");
audio.setBitRate(128000);
audio.setChannels(2);
audio.setSamplingRate(44100);
EncodingAttributes encoding = new EncodingAttributes();
encoding.setFormat("mp3");
encoding.setAudioAttributes(audio);
Encoder encoder = new Encoder(locator);
encoder.encode(new MultimediaObject(wavFile,locator),mp3File,encoding);
} catch (EncoderException e) {
e.printStackTrace();
}
}
结果:
过程都弄完了,剩下的就是听音频了 ,听音乐就简单了,可以直接用audio标签
五、播放音频
<button class="playMusic">
播放
</button>
//预览音频
$(document).on('click', '.playMusic', function() {
//注:如果是本地,因为处于安全,会出现前缀file://目前我还没有什么好的解决方案,所以,我是放的我自己的服务器音频文件
var _src = "http://xxxx:8080/audio/0c5cb6f68228451fa20422554a23f1317498.wav";
previewaudio(_src);
})
//音频预览
function previewaudio(obj) {
var audioHtml = '<div style="padding:20px;margin:0 auto;">' +
'<audio src="'+obj+'" width="68px" height="68px" controls></audio></div>';
//弹出层
layer.open({
type: 1,
shade: 0.8,
offset: 'auto',
area: ['auto','200px'],
shadeClose:true,//点击外围关闭弹窗
scrollbar: false,//不现实滚动条
title: "音频预览", //不显示标题
content: audioHtml
});
}
结果:
该录音并不是我测试用的音频文件
注:文中公共参数
private static Logger logger = LoggerFactory.getLogger(AudioUtils.class);
private static final int HEAD_LENGTH = 12;
private static final int FORMAT_LENGTH = 24;
private static FFMPEGLocator locator = new FFMPEGLocator() {
@Override
protected String getFFMPEGExecutablePath() {
return "F:/ffmpeg/bin/ffmpeg.exe"; //你自己下载的ffmpeg路径
}
};
非常感谢以下几位大佬的博客,因为借鉴太多,我就厚脸皮的都加上了,所以,各位大佬原谅我发布为原创