diff options
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/win/audio_low_latency_output_win.cc | 175 |
1 files changed, 93 insertions, 82 deletions
diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc index 83bac16..494d1b1 100644 --- a/media/audio/win/audio_low_latency_output_win.cc +++ b/media/audio/win/audio_low_latency_output_win.cc @@ -172,16 +172,44 @@ bool WASAPIAudioOutputStream::Open() { if (FAILED(hr)) return false; - // We know from experience that the best possible callback sequence is - // achieved when the packet size (given by the native device period) - // is an even divisor of the endpoint buffer size. + REFERENCE_TIME device_period = 0; + if (FAILED(CoreAudioUtil::GetDevicePeriod( + audio_client.get(), AUDCLNT_SHAREMODE_SHARED, &device_period))) { + return false; + } + + const int preferred_frames_per_buffer = static_cast<int>( + format_.Format.nSamplesPerSec * + CoreAudioUtil::RefererenceTimeToTimeDelta(device_period) + .InSecondsF() + + 0.5); + + // Packet size should always be an even divisor of the device period for + // best performance; things will still work otherwise, but may glitch for a + // couple of reasons. + // + // The first reason is if/when repeated RenderAudioFromSource() hit the + // shared memory boundary between the renderer and the browser. The next + // audio buffer is always requested after the current request is consumed. + // With back-to-back calls the round-trip may not be fast enough and thus + // audio will glitch as we fail to deliver audio in a timely manner. + // + // The second reason is event wakeup efficiency. We may have too few or too + // many frames to fill the output buffer requested by WASAPI. If too few, + // we'll refuse the render event and wait until more output space is + // available. If we have too many frames, we'll only partially fill and + // wait for the next render event. In either case certain remainders may + // leave us unable to fulfill the request in a timely manner, thus glitches. + // + // Log a warning in these cases so we can help users in the field. // Examples: 48kHz => 960 % 480, 44.1kHz => 896 % 448 or 882 % 441. - if (endpoint_buffer_size_frames_ % packet_size_frames_ != 0) { - LOG(ERROR) - << "Bailing out due to non-perfect timing. Buffer size of " + if (preferred_frames_per_buffer % packet_size_frames_) { + LOG(WARNING) + << "Using WASAPI output with a non-optimal buffer size, glitches from" + << " back to back shared memory reads and partial fills of WASAPI" + << " output buffers may occur. Buffer size of " << packet_size_frames_ << " is not an even divisor of " - << endpoint_buffer_size_frames_; - return false; + << preferred_frames_per_buffer; } } else { // TODO(henrika): break out to CoreAudioUtil::ExclusiveModeInitialize() @@ -439,86 +467,69 @@ bool WASAPIAudioOutputStream::RenderAudioFromSource(UINT64 device_frequency) { } // Check if there is enough available space to fit the packet size - // specified by the client. + // specified by the client, wait until a future callback. if (num_available_frames < packet_size_frames_) return true; - DLOG_IF(ERROR, num_available_frames % packet_size_frames_ != 0) - << "Non-perfect timing detected (num_available_frames=" - << num_available_frames << ", packet_size_frames=" - << packet_size_frames_ << ")"; - - // Derive the number of packets we need to get from the client to - // fill up the available area in the endpoint buffer. - // |num_packets| will always be one for exclusive-mode streams and - // will be one in most cases for shared mode streams as well. - // However, we have found that two packets can sometimes be - // required. - size_t num_packets = (num_available_frames / packet_size_frames_); - - for (size_t n = 0; n < num_packets; ++n) { - // Grab all available space in the rendering endpoint buffer - // into which the client can write a data packet. - hr = audio_render_client_->GetBuffer(packet_size_frames_, - &audio_data); - if (FAILED(hr)) { - DLOG(ERROR) << "Failed to use rendering audio buffer: " - << std::hex << hr; - return false; - } - - // Derive the audio delay which corresponds to the delay between - // a render event and the time when the first audio sample in a - // packet is played out through the speaker. This delay value - // can typically be utilized by an acoustic echo-control (AEC) - // unit at the render side. - UINT64 position = 0; - uint32 audio_delay_bytes = 0; - hr = audio_clock_->GetPosition(&position, NULL); - if (SUCCEEDED(hr)) { - // Stream position of the sample that is currently playing - // through the speaker. - double pos_sample_playing_frames = format_.Format.nSamplesPerSec * - (static_cast<double>(position) / device_frequency); - - // Stream position of the last sample written to the endpoint - // buffer. Note that, the packet we are about to receive in - // the upcoming callback is also included. - size_t pos_last_sample_written_frames = - num_written_frames_ + packet_size_frames_; - - // Derive the actual delay value which will be fed to the - // render client using the OnMoreData() callback. - audio_delay_bytes = (pos_last_sample_written_frames - - pos_sample_playing_frames) * format_.Format.nBlockAlign; - } - - // Read a data packet from the registered client source and - // deliver a delay estimate in the same callback to the client. - - int frames_filled = source_->OnMoreData( - audio_bus_.get(), audio_delay_bytes); - uint32 num_filled_bytes = frames_filled * format_.Format.nBlockAlign; - DCHECK_LE(num_filled_bytes, packet_size_bytes_); - - // Note: If this ever changes to output raw float the data must be - // clipped and sanitized since it may come from an untrusted - // source such as NaCl. - const int bytes_per_sample = format_.Format.wBitsPerSample >> 3; - audio_bus_->Scale(volume_); - audio_bus_->ToInterleaved( - frames_filled, bytes_per_sample, audio_data); - - - // Release the buffer space acquired in the GetBuffer() call. - // Render silence if we were not able to fill up the buffer totally. - DWORD flags = (num_filled_bytes < packet_size_bytes_) ? - AUDCLNT_BUFFERFLAGS_SILENT : 0; - audio_render_client_->ReleaseBuffer(packet_size_frames_, flags); + // Grab all available space in the rendering endpoint buffer + // into which the client can write a data packet. + hr = audio_render_client_->GetBuffer(packet_size_frames_, + &audio_data); + if (FAILED(hr)) { + DLOG(ERROR) << "Failed to use rendering audio buffer: " + << std::hex << hr; + return false; + } - num_written_frames_ += packet_size_frames_; + // Derive the audio delay which corresponds to the delay between + // a render event and the time when the first audio sample in a + // packet is played out through the speaker. This delay value + // can typically be utilized by an acoustic echo-control (AEC) + // unit at the render side. + UINT64 position = 0; + uint32 audio_delay_bytes = 0; + hr = audio_clock_->GetPosition(&position, NULL); + if (SUCCEEDED(hr)) { + // Stream position of the sample that is currently playing + // through the speaker. + double pos_sample_playing_frames = format_.Format.nSamplesPerSec * + (static_cast<double>(position) / device_frequency); + + // Stream position of the last sample written to the endpoint + // buffer. Note that, the packet we are about to receive in + // the upcoming callback is also included. + size_t pos_last_sample_written_frames = + num_written_frames_ + packet_size_frames_; + + // Derive the actual delay value which will be fed to the + // render client using the OnMoreData() callback. + audio_delay_bytes = (pos_last_sample_written_frames - + pos_sample_playing_frames) * format_.Format.nBlockAlign; } + // Read a data packet from the registered client source and + // deliver a delay estimate in the same callback to the client. + + int frames_filled = source_->OnMoreData( + audio_bus_.get(), audio_delay_bytes); + uint32 num_filled_bytes = frames_filled * format_.Format.nBlockAlign; + DCHECK_LE(num_filled_bytes, packet_size_bytes_); + + // Note: If this ever changes to output raw float the data must be + // clipped and sanitized since it may come from an untrusted + // source such as NaCl. + const int bytes_per_sample = format_.Format.wBitsPerSample >> 3; + audio_bus_->Scale(volume_); + audio_bus_->ToInterleaved( + frames_filled, bytes_per_sample, audio_data); + + // Release the buffer space acquired in the GetBuffer() call. + // Render silence if we were not able to fill up the buffer totally. + DWORD flags = (num_filled_bytes < packet_size_bytes_) ? + AUDCLNT_BUFFERFLAGS_SILENT : 0; + audio_render_client_->ReleaseBuffer(packet_size_frames_, flags); + + num_written_frames_ += packet_size_frames_; return true; } |