需求说明

需要根据时间切割mp3、wav文件
切割:列如一个时常2分钟的mp3,需要保留XX秒后的音频数据,XX秒之前则丢弃
合并:需要在MP3或wav歌曲播放之前写入指定音频,将其连在一起

技术实现

环境准备

该工具为了快速实现本地化功能,采用了javafx8
在切割MP3和WAV时使用到了第三方库jaudiotagger
pom依赖:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/net.jthink/jaudiotagger -->
<dependency>
<groupId>net.jthink</groupId>
<artifactId>jaudiotagger</artifactId>
<version>3.0.1</version>
</dependency>

在合并时可能出现音频格式不一致,由于jaudiotagger没有发现针对mp3音频格式控制的接口暴露,所以采用了FFmpeg,使用javaac集成包
添加pom依赖

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.bytedeco/javacv-platform -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.9</version>
</dependency>

核心功能实现

mp3切割实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

/**
*
* @param music 需要切割的mp3文件
* @param savePath 保存路径
* @param time 切割时间
* @return
*/
public File cutMusicByTime(File music, String savePath, long time) {
//预备开始截取的时间
long beginTime = time * 1000;
//原mp3文件
MP3File mp3 = new MP3File(music);
MP3AudioHeader header = (MP3AudioHeader) mp3.getAudioHeader();
int length = header.getTrackLength() ;
//截取结算时间
long endTime = length * 1000;
long bitRateKbps = header.getBitRateAsNumber();
// 1KByte/s=8Kbps, bitRate *1024L / 8L / 1000L 转换为 bps 每毫秒
// 计算出开始字节位置
long beginBitRateBpm = (bitRateKbps* 1024L / 8L / 1000L) * beginTime;
// 返回音乐数据的第一个字节
long firstFrameByte = header.getMp3StartByte();
// 获取开始时间所在文件的字节位置
long beginByte = firstFrameByte + beginBitRateBpm;
// 计算出结束字节位置
long endByte = beginByte + (bitRateKbps* 1024L / 8L / 1000L) * (endTime - beginTime);
//目标文件
File dFile = new File(savePath);
RandomAccessFile dRaf = new RandomAccessFile(dFile, &quot;rw&quot;);
RandomAccessFile sRaf = new RandomAccessFile(music, &quot;rw&quot;);
//先将mp3的头文件写入文件
for(long i = 0; i&lt; firstFrameByte;i++){
int m = sRaf.read();
dRaf.write(m);
}
//跳转到指定的位置
sRaf.seek(beginByte);
//开始写入 mp3实体
for(long i = 0; i&lt;= endByte-beginByte ;i++){
int m = sRaf.read();
dRaf.write(m);
}
sRaf.close();
dRaf.close();
return dFile;
}

mp3合并实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void convertAudioParameters(String fileAPath, String fileBPath, String outputPath)
throws FrameGrabber.Exception, FrameRecorder.Exception {

FFmpegFrameGrabber grabberA = new FFmpegFrameGrabber(fileAPath);
FFmpegFrameGrabber grabberB = new FFmpegFrameGrabber(fileBPath);
grabberA.start();
grabberB.start();
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, grabberB.getAudioChannels());
//写入采样率
recorder.setSampleRate(grabberB.getSampleRate());
//写入音频码率
recorder.setAudioBitrate(grabberB.getAudioBitrate());
//复制tag
for (Map.Entry&lt;String, String&gt; entry : grabberB.getMetadata().entrySet()) {
recorder.setMetadata(entry.getKey(),entry.getValue());
}
recorder.start();
Frame frameA;
Frame frameB;
//写入A文件
while ((frameA = grabberA.grab()) != null) {
recorder.record(frameA);
}
//写入B文件
while ((frameB = grabberB.grab()) != null) {
recorder.record(frameB);
}
grabberA.stop();
grabberB.stop();
recorder.stop();
}

wav切割实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public File cutMusicByTime(File inputFile, String savePath, long time) {
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputFile);

int bytesPerFrame = audioInputStream.getFormat().getFrameSize();
int frameRate = (int) audioInputStream.getFormat().getFrameRate();
audioInputStream.skip(time * frameRate * bytesPerFrame);
long lengthToRead = audioInputStream.getFrameLength() * bytesPerFrame;
AudioInputStream shortenedStream = new AudioInputStream(
audioInputStream,
audioInputStream.getFormat(),
lengthToRead / bytesPerFrame
);
File outputFile = new File(savePath);
AudioSystem.write(shortenedStream, AudioFileFormat.Type.WAVE, outputFile);
copyAlbumInformation(inputFile, outputFile);
return outputFile;
}
private static void copyAlbumInformation(File sourceFile, File outputFile)
throws UnsupportedAudioFileException, IOException, TagException, ReadOnlyFileException, CannotWriteException, CannotReadException, InvalidAudioFrameException {
AudioFile sourceAudioFile = AudioFileIO.read(sourceFile);
Tag sourceTag = sourceAudioFile.getTag();
AudioHeader audioHeader = sourceAudioFile.getAudioHeader();
if (sourceTag != null) {
AudioFile outputAudioFile = AudioFileIO.read(outputFile);
Tag outputTag = outputAudioFile.getTag();
Iterator&lt;TagField&gt; fields = sourceTag.getFields();
while (fields.hasNext()){
TagField tagField = fields.next();
outputTag.setField(tagField);
}
outputAudioFile.commit();
}
}

wav合并实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void mergeWAV(File inputFile1, File inputFile2, String outputFilePath) throws IOException, UnsupportedAudioFileException {
AudioInputStream audioInputStream1 = AudioSystem.getAudioInputStream(inputFile1);
AudioInputStream audioInputStream2 = AudioSystem.getAudioInputStream(inputFile2);

AudioFormat format1 = audioInputStream1.getFormat();
AudioFormat format2 = audioInputStream2.getFormat();

if (!format1.matches(format2)) {
audioInputStream1 = AudioSystem.getAudioInputStream(format2, audioInputStream1);
format1 = audioInputStream1.getFormat();
}

AudioInputStream appendedFiles = new AudioInputStream(
new SequenceInputStream(audioInputStream1, audioInputStream2),
format1,
audioInputStream1.getFrameLength() + audioInputStream2.getFrameLength()
);

AudioSystem.write(appendedFiles, AudioFileFormat.Type.WAVE, new File(outputFilePath));
}