Skip to content

Commit 9e31c27

Browse files
committed
AudioResampler utility class which can be used for sampling rate conversion
1 parent dc7d00f commit 9e31c27

File tree

3 files changed

+270
-0
lines changed

3 files changed

+270
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2021 Alex Andres
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "JNI_AudioResampler.h"
18+
#include "Exception.h"
19+
#include "JavaArrayList.h"
20+
#include "JavaEnums.h"
21+
#include "JavaError.h"
22+
#include "JavaObject.h"
23+
#include "JavaRef.h"
24+
#include "JavaString.h"
25+
#include "JavaUtils.h"
26+
27+
#include "common_audio/resampler/push_sinc_resampler.h"
28+
29+
JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_audio_AudioResampler_dispose
30+
(JNIEnv * env, jobject caller)
31+
{
32+
webrtc::PushSincResampler * resampler = GetHandle<webrtc::PushSincResampler>(env, caller);
33+
CHECK_HANDLE(resampler);
34+
35+
delete resampler;
36+
37+
SetHandle<std::nullptr_t>(env, caller, nullptr);
38+
}
39+
40+
JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_audio_AudioResampler_initialize
41+
(JNIEnv * env, jobject caller, jint sourceFrames, jint targetFrames)
42+
{
43+
webrtc::PushSincResampler * resampler = new webrtc::PushSincResampler(sourceFrames, targetFrames);
44+
45+
SetHandle(env, caller, resampler);
46+
}
47+
48+
JNIEXPORT jint JNICALL Java_dev_onvoid_webrtc_media_audio_AudioResampler_resampleInternal
49+
(JNIEnv * env, jobject caller, jbyteArray samplesIn, jint nSamplesIn, jbyteArray samplesOut, jint maxSamplesOut)
50+
{
51+
webrtc::PushSincResampler * resampler = GetHandle<webrtc::PushSincResampler>(env, caller);
52+
CHECK_HANDLEV(resampler, -1);
53+
54+
jboolean isDstCopy = JNI_FALSE;
55+
56+
jbyte * srcPtr = env->GetByteArrayElements(samplesIn, nullptr);
57+
jbyte * dstPtr = env->GetByteArrayElements(samplesOut, &isDstCopy);
58+
59+
size_t result = resampler->Resample(reinterpret_cast<int16_t *>(srcPtr), nSamplesIn, reinterpret_cast<int16_t *>(dstPtr), maxSamplesOut);
60+
61+
if (isDstCopy == JNI_TRUE) {
62+
jsize dstLength = env->GetArrayLength(samplesOut);
63+
64+
env->SetByteArrayRegion(samplesOut, 0, dstLength, dstPtr);
65+
}
66+
67+
env->ReleaseByteArrayElements(samplesIn, srcPtr, JNI_ABORT);
68+
env->ReleaseByteArrayElements(samplesOut, dstPtr, JNI_ABORT);
69+
70+
return static_cast<jint>(result);
71+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2021 Alex Andres
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dev.onvoid.webrtc.media.audio;
18+
19+
import static java.util.Objects.requireNonNull;
20+
21+
import dev.onvoid.webrtc.internal.DisposableNativeObject;
22+
23+
/**
24+
* Audio sampling rate converter. This resampler operates on audio frames of 10
25+
* milliseconds.
26+
*
27+
* @author Alex Andres
28+
*/
29+
public class AudioResampler extends DisposableNativeObject {
30+
31+
private final int sourceFrames;
32+
33+
private final int targetFrames;
34+
35+
36+
/**
37+
* Creates a new {@code AudioResampler} with specified sampling frequency
38+
* parameters.
39+
*
40+
* @param sourceSampleRate The sampling frequency of the input signal.
41+
* @param targetSampleRate The sampling frequency of the output signal.
42+
*/
43+
public AudioResampler(int sourceSampleRate, int targetSampleRate) {
44+
// 10 ms frames
45+
sourceFrames = sourceSampleRate / 100;
46+
targetFrames = targetSampleRate / 100;
47+
48+
initialize(sourceFrames, targetFrames);
49+
}
50+
51+
/**
52+
* Converts the input samples into the output samples with the sampling
53+
* frequency specified in the constructor. The audio input must be of the
54+
* length of 10 milliseconds. Accordingly, the output has the length of 10
55+
* milliseconds.
56+
*
57+
* @param samplesIn The audio samples to convert.
58+
* @param nSamplesIn The number of audio samples to consider from {@code
59+
* samplesIn}.
60+
* @param samplesOut The converted audio samples.
61+
*
62+
* @return The number of converted audio samples.
63+
*/
64+
public int resample(byte[] samplesIn, int nSamplesIn, byte[] samplesOut) {
65+
requireNonNull(samplesIn);
66+
requireNonNull(samplesOut);
67+
68+
final int arraySamplesIn = samplesIn.length / 2;
69+
final int maxSamplesOut = samplesOut.length / 2;
70+
71+
nSamplesIn = Math.min(arraySamplesIn, Math.max(0, nSamplesIn));
72+
73+
if (targetFrames > maxSamplesOut) {
74+
throw new IllegalArgumentException("Insufficient samples output length");
75+
}
76+
77+
return resampleInternal(samplesIn, nSamplesIn, samplesOut, maxSamplesOut);
78+
}
79+
80+
@Override
81+
public native void dispose();
82+
83+
private native void initialize(int sourceFrames, int targetFrames);
84+
85+
private native int resampleInternal(byte[] samplesIn, int nSamplesIn,
86+
byte[] samplesOut, int maxSamplesOut);
87+
88+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2019 Alex Andres
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dev.onvoid.webrtc.media.audio;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertThrows;
21+
22+
import dev.onvoid.webrtc.TestBase;
23+
24+
import org.junit.jupiter.api.AfterEach;
25+
import org.junit.jupiter.api.Test;
26+
27+
class AudioResamplerTest extends TestBase {
28+
29+
private AudioResampler resampler;
30+
31+
32+
@AfterEach
33+
void dispose() {
34+
resampler.dispose();
35+
}
36+
37+
@Test
38+
void targetBufferUnderflow() {
39+
resampler = new AudioResampler(48000, 24000);
40+
41+
SampleBuffer buffer = new SampleBuffer(48000, 24000);
42+
buffer.setTargetBufferSize(buffer.frameSizeOut / 2);
43+
44+
assertThrows(IllegalArgumentException.class, () -> {
45+
resample(resampler, buffer);
46+
});
47+
}
48+
49+
@Test
50+
void downSample() {
51+
resampler = new AudioResampler(48000, 44100);
52+
53+
SampleBuffer buffer = new SampleBuffer(48000, 44100);
54+
55+
int result = resample(resampler, buffer);
56+
57+
assertEquals(buffer.nSamplesOut, result);
58+
}
59+
60+
@Test
61+
void upSample() {
62+
resampler = new AudioResampler(32000, 48000);
63+
64+
SampleBuffer buffer = new SampleBuffer(32000, 48000);
65+
66+
int result = resample(resampler, buffer);
67+
68+
assertEquals(buffer.nSamplesOut, result);
69+
}
70+
71+
private static int resample(AudioResampler resampler, SampleBuffer buffer) {
72+
return resampler.resample(buffer.src, buffer.nSamplesIn, buffer.dst);
73+
}
74+
75+
76+
77+
private static class SampleBuffer {
78+
79+
final int bytesPerFrame = 2;
80+
81+
final int sampleRateIn;
82+
final int sampleRateOut;
83+
84+
final int nSamplesIn;
85+
final int nSamplesOut;
86+
87+
final int frameSizeIn;
88+
final int frameSizeOut;
89+
90+
byte[] src;
91+
byte[] dst;
92+
93+
94+
SampleBuffer(int sampleRateIn, int sampleRateOut) {
95+
this.sampleRateIn = sampleRateIn;
96+
this.sampleRateOut = sampleRateOut;
97+
98+
nSamplesIn = sampleRateIn / 100; // 10 ms frame
99+
nSamplesOut = sampleRateOut / 100;
100+
frameSizeIn = nSamplesIn * bytesPerFrame;
101+
frameSizeOut = nSamplesOut * bytesPerFrame;
102+
103+
src = new byte[frameSizeIn];
104+
dst = new byte[frameSizeOut];
105+
}
106+
107+
void setTargetBufferSize(int size) {
108+
dst = new byte[size];
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)