Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Side by Side Diff: media/base/audio_splicer.cc

Issue 156783003: Enhance AudioSplicer to crossfade marked splice frames. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: More tests. Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 <cmath>
7 #include <cstdlib> 8 #include <cstdlib>
9 #include <deque>
8 10
9 #include "base/logging.h" 11 #include "base/logging.h"
10 #include "media/base/audio_buffer.h" 12 #include "media/base/audio_buffer.h"
13 #include "media/base/audio_bus.h"
11 #include "media/base/audio_decoder_config.h" 14 #include "media/base/audio_decoder_config.h"
12 #include "media/base/audio_timestamp_helper.h" 15 #include "media/base/audio_timestamp_helper.h"
13 #include "media/base/buffers.h" 16 #include "media/base/buffers.h"
17 #include "media/base/vector_math.h"
14 18
15 namespace media { 19 namespace media {
16 20
17 // Largest gap or overlap allowed by this class. Anything 21 // Largest gap or overlap allowed by this class. Anything
18 // larger than this will trigger an error. 22 // larger than this will trigger an error.
19 // This is an arbitrary value, but the initial selection of 50ms 23 // This is an arbitrary value, but the initial selection of 50ms
20 // roughly represents the duration of 2 compressed AAC or MP3 frames. 24 // roughly represents the duration of 2 compressed AAC or MP3 frames.
21 static const int kMaxTimeDeltaInMilliseconds = 50; 25 static const int kMaxTimeDeltaInMilliseconds = 50;
22 26
23 AudioSplicer::AudioSplicer(int samples_per_second) 27 // Minimum gap size needed before the splicer will take action to
28 // fill a gap. This avoids periodically inserting and then dropping samples
29 // when the buffer timestamps are slightly off because of timestamp rounding
30 // in the source content. Unit is frames.
31 static const int kMinGapSize = 2;
32
33 // The number of milliseconds to crossfade before trimming when buffers overlap.
34 static const int kCrossfadeDurationInMilliseconds = 5;
35
36 class AudioStreamSanitizer {
37 public:
38 explicit AudioStreamSanitizer(int samples_per_second);
39 ~AudioStreamSanitizer();
40
41 // Resets the sanitizer state by clearing the output buffers queue,
42 // and resetting the timestamp helper.
43 void Reset();
44
45 // Adds a new buffer full of samples or end of stream buffer to the splicer.
46 // Returns true if the buffer was accepted. False is returned if an error
47 // occurred.
48 bool AddInput(const scoped_refptr<AudioBuffer>& input);
49
50 // Returns true if the sanitizer has a buffer to return.
51 bool HasNextBuffer() const;
52
53 // Removes the next buffer from the output buffer queue and returns it; should
54 // only be called if HasNextBuffer() returns true.
55 scoped_refptr<AudioBuffer> GetNextBuffer();
56
57 // Returns a reference to the next buffer in the output buffer queue; should
58 // only be called if HasNextBuffer() returns true.
59 const scoped_refptr<AudioBuffer>& PeekNextBuffer() const;
60
61 // Get the current timestamp. This value is computed from based on the first
62 // buffer's timestamp and the number of frames that have been added so far.
63 base::TimeDelta GetTimestamp() const;
64
65 // Returns the duration of all buffers added to the output queue thus far.
66 base::TimeDelta GetDuration() const;
67
68 private:
69 void AddOutputBuffer(const scoped_refptr<AudioBuffer>& buffer);
70
71 AudioTimestampHelper output_timestamp_helper_;
72 std::deque<scoped_refptr<AudioBuffer> > 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
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 : samples_per_second_(samples_per_second),
199 max_crossfade_duration_(
200 base::TimeDelta::FromMilliseconds(kCrossfadeDurationInMilliseconds)),
201 splice_timestamp_(kNoTimestamp()),
202 output_sanitizer_(new AudioStreamSanitizer(samples_per_second)),
203 pre_splice_sanitizer_(new AudioStreamSanitizer(samples_per_second)),
204 post_splice_sanitizer_(new AudioStreamSanitizer(samples_per_second)) {}
205
206 AudioSplicer::~AudioSplicer() {}
207
208 void AudioSplicer::Reset() {
209 output_sanitizer_->Reset();
210 pre_splice_sanitizer_->Reset();
211 post_splice_sanitizer_->Reset();
212 splice_timestamp_ = kNoTimestamp();
213 }
214
215 bool AudioSplicer::AddInput(const scoped_refptr<AudioBuffer>& input) {
216 // If we're not processing a splice, add the input to the output queue.
217 if (splice_timestamp_ == kNoTimestamp())
218 return output_sanitizer_->AddInput(input);
219
220 // If we're still receiving buffers before the splice point figure out which
221 // sanitizer (if any) to put them in.
222 if (!post_splice_sanitizer_->HasNextBuffer()) {
223 DCHECK(!input->end_of_stream());
224
225 // If the provided buffer is entirely before the splice point it can also be
226 // added to the output queue.
227 if (input->timestamp() + input->duration() < splice_timestamp_)
228 return output_sanitizer_->AddInput(input);
229
230 // If we're processing a splice and the input buffer does not overlap any of
231 // the existing buffers, append it to the splice queue for processing.
232 if (!pre_splice_sanitizer_->HasNextBuffer() ||
233 input->timestamp() >= pre_splice_sanitizer_->GetTimestamp()) {
234 return pre_splice_sanitizer_->AddInput(input);
235 }
236
237 // We've received the first overlapping buffer.
238 }
239
240 // At this point we have all the fade out preroll buffers from the decoder.
241 // We now need to wait until we have enough data to perform the crossfade (or
242 // we receive an end of stream).
243 if (!post_splice_sanitizer_->AddInput(input))
244 return false;
245
246 if (!input->end_of_stream() &&
247 post_splice_sanitizer_->GetDuration() < max_crossfade_duration_) {
248 return true;
249 }
250
251 // Determine crossfade duration based on available frames in each splicer and
252 // capping to the maximum crossfade duration.
253 const base::TimeDelta crossfade_duration = std::min(
254 max_crossfade_duration_,
255 std::min(pre_splice_sanitizer_->GetTimestamp() - splice_timestamp_,
256 post_splice_sanitizer_->GetDuration()));
257 const int frames_to_crossfade =
258 CalculateFrameCountFromDuration(crossfade_duration);
acolwell GONE FROM CHROMIUM 2014/02/24 21:46:11 I think you use GetFramesToTarget() on the output
DaleCurtis 2014/02/26 02:38:45 Done.
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 // Transfer out preroll buffers involved in the splice, drop those not. Since
266 // we don't want to care what format the AudioBuffers are in, we need to use
267 // an intermediary AudioBus to convert the data to float.
268 scoped_ptr<AudioBus> pre_splice_bus =
269 AudioBus::Create(channel_count, frames_to_crossfade);
270 ExtractCrossfadeFromPreSplice(pre_splice_bus.get());
acolwell GONE FROM CHROMIUM 2014/02/24 21:46:11 nit: Any reason to not have this method just retur
DaleCurtis 2014/02/26 02:38:45 Done.
271 DCHECK(!pre_splice_sanitizer_->HasNextBuffer());
272
273 // Allocate output buffer for crossfade.
274 scoped_refptr<AudioBuffer> crossfade_buffer = AudioBuffer::CreateBuffer(
275 kSampleFormatPlanarF32, channel_count, frames_to_crossfade);
276 crossfade_buffer->set_timestamp(splice_timestamp_);
277 crossfade_buffer->set_duration(crossfade_duration);
278
279 // AudioBuffer::ReadFrames() only allows output into an AudioBus, so wrap
280 // our AudioBuffer in one so we can avoid extra data copies.
281 scoped_ptr<AudioBus> crossfade_bus_wrapper =
282 AudioBus::CreateWrapper(crossfade_buffer->channel_count());
acolwell GONE FROM CHROMIUM 2014/02/24 21:46:11 nit: Lines 282-287 look like they could be moved i
DaleCurtis 2014/02/24 23:04:09 It's only used here, so I don't think it warrants
283 crossfade_bus_wrapper->set_frames(frames_to_crossfade);
284 for (int ch = 0; ch < crossfade_buffer->channel_count(); ++ch) {
285 crossfade_bus_wrapper->SetChannelData(
286 ch, reinterpret_cast<float*>(crossfade_buffer->channel_data()[ch]));
287 }
288
289 // Insert the crossfade buffer into the output queue now so post splice
290 // buffers can be added in processing order. We will still modify the buffer
291 // during the crossfade step.
292 CHECK(output_sanitizer_->AddInput(crossfade_buffer));
acolwell GONE FROM CHROMIUM 2014/02/24 21:46:11 nit: This seems weird to add it to the output befo
DaleCurtis 2014/02/24 23:04:09 Can't since then the buffers added during ExtractC
293
294 ExtractCrossfadeFromPostSplice(crossfade_bus_wrapper.get());
295 DCHECK(!post_splice_sanitizer_->HasNextBuffer());
296
297 // Crossfade the audio into |crossfade_buffer|.
298 for (int ch = 0; ch < crossfade_bus_wrapper->channels(); ++ch) {
299 vector_math::Crossfade(pre_splice_bus->channel(ch),
300 frames_to_crossfade,
301 crossfade_bus_wrapper->channel(ch));
302 }
303
304 // Clear the splice timestamp and sanitizers so new splices can be accepted.
305 splice_timestamp_ = kNoTimestamp();
306 pre_splice_sanitizer_->Reset();
307 post_splice_sanitizer_->Reset();
308 return true;
309 }
310
311 bool AudioSplicer::HasNextBuffer() const {
312 return output_sanitizer_->HasNextBuffer();
313 }
314
315 scoped_refptr<AudioBuffer> AudioSplicer::GetNextBuffer() {
316 return output_sanitizer_->GetNextBuffer();
317 }
318
319 void AudioSplicer::SetSpliceTimestamp(base::TimeDelta splice_timestamp) {
320 DCHECK(splice_timestamp != kNoTimestamp());
321 if (splice_timestamp_ == splice_timestamp)
322 return;
323
324 // TODO(dalecurtis): We may need the concept of a future_splice_timestamp_ to
325 // handle cases where another splice comes in before we've received 5ms of
326 // data from the last one. Leave this as a CHECK for now to figure out if
327 // this case is possible.
328 CHECK(splice_timestamp_ == kNoTimestamp());
329 splice_timestamp_ = splice_timestamp;
330 }
331
332 void AudioSplicer::ExtractCrossfadeFromPreSplice(AudioBus* output_bus) {
333 int frames_read = 0;
334 while (pre_splice_sanitizer_->HasNextBuffer() &&
335 frames_read < output_bus->frames()) {
336 scoped_refptr<AudioBuffer> preroll = pre_splice_sanitizer_->GetNextBuffer();
337
338 int read_offset = 0;
339 if (splice_timestamp_ > preroll->timestamp()) {
340 // This should only happen if the splice point is within the preroll
acolwell GONE FROM CHROMIUM 2014/02/24 21:46:11 nit: s/preroll/|preroll|/
DaleCurtis 2014/02/26 02:38:45 Removed.
341 // buffer somewhere. Early code should have put it in |sanitizer_|
acolwell GONE FROM CHROMIUM 2014/02/24 21:46:11 nit: s/sanitizer_/output_sanitizer_/?
DaleCurtis 2014/02/26 02:38:45 Removed.
342 // otherwise.
343 DCHECK(preroll->timestamp() + preroll->duration() >= splice_timestamp_);
acolwell GONE FROM CHROMIUM 2014/02/24 21:46:11 I wonder if we should create an end_timestamp() to
DaleCurtis 2014/02/24 23:04:09 Only two occurrences in this code. Seems like som
344 read_offset =
345 ceil(preroll->frame_count() *
acolwell GONE FROM CHROMIUM 2014/02/24 21:46:11 I think we should use GetFramesToTarget() on outpu
DaleCurtis 2014/02/26 02:38:45 Done.
346 (splice_timestamp_ - preroll->timestamp()).InSecondsF() /
347 preroll->duration().InSecondsF());
348 }
349
350 const int frames_to_read = std::min(preroll->frame_count() - read_offset,
351 output_bus->frames() - frames_read);
352 preroll->ReadFrames(frames_to_read, read_offset, frames_read, output_bus);
353 frames_read += frames_to_read;
354
355 // If only part of the buffer was consumed, trim it appropriately and stick
356 // it into the output queue.
357 if (read_offset) {
358 preroll->TrimEnd(preroll->frame_count() - read_offset);
359 CHECK(output_sanitizer_->AddInput(preroll));
360 }
361 }
362
363 // All necessary buffers have been processed, it's safe to reset.
364 DCHECK_EQ(output_bus->frames(), frames_read);
365 pre_splice_sanitizer_->Reset();
366 }
367
368 void AudioSplicer::ExtractCrossfadeFromPostSplice(AudioBus* output_bus) {
369 int frames_read = 0;
370 while (post_splice_sanitizer_->HasNextBuffer() &&
371 frames_read < output_bus->frames()) {
372 scoped_refptr<AudioBuffer> postroll =
373 post_splice_sanitizer_->GetNextBuffer();
374 const int frames_to_read =
375 std::min(postroll->frame_count(), output_bus->frames() - frames_read);
376 postroll->ReadFrames(frames_to_read, 0, frames_read, output_bus);
377 frames_read += frames_to_read;
378
379 // If only part of the buffer was consumed, trim it appropriately and stick
380 // it into the output queue.
381 if (frames_to_read < postroll->frame_count()) {
382 postroll->TrimStart(frames_to_read);
383 CHECK(output_sanitizer_->AddInput(postroll));
384 }
385 }
386
387 DCHECK_EQ(output_bus->frames(), frames_read);
388
389 // Transfer all remaining buffers out.
390 while (post_splice_sanitizer_->HasNextBuffer())
391 CHECK(output_sanitizer_->AddInput(post_splice_sanitizer_->GetNextBuffer()));
392 }
393
394 int AudioSplicer::CalculateFrameCountFromDuration(base::TimeDelta duration) {
395 return ceil(samples_per_second_ * duration.InMicroseconds() /
396 base::Time::kMicrosecondsPerSecond);
397 }
398
130 } // namespace media 399 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698