Skip to content

Commit a9dd86a

Browse files
committedJan 25, 2023
feat: rewrote android wav handling to support float encoding (AudioFormat.ENCODING_PCM_FLOAT)
On ios you can now pass any recorder setting in the "ios" options object BREAKING_CHANGE: some options specific to ios/android were moved into "ios" or "android" objects (See typings).
1 parent 719181a commit a9dd86a

23 files changed

+1709
-20
lines changed
 

Diff for: ‎packages/audio/platforms/android/include.gradle

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
dependencies {
2-
implementation 'com.github.squti:Android-Wave-Recorder:1.7.0'
2+
// implementation 'com.github.squti:Android-Wave-Recorder:1.7.0'
3+
// implementation 'com.kailashdabhi:om-recorder:1.1.5'
4+
35
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import java.io.File;
19+
import java.io.FileNotFoundException;
20+
import java.io.FileOutputStream;
21+
import java.io.IOException;
22+
import java.io.OutputStream;
23+
import java.util.concurrent.ExecutorService;
24+
import java.util.concurrent.Executors;
25+
26+
/**
27+
* @author Kailash Dabhi
28+
* @date 22-08-2016.
29+
* Copyright (c) 2017 Kingbull Technology. All rights reserved.
30+
*/
31+
public abstract class AbstractRecorder implements Recorder {
32+
protected final PullTransport pullTransport;
33+
protected final File file;
34+
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
35+
private OutputStream outputStream;
36+
private final Runnable recordingTask = new Runnable() {
37+
@Override public void run() {
38+
try {
39+
pullTransport.start(outputStream);
40+
} catch (IOException e) {
41+
throw new RuntimeException(e);
42+
} catch (IllegalStateException e) {
43+
throw new RuntimeException("AudioRecord state has uninitialized state", e);
44+
}
45+
}
46+
};
47+
48+
protected AbstractRecorder(PullTransport pullTransport, File file) {
49+
this.pullTransport = pullTransport;
50+
this.file = file;
51+
}
52+
53+
@Override public void startRecording() {
54+
outputStream = outputStream(file);
55+
executorService.submit(recordingTask);
56+
}
57+
58+
private OutputStream outputStream(File file) {
59+
if (file == null) {
60+
throw new RuntimeException("file is null !");
61+
}
62+
OutputStream outputStream;
63+
try {
64+
outputStream = new FileOutputStream(file);
65+
} catch (FileNotFoundException e) {
66+
throw new RuntimeException(
67+
"could not build OutputStream from" + " this file " + file.getName(), e);
68+
}
69+
return outputStream;
70+
}
71+
72+
@Override public void stopRecording() throws IOException {
73+
pullTransport.stop();
74+
outputStream.flush();
75+
outputStream.close();
76+
}
77+
78+
@Override public void pauseRecording() {
79+
pullTransport.pullableSource().isEnableToBePulled(false);
80+
}
81+
82+
@Override public void resumeRecording() {
83+
pullTransport.pullableSource().isEnableToBePulled(true);
84+
executorService.submit(recordingTask);
85+
}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import java.io.ByteArrayOutputStream;
19+
import java.io.DataOutputStream;
20+
import java.nio.ByteBuffer;
21+
import java.nio.ByteOrder;
22+
23+
/**
24+
* An AudioChunk is a audio data wrapper.
25+
*
26+
* @author Kailash Dabhi
27+
* @date 20-07-2016
28+
*/
29+
public interface AudioChunk {
30+
final static int BYTES_IN_FLOAT = Float.SIZE / Byte.SIZE;
31+
double maxAmplitude();
32+
33+
byte[] toBytes();
34+
35+
int readCount();
36+
37+
void readCount(int numberOfUnitThatWereRead);
38+
39+
final class Bytes implements AudioChunk {
40+
private static final double REFERENCE = 0.6;
41+
private final byte[] bytes;
42+
private int numberOfBytesRead;
43+
44+
Bytes(byte[] bytes) {
45+
this.bytes = bytes;
46+
}
47+
48+
public short[] toShorts() {
49+
short[] shorts = new short[bytes.length / 2];
50+
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
51+
return shorts;
52+
}
53+
54+
@Override public double maxAmplitude() {
55+
short[] shorts = toShorts();
56+
int nMaxAmp = 0;
57+
int arrLen = shorts.length;
58+
int peakIndex;
59+
for (peakIndex = 0; peakIndex < arrLen; peakIndex++) {
60+
if (shorts[peakIndex] >= nMaxAmp) {
61+
nMaxAmp = shorts[peakIndex];
62+
}
63+
}
64+
return (int) (20 * Math.log10(nMaxAmp / REFERENCE));
65+
}
66+
67+
@Override public byte[] toBytes() {
68+
return bytes;
69+
}
70+
71+
@Override public int readCount() {
72+
return numberOfBytesRead;
73+
}
74+
75+
@Override public void readCount(int numberOfUnitThatWereRead) {
76+
this.numberOfBytesRead = numberOfUnitThatWereRead;
77+
}
78+
}
79+
80+
final class Shorts implements AudioChunk {
81+
//number denotes the bytes read in @code buffer
82+
private static final short SILENCE_THRESHOLD = 2700;
83+
private static final double REFERENCE = 0.6;
84+
private final short[] shorts;
85+
private int numberOfShortsRead;
86+
87+
Shorts(short[] bytes) {
88+
this.shorts = bytes;
89+
}
90+
91+
int peakIndex() {
92+
int peakIndex;
93+
int arrLen = shorts.length;
94+
for (peakIndex = 0; peakIndex < arrLen; peakIndex++) {
95+
if ((shorts[peakIndex] >= SILENCE_THRESHOLD) || (shorts[peakIndex] <= -SILENCE_THRESHOLD)) {
96+
return peakIndex;
97+
}
98+
}
99+
return -1;
100+
}
101+
102+
@Override public double maxAmplitude() {
103+
int nMaxAmp = 0;
104+
int arrLen = shorts.length;
105+
int peakIndex;
106+
for (peakIndex = 0; peakIndex < arrLen; peakIndex++) {
107+
if (shorts[peakIndex] >= nMaxAmp) {
108+
nMaxAmp = shorts[peakIndex];
109+
}
110+
}
111+
return (int) (20 * Math.log10(nMaxAmp / REFERENCE));
112+
}
113+
114+
@Override public byte[] toBytes() {
115+
int shortIndex, byteIndex;
116+
byte[] buffer = new byte[numberOfShortsRead * 2];
117+
shortIndex = byteIndex = 0;
118+
for (; shortIndex != numberOfShortsRead; ) {
119+
buffer[byteIndex] = (byte) (shorts[shortIndex] & 0x00FF);
120+
buffer[byteIndex + 1] = (byte) ((shorts[shortIndex] & 0xFF00) >> 8);
121+
++shortIndex;
122+
byteIndex += 2;
123+
}
124+
return buffer;
125+
}
126+
127+
public short[] toShorts() {
128+
return shorts;
129+
}
130+
131+
@Override public int readCount() {
132+
return numberOfShortsRead;
133+
}
134+
135+
@Override public void readCount(int numberOfUnitThatWereRead) {
136+
this.numberOfShortsRead = numberOfUnitThatWereRead;
137+
}
138+
}
139+
140+
final class Floats implements AudioChunk {
141+
//number denotes the bytes read in @code buffer
142+
private static final short SILENCE_THRESHOLD = 2700;
143+
private static final double REFERENCE = 0.6;
144+
private final float[] floats;
145+
private int numberOfShortsRead;
146+
147+
Floats(float[] bytes) {
148+
this.floats = bytes;
149+
}
150+
151+
int peakIndex() {
152+
int peakIndex;
153+
int arrLen = floats.length;
154+
for (peakIndex = 0; peakIndex < arrLen; peakIndex++) {
155+
if ((floats[peakIndex] >= SILENCE_THRESHOLD) || (floats[peakIndex] <= -SILENCE_THRESHOLD)) {
156+
return peakIndex;
157+
}
158+
}
159+
return -1;
160+
}
161+
162+
@Override public double maxAmplitude() {
163+
float nMaxAmp = 0;
164+
int arrLen = floats.length;
165+
int peakIndex;
166+
for (peakIndex = 0; peakIndex < arrLen; peakIndex++) {
167+
if (floats[peakIndex] >= nMaxAmp) {
168+
nMaxAmp = floats[peakIndex];
169+
}
170+
}
171+
return (int) (20 * Math.log10(nMaxAmp / REFERENCE));
172+
}
173+
174+
@Override public byte[] toBytes() {
175+
int bufferSize = this.floats.length * BYTES_IN_FLOAT;
176+
ByteBuffer buffer = ByteBuffer.allocate(bufferSize).order(ByteOrder.LITTLE_ENDIAN);;
177+
buffer.asFloatBuffer().put(this.floats);
178+
byte[] result = buffer.array();
179+
return result;
180+
}
181+
182+
public float[] toFloats() {
183+
return floats;
184+
}
185+
186+
@Override public int readCount() {
187+
return numberOfShortsRead;
188+
}
189+
190+
@Override public void readCount(int numberOfUnitThatWereRead) {
191+
this.numberOfShortsRead = numberOfUnitThatWereRead;
192+
}
193+
}
194+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import android.media.AudioFormat;
19+
20+
/**
21+
* This is an interface to configure the {@link Source}
22+
*
23+
* @author Kailash Dabhi
24+
* @date 06-07-2016
25+
*/
26+
public interface AudioRecordConfig {
27+
int channelPositionMask();
28+
29+
int audioSource();
30+
31+
/**
32+
* @return sampleRateInHz
33+
*/
34+
int frequency();
35+
36+
int audioEncoding();
37+
38+
byte bitsPerSample();
39+
40+
/**
41+
* Application should use this default implementation of {@link AudioRecordConfig} to configure
42+
* the Audio Record Source.
43+
*/
44+
class Default implements AudioRecordConfig {
45+
private final int audioSource;
46+
private final int channelPositionMask;
47+
private final int frequency;
48+
private final int audioEncoding;
49+
50+
public Default(int audioSource, int audioEncoding, int channelPositionMask, int frequency) {
51+
this.audioSource = audioSource;
52+
this.audioEncoding = audioEncoding;
53+
this.channelPositionMask = channelPositionMask;
54+
this.frequency = frequency;
55+
}
56+
57+
@Override public int channelPositionMask() {
58+
return channelPositionMask;
59+
}
60+
61+
@Override public int audioSource() {
62+
return audioSource;
63+
}
64+
65+
@Override public int frequency() {
66+
return frequency;
67+
}
68+
69+
@Override public int audioEncoding() {
70+
return audioEncoding;
71+
}
72+
73+
@Override public byte bitsPerSample() {
74+
if (audioEncoding == AudioFormat.ENCODING_PCM_8BIT) {
75+
return 8;
76+
} else if (audioEncoding == AudioFormat.ENCODING_PCM_FLOAT || audioEncoding == AudioFormat.ENCODING_PCM_32BIT) {
77+
return 32;
78+
} else {
79+
return 16;
80+
}
81+
}
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.kailashdabhi.omrecorder;
2+
3+
import android.media.AudioRecord;
4+
5+
/**
6+
* @author Kailash Dabhi
7+
* @date 01 July, 2017 12:34 PM
8+
*/
9+
final class MinimumBufferSize {
10+
private final int minimumBufferSize;
11+
12+
MinimumBufferSize(AudioRecordConfig audioRecordConfig) {
13+
this.minimumBufferSize = 2 * AudioRecord.getMinBufferSize(audioRecordConfig.frequency(),
14+
audioRecordConfig.channelPositionMask(), audioRecordConfig.audioEncoding());
15+
}
16+
17+
int asInt() {
18+
return minimumBufferSize;
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import java.io.File;
19+
20+
/**
21+
* Essential APIs for working with OmRecorder.
22+
*
23+
* @author Kailash Dabhi
24+
* @date 31-07-2016
25+
*/
26+
public final class OmRecorder {
27+
private OmRecorder() {
28+
}
29+
30+
public static Recorder pcm(PullTransport pullTransport, File file) {
31+
return new Pcm(pullTransport, file);
32+
}
33+
34+
public static Recorder wav(PullTransport pullTransport, File file) {
35+
return new Wav(pullTransport, file);
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import java.io.File;
19+
20+
/**
21+
* {@code Pcm} is recorder for recording audio in wav format.
22+
*
23+
* @author Kailash Dabhi
24+
* @date 31-07-2016
25+
*/
26+
final class Pcm extends AbstractRecorder {
27+
public Pcm(PullTransport pullTransport, File file) {
28+
super(pullTransport, file);
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import android.media.AudioFormat;
19+
import android.media.AudioRecord;
20+
import android.os.Build;
21+
22+
import java.io.IOException;
23+
import java.io.OutputStream;
24+
25+
/**
26+
* This class represents a bus between {@link PullableSource} and {@link OutputStream}.
27+
* Basically it just pulls the data from {@link PullableSource} and transport it to
28+
* {@link OutputStream}
29+
*
30+
* @author Kailash Dabhi
31+
* @date 06-07-2016
32+
*/
33+
public interface PullTransport {
34+
/**
35+
* It starts to pull the {@link PullableSource} and transport it to
36+
* {@link OutputStream}
37+
*
38+
* @param outputStream the OutputStream where we want to transport the pulled audio data.
39+
* @throws IOException if there is any problem arise in pulling and transporting
40+
*/
41+
void start(OutputStream outputStream) throws IOException;
42+
43+
//It immediately stop pulling PullableSource
44+
void stop();
45+
46+
//Returns the pullableSource which is used for pulling
47+
PullableSource pullableSource();
48+
49+
/**
50+
* Interface definition for a callback to be invoked when a chunk of audio is pulled from
51+
* {@link PullableSource}.
52+
*/
53+
interface OnAudioChunkPulledListener {
54+
/**
55+
* Called when {@link PullableSource} is pulled and returned{@link AudioChunk}.
56+
*/
57+
void onAudioChunkPulled(AudioChunk audioChunk);
58+
}
59+
60+
abstract class AbstractPullTransport implements PullTransport {
61+
final PullableSource pullableSource;
62+
final OnAudioChunkPulledListener onAudioChunkPulledListener;
63+
private final UiThread uiThread = new UiThread();
64+
65+
AbstractPullTransport(PullableSource pullableSource,
66+
OnAudioChunkPulledListener onAudioChunkPulledListener) {
67+
this.pullableSource = pullableSource;
68+
this.onAudioChunkPulledListener = onAudioChunkPulledListener;
69+
}
70+
71+
@Override public void start(OutputStream outputStream) throws IOException {
72+
startPoolingAndWriting(pullableSource.preparedToBePulled(), pullableSource.pullSizeInBytes(),
73+
outputStream);
74+
}
75+
76+
void startPoolingAndWriting(AudioRecord audioRecord, int pullSizeInBytes,
77+
OutputStream outputStream) throws IOException {
78+
}
79+
80+
@Override public void stop() {
81+
pullableSource.isEnableToBePulled(false);
82+
pullableSource.audioRecord().stop();
83+
pullableSource.audioRecord().release();
84+
}
85+
86+
public PullableSource pullableSource() {
87+
return pullableSource;
88+
}
89+
90+
void postSilenceEvent(final Recorder.OnSilenceListener onSilenceListener,
91+
final long silenceTime) {
92+
uiThread.execute(new Runnable() {
93+
@Override public void run() {
94+
onSilenceListener.onSilence(silenceTime);
95+
}
96+
});
97+
}
98+
99+
void postPullEvent(final AudioChunk audioChunk) {
100+
uiThread.execute(new Runnable() {
101+
@Override public void run() {
102+
onAudioChunkPulledListener.onAudioChunkPulled(audioChunk);
103+
}
104+
});
105+
}
106+
}
107+
108+
final class Default extends AbstractPullTransport {
109+
private final WriteAction writeAction;
110+
111+
public Default(PullableSource pullableSource, WriteAction writeAction) {
112+
this(pullableSource, null, writeAction);
113+
}
114+
115+
public Default(PullableSource pullableSource,
116+
OnAudioChunkPulledListener onAudioChunkPulledListener, WriteAction writeAction) {
117+
super(pullableSource, onAudioChunkPulledListener);
118+
this.writeAction = writeAction;
119+
}
120+
121+
public Default(PullableSource pullableSource,
122+
OnAudioChunkPulledListener onAudioChunkPulledListener) {
123+
this(pullableSource, onAudioChunkPulledListener, new WriteAction.Default());
124+
}
125+
126+
public Default(PullableSource audioRecordSource) {
127+
this(audioRecordSource, null, new WriteAction.Default());
128+
}
129+
130+
@Override void startPoolingAndWriting(AudioRecord audioRecord, int pullSizeInBytes,
131+
OutputStream outputStream) throws IOException {
132+
int audioFormat = audioRecord.getAudioFormat();
133+
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
134+
AudioChunk.Floats audioChunk = new AudioChunk.Floats(new float[pullSizeInBytes]);
135+
while (pullableSource.isEnableToBePulled()) {
136+
137+
audioChunk.readCount(audioRecord.read(audioChunk.toFloats(), 0, pullSizeInBytes, AudioRecord.READ_BLOCKING));
138+
int readCount = audioChunk.readCount();
139+
if (AudioRecord.ERROR_INVALID_OPERATION != readCount
140+
&& AudioRecord.ERROR_BAD_VALUE != readCount) {
141+
if (onAudioChunkPulledListener != null) {
142+
postPullEvent(audioChunk);
143+
}
144+
writeAction.execute(audioChunk, outputStream);
145+
}
146+
}
147+
} else {
148+
AudioChunk audioChunk = new AudioChunk.Bytes(new byte[pullSizeInBytes]);
149+
while (pullableSource.isEnableToBePulled()) {
150+
audioChunk.readCount(audioRecord.read(audioChunk.toBytes(), 0, pullSizeInBytes));
151+
int readCount = audioChunk.readCount();
152+
if (AudioRecord.ERROR_INVALID_OPERATION != readCount
153+
&& AudioRecord.ERROR_BAD_VALUE != readCount) {
154+
if (onAudioChunkPulledListener != null) {
155+
postPullEvent(audioChunk);
156+
}
157+
writeAction.execute(audioChunk, outputStream);
158+
}
159+
}
160+
}
161+
162+
}
163+
}
164+
165+
final class Noise extends AbstractPullTransport {
166+
private final long silenceTimeThreshold;
167+
private final Recorder.OnSilenceListener silenceListener;
168+
private final WriteAction writeAction;
169+
private long firstSilenceMoment = 0;
170+
private int noiseRecordedAfterFirstSilenceThreshold = 0;
171+
172+
public Noise(PullableSource pullableSource,
173+
OnAudioChunkPulledListener onAudioChunkPulledListener,
174+
Recorder.OnSilenceListener silenceListener, long silenceTimeThreshold) {
175+
this(pullableSource, onAudioChunkPulledListener, new WriteAction.Default(),
176+
silenceListener, silenceTimeThreshold);
177+
}
178+
179+
public Noise(PullableSource pullableSource,
180+
OnAudioChunkPulledListener onAudioChunkPulledListener, WriteAction writeAction,
181+
Recorder.OnSilenceListener silenceListener, long silenceTimeThreshold) {
182+
super(pullableSource, onAudioChunkPulledListener);
183+
this.writeAction = writeAction;
184+
this.silenceListener = silenceListener;
185+
this.silenceTimeThreshold = silenceTimeThreshold;
186+
}
187+
188+
public Noise(PullableSource pullableSource, WriteAction writeAction,
189+
Recorder.OnSilenceListener silenceListener, long silenceTimeThreshold) {
190+
this(pullableSource, null, writeAction, silenceListener, silenceTimeThreshold);
191+
}
192+
193+
public Noise(PullableSource pullableSource, Recorder.OnSilenceListener silenceListener,
194+
long silenceTimeThreshold) {
195+
this(pullableSource, null, new WriteAction.Default(), silenceListener,
196+
silenceTimeThreshold);
197+
}
198+
199+
public Noise(PullableSource pullableSource, Recorder.OnSilenceListener silenceListener) {
200+
this(pullableSource, null, new WriteAction.Default(), silenceListener, 200);
201+
}
202+
203+
public Noise(PullableSource pullableSource) {
204+
this(pullableSource, null, new WriteAction.Default(), null, 200);
205+
}
206+
207+
@Override void startPoolingAndWriting(AudioRecord audioRecord, int pullSizeInBytes,
208+
OutputStream outputStream) throws IOException {
209+
final AudioChunk.Shorts audioChunk = new AudioChunk.Shorts(new short[pullSizeInBytes]);
210+
while (pullableSource.isEnableToBePulled()) {
211+
short[] shorts = audioChunk.toShorts();
212+
audioChunk.readCount(audioRecord.read(shorts, 0, shorts.length));
213+
if (AudioRecord.ERROR_INVALID_OPERATION != audioChunk.readCount()
214+
&& AudioRecord.ERROR_BAD_VALUE != audioChunk.readCount()) {
215+
if (onAudioChunkPulledListener != null) {
216+
postPullEvent(audioChunk);
217+
}
218+
if (audioChunk.peakIndex() > -1) {
219+
writeAction.execute(audioChunk, outputStream);
220+
firstSilenceMoment = 0;
221+
noiseRecordedAfterFirstSilenceThreshold++;
222+
} else {
223+
if (firstSilenceMoment == 0) {
224+
firstSilenceMoment = System.currentTimeMillis();
225+
}
226+
final long silenceTime = System.currentTimeMillis() - firstSilenceMoment;
227+
if (firstSilenceMoment != 0 && silenceTime > this.silenceTimeThreshold) {
228+
if (silenceTime > 1000) {
229+
if (noiseRecordedAfterFirstSilenceThreshold >= 3) {
230+
noiseRecordedAfterFirstSilenceThreshold = 0;
231+
if (silenceListener != null) {
232+
postSilenceEvent(silenceListener, silenceTime);
233+
}
234+
}
235+
}
236+
} else {
237+
writeAction.execute(audioChunk, outputStream);
238+
}
239+
}
240+
}
241+
}
242+
}
243+
}
244+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package com.kailashdabhi.omrecorder;
2+
3+
import android.media.AudioRecord;
4+
import android.os.Build;
5+
import android.util.Log;
6+
7+
/**
8+
* An {@code PullableSource} represents {@link Source} which is Pullable
9+
*
10+
* @author Kailash Dabhi
11+
* @date 01-07-2017
12+
*/
13+
public interface PullableSource extends Source {
14+
/**
15+
* @return number of bytes to be read from @{@link Source}
16+
*/
17+
int pullSizeInBytes();
18+
19+
void isEnableToBePulled(boolean enabledToBePulled);
20+
21+
boolean isEnableToBePulled();
22+
23+
AudioRecord preparedToBePulled();
24+
25+
class Base implements PullableSource {
26+
private final PullableSource pullableSource;
27+
28+
Base(PullableSource pullableSource) {
29+
this.pullableSource = pullableSource;
30+
}
31+
32+
@Override
33+
public AudioRecord audioRecord() {
34+
return pullableSource.audioRecord();
35+
}
36+
37+
@Override
38+
public AudioRecordConfig config() {
39+
return pullableSource.config();
40+
}
41+
42+
@Override
43+
public int minimumBufferSize() {
44+
return pullableSource.minimumBufferSize();
45+
}
46+
47+
@Override
48+
public int pullSizeInBytes() {
49+
return pullableSource.pullSizeInBytes();
50+
}
51+
52+
@Override
53+
public void isEnableToBePulled(boolean enabledToBePulled) {
54+
pullableSource.isEnableToBePulled(enabledToBePulled);
55+
}
56+
57+
@Override
58+
public boolean isEnableToBePulled() {
59+
return pullableSource.isEnableToBePulled();
60+
}
61+
62+
@Override
63+
public AudioRecord preparedToBePulled() {
64+
return pullableSource.preparedToBePulled();
65+
}
66+
}
67+
68+
class Default extends Source.Default implements PullableSource {
69+
private final int pullSizeInBytes;
70+
private volatile boolean pull;
71+
72+
public Default(AudioRecordConfig config, int pullSizeInBytes) {
73+
super(config);
74+
this.pullSizeInBytes = pullSizeInBytes;
75+
}
76+
77+
public Default(AudioRecordConfig config) {
78+
super(config);
79+
this.pullSizeInBytes = minimumBufferSize();
80+
}
81+
82+
@Override
83+
public int pullSizeInBytes() {
84+
return pullSizeInBytes;
85+
}
86+
87+
@Override
88+
public void isEnableToBePulled(boolean enabledToBePulled) {
89+
this.pull = enabledToBePulled;
90+
}
91+
92+
@Override
93+
public boolean isEnableToBePulled() {
94+
return pull;
95+
}
96+
97+
@Override
98+
public AudioRecord preparedToBePulled() {
99+
final AudioRecord audioRecord = audioRecord();
100+
audioRecord.startRecording();
101+
isEnableToBePulled(true);
102+
return audioRecord;
103+
}
104+
}
105+
106+
class NoiseSuppressor extends Base {
107+
public NoiseSuppressor(PullableSource pullableSource) {
108+
super(pullableSource);
109+
}
110+
111+
@Override
112+
public AudioRecord preparedToBePulled() {
113+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
114+
if (android.media.audiofx.NoiseSuppressor.isAvailable()) {
115+
android.media.audiofx.NoiseSuppressor noiseSuppressor = android.media.audiofx.NoiseSuppressor
116+
.create(audioRecord().getAudioSessionId());
117+
if (noiseSuppressor != null) {
118+
noiseSuppressor.setEnabled(true);
119+
Log.i(getClass().getSimpleName(), "NoiseSuppressor ON");
120+
} else {
121+
Log.i(getClass().getSimpleName(), "NoiseSuppressor failed :(");
122+
}
123+
} else {
124+
Log.i(getClass().getSimpleName(), "This device don't support NoiseSuppressor");
125+
}
126+
} else {
127+
Log.i(getClass().getSimpleName(),
128+
"For this effect, Android api should be higher than or equals 16");
129+
}
130+
return super.preparedToBePulled();
131+
}
132+
}
133+
134+
class AutomaticGainControl extends Base {
135+
public AutomaticGainControl(PullableSource pullableSource) {
136+
super(pullableSource);
137+
}
138+
139+
@Override
140+
public AudioRecord preparedToBePulled() {
141+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
142+
if (android.media.audiofx.AutomaticGainControl.isAvailable()) {
143+
android.media.audiofx.AutomaticGainControl automaticGainControl = android.media.audiofx.AutomaticGainControl
144+
.create(audioRecord().getAudioSessionId());
145+
if (automaticGainControl != null) {
146+
automaticGainControl.setEnabled(true);
147+
Log.i(getClass().getSimpleName(), "AutomaticGainControl ON");
148+
} else {
149+
Log.i(getClass().getSimpleName(), "AutomaticGainControl failed :(");
150+
}
151+
} else {
152+
Log.i(getClass().getSimpleName(), "This device don't support AutomaticGainControl");
153+
}
154+
} else {
155+
Log.i(getClass().getSimpleName(),
156+
"For this effect, Android api should be higher than or equals 16");
157+
}
158+
return super.preparedToBePulled();
159+
}
160+
}
161+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import java.io.IOException;
19+
20+
/**
21+
* A Recorder who can start and stop recording with startRecording() and stopRecording() method
22+
* respectively.
23+
*
24+
* @author Kailash Dabhi
25+
* @date 06-07-2016
26+
*/
27+
public interface Recorder {
28+
void startRecording();
29+
30+
void stopRecording() throws IOException;
31+
32+
void pauseRecording();
33+
34+
void resumeRecording();
35+
36+
/**
37+
* Interface definition for a callback to be invoked when a silence is measured.
38+
*/
39+
interface OnSilenceListener {
40+
/**
41+
* Called when a silence measured
42+
*
43+
* @param silenceTime The silence measured
44+
*/
45+
void onSilence(long silenceTime);
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.kailashdabhi.omrecorder;
2+
3+
import android.annotation.SuppressLint;
4+
import android.media.AudioRecord;
5+
6+
/**
7+
* A {@code Source} represents Audio Record Source.
8+
*
9+
* @author Kailash Dabhi
10+
* @date 01 July, 2017 12:52 AM
11+
*/
12+
interface Source {
13+
AudioRecord audioRecord();
14+
15+
AudioRecordConfig config();
16+
17+
int minimumBufferSize();
18+
19+
class Default implements Source {
20+
private final AudioRecord audioRecord;
21+
private final AudioRecordConfig config;
22+
private final int minimumBufferSize;
23+
24+
@SuppressLint("MissingPermission")
25+
Default(AudioRecordConfig config) {
26+
this.config = config;
27+
this.minimumBufferSize = new MinimumBufferSize(config).asInt();
28+
this.audioRecord =
29+
new AudioRecord(config.audioSource(), config.frequency(), config.channelPositionMask(),
30+
config.audioEncoding(), minimumBufferSize);
31+
}
32+
33+
@Override public AudioRecord audioRecord() {
34+
return audioRecord;
35+
}
36+
37+
@Override public AudioRecordConfig config() {
38+
return config;
39+
}
40+
41+
@Override public int minimumBufferSize() {
42+
return minimumBufferSize;
43+
}
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
/**
19+
* A {@code ThreadAction} is an action which going to be executed on the implementer thread.
20+
*
21+
* @author Kailash Dabhi
22+
* @date 25-07-2016
23+
*/
24+
interface ThreadAction {
25+
/**
26+
* Execute {@code runnable} action on implementer {@code Thread}
27+
*/
28+
void execute(Runnable action);
29+
}
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import android.os.Handler;
19+
import android.os.Looper;
20+
21+
/**
22+
* A {@code UiThread} is representation of Ui / main thread.
23+
*
24+
* @author Kailash Dabhi
25+
* @date 25-07-2016
26+
*/
27+
final class UiThread implements ThreadAction {
28+
private static final Handler handler = new Handler(Looper.getMainLooper());
29+
30+
/** executes the {@code Runnable} on UI Thread. */
31+
@Override public void execute(Runnable runnable) {
32+
handler.post(runnable);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import java.io.File;
19+
import java.io.FileNotFoundException;
20+
import java.io.IOException;
21+
import java.io.RandomAccessFile;
22+
23+
/**
24+
* {@code Wav} is recorder for recording audio in wav format.
25+
*
26+
* @author Kailash Dabhi
27+
* @date 31-07-2016
28+
*/
29+
final class Wav extends AbstractRecorder {
30+
public Wav(PullTransport pullTransport, File file) {
31+
super(pullTransport, file);
32+
}
33+
34+
@Override public void stopRecording() {
35+
try {
36+
super.stopRecording();
37+
writeWavHeader();
38+
} catch (IOException e) {
39+
throw new RuntimeException("Error in applying wav header", e);
40+
}
41+
}
42+
43+
private void writeWavHeader() throws IOException {
44+
final RandomAccessFile wavFile = randomAccessFile(file);
45+
wavFile.seek(0); // to the beginning
46+
wavFile.write(new WavHeader(pullTransport.pullableSource(), file.length()).toBytes());
47+
wavFile.close();
48+
}
49+
50+
private RandomAccessFile randomAccessFile(File file) {
51+
RandomAccessFile randomAccessFile;
52+
try {
53+
randomAccessFile = new RandomAccessFile(file, "rw");
54+
} catch (FileNotFoundException e) {
55+
throw new RuntimeException(e);
56+
}
57+
return randomAccessFile;
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import android.media.AudioFormat;
19+
20+
/**
21+
* A Header to be appended to the end of Wav audio file
22+
*
23+
* @author Kailash Dabhi
24+
* @date 26-07-2016
25+
*/
26+
final class WavHeader {
27+
private final PullableSource pullableSource;
28+
private final long totalAudioLength;
29+
30+
WavHeader(PullableSource pullableSource, long totalAudioLength) {
31+
this.pullableSource = pullableSource;
32+
this.totalAudioLength = totalAudioLength;
33+
}
34+
35+
/** Returns the {@code WavHeader} in bytes. */
36+
public byte[] toBytes() {
37+
long sampleRateInHz = pullableSource.config().frequency();
38+
int channels =
39+
(pullableSource.config().channelPositionMask() == AudioFormat.CHANNEL_IN_MONO ? 1 : 2);
40+
byte bitsPerSample = pullableSource.config().bitsPerSample();
41+
return wavFileHeader(totalAudioLength - 44, totalAudioLength - 44 + 36, sampleRateInHz,
42+
channels, bitsPerSample * sampleRateInHz * channels / 8, bitsPerSample);
43+
}
44+
45+
private byte[] wavFileHeader(long totalAudioLen, long totalDataLen, long longSampleRate,
46+
int channels, long byteRate, byte bitsPerSample) {
47+
byte[] header = new byte[44];
48+
header[0] = 'R'; // RIFF/WAVE header
49+
header[1] = 'I';
50+
header[2] = 'F';
51+
header[3] = 'F';
52+
header[4] = (byte) (totalDataLen & 0xff);
53+
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
54+
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
55+
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
56+
header[8] = 'W';
57+
header[9] = 'A';
58+
header[10] = 'V';
59+
header[11] = 'E';
60+
header[12] = 'f'; // 'fmt ' chunk
61+
header[13] = 'm';
62+
header[14] = 't';
63+
header[15] = ' ';
64+
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
65+
header[17] = 0;
66+
header[18] = 0;
67+
header[19] = 0;
68+
header[20] = (byte) (bitsPerSample == 32 ? 3 : 1); // format = 1
69+
// header[20] = 1; // format = 1
70+
header[21] = 0;
71+
header[22] = (byte) channels;
72+
header[23] = 0;
73+
header[24] = (byte) (longSampleRate & 0xff);
74+
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
75+
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
76+
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
77+
header[28] = (byte) (byteRate & 0xff);
78+
header[29] = (byte) ((byteRate >> 8) & 0xff);
79+
header[30] = (byte) ((byteRate >> 16) & 0xff);
80+
header[31] = (byte) ((byteRate >> 24) & 0xff);
81+
header[32] = (byte) (channels * (bitsPerSample / 8)); //
82+
// block align
83+
header[33] = 0;
84+
header[34] = bitsPerSample; // bits per sample
85+
header[35] = 0;
86+
header[36] = 'd';
87+
header[37] = 'a';
88+
header[38] = 't';
89+
header[39] = 'a';
90+
header[40] = (byte) (totalAudioLen & 0xff);
91+
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
92+
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
93+
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
94+
return header;
95+
}
96+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright 2017 Kailash Dabhi (Kingbull Technology)
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+
package com.kailashdabhi.omrecorder;
17+
18+
import java.io.IOException;
19+
import java.io.OutputStream;
20+
21+
/**
22+
* An Implementer class should use this interface to write encoded
23+
* audio chunk to OutputStream according to chosen audio format.
24+
*
25+
* @author Kailash Dabhi
26+
* @date 06-07-2016
27+
*/
28+
public interface WriteAction {
29+
/**
30+
* Implement this behaviour to provide custom Write Action for audio which
31+
* requires {@code data} to encode. So here One can encode the data
32+
* according to chosen audio format.
33+
*/
34+
void execute(AudioChunk audioChunk, OutputStream outputStream) throws IOException;
35+
36+
/**
37+
* Use this default implementation to write data directly without any encoding to OutputStream.
38+
*/
39+
final class Default implements WriteAction {
40+
@Override public void execute(AudioChunk audioChunk, OutputStream outputStream)
41+
throws IOException {
42+
outputStream.write(audioChunk.toBytes());
43+
}
44+
}
45+
}
46+

Diff for: ‎src/audio/android/recorder.ts

+23-13
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { check, request } from '@nativescript-community/perms';
2-
import { Application } from '@nativescript/core';
3-
import { ANDROID_ENCODER_PCM, ANDROID_ENCODER_PCM_16, AudioRecorderOptions } from '..';
2+
import { Application, Utils } from '@nativescript/core';
3+
import { ANDROID_ENCODER_PCM, AudioRecorderAndroidOptions, AudioRecorderOptions } from '..';
44

55
export class TNSRecorder {
6-
private _recorder: any;
7-
private _wavrecorder: any;
6+
// private _recorder: android.media.MediaRecorder;
7+
// private _wavrecorder: com.github.squti.androidwaverecorder.WaveRecorder;
88

9+
private _recorder: android.media.MediaRecorder;
10+
private _wavrecorder: com.kailashdabhi.omrecorder.Recorder;
911

1012
public static CAN_RECORD(): boolean {
11-
const pManager = Application.android.context.getPackageManager();
13+
const pManager = Utils.android.getApplicationContext().getPackageManager();
1214
const canRecord = pManager.hasSystemFeature(android.content.pm.PackageManager.FEATURE_MICROPHONE);
1315
if (canRecord) {
1416
return true;
@@ -29,15 +31,27 @@ export class TNSRecorder {
2931
public async start(options: AudioRecorderOptions) {
3032
// bake the permission into this so the dev doesn't have to call it
3133
await this.requestRecordPermission();
32-
34+
const androidOptions: AudioRecorderAndroidOptions = options.android || {};
3335
const audioSource = options.source ? options.source : 0;
3436
if (this._recorder) {
3537
// reset for reuse
3638
this._recorder.reset();
3739
} else {
38-
if (options.encoder === ANDROID_ENCODER_PCM || options.encoder === ANDROID_ENCODER_PCM_16) {
40+
if (androidOptions.wavAaudioFormat !== undefined) {
3941
//@ts-ignore
40-
this._wavrecorder = new com.github.squti.androidwaverecorder.WaveRecorder(options.filename);
42+
this._wavrecorder = new com.kailashdabhi.omrecorder.OmRecorder.wav(
43+
new com.kailashdabhi.omrecorder.PullTransport.Default(
44+
new com.kailashdabhi.omrecorder.PullableSource.Default(
45+
new com.kailashdabhi.omrecorder.AudioRecordConfig.Default(
46+
androidOptions.audioSource !== undefined ? androidOptions.audioSource : android.media.MediaRecorder.AudioSource.MIC,
47+
androidOptions.wavAaudioFormat,
48+
options.channels === 1 ? android.media.AudioFormat.CHANNEL_IN_MONO : android.media.AudioFormat.CHANNEL_IN_STEREO,
49+
options.sampleRate || 16000
50+
)
51+
)
52+
),
53+
new java.io.File(options.filename)
54+
);
4155
} else {
4256
this._recorder = new android.media.MediaRecorder();
4357
}
@@ -47,7 +61,7 @@ export class TNSRecorder {
4761
const outFormat = options.format ? options.format : 0;
4862
this._recorder.setOutputFormat(outFormat);
4963

50-
const encoder = options.encoder ? options.encoder : 0;
64+
const encoder = androidOptions.encoder ? androidOptions.encoder : 0;
5165
this._recorder.setAudioEncoder(encoder);
5266

5367
if (options.channels) {
@@ -85,12 +99,8 @@ export class TNSRecorder {
8599
this._recorder.prepare();
86100
this._recorder.start();
87101
} else if (this._wavrecorder) {
88-
this._wavrecorder.waveConfig.sampleRate = options.sampleRate || 16000;
89-
this._wavrecorder.waveConfig.channels = options.channels === 1 ? android.media.AudioFormat.CHANNEL_IN_MONO : android.media.AudioFormat.CHANNEL_IN_STEREO;
90-
this._wavrecorder.waveConfig.audioEncoding = options.encoder === ANDROID_ENCODER_PCM_16 ? android.media.AudioFormat.ENCODING_PCM_16BIT : android.media.AudioFormat.ENCODING_PCM_8BIT;
91102
this._wavrecorder.startRecording();
92103
}
93-
94104
}
95105

96106
public getMeters(): number {

Diff for: ‎src/audio/common.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Utils, knownFolders, path as nsFilePath } from '@nativescript/core';
22

33
export const ANDROID_ENCODER_PCM = 161234;
4-
export const ANDROID_ENCODER_PCM_16 = 1612341;
54

65
export enum AudioPlayerEvents {
76
seek = 'seek',

Diff for: ‎src/audio/index.android.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './android/player';
22
export * from './android/recorder';
3+
export { AudioPlayerEvents, ANDROID_ENCODER_PCM } from './common';

Diff for: ‎src/audio/index.d.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Observable } from '@nativescript/core';
22

3-
export { AudioPlayerEvents, ANDROID_ENCODER_PCM, ANDROID_ENCODER_PCM_16 } from './common';
3+
export { AudioPlayerEvents, ANDROID_ENCODER_PCM } from './common';
44
export interface AudioPlayerOptions {
55
/**
66
* The audio file to play.
@@ -66,6 +66,11 @@ export interface AudioPlayerOptions {
6666
sessionRouteSharingPolicy?: AVAudioSessionRouteSharingPolicy;
6767
}
6868

69+
export interface AudioRecorderAndroidOptions {
70+
wavAaudioFormat?: any; // Android only select AudioFormat for wav recording
71+
audioSource?: any; // Android only select AudioFormat for wav recording
72+
encoder?: any;
73+
}
6974
export interface AudioRecorderOptions {
7075
/**
7176
* The name of the file recorded.
@@ -95,9 +100,12 @@ export interface AudioRecorderOptions {
95100
channels?: any;
96101
sampleRate?: any;
97102
bitRate?: any;
98-
encoder?: any;
99103

100-
quality?: number; // iOS quality AVEncoderAudioQualityKey
104+
android?: AudioRecorderAndroidOptions;
105+
106+
ios?: {
107+
[k: string]: any;
108+
};
101109

102110
/**
103111
* Callback to execute when playback has an error.

Diff for: ‎src/audio/index.ios.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './ios/player';
22
export * from './ios/recorder';
3+
export { AudioPlayerEvents, ANDROID_ENCODER_PCM } from './common';

Diff for: ‎src/audio/ios/recorder.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ class TNSRecorderDelegate extends NSObject implements AVAudioRecorderDelegate {
3333

3434
export { TNSRecorderDelegate };
3535

36+
function isInt(n) {
37+
return n % 1 === 0;
38+
}
39+
function isBoolean(n) {
40+
return typeof n === 'boolean';
41+
}
42+
3643
export class TNSRecorder extends Observable {
3744
private _recorder: AVAudioRecorder;
3845
private _recordingSession: AVAudioSession;
@@ -89,10 +96,26 @@ export class TNSRecorder extends Observable {
8996
// NSNumber.numberWithInt((<any>AVAudioQuality).Medium.rawValue),
9097
// 'AVEncoderAudioQualityKey'
9198
// );
92-
recordSetting.setValueForKey(NSNumber.numberWithInt(options.quality || AVAudioQuality.Medium), 'AVEncoderAudioQualityKey');
99+
// recordSetting.setValueForKey(NSNumber.numberWithInt(options.quality || AVAudioQuality.Medium), 'AVEncoderAudioQualityKey');
93100
recordSetting.setValueForKey(NSNumber.numberWithFloat(options.sampleRate || 16000), 'AVSampleRateKey');
94101
recordSetting.setValueForKey(NSNumber.numberWithInt(options.channels || 1), 'AVNumberOfChannelsKey');
95-
102+
if (options.ios) {
103+
Object.keys(options.ios).forEach((k) => {
104+
const value = options.ios[k];
105+
if (isBoolean(value)) {
106+
recordSetting.setValueForKey(NSNumber.numberWithBool(value), k);
107+
}
108+
if (typeof value === 'number') {
109+
if (isInt(value)) {
110+
recordSetting.setValueForKey(NSNumber.numberWithFloat(value), k);
111+
} else {
112+
recordSetting.setValueForKey(NSNumber.numberWithInt(value), k);
113+
}
114+
} else {
115+
recordSetting.setValueForKey(value, k);
116+
}
117+
});
118+
}
96119
const url = NSURL.fileURLWithPath(options.filename);
97120

98121
if (!this._recorder) {

Diff for: ‎src/audio/typings/android.d.ts

+433
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.