Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Comminos <andrew@morlunk.com>2015-03-20 02:49:28 +0300
committerAndrew Comminos <andrew@morlunk.com>2015-03-20 02:49:28 +0300
commit8afb5629012e2a1fef813fb11c002c227aedbd46 (patch)
tree722e41b39703cbcbc338ec51f0efcbfb0cf94745
parentdc9a8f8aa589a8f612523d055508978d894ebce3 (diff)
New AudioHandler talking and mute/deafen mechanics.
Should maintain audio state better.
-rw-r--r--src/main/java/com/morlunk/jumble/JumbleService.java36
-rw-r--r--src/main/java/com/morlunk/jumble/audio/AudioInput.java4
-rw-r--r--src/main/java/com/morlunk/jumble/net/JumbleConnection.java10
-rw-r--r--src/main/java/com/morlunk/jumble/protocol/AudioHandler.java108
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);
}