1/* 2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#include "webrtc/voice_engine/transmit_mixer.h" 12 13#include "webrtc/modules/utility/interface/audio_frame_operations.h" 14#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" 15#include "webrtc/system_wrappers/interface/event_wrapper.h" 16#include "webrtc/system_wrappers/interface/logging.h" 17#include "webrtc/system_wrappers/interface/trace.h" 18#include "webrtc/voice_engine/channel.h" 19#include "webrtc/voice_engine/channel_manager.h" 20#include "webrtc/voice_engine/include/voe_external_media.h" 21#include "webrtc/voice_engine/statistics.h" 22#include "webrtc/voice_engine/utility.h" 23#include "webrtc/voice_engine/voe_base_impl.h" 24 25#define WEBRTC_ABS(a) (((a) < 0) ? -(a) : (a)) 26 27namespace webrtc { 28namespace voe { 29 30// TODO(ajm): The thread safety of this is dubious... 31void 32TransmitMixer::OnPeriodicProcess() 33{ 34 WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId, -1), 35 "TransmitMixer::OnPeriodicProcess()"); 36 37#if defined(WEBRTC_VOICE_ENGINE_TYPING_DETECTION) 38 if (_typingNoiseWarningPending) 39 { 40 CriticalSectionScoped cs(&_callbackCritSect); 41 if (_voiceEngineObserverPtr) 42 { 43 if (_typingNoiseDetected) { 44 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 45 "TransmitMixer::OnPeriodicProcess() => " 46 "CallbackOnError(VE_TYPING_NOISE_WARNING)"); 47 _voiceEngineObserverPtr->CallbackOnError( 48 -1, 49 VE_TYPING_NOISE_WARNING); 50 } else { 51 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 52 "TransmitMixer::OnPeriodicProcess() => " 53 "CallbackOnError(VE_TYPING_NOISE_OFF_WARNING)"); 54 _voiceEngineObserverPtr->CallbackOnError( 55 -1, 56 VE_TYPING_NOISE_OFF_WARNING); 57 } 58 } 59 _typingNoiseWarningPending = false; 60 } 61#endif 62 63 bool saturationWarning = false; 64 { 65 // Modify |_saturationWarning| under lock to avoid conflict with write op 66 // in ProcessAudio and also ensure that we don't hold the lock during the 67 // callback. 68 CriticalSectionScoped cs(&_critSect); 69 saturationWarning = _saturationWarning; 70 if (_saturationWarning) 71 _saturationWarning = false; 72 } 73 74 if (saturationWarning) 75 { 76 CriticalSectionScoped cs(&_callbackCritSect); 77 if (_voiceEngineObserverPtr) 78 { 79 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 80 "TransmitMixer::OnPeriodicProcess() =>" 81 " CallbackOnError(VE_SATURATION_WARNING)"); 82 _voiceEngineObserverPtr->CallbackOnError(-1, VE_SATURATION_WARNING); 83 } 84 } 85} 86 87 88void TransmitMixer::PlayNotification(int32_t id, 89 uint32_t durationMs) 90{ 91 WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId, -1), 92 "TransmitMixer::PlayNotification(id=%d, durationMs=%d)", 93 id, durationMs); 94 95 // Not implement yet 96} 97 98void TransmitMixer::RecordNotification(int32_t id, 99 uint32_t durationMs) 100{ 101 WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId,-1), 102 "TransmitMixer::RecordNotification(id=%d, durationMs=%d)", 103 id, durationMs); 104 105 // Not implement yet 106} 107 108void TransmitMixer::PlayFileEnded(int32_t id) 109{ 110 WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId, -1), 111 "TransmitMixer::PlayFileEnded(id=%d)", id); 112 113 assert(id == _filePlayerId); 114 115 CriticalSectionScoped cs(&_critSect); 116 117 _filePlaying = false; 118 WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, VoEId(_instanceId, -1), 119 "TransmitMixer::PlayFileEnded() =>" 120 "file player module is shutdown"); 121} 122 123void 124TransmitMixer::RecordFileEnded(int32_t id) 125{ 126 WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId, -1), 127 "TransmitMixer::RecordFileEnded(id=%d)", id); 128 129 if (id == _fileRecorderId) 130 { 131 CriticalSectionScoped cs(&_critSect); 132 _fileRecording = false; 133 WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, VoEId(_instanceId, -1), 134 "TransmitMixer::RecordFileEnded() => fileRecorder module" 135 "is shutdown"); 136 } else if (id == _fileCallRecorderId) 137 { 138 CriticalSectionScoped cs(&_critSect); 139 _fileCallRecording = false; 140 WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, VoEId(_instanceId, -1), 141 "TransmitMixer::RecordFileEnded() => fileCallRecorder" 142 "module is shutdown"); 143 } 144} 145 146int32_t 147TransmitMixer::Create(TransmitMixer*& mixer, uint32_t instanceId) 148{ 149 WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(instanceId, -1), 150 "TransmitMixer::Create(instanceId=%d)", instanceId); 151 mixer = new TransmitMixer(instanceId); 152 if (mixer == NULL) 153 { 154 WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(instanceId, -1), 155 "TransmitMixer::Create() unable to allocate memory" 156 "for mixer"); 157 return -1; 158 } 159 return 0; 160} 161 162void 163TransmitMixer::Destroy(TransmitMixer*& mixer) 164{ 165 if (mixer) 166 { 167 delete mixer; 168 mixer = NULL; 169 } 170} 171 172TransmitMixer::TransmitMixer(uint32_t instanceId) : 173 _engineStatisticsPtr(NULL), 174 _channelManagerPtr(NULL), 175 audioproc_(NULL), 176 _voiceEngineObserverPtr(NULL), 177 _processThreadPtr(NULL), 178 _filePlayerPtr(NULL), 179 _fileRecorderPtr(NULL), 180 _fileCallRecorderPtr(NULL), 181 // Avoid conflict with other channels by adding 1024 - 1026, 182 // won't use as much as 1024 channels. 183 _filePlayerId(instanceId + 1024), 184 _fileRecorderId(instanceId + 1025), 185 _fileCallRecorderId(instanceId + 1026), 186 _filePlaying(false), 187 _fileRecording(false), 188 _fileCallRecording(false), 189 _audioLevel(), 190 _critSect(*CriticalSectionWrapper::CreateCriticalSection()), 191 _callbackCritSect(*CriticalSectionWrapper::CreateCriticalSection()), 192#ifdef WEBRTC_VOICE_ENGINE_TYPING_DETECTION 193 _typingNoiseWarningPending(false), 194 _typingNoiseDetected(false), 195#endif 196 _saturationWarning(false), 197 _instanceId(instanceId), 198 _mixFileWithMicrophone(false), 199 _captureLevel(0), 200 external_postproc_ptr_(NULL), 201 external_preproc_ptr_(NULL), 202 _mute(false), 203 _remainingMuteMicTimeMs(0), 204 stereo_codec_(false), 205 swap_stereo_channels_(false) 206{ 207 WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_instanceId, -1), 208 "TransmitMixer::TransmitMixer() - ctor"); 209} 210 211TransmitMixer::~TransmitMixer() 212{ 213 WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_instanceId, -1), 214 "TransmitMixer::~TransmitMixer() - dtor"); 215 _monitorModule.DeRegisterObserver(); 216 if (_processThreadPtr) 217 { 218 _processThreadPtr->DeRegisterModule(&_monitorModule); 219 } 220 DeRegisterExternalMediaProcessing(kRecordingAllChannelsMixed); 221 DeRegisterExternalMediaProcessing(kRecordingPreprocessing); 222 { 223 CriticalSectionScoped cs(&_critSect); 224 if (_fileRecorderPtr) 225 { 226 _fileRecorderPtr->RegisterModuleFileCallback(NULL); 227 _fileRecorderPtr->StopRecording(); 228 FileRecorder::DestroyFileRecorder(_fileRecorderPtr); 229 _fileRecorderPtr = NULL; 230 } 231 if (_fileCallRecorderPtr) 232 { 233 _fileCallRecorderPtr->RegisterModuleFileCallback(NULL); 234 _fileCallRecorderPtr->StopRecording(); 235 FileRecorder::DestroyFileRecorder(_fileCallRecorderPtr); 236 _fileCallRecorderPtr = NULL; 237 } 238 if (_filePlayerPtr) 239 { 240 _filePlayerPtr->RegisterModuleFileCallback(NULL); 241 _filePlayerPtr->StopPlayingFile(); 242 FilePlayer::DestroyFilePlayer(_filePlayerPtr); 243 _filePlayerPtr = NULL; 244 } 245 } 246 delete &_critSect; 247 delete &_callbackCritSect; 248} 249 250int32_t 251TransmitMixer::SetEngineInformation(ProcessThread& processThread, 252 Statistics& engineStatistics, 253 ChannelManager& channelManager) 254{ 255 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 256 "TransmitMixer::SetEngineInformation()"); 257 258 _processThreadPtr = &processThread; 259 _engineStatisticsPtr = &engineStatistics; 260 _channelManagerPtr = &channelManager; 261 262 if (_processThreadPtr->RegisterModule(&_monitorModule) == -1) 263 { 264 WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1), 265 "TransmitMixer::SetEngineInformation() failed to" 266 "register the monitor module"); 267 } else 268 { 269 _monitorModule.RegisterObserver(*this); 270 } 271 272 return 0; 273} 274 275int32_t 276TransmitMixer::RegisterVoiceEngineObserver(VoiceEngineObserver& observer) 277{ 278 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 279 "TransmitMixer::RegisterVoiceEngineObserver()"); 280 CriticalSectionScoped cs(&_callbackCritSect); 281 282 if (_voiceEngineObserverPtr) 283 { 284 _engineStatisticsPtr->SetLastError( 285 VE_INVALID_OPERATION, kTraceError, 286 "RegisterVoiceEngineObserver() observer already enabled"); 287 return -1; 288 } 289 _voiceEngineObserverPtr = &observer; 290 return 0; 291} 292 293int32_t 294TransmitMixer::SetAudioProcessingModule(AudioProcessing* audioProcessingModule) 295{ 296 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 297 "TransmitMixer::SetAudioProcessingModule(" 298 "audioProcessingModule=0x%x)", 299 audioProcessingModule); 300 audioproc_ = audioProcessingModule; 301 return 0; 302} 303 304void TransmitMixer::GetSendCodecInfo(int* max_sample_rate, int* max_channels) { 305 *max_sample_rate = 8000; 306 *max_channels = 1; 307 for (ChannelManager::Iterator it(_channelManagerPtr); it.IsValid(); 308 it.Increment()) { 309 Channel* channel = it.GetChannel(); 310 if (channel->Sending()) { 311 CodecInst codec; 312 channel->GetSendCodec(codec); 313 *max_sample_rate = std::max(*max_sample_rate, codec.plfreq); 314 *max_channels = std::max(*max_channels, codec.channels); 315 } 316 } 317} 318 319int32_t 320TransmitMixer::PrepareDemux(const void* audioSamples, 321 uint32_t nSamples, 322 uint8_t nChannels, 323 uint32_t samplesPerSec, 324 uint16_t totalDelayMS, 325 int32_t clockDrift, 326 uint16_t currentMicLevel, 327 bool keyPressed) 328{ 329 WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId, -1), 330 "TransmitMixer::PrepareDemux(nSamples=%u, nChannels=%u," 331 "samplesPerSec=%u, totalDelayMS=%u, clockDrift=%d," 332 "currentMicLevel=%u)", nSamples, nChannels, samplesPerSec, 333 totalDelayMS, clockDrift, currentMicLevel); 334 335 // --- Resample input audio and create/store the initial audio frame 336 GenerateAudioFrame(static_cast<const int16_t*>(audioSamples), 337 nSamples, 338 nChannels, 339 samplesPerSec); 340 341 { 342 CriticalSectionScoped cs(&_callbackCritSect); 343 if (external_preproc_ptr_) { 344 external_preproc_ptr_->Process(-1, kRecordingPreprocessing, 345 _audioFrame.data_, 346 _audioFrame.samples_per_channel_, 347 _audioFrame.sample_rate_hz_, 348 _audioFrame.num_channels_ == 2); 349 } 350 } 351 352 // --- Near-end audio processing. 353 ProcessAudio(totalDelayMS, clockDrift, currentMicLevel, keyPressed); 354 355 if (swap_stereo_channels_ && stereo_codec_) 356 // Only bother swapping if we're using a stereo codec. 357 AudioFrameOperations::SwapStereoChannels(&_audioFrame); 358 359 // --- Annoying typing detection (utilizes the APM/VAD decision) 360#ifdef WEBRTC_VOICE_ENGINE_TYPING_DETECTION 361 TypingDetection(keyPressed); 362#endif 363 364 // --- Mute during DTMF tone if direct feedback is enabled 365 if (_remainingMuteMicTimeMs > 0) 366 { 367 AudioFrameOperations::Mute(_audioFrame); 368 _remainingMuteMicTimeMs -= 10; 369 if (_remainingMuteMicTimeMs < 0) 370 { 371 _remainingMuteMicTimeMs = 0; 372 } 373 } 374 375 // --- Mute signal 376 if (_mute) 377 { 378 AudioFrameOperations::Mute(_audioFrame); 379 } 380 381 // --- Mix with file (does not affect the mixing frequency) 382 if (_filePlaying) 383 { 384 MixOrReplaceAudioWithFile(_audioFrame.sample_rate_hz_); 385 } 386 387 // --- Record to file 388 bool file_recording = false; 389 { 390 CriticalSectionScoped cs(&_critSect); 391 file_recording = _fileRecording; 392 } 393 if (file_recording) 394 { 395 RecordAudioToFile(_audioFrame.sample_rate_hz_); 396 } 397 398 { 399 CriticalSectionScoped cs(&_callbackCritSect); 400 if (external_postproc_ptr_) { 401 external_postproc_ptr_->Process(-1, kRecordingAllChannelsMixed, 402 _audioFrame.data_, 403 _audioFrame.samples_per_channel_, 404 _audioFrame.sample_rate_hz_, 405 _audioFrame.num_channels_ == 2); 406 } 407 } 408 409 // --- Measure audio level of speech after all processing. 410 _audioLevel.ComputeLevel(_audioFrame); 411 return 0; 412} 413 414int32_t 415TransmitMixer::DemuxAndMix() 416{ 417 WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId, -1), 418 "TransmitMixer::DemuxAndMix()"); 419 420 for (ChannelManager::Iterator it(_channelManagerPtr); it.IsValid(); 421 it.Increment()) 422 { 423 Channel* channelPtr = it.GetChannel(); 424 if (channelPtr->Sending()) 425 { 426 // Demultiplex makes a copy of its input. 427 channelPtr->Demultiplex(_audioFrame); 428 channelPtr->PrepareEncodeAndSend(_audioFrame.sample_rate_hz_); 429 } 430 } 431 return 0; 432} 433 434void TransmitMixer::DemuxAndMix(const int voe_channels[], 435 int number_of_voe_channels) { 436 for (int i = 0; i < number_of_voe_channels; ++i) { 437 voe::ChannelOwner ch = _channelManagerPtr->GetChannel(voe_channels[i]); 438 voe::Channel* channel_ptr = ch.channel(); 439 if (channel_ptr) { 440 if (channel_ptr->Sending()) { 441 // Demultiplex makes a copy of its input. 442 channel_ptr->Demultiplex(_audioFrame); 443 channel_ptr->PrepareEncodeAndSend(_audioFrame.sample_rate_hz_); 444 } 445 } 446 } 447} 448 449int32_t 450TransmitMixer::EncodeAndSend() 451{ 452 WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId, -1), 453 "TransmitMixer::EncodeAndSend()"); 454 455 for (ChannelManager::Iterator it(_channelManagerPtr); it.IsValid(); 456 it.Increment()) 457 { 458 Channel* channelPtr = it.GetChannel(); 459 if (channelPtr->Sending()) 460 { 461 channelPtr->EncodeAndSend(); 462 } 463 } 464 return 0; 465} 466 467void TransmitMixer::EncodeAndSend(const int voe_channels[], 468 int number_of_voe_channels) { 469 for (int i = 0; i < number_of_voe_channels; ++i) { 470 voe::ChannelOwner ch = _channelManagerPtr->GetChannel(voe_channels[i]); 471 voe::Channel* channel_ptr = ch.channel(); 472 if (channel_ptr && channel_ptr->Sending()) 473 channel_ptr->EncodeAndSend(); 474 } 475} 476 477uint32_t TransmitMixer::CaptureLevel() const 478{ 479 return _captureLevel; 480} 481 482void 483TransmitMixer::UpdateMuteMicrophoneTime(uint32_t lengthMs) 484{ 485 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 486 "TransmitMixer::UpdateMuteMicrophoneTime(lengthMs=%d)", 487 lengthMs); 488 _remainingMuteMicTimeMs = lengthMs; 489} 490 491int32_t 492TransmitMixer::StopSend() 493{ 494 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 495 "TransmitMixer::StopSend()"); 496 _audioLevel.Clear(); 497 return 0; 498} 499 500int TransmitMixer::StartPlayingFileAsMicrophone(const char* fileName, 501 bool loop, 502 FileFormats format, 503 int startPosition, 504 float volumeScaling, 505 int stopPosition, 506 const CodecInst* codecInst) 507{ 508 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 509 "TransmitMixer::StartPlayingFileAsMicrophone(" 510 "fileNameUTF8[]=%s,loop=%d, format=%d, volumeScaling=%5.3f," 511 " startPosition=%d, stopPosition=%d)", fileName, loop, 512 format, volumeScaling, startPosition, stopPosition); 513 514 if (_filePlaying) 515 { 516 _engineStatisticsPtr->SetLastError( 517 VE_ALREADY_PLAYING, kTraceWarning, 518 "StartPlayingFileAsMicrophone() is already playing"); 519 return 0; 520 } 521 522 CriticalSectionScoped cs(&_critSect); 523 524 // Destroy the old instance 525 if (_filePlayerPtr) 526 { 527 _filePlayerPtr->RegisterModuleFileCallback(NULL); 528 FilePlayer::DestroyFilePlayer(_filePlayerPtr); 529 _filePlayerPtr = NULL; 530 } 531 532 // Dynamically create the instance 533 _filePlayerPtr 534 = FilePlayer::CreateFilePlayer(_filePlayerId, 535 (const FileFormats) format); 536 537 if (_filePlayerPtr == NULL) 538 { 539 _engineStatisticsPtr->SetLastError( 540 VE_INVALID_ARGUMENT, kTraceError, 541 "StartPlayingFileAsMicrophone() filePlayer format isnot correct"); 542 return -1; 543 } 544 545 const uint32_t notificationTime(0); 546 547 if (_filePlayerPtr->StartPlayingFile( 548 fileName, 549 loop, 550 startPosition, 551 volumeScaling, 552 notificationTime, 553 stopPosition, 554 (const CodecInst*) codecInst) != 0) 555 { 556 _engineStatisticsPtr->SetLastError( 557 VE_BAD_FILE, kTraceError, 558 "StartPlayingFile() failed to start file playout"); 559 _filePlayerPtr->StopPlayingFile(); 560 FilePlayer::DestroyFilePlayer(_filePlayerPtr); 561 _filePlayerPtr = NULL; 562 return -1; 563 } 564 565 _filePlayerPtr->RegisterModuleFileCallback(this); 566 _filePlaying = true; 567 568 return 0; 569} 570 571int TransmitMixer::StartPlayingFileAsMicrophone(InStream* stream, 572 FileFormats format, 573 int startPosition, 574 float volumeScaling, 575 int stopPosition, 576 const CodecInst* codecInst) 577{ 578 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId,-1), 579 "TransmitMixer::StartPlayingFileAsMicrophone(format=%d," 580 " volumeScaling=%5.3f, startPosition=%d, stopPosition=%d)", 581 format, volumeScaling, startPosition, stopPosition); 582 583 if (stream == NULL) 584 { 585 _engineStatisticsPtr->SetLastError( 586 VE_BAD_FILE, kTraceError, 587 "StartPlayingFileAsMicrophone() NULL as input stream"); 588 return -1; 589 } 590 591 if (_filePlaying) 592 { 593 _engineStatisticsPtr->SetLastError( 594 VE_ALREADY_PLAYING, kTraceWarning, 595 "StartPlayingFileAsMicrophone() is already playing"); 596 return 0; 597 } 598 599 CriticalSectionScoped cs(&_critSect); 600 601 // Destroy the old instance 602 if (_filePlayerPtr) 603 { 604 _filePlayerPtr->RegisterModuleFileCallback(NULL); 605 FilePlayer::DestroyFilePlayer(_filePlayerPtr); 606 _filePlayerPtr = NULL; 607 } 608 609 // Dynamically create the instance 610 _filePlayerPtr 611 = FilePlayer::CreateFilePlayer(_filePlayerId, 612 (const FileFormats) format); 613 614 if (_filePlayerPtr == NULL) 615 { 616 _engineStatisticsPtr->SetLastError( 617 VE_INVALID_ARGUMENT, kTraceWarning, 618 "StartPlayingFileAsMicrophone() filePlayer format isnot correct"); 619 return -1; 620 } 621 622 const uint32_t notificationTime(0); 623 624 if (_filePlayerPtr->StartPlayingFile( 625 (InStream&) *stream, 626 startPosition, 627 volumeScaling, 628 notificationTime, 629 stopPosition, 630 (const CodecInst*) codecInst) != 0) 631 { 632 _engineStatisticsPtr->SetLastError( 633 VE_BAD_FILE, kTraceError, 634 "StartPlayingFile() failed to start file playout"); 635 _filePlayerPtr->StopPlayingFile(); 636 FilePlayer::DestroyFilePlayer(_filePlayerPtr); 637 _filePlayerPtr = NULL; 638 return -1; 639 } 640 _filePlayerPtr->RegisterModuleFileCallback(this); 641 _filePlaying = true; 642 643 return 0; 644} 645 646int TransmitMixer::StopPlayingFileAsMicrophone() 647{ 648 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId,-1), 649 "TransmitMixer::StopPlayingFileAsMicrophone()"); 650 651 if (!_filePlaying) 652 { 653 _engineStatisticsPtr->SetLastError( 654 VE_INVALID_OPERATION, kTraceWarning, 655 "StopPlayingFileAsMicrophone() isnot playing"); 656 return 0; 657 } 658 659 CriticalSectionScoped cs(&_critSect); 660 661 if (_filePlayerPtr->StopPlayingFile() != 0) 662 { 663 _engineStatisticsPtr->SetLastError( 664 VE_CANNOT_STOP_PLAYOUT, kTraceError, 665 "StopPlayingFile() couldnot stop playing file"); 666 return -1; 667 } 668 669 _filePlayerPtr->RegisterModuleFileCallback(NULL); 670 FilePlayer::DestroyFilePlayer(_filePlayerPtr); 671 _filePlayerPtr = NULL; 672 _filePlaying = false; 673 674 return 0; 675} 676 677int TransmitMixer::IsPlayingFileAsMicrophone() const 678{ 679 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 680 "TransmitMixer::IsPlayingFileAsMicrophone()"); 681 return _filePlaying; 682} 683 684int TransmitMixer::StartRecordingMicrophone(const char* fileName, 685 const CodecInst* codecInst) 686{ 687 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 688 "TransmitMixer::StartRecordingMicrophone(fileName=%s)", 689 fileName); 690 691 CriticalSectionScoped cs(&_critSect); 692 693 if (_fileRecording) 694 { 695 WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1), 696 "StartRecordingMicrophone() is already recording"); 697 return 0; 698 } 699 700 FileFormats format; 701 const uint32_t notificationTime(0); // Not supported in VoE 702 CodecInst dummyCodec = { 100, "L16", 16000, 320, 1, 320000 }; 703 704 if (codecInst != NULL && 705 (codecInst->channels < 0 || codecInst->channels > 2)) 706 { 707 _engineStatisticsPtr->SetLastError( 708 VE_BAD_ARGUMENT, kTraceError, 709 "StartRecordingMicrophone() invalid compression"); 710 return (-1); 711 } 712 if (codecInst == NULL) 713 { 714 format = kFileFormatPcm16kHzFile; 715 codecInst = &dummyCodec; 716 } else if ((STR_CASE_CMP(codecInst->plname,"L16") == 0) || 717 (STR_CASE_CMP(codecInst->plname,"PCMU") == 0) || 718 (STR_CASE_CMP(codecInst->plname,"PCMA") == 0)) 719 { 720 format = kFileFormatWavFile; 721 } else 722 { 723 format = kFileFormatCompressedFile; 724 } 725 726 // Destroy the old instance 727 if (_fileRecorderPtr) 728 { 729 _fileRecorderPtr->RegisterModuleFileCallback(NULL); 730 FileRecorder::DestroyFileRecorder(_fileRecorderPtr); 731 _fileRecorderPtr = NULL; 732 } 733 734 _fileRecorderPtr = 735 FileRecorder::CreateFileRecorder(_fileRecorderId, 736 (const FileFormats) format); 737 if (_fileRecorderPtr == NULL) 738 { 739 _engineStatisticsPtr->SetLastError( 740 VE_INVALID_ARGUMENT, kTraceError, 741 "StartRecordingMicrophone() fileRecorder format isnot correct"); 742 return -1; 743 } 744 745 if (_fileRecorderPtr->StartRecordingAudioFile( 746 fileName, 747 (const CodecInst&) *codecInst, 748 notificationTime) != 0) 749 { 750 _engineStatisticsPtr->SetLastError( 751 VE_BAD_FILE, kTraceError, 752 "StartRecordingAudioFile() failed to start file recording"); 753 _fileRecorderPtr->StopRecording(); 754 FileRecorder::DestroyFileRecorder(_fileRecorderPtr); 755 _fileRecorderPtr = NULL; 756 return -1; 757 } 758 _fileRecorderPtr->RegisterModuleFileCallback(this); 759 _fileRecording = true; 760 761 return 0; 762} 763 764int TransmitMixer::StartRecordingMicrophone(OutStream* stream, 765 const CodecInst* codecInst) 766{ 767 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 768 "TransmitMixer::StartRecordingMicrophone()"); 769 770 CriticalSectionScoped cs(&_critSect); 771 772 if (_fileRecording) 773 { 774 WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1), 775 "StartRecordingMicrophone() is already recording"); 776 return 0; 777 } 778 779 FileFormats format; 780 const uint32_t notificationTime(0); // Not supported in VoE 781 CodecInst dummyCodec = { 100, "L16", 16000, 320, 1, 320000 }; 782 783 if (codecInst != NULL && codecInst->channels != 1) 784 { 785 _engineStatisticsPtr->SetLastError( 786 VE_BAD_ARGUMENT, kTraceError, 787 "StartRecordingMicrophone() invalid compression"); 788 return (-1); 789 } 790 if (codecInst == NULL) 791 { 792 format = kFileFormatPcm16kHzFile; 793 codecInst = &dummyCodec; 794 } else if ((STR_CASE_CMP(codecInst->plname,"L16") == 0) || 795 (STR_CASE_CMP(codecInst->plname,"PCMU") == 0) || 796 (STR_CASE_CMP(codecInst->plname,"PCMA") == 0)) 797 { 798 format = kFileFormatWavFile; 799 } else 800 { 801 format = kFileFormatCompressedFile; 802 } 803 804 // Destroy the old instance 805 if (_fileRecorderPtr) 806 { 807 _fileRecorderPtr->RegisterModuleFileCallback(NULL); 808 FileRecorder::DestroyFileRecorder(_fileRecorderPtr); 809 _fileRecorderPtr = NULL; 810 } 811 812 _fileRecorderPtr = 813 FileRecorder::CreateFileRecorder(_fileRecorderId, 814 (const FileFormats) format); 815 if (_fileRecorderPtr == NULL) 816 { 817 _engineStatisticsPtr->SetLastError( 818 VE_INVALID_ARGUMENT, kTraceError, 819 "StartRecordingMicrophone() fileRecorder format isnot correct"); 820 return -1; 821 } 822 823 if (_fileRecorderPtr->StartRecordingAudioFile(*stream, 824 *codecInst, 825 notificationTime) != 0) 826 { 827 _engineStatisticsPtr->SetLastError(VE_BAD_FILE, kTraceError, 828 "StartRecordingAudioFile() failed to start file recording"); 829 _fileRecorderPtr->StopRecording(); 830 FileRecorder::DestroyFileRecorder(_fileRecorderPtr); 831 _fileRecorderPtr = NULL; 832 return -1; 833 } 834 835 _fileRecorderPtr->RegisterModuleFileCallback(this); 836 _fileRecording = true; 837 838 return 0; 839} 840 841 842int TransmitMixer::StopRecordingMicrophone() 843{ 844 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 845 "TransmitMixer::StopRecordingMicrophone()"); 846 847 CriticalSectionScoped cs(&_critSect); 848 849 if (!_fileRecording) 850 { 851 WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1), 852 "StopRecordingMicrophone() isnot recording"); 853 return 0; 854 } 855 856 if (_fileRecorderPtr->StopRecording() != 0) 857 { 858 _engineStatisticsPtr->SetLastError( 859 VE_STOP_RECORDING_FAILED, kTraceError, 860 "StopRecording(), could not stop recording"); 861 return -1; 862 } 863 _fileRecorderPtr->RegisterModuleFileCallback(NULL); 864 FileRecorder::DestroyFileRecorder(_fileRecorderPtr); 865 _fileRecorderPtr = NULL; 866 _fileRecording = false; 867 868 return 0; 869} 870 871int TransmitMixer::StartRecordingCall(const char* fileName, 872 const CodecInst* codecInst) 873{ 874 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 875 "TransmitMixer::StartRecordingCall(fileName=%s)", fileName); 876 877 if (_fileCallRecording) 878 { 879 WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1), 880 "StartRecordingCall() is already recording"); 881 return 0; 882 } 883 884 FileFormats format; 885 const uint32_t notificationTime(0); // Not supported in VoE 886 CodecInst dummyCodec = { 100, "L16", 16000, 320, 1, 320000 }; 887 888 if (codecInst != NULL && codecInst->channels != 1) 889 { 890 _engineStatisticsPtr->SetLastError( 891 VE_BAD_ARGUMENT, kTraceError, 892 "StartRecordingCall() invalid compression"); 893 return (-1); 894 } 895 if (codecInst == NULL) 896 { 897 format = kFileFormatPcm16kHzFile; 898 codecInst = &dummyCodec; 899 } else if ((STR_CASE_CMP(codecInst->plname,"L16") == 0) || 900 (STR_CASE_CMP(codecInst->plname,"PCMU") == 0) || 901 (STR_CASE_CMP(codecInst->plname,"PCMA") == 0)) 902 { 903 format = kFileFormatWavFile; 904 } else 905 { 906 format = kFileFormatCompressedFile; 907 } 908 909 CriticalSectionScoped cs(&_critSect); 910 911 // Destroy the old instance 912 if (_fileCallRecorderPtr) 913 { 914 _fileCallRecorderPtr->RegisterModuleFileCallback(NULL); 915 FileRecorder::DestroyFileRecorder(_fileCallRecorderPtr); 916 _fileCallRecorderPtr = NULL; 917 } 918 919 _fileCallRecorderPtr 920 = FileRecorder::CreateFileRecorder(_fileCallRecorderId, 921 (const FileFormats) format); 922 if (_fileCallRecorderPtr == NULL) 923 { 924 _engineStatisticsPtr->SetLastError( 925 VE_INVALID_ARGUMENT, kTraceError, 926 "StartRecordingCall() fileRecorder format isnot correct"); 927 return -1; 928 } 929 930 if (_fileCallRecorderPtr->StartRecordingAudioFile( 931 fileName, 932 (const CodecInst&) *codecInst, 933 notificationTime) != 0) 934 { 935 _engineStatisticsPtr->SetLastError( 936 VE_BAD_FILE, kTraceError, 937 "StartRecordingAudioFile() failed to start file recording"); 938 _fileCallRecorderPtr->StopRecording(); 939 FileRecorder::DestroyFileRecorder(_fileCallRecorderPtr); 940 _fileCallRecorderPtr = NULL; 941 return -1; 942 } 943 _fileCallRecorderPtr->RegisterModuleFileCallback(this); 944 _fileCallRecording = true; 945 946 return 0; 947} 948 949int TransmitMixer::StartRecordingCall(OutStream* stream, 950 const CodecInst* codecInst) 951{ 952 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 953 "TransmitMixer::StartRecordingCall()"); 954 955 if (_fileCallRecording) 956 { 957 WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1), 958 "StartRecordingCall() is already recording"); 959 return 0; 960 } 961 962 FileFormats format; 963 const uint32_t notificationTime(0); // Not supported in VoE 964 CodecInst dummyCodec = { 100, "L16", 16000, 320, 1, 320000 }; 965 966 if (codecInst != NULL && codecInst->channels != 1) 967 { 968 _engineStatisticsPtr->SetLastError( 969 VE_BAD_ARGUMENT, kTraceError, 970 "StartRecordingCall() invalid compression"); 971 return (-1); 972 } 973 if (codecInst == NULL) 974 { 975 format = kFileFormatPcm16kHzFile; 976 codecInst = &dummyCodec; 977 } else if ((STR_CASE_CMP(codecInst->plname,"L16") == 0) || 978 (STR_CASE_CMP(codecInst->plname,"PCMU") == 0) || 979 (STR_CASE_CMP(codecInst->plname,"PCMA") == 0)) 980 { 981 format = kFileFormatWavFile; 982 } else 983 { 984 format = kFileFormatCompressedFile; 985 } 986 987 CriticalSectionScoped cs(&_critSect); 988 989 // Destroy the old instance 990 if (_fileCallRecorderPtr) 991 { 992 _fileCallRecorderPtr->RegisterModuleFileCallback(NULL); 993 FileRecorder::DestroyFileRecorder(_fileCallRecorderPtr); 994 _fileCallRecorderPtr = NULL; 995 } 996 997 _fileCallRecorderPtr = 998 FileRecorder::CreateFileRecorder(_fileCallRecorderId, 999 (const FileFormats) format); 1000 if (_fileCallRecorderPtr == NULL) 1001 { 1002 _engineStatisticsPtr->SetLastError( 1003 VE_INVALID_ARGUMENT, kTraceError, 1004 "StartRecordingCall() fileRecorder format isnot correct"); 1005 return -1; 1006 } 1007 1008 if (_fileCallRecorderPtr->StartRecordingAudioFile(*stream, 1009 *codecInst, 1010 notificationTime) != 0) 1011 { 1012 _engineStatisticsPtr->SetLastError(VE_BAD_FILE, kTraceError, 1013 "StartRecordingAudioFile() failed to start file recording"); 1014 _fileCallRecorderPtr->StopRecording(); 1015 FileRecorder::DestroyFileRecorder(_fileCallRecorderPtr); 1016 _fileCallRecorderPtr = NULL; 1017 return -1; 1018 } 1019 1020 _fileCallRecorderPtr->RegisterModuleFileCallback(this); 1021 _fileCallRecording = true; 1022 1023 return 0; 1024} 1025 1026int TransmitMixer::StopRecordingCall() 1027{ 1028 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 1029 "TransmitMixer::StopRecordingCall()"); 1030 1031 if (!_fileCallRecording) 1032 { 1033 WEBRTC_TRACE(kTraceError, kTraceVoice, VoEId(_instanceId, -1), 1034 "StopRecordingCall() file isnot recording"); 1035 return -1; 1036 } 1037 1038 CriticalSectionScoped cs(&_critSect); 1039 1040 if (_fileCallRecorderPtr->StopRecording() != 0) 1041 { 1042 _engineStatisticsPtr->SetLastError( 1043 VE_STOP_RECORDING_FAILED, kTraceError, 1044 "StopRecording(), could not stop recording"); 1045 return -1; 1046 } 1047 1048 _fileCallRecorderPtr->RegisterModuleFileCallback(NULL); 1049 FileRecorder::DestroyFileRecorder(_fileCallRecorderPtr); 1050 _fileCallRecorderPtr = NULL; 1051 _fileCallRecording = false; 1052 1053 return 0; 1054} 1055 1056void 1057TransmitMixer::SetMixWithMicStatus(bool mix) 1058{ 1059 _mixFileWithMicrophone = mix; 1060} 1061 1062int TransmitMixer::RegisterExternalMediaProcessing( 1063 VoEMediaProcess* object, 1064 ProcessingTypes type) { 1065 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 1066 "TransmitMixer::RegisterExternalMediaProcessing()"); 1067 1068 CriticalSectionScoped cs(&_callbackCritSect); 1069 if (!object) { 1070 return -1; 1071 } 1072 1073 // Store the callback object according to the processing type. 1074 if (type == kRecordingAllChannelsMixed) { 1075 external_postproc_ptr_ = object; 1076 } else if (type == kRecordingPreprocessing) { 1077 external_preproc_ptr_ = object; 1078 } else { 1079 return -1; 1080 } 1081 return 0; 1082} 1083 1084int TransmitMixer::DeRegisterExternalMediaProcessing(ProcessingTypes type) { 1085 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 1086 "TransmitMixer::DeRegisterExternalMediaProcessing()"); 1087 1088 CriticalSectionScoped cs(&_callbackCritSect); 1089 if (type == kRecordingAllChannelsMixed) { 1090 external_postproc_ptr_ = NULL; 1091 } else if (type == kRecordingPreprocessing) { 1092 external_preproc_ptr_ = NULL; 1093 } else { 1094 return -1; 1095 } 1096 return 0; 1097} 1098 1099int 1100TransmitMixer::SetMute(bool enable) 1101{ 1102 WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, -1), 1103 "TransmitMixer::SetMute(enable=%d)", enable); 1104 _mute = enable; 1105 return 0; 1106} 1107 1108bool 1109TransmitMixer::Mute() const 1110{ 1111 return _mute; 1112} 1113 1114int8_t TransmitMixer::AudioLevel() const 1115{ 1116 // Speech + file level [0,9] 1117 return _audioLevel.Level(); 1118} 1119 1120int16_t TransmitMixer::AudioLevelFullRange() const 1121{ 1122 // Speech + file level [0,32767] 1123 return _audioLevel.LevelFullRange(); 1124} 1125 1126bool TransmitMixer::IsRecordingCall() 1127{ 1128 return _fileCallRecording; 1129} 1130 1131bool TransmitMixer::IsRecordingMic() 1132{ 1133 CriticalSectionScoped cs(&_critSect); 1134 return _fileRecording; 1135} 1136 1137void TransmitMixer::GenerateAudioFrame(const int16_t* audio, 1138 int samples_per_channel, 1139 int num_channels, 1140 int sample_rate_hz) { 1141 int codec_rate; 1142 int num_codec_channels; 1143 GetSendCodecInfo(&codec_rate, &num_codec_channels); 1144 // TODO(ajm): This currently restricts the sample rate to 32 kHz. 1145 // See: https://code.google.com/p/webrtc/issues/detail?id=3146 1146 // When 48 kHz is supported natively by AudioProcessing, this will have 1147 // to be changed to handle 44.1 kHz. 1148 int max_sample_rate_hz = kAudioProcMaxNativeSampleRateHz; 1149 if (audioproc_->echo_control_mobile()->is_enabled()) { 1150 // AECM only supports 8 and 16 kHz. 1151 max_sample_rate_hz = 16000; 1152 } 1153 codec_rate = std::min(codec_rate, max_sample_rate_hz); 1154 stereo_codec_ = num_codec_channels == 2; 1155 1156 if (!mono_buffer_.get()) { 1157 // Temporary space for DownConvertToCodecFormat. 1158 mono_buffer_.reset(new int16_t[kMaxMonoDataSizeSamples]); 1159 } 1160 DownConvertToCodecFormat(audio, 1161 samples_per_channel, 1162 num_channels, 1163 sample_rate_hz, 1164 num_codec_channels, 1165 codec_rate, 1166 mono_buffer_.get(), 1167 &resampler_, 1168 &_audioFrame); 1169} 1170 1171int32_t TransmitMixer::RecordAudioToFile( 1172 uint32_t mixingFrequency) 1173{ 1174 CriticalSectionScoped cs(&_critSect); 1175 if (_fileRecorderPtr == NULL) 1176 { 1177 WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1), 1178 "TransmitMixer::RecordAudioToFile() filerecorder doesnot" 1179 "exist"); 1180 return -1; 1181 } 1182 1183 if (_fileRecorderPtr->RecordAudioToFile(_audioFrame) != 0) 1184 { 1185 WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1), 1186 "TransmitMixer::RecordAudioToFile() file recording" 1187 "failed"); 1188 return -1; 1189 } 1190 1191 return 0; 1192} 1193 1194int32_t TransmitMixer::MixOrReplaceAudioWithFile( 1195 int mixingFrequency) 1196{ 1197 scoped_ptr<int16_t[]> fileBuffer(new int16_t[640]); 1198 1199 int fileSamples(0); 1200 { 1201 CriticalSectionScoped cs(&_critSect); 1202 if (_filePlayerPtr == NULL) 1203 { 1204 WEBRTC_TRACE(kTraceWarning, kTraceVoice, 1205 VoEId(_instanceId, -1), 1206 "TransmitMixer::MixOrReplaceAudioWithFile()" 1207 "fileplayer doesnot exist"); 1208 return -1; 1209 } 1210 1211 if (_filePlayerPtr->Get10msAudioFromFile(fileBuffer.get(), 1212 fileSamples, 1213 mixingFrequency) == -1) 1214 { 1215 WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, -1), 1216 "TransmitMixer::MixOrReplaceAudioWithFile() file" 1217 " mixing failed"); 1218 return -1; 1219 } 1220 } 1221 1222 assert(_audioFrame.samples_per_channel_ == fileSamples); 1223 1224 if (_mixFileWithMicrophone) 1225 { 1226 // Currently file stream is always mono. 1227 // TODO(xians): Change the code when FilePlayer supports real stereo. 1228 MixWithSat(_audioFrame.data_, 1229 _audioFrame.num_channels_, 1230 fileBuffer.get(), 1231 1, 1232 fileSamples); 1233 } else 1234 { 1235 // Replace ACM audio with file. 1236 // Currently file stream is always mono. 1237 // TODO(xians): Change the code when FilePlayer supports real stereo. 1238 _audioFrame.UpdateFrame(-1, 1239 0xFFFFFFFF, 1240 fileBuffer.get(), 1241 fileSamples, 1242 mixingFrequency, 1243 AudioFrame::kNormalSpeech, 1244 AudioFrame::kVadUnknown, 1245 1); 1246 } 1247 return 0; 1248} 1249 1250void TransmitMixer::ProcessAudio(int delay_ms, int clock_drift, 1251 int current_mic_level, bool key_pressed) { 1252 if (audioproc_->set_stream_delay_ms(delay_ms) != 0) { 1253 // A redundant warning is reported in AudioDevice, which we've throttled 1254 // to avoid flooding the logs. Relegate this one to LS_VERBOSE to avoid 1255 // repeating the problem here. 1256 LOG_FERR1(LS_VERBOSE, set_stream_delay_ms, delay_ms); 1257 } 1258 1259 GainControl* agc = audioproc_->gain_control(); 1260 if (agc->set_stream_analog_level(current_mic_level) != 0) { 1261 LOG_FERR1(LS_ERROR, set_stream_analog_level, current_mic_level); 1262 assert(false); 1263 } 1264 1265 EchoCancellation* aec = audioproc_->echo_cancellation(); 1266 if (aec->is_drift_compensation_enabled()) { 1267 aec->set_stream_drift_samples(clock_drift); 1268 } 1269 1270 audioproc_->set_stream_key_pressed(key_pressed); 1271 1272 int err = audioproc_->ProcessStream(&_audioFrame); 1273 if (err != 0) { 1274 LOG(LS_ERROR) << "ProcessStream() error: " << err; 1275 assert(false); 1276 } 1277 1278 // Store new capture level. Only updated when analog AGC is enabled. 1279 _captureLevel = agc->stream_analog_level(); 1280 1281 CriticalSectionScoped cs(&_critSect); 1282 // Triggers a callback in OnPeriodicProcess(). 1283 _saturationWarning |= agc->stream_is_saturated(); 1284} 1285 1286#ifdef WEBRTC_VOICE_ENGINE_TYPING_DETECTION 1287void TransmitMixer::TypingDetection(bool keyPressed) 1288{ 1289 // We let the VAD determine if we're using this feature or not. 1290 if (_audioFrame.vad_activity_ == AudioFrame::kVadUnknown) { 1291 return; 1292 } 1293 1294 bool vadActive = _audioFrame.vad_activity_ == AudioFrame::kVadActive; 1295 if (_typingDetection.Process(keyPressed, vadActive)) { 1296 _typingNoiseWarningPending = true; 1297 _typingNoiseDetected = true; 1298 } else { 1299 // If there is already a warning pending, do not change the state. 1300 // Otherwise set a warning pending if last callback was for noise detected. 1301 if (!_typingNoiseWarningPending && _typingNoiseDetected) { 1302 _typingNoiseWarningPending = true; 1303 _typingNoiseDetected = false; 1304 } 1305 } 1306} 1307#endif 1308 1309int TransmitMixer::GetMixingFrequency() 1310{ 1311 assert(_audioFrame.sample_rate_hz_ != 0); 1312 return _audioFrame.sample_rate_hz_; 1313} 1314 1315#ifdef WEBRTC_VOICE_ENGINE_TYPING_DETECTION 1316int TransmitMixer::TimeSinceLastTyping(int &seconds) 1317{ 1318 // We check in VoEAudioProcessingImpl that this is only called when 1319 // typing detection is active. 1320 seconds = _typingDetection.TimeSinceLastDetectionInSeconds(); 1321 return 0; 1322} 1323#endif 1324 1325#ifdef WEBRTC_VOICE_ENGINE_TYPING_DETECTION 1326int TransmitMixer::SetTypingDetectionParameters(int timeWindow, 1327 int costPerTyping, 1328 int reportingThreshold, 1329 int penaltyDecay, 1330 int typeEventDelay) 1331{ 1332 _typingDetection.SetParameters(timeWindow, 1333 costPerTyping, 1334 reportingThreshold, 1335 penaltyDecay, 1336 typeEventDelay, 1337 0); 1338 return 0; 1339} 1340#endif 1341 1342void TransmitMixer::EnableStereoChannelSwapping(bool enable) { 1343 swap_stereo_channels_ = enable; 1344} 1345 1346bool TransmitMixer::IsStereoChannelSwappingEnabled() { 1347 return swap_stereo_channels_; 1348} 1349 1350} // namespace voe 1351} // namespace webrtc 1352