Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split stereo or merge monos helper #2052

Merged
merged 4 commits into from
Jul 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
152 changes: 152 additions & 0 deletions samples/AudioSplitMergeHelper.java
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* The code has been tested on s16le audio.
* <p>
* s16le means short 16bit little end. For other format, you may need change the ShortBuffer to other Buffer subclass
* <p>
* 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.
* <p>
* <p>
* ///////////////////////////////////////////////////////////////////////////
* JavaCV is an excellent open-source streaming processing framework in the Java field
* <p>
* 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.
* ///////////////////////////////////////////////////////////////////////////
* <p>
* 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
* <p>
* 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();
}
}