javacv ffmpeg 计算音频时长以及合并多个音频

311 阅读2分钟

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();
        }
    }
}