audio_low_latency_input_mac.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "media/audio/mac/audio_low_latency_input_mac.h" 6 7#include <CoreServices/CoreServices.h> 8 9#include "base/basictypes.h" 10#include "base/logging.h" 11#include "base/mac/mac_logging.h" 12#include "media/audio/audio_util.h" 13#include "media/audio/mac/audio_manager_mac.h" 14#include "media/base/data_buffer.h" 15 16namespace media { 17 18static const int kMinIntervalBetweenVolumeUpdatesMs = 1000; 19 20static std::ostream& operator<<(std::ostream& os, 21 const AudioStreamBasicDescription& format) { 22 os << "sample rate : " << format.mSampleRate << std::endl 23 << "format ID : " << format.mFormatID << std::endl 24 << "format flags : " << format.mFormatFlags << std::endl 25 << "bytes per packet : " << format.mBytesPerPacket << std::endl 26 << "frames per packet : " << format.mFramesPerPacket << std::endl 27 << "bytes per frame : " << format.mBytesPerFrame << std::endl 28 << "channels per frame: " << format.mChannelsPerFrame << std::endl 29 << "bits per channel : " << format.mBitsPerChannel; 30 return os; 31} 32 33// See "Technical Note TN2091 - Device input using the HAL Output Audio Unit" 34// http://developer.apple.com/library/mac/#technotes/tn2091/_index.html 35// for more details and background regarding this implementation. 36 37AUAudioInputStream::AUAudioInputStream( 38 AudioManagerMac* manager, const AudioParameters& params, 39 AudioDeviceID audio_device_id) 40 : manager_(manager), 41 sink_(NULL), 42 audio_unit_(0), 43 input_device_id_(audio_device_id), 44 started_(false), 45 hardware_latency_frames_(0), 46 number_of_channels_in_frame_(0) { 47 DCHECK(manager_); 48 49 // Set up the desired (output) format specified by the client. 50 format_.mSampleRate = params.sample_rate(); 51 format_.mFormatID = kAudioFormatLinearPCM; 52 format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | 53 kLinearPCMFormatFlagIsSignedInteger; 54 format_.mBitsPerChannel = params.bits_per_sample(); 55 format_.mChannelsPerFrame = params.channels(); 56 format_.mFramesPerPacket = 1; // uncompressed audio 57 format_.mBytesPerPacket = (format_.mBitsPerChannel * 58 params.channels()) / 8; 59 format_.mBytesPerFrame = format_.mBytesPerPacket; 60 format_.mReserved = 0; 61 62 DVLOG(1) << "Desired ouput format: " << format_; 63 64 // Set number of sample frames per callback used by the internal audio layer. 65 // An internal FIFO is then utilized to adapt the internal size to the size 66 // requested by the client. 67 // Note that we use the same native buffer size as for the output side here 68 // since the AUHAL implementation requires that both capture and render side 69 // use the same buffer size. See http://crbug.com/154352 for more details. 70 // TODO(xians): Get the audio parameters from the right device. 71 const AudioParameters parameters = 72 manager_->GetInputStreamParameters(AudioManagerBase::kDefaultDeviceId); 73 number_of_frames_ = parameters.frames_per_buffer(); 74 DVLOG(1) << "Size of data buffer in frames : " << number_of_frames_; 75 76 // Derive size (in bytes) of the buffers that we will render to. 77 UInt32 data_byte_size = number_of_frames_ * format_.mBytesPerFrame; 78 DVLOG(1) << "Size of data buffer in bytes : " << data_byte_size; 79 80 // Allocate AudioBuffers to be used as storage for the received audio. 81 // The AudioBufferList structure works as a placeholder for the 82 // AudioBuffer structure, which holds a pointer to the actual data buffer. 83 audio_data_buffer_.reset(new uint8[data_byte_size]); 84 audio_buffer_list_.mNumberBuffers = 1; 85 86 AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers; 87 audio_buffer->mNumberChannels = params.channels(); 88 audio_buffer->mDataByteSize = data_byte_size; 89 audio_buffer->mData = audio_data_buffer_.get(); 90 91 // Set up an internal FIFO buffer that will accumulate recorded audio frames 92 // until a requested size is ready to be sent to the client. 93 // It is not possible to ask for less than |kAudioFramesPerCallback| number of 94 // audio frames. 95 const size_t requested_size_frames = 96 params.GetBytesPerBuffer() / format_.mBytesPerPacket; 97 DCHECK_GE(requested_size_frames, number_of_frames_); 98 requested_size_bytes_ = requested_size_frames * format_.mBytesPerFrame; 99 DVLOG(1) << "Requested buffer size in bytes : " << requested_size_bytes_; 100 DLOG_IF(INFO, requested_size_frames > number_of_frames_) << "FIFO is used"; 101 102 // Allocate some extra memory to avoid memory reallocations. 103 // Ensure that the size is an even multiple of |number_of_frames_ and 104 // larger than |requested_size_frames|. 105 // Example: number_of_frames_=128, requested_size_frames=480 => 106 // allocated space equals 4*128=512 audio frames 107 const int max_forward_capacity = format_.mBytesPerFrame * number_of_frames_ * 108 ((requested_size_frames / number_of_frames_) + 1); 109 fifo_.reset(new media::SeekableBuffer(0, max_forward_capacity)); 110 111 data_ = new media::DataBuffer(requested_size_bytes_); 112} 113 114AUAudioInputStream::~AUAudioInputStream() {} 115 116// Obtain and open the AUHAL AudioOutputUnit for recording. 117bool AUAudioInputStream::Open() { 118 // Verify that we are not already opened. 119 if (audio_unit_) 120 return false; 121 122 // Verify that we have a valid device. 123 if (input_device_id_ == kAudioObjectUnknown) { 124 NOTREACHED() << "Device ID is unknown"; 125 return false; 126 } 127 128 // Start by obtaining an AudioOuputUnit using an AUHAL component description. 129 130 Component comp; 131 ComponentDescription desc; 132 133 // Description for the Audio Unit we want to use (AUHAL in this case). 134 desc.componentType = kAudioUnitType_Output; 135 desc.componentSubType = kAudioUnitSubType_HALOutput; 136 desc.componentManufacturer = kAudioUnitManufacturer_Apple; 137 desc.componentFlags = 0; 138 desc.componentFlagsMask = 0; 139 comp = FindNextComponent(0, &desc); 140 DCHECK(comp); 141 142 // Get access to the service provided by the specified Audio Unit. 143 OSStatus result = OpenAComponent(comp, &audio_unit_); 144 if (result) { 145 HandleError(result); 146 return false; 147 } 148 149 // Enable IO on the input scope of the Audio Unit. 150 151 // After creating the AUHAL object, we must enable IO on the input scope 152 // of the Audio Unit to obtain the device input. Input must be explicitly 153 // enabled with the kAudioOutputUnitProperty_EnableIO property on Element 1 154 // of the AUHAL. Beacause the AUHAL can be used for both input and output, 155 // we must also disable IO on the output scope. 156 157 UInt32 enableIO = 1; 158 159 // Enable input on the AUHAL. 160 result = AudioUnitSetProperty(audio_unit_, 161 kAudioOutputUnitProperty_EnableIO, 162 kAudioUnitScope_Input, 163 1, // input element 1 164 &enableIO, // enable 165 sizeof(enableIO)); 166 if (result) { 167 HandleError(result); 168 return false; 169 } 170 171 // Disable output on the AUHAL. 172 enableIO = 0; 173 result = AudioUnitSetProperty(audio_unit_, 174 kAudioOutputUnitProperty_EnableIO, 175 kAudioUnitScope_Output, 176 0, // output element 0 177 &enableIO, // disable 178 sizeof(enableIO)); 179 if (result) { 180 HandleError(result); 181 return false; 182 } 183 184 // Next, set the audio device to be the Audio Unit's current device. 185 // Note that, devices can only be set to the AUHAL after enabling IO. 186 result = AudioUnitSetProperty(audio_unit_, 187 kAudioOutputUnitProperty_CurrentDevice, 188 kAudioUnitScope_Global, 189 0, 190 &input_device_id_, 191 sizeof(input_device_id_)); 192 if (result) { 193 HandleError(result); 194 return false; 195 } 196 197 // Register the input procedure for the AUHAL. 198 // This procedure will be called when the AUHAL has received new data 199 // from the input device. 200 AURenderCallbackStruct callback; 201 callback.inputProc = InputProc; 202 callback.inputProcRefCon = this; 203 result = AudioUnitSetProperty(audio_unit_, 204 kAudioOutputUnitProperty_SetInputCallback, 205 kAudioUnitScope_Global, 206 0, 207 &callback, 208 sizeof(callback)); 209 if (result) { 210 HandleError(result); 211 return false; 212 } 213 214 // Set up the the desired (output) format. 215 // For obtaining input from a device, the device format is always expressed 216 // on the output scope of the AUHAL's Element 1. 217 result = AudioUnitSetProperty(audio_unit_, 218 kAudioUnitProperty_StreamFormat, 219 kAudioUnitScope_Output, 220 1, 221 &format_, 222 sizeof(format_)); 223 if (result) { 224 HandleError(result); 225 return false; 226 } 227 228 // Set the desired number of frames in the IO buffer (output scope). 229 // WARNING: Setting this value changes the frame size for all audio units in 230 // the current process. It's imperative that the input and output frame sizes 231 // be the same as the frames_per_buffer() returned by 232 // GetInputStreamParameters(). 233 // TODO(henrika): Due to http://crrev.com/159666 this is currently not true 234 // and should be fixed, a CHECK() should be added at that time. 235 result = AudioUnitSetProperty(audio_unit_, 236 kAudioDevicePropertyBufferFrameSize, 237 kAudioUnitScope_Output, 238 1, 239 &number_of_frames_, // size is set in the ctor 240 sizeof(number_of_frames_)); 241 if (result) { 242 HandleError(result); 243 return false; 244 } 245 246 // Finally, initialize the audio unit and ensure that it is ready to render. 247 // Allocates memory according to the maximum number of audio frames 248 // it can produce in response to a single render call. 249 result = AudioUnitInitialize(audio_unit_); 250 if (result) { 251 HandleError(result); 252 return false; 253 } 254 255 // The hardware latency is fixed and will not change during the call. 256 hardware_latency_frames_ = GetHardwareLatency(); 257 258 // The master channel is 0, Left and right are channels 1 and 2. 259 // And the master channel is not counted in |number_of_channels_in_frame_|. 260 number_of_channels_in_frame_ = GetNumberOfChannelsFromStream(); 261 262 return true; 263} 264 265void AUAudioInputStream::Start(AudioInputCallback* callback) { 266 DCHECK(callback); 267 DLOG_IF(ERROR, !audio_unit_) << "Open() has not been called successfully"; 268 if (started_ || !audio_unit_) 269 return; 270 sink_ = callback; 271 OSStatus result = AudioOutputUnitStart(audio_unit_); 272 if (result == noErr) { 273 started_ = true; 274 } 275 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) 276 << "Failed to start acquiring data"; 277} 278 279void AUAudioInputStream::Stop() { 280 if (!started_) 281 return; 282 OSStatus result = AudioOutputUnitStop(audio_unit_); 283 if (result == noErr) { 284 started_ = false; 285 } 286 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) 287 << "Failed to stop acquiring data"; 288} 289 290void AUAudioInputStream::Close() { 291 // It is valid to call Close() before calling open or Start(). 292 // It is also valid to call Close() after Start() has been called. 293 if (started_) { 294 Stop(); 295 } 296 if (audio_unit_) { 297 // Deallocate the audio unit’s resources. 298 AudioUnitUninitialize(audio_unit_); 299 300 // Terminates our connection to the AUHAL component. 301 CloseComponent(audio_unit_); 302 audio_unit_ = 0; 303 } 304 if (sink_) { 305 sink_->OnClose(this); 306 sink_ = NULL; 307 } 308 309 // Inform the audio manager that we have been closed. This can cause our 310 // destruction. 311 manager_->ReleaseInputStream(this); 312} 313 314double AUAudioInputStream::GetMaxVolume() { 315 // Verify that we have a valid device. 316 if (input_device_id_ == kAudioObjectUnknown) { 317 NOTREACHED() << "Device ID is unknown"; 318 return 0.0; 319 } 320 321 // Query if any of the master, left or right channels has volume control. 322 for (int i = 0; i <= number_of_channels_in_frame_; ++i) { 323 // If the volume is settable, the valid volume range is [0.0, 1.0]. 324 if (IsVolumeSettableOnChannel(i)) 325 return 1.0; 326 } 327 328 // Volume control is not available for the audio stream. 329 return 0.0; 330} 331 332void AUAudioInputStream::SetVolume(double volume) { 333 DVLOG(1) << "SetVolume(volume=" << volume << ")"; 334 DCHECK_GE(volume, 0.0); 335 DCHECK_LE(volume, 1.0); 336 337 // Verify that we have a valid device. 338 if (input_device_id_ == kAudioObjectUnknown) { 339 NOTREACHED() << "Device ID is unknown"; 340 return; 341 } 342 343 Float32 volume_float32 = static_cast<Float32>(volume); 344 AudioObjectPropertyAddress property_address = { 345 kAudioDevicePropertyVolumeScalar, 346 kAudioDevicePropertyScopeInput, 347 kAudioObjectPropertyElementMaster 348 }; 349 350 // Try to set the volume for master volume channel. 351 if (IsVolumeSettableOnChannel(kAudioObjectPropertyElementMaster)) { 352 OSStatus result = AudioObjectSetPropertyData(input_device_id_, 353 &property_address, 354 0, 355 NULL, 356 sizeof(volume_float32), 357 &volume_float32); 358 if (result != noErr) { 359 DLOG(WARNING) << "Failed to set volume to " << volume_float32; 360 } 361 return; 362 } 363 364 // There is no master volume control, try to set volume for each channel. 365 int successful_channels = 0; 366 for (int i = 1; i <= number_of_channels_in_frame_; ++i) { 367 property_address.mElement = static_cast<UInt32>(i); 368 if (IsVolumeSettableOnChannel(i)) { 369 OSStatus result = AudioObjectSetPropertyData(input_device_id_, 370 &property_address, 371 0, 372 NULL, 373 sizeof(volume_float32), 374 &volume_float32); 375 if (result == noErr) 376 ++successful_channels; 377 } 378 } 379 380 DLOG_IF(WARNING, successful_channels == 0) 381 << "Failed to set volume to " << volume_float32; 382 383 // Update the AGC volume level based on the last setting above. Note that, 384 // the volume-level resolution is not infinite and it is therefore not 385 // possible to assume that the volume provided as input parameter can be 386 // used directly. Instead, a new query to the audio hardware is required. 387 // This method does nothing if AGC is disabled. 388 UpdateAgcVolume(); 389} 390 391double AUAudioInputStream::GetVolume() { 392 // Verify that we have a valid device. 393 if (input_device_id_ == kAudioObjectUnknown){ 394 NOTREACHED() << "Device ID is unknown"; 395 return 0.0; 396 } 397 398 AudioObjectPropertyAddress property_address = { 399 kAudioDevicePropertyVolumeScalar, 400 kAudioDevicePropertyScopeInput, 401 kAudioObjectPropertyElementMaster 402 }; 403 404 if (AudioObjectHasProperty(input_device_id_, &property_address)) { 405 // The device supports master volume control, get the volume from the 406 // master channel. 407 Float32 volume_float32 = 0.0; 408 UInt32 size = sizeof(volume_float32); 409 OSStatus result = AudioObjectGetPropertyData(input_device_id_, 410 &property_address, 411 0, 412 NULL, 413 &size, 414 &volume_float32); 415 if (result == noErr) 416 return static_cast<double>(volume_float32); 417 } else { 418 // There is no master volume control, try to get the average volume of 419 // all the channels. 420 Float32 volume_float32 = 0.0; 421 int successful_channels = 0; 422 for (int i = 1; i <= number_of_channels_in_frame_; ++i) { 423 property_address.mElement = static_cast<UInt32>(i); 424 if (AudioObjectHasProperty(input_device_id_, &property_address)) { 425 Float32 channel_volume = 0; 426 UInt32 size = sizeof(channel_volume); 427 OSStatus result = AudioObjectGetPropertyData(input_device_id_, 428 &property_address, 429 0, 430 NULL, 431 &size, 432 &channel_volume); 433 if (result == noErr) { 434 volume_float32 += channel_volume; 435 ++successful_channels; 436 } 437 } 438 } 439 440 // Get the average volume of the channels. 441 if (successful_channels != 0) 442 return static_cast<double>(volume_float32 / successful_channels); 443 } 444 445 DLOG(WARNING) << "Failed to get volume"; 446 return 0.0; 447} 448 449// AUHAL AudioDeviceOutput unit callback 450OSStatus AUAudioInputStream::InputProc(void* user_data, 451 AudioUnitRenderActionFlags* flags, 452 const AudioTimeStamp* time_stamp, 453 UInt32 bus_number, 454 UInt32 number_of_frames, 455 AudioBufferList* io_data) { 456 // Verify that the correct bus is used (Input bus/Element 1) 457 DCHECK_EQ(bus_number, static_cast<UInt32>(1)); 458 AUAudioInputStream* audio_input = 459 reinterpret_cast<AUAudioInputStream*>(user_data); 460 DCHECK(audio_input); 461 if (!audio_input) 462 return kAudioUnitErr_InvalidElement; 463 464 // Receive audio from the AUHAL from the output scope of the Audio Unit. 465 OSStatus result = AudioUnitRender(audio_input->audio_unit(), 466 flags, 467 time_stamp, 468 bus_number, 469 number_of_frames, 470 audio_input->audio_buffer_list()); 471 if (result) 472 return result; 473 474 // Deliver recorded data to the consumer as a callback. 475 return audio_input->Provide(number_of_frames, 476 audio_input->audio_buffer_list(), 477 time_stamp); 478} 479 480OSStatus AUAudioInputStream::Provide(UInt32 number_of_frames, 481 AudioBufferList* io_data, 482 const AudioTimeStamp* time_stamp) { 483 // Update the capture latency. 484 double capture_latency_frames = GetCaptureLatency(time_stamp); 485 486 // Update the AGC volume level once every second. Note that, |volume| is 487 // also updated each time SetVolume() is called through IPC by the 488 // render-side AGC. 489 double normalized_volume = 0.0; 490 QueryAgcVolume(&normalized_volume); 491 492 AudioBuffer& buffer = io_data->mBuffers[0]; 493 uint8* audio_data = reinterpret_cast<uint8*>(buffer.mData); 494 uint32 capture_delay_bytes = static_cast<uint32> 495 ((capture_latency_frames + 0.5) * format_.mBytesPerFrame); 496 DCHECK(audio_data); 497 if (!audio_data) 498 return kAudioUnitErr_InvalidElement; 499 500 // Accumulate captured audio in FIFO until we can match the output size 501 // requested by the client. 502 fifo_->Append(audio_data, buffer.mDataByteSize); 503 504 // Deliver recorded data to the client as soon as the FIFO contains a 505 // sufficient amount. 506 while (fifo_->forward_bytes() >= requested_size_bytes_) { 507 // Read from FIFO into temporary data buffer. 508 fifo_->Read(data_->GetWritableData(), requested_size_bytes_); 509 510 // Deliver data packet, delay estimation and volume level to the user. 511 sink_->OnData(this, 512 data_->GetData(), 513 requested_size_bytes_, 514 capture_delay_bytes, 515 normalized_volume); 516 } 517 518 return noErr; 519} 520 521int AUAudioInputStream::HardwareSampleRate() { 522 // Determine the default input device's sample-rate. 523 AudioDeviceID device_id = kAudioObjectUnknown; 524 UInt32 info_size = sizeof(device_id); 525 526 AudioObjectPropertyAddress default_input_device_address = { 527 kAudioHardwarePropertyDefaultInputDevice, 528 kAudioObjectPropertyScopeGlobal, 529 kAudioObjectPropertyElementMaster 530 }; 531 OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, 532 &default_input_device_address, 533 0, 534 0, 535 &info_size, 536 &device_id); 537 if (result != noErr) 538 return 0.0; 539 540 Float64 nominal_sample_rate; 541 info_size = sizeof(nominal_sample_rate); 542 543 AudioObjectPropertyAddress nominal_sample_rate_address = { 544 kAudioDevicePropertyNominalSampleRate, 545 kAudioObjectPropertyScopeGlobal, 546 kAudioObjectPropertyElementMaster 547 }; 548 result = AudioObjectGetPropertyData(device_id, 549 &nominal_sample_rate_address, 550 0, 551 0, 552 &info_size, 553 &nominal_sample_rate); 554 if (result != noErr) 555 return 0.0; 556 557 return static_cast<int>(nominal_sample_rate); 558} 559 560double AUAudioInputStream::GetHardwareLatency() { 561 if (!audio_unit_ || input_device_id_ == kAudioObjectUnknown) { 562 DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown"; 563 return 0.0; 564 } 565 566 // Get audio unit latency. 567 Float64 audio_unit_latency_sec = 0.0; 568 UInt32 size = sizeof(audio_unit_latency_sec); 569 OSStatus result = AudioUnitGetProperty(audio_unit_, 570 kAudioUnitProperty_Latency, 571 kAudioUnitScope_Global, 572 0, 573 &audio_unit_latency_sec, 574 &size); 575 OSSTATUS_DLOG_IF(WARNING, result != noErr, result) 576 << "Could not get audio unit latency"; 577 578 // Get input audio device latency. 579 AudioObjectPropertyAddress property_address = { 580 kAudioDevicePropertyLatency, 581 kAudioDevicePropertyScopeInput, 582 kAudioObjectPropertyElementMaster 583 }; 584 UInt32 device_latency_frames = 0; 585 size = sizeof(device_latency_frames); 586 result = AudioObjectGetPropertyData(input_device_id_, 587 &property_address, 588 0, 589 NULL, 590 &size, 591 &device_latency_frames); 592 DLOG_IF(WARNING, result != noErr) << "Could not get audio device latency."; 593 594 return static_cast<double>((audio_unit_latency_sec * 595 format_.mSampleRate) + device_latency_frames); 596} 597 598double AUAudioInputStream::GetCaptureLatency( 599 const AudioTimeStamp* input_time_stamp) { 600 // Get the delay between between the actual recording instant and the time 601 // when the data packet is provided as a callback. 602 UInt64 capture_time_ns = AudioConvertHostTimeToNanos( 603 input_time_stamp->mHostTime); 604 UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); 605 double delay_frames = static_cast<double> 606 (1e-9 * (now_ns - capture_time_ns) * format_.mSampleRate); 607 608 // Total latency is composed by the dynamic latency and the fixed 609 // hardware latency. 610 return (delay_frames + hardware_latency_frames_); 611} 612 613int AUAudioInputStream::GetNumberOfChannelsFromStream() { 614 // Get the stream format, to be able to read the number of channels. 615 AudioObjectPropertyAddress property_address = { 616 kAudioDevicePropertyStreamFormat, 617 kAudioDevicePropertyScopeInput, 618 kAudioObjectPropertyElementMaster 619 }; 620 AudioStreamBasicDescription stream_format; 621 UInt32 size = sizeof(stream_format); 622 OSStatus result = AudioObjectGetPropertyData(input_device_id_, 623 &property_address, 624 0, 625 NULL, 626 &size, 627 &stream_format); 628 if (result != noErr) { 629 DLOG(WARNING) << "Could not get stream format"; 630 return 0; 631 } 632 633 return static_cast<int>(stream_format.mChannelsPerFrame); 634} 635 636void AUAudioInputStream::HandleError(OSStatus err) { 637 NOTREACHED() << "error " << GetMacOSStatusErrorString(err) 638 << " (" << err << ")"; 639 if (sink_) 640 sink_->OnError(this); 641} 642 643bool AUAudioInputStream::IsVolumeSettableOnChannel(int channel) { 644 Boolean is_settable = false; 645 AudioObjectPropertyAddress property_address = { 646 kAudioDevicePropertyVolumeScalar, 647 kAudioDevicePropertyScopeInput, 648 static_cast<UInt32>(channel) 649 }; 650 OSStatus result = AudioObjectIsPropertySettable(input_device_id_, 651 &property_address, 652 &is_settable); 653 return (result == noErr) ? is_settable : false; 654} 655 656} // namespace media 657