javacv
JavaCV 是一个用于 Java 的开源库,它提供了对 OpenCV 和 FFmpeg 等流行计算机视觉和音视频处理库的接口。通过 JavaCV,Java 开发者可以方便地利用这些库的强大功能,而无需深入了解底层的 C/C++ 代码。
ffmpeg
FFmpeg 是一个非常流行的开源多媒体框架,它能够处理视频和音频数据。FFmpeg 提供了一套丰富的命令行工具和库,用于转换、录制、解码和编码几乎所有格式的音视频文件。
maven
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.10</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>1.5.10</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>windows-x86_64</classifier>
</dependency>
计算音频时长
import org.bytedeco.javacv.FFmpegFrameGrabber;
import java.util.Objects;
public class Test {
/**
* 计算音频时长
* @param filePath 文件路径
* @return 音频时长,单位毫秒
* @throws Exception
*/
public static Long getLengthInTime(String filePath) throws Exception {
FFmpegFrameGrabber grabberOne = null;
try {
grabberOne = FFmpegFrameGrabber.createDefault(filePath);
grabberOne.start();
// 计算时长
return grabberOne.getLengthInTime() / 1000;
} finally {
if (Objects.nonNull(grabberOne)) {
grabberOne.close();
}
}
}
public static void main(String[] args) throws Exception {
String filePath = "test.wav";
System.out.println("getLengthInTime ======> " + getLengthInTime(filePath));
}
}
getLengthInTime ======> 7575
Input #0, wav, from 'test.wav':
Duration: 00:00:07.58, bitrate: 705 kb/s
Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 1 channels, s16, 705 kb/s
音频转码
public static byte[] audioFormat(byte[] bytes) throws Exception {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputStream)) {
grabber.start();
try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputStream, grabber.getAudioChannels())) {
recorder.setAudioCodec(avcodec.AV_CODEC_ID_PCM_S16LE);
recorder.setSampleRate(32000);
recorder.setAudioChannels(1);
recorder.setAudioBitrate(512000);
recorder.setFormat("wav");
recorder.start();
Frame frame;
while ((frame = grabber.grabFrame()) != null) {
recorder.record(frame);
}
recorder.stop();
}
grabber.stop();
}
return outputStream.toByteArray();
}
}
合并多个音频
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import java.io.IOException;
import java.util.List;
public class Test {
public static void audioConcat(String output, List<String> inputs) throws IOException {
List<FFmpegFrameGrabber> grabbers = new ArrayList<>();
List<Integer> sampleRates = new ArrayList<>();
FFmpegFrameGrabber firstGrabber = null;
for (String srcFile : inputs) {
firstGrabber = new FFmpegFrameGrabber(srcFile);
firstGrabber.start();
grabbers.add(firstGrabber);
sampleRates.add(firstGrabber.getSampleRate());
}
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(output, firstGrabber.getAudioChannels());
recorder.setSampleRate(sampleRates.getFirst());
recorder.start();
Frame frame;
for (FFmpegFrameGrabber grabber : grabbers) {
while ((frame = grabber.grabFrame()) != null) {
if (frame.samples == null) {
break;
}
recorder.recordSamples(frame.samples);
}
}
for (FFmpegFrameGrabber grabber : grabbers) {
grabber.stop();
}
recorder.stop();
}
public static void main(String[] args) throws Exception {
String outputPath = "output.wav";
List<String> inputList = List.of("input1.wav", "input2.wav", "input3.wav");
mergeWav(outputPath, inputList);
}
}
合并并且指定间隔以及片段的声音大小速度等
@Data
public class AudioSegment {
// 输入
// 路径
private String audioPath;
// 音量
private Double audioVolume;
// 倍速
private Double audioSpeed;
// 和下一个音频的间隔
private Integer audioInterval;
// 输出
// 音频时长
private Long audioLength;
@JsonIgnore
private byte[] audioBytes;
}
public class AudioUtils {
static {
FFmpegLogCallback.set();
FFmpegLogCallback.setLevel(AV_LOG_ERROR);
}
public static void mergeAudioFiles(List<AudioSegment> audioSegments, String outputPath) throws Exception {
if (CollectionUtils.isEmpty(audioSegments)) {
throw new RuntimeException("Audio segments list cannot be null or empty");
}
for (AudioSegment audioSegment : audioSegments) {
filterProcess(audioSegment);
}
mergeAudio(audioSegments, outputPath);
}
private static void filterProcess(AudioSegment audioSegment) throws Exception {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(Files.readAllBytes(Path.of(audioSegment.getAudioPath())));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
audioFrame(inputStream, outputStream, audioSegment.getAudioSpeed(), audioSegment.getAudioVolume());
audioSegment.setAudioBytes(outputStream.toByteArray());
}
}
public static void mergeAudio(List<AudioSegment> audioSegments, String outputPath) throws Exception {
if (CollectionUtils.isEmpty(audioSegments)) {
return;
}
try (FFmpegFrameGrabber initialGrabber = new FFmpegFrameGrabber(new ByteArrayInputStream(audioSegments.getFirst().getAudioBytes()))) {
initialGrabber.start();
int sampleRate = initialGrabber.getSampleRate();
int audioChannels = initialGrabber.getAudioChannels();
int audioBitrate = initialGrabber.getAudioBitrate();
int audioCodec = initialGrabber.getAudioCodec();
initialGrabber.stop();
try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, audioChannels)) {
recorder.setAudioCodec(audioCodec);
recorder.setSampleRate(sampleRate);
recorder.setAudioBitrate(audioBitrate);
recorder.start();
for (int i = 0; i < audioSegments.size(); i++) {
AudioSegment audioSegment = audioSegments.get(i);
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(new ByteArrayInputStream(audioSegment.getAudioBytes()))) {
grabber.start();
audioSegment.setAudioLength(grabber.getLengthInTime() / 1000);
recordAudio(grabber, recorder);
if (i < audioSegments.size() - 1
&& Objects.nonNull(audioSegment.getAudioInterval())
&& audioSegment.getAudioInterval() > 0) {
recordSilence(recorder, sampleRate, audioChannels, audioSegment.getAudioInterval());
}
}
audioSegment.setAudioBytes(null);
}
recorder.stop();
}
}
}
private static void recordAudio(FFmpegFrameGrabber grabber, FFmpegFrameRecorder recorder) throws Exception {
Frame frame;
while ((frame = grabber.grabFrame()) != null) {
recorder.record(frame);
}
}
private static void recordSilence(FFmpegFrameRecorder recorder, int sampleRate, int audioChannels, int durationMs) throws Exception {
int numSamples = (int) ((sampleRate * durationMs) / 1000.0);
short[] silentBuffer = new short[numSamples * audioChannels];
ShortBuffer buffer = ShortBuffer.wrap(silentBuffer);
try (Frame silenceFrame = new Frame()) {
silenceFrame.sampleRate = sampleRate;
silenceFrame.audioChannels = audioChannels;
silenceFrame.samples = new Buffer[]{buffer};
recorder.recordSamples(sampleRate, audioChannels, buffer);
}
}
public static void audioFrame(InputStream in, OutputStream out, Double audioSpeed, Double audioVolume) throws Exception {
FFmpegLogCallback.set();
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(in)) {
grabber.start();
try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(out, grabber.getAudioChannels())) {
recorder.setAudioCodec(grabber.getAudioCodec());
recorder.setSampleRate(grabber.getSampleRate());
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioBitrate(grabber.getAudioBitrate());
recorder.setFormat("wav");
recorder.start();
String filterString = String.format("atempo=%.1f,volume=%.1f", audioSpeed, audioVolume);
try (FFmpegFrameFilter filter = new FFmpegFrameFilter(filterString, grabber.getAudioChannels())) {
filter.setSampleRate(grabber.getSampleRate());
filter.start();
Frame frame;
while ((frame = grabber.grabFrame()) != null) {
filter.push(frame);
Frame filteredFrame;
while ((filteredFrame = filter.pull()) != null) {
recorder.record(filteredFrame);
}
}
filter.stop();
}
recorder.stop();
}
grabber.stop();
}
}
}