diff options
author | Andrew Comminos <andrew@comminos.com> | 2016-04-29 23:22:09 +0300 |
---|---|---|
committer | Andrew Comminos <andrew@comminos.com> | 2016-04-30 00:30:29 +0300 |
commit | d2989367e745ca39362bc68a5502adebcaee6499 (patch) | |
tree | 3ef25896a04e40ebf1cac8ed987c2bbb9bc4db61 | |
parent | 169ee0abb9755efd907c237c49c0cdaa7b191107 (diff) |
Work on whisper target management.
11 files changed, 223 insertions, 42 deletions
diff --git a/src/main/java/com/morlunk/jumble/IJumbleService.java b/src/main/java/com/morlunk/jumble/IJumbleService.java index 529e7e6..90b5d23 100644 --- a/src/main/java/com/morlunk/jumble/IJumbleService.java +++ b/src/main/java/com/morlunk/jumble/IJumbleService.java @@ -25,6 +25,7 @@ import com.morlunk.jumble.model.WhisperTarget; import com.morlunk.jumble.net.JumbleUDPMessageType; import com.morlunk.jumble.util.IJumbleObserver; import com.morlunk.jumble.util.JumbleException; +import com.morlunk.jumble.util.VoiceTargetMode; import java.util.List; @@ -244,16 +245,16 @@ public interface IJumbleService { * Registers a whisper target to be used as a voice target on the server. * Note that Mumble only supports a maximum of 30 active voice targets at once. * @param target The target to register. - * @return A voice target ID in the range [1, 30]. + * @return A voice target ID in the range [1, 30], or a negative value if all slots are full. */ byte registerWhisperTarget(final WhisperTarget target); /** * Unregisters a whisper target from the server. * Note that Mumble only supports a maximum of 30 active voice targets at once. - * @param target The target to unregister. + * @param target The target ID to unregister. */ - void unregisterWhisperTarget(final WhisperTarget target); + void unregisterWhisperTarget(byte targetId); /** * Sets the active voice target to the provided ID.<br> @@ -262,7 +263,19 @@ public interface IJumbleService { * 31: Server loopback * @param targetId A voice target ID in the range [0, 31]. */ - void setVoiceTarget(byte targetId); + void setVoiceTargetId(byte targetId); + + /** + * Gets the current voice target ID in use, in the range [0, 31]. + * @return The active voice target ID. + */ + byte getVoiceTargetId(); + + /** + * Gets the current voice target mode. + * @return The active voice target mode. + */ + VoiceTargetMode getVoiceTargetMode(); /** * Returns the current whisper target. diff --git a/src/main/java/com/morlunk/jumble/JumbleService.java b/src/main/java/com/morlunk/jumble/JumbleService.java index f510ff1..65b5fd5 100644 --- a/src/main/java/com/morlunk/jumble/JumbleService.java +++ b/src/main/java/com/morlunk/jumble/JumbleService.java @@ -51,6 +51,7 @@ import com.morlunk.jumble.model.Server; import com.morlunk.jumble.model.TalkState; import com.morlunk.jumble.model.User; import com.morlunk.jumble.model.WhisperTarget; +import com.morlunk.jumble.model.WhisperTargetList; import com.morlunk.jumble.net.JumbleConnection; import com.morlunk.jumble.net.JumbleUDPMessageType; import com.morlunk.jumble.util.IJumbleObserver; @@ -61,11 +62,14 @@ import com.morlunk.jumble.protocol.AudioHandler; import com.morlunk.jumble.protocol.ModelHandler; import com.morlunk.jumble.util.JumbleCallbacks; import com.morlunk.jumble.util.JumbleLogger; +import com.morlunk.jumble.util.VoiceTargetMode; import java.security.Security; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Hashtable; import java.util.List; +import java.util.Queue; public class JumbleService extends Service implements IJumbleService, JumbleConnection.JumbleConnectionListener, JumbleLogger, BluetoothScoReceiver.Listener { @@ -131,6 +135,9 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn private AudioHandler.Builder mAudioBuilder; private int mTransmitMode; + private byte mVoiceTargetId; + private WhisperTargetList mWhisperTargetList; + private PowerManager.WakeLock mWakeLock; private Handler mHandler; private JumbleCallbacks mCallbacks; @@ -254,6 +261,7 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn mToggleInputMode = new ToggleInputMode(); mActivityInputMode = new ActivityInputMode(0); // FIXME: reasonable default mContinuousInputMode = new ContinuousInputMode(); + mWhisperTargetList = new WhisperTargetList(); } @Override @@ -270,6 +278,8 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn try { setReconnecting(false); mConnectionState = ConnectionState.DISCONNECTED; + mVoiceTargetId = 0; + mWhisperTargetList.clear(); mConnection = new JumbleConnection(this); mConnection.setForceTCP(mForceTcp); @@ -340,7 +350,8 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn try { mAudioHandler = mAudioBuilder.initialize( mModelHandler.getUser(mConnection.getSession()), - mConnection.getMaxBandwidth(), mConnection.getCodec()); + mConnection.getMaxBandwidth(), mConnection.getCodec(), + mVoiceTargetId); mConnection.addTCPMessageHandlers(mAudioHandler); mConnection.addUDPMessageHandlers(mAudioHandler); } catch (AudioException e) { @@ -382,6 +393,8 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn mModelHandler = null; mAudioHandler = null; + mVoiceTargetId = 0; + mWhisperTargetList.clear(); // Halt SCO connection on shutdown. mBluetoothReceiver.stopBluetoothSco(); @@ -467,7 +480,8 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn try { mAudioHandler = mAudioBuilder.initialize( mModelHandler.getUser(mConnection.getSession()), - mConnection.getMaxBandwidth(), mConnection.getCodec()); + mConnection.getMaxBandwidth(), mConnection.getCodec(), + mVoiceTargetId); mConnection.addTCPMessageHandlers(mAudioHandler); mConnection.addUDPMessageHandlers(mAudioHandler); } catch (NotSynchronizedException e) { @@ -1082,33 +1096,52 @@ public class JumbleService extends Service implements IJumbleService, JumbleConn getConnection().sendTCPMessage(csb.build(), JumbleTCPMessageType.ChannelState); } - /** - * Registers a whisper target to an unassigned ID on the server. - * @param target The whisper target to register. - * @return A free voice target ID in the range [1, 30]. - */ @Override public byte registerWhisperTarget(final WhisperTarget target) { + byte id = mWhisperTargetList.append(target); + if (id < 0) { + return -1; + } + Mumble.VoiceTarget.Target voiceTarget = target.createTarget(); Mumble.VoiceTarget.Builder vtb = Mumble.VoiceTarget.newBuilder(); - vtb.setId(1); // TODO: assign free ID. + vtb.setId(id); vtb.addTargets(voiceTarget); getConnection().sendTCPMessage(vtb.build(), JumbleTCPMessageType.VoiceTarget); - return 1; // FIXME + return id; } @Override - public void unregisterWhisperTarget(final WhisperTarget target) { - // TODO + public void unregisterWhisperTarget(byte targetId) { + mWhisperTargetList.free(targetId); } @Override - public void setVoiceTarget(byte targetId) { + public void setVoiceTargetId(byte targetId) { if ((targetId & ~0x1F) > 0) { throw new IllegalArgumentException("Target ID must be at most 5 bits."); } - // TODO: persist over handler recreation + mVoiceTargetId = targetId; mAudioHandler.setVoiceTargetId(targetId); + mCallbacks.onVoiceTargetChanged(VoiceTargetMode.fromId(targetId)); + } + + @Override + public byte getVoiceTargetId() { + return mVoiceTargetId; + } + + @Override + public VoiceTargetMode getVoiceTargetMode() { + return VoiceTargetMode.fromId(mVoiceTargetId); + } + + @Override + public WhisperTarget getWhisperTarget() { + if (VoiceTargetMode.fromId(mVoiceTargetId) == VoiceTargetMode.WHISPER) { + return mWhisperTargetList.get(mVoiceTargetId); + } + return null; } /** diff --git a/src/main/java/com/morlunk/jumble/model/WhisperTarget.java b/src/main/java/com/morlunk/jumble/model/WhisperTarget.java index 508c2a0..91039b3 100644 --- a/src/main/java/com/morlunk/jumble/model/WhisperTarget.java +++ b/src/main/java/com/morlunk/jumble/model/WhisperTarget.java @@ -24,4 +24,10 @@ import com.morlunk.jumble.protobuf.Mumble; */ public interface WhisperTarget { Mumble.VoiceTarget.Target createTarget(); + + /** + * Returns a user-readable name for the whisper target, to display in the UI. + * @return A channel name or list of users, depending on the implementation. + */ + String getName(); }
\ No newline at end of file diff --git a/src/main/java/com/morlunk/jumble/model/WhisperTargetChannel.java b/src/main/java/com/morlunk/jumble/model/WhisperTargetChannel.java index 56b805a..abe41b5 100644 --- a/src/main/java/com/morlunk/jumble/model/WhisperTargetChannel.java +++ b/src/main/java/com/morlunk/jumble/model/WhisperTargetChannel.java @@ -28,19 +28,7 @@ import java.util.List; * An abstraction around a channel whisper target. * Created by andrew on 28/04/16. */ -public class WhisperTargetChannel implements WhisperTarget, Parcelable { - public static final Parcelable.Creator<WhisperTargetChannel> CREATOR = new Creator<WhisperTargetChannel>() { - @Override - public WhisperTargetChannel createFromParcel(Parcel source) { - return null; - } - - @Override - public WhisperTargetChannel[] newArray(int size) { - return new WhisperTargetChannel[size]; - } - }; - +public class WhisperTargetChannel implements WhisperTarget { private final IChannel mChannel; private final boolean mIncludeLinked; private final boolean mIncludeSubchannels; @@ -59,18 +47,14 @@ public class WhisperTargetChannel implements WhisperTarget, Parcelable { Mumble.VoiceTarget.Target.Builder vtb = Mumble.VoiceTarget.Target.newBuilder(); vtb.setLinks(mIncludeLinked); vtb.setChildren(mIncludeSubchannels); - vtb.setGroup(mGroupRestriction); + if (mGroupRestriction != null) + vtb.setGroup(mGroupRestriction); vtb.setChannelId(mChannel.getId()); return vtb.build(); } @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - + public String getName() { + return mChannel.getName(); } } diff --git a/src/main/java/com/morlunk/jumble/model/WhisperTargetList.java b/src/main/java/com/morlunk/jumble/model/WhisperTargetList.java new file mode 100644 index 0000000..32f5761 --- /dev/null +++ b/src/main/java/com/morlunk/jumble/model/WhisperTargetList.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 Andrew Comminos <andrew@comminos.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.morlunk.jumble.model; + +import java.util.List; + +/** + * A simple implementation of a fixed-size whisper target list using a bit vector. + * Created by andrew on 29/04/16. + */ +public class WhisperTargetList { + public static final byte TARGET_MIN = 1; + public static final byte TARGET_MAX = 30; + + private final WhisperTarget[] mActiveTargets; + // Mumble stores voice targets using a 5-bit identifier. + // Use a bit vector to represent this 32-element range. + private int mTakenIds; + + public WhisperTargetList() { + mActiveTargets = new WhisperTarget[TARGET_MAX - TARGET_MIN + 1]; + clear(); + } + + /** + * Assigns the target to a slot. + * @param target The whisper target to assign. + * @return The slot number in range [1, 30]. + */ + public byte append(WhisperTarget target) { + byte freeId = -1; + for (byte i = TARGET_MIN; i < TARGET_MAX; i++) { + if ((mTakenIds & (1 << i)) == 0) { + freeId = i; + break; + } + } + if (freeId != -1) { + mActiveTargets[freeId - TARGET_MIN] = target; + } + + return freeId; + } + + public WhisperTarget get(byte id) { + if ((mTakenIds & (1 << id)) > 0) + return null; + return mActiveTargets[id - TARGET_MIN]; + } + + public void free(byte slot) { + if (slot < TARGET_MIN || slot > TARGET_MAX) + throw new IllegalArgumentException(); + + mTakenIds &= ~(1 << slot); + } + + public int spaceRemaining() { + int counter = 0; + for (byte i = TARGET_MIN; i < TARGET_MAX; i++) { + if ((mTakenIds & (1 << i)) == 0) { + counter++; + } + } + return counter; + } + + public void clear() { + // Slots 0 and 31 are non-whisper targets. + mTakenIds = 1 | (1 << 31); + } +} diff --git a/src/main/java/com/morlunk/jumble/model/WhisperTargetUsers.java b/src/main/java/com/morlunk/jumble/model/WhisperTargetUsers.java index 5bb26a8..29537c7 100644 --- a/src/main/java/com/morlunk/jumble/model/WhisperTargetUsers.java +++ b/src/main/java/com/morlunk/jumble/model/WhisperTargetUsers.java @@ -27,4 +27,9 @@ public class WhisperTargetUsers implements WhisperTarget { public Mumble.VoiceTarget.Target createTarget() { throw new UnsupportedOperationException(); // TODO } + + @Override + public String getName() { + throw new UnsupportedOperationException(); // TODO + } } diff --git a/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java b/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java index 4bd4e79..357d613 100644 --- a/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java +++ b/src/main/java/com/morlunk/jumble/protocol/AudioHandler.java @@ -92,7 +92,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, - IInputMode inputMode, float amplitudeBoost, + IInputMode inputMode, byte targetId, float amplitudeBoost, boolean bluetoothEnabled, boolean halfDuplexEnabled, boolean preprocessorEnabled, AudioEncodeListener encodeListener, AudioOutput.AudioOutputListener outputListener) throws AudioInitializationException, NativeAudioException { @@ -111,7 +111,7 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au mEncodeListener = encodeListener; mOutputListener = outputListener; mTalking = false; - mTargetId = 0; + mTargetId = targetId; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mEncoderLock = new Object(); @@ -581,9 +581,9 @@ public class AudioHandler extends JumbleNetworkListener implements AudioInput.Au * 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 { + public AudioHandler initialize(User self, int maxBandwidth, JumbleUDPMessageType codec, byte targetId) throws AudioException { AudioHandler handler = new AudioHandler(mContext, mLogger, mAudioStream, mAudioSource, - mInputSampleRate, mTargetBitrate, mTargetFramesPerPacket, mInputMode, + mInputSampleRate, mTargetBitrate, mTargetFramesPerPacket, mInputMode, targetId, mAmplitudeBoost, mBluetoothEnabled, mHalfDuplexEnabled, mPreprocessorEnabled, mEncodeListener, mTalkingListener); handler.initialize(self, maxBandwidth, codec); diff --git a/src/main/java/com/morlunk/jumble/util/IJumbleObserver.java b/src/main/java/com/morlunk/jumble/util/IJumbleObserver.java index d167da3..f037f16 100644 --- a/src/main/java/com/morlunk/jumble/util/IJumbleObserver.java +++ b/src/main/java/com/morlunk/jumble/util/IJumbleObserver.java @@ -57,6 +57,8 @@ public interface IJumbleObserver { void onMessageLogged(IMessage message); + void onVoiceTargetChanged(VoiceTargetMode mode); + void onLogInfo(String message); void onLogWarning(String message); diff --git a/src/main/java/com/morlunk/jumble/util/JumbleCallbacks.java b/src/main/java/com/morlunk/jumble/util/JumbleCallbacks.java index 192c98f..5d1aa71 100644 --- a/src/main/java/com/morlunk/jumble/util/JumbleCallbacks.java +++ b/src/main/java/com/morlunk/jumble/util/JumbleCallbacks.java @@ -154,6 +154,13 @@ public class JumbleCallbacks implements IJumbleObserver { } @Override + public void onVoiceTargetChanged(VoiceTargetMode mode) { + for (IJumbleObserver observer : mCallbacks) { + observer.onVoiceTargetChanged(mode); + } + } + + @Override public void onLogInfo(String message) { for (IJumbleObserver observer : mCallbacks) { observer.onLogInfo(message); diff --git a/src/main/java/com/morlunk/jumble/util/JumbleObserver.java b/src/main/java/com/morlunk/jumble/util/JumbleObserver.java index 91f2751..68c5faf 100644 --- a/src/main/java/com/morlunk/jumble/util/JumbleObserver.java +++ b/src/main/java/com/morlunk/jumble/util/JumbleObserver.java @@ -104,6 +104,11 @@ public class JumbleObserver implements IJumbleObserver { } @Override + public void onVoiceTargetChanged(VoiceTargetMode mode) { + + } + + @Override public void onLogInfo(String message) { } diff --git a/src/main/java/com/morlunk/jumble/util/VoiceTargetMode.java b/src/main/java/com/morlunk/jumble/util/VoiceTargetMode.java new file mode 100644 index 0000000..6d7eb65 --- /dev/null +++ b/src/main/java/com/morlunk/jumble/util/VoiceTargetMode.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 Andrew Comminos <andrew@comminos.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.morlunk.jumble.util; + +/** + * Created by andrew on 29/04/16. + */ +public enum VoiceTargetMode { + NORMAL, + WHISPER, + SERVER_LOOPBACK; + + public static VoiceTargetMode fromId(byte targetId) { + if (targetId == 0) { + return VoiceTargetMode.NORMAL; + } else if (targetId > 0 && targetId < 31) { + return VoiceTargetMode.WHISPER; + } else if (targetId == 31) { + return VoiceTargetMode.SERVER_LOOPBACK; + } else { + throw new IllegalArgumentException(); + } + } +} |