1 /* 2 * Copyright 2004 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/sound/alsasoundsystem.h" 12 13 #include "webrtc/sound/sounddevicelocator.h" 14 #include "webrtc/sound/soundinputstreaminterface.h" 15 #include "webrtc/sound/soundoutputstreaminterface.h" 16 #include "webrtc/base/common.h" 17 #include "webrtc/base/logging.h" 18 #include "webrtc/base/scoped_ptr.h" 19 #include "webrtc/base/stringutils.h" 20 #include "webrtc/base/timeutils.h" 21 #include "webrtc/base/worker.h" 22 23 namespace rtc { 24 25 // Lookup table from the rtc format enum in soundsysteminterface.h to 26 // ALSA's enums. 27 static const snd_pcm_format_t kCricketFormatToAlsaFormatTable[] = { 28 // The order here must match the order in soundsysteminterface.h 29 SND_PCM_FORMAT_S16_LE, 30 }; 31 32 // Lookup table for the size of a single sample of a given format. 33 static const size_t kCricketFormatToSampleSizeTable[] = { 34 // The order here must match the order in soundsysteminterface.h 35 sizeof(int16_t), // 2 36 }; 37 38 // Minimum latency we allow, in microseconds. This is more or less arbitrary, 39 // but it has to be at least large enough to be able to buffer data during a 40 // missed context switch, and the typical Linux scheduling quantum is 10ms. 41 static const int kMinimumLatencyUsecs = 20 * 1000; 42 43 // The latency we'll use for kNoLatencyRequirements (chosen arbitrarily). 44 static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2; 45 46 // We translate newlines in ALSA device descriptions to hyphens. 47 static const char kAlsaDescriptionSearch[] = "\n"; 48 static const char kAlsaDescriptionReplace[] = " - "; 49 50 class AlsaDeviceLocator : public SoundDeviceLocator { 51 public: 52 AlsaDeviceLocator(const std::string &name, 53 const std::string &device_name) 54 : SoundDeviceLocator(name, device_name) { 55 // The ALSA descriptions have newlines in them, which won't show up in 56 // a drop-down box. Replace them with hyphens. 57 rtc::replace_substrs(kAlsaDescriptionSearch, 58 sizeof(kAlsaDescriptionSearch) - 1, 59 kAlsaDescriptionReplace, 60 sizeof(kAlsaDescriptionReplace) - 1, 61 &name_); 62 } 63 64 virtual SoundDeviceLocator *Copy() const { 65 return new AlsaDeviceLocator(*this); 66 } 67 }; 68 69 // Functionality that is common to both AlsaInputStream and AlsaOutputStream. 70 class AlsaStream { 71 public: 72 AlsaStream(AlsaSoundSystem *alsa, 73 snd_pcm_t *handle, 74 size_t frame_size, 75 int wait_timeout_ms, 76 int flags, 77 int freq) 78 : alsa_(alsa), 79 handle_(handle), 80 frame_size_(frame_size), 81 wait_timeout_ms_(wait_timeout_ms), 82 flags_(flags), 83 freq_(freq) { 84 } 85 86 ~AlsaStream() { 87 Close(); 88 } 89 90 // Waits for the stream to be ready to accept/return more data, and returns 91 // how much can be written/read, or 0 if we need to Wait() again. 92 snd_pcm_uframes_t Wait() { 93 snd_pcm_sframes_t frames; 94 // Ideally we would not use snd_pcm_wait() and instead hook snd_pcm_poll_* 95 // into PhysicalSocketServer, but PhysicalSocketServer is nasty enough 96 // already and the current clients of SoundSystemInterface do not run 97 // anything else on their worker threads, so snd_pcm_wait() is good enough. 98 frames = symbol_table()->snd_pcm_avail_update()(handle_); 99 if (frames < 0) { 100 LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames); 101 Recover(frames); 102 return 0; 103 } else if (frames > 0) { 104 // Already ready, so no need to wait. 105 return frames; 106 } 107 // Else no space/data available, so must wait. 108 int ready = symbol_table()->snd_pcm_wait()(handle_, wait_timeout_ms_); 109 if (ready < 0) { 110 LOG(LS_ERROR) << "snd_pcm_wait(): " << GetError(ready); 111 Recover(ready); 112 return 0; 113 } else if (ready == 0) { 114 // Timeout, so nothing can be written/read right now. 115 // We set the timeout to twice the requested latency, so continuous 116 // timeouts are indicative of a problem, so log as a warning. 117 LOG(LS_WARNING) << "Timeout while waiting on stream"; 118 return 0; 119 } 120 // Else ready > 0 (i.e., 1), so it's ready. Get count. 121 frames = symbol_table()->snd_pcm_avail_update()(handle_); 122 if (frames < 0) { 123 LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames); 124 Recover(frames); 125 return 0; 126 } else if (frames == 0) { 127 // wait() said we were ready, so this ought to have been positive. Has 128 // been observed to happen in practice though. 129 LOG(LS_WARNING) << "Spurious wake-up"; 130 } 131 return frames; 132 } 133 134 int CurrentDelayUsecs() { 135 if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) { 136 return 0; 137 } 138 139 snd_pcm_sframes_t delay; 140 int err = symbol_table()->snd_pcm_delay()(handle_, &delay); 141 if (err != 0) { 142 LOG(LS_ERROR) << "snd_pcm_delay(): " << GetError(err); 143 Recover(err); 144 // We'd rather continue playout/capture with an incorrect delay than stop 145 // it altogether, so return a valid value. 146 return 0; 147 } 148 // The delay is in frames. Convert to microseconds. 149 return delay * rtc::kNumMicrosecsPerSec / freq_; 150 } 151 152 // Used to recover from certain recoverable errors, principally buffer overrun 153 // or underrun (identified as EPIPE). Without calling this the stream stays 154 // in the error state forever. 155 bool Recover(int error) { 156 int err; 157 err = symbol_table()->snd_pcm_recover()( 158 handle_, 159 error, 160 // Silent; i.e., no logging on stderr. 161 1); 162 if (err != 0) { 163 // Docs say snd_pcm_recover returns the original error if it is not one 164 // of the recoverable ones, so this log message will probably contain the 165 // same error twice. 166 LOG(LS_ERROR) << "Unable to recover from \"" << GetError(error) << "\": " 167 << GetError(err); 168 return false; 169 } 170 if (error == -EPIPE && // Buffer underrun/overrun. 171 symbol_table()->snd_pcm_stream()(handle_) == SND_PCM_STREAM_CAPTURE) { 172 // For capture streams we also have to repeat the explicit start() to get 173 // data flowing again. 174 err = symbol_table()->snd_pcm_start()(handle_); 175 if (err != 0) { 176 LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err); 177 return false; 178 } 179 } 180 return true; 181 } 182 183 bool Close() { 184 if (handle_) { 185 int err; 186 err = symbol_table()->snd_pcm_drop()(handle_); 187 if (err != 0) { 188 LOG(LS_ERROR) << "snd_pcm_drop(): " << GetError(err); 189 // Continue anyways. 190 } 191 err = symbol_table()->snd_pcm_close()(handle_); 192 if (err != 0) { 193 LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err); 194 // Continue anyways. 195 } 196 handle_ = NULL; 197 } 198 return true; 199 } 200 201 AlsaSymbolTable *symbol_table() { 202 return &alsa_->symbol_table_; 203 } 204 205 snd_pcm_t *handle() { 206 return handle_; 207 } 208 209 const char *GetError(int err) { 210 return alsa_->GetError(err); 211 } 212 213 size_t frame_size() { 214 return frame_size_; 215 } 216 217 private: 218 AlsaSoundSystem *alsa_; 219 snd_pcm_t *handle_; 220 size_t frame_size_; 221 int wait_timeout_ms_; 222 int flags_; 223 int freq_; 224 225 DISALLOW_COPY_AND_ASSIGN(AlsaStream); 226 }; 227 228 // Implementation of an input stream. See soundinputstreaminterface.h regarding 229 // thread-safety. 230 class AlsaInputStream : 231 public SoundInputStreamInterface, 232 private rtc::Worker { 233 public: 234 AlsaInputStream(AlsaSoundSystem *alsa, 235 snd_pcm_t *handle, 236 size_t frame_size, 237 int wait_timeout_ms, 238 int flags, 239 int freq) 240 : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq), 241 buffer_size_(0) { 242 } 243 244 virtual ~AlsaInputStream() { 245 bool success = StopReading(); 246 // We need that to live. 247 VERIFY(success); 248 } 249 250 virtual bool StartReading() { 251 return StartWork(); 252 } 253 254 virtual bool StopReading() { 255 return StopWork(); 256 } 257 258 virtual bool GetVolume(int *volume) { 259 // TODO: Implement this. 260 return false; 261 } 262 263 virtual bool SetVolume(int volume) { 264 // TODO: Implement this. 265 return false; 266 } 267 268 virtual bool Close() { 269 return StopReading() && stream_.Close(); 270 } 271 272 virtual int LatencyUsecs() { 273 return stream_.CurrentDelayUsecs(); 274 } 275 276 private: 277 // Inherited from Worker. 278 virtual void OnStart() { 279 HaveWork(); 280 } 281 282 // Inherited from Worker. 283 virtual void OnHaveWork() { 284 // Block waiting for data. 285 snd_pcm_uframes_t avail = stream_.Wait(); 286 if (avail > 0) { 287 // Data is available. 288 size_t size = avail * stream_.frame_size(); 289 if (size > buffer_size_) { 290 // Must increase buffer size. 291 buffer_.reset(new char[size]); 292 buffer_size_ = size; 293 } 294 // Read all the data. 295 snd_pcm_sframes_t read = stream_.symbol_table()->snd_pcm_readi()( 296 stream_.handle(), 297 buffer_.get(), 298 avail); 299 if (read < 0) { 300 LOG(LS_ERROR) << "snd_pcm_readi(): " << GetError(read); 301 stream_.Recover(read); 302 } else if (read == 0) { 303 // Docs say this shouldn't happen. 304 ASSERT(false); 305 LOG(LS_ERROR) << "No data?"; 306 } else { 307 // Got data. Pass it off to the app. 308 SignalSamplesRead(buffer_.get(), 309 read * stream_.frame_size(), 310 this); 311 } 312 } 313 // Check for more data with no delay, after any pending messages are 314 // dispatched. 315 HaveWork(); 316 } 317 318 // Inherited from Worker. 319 virtual void OnStop() { 320 // Nothing to do. 321 } 322 323 const char *GetError(int err) { 324 return stream_.GetError(err); 325 } 326 327 AlsaStream stream_; 328 rtc::scoped_ptr<char[]> buffer_; 329 size_t buffer_size_; 330 331 DISALLOW_COPY_AND_ASSIGN(AlsaInputStream); 332 }; 333 334 // Implementation of an output stream. See soundoutputstreaminterface.h 335 // regarding thread-safety. 336 class AlsaOutputStream : 337 public SoundOutputStreamInterface, 338 private rtc::Worker { 339 public: 340 AlsaOutputStream(AlsaSoundSystem *alsa, 341 snd_pcm_t *handle, 342 size_t frame_size, 343 int wait_timeout_ms, 344 int flags, 345 int freq) 346 : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq) { 347 } 348 349 virtual ~AlsaOutputStream() { 350 bool success = DisableBufferMonitoring(); 351 // We need that to live. 352 VERIFY(success); 353 } 354 355 virtual bool EnableBufferMonitoring() { 356 return StartWork(); 357 } 358 359 virtual bool DisableBufferMonitoring() { 360 return StopWork(); 361 } 362 363 virtual bool WriteSamples(const void *sample_data, 364 size_t size) { 365 if (size % stream_.frame_size() != 0) { 366 // No client of SoundSystemInterface does this, so let's not support it. 367 // (If we wanted to support it, we'd basically just buffer the fractional 368 // frame until we get more data.) 369 ASSERT(false); 370 LOG(LS_ERROR) << "Writes with fractional frames are not supported"; 371 return false; 372 } 373 snd_pcm_uframes_t frames = size / stream_.frame_size(); 374 snd_pcm_sframes_t written = stream_.symbol_table()->snd_pcm_writei()( 375 stream_.handle(), 376 sample_data, 377 frames); 378 if (written < 0) { 379 LOG(LS_ERROR) << "snd_pcm_writei(): " << GetError(written); 380 stream_.Recover(written); 381 return false; 382 } else if (static_cast<snd_pcm_uframes_t>(written) < frames) { 383 // Shouldn't happen. Drop the rest of the data. 384 LOG(LS_ERROR) << "Stream wrote only " << written << " of " << frames 385 << " frames!"; 386 return false; 387 } 388 return true; 389 } 390 391 virtual bool GetVolume(int *volume) { 392 // TODO: Implement this. 393 return false; 394 } 395 396 virtual bool SetVolume(int volume) { 397 // TODO: Implement this. 398 return false; 399 } 400 401 virtual bool Close() { 402 return DisableBufferMonitoring() && stream_.Close(); 403 } 404 405 virtual int LatencyUsecs() { 406 return stream_.CurrentDelayUsecs(); 407 } 408 409 private: 410 // Inherited from Worker. 411 virtual void OnStart() { 412 HaveWork(); 413 } 414 415 // Inherited from Worker. 416 virtual void OnHaveWork() { 417 snd_pcm_uframes_t avail = stream_.Wait(); 418 if (avail > 0) { 419 size_t space = avail * stream_.frame_size(); 420 SignalBufferSpace(space, this); 421 } 422 HaveWork(); 423 } 424 425 // Inherited from Worker. 426 virtual void OnStop() { 427 // Nothing to do. 428 } 429 430 const char *GetError(int err) { 431 return stream_.GetError(err); 432 } 433 434 AlsaStream stream_; 435 436 DISALLOW_COPY_AND_ASSIGN(AlsaOutputStream); 437 }; 438 439 AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {} 440 441 AlsaSoundSystem::~AlsaSoundSystem() { 442 // Not really necessary, because Terminate() doesn't really do anything. 443 Terminate(); 444 } 445 446 bool AlsaSoundSystem::Init() { 447 if (IsInitialized()) { 448 return true; 449 } 450 451 // Load libasound. 452 if (!symbol_table_.Load()) { 453 // Very odd for a Linux machine to not have a working libasound ... 454 LOG(LS_ERROR) << "Failed to load symbol table"; 455 return false; 456 } 457 458 initialized_ = true; 459 460 return true; 461 } 462 463 void AlsaSoundSystem::Terminate() { 464 if (!IsInitialized()) { 465 return; 466 } 467 468 initialized_ = false; 469 470 // We do not unload the symbol table because we may need it again soon if 471 // Init() is called again. 472 } 473 474 bool AlsaSoundSystem::EnumeratePlaybackDevices( 475 SoundDeviceLocatorList *devices) { 476 return EnumerateDevices(devices, false); 477 } 478 479 bool AlsaSoundSystem::EnumerateCaptureDevices( 480 SoundDeviceLocatorList *devices) { 481 return EnumerateDevices(devices, true); 482 } 483 484 bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) { 485 return GetDefaultDevice(device); 486 } 487 488 bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) { 489 return GetDefaultDevice(device); 490 } 491 492 SoundOutputStreamInterface *AlsaSoundSystem::OpenPlaybackDevice( 493 const SoundDeviceLocator *device, 494 const OpenParams ¶ms) { 495 return OpenDevice<SoundOutputStreamInterface>( 496 device, 497 params, 498 SND_PCM_STREAM_PLAYBACK, 499 &AlsaSoundSystem::StartOutputStream); 500 } 501 502 SoundInputStreamInterface *AlsaSoundSystem::OpenCaptureDevice( 503 const SoundDeviceLocator *device, 504 const OpenParams ¶ms) { 505 return OpenDevice<SoundInputStreamInterface>( 506 device, 507 params, 508 SND_PCM_STREAM_CAPTURE, 509 &AlsaSoundSystem::StartInputStream); 510 } 511 512 const char *AlsaSoundSystem::GetName() const { 513 return "ALSA"; 514 } 515 516 bool AlsaSoundSystem::EnumerateDevices( 517 SoundDeviceLocatorList *devices, 518 bool capture_not_playback) { 519 ClearSoundDeviceLocatorList(devices); 520 521 if (!IsInitialized()) { 522 return false; 523 } 524 525 const char *type = capture_not_playback ? "Input" : "Output"; 526 // dmix and dsnoop are only for playback and capture, respectively, but ALSA 527 // stupidly includes them in both lists. 528 const char *ignore_prefix = capture_not_playback ? "dmix:" : "dsnoop:"; 529 // (ALSA lists many more "devices" of questionable interest, but we show them 530 // just in case the weird devices may actually be desirable for some 531 // users/systems.) 532 const char *ignore_default = "default"; 533 const char *ignore_null = "null"; 534 const char *ignore_pulse = "pulse"; 535 // The 'pulse' entry has a habit of mysteriously disappearing when you query 536 // a second time. Remove it from our list. (GIPS lib did the same thing.) 537 int err; 538 539 void **hints; 540 err = symbol_table_.snd_device_name_hint()(-1, // All cards 541 "pcm", // Only PCM devices 542 &hints); 543 if (err != 0) { 544 LOG(LS_ERROR) << "snd_device_name_hint(): " << GetError(err); 545 return false; 546 } 547 548 for (void **list = hints; *list != NULL; ++list) { 549 char *actual_type = symbol_table_.snd_device_name_get_hint()(*list, "IOID"); 550 if (actual_type) { // NULL means it's both. 551 bool wrong_type = (strcmp(actual_type, type) != 0); 552 free(actual_type); 553 if (wrong_type) { 554 // Wrong type of device (i.e., input vs. output). 555 continue; 556 } 557 } 558 559 char *name = symbol_table_.snd_device_name_get_hint()(*list, "NAME"); 560 if (!name) { 561 LOG(LS_ERROR) << "Device has no name???"; 562 // Skip it. 563 continue; 564 } 565 566 // Now check if we actually want to show this device. 567 if (strcmp(name, ignore_default) != 0 && 568 strcmp(name, ignore_null) != 0 && 569 strcmp(name, ignore_pulse) != 0 && 570 !rtc::starts_with(name, ignore_prefix)) { 571 572 // Yes, we do. 573 char *desc = symbol_table_.snd_device_name_get_hint()(*list, "DESC"); 574 if (!desc) { 575 // Virtual devices don't necessarily have descriptions. Use their names 576 // instead (not pretty!). 577 desc = name; 578 } 579 580 AlsaDeviceLocator *device = new AlsaDeviceLocator(desc, name); 581 582 devices->push_back(device); 583 584 if (desc != name) { 585 free(desc); 586 } 587 } 588 589 free(name); 590 } 591 592 err = symbol_table_.snd_device_name_free_hint()(hints); 593 if (err != 0) { 594 LOG(LS_ERROR) << "snd_device_name_free_hint(): " << GetError(err); 595 // Continue and return true anyways, since we did get the whole list. 596 } 597 598 return true; 599 } 600 601 bool AlsaSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) { 602 if (!IsInitialized()) { 603 return false; 604 } 605 *device = new AlsaDeviceLocator("Default device", "default"); 606 return true; 607 } 608 609 inline size_t AlsaSoundSystem::FrameSize(const OpenParams ¶ms) { 610 ASSERT(static_cast<int>(params.format) < 611 ARRAY_SIZE(kCricketFormatToSampleSizeTable)); 612 return kCricketFormatToSampleSizeTable[params.format] * params.channels; 613 } 614 615 template <typename StreamInterface> 616 StreamInterface *AlsaSoundSystem::OpenDevice( 617 const SoundDeviceLocator *device, 618 const OpenParams ¶ms, 619 snd_pcm_stream_t type, 620 StreamInterface *(AlsaSoundSystem::*start_fn)( 621 snd_pcm_t *handle, 622 size_t frame_size, 623 int wait_timeout_ms, 624 int flags, 625 int freq)) { 626 627 if (!IsInitialized()) { 628 return NULL; 629 } 630 631 StreamInterface *stream; 632 int err; 633 634 const char *dev = static_cast<const AlsaDeviceLocator *>(device)-> 635 device_name().c_str(); 636 637 snd_pcm_t *handle = NULL; 638 err = symbol_table_.snd_pcm_open()( 639 &handle, 640 dev, 641 type, 642 // No flags. 643 0); 644 if (err != 0) { 645 LOG(LS_ERROR) << "snd_pcm_open(" << dev << "): " << GetError(err); 646 return NULL; 647 } 648 LOG(LS_VERBOSE) << "Opening " << dev; 649 ASSERT(handle); // If open succeeded, handle ought to be valid 650 651 // Compute requested latency in microseconds. 652 int latency; 653 if (params.latency == kNoLatencyRequirements) { 654 latency = kDefaultLatencyUsecs; 655 } else { 656 // kLowLatency is 0, so we treat it the same as a request for zero latency. 657 // Compute what the user asked for. 658 latency = rtc::kNumMicrosecsPerSec * 659 params.latency / 660 params.freq / 661 FrameSize(params); 662 // And this is what we'll actually use. 663 latency = rtc::_max(latency, kMinimumLatencyUsecs); 664 } 665 666 ASSERT(static_cast<int>(params.format) < 667 ARRAY_SIZE(kCricketFormatToAlsaFormatTable)); 668 669 err = symbol_table_.snd_pcm_set_params()( 670 handle, 671 kCricketFormatToAlsaFormatTable[params.format], 672 // SoundSystemInterface only supports interleaved audio. 673 SND_PCM_ACCESS_RW_INTERLEAVED, 674 params.channels, 675 params.freq, 676 1, // Allow ALSA to resample. 677 latency); 678 if (err != 0) { 679 LOG(LS_ERROR) << "snd_pcm_set_params(): " << GetError(err); 680 goto fail; 681 } 682 683 err = symbol_table_.snd_pcm_prepare()(handle); 684 if (err != 0) { 685 LOG(LS_ERROR) << "snd_pcm_prepare(): " << GetError(err); 686 goto fail; 687 } 688 689 stream = (this->*start_fn)( 690 handle, 691 FrameSize(params), 692 // We set the wait time to twice the requested latency, so that wait 693 // timeouts should be rare. 694 2 * latency / rtc::kNumMicrosecsPerMillisec, 695 params.flags, 696 params.freq); 697 if (stream) { 698 return stream; 699 } 700 // Else fall through. 701 702 fail: 703 err = symbol_table_.snd_pcm_close()(handle); 704 if (err != 0) { 705 LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err); 706 } 707 return NULL; 708 } 709 710 SoundOutputStreamInterface *AlsaSoundSystem::StartOutputStream( 711 snd_pcm_t *handle, 712 size_t frame_size, 713 int wait_timeout_ms, 714 int flags, 715 int freq) { 716 // Nothing to do here but instantiate the stream. 717 return new AlsaOutputStream( 718 this, handle, frame_size, wait_timeout_ms, flags, freq); 719 } 720 721 SoundInputStreamInterface *AlsaSoundSystem::StartInputStream( 722 snd_pcm_t *handle, 723 size_t frame_size, 724 int wait_timeout_ms, 725 int flags, 726 int freq) { 727 // Output streams start automatically once enough data has been written, but 728 // input streams must be started manually or else snd_pcm_wait() will never 729 // return true. 730 int err; 731 err = symbol_table_.snd_pcm_start()(handle); 732 if (err != 0) { 733 LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err); 734 return NULL; 735 } 736 return new AlsaInputStream( 737 this, handle, frame_size, wait_timeout_ms, flags, freq); 738 } 739 740 inline const char *AlsaSoundSystem::GetError(int err) { 741 return symbol_table_.snd_strerror()(err); 742 } 743 744 } // namespace rtc 745