From dba789d3bd22ab5e7ddd9734f78427e0aade2fa7 Mon Sep 17 00:00:00 2001 From: steeveen <1136671955@qq.com> Date: Sat, 8 Jul 2023 19:20:56 +0800 Subject: [PATCH] * Add new `AudioSplitMergeHelper` sample for processing raw audio frames (pull #2052) --- CHANGELOG.md | 2 + samples/AudioSplitMergeHelper.java | 152 +++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 samples/AudioSplitMergeHelper.java diff --git a/CHANGELOG.md b/CHANGELOG.md index cd266bf6..3fcb2585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ + * Add new `AudioSplitMergeHelper` sample for processing raw audio frames ([pull #2052](https://github.com/bytedeco/javacv/pull/2052)) + ### June 6, 2023 version 1.5.9 * Add `FrameRecorder.videoSideData/audioSideData` properties and `FFmpegFrameRecorder.setDisplayRotation()` for convenience ([issue #1976](https://github.com/bytedeco/javacv/issues/1976)) * Fix `FFmpegFrameGrabber.grab()` not returning audio frames buffered by the codec ([issue #1971](https://github.com/bytedeco/javacv/issues/1971)) diff --git a/samples/AudioSplitMergeHelper.java b/samples/AudioSplitMergeHelper.java new file mode 100644 index 00000000..bf7d0ea0 --- /dev/null +++ b/samples/AudioSplitMergeHelper.java @@ -0,0 +1,152 @@ +import org.bytedeco.javacv.*; + +import java.nio.Buffer; +import java.nio.ShortBuffer; + +/** + * This code is a sample which split a 2-channel stereo audio into 2 single-channel mono audios + * or merge 2 single-channel mono audios into a 2-channel stereo. + *
+ * The code has been tested on s16le audio. + *
+ * s16le means short 16bit little end. For other format, you may need change the ShortBuffer to other Buffer subclass + *
+ * For s16lep, s32lep,xxxxxp format, the sample point arrangement format is no longer in ‘LRLRLR'. + * Instead, it is arragement in format 'LLLLLL','RRRRRR'. So you have to change the short copy code. + *
+ *
+ * /////////////////////////////////////////////////////////////////////////// + * JavaCV is an excellent open-source streaming processing framework in the Java field + *
+ * But I see many people, especially in China, making profits for themselves by introducing its usage, + * which is not in line with the concept of open source projects. + * I hope that If this code helped you, you can share your experience and knowledge with others in the world, rather + * than for personal gain. Spread the spirit of open source. + * /////////////////////////////////////////////////////////////////////////// + *
+ * Acknowledge: Thanks for my hot girlfriend. + * + * @author steeveen + * @date 2023/7/1 14:32 + */ +public class AudioSplitMergeHelper { + + + /** + * split a 2-channel stereo audio into 2 single-channel mono audios + *
+ * If you want to split this 2-channel stereo to 2 single-channel stereo, you should create 2 2-channel stereos + * and fill one channel with 0 data. It is similar in principle, so the code won't go into too much here. + * + * @param input the file path which is to be splited + * @param outputLeft the file path which store the left channel audio file + * @param outputRight the file path which store the right channel audio file + * @throws FrameGrabber.Exception + * @throws FrameRecorder.Exception + */ + public static void split(String input, String outputLeft, String outputRight) throws FrameGrabber.Exception, FrameRecorder.Exception { + //grabber from input + FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(input); + grabber.start(); + //two recorders for two channels + FFmpegFrameRecorder leftRecorder = new FFmpegFrameRecorder(outputLeft, 1); + leftRecorder.setSampleRate(grabber.getSampleRate()); + leftRecorder.start(); + FFmpegFrameRecorder rightRecorder = new FFmpegFrameRecorder(outputRight, 1); + rightRecorder.setSampleRate(grabber.getSampleRate()); + rightRecorder.start(); + + Frame frame = null; + while ((frame = grabber.grabSamples()) != null) { + // use s16le for example. so select ShortBuffer to receive the sample + ShortBuffer sb = (ShortBuffer) frame.samples[0]; + short[] shorts = new short[sb.limit()]; + sb.get(shorts); + //Split the LRLRLR to LLL in left channel and RRR int right channel + Frame leftFrame = frame.clone(); + ShortBuffer leftSb = ShortBuffer.allocate(sb.capacity() / 2); + leftFrame.samples = new Buffer[]{leftSb}; + leftFrame.audioChannels = 1; + + Frame rightFrame = frame.clone(); + ShortBuffer rightSb = ShortBuffer.allocate(sb.capacity() / 2); + rightFrame.samples = new Buffer[]{rightSb}; + rightFrame.audioChannels = 1; + + for (int i = 0; i < shorts.length; i++) { + if (i % 2 == 0) { + leftSb.put(shorts[i]); + } else { + rightSb.put(shorts[i]); + } + } + // reset the buffer to read mode + leftSb.rewind(); + rightSb.rewind(); + leftRecorder.record(leftFrame); + rightRecorder.record(rightFrame); + } + //release source + grabber.close(); + leftRecorder.close(); + rightRecorder.close(); + } + + /** + * Merge 2 single-channel mono audios into a 2-channel stereo. + * As usual the two input audios should have the same parameter and length; + * + * @param inputLeft the left channel to be merged in + * @param inputRight the right channel to be merged in + * @param output the merged stereo audio + * @throws FFmpegFrameGrabber.Exception + * @throws FFmpegFrameRecorder.Exception + */ + public static void merge(String inputLeft, String inputRight, String output) throws FrameGrabber.Exception, FrameRecorder.Exception { + FFmpegFrameGrabber leftGrabber = new FFmpegFrameGrabber(inputLeft); + leftGrabber.start(); + FFmpegFrameGrabber rightGrabber = new FFmpegFrameGrabber(inputRight); + rightGrabber.start(); + FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(output, 2); + //you'd better confirm the two input have the same samplerate. otherwise, you should control it manually by yourself + recorder.setSampleRate(leftGrabber.getSampleRate()); + recorder.start(); + + Frame leftFrame = null; + Frame rightFrame = null; + int index = 0; + int maxLength = leftGrabber.getLengthInAudioFrames(); + while (index < maxLength) { + // carry the bit data from two input into result frame by frame + leftFrame = leftGrabber.grabSamples(); + rightFrame = rightGrabber.grabSamples(); + ShortBuffer leftSb = (ShortBuffer) leftFrame.samples[0]; + ShortBuffer rightSb = (ShortBuffer) rightFrame.samples[0]; + short[] leftShorts = new short[leftSb.limit()]; + short[] rightShorts = new short[rightSb.limit()]; + leftSb.get(leftShorts); + rightSb.get(rightShorts); + ShortBuffer mergeSb = ShortBuffer.allocate(leftSb.capacity() + rightSb.capacity()); + + // create a template from the existing frame + Frame mergeFrame = leftFrame.clone(); + // replace the frame tempalte by our merged buffer + mergeFrame.samples = new Buffer[]{mergeSb}; + mergeFrame.audioChannels = 2; + + for (int i = 0; i < leftShorts.length; i++) { + mergeSb.put(leftShorts[i]); + mergeSb.put(rightShorts[i]); + } + + //reset buffer to read mode + mergeSb.flip(); + recorder.record(mergeFrame); + index++; + } + //release source + leftGrabber.close(); + rightGrabber.close(); + recorder.close(); + } +}