1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "media/base/audio_discard_helper.h"
6
7#include <algorithm>
8
9#include "base/logging.h"
10#include "media/base/audio_buffer.h"
11#include "media/base/buffers.h"
12
13namespace media {
14
15static void WarnOnNonMonotonicTimestamps(base::TimeDelta last_timestamp,
16                                         base::TimeDelta current_timestamp) {
17  if (last_timestamp == kNoTimestamp() || last_timestamp < current_timestamp)
18    return;
19
20  const base::TimeDelta diff = current_timestamp - last_timestamp;
21  DLOG(WARNING) << "Input timestamps are not monotonically increasing! "
22                << " ts " << current_timestamp.InMicroseconds() << " us"
23                << " diff " << diff.InMicroseconds() << " us";
24}
25
26AudioDiscardHelper::AudioDiscardHelper(int sample_rate, size_t decoder_delay)
27    : sample_rate_(sample_rate),
28      decoder_delay_(decoder_delay),
29      timestamp_helper_(sample_rate_),
30      discard_frames_(0),
31      last_input_timestamp_(kNoTimestamp()),
32      delayed_discard_(false) {
33  DCHECK_GT(sample_rate_, 0);
34}
35
36AudioDiscardHelper::~AudioDiscardHelper() {
37}
38
39size_t AudioDiscardHelper::TimeDeltaToFrames(base::TimeDelta duration) const {
40  DCHECK(duration >= base::TimeDelta());
41  return duration.InSecondsF() * sample_rate_ + 0.5;
42}
43
44void AudioDiscardHelper::Reset(size_t initial_discard) {
45  discard_frames_ = initial_discard;
46  last_input_timestamp_ = kNoTimestamp();
47  timestamp_helper_.SetBaseTimestamp(kNoTimestamp());
48  delayed_discard_ = false;
49  delayed_discard_padding_ = DecoderBuffer::DiscardPadding();
50}
51
52bool AudioDiscardHelper::ProcessBuffers(
53    const scoped_refptr<DecoderBuffer>& encoded_buffer,
54    const scoped_refptr<AudioBuffer>& decoded_buffer) {
55  DCHECK(!encoded_buffer->end_of_stream());
56  DCHECK(encoded_buffer->timestamp() != kNoTimestamp());
57
58  // Issue a debug warning when we see non-monotonic timestamps.  Only a warning
59  // to allow chained OGG playback.
60  WarnOnNonMonotonicTimestamps(last_input_timestamp_,
61                               encoded_buffer->timestamp());
62  last_input_timestamp_ = encoded_buffer->timestamp();
63
64  // If this is the first buffer seen, setup the timestamp helper.
65  const bool first_buffer = !initialized();
66  if (first_buffer) {
67    // Clamp the base timestamp to zero.
68    timestamp_helper_.SetBaseTimestamp(
69        std::max(base::TimeDelta(), encoded_buffer->timestamp()));
70  }
71  DCHECK(initialized());
72
73  if (!decoded_buffer) {
74    // If there's a one buffer delay for decoding, we need to save it so it can
75    // be processed with the next decoder buffer.
76    if (first_buffer) {
77      delayed_discard_ = true;
78      delayed_discard_padding_ = encoded_buffer->discard_padding();
79    }
80    return false;
81  }
82
83  const size_t original_frame_count = decoded_buffer->frame_count();
84
85  // If there's a one buffer delay for decoding, pick up the last encoded
86  // buffer's discard padding for processing with the current decoded buffer.
87  DecoderBuffer::DiscardPadding current_discard_padding =
88      encoded_buffer->discard_padding();
89  if (delayed_discard_) {
90    // For simplicity disallow cases where decoder delay is present with delayed
91    // discard (no codecs at present).  Doing so allows us to avoid complexity
92    // around endpoint tracking when handling complete buffer discards.
93    DCHECK_EQ(decoder_delay_, 0u);
94    std::swap(current_discard_padding, delayed_discard_padding_);
95  }
96
97  if (discard_frames_ > 0) {
98    const size_t decoded_frames = decoded_buffer->frame_count();
99    const size_t frames_to_discard = std::min(discard_frames_, decoded_frames);
100    discard_frames_ -= frames_to_discard;
101
102    // If everything would be discarded, indicate a new buffer is required.
103    if (frames_to_discard == decoded_frames) {
104      // For simplicity disallow cases where a buffer with discard padding is
105      // present.  Doing so allows us to avoid complexity around tracking
106      // discards across buffers.
107      DCHECK(current_discard_padding.first == base::TimeDelta());
108      DCHECK(current_discard_padding.second == base::TimeDelta());
109      return false;
110    }
111
112    decoded_buffer->TrimStart(frames_to_discard);
113  }
114
115  // Handle front discard padding.
116  if (current_discard_padding.first > base::TimeDelta()) {
117    const size_t decoded_frames = decoded_buffer->frame_count();
118
119    // If a complete buffer discard is requested and there's no decoder delay,
120    // just discard all remaining frames from this buffer.  With decoder delay
121    // we have to estimate the correct number of frames to discard based on the
122    // duration of the encoded buffer.
123    const size_t start_frames_to_discard =
124        current_discard_padding.first == kInfiniteDuration()
125            ? (decoder_delay_ > 0
126                   ? TimeDeltaToFrames(encoded_buffer->duration())
127                   : decoded_frames)
128            : TimeDeltaToFrames(current_discard_padding.first);
129
130    // Regardless of the timestamp on the encoded buffer, the corresponding
131    // decoded output will appear |decoder_delay_| frames later.
132    size_t discard_start = decoder_delay_;
133    if (decoder_delay_ > 0) {
134      // If we have a |decoder_delay_| and have already discarded frames from
135      // this buffer, the |discard_start| must be adjusted by the number of
136      // frames already discarded.
137      const size_t frames_discarded_so_far =
138          original_frame_count - decoded_buffer->frame_count();
139      CHECK_LE(frames_discarded_so_far, decoder_delay_);
140      discard_start -= frames_discarded_so_far;
141    }
142
143    // For simplicity require the start of the discard to be within the current
144    // buffer.  Doing so allows us avoid complexity around tracking discards
145    // across buffers.
146    CHECK_LT(discard_start, decoded_frames);
147
148    const size_t frames_to_discard =
149        std::min(start_frames_to_discard, decoded_frames - discard_start);
150
151    // Carry over any frames which need to be discarded from the front of the
152    // next buffer.
153    DCHECK(!discard_frames_);
154    discard_frames_ = start_frames_to_discard - frames_to_discard;
155
156    // If everything would be discarded, indicate a new buffer is required.
157    if (frames_to_discard == decoded_frames) {
158      // The buffer should not have been marked with end discard if the front
159      // discard removes everything.
160      DCHECK(current_discard_padding.second == base::TimeDelta());
161      return false;
162    }
163
164    decoded_buffer->TrimRange(discard_start, discard_start + frames_to_discard);
165  } else {
166    DCHECK(current_discard_padding.first == base::TimeDelta());
167  }
168
169  // Handle end discard padding.
170  if (current_discard_padding.second > base::TimeDelta()) {
171    // Limit end discarding to when there is no |decoder_delay_|, otherwise it's
172    // non-trivial determining where to start discarding end frames.
173    CHECK(!decoder_delay_);
174
175    const size_t decoded_frames = decoded_buffer->frame_count();
176    const size_t end_frames_to_discard =
177        TimeDeltaToFrames(current_discard_padding.second);
178
179    if (end_frames_to_discard > decoded_frames) {
180      DLOG(ERROR) << "Encountered invalid discard padding value.";
181      return false;
182    }
183
184    // If everything would be discarded, indicate a new buffer is required.
185    if (end_frames_to_discard == decoded_frames)
186      return false;
187
188    decoded_buffer->TrimEnd(end_frames_to_discard);
189  } else {
190    DCHECK(current_discard_padding.second == base::TimeDelta());
191  }
192
193  // Assign timestamp to the buffer.
194  decoded_buffer->set_timestamp(timestamp_helper_.GetTimestamp());
195  timestamp_helper_.AddFrames(decoded_buffer->frame_count());
196  return true;
197}
198
199}  // namespace media
200