| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "media/audio/audio_output_resampler.h" | 
 |  | 
 | #include <stdint.h> | 
 |  | 
 | #include <algorithm> | 
 | #include <memory> | 
 | #include <string> | 
 | #include <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/callback_helpers.h" | 
 | #include "base/compiler_specific.h" | 
 | #include "base/logging.h" | 
 | #include "base/macros.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/metrics/histogram_functions.h" | 
 | #include "base/numerics/safe_conversions.h" | 
 | #include "base/trace_event/trace_event.h" | 
 | #include "build/build_config.h" | 
 | #include "media/audio/audio_manager.h" | 
 | #include "media/audio/audio_output_dispatcher_impl.h" | 
 | #include "media/audio/audio_output_proxy.h" | 
 | #include "media/base/audio_converter.h" | 
 | #include "media/base/audio_timestamp_helper.h" | 
 | #include "media/base/limits.h" | 
 | #include "media/base/sample_rates.h" | 
 |  | 
 | namespace media { | 
 |  | 
 | class OnMoreDataConverter | 
 |     : public AudioOutputStream::AudioSourceCallback, | 
 |       public AudioConverter::InputCallback { | 
 |  public: | 
 |   OnMoreDataConverter(const AudioParameters& input_params, | 
 |                       const AudioParameters& output_params, | 
 |                       std::unique_ptr<AudioDebugRecorder> debug_recorder); | 
 |  | 
 |   OnMoreDataConverter(const OnMoreDataConverter&) = delete; | 
 |   OnMoreDataConverter& operator=(const OnMoreDataConverter&) = delete; | 
 |  | 
 |   ~OnMoreDataConverter() override; | 
 |  | 
 |   // AudioSourceCallback interface. | 
 |   int OnMoreData(base::TimeDelta delay, | 
 |                  base::TimeTicks delay_timestamp, | 
 |                  int prior_frames_skipped, | 
 |                  AudioBus* dest) override; | 
 |   void OnError(ErrorType type) override; | 
 |  | 
 |   // Sets |source_callback_|.  If this is not a new object, then Stop() must be | 
 |   // called before Start(). | 
 |   void Start(AudioOutputStream::AudioSourceCallback* callback); | 
 |  | 
 |   // Clears |source_callback_| and flushes the resampler. | 
 |   void Stop(); | 
 |  | 
 |   bool started() const { return source_callback_ != nullptr; } | 
 |  | 
 |   bool error_occurred() const { return error_occurred_; } | 
 |  | 
 |  private: | 
 |   // AudioConverter::InputCallback implementation. | 
 |   double ProvideInput(AudioBus* audio_bus, uint32_t frames_delayed) override; | 
 |  | 
 |   // Source callback. | 
 |   AudioOutputStream::AudioSourceCallback* source_callback_; | 
 |  | 
 |   // Last |delay| and |delay_timestamp| received via OnMoreData(). Used to | 
 |   // correct playback delay in ProvideInput() before calling |source_callback_|. | 
 |   base::TimeDelta current_delay_; | 
 |   base::TimeTicks current_delay_timestamp_; | 
 |  | 
 |   const int input_samples_per_second_; | 
 |  | 
 |   // Handles resampling, buffering, and channel mixing between input and output | 
 |   // parameters. | 
 |   AudioConverter audio_converter_; | 
 |  | 
 |   // True if OnError() was ever called.  Should only be read if the underlying | 
 |   // stream has been stopped. | 
 |   bool error_occurred_; | 
 |  | 
 |   // Information about input and output buffer sizes to be traced. | 
 |   const int input_buffer_size_; | 
 |   const int output_buffer_size_; | 
 |  | 
 |   // For audio debug recordings. | 
 |   std::unique_ptr<AudioDebugRecorder> debug_recorder_; | 
 | }; | 
 |  | 
 | namespace { | 
 |  | 
 | // Record UMA statistics for hardware output configuration. | 
 | static void RecordStats(const AudioParameters& output_params) { | 
 |   base::UmaHistogramEnumeration( | 
 |       "Media.HardwareAudioChannelLayout", output_params.channel_layout(), | 
 |       static_cast<ChannelLayout>(CHANNEL_LAYOUT_MAX + 1)); | 
 |   base::UmaHistogramExactLinear("Media.HardwareAudioChannelCount", | 
 |                                 output_params.channels(), | 
 |                                 static_cast<int>(limits::kMaxChannels)); | 
 |  | 
 |   AudioSampleRate asr; | 
 |   if (!ToAudioSampleRate(output_params.sample_rate(), &asr)) | 
 |     return; | 
 |  | 
 |   base::UmaHistogramEnumeration( | 
 |       "Media.HardwareAudioSamplesPerSecond", asr, | 
 |       static_cast<AudioSampleRate>(kAudioSampleRateMax + 1)); | 
 | } | 
 |  | 
 | // Only Windows has a high latency output driver that is not the same as the low | 
 | // latency path. | 
 | #if defined(OS_WIN) | 
 | // Converts low latency based |output_params| into high latency appropriate | 
 | // output parameters in error situations. | 
 | AudioParameters GetFallbackOutputParams( | 
 |     const AudioParameters& original_output_params) { | 
 |   DCHECK_EQ(original_output_params.format(), | 
 |             AudioParameters::AUDIO_PCM_LOW_LATENCY); | 
 |   // Choose AudioParameters appropriate for opening the device in high latency | 
 |   // mode.  |kMinLowLatencyFrameSize| is arbitrarily based on Pepper Flash's | 
 |   // MAXIMUM frame size for low latency. | 
 |   static const int kMinLowLatencyFrameSize = 2048; | 
 |   const int frames_per_buffer = std::max( | 
 |       original_output_params.frames_per_buffer(), kMinLowLatencyFrameSize); | 
 |  | 
 |   return AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, | 
 |                          original_output_params.channel_layout(), | 
 |                          original_output_params.sample_rate(), | 
 |                          frames_per_buffer); | 
 | } | 
 | #endif | 
 |  | 
 | // This enum must match the numbering for | 
 | // AudioOutputResamplerOpenLowLatencyStreamResult in enums.xml. Do not reorder | 
 | // or remove items, only add new items before OPEN_STREAM_MAX. | 
 | enum class OpenStreamResult { | 
 |   kFail = 0, | 
 |   kFallbackToFake = 1, | 
 |   kFallbackToLinear = 2, | 
 |   kSuccess = 3, | 
 |   kFallbackToFakeFail = 4, | 
 |   kFallbackToFakeSuccess = 5, | 
 |   kFallbackToLinearFail = 6, | 
 |   kFallbackToLinearSuccess = 7, | 
 |   kSubsequentFail = 8, | 
 |   kSubsequentSuccess = 9, | 
 |   kMaxValue = kSubsequentSuccess, | 
 | }; | 
 |  | 
 | OpenStreamResult GetSubsequentStreamCreationResultBucket( | 
 |     const AudioParameters& current_params, | 
 |     bool success) { | 
 |   switch (current_params.format()) { | 
 |     case AudioParameters::AUDIO_PCM_LOW_LATENCY: | 
 |       return success ? OpenStreamResult::kSubsequentSuccess | 
 |                      : OpenStreamResult::kSubsequentFail; | 
 |     case AudioParameters::AUDIO_PCM_LINEAR: | 
 |       return success ? OpenStreamResult::kFallbackToLinearSuccess | 
 |                      : OpenStreamResult::kFallbackToLinearFail; | 
 |     case AudioParameters::AUDIO_FAKE: | 
 |       return success ? OpenStreamResult::kFallbackToFakeSuccess | 
 |                      : OpenStreamResult::kFallbackToFakeFail; | 
 |     default: | 
 |       NOTREACHED(); | 
 |       return OpenStreamResult::kFail; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | AudioOutputResampler::AudioOutputResampler( | 
 |     AudioManager* audio_manager, | 
 |     const AudioParameters& input_params, | 
 |     const AudioParameters& output_params, | 
 |     const std::string& output_device_id, | 
 |     base::TimeDelta close_delay, | 
 |     const RegisterDebugRecordingSourceCallback& | 
 |         register_debug_recording_source_callback) | 
 |     : AudioOutputDispatcher(audio_manager), | 
 |       close_delay_(close_delay), | 
 |       input_params_(input_params), | 
 |       output_params_(output_params), | 
 |       original_output_params_(output_params), | 
 |       device_id_(output_device_id), | 
 |       reinitialize_timer_( | 
 |           FROM_HERE, | 
 |           close_delay_, | 
 |           base::BindRepeating(&AudioOutputResampler::Reinitialize, | 
 |                               base::Unretained(this))), | 
 |       register_debug_recording_source_callback_( | 
 |           register_debug_recording_source_callback) { | 
 |   DCHECK(audio_manager->GetTaskRunner()->BelongsToCurrentThread()); | 
 |   DCHECK(input_params.IsValid()); | 
 |   DCHECK(output_params.IsValid()); | 
 |   DCHECK(output_params_.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY || | 
 |          output_params_.format() == AudioParameters::AUDIO_PCM_LINEAR); | 
 |   DCHECK(register_debug_recording_source_callback_); | 
 |  | 
 |   // Record UMA statistics for the hardware configuration. | 
 |   RecordStats(output_params); | 
 | } | 
 |  | 
 | AudioOutputResampler::~AudioOutputResampler() { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |   for (const auto& item : callbacks_) { | 
 |     if (item.second->started()) | 
 |       StopStreamInternal(item); | 
 |   } | 
 | } | 
 |  | 
 | void AudioOutputResampler::Reinitialize() { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |  | 
 |   // We can only reinitialize the dispatcher if it has no active proxies. Check | 
 |   // if one has been created since the reinitialization timer was started. | 
 |   if (dispatcher_ && dispatcher_->HasOutputProxies()) | 
 |     return; | 
 |  | 
 |   DCHECK(callbacks_.empty()); | 
 |  | 
 |   // Log a trace event so we can get feedback in the field when this happens. | 
 |   TRACE_EVENT0("audio", "AudioOutputResampler::Reinitialize"); | 
 |  | 
 |   output_params_ = original_output_params_; | 
 |   dispatcher_.reset(); | 
 | } | 
 |  | 
 | std::unique_ptr<AudioOutputDispatcherImpl> AudioOutputResampler::MakeDispatcher( | 
 |     const std::string& output_device_id, | 
 |     const AudioParameters& params) { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |   DCHECK(callbacks_.empty()); | 
 |   return std::make_unique<AudioOutputDispatcherImpl>( | 
 |       audio_manager(), params, output_device_id, close_delay_); | 
 | } | 
 |  | 
 | AudioOutputProxy* AudioOutputResampler::CreateStreamProxy() { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |   return new AudioOutputProxy(weak_factory_.GetWeakPtr()); | 
 | } | 
 |  | 
 | bool AudioOutputResampler::OpenStream() { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |  | 
 |   bool first_stream = false; | 
 |   if (!dispatcher_) { | 
 |     first_stream = true; | 
 |     // No open streams => no fallback has happened. | 
 |     DCHECK(original_output_params_.Equals(output_params_)); | 
 |     DCHECK(callbacks_.empty()); | 
 |     dispatcher_ = MakeDispatcher(device_id_, output_params_); | 
 |   } | 
 |  | 
 |   constexpr char kFallbackHistogramName[] = | 
 |       "Media.FallbackToHighLatencyAudioPath"; | 
 |   constexpr char kOpenLowLatencyHistogramName[] = | 
 |       "Media.AudioOutputResampler.OpenLowLatencyStream"; | 
 |  | 
 |   if (dispatcher_->OpenStream()) { | 
 |     // Only record the UMA statistic if we didn't fallback during construction | 
 |     // and only for the first stream we open. | 
 |     if (original_output_params_.format() == | 
 |         AudioParameters::AUDIO_PCM_LOW_LATENCY) { | 
 |       if (first_stream) | 
 |         base::UmaHistogramBoolean(kFallbackHistogramName, false); | 
 |  | 
 |       base::UmaHistogramEnumeration( | 
 |           kOpenLowLatencyHistogramName, | 
 |           first_stream | 
 |               ? OpenStreamResult::kSuccess | 
 |               : GetSubsequentStreamCreationResultBucket(output_params_, true)); | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Fallback is available for low latency streams only. | 
 |   if (original_output_params_.format() != | 
 |       AudioParameters::AUDIO_PCM_LOW_LATENCY) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // If we have successfully opened a stream previously, there's nothing more to | 
 |   // be done. | 
 |   if (!first_stream) { | 
 |     base::UmaHistogramEnumeration( | 
 |         kOpenLowLatencyHistogramName, | 
 |         GetSubsequentStreamCreationResultBucket(output_params_, false)); | 
 |     return false; | 
 |   } | 
 |  | 
 |   base::UmaHistogramBoolean(kFallbackHistogramName, true); | 
 |  | 
 |   // Only Windows has a high latency output driver that is not the same as the | 
 |   // low latency path. | 
 | #if defined(OS_WIN) | 
 |   DLOG(ERROR) << "Unable to open audio device in low latency mode.  Falling " | 
 |               << "back to high latency audio output."; | 
 |  | 
 |   output_params_ = GetFallbackOutputParams(original_output_params_); | 
 |   const std::string fallback_device_id = ""; | 
 |   dispatcher_ = MakeDispatcher(fallback_device_id, output_params_); | 
 |   if (dispatcher_->OpenStream()) { | 
 |     base::UmaHistogramEnumeration(kOpenLowLatencyHistogramName, | 
 |                                   OpenStreamResult::kFallbackToLinear); | 
 |     return true; | 
 |   } | 
 | #endif | 
 |  | 
 |   DLOG(ERROR) << "Unable to open audio device in high latency mode.  Falling " | 
 |               << "back to fake audio output."; | 
 |  | 
 |   // Finally fall back to a fake audio output device. | 
 |   output_params_ = input_params_; | 
 |   output_params_.set_format(AudioParameters::AUDIO_FAKE); | 
 |   dispatcher_ = MakeDispatcher(device_id_, output_params_); | 
 |   if (dispatcher_->OpenStream()) { | 
 |     base::UmaHistogramEnumeration(kOpenLowLatencyHistogramName, | 
 |                                   OpenStreamResult::kFallbackToFake); | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Resetting the malfunctioning dispatcher. | 
 |   Reinitialize(); | 
 |  | 
 |   base::UmaHistogramEnumeration(kOpenLowLatencyHistogramName, | 
 |                                 OpenStreamResult::kFail); | 
 |   return false; | 
 | } | 
 |  | 
 | bool AudioOutputResampler::StartStream( | 
 |     AudioOutputStream::AudioSourceCallback* callback, | 
 |     AudioOutputProxy* stream_proxy) { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |   DCHECK(dispatcher_); | 
 |  | 
 |   OnMoreDataConverter* resampler_callback = nullptr; | 
 |   auto it = callbacks_.find(stream_proxy); | 
 |   if (it == callbacks_.end()) { | 
 |     // If a register callback has been given, register and pass the returned | 
 |     // recoder to the converter. Data is fed to same recorder for the lifetime | 
 |     // of the converter, which is until the stream is closed. | 
 |     resampler_callback = new OnMoreDataConverter( | 
 |         input_params_, output_params_, | 
 |         register_debug_recording_source_callback_.Run(output_params_)); | 
 |     callbacks_[stream_proxy] = | 
 |         base::WrapUnique<OnMoreDataConverter>(resampler_callback); | 
 |   } else { | 
 |     resampler_callback = it->second.get(); | 
 |   } | 
 |  | 
 |   resampler_callback->Start(callback); | 
 |   bool result = dispatcher_->StartStream(resampler_callback, stream_proxy); | 
 |   if (!result) | 
 |     resampler_callback->Stop(); | 
 |   return result; | 
 | } | 
 |  | 
 | void AudioOutputResampler::StreamVolumeSet(AudioOutputProxy* stream_proxy, | 
 |                                            double volume) { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |   DCHECK(dispatcher_); | 
 |   dispatcher_->StreamVolumeSet(stream_proxy, volume); | 
 | } | 
 |  | 
 | void AudioOutputResampler::StopStream(AudioOutputProxy* stream_proxy) { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |  | 
 |   auto it = callbacks_.find(stream_proxy); | 
 |   DCHECK(it != callbacks_.end()); | 
 |   StopStreamInternal(*it); | 
 | } | 
 |  | 
 | void AudioOutputResampler::CloseStream(AudioOutputProxy* stream_proxy) { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |   DCHECK(dispatcher_); | 
 |  | 
 |   dispatcher_->CloseStream(stream_proxy); | 
 |  | 
 |   // We assume that StopStream() is always called prior to CloseStream(), so | 
 |   // that it is safe to delete the OnMoreDataConverter here. | 
 |   callbacks_.erase(stream_proxy); | 
 |  | 
 |   // Start the reinitialization timer if there are no active proxies and we're | 
 |   // not using the originally requested output parameters.  This allows us to | 
 |   // recover from transient output creation errors. | 
 |   if (!dispatcher_->HasOutputProxies() && callbacks_.empty() && | 
 |       !output_params_.Equals(original_output_params_)) { | 
 |     reinitialize_timer_.Reset(); | 
 |   } | 
 | } | 
 |  | 
 | void AudioOutputResampler::FlushStream(AudioOutputProxy* stream_proxy) { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |   DCHECK(dispatcher_); | 
 |  | 
 |   dispatcher_->FlushStream(stream_proxy); | 
 | } | 
 |  | 
 | void AudioOutputResampler::StopStreamInternal( | 
 |     const CallbackMap::value_type& item) { | 
 |   DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread()); | 
 |   DCHECK(dispatcher_); | 
 |   AudioOutputProxy* stream_proxy = item.first; | 
 |   OnMoreDataConverter* callback = item.second.get(); | 
 |   DCHECK(callback->started()); | 
 |  | 
 |   // Stop the underlying physical stream. | 
 |   dispatcher_->StopStream(stream_proxy); | 
 |  | 
 |   // Now that StopStream() has completed the underlying physical stream should | 
 |   // be stopped and no longer calling OnMoreData(), making it safe to Stop() the | 
 |   // OnMoreDataConverter. | 
 |   callback->Stop(); | 
 |  | 
 |   // Destroy idle streams if any errors occurred during output; this ensures | 
 |   // bad streams will not be reused.  Note: Errors may occur during the Stop() | 
 |   // call above. | 
 |   if (callback->error_occurred()) | 
 |     dispatcher_->CloseAllIdleStreams(); | 
 | } | 
 |  | 
 | OnMoreDataConverter::OnMoreDataConverter( | 
 |     const AudioParameters& input_params, | 
 |     const AudioParameters& output_params, | 
 |     std::unique_ptr<AudioDebugRecorder> debug_recorder) | 
 |     : source_callback_(nullptr), | 
 |       input_samples_per_second_(input_params.sample_rate()), | 
 |       audio_converter_(input_params, output_params, false), | 
 |       error_occurred_(false), | 
 |       input_buffer_size_(input_params.frames_per_buffer()), | 
 |       output_buffer_size_(output_params.frames_per_buffer()), | 
 |       debug_recorder_(std::move(debug_recorder)) {} | 
 |  | 
 | OnMoreDataConverter::~OnMoreDataConverter() { | 
 |   // Ensure Stop() has been called so we don't end up with an AudioOutputStream | 
 |   // calling back into OnMoreData() after destruction. | 
 |   CHECK(!source_callback_); | 
 | } | 
 |  | 
 | void OnMoreDataConverter::Start( | 
 |     AudioOutputStream::AudioSourceCallback* callback) { | 
 |   CHECK(!source_callback_); | 
 |   CHECK(callback); | 
 |   source_callback_ = callback; | 
 |  | 
 |   // While AudioConverter can handle multiple inputs, we're using it only with | 
 |   // a single input currently.  Eventually this may be the basis for a browser | 
 |   // side mixer. | 
 |   audio_converter_.AddInput(this); | 
 | } | 
 |  | 
 | void OnMoreDataConverter::Stop() { | 
 |   CHECK(source_callback_); | 
 |   audio_converter_.RemoveInput(this); | 
 |   source_callback_ = nullptr; | 
 | } | 
 |  | 
 | int OnMoreDataConverter::OnMoreData(base::TimeDelta delay, | 
 |                                     base::TimeTicks delay_timestamp, | 
 |                                     int /* prior_frames_skipped */, | 
 |                                     AudioBus* dest) { | 
 |   TRACE_EVENT2("audio", "OnMoreDataConverter::OnMoreData", "input buffer size", | 
 |                input_buffer_size_, "output buffer size", output_buffer_size_); | 
 |   current_delay_ = delay; | 
 |   current_delay_timestamp_ = delay_timestamp; | 
 |   audio_converter_.Convert(dest); | 
 |  | 
 |   if (debug_recorder_) | 
 |     debug_recorder_->OnData(dest); | 
 |  | 
 |   // Always return the full number of frames requested, ProvideInput() | 
 |   // will pad with silence if it wasn't able to acquire enough data. | 
 |   return dest->frames(); | 
 | } | 
 |  | 
 | double OnMoreDataConverter::ProvideInput(AudioBus* dest, | 
 |                                          uint32_t frames_delayed) { | 
 |   base::TimeDelta new_delay = | 
 |       current_delay_ + AudioTimestampHelper::FramesToTime( | 
 |                            frames_delayed, input_samples_per_second_); | 
 |   // Retrieve data from the original callback. | 
 |   const int frames = source_callback_->OnMoreData( | 
 |       new_delay, current_delay_timestamp_, 0, dest); | 
 |  | 
 |   // Zero any unfilled frames if anything was filled, otherwise we'll just | 
 |   // return a volume of zero and let AudioConverter drop the output. | 
 |   if (frames > 0 && frames < dest->frames()) | 
 |     dest->ZeroFramesPartial(frames, dest->frames() - frames); | 
 |   return frames > 0 ? 1 : 0; | 
 | } | 
 |  | 
 | void OnMoreDataConverter::OnError(ErrorType type) { | 
 |   error_occurred_ = true; | 
 |   source_callback_->OnError(type); | 
 | } | 
 |  | 
 | }  // namespace media |