#include "media/base/audio_splicer.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/demuxer_stream.h"
+#include "media/filters/audio_clock.h"
#include "media/filters/decrypting_demuxer_stream.h"
namespace media {
pending_read_(false),
received_end_of_stream_(false),
rendered_end_of_stream_(false),
- audio_time_buffered_(kNoTimestamp()),
- current_time_(kNoTimestamp()),
underflow_disabled_(false),
preroll_aborted_(false),
weak_factory_(this) {
DCHECK_EQ(state_, kPaused);
DCHECK(!flush_cb_.is_null());
- audio_time_buffered_ = kNoTimestamp();
- current_time_ = kNoTimestamp();
+ audio_clock_.reset(new AudioClock(audio_parameters_.sample_rate()));
received_end_of_stream_ = false;
rendered_end_of_stream_ = false;
preroll_aborted_ = false;
const base::Closure& underflow_cb,
const TimeCB& time_cb,
const base::Closure& ended_cb,
- const base::Closure& disabled_cb,
const PipelineStatusCB& error_cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(stream);
DCHECK(!underflow_cb.is_null());
DCHECK(!time_cb.is_null());
DCHECK(!ended_cb.is_null());
- DCHECK(!disabled_cb.is_null());
DCHECK(!error_cb.is_null());
DCHECK_EQ(kUninitialized, state_);
DCHECK(sink_);
underflow_cb_ = underflow_cb;
time_cb_ = time_cb;
ended_cb_ = ended_cb;
- disabled_cb_ = disabled_cb;
error_cb_ = error_cb;
expecting_config_changes_ = stream->SupportsConfigChanges();
buffer_converter_.reset();
} else {
// TODO(rileya): Support hardware config changes
- audio_parameters_ = hardware_config_->GetOutputConfig();
+ const AudioParameters& hw_params = hardware_config_->GetOutputConfig();
+ audio_parameters_.Reset(hw_params.format(),
+ hw_params.channel_layout(),
+ hw_params.channels(),
+ hw_params.input_channels(),
+ hw_params.sample_rate(),
+ hw_params.bits_per_sample(),
+ hardware_config_->GetHighLatencyBufferSize());
}
+ audio_clock_.reset(new AudioClock(audio_parameters_.sample_rate()));
+
audio_buffer_stream_.Initialize(
stream,
+ false,
statistics_cb,
base::Bind(&AudioRendererImpl::OnAudioBufferStreamInitialized,
weak_factory_.GetWeakPtr()));
int AudioRendererImpl::Render(AudioBus* audio_bus,
int audio_delay_milliseconds) {
const int requested_frames = audio_bus->frames();
- base::TimeDelta current_time = kNoTimestamp();
- base::TimeDelta max_time = kNoTimestamp();
base::TimeDelta playback_delay = base::TimeDelta::FromMilliseconds(
audio_delay_milliseconds);
-
+ const int delay_frames = static_cast<int>(playback_delay.InSecondsF() *
+ audio_parameters_.sample_rate());
int frames_written = 0;
+ base::Closure time_cb;
base::Closure underflow_cb;
{
base::AutoLock auto_lock(lock_);
// Ensure Stop() hasn't destroyed our |algorithm_| on the pipeline thread.
- if (!algorithm_)
+ if (!algorithm_) {
+ audio_clock_->WroteSilence(requested_frames, delay_frames);
return 0;
+ }
float playback_rate = algorithm_->playback_rate();
- if (playback_rate == 0)
+ if (playback_rate == 0) {
+ audio_clock_->WroteSilence(requested_frames, delay_frames);
return 0;
+ }
// Mute audio by returning 0 when not playing.
- if (state_ != kPlaying)
+ if (state_ != kPlaying) {
+ audio_clock_->WroteSilence(requested_frames, delay_frames);
return 0;
+ }
// We use the following conditions to determine end of playback:
// 1) Algorithm can not fill the audio callback buffer
// 3) We are in the kPlaying state
//
// Otherwise the buffer has data we can send to the device.
- frames_written = algorithm_->FillBuffer(audio_bus, requested_frames);
+ const base::TimeDelta media_timestamp_before_filling =
+ audio_clock_->CurrentMediaTimestamp();
+ if (algorithm_->frames_buffered() > 0) {
+ frames_written = algorithm_->FillBuffer(audio_bus, requested_frames);
+ audio_clock_->WroteAudio(
+ frames_written, delay_frames, playback_rate, algorithm_->GetTime());
+ }
+ audio_clock_->WroteSilence(requested_frames - frames_written, delay_frames);
+
if (frames_written == 0) {
const base::TimeTicks now = now_cb_.Run();
weak_factory_.GetWeakPtr()));
}
- // The |audio_time_buffered_| is the ending timestamp of the last frame
- // buffered at the audio device. |playback_delay| is the amount of time
- // buffered at the audio device. The current time can be computed by their
- // difference.
- if (audio_time_buffered_ != kNoTimestamp()) {
- // Adjust the delay according to playback rate.
- base::TimeDelta adjusted_playback_delay =
- base::TimeDelta::FromMicroseconds(ceil(
- playback_delay.InMicroseconds() * playback_rate));
-
- base::TimeDelta previous_time = current_time_;
- current_time_ = audio_time_buffered_ - adjusted_playback_delay;
-
- // Time can change in one of two ways:
- // 1) The time of the audio data at the audio device changed, or
- // 2) The playback delay value has changed
- //
- // We only want to set |current_time| (and thus execute |time_cb_|) if
- // time has progressed and we haven't signaled end of stream yet.
- //
- // Why? The current latency of the system results in getting the last call
- // to FillBuffer() later than we'd like, which delays firing the 'ended'
- // event, which delays the looping/trigging performance of short sound
- // effects.
- //
- // TODO(scherkus): revisit this and switch back to relying on playback
- // delay after we've revamped our audio IPC subsystem.
- if (current_time_ > previous_time && !rendered_end_of_stream_) {
- current_time = current_time_;
- }
+ // We only want to execute |time_cb_| if time has progressed and we haven't
+ // signaled end of stream yet.
+ if (media_timestamp_before_filling !=
+ audio_clock_->CurrentMediaTimestamp() &&
+ !rendered_end_of_stream_) {
+ time_cb = base::Bind(time_cb_,
+ audio_clock_->CurrentMediaTimestamp(),
+ audio_clock_->last_endpoint_timestamp());
}
- // The call to FillBuffer() on |algorithm_| has increased the amount of
- // buffered audio data. Update the new amount of time buffered.
- max_time = algorithm_->GetTime();
- audio_time_buffered_ = max_time;
-
if (frames_written > 0) {
UpdateEarliestEndTime_Locked(
frames_written, playback_delay, now_cb_.Run());
}
}
- if (current_time != kNoTimestamp() && max_time != kNoTimestamp())
- time_cb_.Run(current_time, max_time);
+ if (!time_cb.is_null())
+ time_cb.Run();
if (!underflow_cb.is_null())
underflow_cb.Run();
}
void AudioRendererImpl::OnRenderError() {
+ // UMA data tells us this happens ~0.01% of the time. Trigger an error instead
+ // of trying to gracefully fall back to a fake sink. It's very likely
+ // OnRenderError() should be removed and the audio stack handle errors without
+ // notifying clients. See http://crbug.com/234708 for details.
HistogramRendererEvent(RENDER_ERROR);
- disabled_cb_.Run();
+ error_cb_.Run(PIPELINE_ERROR_DECODE);
}
void AudioRendererImpl::DisableUnderflowForTesting() {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(expecting_config_changes_);
buffer_converter_->ResetTimestampState();
+ // Drain flushed buffers from the converter so the AudioSplicer receives all
+ // data ahead of any OnNewSpliceBuffer() calls. Since discontinuities should
+ // only appear after config changes, AddInput() should never fail here.
+ while (buffer_converter_->HasNextBuffer())
+ CHECK(splicer_->AddInput(buffer_converter_->GetNextBuffer()));
}
} // namespace media