Commit dcb4ac86 authored by kmackay's avatar kmackay Committed by Commit bot

[Chromecast] Make ALSA rendering delay either accurate or kNoTimestamp

Update the ALSA backend so that the calculated rendering delay is either
accurate, or kNoTimestamp. We no longer try to guess the correct delay
timestamp. Note that this requires callers of GetRenderingDelay() to
correctly handle the kNoTimestamp case.

BUG= internal b/30034408

Review-Url: https://codereview.chromium.org/2133293003
Cr-Commit-Position: refs/heads/master@{#405893}
parent 1be5656f
......@@ -51,6 +51,10 @@ void AlsaWrapper::PcmStatusGetHtstamp(const snd_pcm_status_t* obj,
snd_pcm_status_get_htstamp(obj, ptr);
}
snd_pcm_state_t AlsaWrapper::PcmStatusGetState(const snd_pcm_status_t* obj) {
return snd_pcm_status_get_state(obj);
}
int AlsaWrapper::PcmHwParamsMalloc(snd_pcm_hw_params_t** ptr) {
return snd_pcm_hw_params_malloc(ptr);
}
......
......@@ -27,6 +27,7 @@ class AlsaWrapper : public ::media::AlsaWrapper {
virtual snd_pcm_sframes_t PcmStatusGetDelay(const snd_pcm_status_t* obj);
virtual void PcmStatusGetHtstamp(const snd_pcm_status_t* obj,
snd_htimestamp_t* ptr);
virtual snd_pcm_state_t PcmStatusGetState(const snd_pcm_status_t* obj);
virtual ssize_t PcmFormatSize(snd_pcm_format_t format, size_t samples);
virtual int PcmHwParamsMalloc(snd_pcm_hw_params_t** ptr);
virtual void PcmHwParamsFree(snd_pcm_hw_params_t* obj);
......
......@@ -77,15 +77,7 @@ bool AudioDecoderAlsa::Initialize() {
is_eos_ = false;
last_buffer_pts_ = std::numeric_limits<int64_t>::min();
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
last_known_delay_.timestamp_microseconds =
static_cast<int64_t>(now.tv_sec) * 1000000 + now.tv_nsec / 1000;
} else {
LOG(ERROR) << "Failed to get current timestamp";
last_known_delay_.timestamp_microseconds = kInvalidDelayTimestamp;
return false;
}
last_known_delay_.timestamp_microseconds = kInvalidDelayTimestamp;
last_known_delay_.delay_microseconds = 0;
return true;
}
......
......@@ -8,6 +8,7 @@
// Define dummy structs here to avoid linking in the ALSA lib.
struct _snd_pcm_hw_params {};
struct _snd_pcm_status {};
struct _snd_pcm {};
namespace chromecast {
......@@ -25,11 +26,13 @@ class MockAlsaWrapper::FakeAlsaWrapper : public AlsaWrapper {
FakeAlsaWrapper()
: state_(SND_PCM_STATE_RUNNING),
fake_handle_(nullptr),
fake_pcm_hw_params_(nullptr) {}
fake_pcm_hw_params_(nullptr),
fake_pcm_status_(nullptr) {}
~FakeAlsaWrapper() override {
delete fake_handle_;
delete fake_pcm_hw_params_;
delete fake_pcm_status_;
}
// AlsaWrapper implementation:
......@@ -72,6 +75,17 @@ class MockAlsaWrapper::FakeAlsaWrapper : public AlsaWrapper {
return 0;
}
int PcmStatusMalloc(snd_pcm_status_t** ptr) override {
fake_pcm_status_ = new snd_pcm_status_t();
CHECK(fake_pcm_status_);
*ptr = fake_pcm_status_;
return 0;
}
snd_pcm_state_t PcmStatusGetState(const snd_pcm_status_t* obj) override {
return state_;
}
ssize_t PcmFormatSize(snd_pcm_format_t format, size_t samples) override {
return kBytesPerSample * samples;
};
......@@ -83,6 +97,7 @@ class MockAlsaWrapper::FakeAlsaWrapper : public AlsaWrapper {
snd_pcm_state_t state_;
snd_pcm_t* fake_handle_;
snd_pcm_hw_params_t* fake_pcm_hw_params_;
snd_pcm_status_t* fake_pcm_status_;
std::vector<uint8_t> data_;
DISALLOW_COPY_AND_ASSIGN(FakeAlsaWrapper);
......@@ -106,6 +121,12 @@ MockAlsaWrapper::MockAlsaWrapper() : fake_(new FakeAlsaWrapper()) {
ON_CALL(*this, PcmHwParamsCanPause(_)).WillByDefault(testing::Return(true));
ON_CALL(*this, PcmHwParamsMalloc(_)).WillByDefault(
testing::Invoke(fake_.get(), &FakeAlsaWrapper::PcmHwParamsMalloc));
ON_CALL(*this, PcmStatusMalloc(_))
.WillByDefault(
testing::Invoke(fake_.get(), &FakeAlsaWrapper::PcmStatusMalloc));
ON_CALL(*this, PcmStatusGetState(_))
.WillByDefault(
testing::Invoke(fake_.get(), &FakeAlsaWrapper::PcmStatusGetState));
}
MockAlsaWrapper::~MockAlsaWrapper() {
......
......@@ -72,6 +72,7 @@ class MockAlsaWrapper : public AlsaWrapper {
snd_pcm_sframes_t(const snd_pcm_status_t* obj));
MOCK_METHOD2(PcmStatusGetHtstamp,
void(const snd_pcm_status_t* obj, snd_htimestamp_t* ptr));
MOCK_METHOD1(PcmStatusGetState, snd_pcm_state_t(const snd_pcm_status_t* obj));
MOCK_METHOD1(PcmHwParamsMalloc, int(snd_pcm_hw_params_t** ptr));
MOCK_METHOD1(PcmHwParamsFree, void(snd_pcm_hw_params_t* obj));
......
......@@ -6,6 +6,7 @@
#include <algorithm>
#include <cmath>
#include <limits>
#include <utility>
#include "base/bind_helpers.h"
......@@ -109,6 +110,8 @@ static int* kAlsaDirDontCare = nullptr;
const snd_pcm_format_t kPreferredSampleFormats[] = {SND_PCM_FORMAT_S32,
SND_PCM_FORMAT_S16};
const int64_t kNoTimestamp = std::numeric_limits<int64_t>::min();
int64_t TimespecToMicroseconds(struct timespec time) {
return static_cast<int64_t>(time.tv_sec) *
base::Time::kMicrosecondsPerSecond +
......@@ -534,9 +537,7 @@ void StreamMixerAlsa::Start() {
RETURN_REPORT_ERROR(PcmPrepare, pcm_);
RETURN_REPORT_ERROR(PcmStatusMalloc, &pcm_status_);
struct timespec now = {0, 0};
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
rendering_delay_.timestamp_microseconds = TimespecToMicroseconds(now);
rendering_delay_.timestamp_microseconds = kNoTimestamp;
rendering_delay_.delay_microseconds = 0;
state_ = kStateNormalPlayback;
......@@ -829,8 +830,14 @@ void StreamMixerAlsa::WriteMixedPcm(const ::media::AudioBus& mixed,
interleaved_.resize(interleaved_size);
}
int64_t expected_playback_time = rendering_delay_.timestamp_microseconds +
rendering_delay_.delay_microseconds;
int64_t expected_playback_time;
if (rendering_delay_.timestamp_microseconds == kNoTimestamp) {
expected_playback_time = kNoTimestamp;
} else {
expected_playback_time = rendering_delay_.timestamp_microseconds +
rendering_delay_.delay_microseconds;
}
mixed.ToInterleaved(frames, BytesPerOutputFormatSample(),
interleaved_.data());
// Filter, send to observers, and post filter
......@@ -878,11 +885,10 @@ void StreamMixerAlsa::UpdateRenderingDelay(int newly_pushed_frames) {
DCHECK(mixer_task_runner_->BelongsToCurrentThread());
CHECK_PCM_INITIALIZED();
if (alsa_->PcmStatus(pcm_, pcm_status_) != 0) {
// Estimate updated delay based on the number of frames we just pushed.
rendering_delay_.delay_microseconds +=
static_cast<int64_t>(newly_pushed_frames) *
base::Time::kMicrosecondsPerSecond / output_samples_per_second_;
if (alsa_->PcmStatus(pcm_, pcm_status_) != 0 ||
alsa_->PcmStatusGetState(pcm_status_) != SND_PCM_STATE_RUNNING) {
rendering_delay_.timestamp_microseconds = kNoTimestamp;
rendering_delay_.delay_microseconds = 0;
return;
}
......
......@@ -5,6 +5,7 @@
#include "chromecast/media/cma/backend/alsa/stream_mixer_alsa_input_impl.h"
#include <algorithm>
#include <limits>
#include "base/bind.h"
#include "base/bind_helpers.h"
......@@ -53,6 +54,7 @@ const int64_t kFadeMs = 15;
// fill the frames with silence.
const int kPausedReadSamples = 512;
const int kDefaultReadSize = ::media::SincResampler::kDefaultRequestSize;
const int64_t kNoTimestamp = std::numeric_limits<int64_t>::min();
} // namespace
......@@ -200,9 +202,11 @@ MediaPipelineBackendAlsa::RenderingDelay StreamMixerAlsaInputImpl::QueueData(
}
MediaPipelineBackendAlsa::RenderingDelay delay = mixer_rendering_delay_;
delay.delay_microseconds += static_cast<int64_t>(
queued_frames_including_resampler_ * base::Time::kMicrosecondsPerSecond /
input_samples_per_second_);
if (delay.timestamp_microseconds != kNoTimestamp) {
delay.delay_microseconds += static_cast<int64_t>(
queued_frames_including_resampler_ *
base::Time::kMicrosecondsPerSecond / input_samples_per_second_);
}
return delay;
}
......
......@@ -41,9 +41,9 @@ const int64_t kMicrosecondsPerSecond = 1000 * 1000;
// Total length of test, in microseconds.
const int64_t kPushTimeUs = 2 * kMicrosecondsPerSecond;
const int64_t kStartPts = 0;
const int64_t kRenderingDelayGracePeriodUs = 250 * 1000;
const int64_t kMaxRenderingDelayErrorUs = 200;
const int kNumEffectsStreams = 1;
const int64_t kNoTimestamp = std::numeric_limits<int64_t>::min();
void IgnoreEos() {}
......@@ -171,7 +171,7 @@ BufferFeeder::BufferFeeder(const AudioConfig& config,
push_limit_us_(effects_only_ ? 0 : kPushTimeUs),
last_push_length_us_(0),
pushed_us_(0),
next_push_playback_timestamp_(0) {
next_push_playback_timestamp_(kNoTimestamp) {
CHECK(!eos_cb_.is_null());
}
......@@ -246,26 +246,30 @@ void BufferFeeder::OnPushBufferComplete(BufferStatus status) {
if (!effects_only_) {
MediaPipelineBackend::AudioDecoder::RenderingDelay delay =
decoder_->GetRenderingDelay();
int64_t expected_next_push_playback_timestamp =
next_push_playback_timestamp_ + last_push_length_us_;
next_push_playback_timestamp_ =
delay.timestamp_microseconds + delay.delay_microseconds;
// Check rendering delay accuracy only if we have pushed more than
// kRenderingDelayGracePeriodUs of data.
if (pushed_us_ > kRenderingDelayGracePeriodUs) {
int64_t error =
next_push_playback_timestamp_ - expected_next_push_playback_timestamp;
max_rendering_delay_error_us_ =
std::max(max_rendering_delay_error_us_, std::abs(error));
total_rendering_delay_error_us_ += std::abs(error);
if (error >= 0) {
max_positive_rendering_delay_error_us_ =
std::max(max_positive_rendering_delay_error_us_, error);
if (delay.timestamp_microseconds != kNoTimestamp) {
if (next_push_playback_timestamp_ == kNoTimestamp) {
next_push_playback_timestamp_ =
delay.timestamp_microseconds + delay.delay_microseconds;
} else {
max_negative_rendering_delay_error_us_ =
std::min(max_negative_rendering_delay_error_us_, error);
int64_t expected_next_push_playback_timestamp =
next_push_playback_timestamp_ + last_push_length_us_;
next_push_playback_timestamp_ =
delay.timestamp_microseconds + delay.delay_microseconds;
int64_t error = next_push_playback_timestamp_ -
expected_next_push_playback_timestamp;
max_rendering_delay_error_us_ =
std::max(max_rendering_delay_error_us_, std::abs(error));
total_rendering_delay_error_us_ += std::abs(error);
if (error >= 0) {
max_positive_rendering_delay_error_us_ =
std::max(max_positive_rendering_delay_error_us_, error);
} else {
max_negative_rendering_delay_error_us_ =
std::min(max_negative_rendering_delay_error_us_, error);
}
sample_count_++;
}
sample_count_++;
}
}
pushed_us_ += last_push_length_us_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment