diff options
Diffstat (limited to 'third_party/libjingle/source/talk/session/phone/channelmanager.cc')
-rw-r--r-- | third_party/libjingle/source/talk/session/phone/channelmanager.cc | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/third_party/libjingle/source/talk/session/phone/channelmanager.cc b/third_party/libjingle/source/talk/session/phone/channelmanager.cc new file mode 100644 index 0000000..92957bf --- /dev/null +++ b/third_party/libjingle/source/talk/session/phone/channelmanager.cc @@ -0,0 +1,752 @@ +/* + * libjingle + * Copyright 2004--2008, 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/channelmanager.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <algorithm> + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/sigslotrepeater.h" +#include "talk/base/stringencode.h" +#include "talk/session/phone/mediaengine.h" +#include "talk/session/phone/soundclip.h" +#ifdef USE_TALK_SOUND +#include "talk/sound/platformsoundsystemfactory.h" +#include "talk/sound/soundsysteminterface.h" +#endif + +namespace cricket { + +enum { + MSG_CREATEVOICECHANNEL = 1, + MSG_DESTROYVOICECHANNEL = 2, + MSG_SETAUDIOOPTIONS = 3, + MSG_SETOUTPUTVOLUME = 4, + MSG_SETLOCALMONITOR = 5, + MSG_SETVOICELOGGING = 6, + MSG_CREATEVIDEOCHANNEL = 11, + MSG_DESTROYVIDEOCHANNEL = 12, + MSG_SETVIDEOOPTIONS = 13, + MSG_SETLOCALRENDERER = 14, + MSG_SETDEFAULTVIDEOCODEC = 15, + MSG_SETVIDEOLOGGING = 16, + MSG_CREATESOUNDCLIP = 17, + MSG_DESTROYSOUNDCLIP = 18, + MSG_CAMERASTARTED = 19, + MSG_SETVIDEOCAPTURE = 20, +}; + +struct CreationParams : public talk_base::MessageData { + CreationParams(BaseSession* s, bool r, VoiceChannel* c) + : transport_factory(s), rtcp(r), voice_channel(c), + video_channel(NULL) {} + BaseSession* transport_factory; + bool rtcp; + VoiceChannel* voice_channel; + VideoChannel* video_channel; +}; + +struct AudioOptions : public talk_base::MessageData { + AudioOptions(int o, const Device* in, const Device* out) + : options(o), in_device(in), out_device(out) {} + int options; + const Device* in_device; + const Device* out_device; + bool result; +}; + +struct VolumeLevel : public talk_base::MessageData { + explicit VolumeLevel(int l) : level(l), result(false) {} + int level; + bool result; +}; + +struct VideoOptions : public talk_base::MessageData { + explicit VideoOptions(const Device* d) : cam_device(d), result(false) {} + const Device* cam_device; + bool result; +}; + +struct DefaultVideoCodec : public talk_base::MessageData { + explicit DefaultVideoCodec(const VideoCodec& c) : codec(c), result(false) {} + VideoCodec codec; + bool result; +}; + +struct LocalMonitor : public talk_base::MessageData { + explicit LocalMonitor(bool e) : enable(e), result(false) {} + bool enable; + bool result; +}; + +struct LocalRenderer : public talk_base::MessageData { + explicit LocalRenderer(VideoRenderer* r) : renderer(r), result(false) {} + VideoRenderer* renderer; + bool result; +}; + +struct LoggingOptions : public talk_base::MessageData { + explicit LoggingOptions(int lev, const char* f) : level(lev), filter(f) {} + int level; + std::string filter; +}; + +struct CaptureParams : public talk_base::MessageData { + explicit CaptureParams(bool c) : capture(c), result(MediaEngine::CR_FAILURE) { + } + bool capture; + MediaEngine::CaptureResult result; +}; + +ChannelManager::ChannelManager(talk_base::Thread* worker_thread) + : +#ifdef USE_TALK_SOUND + sound_system_factory_(new PlatformSoundSystemFactory()), +#endif + media_engine_(MediaEngine::Create( +#ifdef USE_TALK_SOUND + sound_system_factory_.get() +#endif + )), + device_manager_(new DeviceManager( +#ifdef USE_TALK_SOUND + sound_system_factory_.get() +#endif + )), + initialized_(false), main_thread_(talk_base::Thread::Current()), + worker_thread_(NULL), audio_options_(MediaEngine::DEFAULT_AUDIO_OPTIONS), + capturing_(false), + monitoring_(false) { + Construct(worker_thread); +} + +ChannelManager::ChannelManager(MediaEngine* me, DeviceManager* dm, + talk_base::Thread* worker_thread) + : +#ifdef USE_TALK_SOUND + sound_system_factory_(NULL), +#endif + media_engine_(me), device_manager_(dm), + initialized_(false), main_thread_(talk_base::Thread::Current()), + worker_thread_(NULL), audio_options_(MediaEngine::DEFAULT_AUDIO_OPTIONS), + capturing_(false), + monitoring_(false) { + Construct(worker_thread); +} + +void ChannelManager::Construct(talk_base::Thread* worker_thread) { + // Init the device manager immediately, and set up our default video device. + SignalDevicesChange.repeat(device_manager_->SignalDevicesChange); + device_manager_->Init(); + SetVideoOptions(""); + + // Camera is started asynchronously, request callbacks when startup + // completes to be able to forward them to the rendering manager. + media_engine_->SignalVideoCaptureResult.connect( + this, &ChannelManager::OnVideoCaptureResult); + + // If we're given a worker thread, init the media engine right away. + if (worker_thread) + Init(worker_thread); +} + +ChannelManager::~ChannelManager() { + if (initialized_) + Terminate(); +} + +int ChannelManager::GetCapabilities() { + return media_engine_->GetCapabilities() & device_manager_->GetCapabilities(); +} + +void ChannelManager::GetSupportedCodecs(std::vector<Codec>* codecs) const { + codecs->clear(); + + for (std::vector<Codec>::const_iterator it = media_engine_->codecs().begin(); + it != media_engine_->codecs().end(); ++it) { + codecs->push_back(*it); + } +} + +void ChannelManager::GetSupportedVideoCodecs( + std::vector<VideoCodec>* codecs) const { + codecs->clear(); + + std::vector<VideoCodec>::const_iterator it; + for (it = media_engine_->video_codecs().begin(); + it != media_engine_->video_codecs().end(); ++it) { + codecs->push_back(*it); + } +} + +bool ChannelManager::Init(talk_base::Thread* worker_thread) { + ASSERT(!initialized_); + if (initialized_) { + return false; + } + + ASSERT(worker_thread != NULL); + if (worker_thread && worker_thread->started()) { + if (media_engine_->Init()) { + worker_thread_ = worker_thread; + initialized_ = true; + + // Now that we're initialized, apply any stored preferences. + if (!SetAudioOptions(audio_in_device_, audio_out_device_, + audio_options_)) { + audio_in_device_.clear(); + audio_out_device_.clear(); + } + if (!SetVideoOptions(camera_device_)) { + // TODO(juberti): Consider resetting to the default cam here. + camera_device_.clear(); + } + // Now apply the default video codec that has been set earlier. + if (default_video_codec_.id != 0) { + SetDefaultVideoCodec(default_video_codec_); + } + } + } + return initialized_; +} + +void ChannelManager::Terminate() { + ASSERT(initialized_); + if (!initialized_) { + return; + } + + // Need to destroy the voice/video channels + while (!video_channels_.empty()) { + DestroyVideoChannel_w(video_channels_.back()); + } + while (!voice_channels_.empty()) { + DestroyVoiceChannel_w(voice_channels_.back()); + } + while (!soundclips_.empty()) { + DestroySoundclip_w(soundclips_.back()); + } + + media_engine_->Terminate(); + initialized_ = false; + worker_thread_ = NULL; +} + +VoiceChannel* ChannelManager::CreateVoiceChannel(BaseSession* session, + bool rtcp) { + CreationParams params(session, rtcp, NULL); + return (Send(MSG_CREATEVOICECHANNEL, ¶ms)) ? params.voice_channel : NULL; +} + +VoiceChannel* ChannelManager::CreateVoiceChannel_w(BaseSession* session, + bool rtcp) { + talk_base::CritScope cs(&crit_); + + // This is ok to alloc from a thread other than the worker thread + ASSERT(initialized_); + VoiceMediaChannel* media_channel = media_engine_->CreateChannel(); + if (media_channel == NULL) + return NULL; + + VoiceChannel* voice_channel = new VoiceChannel(worker_thread_, + media_engine_.get(), + media_channel, session, rtcp); + voice_channels_.push_back(voice_channel); + return voice_channel; +} + +void ChannelManager::DestroyVoiceChannel(VoiceChannel* voice_channel) { + if (voice_channel) { + talk_base::TypedMessageData<VoiceChannel *> data(voice_channel); + Send(MSG_DESTROYVOICECHANNEL, &data); + } +} + +void ChannelManager::DestroyVoiceChannel_w(VoiceChannel* voice_channel) { + talk_base::CritScope cs(&crit_); + // Destroy voice channel. + ASSERT(initialized_); + VoiceChannels::iterator it = std::find(voice_channels_.begin(), + voice_channels_.end(), voice_channel); + ASSERT(it != voice_channels_.end()); + if (it == voice_channels_.end()) + return; + + voice_channels_.erase(it); + delete voice_channel; +} + +VideoChannel* ChannelManager::CreateVideoChannel(BaseSession* session, + bool rtcp, + VoiceChannel* voice_channel) { + CreationParams params(session, rtcp, voice_channel); + return (Send(MSG_CREATEVIDEOCHANNEL, ¶ms)) ? params.video_channel : NULL; +} + +VideoChannel* ChannelManager::CreateVideoChannel_w(BaseSession* session, + bool rtcp, + VoiceChannel* voice_channel) { + talk_base::CritScope cs(&crit_); + + // This is ok to alloc from a thread other than the worker thread + ASSERT(initialized_); + VideoMediaChannel* media_channel = + // voice_channel can be NULL in case of NullVoiceEngine. + media_engine_->CreateVideoChannel(voice_channel ? + voice_channel->media_channel() : NULL); + if (media_channel == NULL) + return NULL; + + VideoChannel* video_channel = new VideoChannel(worker_thread_, + media_engine_.get(), + media_channel, + session, rtcp, voice_channel); + video_channels_.push_back(video_channel); + return video_channel; +} + +void ChannelManager::DestroyVideoChannel(VideoChannel* video_channel) { + if (video_channel) { + talk_base::TypedMessageData<VideoChannel *> data(video_channel); + Send(MSG_DESTROYVIDEOCHANNEL, &data); + } +} + +void ChannelManager::DestroyVideoChannel_w(VideoChannel *video_channel) { + talk_base::CritScope cs(&crit_); + // Destroy voice channel. + ASSERT(initialized_); + VideoChannels::iterator it = std::find(video_channels_.begin(), + video_channels_.end(), video_channel); + ASSERT(it != video_channels_.end()); + if (it == video_channels_.end()) + return; + + video_channels_.erase(it); + delete video_channel; +} + +Soundclip* ChannelManager::CreateSoundclip() { + talk_base::TypedMessageData<Soundclip*> data(NULL); + Send(MSG_CREATESOUNDCLIP, &data); + return data.data(); +} + +Soundclip* ChannelManager::CreateSoundclip_w() { + talk_base::CritScope cs(&crit_); + + ASSERT(initialized_); + ASSERT(worker_thread_ == talk_base::Thread::Current()); + + SoundclipMedia* soundclip_media = media_engine_->CreateSoundclip(); + if (!soundclip_media) { + return NULL; + } + + Soundclip* soundclip = new Soundclip(worker_thread_, soundclip_media); + soundclips_.push_back(soundclip); + return soundclip; +} + +void ChannelManager::DestroySoundclip(Soundclip* soundclip) { + if (soundclip) { + talk_base::TypedMessageData<Soundclip*> data(soundclip); + Send(MSG_DESTROYSOUNDCLIP, &data); + } +} + +void ChannelManager::DestroySoundclip_w(Soundclip* soundclip) { + talk_base::CritScope cs(&crit_); + // Destroy soundclip. + ASSERT(initialized_); + Soundclips::iterator it = std::find(soundclips_.begin(), + soundclips_.end(), soundclip); + ASSERT(it != soundclips_.end()); + if (it == soundclips_.end()) + return; + + soundclips_.erase(it); + delete soundclip; +} + +bool ChannelManager::GetAudioOptions(std::string* in_name, + std::string* out_name, int* opts) { + *in_name = audio_in_device_; + *out_name = audio_out_device_; + *opts = audio_options_; + return true; +} + +bool ChannelManager::SetAudioOptions(const std::string& in_name, + const std::string& out_name, int opts) { + // Get device ids from DeviceManager. + Device in_dev, out_dev; + if (!device_manager_->GetAudioInputDevice(in_name, &in_dev) || + !device_manager_->GetAudioOutputDevice(out_name, &out_dev)) { + LOG(LS_WARNING) << "Device manager can't find selected device"; + return false; + } + + // If we're initialized, pass the settings to the media engine. + bool ret = true; + if (initialized_) { + AudioOptions options(opts, &in_dev, &out_dev); + ret = (Send(MSG_SETAUDIOOPTIONS, &options) && options.result); + } + + // If all worked well, save the values for use in GetAudioOptions. + if (ret) { + audio_options_ = opts; + audio_in_device_ = in_name; + audio_out_device_ = out_name; + } + return ret; +} + +bool ChannelManager::SetAudioOptions_w(int opts, const Device* in_dev, + const Device* out_dev) { + ASSERT(worker_thread_ == talk_base::Thread::Current()); + ASSERT(initialized_); + + // Set audio options + bool ret = media_engine_->SetAudioOptions(opts); + + // Set the audio devices + if (ret) { + // Need to grab the critsection for this because SetSoundDevices in GIPS + // relies on a list of channels and our Terminate() method destroys channels + // from a different thread. + talk_base::CritScope cs(&crit_); + ret = media_engine_->SetSoundDevices(in_dev, out_dev); + } + + return ret; +} + +bool ChannelManager::SetOutputVolume(int level) { + VolumeLevel volume(level); + return (Send(MSG_SETOUTPUTVOLUME, &volume) && volume.result); +} + +bool ChannelManager::SetOutputVolume_w(int level) { + ASSERT(worker_thread_ == talk_base::Thread::Current()); + ASSERT(initialized_); + return media_engine_->SetOutputVolume(level); +} + +bool ChannelManager::GetVideoOptions(std::string* cam_name) { + *cam_name = camera_device_; + return true; +} + +bool ChannelManager::SetVideoOptions(const std::string& cam_name) { + bool ret; + Device device; + + if (cam_name.empty()) { + // If we're passed the default device name, get the default device. + ret = device_manager_->GetDefaultVideoCaptureDevice(&device); + } else { + // Convert the camera name to a device, fail if it can't be found. + std::vector<Device> devices; + ret = device_manager_->GetVideoCaptureDevices(&devices); + if (ret) { + for (size_t i = 0; i < devices.size(); ++i) { + if (devices[i].name == cam_name) { + device = devices[i]; + break; + } + } + ret = !device.name.empty(); + } + } + + // If we're running, tell the media engine about it. + if (ret && initialized_) { + VideoOptions options(&device); + ret = (Send(MSG_SETVIDEOOPTIONS, &options) && options.result); + } + + // If everything worked, retain the name of the selected camera. + if (ret) { + camera_device_ = device.name; + } + return ret; +} + +bool ChannelManager::SetVideoOptions_w(const Device* cam_device) { + ASSERT(worker_thread_ == talk_base::Thread::Current()); + ASSERT(initialized_); + + // Set the video input device + return media_engine_->SetVideoCaptureDevice(cam_device); +} + +bool ChannelManager::SetDefaultVideoCodec(const VideoCodec& c) { + bool ret = true; + if (initialized_) { + DefaultVideoCodec codec(c); + ret = Send(MSG_SETDEFAULTVIDEOCODEC, &codec) && codec.result; + } + if (ret) { + default_video_codec_ = c; + } + return ret; +} + +bool ChannelManager::SetDefaultVideoCodec_w(const VideoCodec& c) { + ASSERT(worker_thread_ == talk_base::Thread::Current()); + ASSERT(initialized_); + return media_engine_->SetDefaultVideoCodec(c); +} + +bool ChannelManager::SetLocalMonitor(bool enable) { + LocalMonitor monitor(enable); + bool ret = Send(MSG_SETLOCALMONITOR, &monitor) && monitor.result; + if (ret) { + monitoring_ = enable; + } + return ret; +} + +bool ChannelManager::SetLocalMonitor_w(bool enable) { + ASSERT(worker_thread_ == talk_base::Thread::Current()); + ASSERT(initialized_); + return media_engine_->SetLocalMonitor(enable); +} + +bool ChannelManager::SetLocalRenderer(VideoRenderer* renderer) { + bool ret; + LocalRenderer capture(renderer); + ret = (Send(MSG_SETLOCALRENDERER, &capture) && capture.result); + if (ret) { + capturing_ = (renderer != NULL); + } + return ret; +} + +bool ChannelManager::SetLocalRenderer_w(VideoRenderer* renderer) { + ASSERT(worker_thread_ == talk_base::Thread::Current()); + ASSERT(initialized_); + return media_engine_->SetLocalRenderer(renderer); +} + +MediaEngine::CaptureResult ChannelManager::SetVideoCapture(bool capture) { + bool ret; + CaptureParams capture_params(capture); + ret = (Send(MSG_SETVIDEOCAPTURE, &capture_params) && + (capture_params.result != MediaEngine::CR_FAILURE)); + if (ret) { + capturing_ = capture; + } + return capture_params.result; +} + +MediaEngine::CaptureResult ChannelManager::SetVideoCapture_w(bool capture) { + ASSERT(worker_thread_ == talk_base::Thread::Current()); + ASSERT(initialized_); + return media_engine_->SetVideoCapture(capture); +} + +void ChannelManager::SetVoiceLogging(int level, const char* filter) { + SetMediaLogging(false, level, filter); +} + +void ChannelManager::SetVideoLogging(int level, const char* filter) { + SetMediaLogging(true, level, filter); +} + +void ChannelManager::SetMediaLogging(bool video, int level, + const char* filter) { + // Can be called before initialization; in this case, the worker function + // is simply called on the main thread. + if (initialized_) { + LoggingOptions options(level, filter); + Send((video) ? MSG_SETVIDEOLOGGING : MSG_SETVOICELOGGING, &options); + } else { + SetMediaLogging_w(video, level, filter); + } +} + +void ChannelManager::SetMediaLogging_w(bool video, int level, + const char* filter) { + // Can be called before initialization + ASSERT(worker_thread_ == talk_base::Thread::Current() || !initialized_); + if (video) { + media_engine_->SetVideoLogging(level, filter); + } else { + media_engine_->SetVoiceLogging(level, filter); + } +} + +bool ChannelManager::Send(uint32 id, talk_base::MessageData* data) { + if (!worker_thread_) return false; + worker_thread_->Send(this, id, data); + return true; +} + +void ChannelManager::OnVideoCaptureResult(bool result) { + capturing_ = result; + main_thread_->Post(this, MSG_CAMERASTARTED, + new talk_base::TypedMessageData<bool>(result)); +} + +void ChannelManager::OnMessage(talk_base::Message* message) { + talk_base::MessageData* data = message->pdata; + switch (message->message_id) { + case MSG_CREATEVOICECHANNEL: { + CreationParams* p = static_cast<CreationParams*>(data); + p->voice_channel = CreateVoiceChannel_w(p->transport_factory, p->rtcp); + break; + } + case MSG_DESTROYVOICECHANNEL: { + VoiceChannel* p = static_cast<talk_base::TypedMessageData<VoiceChannel*>*> + (data)->data(); + DestroyVoiceChannel_w(p); + break; + } + case MSG_CREATEVIDEOCHANNEL: { + CreationParams* p = static_cast<CreationParams*>(data); + p->video_channel = CreateVideoChannel_w(p->transport_factory, + p->rtcp, p->voice_channel); + break; + } + case MSG_DESTROYVIDEOCHANNEL: { + VideoChannel* p = static_cast<talk_base::TypedMessageData<VideoChannel*>*> + (data)->data(); + DestroyVideoChannel_w(p); + break; + } + case MSG_CREATESOUNDCLIP: { + talk_base::TypedMessageData<Soundclip*> *p = + static_cast<talk_base::TypedMessageData<Soundclip*>*>(data); + p->data() = CreateSoundclip_w(); + break; + } + case MSG_DESTROYSOUNDCLIP: { + talk_base::TypedMessageData<Soundclip*> *p = + static_cast<talk_base::TypedMessageData<Soundclip*>*>(data); + DestroySoundclip_w(p->data()); + break; + } + case MSG_SETAUDIOOPTIONS: { + AudioOptions* p = static_cast<AudioOptions*>(data); + p->result = SetAudioOptions_w(p->options, + p->in_device, p->out_device); + break; + } + case MSG_SETOUTPUTVOLUME: { + VolumeLevel* p = static_cast<VolumeLevel*>(data); + p->result = SetOutputVolume_w(p->level); + break; + } + case MSG_SETLOCALMONITOR: { + LocalMonitor* p = static_cast<LocalMonitor*>(data); + p->result = SetLocalMonitor_w(p->enable); + break; + } + case MSG_SETVIDEOOPTIONS: { + VideoOptions* p = static_cast<VideoOptions*>(data); + p->result = SetVideoOptions_w(p->cam_device); + break; + } + case MSG_SETDEFAULTVIDEOCODEC: { + DefaultVideoCodec* p = static_cast<DefaultVideoCodec*>(data); + p->result = SetDefaultVideoCodec_w(p->codec); + break; + } + case MSG_SETLOCALRENDERER: { + LocalRenderer* p = static_cast<LocalRenderer*>(data); + p->result = SetLocalRenderer_w(p->renderer); + break; + } + case MSG_SETVIDEOCAPTURE: { + CaptureParams* p = static_cast<CaptureParams*>(data); + p->result = SetVideoCapture_w(p->capture); + break; + } + case MSG_SETVOICELOGGING: + case MSG_SETVIDEOLOGGING: { + LoggingOptions* p = static_cast<LoggingOptions*>(data); + bool video = (message->message_id == MSG_SETVIDEOLOGGING); + SetMediaLogging_w(video, p->level, p->filter.c_str()); + break; + } + case MSG_CAMERASTARTED: { + talk_base::TypedMessageData<bool> *data = + static_cast<talk_base::TypedMessageData<bool>*>(message->pdata); + SignalVideoCaptureResult(data->data()); + delete data; + break; + } + } +} + +static void GetDeviceNames(const std::vector<Device>& devs, + std::vector<std::string>* names) { + names->clear(); + for (size_t i = 0; i < devs.size(); ++i) { + names->push_back(devs[i].name); + } +} + +bool ChannelManager::GetAudioInputDevices(std::vector<std::string>* names) { + names->clear(); + std::vector<Device> devs; + bool ret = device_manager_->GetAudioInputDevices(&devs); + if (ret) + GetDeviceNames(devs, names); + + return ret; +} + +bool ChannelManager::GetAudioOutputDevices(std::vector<std::string>* names) { + names->clear(); + std::vector<Device> devs; + bool ret = device_manager_->GetAudioOutputDevices(&devs); + if (ret) + GetDeviceNames(devs, names); + + return ret; +} + +bool ChannelManager::GetVideoCaptureDevices(std::vector<std::string>* names) { + names->clear(); + std::vector<Device> devs; + bool ret = device_manager_->GetVideoCaptureDevices(&devs); + if (ret) + GetDeviceNames(devs, names); + + return ret; +} + +} // namespace cricket |