├── 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 | --------------------------------------------------------------------------------