Index: media/audio/win/audio_low_latency_output_win.cc |
diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc |
index 83bac1625f80a99db61fb912782382cb953ad1b6..494d1b1f9fe3579271df1baf83c5844da5168a83 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; |
} |