summaryrefslogtreecommitdiffstats
path: root/media/base/audio_discard_helper.cc
blob: 12c8e347d2c203f3f294ff07c9a1a5df905d7e26 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// 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"

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),
      delayed_end_discard_(0) {
  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.get()) {
    // 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;

    DVLOG(1) << "Initial discard of " << frames_to_discard << " out of "
             << decoded_frames << " frames.";

    // If everything would be discarded, indicate a new buffer is required.
    if (frames_to_discard == decoded_frames) {
      // For simplicity, we just drop any discard padding if |discard_frames_|
      // consumes the entire buffer.
      return false;
    }

    decoded_buffer->TrimStart(frames_to_discard);
  }

  // Process any delayed end discard from the previous buffer.
  if (delayed_end_discard_ > 0) {
    DCHECK_GT(decoder_delay_, 0u);

    const size_t discard_index = decoder_delay_ - delayed_end_discard_;
    DCHECK_LT(discard_index, decoder_delay_);

    const size_t decoded_frames = decoded_buffer->frame_count();
    DCHECK_LT(delayed_end_discard_, decoded_frames);

    DVLOG(1) << "Delayed end discard of " << delayed_end_discard_ << " out of "
             << decoded_frames << " frames starting at " << discard_index;

    decoded_buffer->TrimRange(discard_index,
                              discard_index + delayed_end_discard_);
    delayed_end_discard_ = 0;
  }

  // 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;

    DVLOG(1) << "Front discard of " << frames_to_discard << " out of "
             << decoded_frames << " frames starting at " << discard_start;

    // 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()) {
    const size_t decoded_frames = decoded_buffer->frame_count();
    size_t end_frames_to_discard =
        TimeDeltaToFrames(current_discard_padding.second);

    if (decoder_delay_) {
      // Delayed end discard only works if the decoder delay is less than a
      // single buffer.
      DCHECK_LT(decoder_delay_, original_frame_count);

      // If the discard is >= the decoder delay, trim everything we can off the
      // end of this buffer and the rest from the start of the next.
      if (end_frames_to_discard >= decoder_delay_) {
        DCHECK(!discard_frames_);
        discard_frames_ = decoder_delay_;
        end_frames_to_discard -= decoder_delay_;
      } else {
        DCHECK(!delayed_end_discard_);
        std::swap(delayed_end_discard_, end_frames_to_discard);
      }
    }

    if (end_frames_to_discard > decoded_frames) {
      DLOG(ERROR) << "Encountered invalid discard padding value.";
      return false;
    }

    if (end_frames_to_discard > 0) {
      DVLOG(1) << "End discard of " << end_frames_to_discard << " out of "
               << decoded_frames;

      // 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