diff options
author | Andrew Comminos <andrew@morlunk.com> | 2015-03-20 02:49:28 +0300 |
---|---|---|
committer | Andrew Comminos <andrew@morlunk.com> | 2015-03-20 02:49:28 +0300 |
commit | 8afb5629012e2a1fef813fb11c002c227aedbd46 (patch) | |
tree | 722e41b39703cbcbc338ec51f0efcbfb0cf94745 | |
parent | dc9a8f8aa589a8f612523d055508978d894ebce3 (diff) |
New AudioHandler talking and mute/deafen mechanics.
Should maintain audio state better.
4 files changed, 118 insertions, 40 deletions
diff --git a/src/main/java/com/morlunk/jumble/JumbleService.java b/src/main/java/com/morlunk/jumble/JumbleService.java index 4ab0e60..407ad41 100644 --- a/src/main/java/com/morlunk/jumble/JumbleService.java +++ b/src/main/java/com/morlunk/jumble/JumbleService.java @@ -379,13 +379,6 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon mConnection.sendTCPMessage(version.build(), JumbleTCPMessageType.Version); mConnection.sendTCPMessage(auth.build(), JumbleTCPMessageType.Authenticate); - - try { - mAudioHandler.initialize(); - } catch (AudioException e) { - e.printStackTrace(); - onConnectionWarning(e.getMessage()); - } } @Override @@ -396,6 +389,15 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon mWakeLock.acquire(); try { + mAudioHandler.initialize( + mModelHandler.getUser(mConnection.getSession()), + mConnection.getCodec()); + } catch (AudioException e) { + e.printStackTrace(); + onConnectionWarning(e.getMessage()); + } + + try { mCallbacks.onConnected(); } catch (RemoteException e) { e.printStackTrace(); @@ -428,7 +430,9 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon mConnectionState = STATE_DISCONNECTED; } - if(mWakeLock.isHeld()) mWakeLock.release(); + if(mWakeLock.isHeld()) { + mWakeLock.release(); + } if (mAudioHandler != null) { mAudioHandler.shutdown(); @@ -663,7 +667,7 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon @Override public int getCodec() throws RemoteException { - return mConnection.getCodec(); + return mConnection.getCodec().ordinal(); // FIXME: ordinal is bad, make enum method } @Override @@ -684,11 +688,7 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon } try { - if (talking) { - mAudioHandler.startRecording(); - } else { - mAudioHandler.stopRecording(); - } + mAudioHandler.setTalking(talking); } catch (AudioException e) { log(Message.Type.WARNING, e.getMessage()); } @@ -872,14 +872,6 @@ public class JumbleService extends Service implements JumbleConnection.JumbleCon Mumble.UserState.Builder usb = Mumble.UserState.newBuilder(); usb.setSelfMute(mute); usb.setSelfDeaf(deaf); - try { - if (!mute && !mAudioHandler.isRecording() && (mTransmitMode == Constants.TRANSMIT_CONTINUOUS || mTransmitMode == Constants.TRANSMIT_VOICE_ACTIVITY)) - mAudioHandler.startRecording(); // Resume recording when unmuted for PTT. - else if (mute && mAudioHandler.isRecording()) - mAudioHandler.stopRecording(); // Stop recording when muted. - } catch (AudioException e) { - log(Message.Type.WARNING, e.getMessage()); - } mConnection.sendTCPMessage(usb.build(), JumbleTCPMessageType.UserState); } diff --git a/src/main/java/com/morlunk/jumble/audio/AudioInput.java b/src/main/java/com/morlunk/jumble/audio/AudioInput.java index 5247bbf..b6e1ad7 100644 --- a/src/main/java/com/morlunk/jumble/audio/AudioInput.java +++ b/src/main/java/com/morlunk/jumble/audio/AudioInput.java @@ -186,6 +186,8 @@ public class AudioInput implements Runnable { public void run() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); + Log.i(Constants.TAG, "AudioInput: started"); + boolean vadLastDetected = false; long vadLastDetectedTime = 0; @@ -246,6 +248,8 @@ public class AudioInput implements Runnable { mAudioRecord.stop(); mListener.onTalkStateChange(User.TalkState.PASSIVE); + + Log.i(Constants.TAG, "AudioInput: stopped"); } public interface AudioInputListener { diff --git a/src/main/java/com/morlunk/jumble/net/JumbleConnection.java b/src/main/java/com/morlunk/jumble/net/JumbleConnection.java index d23be8c..f3474cc 100644 --- a/src/main/java/com/morlunk/jumble/net/JumbleConnection.java +++ b/src/main/java/com/morlunk/jumble/net/JumbleConnection.java @@ -110,7 +110,7 @@ public class JumbleConnection implements JumbleTCP.TCPConnectionListener, Jumble private String mServerOSName; private String mServerOSVersion; private int mMaxBandwidth; - private int mCodec; + private JumbleUDPMessageType mCodec; // Session private int mSession; @@ -150,11 +150,11 @@ public class JumbleConnection implements JumbleTCP.TCPConnectionListener, Jumble @Override public void messageCodecVersion(Mumble.CodecVersion msg) { if(msg.hasOpus() && msg.getOpus()) - mCodec = JumbleUDPMessageType.UDPVoiceOpus.ordinal(); + mCodec = JumbleUDPMessageType.UDPVoiceOpus; else if(msg.hasBeta() && !msg.getPreferAlpha()) - mCodec = JumbleUDPMessageType.UDPVoiceCELTBeta.ordinal(); + mCodec = JumbleUDPMessageType.UDPVoiceCELTBeta; else - mCodec = JumbleUDPMessageType.UDPVoiceCELTAlpha.ordinal(); + mCodec = JumbleUDPMessageType.UDPVoiceCELTAlpha; } @Override @@ -432,7 +432,7 @@ public class JumbleConnection implements JumbleTCP.TCPConnectionListener, Jumble return mMaxBandwidth; } - public int getCodec() { + public JumbleUDPMessageType getCodec() { return mCodec; } diff --git a/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java b/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java index 21e0ebf..0fc347c 100644 --- a/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java +++ b/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java @@ -50,6 +50,8 @@ 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)}. * 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. @@ -60,14 +62,15 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au public static final int FRAME_SIZE = SAMPLE_RATE/100; public static final int MAX_BUFFER_SIZE = 960; - private Context mContext; - private JumbleLogger mLogger; - private AudioManager mAudioManager; + private final Context mContext; + private final JumbleLogger mLogger; + private final AudioManager mAudioManager; private AudioInput mInput; private AudioOutput mOutput; private AudioOutput.AudioOutputListener mOutputListener; private AudioEncodeListener mEncodeListener; + private int mSession; private JumbleUDPMessageType mCodec; private IEncoder mEncoder; private int mFrameCounter; @@ -82,6 +85,13 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au private float mAmplitudeBoost = 1.0f; 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; private boolean mHalfDuplex; private boolean mPreprocessorEnabled; @@ -142,9 +152,8 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au mInput = new AudioInput(this, mAudioSource, mSampleRate, mTransmitMode, mVADThreshold, mAmplitudeBoost); - if(mTransmitMode == Constants.TRANSMIT_VOICE_ACTIVITY || mTransmitMode == Constants.TRANSMIT_CONTINUOUS) { - mInput.startRecording(); - } + if (mTalking && !mMuted) + startRecording(); } /** @@ -157,17 +166,19 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au } /** - * Starts the audio output thread. Will create both the input and output modules if they - * haven't been created yet. If the codec information has not yet been received from the server, - * we'll initialize input once we receive that. + * Starts the audio output thread, and input if {@link #isTalking()} and not muted. + * Will create both the input and output modules if they haven't been created yet. */ - public synchronized void initialize() throws AudioException { + public synchronized void initialize(User self, JumbleUDPMessageType codec) throws AudioException { if(mInitialized) return; + mSession = self.getSession(); + setServerMuted(self.isMuted() || self.isLocalMuted() || self.isSuppressed()); if(mOutput == null) createAudioOutput(); if(mInput == null) createAudioInput(); // This sticky broadcast will initialize the audio output. mContext.registerReceiver(mBluetoothReceiver, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED)); mInitialized = true; + setCodec(codec); } /** @@ -175,7 +186,7 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au * @throws AudioException if the input thread failed to initialize, or if a thread was already * recording. */ - public synchronized void startRecording() throws AudioException { + private synchronized void startRecording() throws AudioException { if(mInput == null) createAudioInput(); if (!mInput.isRecording()) { @@ -192,7 +203,7 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au * Stops the recording AudioInput thread. * @throws AudioException if there was no thread recording. */ - public synchronized void stopRecording() throws AudioException { + private synchronized void stopRecording() throws AudioException { if(mInput == null) return; if (mInput.isRecording()) { @@ -206,6 +217,47 @@ 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(); + } + } + + /** * Returns whether or not the handler has been initialized. * @return true if the handler is ready to play and record audio. */ @@ -213,10 +265,17 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au return mInitialized; } + /** + * User is recording if isTalking() && !isMuted(). + * @return + */ public boolean isRecording() { return mInput != null && mInput.isRecording(); } + public boolean isPlaying() { + return mOutput != null && mOutput.isPlaying(); + } public JumbleUDPMessageType getCodec() { return mCodec; @@ -391,6 +450,8 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au public void setTransmitMode(int transmitMode) throws AudioException { this.mTransmitMode = transmitMode; if(mInitialized) createAudioInput(); + setTalking(transmitMode == Constants.TRANSMIT_CONTINUOUS + || transmitMode == Constants.TRANSMIT_VOICE_ACTIVITY); } public float getVADThreshold() { @@ -456,7 +517,8 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au /** * Shuts down the audio handler, halting input and output. - * The handler may still be reinitialized with {@link AudioHandler#initialize()} after. + * The handler may still be reinitialized with + * {@link AudioHandler#initialize(int, com.morlunk.jumble.net.JumbleUDPMessageType)} after. */ public synchronized void shutdown() { if(mInput != null) { @@ -485,8 +547,12 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au audioManager.stopBluetoothSco(); } + @Override public void messageCodecVersion(Mumble.CodecVersion msg) { + if (!mInitialized) + return; // Only listen to change events in this handler. + JumbleUDPMessageType codec; if (msg.hasOpus() && msg.getOpus()) { codec = JumbleUDPMessageType.UDPVoiceOpus; @@ -517,6 +583,22 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au } @Override + public void messageUserState(Mumble.UserState msg) { + if (!mInitialized) + return; // We shouldn't initialize on UserState- wait for ServerSync. + + // Stop audio input if the user is muted, and resume if the user has set talking enabled. + if (msg.hasSession() && msg.getSession() == mSession && + (msg.hasMute() || msg.hasSelfMute() || msg.hasSuppress())) { + try { + setServerMuted(msg.getMute() || msg.getSelfMute() || msg.getSuppress()); + } catch (AudioException e) { + e.printStackTrace(); + } + } + } + + @Override public void messageVoiceData(byte[] data, JumbleUDPMessageType messageType) { mOutput.queueVoiceData(data, messageType); } |