diff options
author | Andrew Comminos <andrew@comminos.com> | 2016-03-06 12:13:30 +0300 |
---|---|---|
committer | Andrew Comminos <andrew@comminos.com> | 2016-03-06 12:13:30 +0300 |
commit | b678b2405dc22015c9dad80c0368bf8af62c9fde (patch) | |
tree | 4c72d74519b99fb70b3289b20daf344f938868c0 /src/main/java/com | |
parent | 6af23db0e772fd67ba5c55aa0f9ac74ea9606153 (diff) |
Keep audio input thread running on PTT, integrate polymorphic input modes into AudioInput.
Diffstat (limited to 'src/main/java/com')
7 files changed, 137 insertions, 251 deletions
diff --git a/src/main/java/com/morlunk/jumble/JumbleService.java b/src/main/java/com/morlunk/jumble/JumbleService.java index ff05434..dce42df 100644 --- a/src/main/java/com/morlunk/jumble/JumbleService.java +++ b/src/main/java/com/morlunk/jumble/JumbleService.java @@ -35,6 +35,10 @@ import android.util.Log; import com.morlunk.jumble.audio.AudioOutput; import com.morlunk.jumble.audio.BluetoothScoReceiver; +import com.morlunk.jumble.audio.inputmode.ActivityInputMode; +import com.morlunk.jumble.audio.inputmode.ContinuousInputMode; +import com.morlunk.jumble.audio.inputmode.IInputMode; +import com.morlunk.jumble.audio.inputmode.ToggleInputMode; import com.morlunk.jumble.audio.javacpp.CELT7; import com.morlunk.jumble.exception.AudioException; import com.morlunk.jumble.exception.NotConnectedException; @@ -124,6 +128,7 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn private List<Integer> mLocalMuteHistory; private List<Integer> mLocalIgnoreHistory; private AudioHandler.Builder mAudioBuilder; + private int mTransmitMode; private PowerManager.WakeLock mWakeLock; private Handler mHandler; @@ -135,6 +140,10 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn private AudioHandler mAudioHandler; private BluetoothScoReceiver mBluetoothReceiver; + private ActivityInputMode mActivityInputMode; + private ToggleInputMode mToggleInputMode; + private ContinuousInputMode mContinuousInputMode; + private boolean mReconnecting; /** @@ -158,34 +167,38 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn private AudioHandler.AudioEncodeListener mAudioInputListener = new AudioHandler.AudioEncodeListener() { - @Override - public void onAudioEncoded(byte[] data, int length) { - if(mConnection != null && mConnection.isSynchronized()) { - mConnection.sendUDPMessage(data, length, false); - } - } - - @Override - public void onTalkStateChange(final TalkState state) { - mHandler.post(new Runnable() { @Override - public void run() { - try { - if (!isSynchronized()) - throw new NotSynchronizedException(); - - final User currentUser = mModelHandler.getUser(mConnection.getSession()); - if (currentUser == null) return; - - currentUser.setTalkState(state); - mCallbacks.onUserTalkStateUpdated(currentUser); - } catch (NotSynchronizedException e) { - e.printStackTrace(); + public void onAudioEncoded(byte[] data, int length) { + if(mConnection != null && mConnection.isSynchronized()) { + mConnection.sendUDPMessage(data, length, false); } } - }); - } - }; + + @Override + public void setTransmitting(final boolean talking) { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + if (!isSynchronized()) + throw new NotSynchronizedException(); + + final User currentUser = mModelHandler.getUser(mConnection.getSession()); + if (currentUser == null) return; + + // FIXME: should be changed to work for whispers. + if ((currentUser.getTalkState() == TalkState.TALKING) ^ talking) { + currentUser.setTalkState(talking ? TalkState.TALKING : TalkState.PASSIVE); + mCallbacks.onUserTalkStateUpdated(currentUser); + + } + } catch (NotSynchronizedException e) { + e.printStackTrace(); + } + } + }); + } + }; private AudioOutput.AudioOutputListener mAudioOutputListener = new AudioOutput.AudioOutputListener() { @Override @@ -241,6 +254,9 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn mConnectionState = ConnectionState.DISCONNECTED; mBluetoothReceiver = new BluetoothScoReceiver(this, this); registerReceiver(mBluetoothReceiver, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED)); + mToggleInputMode = new ToggleInputMode(); + mActivityInputMode = new ActivityInputMode(0); // FIXME: reasonable default + mContinuousInputMode = new ContinuousInputMode(); } @Override @@ -490,13 +506,28 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn reconnectNeeded = true; } if (extras.containsKey(EXTRAS_DETECTION_THRESHOLD)) { - mAudioBuilder.setVADThreshold(extras.getFloat(EXTRAS_DETECTION_THRESHOLD)); + mActivityInputMode.setThreshold(extras.getFloat(EXTRAS_DETECTION_THRESHOLD)); } if (extras.containsKey(EXTRAS_AMPLITUDE_BOOST)) { mAudioBuilder.setAmplitudeBoost(extras.getFloat(EXTRAS_AMPLITUDE_BOOST)); } if (extras.containsKey(EXTRAS_TRANSMIT_MODE)) { - mAudioBuilder.setTransmitMode(extras.getInt(EXTRAS_TRANSMIT_MODE)); + mTransmitMode = extras.getInt(EXTRAS_TRANSMIT_MODE); + IInputMode inputMode; + switch (mTransmitMode) { + case Constants.TRANSMIT_PUSH_TO_TALK: + inputMode = mToggleInputMode; + break; + case Constants.TRANSMIT_CONTINUOUS: + inputMode = mContinuousInputMode; + break; + case Constants.TRANSMIT_VOICE_ACTIVITY: + inputMode = mActivityInputMode; + break; + default: + throw new IllegalArgumentException(); + } + mAudioBuilder.setInputMode(inputMode); } if (extras.containsKey(EXTRAS_INPUT_RATE)) { mAudioBuilder.setInputSampleRate(extras.getInt(EXTRAS_INPUT_RATE)); @@ -549,7 +580,9 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn reconnectNeeded = true; } if (extras.containsKey(EXTRAS_HALF_DUPLEX)) { - mAudioBuilder.setHalfDuplexEnabled(extras.getBoolean(EXTRAS_HALF_DUPLEX)); + mAudioBuilder.setHalfDuplexEnabled( + extras.getInt(EXTRAS_TRANSMIT_MODE) == Constants.TRANSMIT_PUSH_TO_TALK + && extras.getBoolean(EXTRAS_HALF_DUPLEX)); } if (extras.containsKey(EXTRAS_LOCAL_MUTE_HISTORY)) { mLocalMuteHistory = extras.getIntegerArrayList(EXTRAS_LOCAL_MUTE_HISTORY); @@ -801,11 +834,7 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn @Override public int getTransmitMode() { - try { - return getAudioHandler().getTransmitMode(); - } catch (NotSynchronizedException e) { - throw new IllegalStateException(e); - } + return mTransmitMode; } @Override @@ -846,32 +875,12 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn @Override public boolean isTalking() { - try { - return getAudioHandler().isRecording(); - } catch (NotSynchronizedException e) { - throw new IllegalStateException(e); - } + return mToggleInputMode.isTalkingOn(); } @Override public void setTalkingState(boolean talking) { - if (getSessionUser().isSelfMuted() || getSessionUser().isMuted()) - return; - - try { - if (getAudioHandler().getTransmitMode() != Constants.TRANSMIT_PUSH_TO_TALK) { - Log.w(Constants.TAG, "Attempted to set talking state when not using PTT"); - return; - } - - try { - getAudioHandler().setTalking(talking); - } catch (AudioException e) { - logError(e.getMessage()); - } - } catch (NotSynchronizedException e) { - throw new IllegalStateException(e); - } + mToggleInputMode.setTalkingOn(talking); } @Override diff --git a/src/main/java/com/morlunk/jumble/audio/AudioInput.java b/src/main/java/com/morlunk/jumble/audio/AudioInput.java index ed44938..f21bd32 100644 --- a/src/main/java/com/morlunk/jumble/audio/AudioInput.java +++ b/src/main/java/com/morlunk/jumble/audio/AudioInput.java @@ -32,28 +32,18 @@ import com.morlunk.jumble.protocol.AudioHandler; */ public class AudioInput implements Runnable { public static final int[] SAMPLE_RATES = { 48000, 44100, 16000, 8000 }; - private static final int SPEECH_DETECT_THRESHOLD = (int) (0.25 * Math.pow(10, 9)); // Continue speech for 250ms to prevent dropping // AudioRecord state private AudioInputListener mListener; private AudioRecord mAudioRecord; private final int mFrameSize; - // Preferences - private int mTransmitMode; - private float mVADThreshold; - private float mAmplitudeBoost = 1.0f; - private Thread mRecordThread; private boolean mRecording; - public AudioInput(AudioInputListener listener, int audioSource, int targetSampleRate, - int transmitMode, float vadThreshold, float amplitudeBoost) throws - NativeAudioException, AudioInitializationException { + public AudioInput(AudioInputListener listener, int audioSource, int targetSampleRate) + throws NativeAudioException, AudioInitializationException { mListener = listener; - mTransmitMode = transmitMode; - mVADThreshold = vadThreshold; - mAmplitudeBoost = amplitudeBoost; // Attempt to construct an AudioRecord with the target sample rate first. // If it fails, keep producing AudioRecord instances until we find one that initializes @@ -122,15 +112,6 @@ public class AudioInput implements Runnable { } } - public void setVADThreshold(float threshold) { - mVADThreshold = threshold; - } - - public void setAmplitudeBoost(float boost) { - if(boost < 0) throw new IllegalArgumentException("Amplitude boost must not be a negative number!"); - mAmplitudeBoost = boost; - } - /** * Stops the record loop and waits on it to finish. * Releases native audio resources. @@ -178,68 +159,17 @@ public class AudioInput implements Runnable { Log.i(Constants.TAG, "AudioInput: started"); - boolean vadLastDetected = false; - long vadLastDetectedTime = 0; - mAudioRecord.startRecording(); if(mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) return; - if(mTransmitMode == Constants.TRANSMIT_CONTINUOUS || mTransmitMode == Constants.TRANSMIT_PUSH_TO_TALK) - mListener.onTalkStateChange(TalkState.TALKING); - final short[] mAudioBuffer = new short[mFrameSize]; // We loop when the 'recording' instance var is true instead of checking audio record state because we want to always cleanly shutdown. while(mRecording) { int shortsRead = mAudioRecord.read(mAudioBuffer, 0, mFrameSize); if(shortsRead > 0) { - // Boost/reduce amplitude based on user preference - // TODO: perhaps amplify to the largest value that does not result in clipping. - if(mAmplitudeBoost != 1.0f) { - for(int i = 0; i < mFrameSize; i++) { - // Java only guarantees the bounded preservation of sign in a narrowing - // primitive conversion from float -> int, not float -> int -> short. - float val = mAudioBuffer[i] * mAmplitudeBoost; - if (val > Short.MAX_VALUE) { - val = Short.MAX_VALUE; - } else if (val < Short.MIN_VALUE) { - val = Short.MIN_VALUE; - } - mAudioBuffer[i] = (short) val; - } - } - - boolean talking = true; - - if(mTransmitMode == Constants.TRANSMIT_VOICE_ACTIVITY) { - // Use a logarithmic energy-based scale for VAD. - float sum = 1.0f; - for (int i = 0; i < mFrameSize; i++) { - sum += Math.pow(mAudioBuffer[i], 2); - } - float micLevel = (float) Math.sqrt(sum / (float)mFrameSize); - float peakSignal = (float) (20.0f*Math.log10(micLevel / 32768.0f))/96.0f; - talking = (peakSignal+1) >= mVADThreshold; - - /* Record the last time where VAD was detected in order to prevent speech dropping. */ - if(talking) { - vadLastDetectedTime = System.nanoTime(); - } else { - talking = (System.nanoTime() - vadLastDetectedTime) < SPEECH_DETECT_THRESHOLD; - } - - // Update the service with the new talking state if we detected voice. - if(talking ^ vadLastDetected) { - mListener.onTalkStateChange(talking ? TalkState.TALKING : TalkState.PASSIVE); - } - - vadLastDetected = talking; - } - - if(talking) { - mListener.onAudioInputReceived(mAudioBuffer, mFrameSize); - } + mListener.onAudioInputReceived(mAudioBuffer, mFrameSize); } else { Log.e(Constants.TAG, "Error fetching audio! AudioRecord error " + shortsRead); } @@ -247,13 +177,10 @@ public class AudioInput implements Runnable { mAudioRecord.stop(); - mListener.onTalkStateChange(TalkState.PASSIVE); - Log.i(Constants.TAG, "AudioInput: stopped"); } public interface AudioInputListener { - public void onTalkStateChange(TalkState state); - public void onAudioInputReceived(short[] frame, int frameSize); + void onAudioInputReceived(short[] frame, int frameSize); } } diff --git a/src/main/java/com/morlunk/jumble/audio/inputmode/ActivityInputMode.java b/src/main/java/com/morlunk/jumble/audio/inputmode/ActivityInputMode.java index bd96b30..fd7d2a7 100644 --- a/src/main/java/com/morlunk/jumble/audio/inputmode/ActivityInputMode.java +++ b/src/main/java/com/morlunk/jumble/audio/inputmode/ActivityInputMode.java @@ -25,7 +25,7 @@ public class ActivityInputMode implements IInputMode { // Continue speech for 250ms to prevent dropping. private static final int SPEECH_DELAY = (int) (0.25 * Math.pow(10, 9)); - private final float mVADThreshold; + private float mVADThreshold; private long mVADLastDetected; public ActivityInputMode(float detectionThreshold) { @@ -33,7 +33,7 @@ public class ActivityInputMode implements IInputMode { } @Override - public boolean onInputReceived(short[] pcm, int length) { + public boolean shouldTransmit(short[] pcm, int length) { // Use a logarithmic energy-based scale for VAD. float sum = 1.0f; for (int i = 0; i < length; i++) { @@ -43,13 +43,17 @@ public class ActivityInputMode implements IInputMode { float peakSignal = (float) (20.0f * Math.log10(micLevel / 32768.0f)) / 96.0f; boolean talking = (peakSignal + 1) >= mVADThreshold; - talking |= (System.nanoTime() - mVADLastDetected) < SPEECH_DELAY; - // Record the last time where VAD was detected in order to prevent speech dropping. if(talking) { mVADLastDetected = System.nanoTime(); } + talking |= (System.nanoTime() - mVADLastDetected) < SPEECH_DELAY; + return talking; } + + public void setThreshold(float threshold) { + mVADThreshold = threshold; + } } diff --git a/src/main/java/com/morlunk/jumble/audio/inputmode/ContinuousInputMode.java b/src/main/java/com/morlunk/jumble/audio/inputmode/ContinuousInputMode.java index cd0c282..9405e00 100644 --- a/src/main/java/com/morlunk/jumble/audio/inputmode/ContinuousInputMode.java +++ b/src/main/java/com/morlunk/jumble/audio/inputmode/ContinuousInputMode.java @@ -23,7 +23,7 @@ package com.morlunk.jumble.audio.inputmode; */ public class ContinuousInputMode implements IInputMode { @Override - public boolean onInputReceived(short[] pcm, int length) { + public boolean shouldTransmit(short[] pcm, int length) { return true; } } diff --git a/src/main/java/com/morlunk/jumble/audio/inputmode/IInputMode.java b/src/main/java/com/morlunk/jumble/audio/inputmode/IInputMode.java index 78f4cf8..4244afc 100644 --- a/src/main/java/com/morlunk/jumble/audio/inputmode/IInputMode.java +++ b/src/main/java/com/morlunk/jumble/audio/inputmode/IInputMode.java @@ -28,5 +28,5 @@ public interface IInputMode { * @param length The number of shorts in the PCM data. * @return true if the input should be transmitted. */ - boolean onInputReceived(short[] pcm, int length); + boolean shouldTransmit(short[] pcm, int length); } diff --git a/src/main/java/com/morlunk/jumble/audio/inputmode/ToggleInputMode.java b/src/main/java/com/morlunk/jumble/audio/inputmode/ToggleInputMode.java index 8466805..1a86e62 100644 --- a/src/main/java/com/morlunk/jumble/audio/inputmode/ToggleInputMode.java +++ b/src/main/java/com/morlunk/jumble/audio/inputmode/ToggleInputMode.java @@ -41,7 +41,7 @@ public class ToggleInputMode implements IInputMode { } @Override - public boolean onInputReceived(short[] pcm, int length) { + public boolean shouldTransmit(short[] pcm, int length) { return mInputOn; } } diff --git a/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java b/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java index 1d5273f..3d17fe2 100644 --- a/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java +++ b/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java @@ -17,13 +17,9 @@ package com.morlunk.jumble.protocol; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.media.AudioManager; import android.util.Log; -import android.widget.Toast; import com.morlunk.jumble.Constants; import com.morlunk.jumble.R; @@ -35,6 +31,7 @@ import com.morlunk.jumble.audio.encoder.IEncoder; import com.morlunk.jumble.audio.encoder.OpusEncoder; import com.morlunk.jumble.audio.encoder.PreprocessingEncoder; import com.morlunk.jumble.audio.encoder.ResamplingEncoder; +import com.morlunk.jumble.audio.inputmode.IInputMode; import com.morlunk.jumble.exception.AudioException; import com.morlunk.jumble.exception.AudioInitializationException; import com.morlunk.jumble.exception.NativeAudioException; @@ -50,8 +47,7 @@ import com.morlunk.jumble.util.JumbleNetworkListener; /** * Bridges the protocol's audio messages to our input and output threads. * A useful intermediate for reducing code coupling. - * Audio playback and recording is exclusively controlled by the protocol. The user can 'hint' to - * the handler that it wishes to talk with {@link #setTalking(boolean)}. + * Audio playback and recording is exclusively controlled by the protocol. * Changes to input/output instance vars after the audio threads have been initialized will recreate * them in most cases (they're immutable for the purpose of avoiding threading issues). * Calling shutdown() will cleanup both input and output threads. It is safe to restart after. @@ -80,16 +76,10 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au private int mSampleRate; private int mBitrate; private int mFramesPerPacket; - private int mTransmitMode; - private final float mVADThreshold; + private final IInputMode mInputMode; private final float mAmplitudeBoost; private boolean mInitialized; - /** - * True if the user wants to transmit voice. - * Always true for voice activity and continuous input methods. - */ - private boolean mTalking; /** True if the user is muted on the server. */ private boolean mMuted; private boolean mBluetoothOn; @@ -100,7 +90,7 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au public AudioHandler(Context context, JumbleLogger logger, int audioStream, int audioSource, int sampleRate, int targetBitrate, int targetFramesPerPacket, - int transmitMode, float vadThreshold, float amplitudeBoost, + IInputMode inputMode, float amplitudeBoost, boolean bluetoothEnabled, boolean halfDuplexEnabled, boolean preprocessorEnabled, AudioEncodeListener encodeListener, AudioOutput.AudioOutputListener outputListener) throws AudioInitializationException, NativeAudioException { @@ -111,8 +101,7 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au mSampleRate = sampleRate; mBitrate = targetBitrate; mFramesPerPacket = targetFramesPerPacket; - mTransmitMode = transmitMode; - mVADThreshold = vadThreshold; + mInputMode = inputMode; mAmplitudeBoost = amplitudeBoost; mBluetoothOn = bluetoothEnabled; mHalfDuplex = halfDuplexEnabled; @@ -123,13 +112,12 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mEncoderLock = new Object(); - mInput = new AudioInput(this, mAudioSource, mSampleRate, mTransmitMode, - mVADThreshold, mAmplitudeBoost); + mInput = new AudioInput(this, mAudioSource, mSampleRate); mOutput = new AudioOutput(mOutputListener); } /** - * Starts the audio output thread, and input if {@link #isTalking()} and not muted. + * Starts the audio output and input threads. * Will create both the input and output modules if they haven't been created yet. */ public synchronized void initialize(User self, int maxBandwidth, JumbleUDPMessageType codec) throws AudioException { @@ -138,12 +126,8 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au setMaxBandwidth(maxBandwidth); setCodec(codec); - - setTalking(mTransmitMode == Constants.TRANSMIT_CONTINUOUS - || mTransmitMode == Constants.TRANSMIT_VOICE_ACTIVITY); setServerMuted(self.isMuted() || self.isLocalMuted() || self.isSuppressed()); - if (mTalking && !mMuted) - startRecording(); + startRecording(); // Ensure that if a bluetooth SCO connection is active, we use the VOICE_CALL stream. // This is required by Android for compatibility with SCO. mOutput.startPlaying(mBluetoothOn ? AudioManager.STREAM_VOICE_CALL : mAudioStream); @@ -160,7 +144,7 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au synchronized (mInput) { if (!mInput.isRecording()) { mInput.startRecording(); - if (mHalfDuplex && mTransmitMode == Constants.TRANSMIT_PUSH_TO_TALK) { + if (mHalfDuplex) { mAudioManager.setStreamMute(getAudioStream(), true); } } else { @@ -177,7 +161,7 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au synchronized (mInput) { if (mInput.isRecording()) { mInput.stopRecording(); - if (mHalfDuplex && mTransmitMode == Constants.TRANSMIT_PUSH_TO_TALK) { + if (mHalfDuplex) { mAudioManager.setStreamMute(getAudioStream(), false); } } else { @@ -187,44 +171,11 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au } /** - * Sets whether we should record if the user is not muted or deafened. - * This defaults to true when using voice activity or continous input, false for push-to-talk. - * @param talking Whether to record, if available. - */ - public synchronized void setTalking(boolean talking) throws AudioException { - mTalking = talking; - // We start recording on initialization. - if (mInitialized) { - if (!mMuted && talking && !isRecording()) - startRecording(); - else if (!talking && isRecording()) - stopRecording(); - } - } - - /** - * Returns the talking state of the client. - * This does **NOT** mean we are recording! - * @return true if the client wants to be talking. - */ - public boolean isTalking() { - return mTalking; - } - - /** * Sets whether or not the server wants the client muted. - * If the user is muted by the server, audio input will be suspended. - * If the user is unmuted by the server, audio input will be unsuspended if {@link #isTalking()}. * @param muted Whether the user is muted on the server. */ private void setServerMuted(boolean muted) throws AudioException { mMuted = muted; - if (mInitialized) { - if (!muted && mTalking && !isRecording()) - startRecording(); - else if (muted && isRecording()) - stopRecording(); - } } /** @@ -235,16 +186,6 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au return mInitialized; } - /** - * User is recording if isTalking() && !isMuted(). - * @return - */ - public boolean isRecording() { - synchronized (mInput) { - return mInput.isRecording(); - } - } - public boolean isPlaying() { synchronized (mOutput) { return mOutput.isPlaying(); @@ -353,14 +294,6 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au return mFramesPerPacket; } - public int getTransmitMode() { - return mTransmitMode; - } - - public float getVADThreshold() { - return mVADThreshold; - } - public float getAmplitudeBoost() { return mAmplitudeBoost; } @@ -457,36 +390,49 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au } @Override - public void onTalkStateChange(TalkState state) { + public void onAudioInputReceived(short[] frame, int frameSize) { + boolean talking = mInputMode.shouldTransmit(frame, frameSize); + mEncodeListener.setTransmitting(talking); synchronized (mEncoderLock) { - if (mEncoder != null && state == TalkState.PASSIVE) { - try { - mEncoder.terminate(); - if (mEncoder.isReady()) { - sendEncodedAudio(); + if (!talking || mMuted) { + if (mEncoder != null) { + try { + mEncoder.terminate(); + if (mEncoder.isReady()) { + sendEncodedAudio(); + } + } catch (NativeAudioException e) { + e.printStackTrace(); } - } catch (NativeAudioException e) { - e.printStackTrace(); } - } - } - mEncodeListener.onTalkStateChange(state); - } - - @Override - public void onAudioInputReceived(short[] frame, int frameSize) { - synchronized (mEncoderLock) { - if (mEncoder != null) { - try { - mEncoder.encode(frame, frameSize); - mFrameCounter++; - } catch (NativeAudioException e) { - e.printStackTrace(); - return; + } else { + // Boost/reduce amplitude based on user preference + // TODO: perhaps amplify to the largest value that does not result in clipping. + if (mAmplitudeBoost != 1.0f) { + for (int i = 0; i < frameSize; i++) { + // Java only guarantees the bounded preservation of sign in a narrowing + // primitive conversion from float -> int, not float -> int -> short. + float val = frame[i] * mAmplitudeBoost; + if (val > Short.MAX_VALUE) { + val = Short.MAX_VALUE; + } else if (val < Short.MIN_VALUE) { + val = Short.MIN_VALUE; + } + frame[i] = (short) val; + } } - if (mEncoder.isReady()) { - sendEncodedAudio(); + if (mEncoder != null) { + try { + mEncoder.encode(frame, frameSize); + mFrameCounter++; + + if (mEncoder.isReady()) { + sendEncodedAudio(); + } + } catch (NativeAudioException e) { + e.printStackTrace(); + } } } } @@ -516,8 +462,8 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au } public interface AudioEncodeListener { - public void onAudioEncoded(byte[] data, int length); - public void onTalkStateChange(TalkState state); + void onAudioEncoded(byte[] data, int length); + void setTransmitting(boolean talking); } /** @@ -531,12 +477,12 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au private int mTargetBitrate; private int mTargetFramesPerPacket; private int mInputSampleRate; - private int mTransmitMode; private float mVADThreshold; private float mAmplitudeBoost; private boolean mBluetoothEnabled; private boolean mHalfDuplexEnabled; private boolean mPreprocessorEnabled; + private IInputMode mInputMode; private AudioEncodeListener mEncodeListener; private AudioOutput.AudioOutputListener mTalkingListener; @@ -575,11 +521,6 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au return this; } - public Builder setTransmitMode(int transmitMode) { - mTransmitMode = transmitMode; - return this; - } - public Builder setVADThreshold(float vadThreshold) { mVADThreshold = vadThreshold; return this; @@ -615,14 +556,19 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au return this; } + public Builder setInputMode(IInputMode inputMode) { + mInputMode = inputMode; + return this; + } + /** * Creates a new AudioHandler for the given session and begins managing input/output. * @return An initialized audio handler. */ public AudioHandler initialize(User self, int maxBandwidth, JumbleUDPMessageType codec) throws AudioException { AudioHandler handler = new AudioHandler(mContext, mLogger, mAudioStream, mAudioSource, - mInputSampleRate, mTargetBitrate, mTargetFramesPerPacket, mTransmitMode, - mVADThreshold, mAmplitudeBoost, mBluetoothEnabled, mHalfDuplexEnabled, + mInputSampleRate, mTargetBitrate, mTargetFramesPerPacket, mInputMode, + mAmplitudeBoost, mBluetoothEnabled, mHalfDuplexEnabled, mPreprocessorEnabled, mEncodeListener, mTalkingListener); handler.initialize(self, maxBandwidth, codec); return handler; |