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
3 // found in the LICENSE file.
4
5 #include "media/filters/audio_renderer_algorithm.h"
6
7 #include <algorithm>
8 #include <cmath>
9
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "cc/base/math_util.h"
13 #include "media/base/audio_bus.h"
14 #include "media/base/audio_timestamp_helper.h"
15 #include "media/base/limits.h"
16 #include "media/filters/wsola_internals.h"
17
18 namespace media {
19
20
21 // Waveform Similarity Overlap-and-add (WSOLA).
22 //
23 // One WSOLA iteration
24 //
25 // 1) Extract |target_block_| as input frames at indices
26 // [|target_block_index_|, |target_block_index_| + |ola_window_size_|).
27 // Note that |target_block_| is the "natural" continuation of the output.
28 //
29 // 2) Extract |search_block_| as input frames at indices
30 // [|search_block_index_|,
31 // |search_block_index_| + |num_candidate_blocks_| + |ola_window_size_|).
32 //
33 // 3) Find a block within the |search_block_| that is most similar
34 // to |target_block_|. Let |optimal_index| be the index of such block and
35 // write it to |optimal_block_|.
36 //
37 // 4) Update:
38 // |optimal_block_| = |transition_window_| * |target_block_| +
39 // (1 - |transition_window_|) * |optimal_block_|.
40 //
41 // 5) Overlap-and-add |optimal_block_| to the |wsola_output_|.
42 //
43 // 6) Update:
44 // |target_block_| = |optimal_index| + |ola_window_size_| / 2.
45 // |output_index_| = |output_index_| + |ola_window_size_| / 2,
46 // |search_block_center_offset_| = |output_index_| * |playback_rate|, and
47 // |search_block_index_| = |search_block_center_offset_| -
48 // |search_block_center_offset_|.
49
50 // Overlap-and-add window size in milliseconds.
51 constexpr base::TimeDelta kOlaWindowSize =
52 base::TimeDelta::FromMilliseconds(20);
53
54 // Size of search interval in milliseconds. The search interval is
55 // [-delta delta] around |output_index_| * |playback_rate|. So the search
56 // interval is 2 * delta.
57 constexpr base::TimeDelta kWsolaSearchInterval =
58 base::TimeDelta::FromMilliseconds(30);
59
60 // The maximum size for the |audio_buffer_|. Arbitrarily determined.
61 constexpr base::TimeDelta kMaxCapacity = base::TimeDelta::FromSeconds(3);
62
63 // The minimum size for the |audio_buffer_|. Arbitrarily determined.
64 constexpr base::TimeDelta kStartingCapacity =
65 base::TimeDelta::FromMilliseconds(200);
66
67 // The minimum size for the |audio_buffer_| for encrypted streams.
68 // Set this to be larger than |kStartingCapacity| because the performance of
69 // encrypted playback is always worse than clear playback, due to decryption and
70 // potentially IPC overhead. For the context, see https://crbug.com/403462,
71 // https://crbug.com/718161 and https://crbug.com/879970.
72 constexpr base::TimeDelta kStartingCapacityForEncrypted =
73 base::TimeDelta::FromMilliseconds(500);
74
AudioRendererAlgorithm(MediaLog * media_log)75 AudioRendererAlgorithm::AudioRendererAlgorithm(MediaLog* media_log)
76 : AudioRendererAlgorithm(
77 media_log,
78 {kMaxCapacity, kStartingCapacity, kStartingCapacityForEncrypted}) {}
79
AudioRendererAlgorithm(MediaLog * media_log,AudioRendererAlgorithmParameters params)80 AudioRendererAlgorithm::AudioRendererAlgorithm(
81 MediaLog* media_log,
82 AudioRendererAlgorithmParameters params)
83 : media_log_(media_log),
84 audio_renderer_algorithm_params_(std::move(params)),
85 channels_(0),
86 samples_per_second_(0),
87 is_bitstream_format_(false),
88 capacity_(0),
89 output_time_(0.0),
90 search_block_center_offset_(0),
91 search_block_index_(0),
92 num_candidate_blocks_(0),
93 target_block_index_(0),
94 ola_window_size_(0),
95 ola_hop_size_(0),
96 num_complete_frames_(0),
97 initial_capacity_(0),
98 max_capacity_(0) {}
99
100 AudioRendererAlgorithm::~AudioRendererAlgorithm() = default;
101
Initialize(const AudioParameters & params,bool is_encrypted)102 void AudioRendererAlgorithm::Initialize(const AudioParameters& params,
103 bool is_encrypted) {
104 CHECK(params.IsValid());
105
106 channels_ = params.channels();
107 samples_per_second_ = params.sample_rate();
108 is_bitstream_format_ = params.IsBitstreamFormat();
109 min_playback_threshold_ = params.frames_per_buffer() * 2;
110 initial_capacity_ = capacity_ = playback_threshold_ = std::max(
111 min_playback_threshold_,
112 AudioTimestampHelper::TimeToFrames(
113 is_encrypted
114 ? audio_renderer_algorithm_params_.starting_capacity_for_encrypted
115 : audio_renderer_algorithm_params_.starting_capacity,
116 samples_per_second_));
117 max_capacity_ = std::max(
118 initial_capacity_,
119 AudioTimestampHelper::TimeToFrames(
120 audio_renderer_algorithm_params_.max_capacity, samples_per_second_));
121 num_candidate_blocks_ = AudioTimestampHelper::TimeToFrames(
122 kWsolaSearchInterval, samples_per_second_);
123 ola_window_size_ =
124 AudioTimestampHelper::TimeToFrames(kOlaWindowSize, samples_per_second_);
125
126 // Make sure window size in an even number.
127 ola_window_size_ += ola_window_size_ & 1;
128 ola_hop_size_ = ola_window_size_ / 2;
129
130 // |num_candidate_blocks_| / 2 is the offset of the center of the search
131 // block to the center of the first (left most) candidate block. The offset
132 // of the center of a candidate block to its left most point is
133 // |ola_window_size_| / 2 - 1. Note that |ola_window_size_| is even and in
134 // our convention the center belongs to the left half, so we need to subtract
135 // one frame to get the correct offset.
136 //
137 // Search Block
138 // <------------------------------------------->
139 //
140 // |ola_window_size_| / 2 - 1
141 // <----
142 //
143 // |num_candidate_blocks_| / 2
144 // <----------------
145 // center
146 // X----X----------------X---------------X-----X
147 // <----------> <---------->
148 // Candidate ... Candidate
149 // 1, ... |num_candidate_blocks_|
150 search_block_center_offset_ =
151 num_candidate_blocks_ / 2 + (ola_window_size_ / 2 - 1);
152
153 // If no mask is provided, assume all channels are valid.
154 if (channel_mask_.empty())
155 SetChannelMask(std::vector<bool>(channels_, true));
156 }
157
SetChannelMask(std::vector<bool> channel_mask)158 void AudioRendererAlgorithm::SetChannelMask(std::vector<bool> channel_mask) {
159 DCHECK_EQ(channel_mask.size(), static_cast<size_t>(channels_));
160 channel_mask_ = std::move(channel_mask);
161 if (ola_window_)
162 CreateSearchWrappers();
163 }
164
OnResamplerRead(int frame_delay,AudioBus * audio_bus)165 void AudioRendererAlgorithm::OnResamplerRead(int frame_delay,
166 AudioBus* audio_bus) {
167 const int requested_frames = audio_bus->frames();
168 int read_frames = audio_buffer_.ReadFrames(requested_frames, 0, audio_bus);
169
170 if (read_frames < requested_frames) {
171 // We should only be filling up |resampler_| with silence if we are playing
172 // out all remaining frames.
173 DCHECK(reached_end_of_stream_);
174 audio_bus->ZeroFramesPartial(read_frames, requested_frames - read_frames);
175 }
176 }
177
MarkEndOfStream()178 void AudioRendererAlgorithm::MarkEndOfStream() {
179 reached_end_of_stream_ = true;
180 }
181
ResampleAndFill(AudioBus * dest,int dest_offset,int requested_frames,double playback_rate)182 int AudioRendererAlgorithm::ResampleAndFill(AudioBus* dest,
183 int dest_offset,
184 int requested_frames,
185 double playback_rate) {
186 if (!resampler_) {
187 resampler_ = std::make_unique<MultiChannelResampler>(
188 channels_, playback_rate, SincResampler::kDefaultRequestSize,
189 base::BindRepeating(&AudioRendererAlgorithm::OnResamplerRead,
190 base::Unretained(this)));
191 }
192
193 // |resampler_| can request more than |requested_frames|, due to the
194 // requests size not being aligned. To prevent having to fill it with silence,
195 // we find the max number of reads it could request, and make sure we have
196 // enough data to satisfy all of those reads.
197 if (!reached_end_of_stream_ &&
198 audio_buffer_.frames() <
199 resampler_->GetMaxInputFramesRequested(requested_frames)) {
200 // Exit early, forgoing at most a total of |audio_buffer_.frames()| +
201 // |resampler_->BufferedFrames()|.
202 // If we have reached the end of stream, |resampler_| will output silence
203 // after running out of frames, which is ok.
204 return 0;
205 }
206 resampler_->SetRatio(playback_rate);
207
208 // Directly use |dest| for the most common case of having 0 offset.
209 if (!dest_offset) {
210 resampler_->Resample(requested_frames, dest);
211 return requested_frames;
212 }
213
214 // This is only really used once, at the beginning of a stream, which means
215 // we can use a temporary variable, rather than saving it as a member.
216 // NOTE: We don't wrap |dest|'s channel data in an AudioBus wrapper, because
217 // |dest_offset| isn't aligned always with AudioBus::kChannelAlignment.
218 std::unique_ptr<AudioBus> resampler_output =
219 AudioBus::Create(channels_, requested_frames);
220
221 resampler_->Resample(requested_frames, resampler_output.get());
222 resampler_output->CopyPartialFramesTo(0, requested_frames, dest_offset, dest);
223
224 return requested_frames;
225 }
226
FillBuffer(AudioBus * dest,int dest_offset,int requested_frames,double playback_rate)227 int AudioRendererAlgorithm::FillBuffer(AudioBus* dest,
228 int dest_offset,
229 int requested_frames,
230 double playback_rate) {
231 if (playback_rate == 0)
232 return 0;
233
234 DCHECK_GT(playback_rate, 0);
235 DCHECK_EQ(channels_, dest->channels());
236
237 // In case of compressed bitstream formats, no post processing is allowed.
238 if (is_bitstream_format_)
239 return audio_buffer_.ReadFrames(requested_frames, dest_offset, dest);
240
241 int slower_step = ceil(ola_window_size_ * playback_rate);
242 int faster_step = ceil(ola_window_size_ / playback_rate);
243
244 // Optimize the most common |playback_rate| ~= 1 case to use a single copy
245 // instead of copying frame by frame.
246 if (ola_window_size_ <= faster_step && slower_step >= ola_window_size_) {
247 const int frames_to_copy =
248 std::min(audio_buffer_.frames(), requested_frames);
249 const int frames_read =
250 audio_buffer_.ReadFrames(frames_to_copy, dest_offset, dest);
251 DCHECK_EQ(frames_read, frames_to_copy);
252 return frames_read;
253 }
254
255 // WSOLA at playback rates that are close to 1.0 produces noticeable
256 // warbling and stuttering. We prefer resampling the audio at these speeds.
257 // This does results in a noticeable pitch shift.
258 // NOTE: The cutoff values are arbitrary, and picked based off of a tradeoff
259 // between "resample pitch shift" vs "WSOLA distortions".
260 if (kLowerResampleThreshold <= playback_rate &&
261 playback_rate <= kUpperResampleThreshold) {
262 return ResampleAndFill(dest, dest_offset, requested_frames, playback_rate);
263 }
264
265 // Destroy the resampler if it was used before, but it's no longer needed
266 // (e.g. before playback rate has changed). This ensures that we don't try to
267 // play later any samples still buffered in the resampler.
268 if (resampler_)
269 resampler_.reset();
270
271 // Allocate structures on first non-1.0 playback rate; these can eat a fair
272 // chunk of memory. ~56kB for stereo 48kHz, up to ~765kB for 7.1 192kHz.
273 if (!ola_window_) {
274 ola_window_.reset(new float[ola_window_size_]);
275 internal::GetSymmetricHanningWindow(ola_window_size_, ola_window_.get());
276
277 transition_window_.reset(new float[ola_window_size_ * 2]);
278 internal::GetSymmetricHanningWindow(2 * ola_window_size_,
279 transition_window_.get());
280
281 // Initialize for overlap-and-add of the first block.
282 wsola_output_ =
283 AudioBus::Create(channels_, ola_window_size_ + ola_hop_size_);
284 wsola_output_->Zero();
285
286 // Auxiliary containers.
287 optimal_block_ = AudioBus::Create(channels_, ola_window_size_);
288 search_block_ = AudioBus::Create(
289 channels_, num_candidate_blocks_ + (ola_window_size_ - 1));
290 target_block_ = AudioBus::Create(channels_, ola_window_size_);
291
292 // Create potentially smaller wrappers for playback rate adaptation.
293 CreateSearchWrappers();
294 }
295
296 // Silent audio can contain non-zero samples small enough to result in
297 // subnormals internalls. Disabling subnormals can be significantly faster in
298 // these cases.
299 cc::ScopedSubnormalFloatDisabler disable_subnormals;
300
301 int rendered_frames = 0;
302 do {
303 rendered_frames +=
304 WriteCompletedFramesTo(requested_frames - rendered_frames,
305 dest_offset + rendered_frames, dest);
306 } while (rendered_frames < requested_frames &&
307 RunOneWsolaIteration(playback_rate));
308 return rendered_frames;
309 }
310
FlushBuffers()311 void AudioRendererAlgorithm::FlushBuffers() {
312 // Clear the queue of decoded packets (releasing the buffers).
313 audio_buffer_.Clear();
314 output_time_ = 0.0;
315 search_block_index_ = 0;
316 target_block_index_ = 0;
317 if (wsola_output_)
318 wsola_output_->Zero();
319 num_complete_frames_ = 0;
320
321 resampler_.reset();
322 reached_end_of_stream_ = false;
323
324 // Reset |capacity_| and |playback_threshold_| so growth triggered by
325 // underflows doesn't penalize seek time. When |latency_hint_| is set we don't
326 // increase the queue for underflow, so avoid resetting it on flush.
327 if (!latency_hint_) {
328 capacity_ = playback_threshold_ = initial_capacity_;
329 }
330 }
331
EnqueueBuffer(scoped_refptr<AudioBuffer> buffer_in)332 void AudioRendererAlgorithm::EnqueueBuffer(
333 scoped_refptr<AudioBuffer> buffer_in) {
334 DCHECK(!buffer_in->end_of_stream());
335 audio_buffer_.Append(std::move(buffer_in));
336 }
337
SetLatencyHint(base::Optional<base::TimeDelta> latency_hint)338 void AudioRendererAlgorithm::SetLatencyHint(
339 base::Optional<base::TimeDelta> latency_hint) {
340 DCHECK_GE(playback_threshold_, min_playback_threshold_);
341 DCHECK_LE(playback_threshold_, capacity_);
342 DCHECK_LE(capacity_, max_capacity_);
343
344 latency_hint_ = latency_hint;
345
346 if (!latency_hint) {
347 // Restore default values.
348 playback_threshold_ = capacity_ = initial_capacity_;
349
350 MEDIA_LOG(DEBUG, media_log_)
351 << "Audio latency hint cleared. Default buffer size ("
352 << AudioTimestampHelper::FramesToTime(playback_threshold_,
353 samples_per_second_)
354 << ") restored";
355 return;
356 }
357
358 int latency_hint_frames =
359 AudioTimestampHelper::TimeToFrames(*latency_hint_, samples_per_second_);
360
361 // Set |plabyack_threshold_| using hint, clamped between
362 // [min_playback_threshold_, max_capacity_].
363 std::string clamp_string;
364 if (latency_hint_frames > max_capacity_) {
365 playback_threshold_ = max_capacity_;
366 clamp_string = " (clamped to max)";
367 } else if (latency_hint_frames < min_playback_threshold_) {
368 playback_threshold_ = min_playback_threshold_;
369 clamp_string = " (clamped to min)";
370 } else {
371 playback_threshold_ = latency_hint_frames;
372 }
373
374 // Use |initial_capacity_| if possible. Increase if needed.
375 capacity_ = std::max(playback_threshold_, initial_capacity_);
376
377 MEDIA_LOG(DEBUG, media_log_)
378 << "Audio latency hint set:" << *latency_hint << ". "
379 << "Effective buffering latency:"
380 << AudioTimestampHelper::FramesToTime(playback_threshold_,
381 samples_per_second_)
382 << clamp_string;
383
384 DCHECK_GE(playback_threshold_, min_playback_threshold_);
385 DCHECK_LE(playback_threshold_, capacity_);
386 DCHECK_LE(capacity_, max_capacity_);
387 }
388
IsQueueAdequateForPlayback()389 bool AudioRendererAlgorithm::IsQueueAdequateForPlayback() {
390 return audio_buffer_.frames() >= playback_threshold_;
391 }
392
IsQueueFull()393 bool AudioRendererAlgorithm::IsQueueFull() {
394 return audio_buffer_.frames() >= capacity_;
395 }
396
IncreasePlaybackThreshold()397 void AudioRendererAlgorithm::IncreasePlaybackThreshold() {
398 DCHECK(!latency_hint_) << "Don't override the user specified latency";
399 DCHECK_EQ(playback_threshold_, capacity_);
400 DCHECK_LE(capacity_, max_capacity_);
401
402 playback_threshold_ = capacity_ = std::min(2 * capacity_, max_capacity_);
403 }
404
GetMemoryUsage() const405 int64_t AudioRendererAlgorithm::GetMemoryUsage() const {
406 return BufferedFrames() * channels_ * sizeof(float);
407 }
408
BufferedFrames() const409 int AudioRendererAlgorithm::BufferedFrames() const {
410 return audio_buffer_.frames() +
411 (resampler_ ? static_cast<int>(resampler_->BufferedFrames()) : 0);
412 }
413
CanPerformWsola() const414 bool AudioRendererAlgorithm::CanPerformWsola() const {
415 const int search_block_size = num_candidate_blocks_ + (ola_window_size_ - 1);
416 const int frames = audio_buffer_.frames();
417 return target_block_index_ + ola_window_size_ <= frames &&
418 search_block_index_ + search_block_size <= frames;
419 }
420
RunOneWsolaIteration(double playback_rate)421 bool AudioRendererAlgorithm::RunOneWsolaIteration(double playback_rate) {
422 if (!CanPerformWsola())
423 return false;
424
425 GetOptimalBlock();
426
427 // Overlap-and-add.
428 for (int k = 0; k < channels_; ++k) {
429 if (!channel_mask_[k])
430 continue;
431
432 const float* const ch_opt_frame = optimal_block_->channel(k);
433 float* ch_output = wsola_output_->channel(k) + num_complete_frames_;
434 for (int n = 0; n < ola_hop_size_; ++n) {
435 ch_output[n] = ch_output[n] * ola_window_[ola_hop_size_ + n] +
436 ch_opt_frame[n] * ola_window_[n];
437 }
438
439 // Copy the second half to the output.
440 memcpy(&ch_output[ola_hop_size_], &ch_opt_frame[ola_hop_size_],
441 sizeof(*ch_opt_frame) * ola_hop_size_);
442 }
443
444 num_complete_frames_ += ola_hop_size_;
445 UpdateOutputTime(playback_rate, ola_hop_size_);
446 RemoveOldInputFrames(playback_rate);
447 return true;
448 }
449
UpdateOutputTime(double playback_rate,double time_change)450 void AudioRendererAlgorithm::UpdateOutputTime(double playback_rate,
451 double time_change) {
452 output_time_ += time_change;
453 // Center of the search region, in frames.
454 const int search_block_center_index = static_cast<int>(
455 output_time_ * playback_rate + 0.5);
456 search_block_index_ = search_block_center_index - search_block_center_offset_;
457 }
458
RemoveOldInputFrames(double playback_rate)459 void AudioRendererAlgorithm::RemoveOldInputFrames(double playback_rate) {
460 const int earliest_used_index = std::min(target_block_index_,
461 search_block_index_);
462 if (earliest_used_index <= 0)
463 return; // Nothing to remove.
464
465 // Remove frames from input and adjust indices accordingly.
466 audio_buffer_.SeekFrames(earliest_used_index);
467 target_block_index_ -= earliest_used_index;
468
469 // Adjust output index.
470 double output_time_change = static_cast<double>(earliest_used_index) /
471 playback_rate;
472 CHECK_GE(output_time_, output_time_change);
473 UpdateOutputTime(playback_rate, -output_time_change);
474 }
475
WriteCompletedFramesTo(int requested_frames,int dest_offset,AudioBus * dest)476 int AudioRendererAlgorithm::WriteCompletedFramesTo(
477 int requested_frames, int dest_offset, AudioBus* dest) {
478 int rendered_frames = std::min(num_complete_frames_, requested_frames);
479
480 if (rendered_frames == 0)
481 return 0; // There is nothing to read from |wsola_output_|, return.
482
483 wsola_output_->CopyPartialFramesTo(0, rendered_frames, dest_offset, dest);
484
485 // Remove the frames which are read.
486 int frames_to_move = wsola_output_->frames() - rendered_frames;
487 for (int k = 0; k < channels_; ++k) {
488 if (!channel_mask_[k])
489 continue;
490 float* ch = wsola_output_->channel(k);
491 memmove(ch, &ch[rendered_frames], sizeof(*ch) * frames_to_move);
492 }
493 num_complete_frames_ -= rendered_frames;
494 return rendered_frames;
495 }
496
TargetIsWithinSearchRegion() const497 bool AudioRendererAlgorithm::TargetIsWithinSearchRegion() const {
498 const int search_block_size = num_candidate_blocks_ + (ola_window_size_ - 1);
499
500 return target_block_index_ >= search_block_index_ &&
501 target_block_index_ + ola_window_size_ <=
502 search_block_index_ + search_block_size;
503 }
504
GetOptimalBlock()505 void AudioRendererAlgorithm::GetOptimalBlock() {
506 int optimal_index = 0;
507
508 // An interval around last optimal block which is excluded from the search.
509 // This is to reduce the buzzy sound. The number 160 is rather arbitrary and
510 // derived heuristically.
511 const int kExcludeIntervalLengthFrames = 160;
512 if (TargetIsWithinSearchRegion()) {
513 optimal_index = target_block_index_;
514 PeekAudioWithZeroPrepend(optimal_index, optimal_block_.get());
515 } else {
516 PeekAudioWithZeroPrepend(target_block_index_, target_block_.get());
517 PeekAudioWithZeroPrepend(search_block_index_, search_block_.get());
518 int last_optimal =
519 target_block_index_ - ola_hop_size_ - search_block_index_;
520 internal::Interval exclude_interval =
521 std::make_pair(last_optimal - kExcludeIntervalLengthFrames / 2,
522 last_optimal + kExcludeIntervalLengthFrames / 2);
523
524 // |optimal_index| is in frames and it is relative to the beginning of the
525 // |search_block_|.
526 optimal_index =
527 internal::OptimalIndex(search_block_wrapper_.get(),
528 target_block_wrapper_.get(), exclude_interval);
529
530 // Translate |index| w.r.t. the beginning of |audio_buffer_| and extract the
531 // optimal block.
532 optimal_index += search_block_index_;
533 PeekAudioWithZeroPrepend(optimal_index, optimal_block_.get());
534
535 // Make a transition from target block to the optimal block if different.
536 // Target block has the best continuation to the current output.
537 // Optimal block is the most similar block to the target, however, it might
538 // introduce some discontinuity when over-lap-added. Therefore, we combine
539 // them for a smoother transition. The length of transition window is twice
540 // as that of the optimal-block which makes it like a weighting function
541 // where target-block has higher weight close to zero (weight of 1 at index
542 // 0) and lower weight close the end.
543 for (int k = 0; k < channels_; ++k) {
544 if (!channel_mask_[k])
545 continue;
546 float* ch_opt = optimal_block_->channel(k);
547 const float* const ch_target = target_block_->channel(k);
548 for (int n = 0; n < ola_window_size_; ++n) {
549 ch_opt[n] = ch_opt[n] * transition_window_[n] +
550 ch_target[n] * transition_window_[ola_window_size_ + n];
551 }
552 }
553 }
554
555 // Next target is one hop ahead of the current optimal.
556 target_block_index_ = optimal_index + ola_hop_size_;
557 }
558
PeekAudioWithZeroPrepend(int read_offset_frames,AudioBus * dest)559 void AudioRendererAlgorithm::PeekAudioWithZeroPrepend(
560 int read_offset_frames, AudioBus* dest) {
561 CHECK_LE(read_offset_frames + dest->frames(), audio_buffer_.frames());
562
563 int write_offset = 0;
564 int num_frames_to_read = dest->frames();
565 if (read_offset_frames < 0) {
566 int num_zero_frames_appended = std::min(-read_offset_frames,
567 num_frames_to_read);
568 read_offset_frames = 0;
569 num_frames_to_read -= num_zero_frames_appended;
570 write_offset = num_zero_frames_appended;
571 dest->ZeroFrames(num_zero_frames_appended);
572 }
573 audio_buffer_.PeekFrames(num_frames_to_read, read_offset_frames,
574 write_offset, dest);
575 }
576
CreateSearchWrappers()577 void AudioRendererAlgorithm::CreateSearchWrappers() {
578 // WSOLA is quite expensive to run, so if a channel mask exists, use it to
579 // reduce the size of our search space.
580 std::vector<float*> active_target_channels;
581 std::vector<float*> active_search_channels;
582 for (int ch = 0; ch < channels_; ++ch) {
583 if (channel_mask_[ch]) {
584 active_target_channels.push_back(target_block_->channel(ch));
585 active_search_channels.push_back(search_block_->channel(ch));
586 }
587 }
588
589 target_block_wrapper_ =
590 AudioBus::WrapVector(target_block_->frames(), active_target_channels);
591 search_block_wrapper_ =
592 AudioBus::WrapVector(search_block_->frames(), active_search_channels);
593 }
594
595 } // namespace media
596