|
@@ -8,6 +8,7 @@
|
|
|
|
|
|
#include <boost/asio.hpp>
|
|
|
#include <boost/bind.hpp>
|
|
|
+#include <openssl/sha.h>
|
|
|
#include <log4cpp/Category.hh>
|
|
|
|
|
|
#include <Mumble.pb.h>
|
|
@@ -32,6 +33,10 @@ namespace mumlib {
|
|
|
|
|
|
int sessionId = 0;
|
|
|
int channelId = 0;
|
|
|
+ int64_t seq = 0;
|
|
|
+
|
|
|
+ std::vector<MumbleUser> listMumbleUser;
|
|
|
+ std::vector<MumbleChannel> listMumbleChannel;
|
|
|
|
|
|
_Mumlib_Private(Callback &callback, MumlibConfiguration &configuration)
|
|
|
: _Mumlib_Private(callback, *(new io_service()), configuration) {
|
|
@@ -43,7 +48,8 @@ namespace mumlib {
|
|
|
ioService(ioService),
|
|
|
externalIoService(true),
|
|
|
transport(ioService, boost::bind(&_Mumlib_Private::processIncomingTcpMessage, this, _1, _2, _3),
|
|
|
- boost::bind(&_Mumlib_Private::processAudioPacket, this, _1, _2, _3)) {
|
|
|
+ boost::bind(&_Mumlib_Private::processAudioPacket, this, _1, _2, _3)),
|
|
|
+ audio(configuration.opusSampleRate, configuration.opusEncoderBitrate, configuration.opusChannels) {
|
|
|
|
|
|
audio.setOpusEncoderBitrate(configuration.opusEncoderBitrate);
|
|
|
}
|
|
@@ -61,16 +67,26 @@ namespace mumlib {
|
|
|
|
|
|
if (type == AudioPacketType::OPUS) {
|
|
|
int16_t pcmData[5000];
|
|
|
- auto status = audio.decodeOpusPayload(incomingAudioPacket.audioPayload,
|
|
|
- incomingAudioPacket.audioPayloadLength,
|
|
|
- pcmData,
|
|
|
- 5000);
|
|
|
+
|
|
|
+ audio.addFrameToBuffer(incomingAudioPacket.audioPayload,
|
|
|
+ incomingAudioPacket.audioPayloadLength,
|
|
|
+ seq);
|
|
|
+
|
|
|
+ auto status = audio.decodeOpusPayload(pcmData, 5000);
|
|
|
+
|
|
|
+ // auto status = audio.decodeOpusPayload(incomingAudioPacket.audioPayload,
|
|
|
+ // incomingAudioPacket.audioPayloadLength,
|
|
|
+ // pcmData,
|
|
|
+ // 5000);
|
|
|
+
|
|
|
+ if(status.second) seq = 0; else seq++;
|
|
|
|
|
|
callback.audio(incomingAudioPacket.target,
|
|
|
- incomingAudioPacket.sessionId,
|
|
|
- incomingAudioPacket.sequenceNumber,
|
|
|
- pcmData,
|
|
|
- status.first);
|
|
|
+ incomingAudioPacket.sessionId,
|
|
|
+ incomingAudioPacket.sequenceNumber,
|
|
|
+ pcmData,
|
|
|
+ status.first);
|
|
|
+
|
|
|
} else {
|
|
|
logger.warn("Incoming audio packet doesn't contain Opus data, calling unsupportedAudio callback.");
|
|
|
callback.unsupportedAudio(incomingAudioPacket.target,
|
|
@@ -122,6 +138,11 @@ namespace mumlib {
|
|
|
case MessageType::CHANNELREMOVE: {
|
|
|
MumbleProto::ChannelRemove channelRemove;
|
|
|
channelRemove.ParseFromArray(buffer, length);
|
|
|
+
|
|
|
+ if(isListChannelContains(channelRemove.channel_id())) {
|
|
|
+ listChannelRemovedBy(channelRemove.channel_id());
|
|
|
+ }
|
|
|
+
|
|
|
callback.channelRemove(channelRemove.channel_id());
|
|
|
}
|
|
|
break;
|
|
@@ -152,7 +173,14 @@ namespace mumlib {
|
|
|
links_remove.push_back(channelState.links_remove(i));
|
|
|
}
|
|
|
|
|
|
- this->channelId = channel_id;
|
|
|
+ MumbleChannel mumbleChannel;
|
|
|
+ mumbleChannel.channelId = channel_id;
|
|
|
+ mumbleChannel.name = channelState.name();
|
|
|
+ mumbleChannel.description = channelState.description();
|
|
|
+
|
|
|
+ if(not isListChannelContains(channel_id)) {
|
|
|
+ listMumbleChannel.push_back(mumbleChannel);
|
|
|
+ }
|
|
|
|
|
|
callback.channelState(
|
|
|
channelState.name(),
|
|
@@ -175,6 +203,10 @@ namespace mumlib {
|
|
|
bool ban = user_remove.has_ban() ? user_remove.ban()
|
|
|
: false; //todo make sure it's correct to assume it's false
|
|
|
|
|
|
+ if(isListUserContains(user_remove.session())) {
|
|
|
+ listUserRemovedBy(user_remove.session());
|
|
|
+ }
|
|
|
+
|
|
|
callback.userRemove(
|
|
|
user_remove.session(),
|
|
|
actor,
|
|
@@ -200,6 +232,18 @@ namespace mumlib {
|
|
|
int32_t priority_speaker = userState.has_priority_speaker() ? userState.priority_speaker() : -1;
|
|
|
int32_t recording = userState.has_recording() ? userState.recording() : -1;
|
|
|
|
|
|
+ if(session == this->sessionId) {
|
|
|
+ this->channelId = channel_id;
|
|
|
+ }
|
|
|
+
|
|
|
+ MumbleUser mumbleUser;
|
|
|
+ mumbleUser.name = userState.name();
|
|
|
+ mumbleUser.sessionId = session;
|
|
|
+
|
|
|
+ if(not isListUserContains(session)) {
|
|
|
+ listMumbleUser.push_back(mumbleUser);
|
|
|
+ }
|
|
|
+
|
|
|
callback.userState(session,
|
|
|
actor,
|
|
|
userState.name(),
|
|
@@ -334,7 +378,37 @@ namespace mumlib {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ bool isListUserContains(int sessionId) {
|
|
|
+ for(int i = 0; i < listMumbleUser.size(); i++)
|
|
|
+ if(listMumbleUser[i].sessionId == sessionId)
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ void listUserRemovedBy(int sessionId) {
|
|
|
+ for(int i = 0; i < listMumbleUser.size(); i++) {
|
|
|
+ if(listMumbleUser[i].sessionId == sessionId) {
|
|
|
+ listMumbleUser.erase(listMumbleUser.begin() + i);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ bool isListChannelContains(int channelId) {
|
|
|
+ for(int i = 0; i < listMumbleChannel.size(); i++)
|
|
|
+ if(listMumbleChannel[i].channelId == channelId)
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
+ void listChannelRemovedBy(int channelId) {
|
|
|
+ for(int i = 0; i < listMumbleChannel.size(); i++) {
|
|
|
+ if(listMumbleChannel[i].channelId == channelId) {
|
|
|
+ listMumbleChannel.erase(listMumbleChannel.begin() + i);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
Mumlib::Mumlib(Callback &callback) {
|
|
@@ -363,13 +437,25 @@ namespace mumlib {
|
|
|
return impl->transport.getConnectionState();
|
|
|
}
|
|
|
|
|
|
+ int Mumlib::getChannelId() {
|
|
|
+ return impl->channelId;
|
|
|
+ }
|
|
|
+
|
|
|
+ vector<mumlib::MumbleUser> Mumlib::getListAllUser() {
|
|
|
+ return impl->listMumbleUser;
|
|
|
+ }
|
|
|
+
|
|
|
+ vector<mumlib::MumbleChannel> Mumlib::getListAllChannel() {
|
|
|
+ return impl->listMumbleChannel;
|
|
|
+ }
|
|
|
+
|
|
|
void Mumlib::connect(string host, int port, string user, string password) {
|
|
|
impl->transport.connect(host, port, user, password);
|
|
|
}
|
|
|
|
|
|
void Mumlib::disconnect() {
|
|
|
if (not impl->externalIoService) {
|
|
|
- impl->ioService.reset();
|
|
|
+ impl->ioService.stop();
|
|
|
}
|
|
|
if (impl->transport.getConnectionState() != ConnectionState::NOT_CONNECTED) {
|
|
|
impl->transport.disconnect();
|
|
@@ -385,8 +471,12 @@ namespace mumlib {
|
|
|
}
|
|
|
|
|
|
void Mumlib::sendAudioData(int16_t *pcmData, int pcmLength) {
|
|
|
+ Mumlib::sendAudioDataTarget(0, pcmData, pcmLength);
|
|
|
+ }
|
|
|
+
|
|
|
+ void Mumlib::sendAudioDataTarget(int targetId, int16_t *pcmData, int pcmLength) {
|
|
|
uint8_t encodedData[5000];
|
|
|
- int length = impl->audio.encodeAudioPacket(0, pcmData, pcmLength, encodedData, 5000);
|
|
|
+ int length = impl->audio.encodeAudioPacket(targetId, pcmData, pcmLength, encodedData, 5000);
|
|
|
impl->transport.sendEncodedAudioPacket(encodedData, length);
|
|
|
}
|
|
|
|
|
@@ -399,9 +489,146 @@ namespace mumlib {
|
|
|
}
|
|
|
|
|
|
void Mumlib::joinChannel(int channelId) {
|
|
|
+ if(!isChannelIdValid(channelId)) // when channel has not been registered / create
|
|
|
+ return;
|
|
|
MumbleProto::UserState userState;
|
|
|
userState.set_channel_id(channelId);
|
|
|
impl->transport.sendControlMessage(MessageType::USERSTATE, userState);
|
|
|
impl->channelId = channelId;
|
|
|
}
|
|
|
+
|
|
|
+ void Mumlib::joinChannel(string name) {
|
|
|
+ int channelId = Mumlib::getChannelIdBy(name);
|
|
|
+ Mumlib::joinChannel(channelId);
|
|
|
+ }
|
|
|
+
|
|
|
+ void Mumlib::sendVoiceTarget(int targetId, VoiceTargetType type, int id) {
|
|
|
+ MumbleProto::VoiceTarget voiceTarget;
|
|
|
+ MumbleProto::VoiceTarget_Target voiceTargetTarget;
|
|
|
+ switch(type) {
|
|
|
+ case VoiceTargetType::CHANNEL: {
|
|
|
+ voiceTargetTarget.set_channel_id(id);
|
|
|
+ voiceTargetTarget.set_children(true);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case VoiceTargetType::USER: {
|
|
|
+ voiceTargetTarget.add_session(id);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ voiceTarget.set_id(targetId);
|
|
|
+ voiceTarget.add_targets()->CopyFrom(voiceTargetTarget);
|
|
|
+ impl->transport.sendControlMessage(MessageType::VOICETARGET, voiceTarget);
|
|
|
+ }
|
|
|
+
|
|
|
+ void Mumlib::sendVoiceTarget(int targetId, VoiceTargetType type, string name, int &error) {
|
|
|
+ int id;
|
|
|
+ switch(type) {
|
|
|
+ case VoiceTargetType::CHANNEL:
|
|
|
+ id = getChannelIdBy(name);
|
|
|
+ break;
|
|
|
+ case VoiceTargetType::USER:
|
|
|
+ id = getUserIdBy(name);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ error = id < 0 ? 1: 0;
|
|
|
+ if(error) return;
|
|
|
+ sendVoiceTarget(targetId, type, id);
|
|
|
+ }
|
|
|
+
|
|
|
+ void Mumlib::sendUserState(mumlib::UserState field, bool val) {
|
|
|
+ MumbleProto::UserState userState;
|
|
|
+
|
|
|
+ switch (field) {
|
|
|
+ case UserState::MUTE:
|
|
|
+ userState.set_mute(val);
|
|
|
+ break;
|
|
|
+ case UserState::DEAF:
|
|
|
+ userState.set_deaf(val);
|
|
|
+ break;
|
|
|
+ case UserState::SUPPRESS:
|
|
|
+ userState.set_suppress(val);
|
|
|
+ break;
|
|
|
+ case UserState::SELF_MUTE:
|
|
|
+ userState.set_self_mute(val);
|
|
|
+ break;
|
|
|
+ case UserState::SELF_DEAF:
|
|
|
+ userState.set_self_deaf(val);
|
|
|
+ break;
|
|
|
+ case UserState::PRIORITY_SPEAKER:
|
|
|
+ userState.set_priority_speaker(val);
|
|
|
+ break;
|
|
|
+ case UserState::RECORDING:
|
|
|
+ userState.set_recording(val);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // in any other case, just ignore the command
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ impl->transport.sendControlMessage(MessageType::USERSTATE, userState);
|
|
|
+ }
|
|
|
+
|
|
|
+ void Mumlib::sendUserState(mumlib::UserState field, std::string val) {
|
|
|
+ MumbleProto::UserState userState;
|
|
|
+
|
|
|
+ // if comment longer than 128 bytes, we need to set the SHA1 hash
|
|
|
+ // http://www.askyb.com/cpp/openssl-sha1-hashing-example-in-cpp/
|
|
|
+ unsigned char digest[SHA_DIGEST_LENGTH];
|
|
|
+ char mdString[SHA_DIGEST_LENGTH * 2 + 1];
|
|
|
+
|
|
|
+ SHA1((unsigned char*) val.c_str(), val.size(), digest);
|
|
|
+ for(int i = 0; i < SHA_DIGEST_LENGTH; i++)
|
|
|
+ sprintf(&mdString[i*2], "%02x", (unsigned int) digest[i]);
|
|
|
+
|
|
|
+ switch (field) {
|
|
|
+ case UserState::COMMENT:
|
|
|
+ if(val.size() < 128)
|
|
|
+ userState.set_comment(val);
|
|
|
+ else
|
|
|
+ userState.set_comment_hash(mdString);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // in any other case, just ignore the command
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ impl->transport.sendControlMessage(MessageType::USERSTATE, userState);
|
|
|
+ }
|
|
|
+
|
|
|
+ int Mumlib::getChannelIdBy(string name) {
|
|
|
+ vector<mumlib::MumbleChannel> listMumbleChannel = impl->listMumbleChannel;
|
|
|
+ for(int i = 0; i < listMumbleChannel.size(); i++)
|
|
|
+ if(listMumbleChannel[i].name == name)
|
|
|
+ return listMumbleChannel[i].channelId;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ int Mumlib::getUserIdBy(string name) {
|
|
|
+ vector<mumlib::MumbleUser> listMumbleUser = impl->listMumbleUser;
|
|
|
+ for(int i = 0; i < listMumbleUser.size(); i++)
|
|
|
+ if(listMumbleUser[i].name == name)
|
|
|
+ return listMumbleUser[i].sessionId;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool Mumlib::isSessionIdValid(int sessionId) {
|
|
|
+ vector<mumlib::MumbleUser> listMumbleUser = impl->listMumbleUser;
|
|
|
+ for(int i = 0; i < listMumbleUser.size(); i++)
|
|
|
+ if(listMumbleUser[i].sessionId == sessionId)
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool Mumlib::isChannelIdValid(int channelId) {
|
|
|
+ vector<mumlib::MumbleChannel> listMumbleChannel = impl->listMumbleChannel;
|
|
|
+ for(int i = 0; i < listMumbleChannel.size(); i++)
|
|
|
+ if(listMumbleChannel[i].channelId == channelId)
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
}
|