Skip to content

Commit 7655915

Browse files
Add native microphone VAD input
1 parent 92d708c commit 7655915

8 files changed

Lines changed: 684 additions & 44 deletions

File tree

NitroWebrtcVad.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ Pod::Spec.new do |s|
3636

3737
s.dependency 'React-jsi'
3838
s.dependency 'React-callinvoker'
39+
s.frameworks = 'AVFoundation'
3940
install_modules_dependencies(s)
4041
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
23
</manifest>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.nitrowebrtcvad;
2+
3+
import android.media.AudioFormat;
4+
import android.media.AudioRecord;
5+
import android.media.MediaRecorder;
6+
import android.util.Log;
7+
8+
import java.util.concurrent.atomic.AtomicBoolean;
9+
10+
final class NitroWebrtcVadAudioInput {
11+
private static final String TAG = "NitroWebrtcVadAudio";
12+
13+
private static final AtomicBoolean isRecording = new AtomicBoolean(false);
14+
private static AudioRecord audioRecord;
15+
private static Thread recordingThread;
16+
17+
private NitroWebrtcVadAudioInput() {}
18+
19+
static synchronized void start(int sampleRate, int frameSamples) {
20+
stop();
21+
22+
int minBufferSize = AudioRecord.getMinBufferSize(
23+
sampleRate,
24+
AudioFormat.CHANNEL_IN_MONO,
25+
AudioFormat.ENCODING_PCM_16BIT
26+
);
27+
if (minBufferSize <= 0) {
28+
throw new IllegalStateException("Failed to resolve Android AudioRecord buffer size.");
29+
}
30+
31+
int frameBytes = frameSamples * 2;
32+
int bufferSize = Math.max(minBufferSize, frameBytes * 4);
33+
34+
audioRecord = new AudioRecord(
35+
MediaRecorder.AudioSource.VOICE_RECOGNITION,
36+
sampleRate,
37+
AudioFormat.CHANNEL_IN_MONO,
38+
AudioFormat.ENCODING_PCM_16BIT,
39+
bufferSize
40+
);
41+
42+
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
43+
releaseAudioRecord();
44+
throw new IllegalStateException("Failed to initialize Android AudioRecord.");
45+
}
46+
47+
audioRecord.startRecording();
48+
isRecording.set(true);
49+
50+
recordingThread = new Thread(() -> readLoop(frameSamples), "NitroWebrtcVadAudioInput");
51+
recordingThread.start();
52+
}
53+
54+
static synchronized void stop() {
55+
isRecording.set(false);
56+
if (audioRecord != null) {
57+
try {
58+
audioRecord.stop();
59+
} catch (IllegalStateException ignored) {
60+
}
61+
}
62+
if (recordingThread != null) {
63+
try {
64+
recordingThread.join(300);
65+
} catch (InterruptedException ignored) {
66+
Thread.currentThread().interrupt();
67+
}
68+
recordingThread = null;
69+
}
70+
releaseAudioRecord();
71+
}
72+
73+
private static void readLoop(int frameSamples) {
74+
short[] frame = new short[frameSamples];
75+
76+
while (isRecording.get()) {
77+
int offset = 0;
78+
while (isRecording.get() && offset < frameSamples) {
79+
AudioRecord recorder = audioRecord;
80+
if (recorder == null) {
81+
return;
82+
}
83+
84+
int read = recorder.read(frame, offset, frameSamples - offset);
85+
if (read < 0) {
86+
Log.e(TAG, "AudioRecord read failed code=" + read);
87+
isRecording.set(false);
88+
return;
89+
}
90+
offset += read;
91+
}
92+
93+
if (isRecording.get() && offset == frameSamples) {
94+
try {
95+
onAudioFrame(frame);
96+
} catch (UnsatisfiedLinkError error) {
97+
Log.e(TAG, "Native audio frame callback is not linked.", error);
98+
isRecording.set(false);
99+
return;
100+
} catch (RuntimeException error) {
101+
Log.e(TAG, "Native audio frame callback failed.", error);
102+
isRecording.set(false);
103+
return;
104+
}
105+
}
106+
}
107+
}
108+
109+
private static void releaseAudioRecord() {
110+
if (audioRecord != null) {
111+
audioRecord.release();
112+
audioRecord = null;
113+
}
114+
}
115+
116+
private static native void onAudioFrame(short[] audioFrame);
117+
}

0 commit comments

Comments
 (0)