├── libs
├── x86
│ └── libiLBC_codec.so
├── mips
│ └── libiLBC_codec.so
├── armeabi
│ └── libiLBC_codec.so
└── armeabi-v7a
│ └── libiLBC_codec.so
├── jni
├── Applicaiton.mk
├── Android.mk
└── iLBC_codec.c
├── AndroidManifest.xml
├── README.md
└── src
└── com
└── tuenti
└── androidilbc
├── Codec.java
└── VoiceInput.java
/libs/x86/libiLBC_codec.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukeweber/iLBC-Android/HEAD/libs/x86/libiLBC_codec.so
--------------------------------------------------------------------------------
/libs/mips/libiLBC_codec.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukeweber/iLBC-Android/HEAD/libs/mips/libiLBC_codec.so
--------------------------------------------------------------------------------
/jni/Applicaiton.mk:
--------------------------------------------------------------------------------
1 | APP_STL := gnustl_static
2 | APP_ABI := all
3 | APP_OPTIM := release
4 | APP_CFLAGS := -Werror
5 |
--------------------------------------------------------------------------------
/libs/armeabi/libiLBC_codec.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukeweber/iLBC-Android/HEAD/libs/armeabi/libiLBC_codec.so
--------------------------------------------------------------------------------
/libs/armeabi-v7a/libiLBC_codec.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukeweber/iLBC-Android/HEAD/libs/armeabi-v7a/libiLBC_codec.so
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | iLBC Android
2 | ==========
3 |
4 | [Internet low bitrate codec](http://en.wikipedia.org/wiki/Internet_Low_Bit_Rate_Codec). Wrapper and shared objects to use ilbc in android.
5 |
6 | ilbc quirks
7 | * Call resetEncoder/Decoder between encoding or decoding entirely new audio clicks, not in between encoding chunks of a stream, if you get this wrong you'll hear clicking. Read next point for technical reason.
8 | * Encoded audio from ilbc starts with a block of zeros. See http://www.ietf.org/rfc/rfc3951.txt, Specifically: "The input to the LPC analysis module is a possibly high-pass filtered speech buffer, speech_hp, that contains 240/300 (LPC_LOOKBACK + BLOCKL = 80/60 + 160/240 = 240/300) speech samples, where samples 0 through 79/59 are from the previous block and samples 80/60 through 239/299 are from the current block. No look-ahead into the next block is used. For the very first block processed, the look-back samples are assumed to be zeros."
9 |
10 | Changes
11 | * Built based on webrtc trunk
12 | * Adds option to use noise supression from webrtc in the encode method
13 | * Uses 30ms mode only
14 |
15 | Building
16 | * svn co http://webrtc.googlecode.com/svn/trunk jni/webrtc
17 | * Download the android NDK
18 | * ndk-build NDK_APPLICATION_MK=jni/Applicaiton.mk
19 |
--------------------------------------------------------------------------------
/jni/Android.mk:
--------------------------------------------------------------------------------
1 | #ARCH_ARM_HAVE_ARMV7A := true
2 | #TARGET_ARCH := arm
3 |
4 | # Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
5 | #
6 | # Use of this source code is governed by a BSD-style license
7 | # that can be found in the LICENSE file in the root of the source
8 | # tree. An additional intellectual property rights grant can be found
9 | # in the file PATENTS. All contributing project authors may
10 | # be found in the AUTHORS file in the root of the source tree.
11 |
12 | ILBC_WRAPPER_MAIN_PATH := $(call my-dir)
13 |
14 | include $(ILBC_WRAPPER_MAIN_PATH)/webrtc/src/common_audio/signal_processing/Android.mk
15 | include $(ILBC_WRAPPER_MAIN_PATH)/webrtc/src/modules/audio_coding/codecs/ilbc/Android.mk
16 | include $(ILBC_WRAPPER_MAIN_PATH)/webrtc/src/modules/audio_processing/ns/Android.mk
17 |
18 | LOCAL_PATH := $(ILBC_WRAPPER_MAIN_PATH)
19 | include $(CLEAR_VARS)
20 |
21 |
22 | #LOCAL_ARM_MODE := arm
23 | LOCAL_MODULE_TAGS := optional
24 |
25 | LOCAL_SRC_FILES :=\
26 | iLBC_codec.c
27 |
28 | LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
29 |
30 | LOCAL_C_INCLUDES := \
31 | $(LOCAL_PATH)/webrtc/src/modules/audio_coding/codecs/ilbc/interface \
32 | $(LOCAL_PATH)/webrtc/src/modules/audio_coding/codecs/ilbc/ \
33 | $(LOCAL_PATH)/webrtc/src/common_audio/signal_processing/include \
34 | $(LOCAL_PATH)/webrtc/src/modules/audio_processing/ns/include/ \
35 | $(LOCAL_PATH)/webrtc/src/
36 |
37 |
38 | LOCAL_WHOLE_STATIC_LIBRARIES := \
39 | libwebrtc_ns \
40 | libwebrtc_spl \
41 | libwebrtc_ilbc
42 |
43 | LOCAL_MODULE := iLBC_codec
44 |
45 | include $(BUILD_SHARED_LIBRARY)
46 |
--------------------------------------------------------------------------------
/src/com/tuenti/androidilbc/Codec.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 Kyan He
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.tuenti.androidilbc;
17 | import android.util.Log;
18 |
19 | public class Codec {
20 | static final private String TAG = "Codec";
21 |
22 | static final private Codec INSTANCE = new Codec();
23 |
24 | /**
25 | * Encodes PCM 16-bit, 8000hz audio at 30ms iLBC mode.
26 | *
27 | * @param byte[] rawAudio - PCM 16bit 8000hz Audio, ideally in chunks
28 | * divisible by 480 bytes(240 16 bit samples). Other size will result in
29 | * audio degredation.
30 | * @param int offset - Offset in rawAudio
31 | * @param int length - Length of bytes to read from rawAudio
32 | * @param byte[] encodedAudio - iLBC encoded audio, 30ms mode.
33 | * @param int nsMode - Noise Supression Level - 0:Off, 1: Mild, 2: Medium , 3: Aggressive
34 | * @return int - bytes encoded length.
35 | */
36 | public native int encode(byte[] rawAudio, int offset, int length,
37 | byte[] encodedAudio, int noiseSupressionMode);
38 |
39 | /**
40 | * Frees and resets the encoder instance
41 | *
42 | * After encoding an audio clip this should be called.
43 | */
44 | public native int resetEncoder();
45 |
46 | /**
47 | * Decodes iLBC 30ms mode audio to PCM 16 bit 8000hz audio.
48 | *
49 | * @param byte[] encodedAudio - iLBC 30ms mode encoded audio.
50 | * @param int offset - Offset in encodedAudio
51 | * @param int length - Length of bytes to read from encodedAudio.
52 | * @param byte[] rawAudio - iLBC encoded audio, 30ms mode.
53 | * @return int - bytes decoded length.
54 | */
55 | public native int decode(byte[] encodedAudio, int offset,
56 | int length, byte[] rawAudio);
57 |
58 | /**
59 | * Frees and resets the decoder instance
60 | *
61 | * After decoding an audio clip this should be called.
62 | */
63 | public native int resetDecoder();
64 |
65 | private Codec() {
66 | System.loadLibrary("iLBC_codec");
67 | }
68 |
69 | static public Codec instance() {
70 | return INSTANCE;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/jni/iLBC_codec.c:
--------------------------------------------------------------------------------
1 | #include "jni.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "ilbc.h"
7 | #include "noise_suppression_x.h"
8 | #include "defines.h"
9 |
10 | #define LOG_TAG "iLBC_codec"
11 |
12 | #include
13 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
14 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOG_TAG, __VA_ARGS__)
15 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO , LOG_TAG, __VA_ARGS__)
16 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN , LOG_TAG, __VA_ARGS__)
17 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG, __VA_ARGS__)
18 |
19 | #define JNI_COPY 0
20 |
21 | static iLBC_encinst_t *Enc_Inst = NULL;
22 | static iLBC_decinst_t *Dec_Inst = NULL;
23 | static NsxHandle *nsxInst = NULL;
24 |
25 | const short MODE = 30;//30ms
26 | const short FRAME_SAMPLES = 240;//MODE << 3;//240
27 | const short FRAME_SIZE = 480;//FRAME_SAMPLES * sizeof(short);//480
28 |
29 | static int encoding, decoding, encoderReset, decoderReset;
30 |
31 | static void noise_supression(short* a, int samples, int ns_mode){
32 | short tmp_input[80];
33 | if(nsxInst == NULL){
34 | WebRtcNsx_Create(&nsxInst);
35 | WebRtcNsx_Init(nsxInst, 8000);//8000 hz sampling
36 | WebRtcNsx_set_policy(nsxInst, ns_mode);//0: Mild, 1: Medium , 2: Aggressive
37 | }
38 | int i;
39 | for (i = 0; i < samples; i+= 80){
40 | memcpy(tmp_input, &a[i], 80 * sizeof(short));
41 | WebRtcNsx_Process(nsxInst, tmp_input, NULL, (short *)&a[i], NULL);
42 | }
43 | }
44 |
45 | static void reset_encoder_impl(){
46 | if(!encoding){
47 | WebRtcIlbcfix_EncoderFree(Enc_Inst);
48 | Enc_Inst = NULL;
49 | WebRtcNsx_Free(nsxInst);
50 | nsxInst = NULL;
51 | encoderReset = 0;
52 | } else {
53 | encoderReset = 1;
54 | }
55 | }
56 |
57 | jint Java_com_tuenti_androidilbc_Codec_resetEncoder(JNIEnv *env, jobject this){
58 | reset_encoder_impl();
59 | return 1;
60 | }
61 |
62 | jint Java_com_tuenti_androidilbc_Codec_encode(JNIEnv *env, jobject this,
63 | jbyteArray src, jint src_offset, jint src_len, jbyteArray dest, jint ns_mode)
64 | {
65 | jsize src_size, dest_size;
66 | jbyte *src_bytes, *dest_bytes;
67 |
68 | int bytes_remaining = 0;
69 | int bytes_encoded = 0;
70 | int bytes = 0;
71 |
72 | encoding = 1;
73 | if(Enc_Inst == NULL){
74 | WebRtcIlbcfix_EncoderCreate(&Enc_Inst);
75 | WebRtcIlbcfix_EncoderInit(Enc_Inst, MODE);
76 | }
77 |
78 | src_size = (*env)->GetArrayLength(env, src);
79 | src_bytes = (*env)->GetByteArrayElements(env, src, JNI_COPY);
80 | dest_size = (*env)->GetArrayLength(env, dest);
81 | dest_bytes = (*env)->GetByteArrayElements(env, dest, JNI_COPY);
82 |
83 | src_bytes += src_offset;
84 | bytes_remaining = src_len;
85 |
86 | int truncated = bytes_remaining % FRAME_SIZE;
87 | if(truncated){
88 | bytes_remaining -= truncated;
89 | LOGW("Ignoring last %d bytes, input must be divisible by %d", truncated, FRAME_SIZE);
90 | }
91 |
92 | if(ns_mode < 0 || ns_mode > 2){
93 | LOGE("Noise supression must be set to a value of 0-3, 0:Off, 1: Mild, 2: Medium , 3: Aggressive");
94 | return -1;
95 | }
96 |
97 | if(ns_mode > 0){
98 | noise_supression((short*)src_bytes, bytes_remaining, ns_mode -1);
99 | }
100 |
101 | while(bytes_remaining > 0){
102 | bytes = WebRtcIlbcfix_Encode(Enc_Inst, (short* )src_bytes, FRAME_SAMPLES, (WebRtc_Word16 *)dest_bytes);
103 | src_bytes += FRAME_SIZE;
104 | bytes_encoded += FRAME_SIZE;
105 | bytes_remaining -= FRAME_SIZE;
106 |
107 | dest_bytes += bytes;
108 | }
109 | src_bytes -= bytes_encoded;
110 | dest_bytes -= src_len;
111 |
112 | (*env)->ReleaseByteArrayElements(env, src, src_bytes, JNI_COPY);
113 | (*env)->ReleaseByteArrayElements(env, dest, dest_bytes, JNI_COPY);
114 |
115 | encoding = 0;
116 | if(encoderReset){
117 | reset_encoder_impl();
118 | }
119 |
120 | return bytes_encoded;
121 | }
122 |
123 | static void reset_decoder_impl(){
124 | if(!decoding){
125 | WebRtcIlbcfix_DecoderFree(Dec_Inst);
126 | Dec_Inst = NULL;
127 | decoderReset = 0;
128 | } else {
129 | decoderReset = 1;
130 | }
131 | }
132 |
133 | jint Java_com_tuenti_androidilbc_Codec_resetDecoder(JNIEnv *env, jobject this){
134 | reset_decoder_impl();
135 | return 1;
136 | }
137 |
138 | jint Java_com_tuenti_androidilbc_Codec_decode(JNIEnv *env, jobject this,
139 | jbyteArray src, jint src_offset, jint src_len, jbyteArray dest)
140 | {
141 | jsize src_size, dest_size;
142 | jbyte *src_bytes, *dest_bytes;
143 |
144 | int bytes_remaining = 0;
145 | int bytes_decoded = 0;
146 | int num_samples = 0;
147 | short speechType = 0;
148 |
149 | src_size = (*env)->GetArrayLength(env, src);
150 | src_bytes = (*env)->GetByteArrayElements(env, src, JNI_COPY);
151 | dest_size = (*env)->GetArrayLength(env, dest);
152 | dest_bytes = (*env)->GetByteArrayElements(env, dest, JNI_COPY);
153 |
154 | decoding = 1;
155 | if(Dec_Inst == NULL){
156 | WebRtcIlbcfix_DecoderCreate(&Dec_Inst);
157 | WebRtcIlbcfix_DecoderInit(Dec_Inst, MODE);
158 | }
159 |
160 | src_bytes += src_offset;
161 | bytes_remaining = src_len;
162 |
163 | int i = 0;
164 | while(bytes_remaining > 0){
165 | num_samples = WebRtcIlbcfix_Decode(Dec_Inst, (short *)src_bytes, NO_OF_BYTES_30MS, (short *)dest_bytes, &speechType);
166 | src_bytes += NO_OF_BYTES_30MS;
167 | bytes_remaining -= NO_OF_BYTES_30MS;
168 | bytes_decoded += NO_OF_BYTES_30MS;
169 | dest_bytes += num_samples * sizeof(short);
170 | }
171 |
172 | src_bytes -= bytes_decoded;
173 | dest_bytes -= src_len;
174 |
175 | (*env)->ReleaseByteArrayElements(env, src, src_bytes, JNI_COPY);
176 | (*env)->ReleaseByteArrayElements(env, dest, dest_bytes, JNI_COPY);
177 |
178 | decoding = 0;
179 | if(decoderReset){
180 | reset_decoder_impl();
181 | }
182 |
183 | return bytes_decoded;
184 | }
185 |
--------------------------------------------------------------------------------
/src/com/tuenti/androidilbc/VoiceInput.java:
--------------------------------------------------------------------------------
1 | package com.tuenti.androidilbc;
2 |
3 | import java.io.FileOutputStream;
4 | import java.io.IOException;
5 | import java.util.Arrays;
6 | import java.util.Vector;
7 |
8 | import com.tuenti.androidilbc.Codec;
9 |
10 | import android.media.AudioFormat;
11 | import android.media.AudioRecord;
12 | import android.media.MediaRecorder;
13 | import android.util.Log;
14 |
15 | /**
16 | * Records and stores audio input.
17 | * Thanks to a lil' help from:
18 | * http://stackoverflow.com/questions/4525206/android-audiorecord-class-process-live-mic-audio-quickly-set-up-callback-func
19 | *
20 | * @author Wijnand Warren
21 | */
22 | public class VoiceInput extends Thread {
23 |
24 | public static final int ILBC_30_MS_FRAME_SIZE_DECODED = 480;
25 | public static final int ILBC_30_MS_FRAME_SIZE_ENCODED = 50;
26 |
27 | private static final String LOG_TAG = "VoiceInput";
28 |
29 | private static final int BUFFER_SIZE = 320;
30 | private static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
31 | // NOTE: 44100Hz is currently the only rate that is guaranteed to work on all devices, but other rates such as 22050, 16000, and 11025 may work on some devices.
32 | // TODO: Do a check to see if 8K is available!!
33 | private static final int SAMPLE_RATE_IN_HZ = 8000;//44100;
34 | private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
35 | private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
36 |
37 | private boolean stopped;
38 | private boolean lastRun;
39 | private AudioRecord recorder;
40 | private Vector audioData;
41 | private int minBufferSize;
42 | private int totalRawAudioSize;
43 |
44 | private byte[] remainderAudioBuffer;
45 | private int remainderBufferSize;
46 |
47 | private FileOutputStream outputFile;
48 |
49 | /**
50 | * CONSTRUCTOR
51 | */
52 | public VoiceInput(FileOutputStream outputFile) {
53 | // TODO: Are we really urgent?
54 | //android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
55 | this.outputFile = outputFile;
56 | stopped = false;
57 | lastRun = false;
58 | recorder = null;
59 | audioData = new Vector();
60 | totalRawAudioSize = 0;
61 |
62 | remainderAudioBuffer = new byte[ILBC_30_MS_FRAME_SIZE_DECODED];
63 | remainderBufferSize = 0;
64 | }
65 |
66 | // ==========================
67 | // PRIVATE METHODS
68 | // ==========================
69 |
70 | /**
71 | * Starts recording the microphone.
72 | */
73 | private void startRecorder() {
74 | Log.i(LOG_TAG, "startRecorder()");
75 | minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ, CHANNEL_CONFIG, AUDIO_FORMAT);
76 | Log.i(LOG_TAG, "Buffer size: " + minBufferSize);
77 |
78 | // 480 bytes for 30ms(y mode)
79 | int truncated = minBufferSize % ILBC_30_MS_FRAME_SIZE_DECODED;
80 | if (truncated != 0) {
81 | minBufferSize += ILBC_30_MS_FRAME_SIZE_DECODED - truncated;
82 | Log.i(LOG_TAG, "Extending buffer to: " + minBufferSize);
83 | }
84 |
85 | recorder = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE_IN_HZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize * 10);
86 | recorder.startRecording();
87 | }
88 |
89 |
90 | // ==========================
91 | // PUBLIC METHODS
92 | // ==========================
93 |
94 | /**
95 | * Retrieves all recorded audio data.
96 | *
97 | * @return The byte array containing all recorded audio data.
98 | */
99 | public byte[] getAudioData() {
100 | Log.i(LOG_TAG, "getAudioData()");
101 | byte[] data = new byte[audioData.size() * BUFFER_SIZE];
102 | Log.i(LOG_TAG, "Expected data size: " + data.length);
103 |
104 | for (int i = 0; i < audioData.size(); i++) {
105 | byte[] buffer = audioData.elementAt(i);
106 | System.arraycopy(buffer, 0, data, i * BUFFER_SIZE, BUFFER_SIZE);
107 | }
108 |
109 | return data;
110 | }
111 |
112 | /**
113 | * Calculates the duration of the recording based on the buffer size.
114 | *
115 | * @return Record duration in seconds.
116 | */
117 | public float getDurationBasedOnBufferLength() {
118 | float duration = 0;
119 |
120 | duration = totalRawAudioSize / 2; // 16 bit pcm
121 | duration = duration / SAMPLE_RATE_IN_HZ;
122 |
123 | return duration;
124 | }
125 |
126 | /**
127 | * Flags this Thread to be stopped.
128 | */
129 | public void requestStop() {
130 | Log.i(LOG_TAG, "requestStop()");
131 | stopped = true;
132 |
133 | // Stop recording.
134 | recorder.stop();
135 | }
136 |
137 | /**
138 | * {@inheritDoc}
139 | */
140 | @Override
141 | public void run() {
142 | try {
143 | while(true) {
144 | final byte[] recorderSamples;
145 | final byte[] encodedData;
146 | final int bytesEncoded;
147 | int bytesToEncode;
148 | byte[] tempSamples;
149 |
150 | recorderSamples = new byte[minBufferSize];
151 | int recorderSampleSize = 0;
152 |
153 | // Calculation taken from iLBC Codec.encode()
154 | int estimatedEncodedDataLength = minBufferSize / ILBC_30_MS_FRAME_SIZE_DECODED * ILBC_30_MS_FRAME_SIZE_ENCODED;
155 | encodedData = new byte[estimatedEncodedDataLength];
156 |
157 | // Read from AudioRecord buffer.
158 | recorderSampleSize = recorder.read(recorderSamples, 0, minBufferSize);
159 |
160 | // Error checking:
161 | if (recorderSampleSize == AudioRecord.ERROR_INVALID_OPERATION) {
162 | Log.e(LOG_TAG, "read() returned AudioRecord.ERROR_INVALID_OPERATION");
163 | } else if (recorderSampleSize == AudioRecord.ERROR_BAD_VALUE) {
164 | Log.e(LOG_TAG, "read() returned AudioRecord.ERROR_BAD_VALUE");
165 | } else if (recorderSampleSize == AudioRecord.ERROR) {
166 | Log.e(LOG_TAG, "read() returned AudioRecord.ERROR");
167 | }
168 |
169 | // Making sure we always pass multiples of 480 to the encoder:
170 | bytesToEncode = recorderSampleSize + remainderBufferSize;
171 |
172 | // Calculate what the size of the new remainder should be.
173 | int newRemainderBufferSize = bytesToEncode % ILBC_30_MS_FRAME_SIZE_DECODED;
174 | bytesToEncode -= newRemainderBufferSize;
175 |
176 | // Final bit to encode.
177 | if(lastRun) {
178 | Log.i(LOG_TAG, "Encoding the last piece of data!");
179 | bytesToEncode = ILBC_30_MS_FRAME_SIZE_DECODED;
180 | newRemainderBufferSize = 0;
181 | estimatedEncodedDataLength = ILBC_30_MS_FRAME_SIZE_ENCODED;
182 | tempSamples = new byte[bytesToEncode];
183 |
184 | // Copy all sample data
185 | System.arraycopy(recorderSamples, 0, tempSamples, remainderBufferSize, recorderSampleSize);
186 |
187 | // Pad last sample with zeroes.
188 | int paddingStart = remainderBufferSize + recorderSampleSize;
189 | int paddingEnd = bytesToEncode - 1;
190 | Log.i(LOG_TAG, "Padding from: " + paddingStart + " to: " + paddingEnd +" with 0-bytes." );
191 | Arrays.fill(tempSamples, paddingStart, paddingEnd, (byte)0);
192 | } else {
193 | tempSamples = new byte[bytesToEncode];
194 |
195 | // Grab bytes from previous remainder, if any.
196 | if(remainderBufferSize > 0) {
197 | System.arraycopy(remainderAudioBuffer, 0, tempSamples, 0, remainderBufferSize);
198 | }
199 |
200 | // Copy all data if no remainder needed.
201 | if(newRemainderBufferSize == 0) {
202 | Log.w(LOG_TAG, "No remainder! :D");
203 |
204 | System.arraycopy(recorderSamples, 0, tempSamples, remainderBufferSize, recorderSampleSize);
205 | remainderBufferSize = 0;
206 | }
207 | // Remainder needed to please ILBC encoder.
208 | else {
209 | Log.w(LOG_TAG, "Found a remainder: " + bytesToEncode + ", %480: " + newRemainderBufferSize);
210 | // Grab up to multiple of 480 from samples.
211 | int copyLength = recorderSampleSize - newRemainderBufferSize - remainderBufferSize;
212 | System.arraycopy(recorderSamples, 0, tempSamples, remainderBufferSize, copyLength);
213 |
214 | // Stick rest in remainder
215 | if(newRemainderBufferSize > 0) {
216 | System.arraycopy(recorderSamples, copyLength, remainderAudioBuffer, 0, newRemainderBufferSize);
217 | remainderBufferSize = newRemainderBufferSize;
218 | }
219 | }
220 | }
221 |
222 | // Keep track of total recorded bytes for duration calculation.
223 | totalRawAudioSize += bytesToEncode;
224 |
225 | // Actual encoding.
226 | // TODO: Fiddle with noise suppression.
227 | bytesEncoded = Codec.instance().encode(tempSamples, 0, bytesToEncode, encodedData, 0);
228 |
229 | try {
230 | outputFile.write(encodedData, 0, bytesEncoded);
231 | } catch (IOException e) {
232 | Log.e(LOG_TAG, "Failed to write audio data.");
233 | }
234 |
235 | // Final checks:
236 | if(stopped && !lastRun && newRemainderBufferSize > 0) {
237 | lastRun = true;
238 | } else if(lastRun || (stopped && newRemainderBufferSize == 0)) {
239 | break;
240 | }
241 | }
242 | } catch(Throwable e) {
243 | Log.e(LOG_TAG, "Error reading voice audio", e);
244 | } finally {
245 | // Clean up
246 | Log.i(LOG_TAG, "VoiceInput Thread is stopped.");
247 | recorder.release();
248 | recorder = null;
249 |
250 | // Reset encoder
251 | Codec.instance().resetEncoder();
252 |
253 | // Close file.
254 | try {
255 | outputFile.close();
256 | } catch (IOException e) {
257 | Log.e(LOG_TAG, "Failed to close outputFile!");
258 | }
259 | }
260 | }
261 |
262 | /**
263 | * {@inheritDoc}
264 | */
265 | @Override
266 | public void start() {
267 | remainderBufferSize = 0;
268 | stopped = false;
269 | lastRun = false;
270 | totalRawAudioSize = 0;
271 |
272 | startRecorder();
273 | super.start();
274 | }
275 |
276 | }
277 |
--------------------------------------------------------------------------------