// Copyright 2014 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/base/audio_discard_helper.h"
#include <algorithm>
#include "base/logging.h"
#include "media/base/audio_buffer.h"
#include "media/base/buffers.h"
namespace media {
static void WarnOnNonMonotonicTimestamps(base::TimeDelta last_timestamp,
base::TimeDelta current_timestamp) {
if (last_timestamp == kNoTimestamp() || last_timestamp < current_timestamp)
return;
const base::TimeDelta diff = current_timestamp - last_timestamp;
DLOG(WARNING) << "Input timestamps are not monotonically increasing! "
<< " ts " << current_timestamp.InMicroseconds() << " us"
<< " diff " << diff.InMicroseconds() << " us";
}
AudioDiscardHelper::AudioDiscardHelper(int sample_rate, size_t decoder_delay)
: sample_rate_(sample_rate),
decoder_delay_(decoder_delay),
timestamp_helper_(sample_rate_),
discard_frames_(0),
last_input_timestamp_(kNoTimestamp()),
delayed_discard_(false) {
DCHECK_GT(sample_rate_, 0);
}
AudioDiscardHelper::~AudioDiscardHelper() {
}
size_t AudioDiscardHelper::TimeDeltaToFrames(base::TimeDelta duration) const {
DCHECK(duration >= base::TimeDelta());
return duration.InSecondsF() * sample_rate_ + 0.5;
}
void AudioDiscardHelper::Reset(size_t initial_discard) {
discard_frames_ = initial_discard;
last_input_timestamp_ = kNoTimestamp();
timestamp_helper_.SetBaseTimestamp(kNoTimestamp());
delayed_discard_ = false;
delayed_discard_padding_ = DecoderBuffer::DiscardPadding();
}
bool AudioDiscardHelper::ProcessBuffers(
const scoped_refptr<DecoderBuffer>& encoded_buffer,
const scoped_refptr<AudioBuffer>& decoded_buffer) {
DCHECK(!encoded_buffer->end_of_stream());
DCHECK(encoded_buffer->timestamp() != kNoTimestamp());
// Issue a debug warning when we see non-monotonic timestamps. Only a warning
// to allow chained OGG playback.
WarnOnNonMonotonicTimestamps(last_input_timestamp_,
encoded_buffer->timestamp());
last_input_timestamp_ = encoded_buffer->timestamp();
// If this is the first buffer seen, setup the timestamp helper.
const bool first_buffer = !initialized();
if (first_buffer) {
// Clamp the base timestamp to zero.
timestamp_helper_.SetBaseTimestamp(
std::max(base::TimeDelta(), encoded_buffer->timestamp()));
}
DCHECK(initialized());
if (!decoded_buffer) {
// If there's a one buffer delay for decoding, we need to save it so it can
// be processed with the next decoder buffer.
if (first_buffer) {
delayed_discard_ = true;
delayed_discard_padding_ = encoded_buffer->discard_padding();
}
return false;
}
const size_t original_frame_count = decoded_buffer->frame_count();
// If there's a one buffer delay for decoding, pick up the last encoded
// buffer's discard padding for processing with the current decoded buffer.
DecoderBuffer::DiscardPadding current_discard_padding =
encoded_buffer->discard_padding();
if (delayed_discard_) {
// For simplicity disallow cases where decoder delay is present with delayed
// discard (no codecs at present). Doing so allows us to avoid complexity
// around endpoint tracking when handling complete buffer discards.
DCHECK_EQ(decoder_delay_, 0u);
std::swap(current_discard_padding, delayed_discard_padding_);
}
if (discard_frames_ > 0) {
const size_t decoded_frames = decoded_buffer->frame_count();
const size_t frames_to_discard = std::min(discard_frames_, decoded_frames);
discard_frames_ -= frames_to_discard;
// If everything would be discarded, indicate a new buffer is required.
if (frames_to_discard == decoded_frames) {
// For simplicity disallow cases where a buffer with discard padding is
// present. Doing so allows us to avoid complexity around tracking
// discards across buffers.
DCHECK(current_discard_padding.first == base::TimeDelta());
DCHECK(current_discard_padding.second == base::TimeDelta());
return false;
}
decoded_buffer->TrimStart(frames_to_discard);
}
// Handle front discard padding.
if (current_discard_padding.first > base::TimeDelta()) {
const size_t decoded_frames = decoded_buffer->frame_count();
// If a complete buffer discard is requested and there's no decoder delay,
// just discard all remaining frames from this buffer. With decoder delay
// we have to estimate the correct number of frames to discard based on the
// duration of the encoded buffer.
const size_t start_frames_to_discard =
current_discard_padding.first == kInfiniteDuration()
? (decoder_delay_ > 0
? TimeDeltaToFrames(encoded_buffer->duration())
: decoded_frames)
: TimeDeltaToFrames(current_discard_padding.first);
// Regardless of the timestamp on the encoded buffer, the corresponding
// decoded output will appear |decoder_delay_| frames later.
size_t discard_start = decoder_delay_;
if (decoder_delay_ > 0) {
// If we have a |decoder_delay_| and have already discarded frames from
// this buffer, the |discard_start| must be adjusted by the number of
// frames already discarded.
const size_t frames_discarded_so_far =
original_frame_count - decoded_buffer->frame_count();
CHECK_LE(frames_discarded_so_far, decoder_delay_);
discard_start -= frames_discarded_so_far;
}
// For simplicity require the start of the discard to be within the current
// buffer. Doing so allows us avoid complexity around tracking discards
// across buffers.
CHECK_LT(discard_start, decoded_frames);
const size_t frames_to_discard =
std::min(start_frames_to_discard, decoded_frames - discard_start);
// Carry over any frames which need to be discarded from the front of the
// next buffer.
DCHECK(!discard_frames_);
discard_frames_ = start_frames_to_discard - frames_to_discard;
// If everything would be discarded, indicate a new buffer is required.
if (frames_to_discard == decoded_frames) {
// The buffer should not have been marked with end discard if the front
// discard removes everything.
DCHECK(current_discard_padding.second == base::TimeDelta());
return false;
}
decoded_buffer->TrimRange(discard_start, discard_start + frames_to_discard);
} else {
DCHECK(current_discard_padding.first == base::TimeDelta());
}
// Handle end discard padding.
if (current_discard_padding.second > base::TimeDelta()) {
// Limit end discarding to when there is no |decoder_delay_|, otherwise it's
// non-trivial determining where to start discarding end frames.
CHECK(!decoder_delay_);
const size_t decoded_frames = decoded_buffer->frame_count();
const size_t end_frames_to_discard =
TimeDeltaToFrames(current_discard_padding.second);
if (end_frames_to_discard > decoded_frames) {
DLOG(ERROR) << "Encountered invalid discard padding value.";
return false;
}
// If everything would be discarded, indicate a new buffer is required.
if (end_frames_to_discard == decoded_frames)
return false;
decoded_buffer->TrimEnd(end_frames_to_discard);
} else {
DCHECK(current_discard_padding.second == base::TimeDelta());
}
// Assign timestamp to the buffer.
decoded_buffer->set_timestamp(timestamp_helper_.GetTimestamp());
timestamp_helper_.AddFrames(decoded_buffer->frame_count());
return true;
}
} // namespace media