diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | bcb704366cb5e333a626c18c308c7e0448a8e69f (patch) | |
tree | f0d6ab7d78ecdd9207cf46536376b44b91a1ca71 /kopete/protocols/jabber/jingle/libjingle/talk/session/phone | |
download | tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.tar.gz tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kopete/protocols/jabber/jingle/libjingle/talk/session/phone')
17 files changed, 2493 insertions, 0 deletions
diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am new file mode 100644 index 00000000..b2acbf81 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am @@ -0,0 +1,18 @@ +libcricketsessionphone_la_SOURCES = audiomonitor.cc \ + channelmanager.cc \ + voicechannel.cc \ + call.cc \ + phonesessionclient.cc \ + linphonemediaengine.cc + +noinst_HEADERS = audiomonitor.h \ + channelmanager.h \ + linphonemediaengine.h \ + mediaengine.h \ + phonesessionclient.h \ + voicechannel.h \ + call.h \ + mediachannel.h + +AM_CPPFLAGS = -DPOSIX $(ORTP_CFLAGS) $(ILBC_CFLAGS) -I$(srcdir)/../../../talk/third_party/mediastreamer -I$(srcdir)/../../.. $(GLIB_CFLAGS) $(SPEEX_CFLAGS) +noinst_LTLIBRARIES = libcricketsessionphone.la diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc new file mode 100644 index 00000000..c1b63d1b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc @@ -0,0 +1,119 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/session/phone/audiomonitor.h" +#include "talk/session/phone/voicechannel.h" +#include <cassert> + +namespace cricket { + +const uint32 MSG_MONITOR_POLL = 1; +const uint32 MSG_MONITOR_START = 2; +const uint32 MSG_MONITOR_STOP = 3; +const uint32 MSG_MONITOR_SIGNAL = 4; + +AudioMonitor::AudioMonitor(VoiceChannel *voice_channel, Thread *monitor_thread) { + voice_channel_ = voice_channel; + monitoring_thread_ = monitor_thread; + monitoring_ = false; +} + +AudioMonitor::~AudioMonitor() { + voice_channel_->worker_thread()->Clear(this); + monitoring_thread_->Clear(this); +} + +void AudioMonitor::Start(int milliseconds) { + rate_ = milliseconds; + if (rate_ < 100) + rate_ = 100; + voice_channel_->worker_thread()->Post(this, MSG_MONITOR_START); +} + +void AudioMonitor::Stop() { + voice_channel_->worker_thread()->Post(this, MSG_MONITOR_STOP); +} + +void AudioMonitor::OnMessage(Message *message) { + CritScope cs(&crit_); + + switch (message->message_id) { + case MSG_MONITOR_START: + assert(Thread::Current() == voice_channel_->worker_thread()); + if (!monitoring_) { + monitoring_ = true; + PollVoiceChannel(); + } + break; + + case MSG_MONITOR_STOP: + assert(Thread::Current() == voice_channel_->worker_thread()); + if (monitoring_) { + monitoring_ = false; + voice_channel_->worker_thread()->Clear(this); + } + break; + + case MSG_MONITOR_POLL: + assert(Thread::Current() == voice_channel_->worker_thread()); + PollVoiceChannel(); + break; + + case MSG_MONITOR_SIGNAL: + { + assert(Thread::Current() == monitoring_thread_); + AudioInfo info = audio_info_; + crit_.Leave(); + SignalUpdate(this, audio_info_); + crit_.Enter(); + } + break; + } +} + +void AudioMonitor::PollVoiceChannel() { + CritScope cs(&crit_); + assert(Thread::Current() == voice_channel_->worker_thread()); + + // Gather connection infos + audio_info_.input_level = voice_channel_->GetInputLevel_w(); + audio_info_.output_level = voice_channel_->GetOutputLevel_w(); + + // Signal the monitoring thread, start another poll timer + monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL); + voice_channel_->worker_thread()->PostDelayed(rate_, this, MSG_MONITOR_POLL); +} + +VoiceChannel *AudioMonitor::voice_channel() { + return voice_channel_; +} + +Thread *AudioMonitor::monitor_thread() { + return monitoring_thread_; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h new file mode 100644 index 00000000..96b95bd7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CRICKET_PHONE_AUDIOMONITOR_H_ +#define _CRICKET_PHONE_AUDIOMONITOR_H_ + +#include "talk/base/thread.h" +#include "talk/base/sigslot.h" +#include "talk/p2p/base/port.h" +#include <vector> + +namespace cricket { + +class VoiceChannel; + + +struct AudioInfo { + int input_level; + int output_level; +}; + +class AudioMonitor : public MessageHandler, public sigslot::has_slots<> { +public: + AudioMonitor(VoiceChannel* voice_channel, Thread *monitor_thread); + ~AudioMonitor(); + + void Start(int cms); + void Stop(); + + VoiceChannel* voice_channel(); + Thread *monitor_thread(); + + sigslot::signal2<AudioMonitor*, const AudioInfo&> SignalUpdate; + +protected: + void OnMessage(Message *message); + void PollVoiceChannel(); + + AudioInfo audio_info_; + VoiceChannel* voice_channel_; + Thread* monitoring_thread_; + CriticalSection crit_; + uint32 rate_; + bool monitoring_; +}; + +} + +#endif // _CRICKET_PHONE_AUDIOMONITOR_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc new file mode 100644 index 00000000..31b12e92 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc @@ -0,0 +1,258 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/thread.h" +#include "talk/p2p/base/helpers.h" +#include "talk/session/phone/call.h" + +namespace cricket { + +const uint32 MSG_CHECKAUTODESTROY = 1; + +Call::Call(PhoneSessionClient *session_client) : muted_(false) { + session_client_ = session_client; + id_ = CreateRandomId(); +} + +Call::~Call() { + while (sessions_.begin() != sessions_.end()) { + Session *session = sessions_[0]; + RemoveSession(session); + session_client_->session_manager()->DestroySession(session); + } + Thread::Current()->Clear(this); +} + +Session *Call::InitiateSession(const buzz::Jid &jid) { + Session *session = session_client_->CreateSession(this); + AddSession(session); + session->Initiate(jid.Str(), session_client_->CreateOfferSessionDescription()); + return session; +} + +void Call::AcceptSession(Session *session) { + std::vector<Session *>::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + assert(it != sessions_.end()); + if (it != sessions_.end()) + session->Accept(session_client_->CreateAcceptSessionDescription(session->remote_description())); +} + +void Call::RedirectSession(Session *session, const buzz::Jid &to) { + std::vector<Session *>::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + assert(it != sessions_.end()); + if (it != sessions_.end()) + session->Redirect(to.Str()); +} + +void Call::RejectSession(Session *session) { + std::vector<Session *>::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + assert(it != sessions_.end()); + if (it != sessions_.end()) + session->Reject(); +} + +void Call::TerminateSession(Session *session) { + assert(std::find(sessions_.begin(), sessions_.end(), session) != sessions_.end()); + std::vector<Session *>::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + if (it != sessions_.end()) + (*it)->Terminate(); +} + +void Call::Terminate() { + // There may be more than one session to terminate + std::vector<Session *>::iterator it = sessions_.begin(); + for (it = sessions_.begin(); it != sessions_.end(); it++) + TerminateSession(*it); +} + +void Call::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CHECKAUTODESTROY: + // If no more sessions for this call, delete it + if (sessions_.size() == 0) + session_client_->DestroyCall(this); + break; + } +} + +const std::vector<Session *> &Call::sessions() { + return sessions_; +} + +void Call::AddSession(Session *session) { + // Add session to list, create voice channel for this session + sessions_.push_back(session); + session->SignalState.connect(this, &Call::OnSessionState); + session->SignalError.connect(this, &Call::OnSessionError); + + VoiceChannel *channel = session_client_->channel_manager()->CreateVoiceChannel(session); + channel_map_[session->id()] = channel; + + // If this call has the focus, enable this channel + if (session_client_->GetFocus() == this) + channel->Enable(true); + + // Signal client + SignalAddSession(this, session); +} + +void Call::RemoveSession(Session *session) { + // Remove session from list + std::vector<Session *>::iterator it_session; + it_session = std::find(sessions_.begin(), sessions_.end(), session); + if (it_session == sessions_.end()) + return; + sessions_.erase(it_session); + + // Destroy session channel + std::map<SessionID, VoiceChannel *>::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = it_channel->second; + channel_map_.erase(it_channel); + session_client_->channel_manager()->DestroyVoiceChannel(channel); + } + + // Signal client + SignalRemoveSession(this, session); + + // The call auto destroys when the lass session is removed + Thread::Current()->Post(this, MSG_CHECKAUTODESTROY); +} + +VoiceChannel* Call::GetChannel(Session* session) { + std::map<SessionID, VoiceChannel *>::iterator it = channel_map_.find(session->id()); + assert(it != channel_map_.end()); + return it->second; +} + +void Call::EnableChannels(bool enable) { + std::vector<Session *>::iterator it; + for (it = sessions_.begin(); it != sessions_.end(); it++) { + VoiceChannel *channel = channel_map_[(*it)->id()]; + if (channel != NULL) + channel->Enable(enable); + } +} + +void Call::Mute(bool mute) { + muted_ = mute; + std::vector<Session *>::iterator it; + for (it = sessions_.begin(); it != sessions_.end(); it++) { + VoiceChannel *channel = channel_map_[(*it)->id()]; + if (channel != NULL) + channel->Mute(mute); + } +} + +void Call::Join(Call *call, bool enable) { + while (call->sessions_.size() != 0) { + // Move session + Session *session = call->sessions_[0]; + call->sessions_.erase(call->sessions_.begin()); + sessions_.push_back(session); + session->SignalState.connect(this, &Call::OnSessionState); + session->SignalError.connect(this, &Call::OnSessionError); + + // Move channel + std::map<SessionID, VoiceChannel *>::iterator it_channel; + it_channel = call->channel_map_.find(session->id()); + if (it_channel != call->channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + call->channel_map_.erase(it_channel); + channel_map_[session->id()] = channel; + channel->Enable(enable); + } + } +} + +void Call::StartConnectionMonitor(Session *session, int cms) { + std::map<SessionID, VoiceChannel *>::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->SignalConnectionMonitor.connect(this, &Call::OnConnectionMonitor); + channel->StartConnectionMonitor(cms); + } +} + +void Call::StopConnectionMonitor(Session *session) { + std::map<SessionID, VoiceChannel *>::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->StopConnectionMonitor(); + channel->SignalConnectionMonitor.disconnect(this); + } +} + +void Call::StartAudioMonitor(Session *session, int cms) { + std::map<SessionID, VoiceChannel *>::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->SignalAudioMonitor.connect(this, &Call::OnAudioMonitor); + channel->StartAudioMonitor(cms); + } +} + +void Call::StopAudioMonitor(Session *session) { + std::map<SessionID, VoiceChannel *>::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->StopAudioMonitor(); + channel->SignalAudioMonitor.disconnect(this); + } +} + + +void Call::OnConnectionMonitor(VoiceChannel *channel, const std::vector<ConnectionInfo> &infos) { + SignalConnectionMonitor(this, channel->session(), infos); +} + +void Call::OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info) { + SignalAudioMonitor(this, channel->session(), info); +} + +uint32 Call::id() { + return id_; +} + +void Call::OnSessionState(Session *session, Session::State state) { + SignalSessionState(this, session, state); +} + +void Call::OnSessionError(Session *session, Session::Error error) { + SignalSessionError(this, session, error); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h new file mode 100644 index 00000000..209e13c9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h @@ -0,0 +1,97 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CALL_H_ +#define _CALL_H_ + +#include "talk/base/messagequeue.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/client/socketmonitor.h" +#include "talk/xmpp/jid.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/session/phone/voicechannel.h" +#include "talk/session/phone/audiomonitor.h" + +#include <map> +#include <vector> + +namespace cricket { + +class PhoneSessionClient; + +class Call : public MessageHandler, public sigslot::has_slots<> { +public: + Call(PhoneSessionClient *session_client); + ~Call(); + + Session *InitiateSession(const buzz::Jid &jid); + void AcceptSession(Session *session); + void RedirectSession(Session *session, const buzz::Jid &to); + void RejectSession(Session *session); + void TerminateSession(Session *session); + void Terminate(); + void StartConnectionMonitor(Session *session, int cms); + void StopConnectionMonitor(Session *session); + void StartAudioMonitor(Session *session, int cms); + void StopAudioMonitor(Session *session); + void Mute(bool mute); + + const std::vector<Session *> &sessions(); + uint32 id(); + bool muted() const { return muted_; } + + sigslot::signal2<Call *, Session *> SignalAddSession; + sigslot::signal2<Call *, Session *> SignalRemoveSession; + sigslot::signal3<Call *, Session *, Session::State> SignalSessionState; + sigslot::signal3<Call *, Session *, Session::Error> SignalSessionError; + sigslot::signal3<Call *, Session *, const std::vector<ConnectionInfo> &> SignalConnectionMonitor; + sigslot::signal3<Call *, Session *, const AudioInfo&> SignalAudioMonitor; + +private: + void OnMessage(Message *message); + void OnSessionState(Session *session, Session::State state); + void OnSessionError(Session *session, Session::Error error); + void AddSession(Session *session); + void RemoveSession(Session *session); + void EnableChannels(bool enable); + void Join(Call *call, bool enable); + void OnConnectionMonitor(VoiceChannel *channel, const std::vector<ConnectionInfo> &infos); + void OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info); + VoiceChannel* GetChannel(Session* session); + + uint32 id_; + PhoneSessionClient *session_client_; + std::vector<Session *> sessions_; + std::map<SessionID, VoiceChannel *> channel_map_; + bool muted_; + + friend class PhoneSessionClient; +}; + +} + +#endif // _CALL_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc new file mode 100644 index 00000000..98634b12 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc @@ -0,0 +1,203 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_GIPS +#include "talk/session/phone/gipsmediaengine.h" +#else +#include "talk/session/phone/linphonemediaengine.h" +#endif +#include "channelmanager.h" +#include <cassert> +#include <iostream> +namespace cricket { + +const uint32 MSG_CREATEVOICECHANNEL = 1; +const uint32 MSG_DESTROYVOICECHANNEL = 2; +const uint32 MSG_SETAUDIOOPTIONS = 3; + +ChannelManager::ChannelManager(Thread *worker_thread) { +#ifdef HAVE_GIPS + media_engine_ = new GipsMediaEngine(); +#else + media_engine_ = new LinphoneMediaEngine(); +#endif + worker_thread_ = worker_thread; + initialized_ = false; + Init(); +} + +ChannelManager::~ChannelManager() { + Exit(); +} + +MediaEngine *ChannelManager::media_engine() { + return media_engine_; +} + +bool ChannelManager::Init() { + initialized_ = media_engine_->Init(); + return initialized_; +} + +void ChannelManager::Exit() { + if (!initialized_) + return; + + // Need to destroy the voice channels + + while (true) { + crit_.Enter(); + VoiceChannel *channel = NULL; + if (channels_.begin() != channels_.end()) + channel = channels_[0]; + crit_.Leave(); + if (channel == NULL) + break; + delete channel; + } + media_engine_->Terminate(); +} + +struct CreateParams { + Session *session; + VoiceChannel *channel; +}; + +VoiceChannel *ChannelManager::CreateVoiceChannel(Session *session) { + CreateParams params; + params.session = session; + params.channel = NULL; + TypedMessageData<CreateParams *> data(¶ms); + worker_thread_->Send(this, MSG_CREATEVOICECHANNEL, &data); + return params.channel; +} + +VoiceChannel *ChannelManager::CreateVoiceChannel_w(Session *session) { + CritScope cs(&crit_); + + // This is ok to alloc from a thread other than the worker thread + assert(initialized_); + MediaChannel *channel = media_engine_->CreateChannel(); + if (channel == NULL) + return NULL; + + VoiceChannel *voice_channel = new VoiceChannel(this, session, channel); + channels_.push_back(voice_channel); + return voice_channel; +} + +void ChannelManager::DestroyVoiceChannel(VoiceChannel *voice_channel) { + TypedMessageData<VoiceChannel *> data(voice_channel); + worker_thread_->Send(this, MSG_DESTROYVOICECHANNEL, &data); +} + +void ChannelManager::DestroyVoiceChannel_w(VoiceChannel *voice_channel) { + CritScope cs(&crit_); + // Destroy voice channel. + assert(initialized_); + std::vector<VoiceChannel *>::iterator it = std::find(channels_.begin(), + channels_.end(), voice_channel); + assert(it != channels_.end()); + if (it == channels_.end()) + return; + + channels_.erase(it); + MediaChannel *channel = voice_channel->channel(); + delete voice_channel; + delete channel; +} + +void ChannelManager::SetAudioOptions(bool auto_gain_control, int wave_in_device, + int wave_out_device) { + AudioOptions options; + options.auto_gain_control = auto_gain_control; + options.wave_in_device = wave_in_device; + options.wave_out_device = wave_out_device; + TypedMessageData<AudioOptions> data(options); + worker_thread_->Send(this, MSG_SETAUDIOOPTIONS, &data); +} + +void ChannelManager::SetAudioOptions_w(AudioOptions options) { + assert(worker_thread_ == Thread::Current()); + + // Set auto gain control on + if (media_engine_->SetAudioOptions(options.auto_gain_control?MediaEngine::AUTO_GAIN_CONTROL:0) != 0) { + // TODO: We need to log these failures. + } + + // Set the audio devices + // This will fail if audio is already playing. Stop all of the media + // start it up again after changing the setting. + { + CritScope cs(&crit_); + for (VoiceChannels::iterator it = channels_.begin(); + it < channels_.end(); + ++it) { + (*it)->PauseMedia_w(); + } + + if (media_engine_->SetSoundDevices(options.wave_in_device, options.wave_out_device) == -1) { + // TODO: We need to log these failures. + } + + for (VoiceChannels::iterator it = channels_.begin(); + it < channels_.end(); + ++it) { + (*it)->UnpauseMedia_w(); + } + } +} + +Thread *ChannelManager::worker_thread() { + return worker_thread_; +} + +void ChannelManager::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CREATEVOICECHANNEL: + { + TypedMessageData<CreateParams *> *data = static_cast<TypedMessageData<CreateParams *> *>(message->pdata); + data->data()->channel = CreateVoiceChannel_w(data->data()->session); + } + break; + + case MSG_DESTROYVOICECHANNEL: + { + TypedMessageData<VoiceChannel *> *data = static_cast<TypedMessageData<VoiceChannel *> *>(message->pdata); + DestroyVoiceChannel_w(data->data()); + } + break; + case MSG_SETAUDIOOPTIONS: + { + TypedMessageData<AudioOptions> *data = static_cast<TypedMessageData<AudioOptions> *>(message->pdata); + SetAudioOptions_w(data->data()); + } + break; + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h new file mode 100644 index 00000000..7200f75e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h @@ -0,0 +1,81 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CHANNELMANAGER_H_ +#define _CHANNELMANAGER_H_ + +#include "talk/base/thread.h" +#include "talk/base/criticalsection.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/session/phone/voicechannel.h" +#include "talk/session/phone/mediaengine.h" +#include <vector> + +namespace cricket { + +class VoiceChannel; + +class ChannelManager : public MessageHandler { +public: + ChannelManager(Thread *worker_thread); + ~ChannelManager(); + + VoiceChannel *CreateVoiceChannel(Session *session); + void DestroyVoiceChannel(VoiceChannel *voice_channel); + void SetAudioOptions(bool auto_gain_control, int wave_in_device, + int wave_out_device); + + MediaEngine *media_engine(); + Thread *worker_thread(); + +private: + VoiceChannel *CreateVoiceChannel_w(Session *session); + void DestroyVoiceChannel_w(VoiceChannel *voice_channel); + void OnMessage(Message *message); + bool Init(); + void Exit(); + + struct AudioOptions { + bool auto_gain_control; + int wave_in_device; + int wave_out_device; + }; + void SetAudioOptions_w(AudioOptions options); + + Thread *worker_thread_; + MediaEngine *media_engine_; + bool initialized_; + CriticalSection crit_; + + typedef std::vector<VoiceChannel*> VoiceChannels; + VoiceChannels channels_; +}; + +} + +#endif // _CHANNELMANAGER_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc new file mode 100644 index 00000000..7d2305dc --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc @@ -0,0 +1,170 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// LinphoneMediaEngine is a Linphone implementation of MediaEngine +extern "C" { +#include "talk/third_party/mediastreamer/mediastream.h" +#ifdef HAVE_ILBC +#include "talk/third_party/mediastreamer/msilbcdec.h" +#endif +#ifdef HAVE_SPEEX +#include "talk/third_party/mediastreamer/msspeexdec.h" +#endif +} +#include <ortp/ortp.h> +#include <netdb.h> +#include <unistd.h> +#include <fcntl.h> +#include <iostream> +#include "talk/session/phone/linphonemediaengine.h" + +using namespace cricket; + +void *thread_function(void *data) +{ + LinphoneMediaChannel *mc =(LinphoneMediaChannel*) data; + while (mc->dying() == false) { + MediaChannel::NetworkInterface *iface = mc->network_interface(); + char *buf[2048]; + int len; + len = read(mc->fd(), buf, sizeof(buf)); + if (iface && (mc->mute()==FALSE)) + iface->SendPacket(buf, len); + } +} + +LinphoneMediaChannel::LinphoneMediaChannel() { + pt_ = 102; + dying_ = false; + pthread_attr_t attr; + audio_stream_ = NULL; + + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = INADDR_ANY; + sockaddr.sin_port = htons(3000); + fd_ = socket(PF_INET, SOCK_DGRAM, 0); + fcntl(fd_, F_SETFL, 0, O_NONBLOCK); + bind (fd_,(struct sockaddr*)&sockaddr, sizeof(sockaddr)); + + pthread_attr_init(&attr); + pthread_create(&thread_, &attr, &thread_function, this); +} + +LinphoneMediaChannel::~LinphoneMediaChannel() { + dying_ = true; + pthread_join(thread_, NULL); + audio_stream_stop(audio_stream_); + close(fd_); +} + +void LinphoneMediaChannel::SetCodec(const char *codec) { + if (!strcmp(codec, "iLBC")) + pt_ = 102; + else if (!strcmp(codec, "speex")) + pt_ = 110; + else + pt_ = 0; + if (audio_stream_) + audio_stream_stop(audio_stream_); + audio_stream_ = audio_stream_start(&av_profile, 2000, "127.0.0.1", 3000, pt_, 250); +} + +void LinphoneMediaChannel::OnPacketReceived(const void *data, int len) { + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + struct hostent *host = gethostbyname("localhost"); + memcpy(&sockaddr.sin_addr.s_addr, host->h_addr, host->h_length); + sockaddr.sin_port = htons(2000); + + char buf[2048]; + memcpy(buf, data, len); + + if (buf[1] == pt_) { + } else if (buf[1] == 13) { + } else if (buf[1] == 102) { + SetCodec("iLBC"); + } else if (buf[1] == 110) { + SetCodec("speex"); + } else if (buf[1] == 0) { + SetCodec("PCMU"); + } + + if (play_ && buf[1] != 13) + sendto(fd_, buf, len, 0, (struct sockaddr*)&sockaddr, sizeof(sockaddr)); +} + +void LinphoneMediaChannel::SetPlayout(bool playout) { + play_ = playout; +} + +void LinphoneMediaChannel::SetSend(bool send) { + mute_ = !send; +} + +float LinphoneMediaChannel::GetCurrentQuality() {} +int LinphoneMediaChannel::GetOutputLevel() {} + +LinphoneMediaEngine::LinphoneMediaEngine() {} +LinphoneMediaEngine::~LinphoneMediaEngine() {} + +static void null_log_handler(const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) { +} + +bool LinphoneMediaEngine::Init() { + g_log_set_handler("MediaStreamer", G_LOG_LEVEL_MASK, null_log_handler, NULL); + g_log_set_handler("oRTP", G_LOG_LEVEL_MASK, null_log_handler, NULL); + g_log_set_handler("oRTP-stats", G_LOG_LEVEL_MASK, null_log_handler, NULL); + ortp_init(); + ms_init(); + +#ifdef HAVE_SPEEX + ms_speex_codec_init(); + rtp_profile_set_payload(&av_profile, 110, &speex_wb); + codecs_.push_back(Codec(110, "speex", 8)); +#endif + +#ifdef HAVE_ILBC + ms_ilbc_codec_init(); + rtp_profile_set_payload(&av_profile, 102, &payload_type_ilbc); + codecs_.push_back(Codec(102, "iLBC", 4)); +#endif + + rtp_profile_set_payload(&av_profile, 0, &pcmu8000); + codecs_.push_back(Codec(0, "PCMU", 2)); + +return true; +} + +void LinphoneMediaEngine::Terminate() { + +} + +MediaChannel *LinphoneMediaEngine::CreateChannel() { + return new LinphoneMediaChannel(); +} + +int LinphoneMediaEngine::SetAudioOptions(int options) {} +int LinphoneMediaEngine::SetSoundDevices(int wave_in_device, int wave_out_device) {} + +float LinphoneMediaEngine::GetCurrentQuality() {} +int LinphoneMediaEngine::GetInputLevel() {} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h new file mode 100644 index 00000000..ee16d2ee --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h @@ -0,0 +1,75 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// LinphoneMediaEngine is a Linphone implementation of MediaEngine + +#ifndef TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_ +#define TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_ + +extern "C" { +#include "talk/third_party/mediastreamer/mediastream.h" +} +#include "talk/session/phone/mediaengine.h" + +namespace cricket { + +class LinphoneMediaChannel : public MediaChannel { + public: + LinphoneMediaChannel(); + virtual ~LinphoneMediaChannel(); + virtual void SetCodec(const char *codec); + virtual void OnPacketReceived(const void *data, int len); + + virtual void SetPlayout(bool playout); + virtual void SetSend(bool send); + + virtual float GetCurrentQuality(); + virtual int GetOutputLevel(); + int fd() {return fd_;} + bool mute() {return mute_;} + bool dying() {return dying_;} + private: + AudioStream *audio_stream_; + pthread_t thread_; + int fd_; + int pt_; + bool dying_; + bool mute_; + bool play_; +}; + +class LinphoneMediaEngine : public MediaEngine { + public: + LinphoneMediaEngine(); + ~LinphoneMediaEngine(); + virtual bool Init(); + virtual void Terminate(); + + virtual MediaChannel *CreateChannel(); + + virtual int SetAudioOptions(int options); + virtual int SetSoundDevices(int wave_in_device, int wave_out_device); + + virtual float GetCurrentQuality(); + virtual int GetInputLevel(); +}; + +} // namespace cricket + +#endif // TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h new file mode 100644 index 00000000..db2f9654 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h @@ -0,0 +1,55 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_SESSION_PHONE_MEDIACHANNEL_H_ +#define TALK_SESSION_PHONE_MEDIACHANNEL_H_ + +namespace cricket { + +class MediaChannel { + public: + class NetworkInterface { + public: + virtual void SendPacket(const void *data, size_t len) = 0; + }; + MediaChannel() {network_interface_ = NULL;} + virtual ~MediaChannel() {}; + void SetInterface(NetworkInterface *iface) {network_interface_ = iface;} + virtual void SetCodec(const char *codec) = 0; + virtual void OnPacketReceived(const void *data, int len) = 0; + virtual void SetPlayout(bool playout) = 0; + virtual void SetSend(bool send) = 0; + virtual float GetCurrentQuality() = 0; + virtual int GetOutputLevel() = 0; + NetworkInterface *network_interface() {return network_interface_;} + protected: + NetworkInterface *network_interface_; +}; + +}; // namespace cricket + +#endif // TALK_SESSION_PHONE_MEDIACHANNEL_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h new file mode 100644 index 00000000..fa07d2ec --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h @@ -0,0 +1,95 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// MediaEngine is an abstraction of a media engine which can be subclassed +// to support different media componentry backends. + +#ifndef TALK_SESSION_PHONE_MEDIAENGINE_H_ +#define TALK_SESSION_PHONE_MEDIAENGINE_H_ + +#include <string> +#include <vector> +#include "mediachannel.h" + +namespace cricket { + +class MediaEngine { + public: + + struct Codec { + int id; + std::string name; + int preference; + // Creates a codec with the given parameters. + Codec(int pt, const std::string& nm, int pr) : id(pt), name(nm), preference(pr) {} + // Ranks codecs by their preferences. + bool operator <(const Codec& c) const { return preference > c.preference; } + }; + + // Bitmask flags for options that may be supported by the media engine implementation + enum MediaEngineOptions { + AUTO_GAIN_CONTROL = 1 << 1, + }; + + MediaEngine() {} + + // Initialize + virtual bool Init() = 0; + virtual void Terminate() = 0; + virtual MediaChannel *CreateChannel() = 0; + + virtual int SetAudioOptions(int options) = 0; + virtual int SetSoundDevices(int wave_in_device, int wave_out_device) = 0; + virtual int GetInputLevel() = 0; + + std::vector<Codec> &codecs() { return codecs_; } + + bool FindCodec(const char* codec) { + for (std::vector<Codec>::iterator i = codecs_.begin(); i < codecs_.end(); i++) { + if ((*i).name == codec) + return true; + } + return false; + } + + bool GetCodecPreference (const char *codec, int & preference) { + for (std::vector<Codec>::iterator i = codecs_.begin(); i < codecs_.end(); i++) { + if ((*i).name == codec) { + preference = (*i).preference; + return true; + } + } + return false; + } + + protected: + std::vector<Codec> codecs_; +}; + +} // namespace cricket + +#endif // TALK_SESSION_PHONE_MEDIAENGINE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc new file mode 100644 index 00000000..d8a31df2 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc @@ -0,0 +1,267 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/logging.h" +#include "talk/session/receiver.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/xmllite/qname.h" +namespace { + +const std::string NS_PHONE("http://www.google.com/session/phone"); +const std::string NS_EMPTY(""); + +const buzz::QName QN_PHONE_DESCRIPTION(true, NS_PHONE, "description"); +const buzz::QName QN_PHONE_PAYLOADTYPE(true, NS_PHONE, "payload-type"); +const buzz::QName QN_PHONE_PAYLOADTYPE_ID(true, NS_EMPTY, "id"); +const buzz::QName QN_PHONE_PAYLOADTYPE_NAME(true, NS_EMPTY, "name"); + +} + +namespace cricket { + +PhoneSessionClient::PhoneSessionClient(const buzz::Jid& jid, + SessionManager *manager) : jid_(jid), SessionClient(manager) { + + // No call to start, and certainly no call with focus + focus_call_ = NULL; + + // Start up the channel manager on a worker thread + channel_manager_ = new ChannelManager(session_manager_->worker_thread()); +} + +PhoneSessionClient::~PhoneSessionClient() { + // Destroy all calls + std::map<uint32, Call *>::iterator it; + while (calls_.begin() != calls_.end()) { + std::map<uint32, Call *>::iterator it = calls_.begin(); + DestroyCall((*it).second); + } + + // Delete channel manager. This will wait for the channels to exit + delete channel_manager_; +} + +const std::string &PhoneSessionClient::GetSessionDescriptionName() { + return NS_PHONE; +} + +PhoneSessionDescription* PhoneSessionClient::CreateOfferSessionDescription() { + PhoneSessionDescription* session_desc = new PhoneSessionDescription(); + + MediaEngine *me = channel_manager_->media_engine(); + std::vector<MediaEngine::Codec> codecs = me->codecs(); + std::vector<MediaEngine::Codec>::iterator i; + for (i = codecs.begin(); i < codecs.end(); i++) + session_desc->AddCodec(*i); + + session_desc->Sort(); + return session_desc; +} + +PhoneSessionDescription* PhoneSessionClient::CreateAcceptSessionDescription(const SessionDescription* offer) { + const PhoneSessionDescription* offer_desc = + static_cast<const PhoneSessionDescription*>(offer); + PhoneSessionDescription* accept_desc = new PhoneSessionDescription(); + std::vector<MediaEngine::Codec> codecs = channel_manager_->media_engine()->codecs(); + std::vector<MediaEngine::Codec>::iterator iter; + for (unsigned int i = 0; i < offer_desc->codecs().size(); ++i) { + for (iter = codecs.begin(); iter < codecs.end(); iter++) { + if ((*iter).name == offer_desc->codecs()[i].name) + accept_desc->AddCodec(*iter); + } + } + + accept_desc->Sort(); + return accept_desc; +} + +bool PhoneSessionClient::FindMediaCodec(MediaEngine* me, + const PhoneSessionDescription* desc, + const char** codec) { + for (size_t i = 0; i < desc->codecs().size(); ++i) { + if (me->FindCodec(desc->codecs()[i].name.c_str())) + *codec = desc->codecs()[i].name.c_str(); + return true; + } + + return false; +} + +const SessionDescription *PhoneSessionClient::CreateSessionDescription(const buzz::XmlElement *element) { + PhoneSessionDescription* desc = new PhoneSessionDescription(); + + const buzz::XmlElement* payload_type = element->FirstNamed(QN_PHONE_PAYLOADTYPE); + int num_payload_types = 0; + + while (payload_type) { + if (payload_type->HasAttr(QN_PHONE_PAYLOADTYPE_ID) && + payload_type->HasAttr(QN_PHONE_PAYLOADTYPE_NAME)) { + int id = atoi(payload_type->Attr(QN_PHONE_PAYLOADTYPE_ID).c_str()); + int pref = 0; + std::string name = payload_type->Attr(QN_PHONE_PAYLOADTYPE_NAME); + desc->AddCodec(MediaEngine::Codec(id, name, 0)); + } + + payload_type = payload_type->NextNamed(QN_PHONE_PAYLOADTYPE); + num_payload_types += 1; + } + + // For backward compatability, we can assume the other client is (an old + // version of Talk) if it has no payload types at all. + if (num_payload_types == 0) { + desc->AddCodec(MediaEngine::Codec(103, "ISAC", 1)); + desc->AddCodec(MediaEngine::Codec(0, "PCMU", 0)); + } + + return desc; +} + +buzz::XmlElement *PhoneSessionClient::TranslateSessionDescription(const SessionDescription *_session_desc) { + const PhoneSessionDescription* session_desc = + static_cast<const PhoneSessionDescription*>(_session_desc); + buzz::XmlElement* description = new buzz::XmlElement(QN_PHONE_DESCRIPTION, true); + + for (size_t i = 0; i < session_desc->codecs().size(); ++i) { + buzz::XmlElement* payload_type = new buzz::XmlElement(QN_PHONE_PAYLOADTYPE, true); + + char buf[32]; + sprintf(buf, "%d", session_desc->codecs()[i].id); + payload_type->AddAttr(QN_PHONE_PAYLOADTYPE_ID, buf); + + payload_type->AddAttr(QN_PHONE_PAYLOADTYPE_NAME, + session_desc->codecs()[i].name.c_str()); + + description->AddElement(payload_type); + } + + return description; +} + +Call *PhoneSessionClient::CreateCall() { + Call *call = new Call(this); + calls_[call->id()] = call; + SignalCallCreate(call); + return call; +} + +void PhoneSessionClient::OnSessionCreate(Session *session, bool received_initiate) { + if (received_initiate) { + session->SignalState.connect(this, &PhoneSessionClient::OnSessionState); + + Call *call = CreateCall(); + session_map_[session->id()] = call; + call->AddSession(session); + } +} + +void PhoneSessionClient::OnSessionState(Session *session, Session::State state) { + if (state == Session::STATE_RECEIVEDINITIATE) { + // If our accept would have no codecs, then we must reject this call. + PhoneSessionDescription* accept_desc = + CreateAcceptSessionDescription(session->remote_description()); + if (accept_desc->codecs().size() == 0) { + // TODO: include an error description with the rejection. + session->Reject(); + } + delete accept_desc; + } +} + +void PhoneSessionClient::DestroyCall(Call *call) { + // Change focus away, signal destruction + + if (call == focus_call_) + SetFocus(NULL); + SignalCallDestroy(call); + + // Remove it from calls_ map and delete + + std::map<uint32, Call *>::iterator it = calls_.find(call->id()); + if (it != calls_.end()) + calls_.erase(it); + + delete call; +} + +void PhoneSessionClient::OnSessionDestroy(Session *session) { + // Find the call this session is in, remove it + + std::map<SessionID, Call *>::iterator it = session_map_.find(session->id()); + assert(it != session_map_.end()); + if (it != session_map_.end()) { + Call *call = (*it).second; + session_map_.erase(it); + call->RemoveSession(session); + } +} + +Call *PhoneSessionClient::GetFocus() { + return focus_call_; +} + +void PhoneSessionClient::SetFocus(Call *call) { + Call *old_focus_call = focus_call_; + if (focus_call_ != call) { + if (focus_call_ != NULL) + focus_call_->EnableChannels(false); + focus_call_ = call; + if (focus_call_ != NULL) + focus_call_->EnableChannels(true); + SignalFocus(focus_call_, old_focus_call); + } +} + +void PhoneSessionClient::JoinCalls(Call *call_to_join, Call *call) { + // Move all sessions from call to call_to_join, delete call. + // If call_to_join has focus, added sessions should have enabled channels. + + if (focus_call_ == call) + SetFocus(NULL); + call_to_join->Join(call, focus_call_ == call_to_join); + DestroyCall(call); +} + +Session *PhoneSessionClient::CreateSession(Call *call) { + Session *session = session_manager_->CreateSession( + GetSessionDescriptionName(), jid().Str()); + session_map_[session->id()] = call; + return session; +} + +ChannelManager *PhoneSessionClient::channel_manager() { + return channel_manager_; +} + +const buzz::Jid &PhoneSessionClient::jid() const { + return jid_; +} + +const buzz::Jid &PhoneSessionClient::GetJid() const { + return jid_; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h new file mode 100644 index 00000000..150bf34b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h @@ -0,0 +1,122 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PHONESESSIONCLIENT_H_ +#define _PHONESESSIONCLIENT_H_ + +#include "talk/session/phone/call.h" +#include "talk/session/phone/channelmanager.h" +#include "talk/base/sigslot.h" +#include "talk/base/messagequeue.h" +#include "talk/base/thread.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessiondescription.h" +#include "talk/xmpp/xmppclient.h" +#include <map> + +namespace cricket { + +class Call; +class PhoneSessionDescription; + +class PhoneSessionClient : public SessionClient { +public: + PhoneSessionClient(const buzz::Jid& jid, SessionManager *manager); + ~PhoneSessionClient(); + + const buzz::Jid &jid() const; + + Call *CreateCall(); + void DestroyCall(Call *call); + + Call *GetFocus(); + void SetFocus(Call *call); + + void JoinCalls(Call *call_to_join, Call *call); + + void SetAudioOptions(bool auto_gain_control, int wave_in_device, + int wave_out_device) { + if (channel_manager_) + channel_manager_->SetAudioOptions(auto_gain_control, wave_in_device, + wave_out_device); + } + + sigslot::signal2<Call *, Call *> SignalFocus; + sigslot::signal1<Call *> SignalCallCreate; + sigslot::signal1<Call *> SignalCallDestroy; + + PhoneSessionDescription* CreateOfferSessionDescription(); + PhoneSessionDescription* CreateAcceptSessionDescription(const SessionDescription* offer); + + // Returns our preference for the given codec. + static int GetMediaCodecPreference(const char* name); + + // Returns the name of the first codec in the description that + // is found. Return value is false if none was found. + static bool FindMediaCodec(MediaEngine* gips, + const PhoneSessionDescription* desc, + const char **codec); + +private: + void OnSessionCreate(Session *session, bool received_initiate); + void OnSessionState(Session *session, Session::State state); + void OnSessionDestroy(Session *session); + const SessionDescription *CreateSessionDescription(const buzz::XmlElement *element); + buzz::XmlElement *TranslateSessionDescription(const SessionDescription *description); + const std::string &GetSessionDescriptionName(); + const buzz::Jid &GetJid() const; + Session *CreateSession(Call *call); + ChannelManager *channel_manager(); + + buzz::Jid jid_; + Call *focus_call_; + ChannelManager *channel_manager_; + std::map<uint32, Call *> calls_; + std::map<SessionID, Call *> session_map_; + + friend class Call; +}; + +class PhoneSessionDescription: public SessionDescription { +public: + // Returns the list of codecs sorted by our preference. + const std::vector<MediaEngine::Codec>& codecs() const { return codecs_; } + + // Adds another codec to the list. + void AddCodec(const MediaEngine::Codec& codec) { codecs_.push_back(codec); } + // Sorts the list of codecs by preference. + void Sort() { /* std::stable_sort(codecs_.begin(), codecs_.end());*/ } + +private: + std::vector<MediaEngine::Codec> codecs_; +}; + +} + +#endif // _PHONESESSIONCLIENT_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc new file mode 100644 index 00000000..b65c9a20 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc @@ -0,0 +1,331 @@ +#include <portaudio.h> +#include <ortp/ortp.h> +#include <speex.h> + +// Socket stuff +#ifndef _WIN32 +#ifdef INET6 +#include <netdb.h> +#endif +#include <sys/types.h> +#include <sys/socket.h> +#include <fcntl.h> +#else +#include <winsock32.h> +#endif + +#include "talk/session/phone/mediaengine.h" +#include "talk/session/phone/portaudiomediaengine.h" + +// Engine settings +#define ENGINE_BUFFER_SIZE 2048 + +// PortAudio settings +#define FRAMES_PER_BUFFER 256 +#define SAMPLE_RATE 1 + +// Speex settings +//#define SPEEX_QUALITY 8 + +// ORTP settings +#define MAX_RTP_SIZE 1500 // From mediastreamer + + +// ----------------------------------------------------------------------------- + +static int portAudioCallback( void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp outTime, void *channel_p ) +{ + PortAudioMediaChannel* channel = (PortAudioMediaChannel*) channel_p; + channel->readOutput((float*) outputBuffer, framesPerBuffer); + channel->writeInput((float*) inputBuffer, framesPerBuffer); + return 0; +} + +// ----------------------------------------------------------------------------- + +PortAudioMediaChannel::PortAudioMediaChannel() : mute_(false), play_(false), stream_(NULL), out_buffer_(NULL), in_buffer_(NULL), speex_frame_(NULL) +{ + // Initialize buffers + out_buffer_ = new float[ENGINE_BUFFER_SIZE]; + out_buffer_read_ = out_buffer_write_ = (float*) out_buffer_; + out_buffer_end_ = (float*) out_buffer_ + ENGINE_BUFFER_SIZE; + in_buffer_ = new float[ENGINE_BUFFER_SIZE]; + in_buffer_read_ = in_buffer_write_ = (float*) in_buffer_; + in_buffer_end_ = (float*) in_buffer_ + ENGINE_BUFFER_SIZE; + + // Initialize PortAudio + int err = Pa_OpenDefaultStream(&stream_, 1, 1, paFloat32, SAMPLE_RATE, FRAMES_PER_BUFFER, 0, portAudioCallback, this ); + if (err != paNoError) + fprintf(stderr, "Error creating a PortAudio stream: %s\n", Pa_GetErrorText(err)); + + // Initialize Speex + speex_bits_init(&speex_bits_); + speex_enc_state_ = speex_encoder_init(&speex_nb_mode); + speex_dec_state_ = speex_decoder_init(&speex_nb_mode); + speex_decoder_ctl(speex_dec_state_, SPEEX_GET_FRAME_SIZE, &speex_frame_size_); + speex_frame_ = new float[speex_frame_size_]; + + // int quality = SPEEX_QUALITY; + // speex_encoder_ctl(state, SPEEX_SET_QUALITY, &quality); + + // Initialize ORTP socket + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = INADDR_ANY; + sockaddr.sin_port = htons(3000); + rtp_socket_ = socket(PF_INET, SOCK_DGRAM, 0); + fcntl(rtp_socket_, F_SETFL, 0, O_NONBLOCK); + bind (rtp_socket_,(struct sockaddr*)&sockaddr, sizeof(sockaddr)); + + // Initialize ORTP Session + rtp_session_ = rtp_session_new(RTP_SESSION_SENDRECV); + rtp_session_max_buf_size_set(rtp_session_, MAX_RTP_SIZE); + rtp_session_set_profile(rtp_session_, &av_profile); + rtp_session_set_local_addr(rtp_session_, "127.0.0.1", 2000); + rtp_session_set_remote_addr(rtp_session_, "127.0.0.1", 3000); + rtp_session_set_scheduling_mode(rtp_session_, 0); + rtp_session_set_blocking_mode(rtp_session_, 0); + rtp_session_set_payload_type(rtp_session_, 110); + rtp_session_set_jitter_compensation(rtp_session_, 250); + rtp_session_enable_adaptive_jitter_compensation(rtp_session_, TRUE); + rtp_timestamp_ = 0; + //rtp_session_signal_connect(rtp_session_, "telephone-event", (RtpCallback) ortpTelephoneCallback,this); +} + +PortAudioMediaChannel::~PortAudioMediaChannel() +{ + if (stream_) { + Pa_CloseStream(stream_); + } + + // Clean up other allocated pointers + + close(rtp_socket_); +} + +void PortAudioMediaChannel::SetCodec(const char *codec) +{ + if (strcmp(codec, "speex")) + printf("Unsupported codec: %s\n", codec); +} + +void PortAudioMediaChannel::OnPacketReceived(const void *data, int len) +{ + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + struct hostent *host = gethostbyname("localhost"); + memcpy(&sockaddr.sin_addr.s_addr, host->h_addr, host->h_length); + sockaddr.sin_port = htons(2000); + + char buf[2048]; + memcpy(buf, data, len); + + // Pass packet on to ORTP + if (play_) { + sendto(rtp_socket_, buf, len, 0, (struct sockaddr*)&sockaddr, sizeof(sockaddr)); + } +} + +void PortAudioMediaChannel::SetPlayout(bool playout) +{ + if (!stream_) + return; + + if (play_ && !playout) { + int err = Pa_StopStream(stream_); + if (err != paNoError) { + fprintf(stderr, "Error stopping PortAudio stream: %s\n", Pa_GetErrorText(err)); + return; + } + play_ = false; + } + else if (!play_ && playout) { + int err = Pa_StartStream(stream_); + if (err != paNoError) { + fprintf(stderr, "Error starting PortAudio stream: %s\n", Pa_GetErrorText(err)); + return; + } + play_ = true; + } +} + +void PortAudioMediaChannel::SetSend(bool send) +{ + mute_ = !send; +} + + +float PortAudioMediaChannel::GetCurrentQuality() +{ + return 0; +} + +int PortAudioMediaChannel::GetOutputLevel() +{ + return 0; +} + +void PortAudioMediaChannel::readOutput(float* buf, int len) +{ + //readBuffer(out_buffer_, &out_buffer_read_, out_buffer_write_, out_buffer_end_, buf, len); + + // Receive a packet (if there is one) + mblk_t *mp; + mp = rtp_session_recvm_with_ts(rtp_session_,rtp_timestamp_); + while (mp != NULL) { + gint in_len = mp->b_cont->b_wptr-mp->b_cont->b_rptr; + + // Decode speex stream + speex_bits_read_from(&speex_bits_,mp->b_cont->b_rptr, in_len); + speex_decode(speex_dec_state_, &speex_bits_, speex_frame_); + writeBuffer(out_buffer_, out_buffer_read_, &out_buffer_write_, out_buffer_end_, speex_frame_, speex_frame_size_); + rtp_timestamp_++; + mp = rtp_session_recvm_with_ts(rtp_session_,rtp_timestamp_); + } + + // Read output + readBuffer(out_buffer_, &out_buffer_read_, out_buffer_write_, out_buffer_end_, buf, len); +} + +void PortAudioMediaChannel::writeInput(float* buf, int len) +{ + //writeBuffer(in_buffer_, in_buffer_read_, &in_buffer_write_, in_buffer_end_, buf, len); +} + + +void PortAudioMediaChannel::readBuffer(float* buffer, float** buffer_read_p, float*buffer_write, float* buffer_end, float* target_buffer, int target_len) +{ + float *end, *tmp, *buffer_read = *buffer_read_p; + int remaining; + + // First phase + tmp = buffer_read + target_len; + if (buffer_write < buffer_read && tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = (tmp > buffer_write ? buffer_write : tmp); + remaining = 0; + } + + while (buffer_read < end) { + *target_buffer++ = *buffer_read++; + } + + // Second phase + if (remaining > 0) { + buffer_read = buffer; + tmp = buffer_read + remaining; + end = (tmp > buffer_write ? buffer_write : tmp); + while (buffer_read < end) { + *target_buffer++ = *buffer_read++; + } + } + + // Finish up + *buffer_read_p = buffer_read; +} + +void PortAudioMediaChannel::writeBuffer(float* buffer, float* buffer_read, float**buffer_write_p, float* buffer_end, float* source_buffer, int source_len) +{ + float *end, *tmp, *buffer_write = *buffer_write_p; + int remaining; + + // First phase + tmp = buffer_write + source_len; + if (buffer_write > buffer_read) { + if (tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = tmp; + remaining = 0; + } + } + else { + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s)\n"); + end = buffer_read; + remaining = 0; + } + else { + end = tmp; + remaining = 0; + } + } + + while (buffer_write < end) { + *buffer_write++ = *source_buffer++; + } + + // Second phase + if (remaining > 0) { + buffer_write = buffer; + tmp = buffer_write + remaining; + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s)\n"); + end = buffer_read; + } + else { + end = tmp; + } + while (buffer_write < end) { + *buffer_write++ = *source_buffer++; + } + } + + // Finish up + *buffer_write_p = buffer_write; +} + +// ----------------------------------------------------------------------------- + +PortAudioMediaEngine::PortAudioMediaEngine() +{ +} + +PortAudioMediaEngine::~PortAudioMediaEngine() +{ + Pa_Terminate(); +} + +bool PortAudioMediaEngine::Init() +{ + ortp_init(); + + int err = Pa_Initialize(); + if (err != paNoError) { + fprintf(stderr,"Error initializing PortAudio: %s\n",Pa_GetErrorText(err)); + return false; + } + + // Speex + rtp_profile_set_payload(&av_profile, 110, &speex_wb); + codecs_.push_back(Codec(110, "speex", 8)); + + return true; +} + +void PortAudioMediaEngine::Terminate() +{ +} + + +cricket::MediaChannel* PortAudioMediaEngine::CreateChannel() +{ + return new PortAudioMediaChannel(); +} + +int PortAudioMediaEngine::SetAudioOptions(int options) +{ +} + +int PortAudioMediaEngine::SetSoundDevices(int wave_in_device, int wave_out_device) +{ +} + +int PortAudioMediaEngine::GetInputLevel() +{ +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h new file mode 100644 index 00000000..95c39a1a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h @@ -0,0 +1,69 @@ +#ifndef PORTAUDIOMEDIAENGINE_H +#define PORTAUDIOMEDIAENGINE_H + +#include <portaudio.h> +#include <speex.h> +#include <ortp/ortp.h> + +#include "talk/session/phone/mediaengine.h" + +class PortAudioMediaChannel : public cricket::MediaChannel +{ +public: + PortAudioMediaChannel(); + virtual ~PortAudioMediaChannel(); + virtual void SetCodec(const char *codec); + virtual void OnPacketReceived(const void *data, int len); + + virtual void SetPlayout(bool playout); + virtual void SetSend(bool send); + + virtual float GetCurrentQuality(); + virtual int GetOutputLevel(); + + void readOutput(float*, int); + void writeInput(float*, int); + +protected: + void readBuffer(float*, float**, float*, float*, float*, int); + void writeBuffer(float*, float*, float**, float*, float*, int); + +private: + bool mute_; + bool play_; + PortAudioStream* stream_; + + // Buffers + float *out_buffer_, *out_buffer_read_, *out_buffer_write_, *out_buffer_end_; + float *in_buffer_, *in_buffer_read_, *in_buffer_write_, *in_buffer_end_; + + // Speex + SpeexBits speex_bits_; + void *speex_enc_state_, *speex_dec_state_; + float *speex_frame_; + int speex_frame_size_; + + // ORTP + int rtp_socket_; + RtpSession* rtp_session_; + int rtp_timestamp_; +}; + + +class PortAudioMediaEngine : public cricket::MediaEngine +{ +public: + PortAudioMediaEngine(); + ~PortAudioMediaEngine(); + virtual bool Init(); + virtual void Terminate(); + + virtual cricket::MediaChannel *CreateChannel(); + + virtual int SetAudioOptions(int options); + virtual int SetSoundDevices(int wave_in_device, int wave_out_device); + + virtual int GetInputLevel(); +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc new file mode 100644 index 00000000..58e1db60 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc @@ -0,0 +1,331 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/session/phone/voicechannel.h" +#include "talk/session/phone/channelmanager.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/base/logging.h" +#include <cassert> +#undef SetPort + +namespace { + +// Delay before quality estimate is meaningful. +uint32 kQualityDelay = 5000; // in ms + +} + +namespace cricket { + +VoiceChannel::VoiceChannel(ChannelManager *manager, Session *session, MediaChannel *channel) { + channel_manager_ = manager; + assert(channel_manager_->worker_thread() == Thread::Current()); + channel_ = channel; + session_ = session; + socket_monitor_ = NULL; + audio_monitor_ = NULL; + socket_ = session_->CreateSocket("rtp"); + socket_->SignalState.connect(this, &VoiceChannel::OnSocketState); + socket_->SignalReadPacket.connect(this, &VoiceChannel::OnSocketRead); + channel->SetInterface(this); + enabled_ = false; + paused_ = false; + socket_writable_ = false; + muted_ = false; + LOG(INFO) << "Created voice channel"; + start_time_ = 0xFFFFFFFF - kQualityDelay; + + session->SignalState.connect(this, &VoiceChannel::OnSessionState); + OnSessionState(session, session->state()); +} + +VoiceChannel::~VoiceChannel() { + assert(channel_manager_->worker_thread() == Thread::Current()); + enabled_ = false; + ChangeState(); + delete socket_monitor_; + delete audio_monitor_; + Thread::Current()->Clear(this); + if (socket_ != NULL) + session_->DestroySocket(socket_); + LOG(INFO) << "Destroyed voice channel"; +} + +void VoiceChannel::OnMessage(Message *pmsg) { + switch (pmsg->message_id) { + case MSG_ENABLE: + EnableMedia_w(); + break; + + case MSG_DISABLE: + DisableMedia_w(); + break; + + case MSG_MUTE: + MuteMedia_w(); + break; + + case MSG_UNMUTE: + UnmuteMedia_w(); + break; + + case MSG_SETSENDCODEC: + SetSendCodec_w(); + break; + } +} + +void VoiceChannel::Enable(bool enable) { + // Can be called from thread other than worker thread + channel_manager_->worker_thread()->Post(this, enable ? MSG_ENABLE : MSG_DISABLE); +} + +void VoiceChannel::Mute(bool mute) { + // Can be called from thread other than worker thread + channel_manager_->worker_thread()->Post(this, mute ? MSG_MUTE : MSG_UNMUTE); +} + +MediaChannel * VoiceChannel::channel() { + return channel_; +} + +void VoiceChannel::OnSessionState(Session* session, Session::State state) { + if ((state == Session::STATE_RECEIVEDACCEPT) || + (state == Session::STATE_RECEIVEDINITIATE)) { + channel_manager_->worker_thread()->Post(this, MSG_SETSENDCODEC); + } +} + +void VoiceChannel::SetSendCodec_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + + const PhoneSessionDescription* desc = + static_cast<const PhoneSessionDescription*>(session()->remote_description()); + + const char *codec = NULL; + + if (desc->codecs().size() > 0) + PhoneSessionClient::FindMediaCodec(channel_manager_->media_engine(), desc, &codec); + + // The other client should have returned one of the codecs that we offered. + // If they could not, they should have rejected the session. So, if we get + // into this state, we're dealing with a bad client, so we may as well just + // pick the mostt common format there is: payload type zero. + if (codec == NULL) + codec = "PCMU"; + + channel_->SetCodec(codec); +} + +void VoiceChannel::OnSocketState(P2PSocket *socket, P2PSocket::State state) { + switch (state) { + case P2PSocket::STATE_WRITABLE: + SocketWritable_w(); + break; + + default: + SocketNotWritable_w(); + break; + } +} + +void VoiceChannel::OnSocketRead(P2PSocket *socket, const char *data, size_t len) { + assert(channel_manager_->worker_thread() == Thread::Current()); + // OnSocketRead gets called from P2PSocket; now pass data to MediaEngine + channel_->OnPacketReceived(data, (int)len); +} + +void VoiceChannel::SendPacket(const void *data, size_t len) { + // SendPacket gets called from MediaEngine; send to socket + // MediaEngine will call us on a random thread. The Send operation on the socket is + // special in that it can handle this. + socket_->Send(static_cast<const char *>(data), len); +} + +void VoiceChannel::ChangeState() { + if (paused_ || !enabled_ || !socket_writable_) { + channel_->SetPlayout(false); + channel_->SetSend(false); + } else { + if (muted_) { + channel_->SetSend(false); + channel_->SetPlayout(true); + } else { + channel_->SetSend(true); + channel_->SetPlayout(true); + } + } +} + +void VoiceChannel::PauseMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + assert(!paused_); + + LOG(INFO) << "Voice channel paused"; + paused_ = true; + ChangeState(); +} + +void VoiceChannel::UnpauseMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + assert(paused_); + + LOG(INFO) << "Voice channel unpaused"; + paused_ = false; + ChangeState(); +} + +void VoiceChannel::EnableMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (enabled_) + return; + + LOG(INFO) << "Voice channel enabled"; + enabled_ = true; + start_time_ = Time(); + ChangeState(); +} + +void VoiceChannel::DisableMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (!enabled_) + return; + + LOG(INFO) << "Voice channel disabled"; + enabled_ = false; + ChangeState(); +} + +void VoiceChannel::MuteMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (muted_) + return; + + LOG(INFO) << "Voice channel muted"; + muted_ = true; + ChangeState(); +} + +void VoiceChannel::UnmuteMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (!muted_) + return; + + LOG(INFO) << "Voice channel unmuted"; + muted_ = false; + ChangeState(); +} + +void VoiceChannel::SocketWritable_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (socket_writable_) + return; + + LOG(INFO) << "Voice channel socket writable"; + socket_writable_ = true; + ChangeState(); +} + +void VoiceChannel::SocketNotWritable_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (!socket_writable_) + return; + + LOG(INFO) << "Voice channel socket not writable"; + socket_writable_ = false; + ChangeState(); +} + +void VoiceChannel::StartConnectionMonitor(int cms) { + delete socket_monitor_; + socket_monitor_ = new SocketMonitor(socket_, Thread::Current()); + socket_monitor_ + ->SignalUpdate.connect(this, &VoiceChannel::OnConnectionMonitorUpdate); + socket_monitor_->Start(cms); +} + +void VoiceChannel::StopConnectionMonitor() { + if (socket_monitor_ != NULL) { + socket_monitor_->Stop(); + socket_monitor_->SignalUpdate.disconnect(this); + delete socket_monitor_; + socket_monitor_ = NULL; + } +} + +void VoiceChannel::OnConnectionMonitorUpdate(SocketMonitor *monitor, + const std::vector<ConnectionInfo> &infos) { + SignalConnectionMonitor(this, infos); +} + +void VoiceChannel::StartAudioMonitor(int cms) { + delete audio_monitor_; + audio_monitor_ = new AudioMonitor(this, Thread::Current()); + audio_monitor_ + ->SignalUpdate.connect(this, &VoiceChannel::OnAudioMonitorUpdate); + audio_monitor_->Start(cms); +} + +void VoiceChannel::StopAudioMonitor() { + if (audio_monitor_ != NULL) { + audio_monitor_ ->Stop(); + audio_monitor_ ->SignalUpdate.disconnect(this); + delete audio_monitor_ ; + audio_monitor_ = NULL; + } +} + +void VoiceChannel::OnAudioMonitorUpdate(AudioMonitor *monitor, + const AudioInfo& info) { + SignalAudioMonitor(this, info); +} + +Session *VoiceChannel::session() { + return session_; +} + +bool VoiceChannel::HasQuality() { + return Time() >= start_time_ + kQualityDelay; +} + +float VoiceChannel::GetCurrentQuality() { + return channel_->GetCurrentQuality(); +} + +int VoiceChannel::GetInputLevel_w() { + return channel_manager_->media_engine()->GetInputLevel(); +} + +int VoiceChannel::GetOutputLevel_w() { + return channel_->GetOutputLevel(); +} + +Thread* VoiceChannel::worker_thread() { + return channel_manager_->worker_thread(); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h new file mode 100644 index 00000000..4cfa0b11 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h @@ -0,0 +1,129 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _VOICECHANNEL_H_ +#define _VOICECHANNEL_H_ + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/network.h" +#include "talk/base/sigslot.h" +#include "talk/p2p/client/socketmonitor.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/p2p/base/session.h" +#include "talk/session/phone/audiomonitor.h" +#include "talk/session/phone/mediaengine.h" + +namespace cricket { + +const uint32 MSG_ENABLE = 1; +const uint32 MSG_DISABLE = 2; +const uint32 MSG_MUTE = 3; +const uint32 MSG_UNMUTE = 4; +const uint32 MSG_SETSENDCODEC = 5; + +class ChannelManager; + +class VoiceChannel + : public MessageHandler, public sigslot::has_slots<>, + public NetworkSession, public MediaChannel::NetworkInterface { + public: + VoiceChannel(ChannelManager *manager, Session *session, MediaChannel *channel); + ~VoiceChannel(); + + void Enable(bool enable); + void Mute(bool mute); + MediaChannel *channel(); + Session *session(); + + // Monitoring + + void StartConnectionMonitor(int cms); + void StopConnectionMonitor(); + sigslot::signal2<VoiceChannel *, const std::vector<ConnectionInfo> &> SignalConnectionMonitor; + + void StartAudioMonitor(int cms); + void StopAudioMonitor(); + sigslot::signal2<VoiceChannel *, const AudioInfo&> SignalAudioMonitor; + Thread* worker_thread(); + + // Pausing so that the ChannelManager can change the audio devices. These + // should only be called from the worker thread + void PauseMedia_w(); + void UnpauseMedia_w(); + + int GetInputLevel_w(); + int GetOutputLevel_w(); + + // Gives a quality estimate to the network quality manager. + virtual bool HasQuality(); + virtual float GetCurrentQuality(); + + // MediaEngine calls this + virtual void SendPacket(const void *data, size_t len); + +private: + void ChangeState(); + void EnableMedia_w(); + void DisableMedia_w(); + void MuteMedia_w(); + void UnmuteMedia_w(); + void SocketWritable_w(); + void SocketNotWritable_w(); + + void OnConnectionMonitorUpdate(SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos); + void OnAudioMonitorUpdate(AudioMonitor *monitor, const AudioInfo& info); + + // From MessageHandler + + void OnMessage(Message *pmsg); + + // Setting the send codec based on the remote description. + void OnSessionState(Session* session, Session::State state); + void SetSendCodec_w(); + + // From P2PSocket + + void OnSocketState(P2PSocket *socket, P2PSocket::State state); + void OnSocketRead(P2PSocket *socket, const char *data, size_t len); + + + bool enabled_; + bool paused_; + bool socket_writable_; + bool muted_; + MediaChannel *channel_; + Session *session_; + P2PSocket *socket_; + ChannelManager *channel_manager_; + SocketMonitor *socket_monitor_; + AudioMonitor *audio_monitor_; + uint32 start_time_; +}; + +} + +#endif // _VOICECHANNEL_H_ |