Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/base/audio_splicer.h" | 5 #include "media/base/audio_splicer.h" |
| 6 | 6 |
| 7 #include <cstdlib> | 7 #include <cstdlib> |
| 8 #include <deque> | |
| 8 | 9 |
| 9 #include "base/logging.h" | 10 #include "base/logging.h" |
| 10 #include "media/base/audio_buffer.h" | 11 #include "media/base/audio_buffer.h" |
| 12 #include "media/base/audio_bus.h" | |
| 11 #include "media/base/audio_decoder_config.h" | 13 #include "media/base/audio_decoder_config.h" |
| 12 #include "media/base/audio_timestamp_helper.h" | 14 #include "media/base/audio_timestamp_helper.h" |
| 13 #include "media/base/buffers.h" | 15 #include "media/base/buffers.h" |
| 16 #include "media/base/vector_math.h" | |
| 14 | 17 |
| 15 namespace media { | 18 namespace media { |
| 16 | 19 |
| 17 // Largest gap or overlap allowed by this class. Anything | 20 // Largest gap or overlap allowed by this class. Anything |
| 18 // larger than this will trigger an error. | 21 // larger than this will trigger an error. |
| 19 // This is an arbitrary value, but the initial selection of 50ms | 22 // This is an arbitrary value, but the initial selection of 50ms |
| 20 // roughly represents the duration of 2 compressed AAC or MP3 frames. | 23 // roughly represents the duration of 2 compressed AAC or MP3 frames. |
| 21 static const int kMaxTimeDeltaInMilliseconds = 50; | 24 static const int kMaxTimeDeltaInMilliseconds = 50; |
| 22 | 25 |
| 23 AudioSplicer::AudioSplicer(int samples_per_second) | 26 // Minimum gap size needed before the splicer will take action to |
| 27 // fill a gap. This avoids periodically inserting and then dropping samples | |
| 28 // when the buffer timestamps are slightly off because of timestamp rounding | |
| 29 // in the source content. Unit is frames. | |
| 30 static const int kMinGapSize = 2; | |
| 31 | |
| 32 // The number of milliseconds to crossfade before trimming when buffers overlap. | |
| 33 static const int kCrossfadeDurationInMilliseconds = 5; | |
| 34 | |
| 35 typedef std::deque<scoped_refptr<AudioBuffer> > BufferQueue; | |
| 36 | |
| 37 class AudioStreamSanitizer { | |
| 38 public: | |
| 39 AudioStreamSanitizer(int samples_per_second); | |
| 40 ~AudioStreamSanitizer(); | |
| 41 | |
| 42 // Resets the sanitizer state by clearing the output buffers queue, | |
| 43 // and resetting the timestamp helper. | |
| 44 void Reset(); | |
| 45 | |
| 46 // Adds a new buffer full of samples or end of stream buffer to the splicer. | |
| 47 // Returns true if the buffer was accepted. False is returned if an error | |
| 48 // occurred. | |
| 49 bool AddInput(const scoped_refptr<AudioBuffer>& input); | |
| 50 | |
| 51 // Returns true if the sanitizer has a buffer to return. | |
| 52 bool HasNextBuffer() const; | |
| 53 | |
| 54 // Removes the next buffer from the output buffer queue and returns it. | |
| 55 // This should only be called if HasNextBuffer() returns true. | |
| 56 scoped_refptr<AudioBuffer> GetNextBuffer(); | |
| 57 const scoped_refptr<AudioBuffer>& PeekNextBuffer() const; | |
| 58 | |
| 59 // Get the current timestamp. This value is computed from based on the first | |
|
acolwell GONE FROM CHROMIUM
2014/02/18 23:22:59
nit: word missing?
DaleCurtis
2014/02/19 03:05:14
Yeah, the methods below need comments too; I just
DaleCurtis
2014/02/22 00:59:04
Done.
| |
| 60 // buffer's timestamp and the number of frames that have been added so far. | |
| 61 base::TimeDelta GetTimestamp() const; | |
| 62 | |
| 63 // Get the duration of all buffers in the... | |
| 64 base::TimeDelta GetDuration() const; | |
| 65 int64 frame_count() const { return output_timestamp_helper_.frame_count(); } | |
| 66 | |
| 67 | |
| 68 private: | |
| 69 void AddOutputBuffer(const scoped_refptr<AudioBuffer>& buffer); | |
| 70 | |
| 71 AudioTimestampHelper output_timestamp_helper_; | |
| 72 BufferQueue output_buffers_; | |
| 73 bool received_end_of_stream_; | |
| 74 }; | |
| 75 | |
| 76 AudioStreamSanitizer::AudioStreamSanitizer(int samples_per_second) | |
| 24 : output_timestamp_helper_(samples_per_second), | 77 : output_timestamp_helper_(samples_per_second), |
| 25 min_gap_size_(2), | 78 received_end_of_stream_(false) {} |
| 26 received_end_of_stream_(false) { | |
| 27 } | |
| 28 | 79 |
| 29 AudioSplicer::~AudioSplicer() { | 80 AudioStreamSanitizer::~AudioStreamSanitizer() {} |
| 30 } | |
| 31 | 81 |
| 32 void AudioSplicer::Reset() { | 82 void AudioStreamSanitizer::Reset() { |
| 33 output_timestamp_helper_.SetBaseTimestamp(kNoTimestamp()); | 83 output_timestamp_helper_.SetBaseTimestamp(kNoTimestamp()); |
| 34 output_buffers_.clear(); | 84 output_buffers_.clear(); |
| 35 received_end_of_stream_ = false; | 85 received_end_of_stream_ = false; |
| 36 } | 86 } |
| 37 | 87 |
| 38 bool AudioSplicer::AddInput(const scoped_refptr<AudioBuffer>& input) { | 88 bool AudioStreamSanitizer::AddInput(const scoped_refptr<AudioBuffer>& input) { |
| 39 DCHECK(!received_end_of_stream_ || input->end_of_stream()); | 89 DCHECK(!received_end_of_stream_ || input->end_of_stream()); |
| 40 | 90 |
| 41 if (input->end_of_stream()) { | 91 if (input->end_of_stream()) { |
| 42 output_buffers_.push_back(input); | 92 output_buffers_.push_back(input); |
| 43 received_end_of_stream_ = true; | 93 received_end_of_stream_ = true; |
| 44 return true; | 94 return true; |
| 45 } | 95 } |
| 46 | 96 |
| 47 DCHECK(input->timestamp() != kNoTimestamp()); | 97 DCHECK(input->timestamp() != kNoTimestamp()); |
| 48 DCHECK(input->duration() > base::TimeDelta()); | 98 DCHECK(input->duration() > base::TimeDelta()); |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 62 | 112 |
| 63 if (std::abs(delta.InMilliseconds()) > kMaxTimeDeltaInMilliseconds) { | 113 if (std::abs(delta.InMilliseconds()) > kMaxTimeDeltaInMilliseconds) { |
| 64 DVLOG(1) << "Timestamp delta too large: " << delta.InMicroseconds() << "us"; | 114 DVLOG(1) << "Timestamp delta too large: " << delta.InMicroseconds() << "us"; |
| 65 return false; | 115 return false; |
| 66 } | 116 } |
| 67 | 117 |
| 68 int frames_to_fill = 0; | 118 int frames_to_fill = 0; |
| 69 if (delta != base::TimeDelta()) | 119 if (delta != base::TimeDelta()) |
| 70 frames_to_fill = output_timestamp_helper_.GetFramesToTarget(timestamp); | 120 frames_to_fill = output_timestamp_helper_.GetFramesToTarget(timestamp); |
| 71 | 121 |
| 72 if (frames_to_fill == 0 || std::abs(frames_to_fill) < min_gap_size_) { | 122 if (frames_to_fill == 0 || std::abs(frames_to_fill) < kMinGapSize) { |
| 73 AddOutputBuffer(input); | 123 AddOutputBuffer(input); |
| 74 return true; | 124 return true; |
| 75 } | 125 } |
| 76 | 126 |
| 77 if (frames_to_fill > 0) { | 127 if (frames_to_fill > 0) { |
| 78 DVLOG(1) << "Gap detected @ " << expected_timestamp.InMicroseconds() | 128 DVLOG(1) << "Gap detected @ " << expected_timestamp.InMicroseconds() |
| 79 << " us: " << delta.InMicroseconds() << " us"; | 129 << " us: " << delta.InMicroseconds() << " us"; |
| 80 | 130 |
| 81 // Create a buffer with enough silence samples to fill the gap and | 131 // Create a buffer with enough silence samples to fill the gap and |
| 82 // add it to the output buffer. | 132 // add it to the output buffer. |
| 83 scoped_refptr<AudioBuffer> gap = AudioBuffer::CreateEmptyBuffer( | 133 scoped_refptr<AudioBuffer> gap = AudioBuffer::CreateEmptyBuffer( |
| 84 input->channel_count(), | 134 input->channel_count(), |
| 85 frames_to_fill, | 135 frames_to_fill, |
| 86 expected_timestamp, | 136 expected_timestamp, |
| 87 output_timestamp_helper_.GetFrameDuration(frames_to_fill)); | 137 output_timestamp_helper_.GetFrameDuration(frames_to_fill)); |
| 88 AddOutputBuffer(gap); | 138 AddOutputBuffer(gap); |
| 89 | 139 |
| 90 // Add the input buffer now that the gap has been filled. | 140 // Add the input buffer now that the gap has been filled. |
| 91 AddOutputBuffer(input); | 141 AddOutputBuffer(input); |
| 92 return true; | 142 return true; |
| 93 } | 143 } |
| 94 | 144 |
| 145 // Overlapping buffers marked as splice frames are handled by AudioSplicer, | |
| 146 // but decoder and demuxer quirks may sometimes produce overlapping samples | |
| 147 // which need to be sanitized. | |
| 148 // | |
| 149 // A crossfade can't be done here because only the current buffer is available | |
| 150 // at this point, not previous buffers. | |
| 151 DVLOG(1) << "Overlap detected @ " << expected_timestamp.InMicroseconds() | |
| 152 << " us: " << -delta.InMicroseconds() << " us"; | |
| 153 | |
| 95 int frames_to_skip = -frames_to_fill; | 154 int frames_to_skip = -frames_to_fill; |
| 96 | |
| 97 DVLOG(1) << "Overlap detected @ " << expected_timestamp.InMicroseconds() | |
| 98 << " us: " << -delta.InMicroseconds() << " us"; | |
| 99 | |
| 100 if (input->frame_count() <= frames_to_skip) { | 155 if (input->frame_count() <= frames_to_skip) { |
| 101 DVLOG(1) << "Dropping whole buffer"; | 156 DVLOG(1) << "Dropping whole buffer"; |
| 102 return true; | 157 return true; |
| 103 } | 158 } |
| 104 | 159 |
| 105 // Copy the trailing samples that do not overlap samples already output | 160 // Copy the trailing samples that do not overlap samples already output |
| 106 // into a new buffer. Add this new buffer to the output queue. | 161 // into a new buffer. Add this new buffer to the output queue. |
| 107 // | |
| 108 // TODO(acolwell): Implement a cross-fade here so the transition is less | |
| 109 // jarring. | |
| 110 input->TrimStart(frames_to_skip); | 162 input->TrimStart(frames_to_skip); |
| 111 AddOutputBuffer(input); | 163 AddOutputBuffer(input); |
| 112 return true; | 164 return true; |
| 113 } | 165 } |
| 114 | 166 |
| 115 bool AudioSplicer::HasNextBuffer() const { | 167 bool AudioStreamSanitizer::HasNextBuffer() const { |
| 116 return !output_buffers_.empty(); | 168 return !output_buffers_.empty(); |
| 117 } | 169 } |
| 118 | 170 |
| 119 scoped_refptr<AudioBuffer> AudioSplicer::GetNextBuffer() { | 171 scoped_refptr<AudioBuffer> AudioStreamSanitizer::GetNextBuffer() { |
| 120 scoped_refptr<AudioBuffer> ret = output_buffers_.front(); | 172 scoped_refptr<AudioBuffer> ret = output_buffers_.front(); |
| 121 output_buffers_.pop_front(); | 173 output_buffers_.pop_front(); |
| 122 return ret; | 174 return ret; |
| 123 } | 175 } |
| 124 | 176 |
| 125 void AudioSplicer::AddOutputBuffer(const scoped_refptr<AudioBuffer>& buffer) { | 177 const scoped_refptr<AudioBuffer>& AudioStreamSanitizer::PeekNextBuffer() const { |
| 178 return output_buffers_.front(); | |
| 179 } | |
| 180 | |
| 181 void AudioStreamSanitizer::AddOutputBuffer( | |
| 182 const scoped_refptr<AudioBuffer>& buffer) { | |
| 126 output_timestamp_helper_.AddFrames(buffer->frame_count()); | 183 output_timestamp_helper_.AddFrames(buffer->frame_count()); |
| 127 output_buffers_.push_back(buffer); | 184 output_buffers_.push_back(buffer); |
| 128 } | 185 } |
| 129 | 186 |
| 187 base::TimeDelta AudioStreamSanitizer::GetTimestamp() const { | |
| 188 return output_timestamp_helper_.GetTimestamp(); | |
| 189 } | |
| 190 | |
| 191 base::TimeDelta AudioStreamSanitizer::GetDuration() const { | |
| 192 DCHECK(output_timestamp_helper_.base_timestamp() != kNoTimestamp()); | |
| 193 return output_timestamp_helper_.GetTimestamp() - | |
| 194 output_timestamp_helper_.base_timestamp(); | |
| 195 } | |
| 196 | |
| 197 AudioSplicer::AudioSplicer(int samples_per_second) | |
| 198 : sanitizer_(new AudioStreamSanitizer(samples_per_second)), | |
|
acolwell GONE FROM CHROMIUM
2014/02/18 23:22:59
Are these pointers just so that you can hide the d
DaleCurtis
2014/02/19 03:05:14
Correct. I could move the decl to the header file
| |
| 199 pre_splice_sanitizer_(new AudioStreamSanitizer(samples_per_second)), | |
| 200 post_splice_sanitizer_(new AudioStreamSanitizer(samples_per_second)), | |
| 201 splice_timestamp_(kNoTimestamp()), | |
| 202 crossfade_frame_count_( | |
| 203 (samples_per_second * | |
| 204 static_cast<double>(kCrossfadeDurationInMilliseconds)) / | |
| 205 base::Time::kMillisecondsPerSecond) {} | |
| 206 | |
| 207 AudioSplicer::~AudioSplicer() {} | |
| 208 | |
| 209 void AudioSplicer::Reset() { | |
| 210 sanitizer_->Reset(); | |
| 211 pre_splice_sanitizer_->Reset(); | |
| 212 post_splice_sanitizer_->Reset(); | |
| 213 splice_timestamp_ = kNoTimestamp(); | |
| 214 } | |
| 215 | |
| 216 bool AudioSplicer::AddInput(const scoped_refptr<AudioBuffer>& input) { | |
| 217 // If we're not processing a splice, add the input to the output queue. | |
| 218 if (splice_timestamp_ == kNoTimestamp()) | |
| 219 return sanitizer_->AddInput(input); | |
|
acolwell GONE FROM CHROMIUM
2014/02/18 23:22:59
nit: s/sanitizer_/output_sanitizer_/?
DaleCurtis
2014/02/22 00:59:04
Done.
| |
| 220 | |
| 221 // If we're still receiving buffers before the splice point figure out which | |
| 222 // sanitizer (if any) to put them in. | |
| 223 if (!post_splice_sanitizer_->HasNextBuffer()) { | |
| 224 DCHECK(!input->end_of_stream()); | |
| 225 | |
| 226 // If the provided buffer is entirely before the splice point it can also be | |
| 227 // added to the output queue. | |
| 228 if (input->timestamp() + input->duration() < splice_timestamp_) | |
| 229 return sanitizer_->AddInput(input); | |
| 230 | |
| 231 // If we're processing a splice and the input buffer does not overlap any of | |
| 232 // the existing buffers, append it to the splice queue for processing. | |
| 233 if (input->timestamp() >= pre_splice_sanitizer_->GetTimestamp()) | |
| 234 return pre_splice_sanitizer_->AddInput(input); | |
| 235 | |
| 236 // We've received the first overlapping buffer. | |
| 237 } | |
| 238 | |
| 239 // At this point we have all the fade out preroll buffers from the decoder. | |
| 240 // We now need to wait until we have enough data to perform the crossfade (or | |
| 241 // we receive an end of stream). | |
| 242 if (!post_splice_sanitizer_->AddInput(input)) | |
| 243 return false; | |
| 244 | |
| 245 if (!input->end_of_stream() && | |
| 246 post_splice_sanitizer_->frame_count() < crossfade_frame_count_) { | |
| 247 // TODO(dalecurtis): What if the next buffer we receive is the start of | |
| 248 // another splice frame? See comment in SetSpliceTimestamp below. | |
| 249 return true; | |
| 250 } | |
| 251 | |
| 252 const int frames_to_crossfade = | |
| 253 std::min(crossfade_frame_count_, | |
| 254 static_cast<int>(post_splice_sanitizer_->frame_count())); | |
| 255 const base::TimeDelta splice_end_timestamp = std::min( | |
| 256 post_splice_sanitizer_->GetDuration(), | |
| 257 splice_timestamp_ + | |
| 258 base::TimeDelta::FromMilliseconds(kCrossfadeDurationInMilliseconds)); | |
| 259 | |
| 260 const int channel_count = | |
| 261 pre_splice_sanitizer_->PeekNextBuffer()->channel_count(); | |
| 262 DCHECK_EQ(channel_count, | |
| 263 post_splice_sanitizer_->PeekNextBuffer()->channel_count()); | |
| 264 | |
| 265 // Allocate output buffer for crossfade. | |
| 266 scoped_refptr<AudioBuffer> crossfade_buffer = AudioBuffer::CreateBuffer( | |
| 267 kSampleFormatPlanarF32, channel_count, frames_to_crossfade); | |
| 268 crossfade_buffer->set_timestamp(splice_timestamp_); | |
| 269 crossfade_buffer->set_duration(splice_end_timestamp - splice_timestamp_); | |
| 270 | |
| 271 // AudioBuffer::ReadFrames() only allows output into an AudioBus, so wrap | |
| 272 // our AudioBuffer in one so we can avoid extra data copies. | |
| 273 scoped_ptr<AudioBus> crossfade_bus_wrapper = | |
| 274 AudioBus::CreateWrapper(crossfade_buffer->channel_count()); | |
| 275 for (int ch = 0; ch < crossfade_buffer->channel_count(); ++ch) { | |
| 276 crossfade_bus_wrapper->SetChannelData( | |
| 277 ch, reinterpret_cast<float*>(crossfade_buffer->channel_data()[ch])); | |
| 278 } | |
| 279 | |
| 280 // Transfer out preroll buffers involved in the splice, drop those not. | |
| 281 ExtractCrossfadeFromPreroll(crossfade_bus_wrapper.get()); | |
|
acolwell GONE FROM CHROMIUM
2014/02/18 23:22:59
nit: s/Preroll/PreSplice/ ?. It seems like you are
DaleCurtis
2014/02/19 03:05:14
I'm not partial to any names, I used preroll here
| |
| 282 DCHECK(!pre_splice_sanitizer_->HasNextBuffer()); | |
| 283 | |
| 284 // Insert the crossfade buffer into the output queue now so post splice | |
| 285 // buffers can be added in processing order. We will still modify the buffer | |
| 286 // during the crossfade step. | |
| 287 sanitizer_->AddInput(crossfade_buffer); | |
| 288 | |
| 289 // Since we don't want to care what format the AudioBuffers are in, we need to | |
| 290 // use an intermediary AudioBus to convert the data to float. | |
| 291 scoped_ptr<AudioBus> post_splice_bus = AudioBus::Create( | |
| 292 crossfade_bus_wrapper->channels(), crossfade_bus_wrapper->frames()); | |
| 293 ExtractCrossfadeFromPostroll(post_splice_bus.get()); | |
| 294 | |
| 295 // Crossfade the audio into |crossfade_buffer|. | |
| 296 for (int ch = 0; ch < crossfade_bus_wrapper->channels(); ++ch) { | |
| 297 vector_math::Crossfade(post_splice_bus->channel(ch), | |
| 298 frames_to_crossfade, | |
| 299 crossfade_bus_wrapper->channel(ch)); | |
| 300 } | |
| 301 | |
| 302 // Clear the splice timestamp so new splices can be accepted. | |
| 303 splice_timestamp_ = kNoTimestamp(); | |
| 304 return true; | |
| 305 } | |
| 306 | |
| 307 bool AudioSplicer::HasNextBuffer() const { | |
| 308 return sanitizer_->HasNextBuffer(); | |
| 309 } | |
| 310 | |
| 311 scoped_refptr<AudioBuffer> AudioSplicer::GetNextBuffer() { | |
| 312 return sanitizer_->GetNextBuffer(); | |
| 313 } | |
| 314 | |
| 315 void AudioSplicer::SetSpliceTimestamp(base::TimeDelta splice_timestamp) { | |
| 316 DCHECK(splice_timestamp != kNoTimestamp()); | |
| 317 if (splice_timestamp_ == splice_timestamp) | |
|
acolwell GONE FROM CHROMIUM
2014/02/18 23:22:59
Why are we allowing this?
DaleCurtis
2014/02/19 03:05:14
Essentially to allow callers to not have to worry
| |
| 318 return; | |
| 319 | |
| 320 DCHECK(splice_timestamp_ == kNoTimestamp()); | |
| 321 splice_timestamp_ = splice_timestamp; | |
| 322 pre_splice_sanitizer_->Reset(); | |
| 323 post_splice_sanitizer_->Reset(); | |
|
acolwell GONE FROM CHROMIUM
2014/02/18 23:22:59
nit: I wonder if these should be at the bottom of
DaleCurtis
2014/02/19 03:05:14
I wondered that as well, I think it's fine.
DaleCurtis
2014/02/22 00:59:04
Done.
| |
| 324 | |
| 325 // TODO(dalecurtis): We may need the concept of a future_splice_timestamp_ to | |
| 326 // handle cases where another splice comes in before we've received 5ms of data | |
| 327 // from the last one. | |
| 328 } | |
| 329 | |
| 330 void AudioSplicer::ExtractCrossfadeFromPreroll(AudioBus* output_bus) { | |
| 331 int frames_read = 0; | |
| 332 while (pre_splice_sanitizer_->HasNextBuffer() && | |
| 333 frames_read < output_bus->frames()) { | |
| 334 scoped_refptr<AudioBuffer> preroll = pre_splice_sanitizer_->GetNextBuffer(); | |
| 335 int read_offset = 0; | |
| 336 if (splice_timestamp_ > preroll->timestamp()) { | |
| 337 // This should only happen if the splice point is within the preroll | |
| 338 // buffer somewhere. Early code should have put it in |sanitizer_| | |
| 339 // otherwise. | |
| 340 DCHECK(preroll->timestamp() + preroll->duration() >= splice_timestamp_); | |
| 341 read_offset = | |
| 342 preroll->frame_count() * preroll->duration().InMillisecondsF() / | |
|
acolwell GONE FROM CHROMIUM
2014/02/18 23:22:59
nit: Any reason to not use SecondsF? It's 5 chars
DaleCurtis
2014/02/22 00:59:04
Done.
| |
| 343 (splice_timestamp_ - preroll->timestamp()).InMillisecondsF(); | |
| 344 } | |
| 345 | |
| 346 const int frames_to_read = std::min(preroll->frame_count() - read_offset, | |
| 347 output_bus->frames() - frames_read); | |
| 348 preroll->ReadFrames(frames_to_read, read_offset, frames_read, output_bus); | |
| 349 frames_read += frames_to_read; | |
| 350 | |
| 351 // If only part of the buffer was consumed, trim it appropriately and stick | |
| 352 // it into the output queue. | |
| 353 if (read_offset) { | |
| 354 preroll->TrimEnd(preroll->frame_count() - read_offset); | |
| 355 sanitizer_->AddInput(preroll); | |
| 356 } | |
| 357 } | |
| 358 | |
| 359 // All necessary buffers have been processed, it's safe to reset. | |
| 360 DCHECK_EQ(output_bus->frames(), frames_read); | |
| 361 pre_splice_sanitizer_->Reset(); | |
| 362 } | |
| 363 | |
| 364 void AudioSplicer::ExtractCrossfadeFromPostroll(AudioBus* output_bus) { | |
| 365 int frames_read = 0; | |
| 366 while (post_splice_sanitizer_->HasNextBuffer() && | |
| 367 frames_read < output_bus->frames()) { | |
| 368 scoped_refptr<AudioBuffer> postroll = | |
| 369 post_splice_sanitizer_->GetNextBuffer(); | |
| 370 const int frames_to_read = std::min( | |
| 371 postroll->frame_count(), output_bus->frames() - frames_read); | |
| 372 postroll->ReadFrames(frames_to_read, 0, frames_read, output_bus); | |
| 373 frames_read += frames_to_read; | |
| 374 | |
| 375 // If only part of the buffer was consumed, trim it appropriately and stick | |
| 376 // it into the output queue. | |
| 377 if (frames_to_read < postroll->frame_count()) { | |
| 378 postroll->TrimStart(frames_to_read); | |
| 379 sanitizer_->AddInput(postroll); | |
| 380 } | |
| 381 } | |
| 382 | |
| 383 DCHECK_EQ(output_bus->frames(), frames_read); | |
| 384 | |
| 385 // Transfer all remaining buffers out. | |
| 386 while (post_splice_sanitizer_->HasNextBuffer()) | |
| 387 sanitizer_->AddInput(post_splice_sanitizer_->GetNextBuffer()); | |
| 388 } | |
| 389 | |
| 130 } // namespace media | 390 } // namespace media |
| OLD | NEW |