blob: a53dfc105da4390f83521a1a7090f7481e955011 [file] [log] [blame]
/*
* libjingle
* Copyright 2004--2011, 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_WEBRTC_VOICE
#include "talk/session/phone/webrtcvoiceengine.h"
#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
#include "talk/base/base64.h"
#include "talk/base/byteorder.h"
#include "talk/base/common.h"
#include "talk/base/helpers.h"
#include "talk/base/logging.h"
#include "talk/base/stringencode.h"
#include "talk/session/phone/webrtcvoe.h"
#ifdef WIN32
#include <objbase.h> // NOLINT
#endif
namespace cricket {
// For Linux/Mac, using the default device is done by specifying index 0 for
// VoE 4.0 and not -1 (which was the case for VoE 3.5).
//
// On Windows Vista and newer, Microsoft introduced the concept of "Default
// Communications Device". This means that there are two types of default
// devices (old Wave Audio style default and Default Communications Device).
//
// On Windows systems which only support Wave Audio style default, uses either
// -1 or 0 to select the default device.
//
// On Windows systems which support both "Default Communication Device" and
// old Wave Audio style default, use -1 for Default Communications Device and
// -2 for Wave Audio style default, which is what we want to use for clips.
// It's not clear yet whether the -2 index is handled properly on other OSes.
#ifdef WIN32
static const int kDefaultAudioDeviceId = -1;
static const int kDefaultSoundclipDeviceId = -2;
#else
static const int kDefaultAudioDeviceId = 0;
#endif
// extension header for audio levels, as defined in
// http://tools.ietf.org/html/draft-ietf-avtext-client-to-mixer-audio-level-03
static const char kRtpAudioLevelHeaderExtension[] =
"urn:ietf:params:rtp-hdrext:ssrc-audio-level";
static void LogMultiline(talk_base::LoggingSeverity sev, char* text) {
const char* delim = "\r\n";
for (char* tok = strtok(text, delim); tok; tok = strtok(NULL, delim)) {
LOG_V(sev) << tok;
}
}
static const char kL16CodecName[] = "L16";
// WebRtcVoiceEngine
const WebRtcVoiceEngine::CodecPref WebRtcVoiceEngine::kCodecPrefs[] = {
{ "ISAC", 16000 },
{ "ISAC", 32000 },
{ "speex", 16000 },
{ "G722", 16000 },
{ "iLBC", 8000 },
{ "speex", 8000 },
{ "PCMU", 8000 },
{ "PCMA", 8000 },
{ "CN", 32000 },
{ "CN", 16000 },
{ "CN", 8000 },
{ "red", 8000 },
{ "telephone-event", 8000 },
};
class WebRtcSoundclipMedia : public SoundclipMedia {
public:
explicit WebRtcSoundclipMedia(WebRtcVoiceEngine *engine)
: engine_(engine), webrtc_channel_(-1) {
engine_->RegisterSoundclip(this);
}
virtual ~WebRtcSoundclipMedia() {
engine_->UnregisterSoundclip(this);
if (webrtc_channel_ != -1) {
if (engine_->voe_sc()->base()->DeleteChannel(webrtc_channel_)
== -1) {
LOG_RTCERR1(DeleteChannel, webrtc_channel_);
}
}
}
bool Init() {
webrtc_channel_ = engine_->voe_sc()->base()->CreateChannel();
if (webrtc_channel_ == -1) {
LOG_RTCERR0(CreateChannel);
return false;
}
return true;
}
bool Enable() {
if (engine_->voe_sc()->base()->StartPlayout(webrtc_channel_) == -1) {
LOG_RTCERR1(StartPlayout, webrtc_channel_);
return false;
}
return true;
}
bool Disable() {
if (engine_->voe_sc()->base()->StopPlayout(webrtc_channel_) == -1) {
LOG_RTCERR1(StopPlayout, webrtc_channel_);
return false;
}
return true;
}
virtual bool PlaySound(const char *buf, int len, int flags) {
// Must stop playing the current sound (if any), because we are about to
// modify the stream.
if (engine_->voe_sc()->file()->StopPlayingFileLocally(webrtc_channel_)
== -1) {
LOG_RTCERR1(StopPlayingFileLocally, webrtc_channel_);
return false;
}
if (buf) {
stream_.reset(new WebRtcSoundclipStream(buf, len));
stream_->set_loop((flags & SF_LOOP) != 0);
stream_->Rewind();
// Play it.
if (engine_->voe_sc()->file()->StartPlayingFileLocally(
webrtc_channel_, stream_.get()) == -1) {
LOG_RTCERR2(StartPlayingFileLocally, webrtc_channel_, stream_.get());
LOG(LS_ERROR) << "Unable to start soundclip";
return false;
}
} else {
stream_.reset();
}
return true;
}
int GetLastEngineError() const { return engine_->voe_sc()->error(); }
private:
WebRtcVoiceEngine *engine_;
int webrtc_channel_;
talk_base::scoped_ptr<WebRtcSoundclipStream> stream_;
};
WebRtcVoiceEngine::WebRtcVoiceEngine()
: voe_wrapper_(new VoEWrapper()),
voe_wrapper_sc_(new VoEWrapper()),
tracing_(new VoETraceWrapper()),
adm_(NULL),
adm_sc_(NULL),
log_level_(kDefaultLogSeverity),
is_dumping_aec_(false),
desired_local_monitor_enable_(false) {
Construct();
}
WebRtcVoiceEngine::WebRtcVoiceEngine(webrtc::AudioDeviceModule* adm,
webrtc::AudioDeviceModule* adm_sc)
: voe_wrapper_(new VoEWrapper()),
voe_wrapper_sc_(new VoEWrapper()),
tracing_(new VoETraceWrapper()),
adm_(adm),
adm_sc_(adm_sc),
log_level_(kDefaultLogSeverity),
is_dumping_aec_(false),
desired_local_monitor_enable_(false) {
Construct();
}
WebRtcVoiceEngine::WebRtcVoiceEngine(VoEWrapper* voe_wrapper,
VoEWrapper* voe_wrapper_sc,
VoETraceWrapper* tracing)
: voe_wrapper_(voe_wrapper),
voe_wrapper_sc_(voe_wrapper_sc),
tracing_(tracing),
adm_(NULL),
adm_sc_(NULL),
log_level_(kDefaultLogSeverity),
is_dumping_aec_(false),
desired_local_monitor_enable_(false) {
Construct();
}
void WebRtcVoiceEngine::Construct() {
initialized_ = false;
LOG(LS_VERBOSE) << "WebRtcVoiceEngine::WebRtcVoiceEngine";
ApplyLogging("");
if (tracing_->SetTraceCallback(this) == -1) {
LOG_RTCERR0(SetTraceCallback);
}
if (voe_wrapper_->base()->RegisterVoiceEngineObserver(*this) == -1) {
LOG_RTCERR0(RegisterVoiceEngineObserver);
}
// Clear the default agc state.
memset(&default_agc_config_, 0, sizeof(default_agc_config_));
// Load our audio codec list
LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
for (int i = 0; i < ncodecs; ++i) {
webrtc::CodecInst gcodec;
if (voe_wrapper_->codec()->GetCodec(i, gcodec) >= 0) {
// Skip the codecs that we don't support.
if (strcmp(gcodec.plname, kL16CodecName) == 0) {
continue;
}
int pref = GetCodecPreference(gcodec.plname, gcodec.plfreq);
if (pref != -1) {
if (gcodec.rate == -1) gcodec.rate = 0;
AudioCodec codec(gcodec.pltype, gcodec.plname, gcodec.plfreq,
gcodec.rate, gcodec.channels, pref);
LOG(LS_INFO) << gcodec.plname << "/" << gcodec.plfreq << "/" \
<< gcodec.channels << " " << gcodec.pltype;
codecs_.push_back(codec);
}
}
}
// Make sure they are in local preference order
std::sort(codecs_.begin(), codecs_.end(), &AudioCodec::Preferable);
}
WebRtcVoiceEngine::~WebRtcVoiceEngine() {
LOG(LS_VERBOSE) << "WebRtcVoiceEngine::~WebRtcVoiceEngine";
if (voe_wrapper_->base()->DeRegisterVoiceEngineObserver() == -1) {
LOG_RTCERR0(DeRegisterVoiceEngineObserver);
}
if (adm_) {
voe_wrapper_.reset();
webrtc::AudioDeviceModule::Destroy(adm_);
adm_ = NULL;
}
if (adm_sc_) {
voe_wrapper_sc_.reset();
webrtc::AudioDeviceModule::Destroy(adm_sc_);
adm_sc_ = NULL;
}
tracing_->SetTraceCallback(NULL);
}
bool WebRtcVoiceEngine::Init() {
LOG(LS_INFO) << "WebRtcVoiceEngine::Init";
bool res = InitInternal();
if (res) {
LOG(LS_INFO) << "WebRtcVoiceEngine::Init Done!";
} else {
LOG(LS_ERROR) << "WebRtcVoiceEngine::Init failed";
Terminate();
}
return res;
}
bool WebRtcVoiceEngine::InitInternal() {
// Temporarily turn logging level up for the Init call
int old_level = log_level_;
log_level_ = talk_base::_min(log_level_,
static_cast<int>(talk_base::LS_INFO));
ApplyLogging("");
if (adm_) {
if (voe_wrapper_->base()->RegisterAudioDeviceModule(*adm_) == -1) {
LOG_RTCERR0_EX(Init, voe_wrapper_->error());
return false;
}
}
if (adm_sc_) {
if (voe_wrapper_sc_->base()->RegisterAudioDeviceModule(*adm_sc_) == -1) {
LOG_RTCERR0_EX(Init, voe_wrapper_sc_->error());
return false;
}
}
// Init WebRtc VoiceEngine, enabling AEC logging if specified in SetLogging.
if (voe_wrapper_->base()->Init() == -1) {
LOG_RTCERR0_EX(Init, voe_wrapper_->error());
return false;
}
// Restore the previous log level and apply the log filter.
log_level_ = old_level;
ApplyLogging(log_filter_);
// Log the VoiceEngine version info
char buffer[1024] = "";
voe_wrapper_->base()->GetVersion(buffer);
LOG(LS_INFO) << "WebRtc VoiceEngine Version:";
LogMultiline(talk_base::LS_INFO, buffer);
// Turn on AEC and AGC by default.
if (!SetOptions(
MediaEngineInterface::ECHO_CANCELLATION |
MediaEngineInterface::AUTO_GAIN_CONTROL)) {
return false;
}
// Save the default AGC configuration settings.
if (voe_wrapper_->processing()->SetAgcConfig(default_agc_config_) == -1) {
LOG_RTCERR0(GetAGCConfig);
return false;
}
// Print our codec list again for the call diagnostic log
LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
for (std::vector<AudioCodec>::const_iterator it = codecs_.begin();
it != codecs_.end(); ++it) {
LOG(LS_INFO) << it->name << "/" << it->clockrate << "/"
<< it->channels << " " << it->id;
}
#if defined(LINUX) && !defined(HAVE_LIBPULSE)
voe_wrapper_sc_->hw()->SetAudioDeviceLayer(webrtc::kAudioLinuxAlsa);
#endif
// Initialize the VoiceEngine instance that we'll use to play out sound clips.
if (voe_wrapper_sc_->base()->Init() == -1) {
LOG_RTCERR0_EX(Init, voe_wrapper_sc_->error());
return false;
}
// On Windows, tell it to use the default sound (not communication) devices.
// First check whether there is a valid sound device for playback.
// TODO: Clean this up when we support setting the soundclip device.
#ifdef WIN32
int num_of_devices = 0;
if (voe_wrapper_sc_->hw()->GetNumOfPlayoutDevices(num_of_devices) != -1 &&
num_of_devices > 0) {
if (voe_wrapper_sc_->hw()->SetPlayoutDevice(kDefaultSoundclipDeviceId)
== -1) {
LOG_RTCERR1_EX(SetPlayoutDevice, kDefaultSoundclipDeviceId,
voe_wrapper_sc_->error());
return false;
}
} else {
LOG(LS_WARNING) << "No valid sound playout device found.";
}
#endif
initialized_ = true;
return true;
}
void WebRtcVoiceEngine::Terminate() {
LOG(LS_INFO) << "WebRtcVoiceEngine::Terminate";
initialized_ = false;
if (is_dumping_aec_) {
if (voe_wrapper_->processing()->StopDebugRecording() == -1) {
LOG_RTCERR0(StopDebugRecording);
}
is_dumping_aec_ = false;
}
voe_wrapper_sc_->base()->Terminate();
voe_wrapper_->base()->Terminate();
desired_local_monitor_enable_ = false;
}
int WebRtcVoiceEngine::GetCapabilities() {
return AUDIO_SEND | AUDIO_RECV;
}
VoiceMediaChannel *WebRtcVoiceEngine::CreateChannel() {
WebRtcVoiceMediaChannel* ch = new WebRtcVoiceMediaChannel(this);
if (!ch->valid()) {
delete ch;
ch = NULL;
}
return ch;
}
SoundclipMedia *WebRtcVoiceEngine::CreateSoundclip() {
WebRtcSoundclipMedia *soundclip = new WebRtcSoundclipMedia(this);
if (!soundclip->Init() || !soundclip->Enable()) {
delete soundclip;
return NULL;
}
return soundclip;
}
bool WebRtcVoiceEngine::SetOptions(int options) {
// NS and typing detection are always on, if supported.
bool aec = (options & MediaEngineInterface::ECHO_CANCELLATION) ? true : false;
bool agc = (options & MediaEngineInterface::AUTO_GAIN_CONTROL) ? true : false;
#if !defined(IOS) && !defined(ANDROID)
if (voe_wrapper_->processing()->SetEcStatus(aec) == -1) {
LOG_RTCERR1(SetEcStatus, aec);
return false;
}
if (voe_wrapper_->processing()->SetAgcStatus(agc) == -1) {
LOG_RTCERR1(SetAgcStatus, agc);
return false;
}
if (voe_wrapper_->processing()->SetNsStatus(true) == -1) {
LOG_RTCERR1(SetNsStatus, true);
return false;
}
if (voe_wrapper_->processing()->SetTypingDetectionStatus(true) == -1) {
// In case of error, log the info and continue
LOG_RTCERR1(SetTypingDetectionStatus, true);
}
#else
if (voe_wrapper_->processing()->SetEcStatus(aec, kEcAecm) == -1) {
LOG_RTCERR2(SetEcStatus, aec, kEcAecm);
return false;
}
if (aec) {
// Use speakerphone mode with comfort noise generation for mobile.
if (voe_wrapper_->processing()->SetAecmMode(kAecmSpeakerphone, true) != 0) {
LOG_RTCERR2(SetAecmMode, kAecmSpeakerphone, true);
}
}
// On mobile, GIPS recommends fixed AGC (not adaptive)
if (voe_wrapper_->processing()->SetAgcStatus(agc, kAgcFixedDigital) == -1) {
LOG_RTCERR2(SetAgcStatus, agc, kAgcFixedDigital);
return false;
}
// On mobile, GIPS recommends moderate aggressiveness.
if (voe_wrapper_->processing()->SetNsStatus(true,
kNsModerateSuppression) == -1) {
LOG_RTCERR2(SetNsStatus, ns, kNsModerateSuppression);
return false;
}
// No typing detection support on iOS or Android.
#endif // !IOS && !ANDROID
return true;
}
struct ResumeEntry {
ResumeEntry(WebRtcVoiceMediaChannel *c, bool p, SendFlags s)
: channel(c),
playout(p),
send(s) {
}
WebRtcVoiceMediaChannel *channel;
bool playout;
SendFlags send;
};
// TODO: Refactor this so that the core logic can be used to set the
// soundclip device. At that time, reinstate the soundclip pause/resume code.
bool WebRtcVoiceEngine::SetDevices(const Device* in_device,
const Device* out_device) {
#if !defined(IOS) && !defined(ANDROID)
int in_id = in_device ? talk_base::FromString<int>(in_device->id) :
kDefaultAudioDeviceId;
int out_id = out_device ? talk_base::FromString<int>(out_device->id) :
kDefaultAudioDeviceId;
// The device manager uses -1 as the default device, which was the case for
// VoE 3.5. VoE 4.0, however, uses 0 as the default in Linux and Mac.
#ifndef WIN32
if (-1 == in_id) {
in_id = kDefaultAudioDeviceId;
}
if (-1 == out_id) {
out_id = kDefaultAudioDeviceId;
}
#endif
std::string in_name = (in_id != kDefaultAudioDeviceId) ?
in_device->name : "Default device";
std::string out_name = (out_id != kDefaultAudioDeviceId) ?
out_device->name : "Default device";
LOG(LS_INFO) << "Setting microphone to (id=" << in_id << ", name=" << in_name
<< ") and speaker to (id=" << out_id << ", name=" << out_name
<< ")";
// If we're running the local monitor, we need to stop it first.
bool ret = true;
if (!PauseLocalMonitor()) {
LOG(LS_WARNING) << "Failed to pause local monitor";
ret = false;
}
// Must also pause all audio playback and capture.
for (ChannelList::const_iterator i = channels_.begin();
i != channels_.end(); ++i) {
WebRtcVoiceMediaChannel *channel = *i;
if (!channel->PausePlayout()) {
LOG(LS_WARNING) << "Failed to pause playout";
ret = false;
}
if (!channel->PauseSend()) {
LOG(LS_WARNING) << "Failed to pause send";
ret = false;
}
}
// Find the recording device id in VoiceEngine and set recording device.
if (!FindWebRtcAudioDeviceId(true, in_name, in_id, &in_id)) {
ret = false;
}
if (ret) {
if (voe_wrapper_->hw()->SetRecordingDevice(in_id) == -1) {
LOG_RTCERR2(SetRecordingDevice, in_device->name, in_id);
ret = false;
}
}
// Find the playout device id in VoiceEngine and set playout device.
if (!FindWebRtcAudioDeviceId(false, out_name, out_id, &out_id)) {
LOG(LS_WARNING) << "Failed to find VoiceEngine device id for " << out_name;
ret = false;
}
if (ret) {
if (voe_wrapper_->hw()->SetPlayoutDevice(out_id) == -1) {
LOG_RTCERR2(SetPlayoutDevice, out_device->name, out_id);
ret = false;
}
}
// Resume all audio playback and capture.
for (ChannelList::const_iterator i = channels_.begin();
i != channels_.end(); ++i) {
WebRtcVoiceMediaChannel *channel = *i;
if (!channel->ResumePlayout()) {
LOG(LS_WARNING) << "Failed to resume playout";
ret = false;
}
if (!channel->ResumeSend()) {
LOG(LS_WARNING) << "Failed to resume send";
ret = false;
}
}
// Resume local monitor.
if (!ResumeLocalMonitor()) {
LOG(LS_WARNING) << "Failed to resume local monitor";
ret = false;
}
if (ret) {
LOG(LS_INFO) << "Set microphone to (id=" << in_id <<" name=" << in_name
<< ") and speaker to (id="<< out_id << " name=" << out_name
<< ")";
}
return ret;
#else
return true;
#endif // !IOS && !ANDROID
}
bool WebRtcVoiceEngine::FindWebRtcAudioDeviceId(
bool is_input, const std::string& dev_name, int dev_id, int* rtc_id) {
// In Linux, VoiceEngine uses the same device dev_id as the device manager.
#ifdef LINUX
*rtc_id = dev_id;
return true;
#else
// In Windows and Mac, we need to find the VoiceEngine device id by name
// unless the input dev_id is the default device id.
if (kDefaultAudioDeviceId == dev_id) {
*rtc_id = dev_id;
return true;
}
// Get the number of VoiceEngine audio devices.
int count = 0;
if (is_input) {
if (-1 == voe_wrapper_->hw()->GetNumOfRecordingDevices(count)) {
LOG_RTCERR0(GetNumOfRecordingDevices);
return false;
}
} else {
if (-1 == voe_wrapper_->hw()->GetNumOfPlayoutDevices(count)) {
LOG_RTCERR0(GetNumOfPlayoutDevices);
return false;
}
}
for (int i = 0; i < count; ++i) {
char name[128];
char guid[128];
if (is_input) {
voe_wrapper_->hw()->GetRecordingDeviceName(i, name, guid);
LOG(LS_VERBOSE) << "VoiceEngine microphone " << i << ": " << name;
} else {
voe_wrapper_->hw()->GetPlayoutDeviceName(i, name, guid);
LOG(LS_VERBOSE) << "VoiceEngine speaker " << i << ": " << name;
}
std::string webrtc_name(name);
if (dev_name.compare(0, webrtc_name.size(), webrtc_name) == 0) {
*rtc_id = i;
return true;
}
}
LOG(LS_WARNING) << "VoiceEngine cannot find device: " << dev_name;
return false;
#endif
}
bool WebRtcVoiceEngine::GetOutputVolume(int* level) {
unsigned int ulevel;
if (voe_wrapper_->volume()->GetSpeakerVolume(ulevel) == -1) {
LOG_RTCERR1(GetSpeakerVolume, level);
return false;
}
*level = ulevel;
return true;
}
bool WebRtcVoiceEngine::SetOutputVolume(int level) {
ASSERT(level >= 0 && level <= 255);
if (voe_wrapper_->volume()->SetSpeakerVolume(level) == -1) {
LOG_RTCERR1(SetSpeakerVolume, level);
return false;
}
return true;
}
int WebRtcVoiceEngine::GetInputLevel() {
unsigned int ulevel;
return (voe_wrapper_->volume()->GetSpeechInputLevel(ulevel) != -1) ?
static_cast<int>(ulevel) : -1;
}
bool WebRtcVoiceEngine::SetLocalMonitor(bool enable) {
desired_local_monitor_enable_ = enable;
return ChangeLocalMonitor(desired_local_monitor_enable_);
}
bool WebRtcVoiceEngine::ChangeLocalMonitor(bool enable) {
if (enable && !monitor_.get()) {
monitor_.reset(new WebRtcMonitorStream);
if (voe_wrapper_->file()->StartRecordingMicrophone(monitor_.get()) == -1) {
LOG_RTCERR1(StartRecordingMicrophone, monitor_.get());
// Must call Stop() because there are some cases where Start will report
// failure but still change the state, and if we leave VE in the on state
// then it could crash later when trying to invoke methods on our monitor.
voe_wrapper_->file()->StopRecordingMicrophone();
monitor_.reset();
return false;
}
} else if (!enable && monitor_.get()) {
voe_wrapper_->file()->StopRecordingMicrophone();
monitor_.reset();
}
return true;
}
bool WebRtcVoiceEngine::PauseLocalMonitor() {
return ChangeLocalMonitor(false);
}
bool WebRtcVoiceEngine::ResumeLocalMonitor() {
return ChangeLocalMonitor(desired_local_monitor_enable_);
}
const std::vector<AudioCodec>& WebRtcVoiceEngine::codecs() {
return codecs_;
}
bool WebRtcVoiceEngine::FindCodec(const AudioCodec& in) {
return FindWebRtcCodec(in, NULL);
}
bool WebRtcVoiceEngine::FindWebRtcCodec(const AudioCodec& in,
webrtc::CodecInst* out) {
int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
for (int i = 0; i < ncodecs; ++i) {
webrtc::CodecInst gcodec;
if (voe_wrapper_->codec()->GetCodec(i, gcodec) >= 0) {
AudioCodec codec(gcodec.pltype, gcodec.plname,
gcodec.plfreq, gcodec.rate, gcodec.channels, 0);
if (codec.Matches(in)) {
if (out) {
// If the codec is VBR and an explicit rate is specified, use it.
if (in.bitrate != 0 && gcodec.rate == -1) {
gcodec.rate = in.bitrate;
}
*out = gcodec;
}
return true;
}
}
}
return false;
}
void WebRtcVoiceEngine::SetLogging(int min_sev, const char* filter) {
// if min_sev == -1, we keep the current log level.
if (min_sev >= 0) {
log_level_ = min_sev;
}
log_filter_ = filter;
ApplyLogging(initialized_ ? log_filter_ : "");
}
int WebRtcVoiceEngine::GetLastEngineError() {
return voe_wrapper_->error();
}
// We suppport three different logging settings for VoiceEngine:
// 1. Observer callback that goes into talk diagnostic logfile.
// Use --logfile and --loglevel
//
// 2. Encrypted VoiceEngine log for debugging VoiceEngine.
// Use --voice_loglevel --voice_logfilter "tracefile file_name"
//
// 3. EC log and dump for debugging QualityEngine.
// Use --voice_loglevel --voice_logfilter "recordEC file_name"
//
// For more details see: "https://sites.google.com/a/google.com/wavelet/Home/
// Magic-Flute--RTC-Engine-/Magic-Flute-Command-Line-Parameters"
void WebRtcVoiceEngine::ApplyLogging(const std::string& log_filter) {
// Set log level.
int filter = 0;
switch (log_level_) {
case talk_base::LS_VERBOSE:
filter |= webrtc::kTraceAll; // fall through
case talk_base::LS_INFO:
filter |= webrtc::kTraceStateInfo; // fall through
case talk_base::LS_WARNING:
filter |= (webrtc::kTraceInfo | webrtc::kTraceWarning); // fall through
case talk_base::LS_ERROR:
filter |= (webrtc::kTraceError | webrtc::kTraceCritical);
}
tracing_->SetTraceFilter(filter);
// Set encrypted trace file.
std::vector<std::string> opts;
talk_base::tokenize(log_filter, ' ', '"', '"', &opts);
std::vector<std::string>::iterator tracefile =
std::find(opts.begin(), opts.end(), "tracefile");
if (tracefile != opts.end() && ++tracefile != opts.end()) {
// Write encrypted debug output (at same loglevel) to file
// EncryptedTraceFile no longer supported.
if (tracing_->SetTraceFile(tracefile->c_str()) == -1) {
LOG_RTCERR1(SetTraceFile, *tracefile);
}
}
// Set AEC dump file
std::vector<std::string>::iterator recordEC =
std::find(opts.begin(), opts.end(), "recordEC");
if (recordEC != opts.end()) {
++recordEC;
if (recordEC != opts.end() && !is_dumping_aec_) {
// Start dumping AEC when we are not dumping and recordEC has a filename.
if (voe_wrapper_->processing()->StartDebugRecording(
recordEC->c_str()) == -1) {
LOG_RTCERR0(StartDebugRecording);
} else {
is_dumping_aec_ = true;
}
} else if (recordEC == opts.end() && is_dumping_aec_) {
// Stop dumping EC when we are dumping and recordEC has no filename.
if (voe_wrapper_->processing()->StopDebugRecording() == -1) {
LOG_RTCERR0(StopDebugRecording);
}
is_dumping_aec_ = false;
}
}
}
// Ignore spammy trace messages, mostly from the stats API when we haven't
// gotten RTCP info yet from the remote side.
static bool ShouldIgnoreTrace(const std::string& trace) {
static const char* kTracesToIgnore[] = {
"\tfailed to GetReportBlockInformation",
"GetRecCodec() failed to get received codec",
"GetRemoteRTCPData() failed to retrieve sender info for remote side",
"GetRTPStatistics() failed to measure RTT since no RTP packets have been received yet", // NOLINT
"GetRTPStatistics() failed to read RTP statistics from the RTP/RTCP module",
"GetRTPStatistics() failed to retrieve RTT from the RTP/RTCP module",
"RTCPReceiver::SenderInfoReceived No received SR",
"StatisticsRTP() no statisitics availble",
NULL
};
for (const char* const* p = kTracesToIgnore; *p; ++p) {
if (trace.find(*p) == 0) {
return true;
}
}
return false;
}
void WebRtcVoiceEngine::Print(const webrtc::TraceLevel level,
const char* trace, const int length) {
talk_base::LoggingSeverity sev = talk_base::LS_VERBOSE;
if (level == webrtc::kTraceError || level == webrtc::kTraceCritical)
sev = talk_base::LS_ERROR;
else if (level == webrtc::kTraceWarning)
sev = talk_base::LS_WARNING;
else if (level == webrtc::kTraceStateInfo || level == webrtc::kTraceInfo)
sev = talk_base::LS_INFO;
if (sev >= log_level_) {
// Skip past boilerplate prefix text
if (length < 72) {
std::string msg(trace, length);
LOG(LS_ERROR) << "Malformed webrtc log message: ";
LOG_V(sev) << msg;
} else {
std::string msg(trace + 71, length - 72);
if (!ShouldIgnoreTrace(msg)) {
LOG_V(sev) << "WebRtc VoE:" << msg;
}
}
}
}
void WebRtcVoiceEngine::CallbackOnError(const int channel_num,
const int err_code) {
talk_base::CritScope lock(&channels_cs_);
WebRtcVoiceMediaChannel* channel = NULL;
uint32 ssrc = 0;
LOG(LS_WARNING) << "VoiceEngine error " << err_code << " reported on channel "
<< channel_num << ".";
if (FindChannelAndSsrc(channel_num, &channel, &ssrc)) {
ASSERT(channel != NULL);
channel->OnError(ssrc, err_code);
} else {
LOG(LS_ERROR) << "VoiceEngine channel " << channel_num
<< " could not be found in the channel list when error reported.";
}
}
int WebRtcVoiceEngine::GetCodecPreference(const char *name, int clockrate) {
for (size_t i = 0; i < ARRAY_SIZE(kCodecPrefs); ++i) {
if ((strcmp(kCodecPrefs[i].name, name) == 0) &&
(kCodecPrefs[i].clockrate == clockrate))
return ARRAY_SIZE(kCodecPrefs) - i;
}
LOG(LS_WARNING) << "Unexpected codec \"" << name << "/" << clockrate << "\"";
return -1;
}
bool WebRtcVoiceEngine::FindChannelAndSsrc(
int channel_num, WebRtcVoiceMediaChannel** channel, uint32* ssrc) const {
ASSERT(channel != NULL && ssrc != NULL);
*channel = NULL;
*ssrc = 0;
// Find corresponding channel and ssrc
for (ChannelList::const_iterator it = channels_.begin();
it != channels_.end(); ++it) {
ASSERT(*it != NULL);
if ((*it)->FindSsrc(channel_num, ssrc)) {
*channel = *it;
return true;
}
}
return false;
}
void WebRtcVoiceEngine::RegisterChannel(WebRtcVoiceMediaChannel *channel) {
talk_base::CritScope lock(&channels_cs_);
channels_.push_back(channel);
}
void WebRtcVoiceEngine::UnregisterChannel(WebRtcVoiceMediaChannel *channel) {
talk_base::CritScope lock(&channels_cs_);
ChannelList::iterator i = std::find(channels_.begin(),
channels_.end(),
channel);
if (i != channels_.end()) {
channels_.erase(i);
}
}
void WebRtcVoiceEngine::RegisterSoundclip(WebRtcSoundclipMedia *soundclip) {
soundclips_.push_back(soundclip);
}
void WebRtcVoiceEngine::UnregisterSoundclip(WebRtcSoundclipMedia *soundclip) {
SoundclipList::iterator i = std::find(soundclips_.begin(),
soundclips_.end(),
soundclip);
if (i != soundclips_.end()) {
soundclips_.erase(i);
}
}
// Adjusts the default AGC target level by the specified delta.
// NB: If we start messing with other config fields, we'll want
// to save the current webrtc::AgcConfig as well.
bool WebRtcVoiceEngine::AdjustAgcLevel(int delta) {
webrtc::AgcConfig config = default_agc_config_;
config.targetLeveldBOv += delta;
LOG(LS_INFO) << "Adjusting AGC level from default -"
<< default_agc_config_.targetLeveldBOv << "dB to -"
<< config.targetLeveldBOv << "dB";
if (voe_wrapper_->processing()->SetAgcConfig(config) == -1) {
LOG_RTCERR1(SetAgcConfig, config.targetLeveldBOv);
return false;
}
return true;
}
// Configures echo cancellation and noise suppression modes according to
// whether or not we are in a multi-point conference.
bool WebRtcVoiceEngine::SetConferenceMode(bool enable) {
// Only use EC_AECM for mobile.
#if defined(IOS) || defined(ANDROID)
return true;
#endif
LOG(LS_INFO) << (enable ? "Enabling" : "Disabling")
<< " Conference Mode noise reduction";
// We always configure noise suppression on, so just toggle the mode.
const webrtc::NsModes ns_mode = enable ? webrtc::kNsConference
: webrtc::kNsDefault;
if (voe_wrapper_->processing()->SetNsStatus(true, ns_mode) == -1) {
LOG_RTCERR2(SetNsStatus, true, ns_mode);
return false;
}
// Echo-cancellation is a user-option, so preserve the enable state and
// just toggle the mode.
bool aec;
webrtc::EcModes ec_mode;
if (voe_wrapper_->processing()->GetEcStatus(aec, ec_mode) == -1) {
LOG_RTCERR0(GetEcStatus);
return false;
}
ec_mode = enable ? webrtc::kEcConference : webrtc::kEcDefault;
if (voe_wrapper_->processing()->SetEcStatus(aec, ec_mode) == -1) {
LOG_RTCERR2(SetEcStatus, aec, ec_mode);
return false;
}
return true;
}
// WebRtcVoiceMediaChannel
WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine)
: WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine>(
engine,
engine->voe()->base()->CreateChannel()),
channel_options_(0),
agc_adjusted_(false),
dtmf_allowed_(false),
desired_playout_(false),
playout_(false),
desired_send_(SEND_NOTHING),
send_(SEND_NOTHING) {
engine->RegisterChannel(this);
LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel "
<< voe_channel();
// Register external transport
if (engine->voe()->network()->RegisterExternalTransport(
voe_channel(), *static_cast<Transport*>(this)) == -1) {
LOG_RTCERR2(RegisterExternalTransport, voe_channel(), this);
}
// Enable RTCP (for quality stats and feedback messages)
EnableRtcp(voe_channel());
// Create a random but nonzero send SSRC
SetSendSsrc(talk_base::CreateRandomNonZeroId());
}
WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel() {
LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel "
<< voe_channel();
// DeRegister external transport
if (engine()->voe()->network()->DeRegisterExternalTransport(
voe_channel()) == -1) {
LOG_RTCERR1(DeRegisterExternalTransport, voe_channel());
}
// Unregister ourselves from the engine.
engine()->UnregisterChannel(this);
// Remove any remaining streams.
while (!mux_channels_.empty()) {
RemoveStream(mux_channels_.begin()->first);
}
// Delete the primary channel.
if (engine()->voe()->base()->DeleteChannel(voe_channel()) == -1) {
LOG_RTCERR1(DeleteChannel, voe_channel());
}
}
bool WebRtcVoiceMediaChannel::SetOptions(int flags) {
// Always accept flags that are unchanged.
if (channel_options_ == flags) {
return true;
}
// Reject new options if we're already sending.
if (send_ != SEND_NOTHING) {
return false;
}
// Save the options, to be interpreted where appropriate.
channel_options_ = flags;
return true;
}
bool WebRtcVoiceMediaChannel::SetRecvCodecs(
const std::vector<AudioCodec>& codecs) {
// Update our receive payload types to match what we offered. This only is
// an issue when a different entity (i.e. a server) is generating the offer
// for us.
bool ret = true;
for (std::vector<AudioCodec>::const_iterator i = codecs.begin();
i != codecs.end() && ret; ++i) {
webrtc::CodecInst gcodec;
if (engine()->FindWebRtcCodec(*i, &gcodec)) {
if (gcodec.pltype != i->id) {
LOG(LS_INFO) << "Updating payload type for " << gcodec.plname
<< " from " << gcodec.pltype << " to " << i->id;
gcodec.pltype = i->id;
if (engine()->voe()->codec()->SetRecPayloadType(
voe_channel(), gcodec) == -1) {
LOG_RTCERR1(SetRecPayloadType, voe_channel());
ret = false;
}
}
} else {
LOG(LS_WARNING) << "Unknown codec " << i->name;
ret = false;
}
}
return ret;
}
bool WebRtcVoiceMediaChannel::SetSendCodecs(
const std::vector<AudioCodec>& codecs) {
// Disable DTMF, VAD, and FEC unless we know the other side wants them.
dtmf_allowed_ = false;
engine()->voe()->codec()->SetVADStatus(voe_channel(), false);
engine()->voe()->rtp()->SetFECStatus(voe_channel(), false);
// Scan through the list to figure out the codec to use for sending, along
// with the proper configuration for VAD and DTMF.
bool first = true;
webrtc::CodecInst send_codec;
memset(&send_codec, 0, sizeof(send_codec));
for (std::vector<AudioCodec>::const_iterator i = codecs.begin();
i != codecs.end(); ++i) {
// Ignore codecs we don't know about. The negotiation step should prevent
// this, but double-check to be sure.
webrtc::CodecInst gcodec;
if (!engine()->FindWebRtcCodec(*i, &gcodec)) {
LOG(LS_WARNING) << "Unknown codec " << i->name;
continue;
}
// Find the DTMF telephone event "codec" and tell VoiceEngine about it.
if (i->name == "telephone-event" || i->name == "audio/telephone-event") {
engine()->voe()->dtmf()->SetSendTelephoneEventPayloadType(
voe_channel(), i->id);
dtmf_allowed_ = true;
}
// Turn voice activity detection/comfort noise on if supported.
// Set the wideband CN payload type appropriately.
// (narrowband always uses the static payload type 13).
if (i->name == "CN") {
webrtc::PayloadFrequencies cn_freq;
switch (i->clockrate) {
case 8000:
cn_freq = webrtc::kFreq8000Hz;
break;
case 16000:
cn_freq = webrtc::kFreq16000Hz;
break;
case 32000:
cn_freq = webrtc::kFreq32000Hz;
break;
default:
LOG(LS_WARNING) << "CN frequency " << i->clockrate
<< " not supported.";
continue;
}
engine()->voe()->codec()->SetVADStatus(voe_channel(), true);
if (cn_freq != webrtc::kFreq8000Hz) {
engine()->voe()->codec()->SetSendCNPayloadType(voe_channel(),
i->id, cn_freq);
}
}
// We'll use the first codec in the list to actually send audio data.
// Be sure to use the payload type requested by the remote side.
// "red", for FEC audio, is a special case where the actual codec to be
// used is specified in params.
if (first) {
if (i->name == "red") {
// Parse out the RED parameters. If we fail, just ignore RED;
// we don't support all possible params/usage scenarios.
if (!GetRedSendCodec(*i, codecs, &send_codec)) {
continue;
}
// Enable redundant encoding of the specified codec. Treat any
// failure as a fatal internal error.
LOG(LS_INFO) << "Enabling RED";
if (engine()->voe()->rtp()->SetFECStatus(voe_channel(),
true, i->id) == -1) {
LOG_RTCERR3(SetFECStatus, voe_channel(), true, i->id);
return false;
}
} else {
send_codec = gcodec;
send_codec.pltype = i->id;
}
first = false;
}
}
// If we're being asked to set an empty list of codecs, due to a buggy client,
// choose the most common format: PCMU
if (first) {
LOG(LS_WARNING) << "Received empty list of codecs; using PCMU/8000";
AudioCodec codec(0, "PCMU", 8000, 0, 1, 0);
engine()->FindWebRtcCodec(codec, &send_codec);
}
// Set the codec.
LOG(LS_INFO) << "Selected voice codec " << send_codec.plname
<< "/" << send_codec.plfreq;
if (engine()->voe()->codec()->SetSendCodec(voe_channel(),
send_codec) == -1) {
LOG_RTCERR1(SetSendCodec, voe_channel());
return false;
}
return true;
}
bool WebRtcVoiceMediaChannel::SetRecvRtpHeaderExtensions(
const std::vector<RtpHeaderExtension>& extensions) {
// We don't support any incoming extensions headers right now.
return true;
}
bool WebRtcVoiceMediaChannel::SetSendRtpHeaderExtensions(
const std::vector<RtpHeaderExtension>& extensions) {
// Enable the audio level extension header if requested.
std::vector<RtpHeaderExtension>::const_iterator it;
for (it = extensions.begin(); it != extensions.end(); ++it) {
if (it->uri == kRtpAudioLevelHeaderExtension) {
break;
}
}
bool enable = (it != extensions.end());
int id = 0;
if (enable) {
id = it->id;
if (id < kMinRtpHeaderExtensionId ||
id > kMaxRtpHeaderExtensionId) {
LOG(LS_WARNING) << "Invalid RTP header extension id " << id;
return false;
}
}
// This api call is not available in iOS version of VoiceEngine currently.
#if !defined(IOS) && !defined(ANDROID)
if (engine()->voe()->rtp()->SetRTPAudioLevelIndicationStatus(
voe_channel(), enable, id) == -1) {
LOG_RTCERR3(SetRTPAudioLevelIndicationStatus, voe_channel(), enable, id);
return false;
}
#endif
return true;
}
bool WebRtcVoiceMediaChannel::SetPlayout(bool playout) {
desired_playout_ = playout;
return ChangePlayout(desired_playout_);
}
bool WebRtcVoiceMediaChannel::PausePlayout() {
return ChangePlayout(false);
}
bool WebRtcVoiceMediaChannel::ResumePlayout() {
return ChangePlayout(desired_playout_);
}
bool WebRtcVoiceMediaChannel::ChangePlayout(bool playout) {
if (playout_ == playout) {
return true;
}
bool result = true;
if (mux_channels_.empty()) {
// Only toggle the default channel if we don't have any other channels.
result = SetPlayout(voe_channel(), playout);
}
for (ChannelMap::iterator it = mux_channels_.begin();
it != mux_channels_.end() && result; ++it) {
if (!SetPlayout(it->second, playout)) {
LOG(LS_ERROR) << "SetPlayout " << playout << " on channel " << it->second
<< " failed";
result = false;
}
}
if (result) {
playout_ = playout;
}
return result;
}
bool WebRtcVoiceMediaChannel::SetSend(SendFlags send) {
desired_send_ = send;
return ChangeSend(desired_send_);
}
bool WebRtcVoiceMediaChannel::PauseSend() {
return ChangeSend(SEND_NOTHING);
}
bool WebRtcVoiceMediaChannel::ResumeSend() {
return ChangeSend(desired_send_);
}
bool WebRtcVoiceMediaChannel::ChangeSend(SendFlags send) {
if (send_ == send) {
return true;
}
if (send == SEND_MICROPHONE) {
#ifdef CHROMEOS
// Conference mode doesn't work well on ChromeOS.
if (!engine()->SetConferenceMode(false)) {
LOG_RTCERR1(SetConferenceMode, voe_channel());
return false;
}
#else
// Multi-point conferences use conference-mode noise filtering.
if (!engine()->SetConferenceMode(
0 != (channel_options_ & OPT_CONFERENCE))) {
LOG_RTCERR1(SetConferenceMode, voe_channel());
return false;
}
#endif // CHROMEOS
// Tandberg-bridged conferences have an AGC target that is lower than
// GTV-only levels.
if ((channel_options_ & OPT_AGC_TANDBERG_LEVELS) && !agc_adjusted_) {
if (engine()->AdjustAgcLevel(kTandbergDbAdjustment)) {
agc_adjusted_ = true;
}
}
// VoiceEngine resets sequence number when StopSend is called. This
// sometimes causes libSRTP to complain about packets being
// replayed. To get around this we store the last sent sequence
// number and initializes the channel with the next to continue on
// the same sequence.
if (sequence_number() != -1) {
LOG(LS_INFO) << "WebRtcVoiceMediaChannel restores seqnum="
<< sequence_number() + 1;
if (engine()->voe()->sync()->SetInitSequenceNumber(
voe_channel(), sequence_number() + 1) == -1) {
LOG_RTCERR2(SetInitSequenceNumber, voe_channel(),
sequence_number() + 1);
}
}
if (engine()->voe()->base()->StartSend(voe_channel()) == -1) {
LOG_RTCERR1(StartSend, voe_channel());
return false;
}
if (engine()->voe()->file()->StopPlayingFileAsMicrophone(
voe_channel()) == -1) {
LOG_RTCERR1(StopPlayingFileAsMicrophone, voe_channel());
return false;
}
} else if (send == SEND_RINGBACKTONE) {
ASSERT(ringback_tone_.get() != NULL);
if (!ringback_tone_.get()) {
return false;
}
if (engine()->voe()->file()->StartPlayingFileAsMicrophone(
voe_channel(), ringback_tone_.get(), false) == -1) {
LOG_RTCERR3(StartPlayingFileAsMicrophone, voe_channel(),
ringback_tone_.get(), false);
return false;
}
// VoiceEngine resets sequence number when StopSend is called. This
// sometimes causes libSRTP to complain about packets being
// replayed. To get around this we store the last sent sequence
// number and initializes the channel with the next to continue on
// the same sequence.
if (sequence_number() != -1) {
LOG(LS_INFO) << "WebRtcVoiceMediaChannel restores seqnum="
<< sequence_number() + 1;
if (engine()->voe()->sync()->SetInitSequenceNumber(
voe_channel(), sequence_number() + 1) == -1) {
LOG_RTCERR2(SetInitSequenceNumber, voe_channel(),
sequence_number() + 1);
}
}
if (engine()->voe()->base()->StartSend(voe_channel()) == -1) {
LOG_RTCERR1(StartSend, voe_channel());
return false;
}
} else { // SEND_NOTHING
if (engine()->voe()->base()->StopSend(voe_channel()) == -1) {
LOG_RTCERR1(StopSend, voe_channel());
}
// Reset the AGC level, if it was set.
if (agc_adjusted_) {
if (engine()->AdjustAgcLevel(0)) {
agc_adjusted_ = false;
}
}
// Disable conference-mode noise filtering.
if (!engine()->SetConferenceMode(false)) {
LOG_RTCERR1(SetConferenceMode, voe_channel());
}
}
send_ = send;
return true;
}
bool WebRtcVoiceMediaChannel::AddStream(uint32 ssrc) {
talk_base::CritScope lock(&mux_channels_cs_);
if (mux_channels_.find(ssrc) != mux_channels_.end()) {
return false;
}
// Create a new channel for receiving audio data.
int channel = engine()->voe()->base()->CreateChannel();
if (channel == -1) {
LOG_RTCERR0(CreateChannel);
return false;
}
// Configure to use external transport, like our default channel.
if (engine()->voe()->network()->RegisterExternalTransport(
channel, *this) == -1) {
LOG_RTCERR2(SetExternalTransport, channel, this);
return false;
}
// Use the same SSRC as our default channel (so the RTCP reports are correct).
unsigned int send_ssrc;
webrtc::VoERTP_RTCP* rtp = engine()->voe()->rtp();
if (rtp->GetLocalSSRC(voe_channel(), send_ssrc) == -1) {
LOG_RTCERR2(GetSendSSRC, channel, send_ssrc);
return false;
}
if (rtp->SetLocalSSRC(channel, send_ssrc) == -1) {
LOG_RTCERR2(SetSendSSRC, channel, send_ssrc);
return false;
}
if (mux_channels_.empty() && playout_) {
// This is the first stream in a multi user meeting. We can now
// disable playback of the default stream. This since the default
// stream will probably have received some initial packets before
// the new stream was added. This will mean that the CN state from
// the default channel will be mixed in with the other streams
// throughout the whole meeting, which might be disturbing.
LOG(LS_INFO) << "Disabling playback on the default voice channel";
SetPlayout(voe_channel(), false);
}
mux_channels_[ssrc] = channel;
// TODO: We should rollback the add if SetPlayout fails.
LOG(LS_INFO) << "New audio stream " << ssrc
<< " registered to VoiceEngine channel #"
<< channel << ".";
return SetPlayout(channel, playout_);
}
bool WebRtcVoiceMediaChannel::RemoveStream(uint32 ssrc) {
talk_base::CritScope lock(&mux_channels_cs_);
ChannelMap::iterator it = mux_channels_.find(ssrc);
if (it != mux_channels_.end()) {
if (engine()->voe()->network()->DeRegisterExternalTransport(
it->second) == -1) {
LOG_RTCERR1(DeRegisterExternalTransport, it->second);
}
LOG(LS_INFO) << "Removing audio stream " << ssrc
<< " with VoiceEngine channel #"
<< it->second << ".";
if (engine()->voe()->base()->DeleteChannel(it->second) == -1) {
LOG_RTCERR1(DeleteChannel, voe_channel());
return false;
}
mux_channels_.erase(it);
if (mux_channels_.empty() && playout_) {
// The last stream was removed. We can now enable the default
// channel for new channels to be played out immediately without
// waiting for AddStream messages.
// TODO: Does the default channel still have it's CN state?
LOG(LS_INFO) << "Enabling playback on the default voice channel";
SetPlayout(voe_channel(), true);
}
}
return true;
}
bool WebRtcVoiceMediaChannel::GetActiveStreams(
AudioInfo::StreamList* actives) {
actives->clear();
for (ChannelMap::iterator it = mux_channels_.begin();
it != mux_channels_.end(); ++it) {
int level = GetOutputLevel(it->second);
if (level > 0) {
actives->push_back(std::make_pair(it->first, level));
}
}
return true;
}
int WebRtcVoiceMediaChannel::GetOutputLevel() {
// return the highest output level of all streams
int highest = GetOutputLevel(voe_channel());
for (ChannelMap::iterator it = mux_channels_.begin();
it != mux_channels_.end(); ++it) {
int level = GetOutputLevel(it->second);
highest = talk_base::_max(level, highest);
}
return highest;
}
bool WebRtcVoiceMediaChannel::SetOutputScaling(
uint32 ssrc, double left, double right) {
talk_base::CritScope lock(&mux_channels_cs_);
// Collect the channels to scale the output volume.
std::vector<int> channels;
if (0 == ssrc) { // Collect all channels, including the default one.
channels.push_back(voe_channel());
for (ChannelMap::const_iterator it = mux_channels_.begin();
it != mux_channels_.end(); ++it) {
channels.push_back(it->second);
}
} else { // Collect only the channel of the specified ssrc.
int channel = GetChannel(ssrc);
if (-1 == channel) {
LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
return false;
}
channels.push_back(channel);
}
// Scale the output volume for the collected channels. We first normalize to
// scale the volume and then set the left and right pan.
float scale = static_cast<float>(talk_base::_max(left, right));
if (scale > 0.0001f) {
left /= scale;
right /= scale;
}
for (std::vector<int>::const_iterator it = channels.begin();
it != channels.end(); ++it) {
if (-1 == engine()->voe()->volume()->SetChannelOutputVolumeScaling(
*it, scale)) {
LOG_RTCERR2(SetChannelOutputVolumeScaling, *it, scale);
return false;
}
if (-1 == engine()->voe()->volume()->SetOutputVolumePan(
*it, static_cast<float>(left), static_cast<float>(right))) {
LOG_RTCERR3(SetOutputVolumePan, *it, left, right);
// Do not return if fails. SetOutputVolumePan is not available for all
// pltforms.
}
LOG(LS_INFO) << "SetOutputScaling to left=" << left * scale
<< " right=" << right * scale
<< " for channel " << *it << " and ssrc " << ssrc;
}
return true;
}
bool WebRtcVoiceMediaChannel::GetOutputScaling(
uint32 ssrc, double* left, double* right) {
if (!left || !right) return false;
talk_base::CritScope lock(&mux_channels_cs_);
// Determine which channel based on ssrc.
int channel = (0 == ssrc) ? voe_channel() : GetChannel(ssrc);
if (channel == -1) {
LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
return false;
}
float scaling;
if (-1 == engine()->voe()->volume()->GetChannelOutputVolumeScaling(
channel, scaling)) {
LOG_RTCERR2(GetChannelOutputVolumeScaling, channel, scaling);
return false;
}
float left_pan;
float right_pan;
if (-1 == engine()->voe()->volume()->GetOutputVolumePan(
channel, left_pan, right_pan)) {
LOG_RTCERR3(GetOutputVolumePan, channel, left_pan, right_pan);
// If GetOutputVolumePan fails, we use the default left and right pan.
left_pan = 1.0f;
right_pan = 1.0f;
}
*left = scaling * left_pan;
*right = scaling * right_pan;
return true;
}
bool WebRtcVoiceMediaChannel::SetRingbackTone(const char *buf, int len) {
ringback_tone_.reset(new WebRtcSoundclipStream(buf, len));
return true;
}
bool WebRtcVoiceMediaChannel::PlayRingbackTone(uint32 ssrc,
bool play, bool loop) {
if (!ringback_tone_.get()) {
return false;
}
// Determine which VoiceEngine channel to play on.
int channel = (ssrc == 0) ? voe_channel() : GetChannel(ssrc);
if (channel == -1) {
return false;
}
// Make sure the ringtone is cued properly, and play it out.
if (play) {
ringback_tone_->set_loop(loop);
ringback_tone_->Rewind();
if (engine()->voe()->file()->StartPlayingFileLocally(channel,
ringback_tone_.get()) == -1) {
LOG_RTCERR2(StartPlayingFileLocally, channel, ringback_tone_.get());
LOG(LS_ERROR) << "Unable to start ringback tone";
return false;
}
ringback_channels_.insert(channel);
LOG(LS_INFO) << "Started ringback on channel " << channel;
} else {
if (engine()->voe()->file()->StopPlayingFileLocally(channel)
== -1) {
LOG_RTCERR1(StopPlayingFileLocally, channel);
return false;
}
LOG(LS_INFO) << "Stopped ringback on channel " << channel;
ringback_channels_.erase(channel);
}
return true;
}
bool WebRtcVoiceMediaChannel::PressDTMF(int event, bool playout) {
if (!dtmf_allowed_) {
return false;
}
// Enable or disable DTMF playout of this tone as requested. This will linger
// until the next call to this method, but that's OK.
if (engine()->voe()->dtmf()->SetDtmfFeedbackStatus(playout) == -1) {
LOG_RTCERR2(SendDTMF, voe_channel(), playout);
return false;
}
// Send DTMF using out-of-band DTMF. ("true", as 3rd arg)
if (engine()->voe()->dtmf()->SendTelephoneEvent(voe_channel(), event,
true) == -1) {
LOG_RTCERR3(SendDTMF, voe_channel(), event, true);
return false;
}
return true;
}
void WebRtcVoiceMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
// Pick which channel to send this packet to. If this packet doesn't match
// any multiplexed streams, just send it to the default channel. Otherwise,
// send it to the specific decoder instance for that stream.
int which_channel = GetChannel(
ParseSsrc(packet->data(), packet->length(), false));
if (which_channel == -1) {
which_channel = voe_channel();
}
// Stop any ringback that might be playing on the channel.
// It's possible the ringback has already stopped, ih which case we'll just
// use the opportunity to remove the channel from ringback_channels_.
const std::set<int>::iterator it = ringback_channels_.find(which_channel);
if (it != ringback_channels_.end()) {
if (engine()->voe()->file()->IsPlayingFileLocally(
which_channel) == 1) {
engine()->voe()->file()->StopPlayingFileLocally(which_channel);
LOG(LS_INFO) << "Stopped ringback on channel " << which_channel
<< " due to incoming media";
}
ringback_channels_.erase(which_channel);
}
// Pass it off to the decoder.
engine()->voe()->network()->ReceivedRTPPacket(which_channel,
packet->data(),
packet->length());
}
void WebRtcVoiceMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
// See above.
int which_channel = GetChannel(
ParseSsrc(packet->data(), packet->length(), true));
if (which_channel == -1) {
which_channel = voe_channel();
}
engine()->voe()->network()->ReceivedRTCPPacket(which_channel,
packet->data(),
packet->length());
}
void WebRtcVoiceMediaChannel::SetSendSsrc(uint32 ssrc) {
if (engine()->voe()->rtp()->SetLocalSSRC(voe_channel(), ssrc)
== -1) {
LOG_RTCERR2(SetSendSSRC, voe_channel(), ssrc);
}
}
bool WebRtcVoiceMediaChannel::SetRtcpCName(const std::string& cname) {
if (engine()->voe()->rtp()->SetRTCP_CNAME(voe_channel(),
cname.c_str()) == -1) {
LOG_RTCERR2(SetRTCP_CNAME, voe_channel(), cname);
return false;
}
return true;
}
bool WebRtcVoiceMediaChannel::Mute(bool muted) {
if (engine()->voe()->volume()->SetInputMute(voe_channel(),
muted) == -1) {
LOG_RTCERR2(SetInputMute, voe_channel(), muted);
return false;
}
return true;
}
bool WebRtcVoiceMediaChannel::GetStats(VoiceMediaInfo* info) {
// In VoiceEngine 3.5, GetRTCPStatistics will return 0 even when it fails,
// causing the stats to contain garbage information. To prevent this, we
// zero the stats structure before calling this API.
// TODO: Remove this workaround.
webrtc::CallStatistics cs;
unsigned int ssrc;
webrtc::CodecInst codec;
unsigned int level;
// Fill in the sender info, based on what we know, and what the
// remote side told us it got from its RTCP report.
VoiceSenderInfo sinfo;
memset(&sinfo, 0, sizeof(sinfo));
// Data we obtain locally.
memset(&cs, 0, sizeof(cs));
if (engine()->voe()->rtp()->GetRTCPStatistics(voe_channel(), cs) == -1 ||
engine()->voe()->rtp()->GetLocalSSRC(voe_channel(), ssrc) == -1) {
return false;
}
sinfo.ssrc = ssrc;
sinfo.bytes_sent = cs.bytesSent;
sinfo.packets_sent = cs.packetsSent;
// RTT isn't known until a RTCP report is received. Until then, VoiceEngine
// returns 0 to indicate an error value.
sinfo.rtt_ms = (cs.rttMs > 0) ? cs.rttMs : -1;
// Data from the last remote RTCP report.
unsigned int ntp_high, ntp_low, timestamp, ptimestamp, jitter;
unsigned short loss; // NOLINT
if (engine()->voe()->rtp()->GetRemoteRTCPData(voe_channel(),
ntp_high, ntp_low, timestamp, ptimestamp, &jitter, &loss) != -1 &&
engine()->voe()->codec()->GetSendCodec(voe_channel(),
codec) != -1) {
// Convert Q8 to floating point.
sinfo.fraction_lost = static_cast<float>(loss) / (1 << 8);
// Convert samples to milliseconds.
if (codec.plfreq / 1000 > 0) {
sinfo.jitter_ms = jitter / (codec.plfreq / 1000);
}
} else {
sinfo.fraction_lost = -1;
sinfo.jitter_ms = -1;
}
// TODO: Figure out how to get remote packets_lost, ext_seqnum
sinfo.packets_lost = -1;
sinfo.ext_seqnum = -1;
// Local speech level.
sinfo.audio_level = (engine()->voe()->volume()->
GetSpeechInputLevelFullRange(level) != -1) ? level : -1;
info->senders.push_back(sinfo);
// Build the list of receivers, one for each mux channel, or 1 in a 1:1 call.
std::vector<int> channels;
for (ChannelMap::const_iterator it = mux_channels_.begin();
it != mux_channels_.end(); ++it) {
channels.push_back(it->second);
}
if (channels.empty()) {
channels.push_back(voe_channel());
}
// Get the SSRC and stats for each receiver, based on our own calculations.
for (std::vector<int>::const_iterator it = channels.begin();
it != channels.end(); ++it) {
memset(&cs, 0, sizeof(cs));
if (engine()->voe()->rtp()->GetRemoteSSRC(*it, ssrc) != -1 &&
engine()->voe()->rtp()->GetRTCPStatistics(*it, cs) != -1 &&
engine()->voe()->codec()->GetRecCodec(*it, codec) != -1) {
VoiceReceiverInfo rinfo;
memset(&rinfo, 0, sizeof(rinfo));
rinfo.ssrc = ssrc;
rinfo.bytes_rcvd = cs.bytesReceived;
rinfo.packets_rcvd = cs.packetsReceived;
// The next four fields are from the most recently sent RTCP report.
// Convert Q8 to floating point.
rinfo.fraction_lost = static_cast<float>(cs.fractionLost) / (1 << 8);
rinfo.packets_lost = cs.cumulativeLost;
rinfo.ext_seqnum = cs.extendedMax;
// Convert samples to milliseconds.
if (codec.plfreq / 1000 > 0) {
rinfo.jitter_ms = cs.jitterSamples / (codec.plfreq / 1000);
}
// Get jitter buffer and total delay (alg + jitter + playout) stats.
webrtc::NetworkStatistics ns;
if (engine()->voe()->neteq() &&
engine()->voe()->neteq()->GetNetworkStatistics(
*it, ns) != -1) {
rinfo.jitter_buffer_ms = ns.currentBufferSize;
rinfo.jitter_buffer_preferred_ms = ns.preferredBufferSize;
}
if (engine()->voe()->sync()) {
engine()->voe()->sync()->GetDelayEstimate(*it,
rinfo.delay_estimate_ms);
}
// Get speech level.
rinfo.audio_level = (engine()->voe()->volume()->
GetSpeechOutputLevelFullRange(*it, level) != -1) ? level : -1;
info->receivers.push_back(rinfo);
}
}
return true;
}
void WebRtcVoiceMediaChannel::GetLastMediaError(
uint32* ssrc, VoiceMediaChannel::Error* error) {
ASSERT(ssrc != NULL);
ASSERT(error != NULL);
FindSsrc(voe_channel(), ssrc);
*error = WebRtcErrorToChannelError(GetLastEngineError());
}
bool WebRtcVoiceMediaChannel::FindSsrc(int channel_num, uint32* ssrc) {
talk_base::CritScope lock(&mux_channels_cs_);
ASSERT(ssrc != NULL);
if (channel_num == voe_channel()) {
unsigned local_ssrc = 0;
// This is a sending channel.
if (engine()->voe()->rtp()->GetLocalSSRC(
channel_num, local_ssrc) != -1) {
*ssrc = local_ssrc;
}
return true;
} else if (channel_num == -1 && send_ != SEND_NOTHING) {
// Sometimes the VoiceEngine core will throw error with channel_num = -1.
// This means the error is not limited to a specific channel. Signal the
// message using ssrc=0. If the current channel is sending, use this
// channel for sending the message.
*ssrc = 0;
return true;
} else {
// Check whether this is a receiving channel.
for (ChannelMap::const_iterator it = mux_channels_.begin();
it != mux_channels_.end(); ++it) {
if (it->second == channel_num) {
*ssrc = it->first;
return true;
}
}
}
return false;
}
void WebRtcVoiceMediaChannel::OnError(uint32 ssrc, int error) {
SignalMediaError(ssrc, WebRtcErrorToChannelError(error));
}
int WebRtcVoiceMediaChannel::GetOutputLevel(int channel) {
unsigned int ulevel;
int ret =
engine()->voe()->volume()->GetSpeechOutputLevel(channel, ulevel);
return (ret == 0) ? static_cast<int>(ulevel) : -1;
}
int WebRtcVoiceMediaChannel::GetChannel(uint32 ssrc) {
ChannelMap::iterator it = mux_channels_.find(ssrc);
return (it != mux_channels_.end()) ? it->second : -1;
}
bool WebRtcVoiceMediaChannel::GetRedSendCodec(const AudioCodec& red_codec,
const std::vector<AudioCodec>& all_codecs, webrtc::CodecInst* send_codec) {
// Get the RED encodings from the parameter with no name. This may
// change based on what is discussed on the Jingle list.
// The encoding parameter is of the form "a/b"; we only support where
// a == b. Verify this and parse out the value into red_pt.
// If the parameter value is absent (as it will be until we wire up the
// signaling of this message), use the second codec specified (i.e. the
// one after "red") as the encoding parameter.
int red_pt = -1;
std::string red_params;
CodecParameterMap::const_iterator it = red_codec.params.find("");
if (it != red_codec.params.end()) {
red_params = it->second;
std::vector<std::string> red_pts;
if (talk_base::split(red_params, '/', &red_pts) != 2 ||
red_pts[0] != red_pts[1] ||
!talk_base::FromString(red_pts[0], &red_pt)) {
LOG(LS_WARNING) << "RED params " << red_params << " not supported.";
return false;
}
} else if (red_codec.params.empty()) {
LOG(LS_WARNING) << "RED params not present, using defaults";
if (all_codecs.size() > 1) {
red_pt = all_codecs[1].id;
}
}
// Try to find red_pt in |codecs|.
std::vector<AudioCodec>::const_iterator codec;
for (codec = all_codecs.begin(); codec != all_codecs.end(); ++codec) {
if (codec->id == red_pt)
break;
}
// If we find the right codec, that will be the codec we pass to
// SetSendCodec, with the desired payload type.
if (codec != all_codecs.end() &&
engine()->FindWebRtcCodec(*codec, send_codec)) {
send_codec->pltype = red_pt;
} else {
LOG(LS_WARNING) << "RED params " << red_params << " are invalid.";
return false;
}
return true;
}
bool WebRtcVoiceMediaChannel::EnableRtcp(int channel) {
if (engine()->voe()->rtp()->SetRTCPStatus(channel, true) == -1) {
LOG_RTCERR2(SetRTCPStatus, voe_channel(), 1);
return false;
}
// TODO: Enable VQMon and RTCP XR reports, once we know what
// what we want to do with them.
// engine()->voe().EnableVQMon(voe_channel(), true);
// engine()->voe().EnableRTCP_XR(voe_channel(), true);
return true;
}
bool WebRtcVoiceMediaChannel::SetPlayout(int channel, bool playout) {
if (playout) {
LOG(LS_INFO) << "Starting playout for channel #" << channel;
if (engine()->voe()->base()->StartPlayout(channel) == -1) {
LOG_RTCERR1(StartPlayout, channel);
return false;
}
} else {
LOG(LS_INFO) << "Stopping playout for channel #" << channel;
engine()->voe()->base()->StopPlayout(channel);
}
return true;
}
uint32 WebRtcVoiceMediaChannel::ParseSsrc(const void* data, size_t len,
bool rtcp) {
size_t ssrc_pos = (!rtcp) ? 8 : 4;
uint32 ssrc = 0;
if (len >= (ssrc_pos + sizeof(ssrc))) {
ssrc = talk_base::GetBE32(static_cast<const char*>(data) + ssrc_pos);
}
return ssrc;
}
// Convert VoiceEngine error code into VoiceMediaChannel::Error enum.
VoiceMediaChannel::Error
WebRtcVoiceMediaChannel::WebRtcErrorToChannelError(int err_code) {
switch (err_code) {
case 0:
return ERROR_NONE;
case VE_CANNOT_START_RECORDING:
case VE_MIC_VOL_ERROR:
case VE_GET_MIC_VOL_ERROR:
case VE_CANNOT_ACCESS_MIC_VOL:
return ERROR_REC_DEVICE_OPEN_FAILED;
case VE_SATURATION_WARNING:
return ERROR_REC_DEVICE_SATURATION;
case VE_REC_DEVICE_REMOVED:
return ERROR_REC_DEVICE_REMOVED;
case VE_RUNTIME_REC_WARNING:
case VE_RUNTIME_REC_ERROR:
return ERROR_REC_RUNTIME_ERROR;
case VE_CANNOT_START_PLAYOUT:
case VE_SPEAKER_VOL_ERROR:
case VE_GET_SPEAKER_VOL_ERROR:
case VE_CANNOT_ACCESS_SPEAKER_VOL:
return ERROR_PLAY_DEVICE_OPEN_FAILED;
case VE_RUNTIME_PLAY_WARNING:
case VE_RUNTIME_PLAY_ERROR:
return ERROR_PLAY_RUNTIME_ERROR;
case VE_TYPING_NOISE_WARNING:
return ERROR_REC_TYPING_NOISE_DETECTED;
default:
return VoiceMediaChannel::ERROR_OTHER;
}
}
int WebRtcSoundclipStream::Read(void *buf, int len) {
size_t res = 0;
mem_.Read(buf, len, &res, NULL);
return res;
}
int WebRtcSoundclipStream::Rewind() {
mem_.Rewind();
// Return -1 to keep VoiceEngine from looping.
return (loop_) ? 0 : -1;
}
} // namespace cricket
#endif // HAVE_WEBRTC_VOICE