Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 // Input buffer layout, dividing the total buffer into regions (r0 - r5): | |
| 6 // | |
| 7 // |----------------|-----------------------------------------|----------------| | |
| 8 // | |
| 9 // kBlockSize + kKernelSize / 2 | |
| 10 // <---------------------------------------------------------> | |
| 11 // r0 | |
| 12 // | |
| 13 // kKernelSize / 2 kKernelSize / 2 kKernelSize / 2 kKernelSize / 2 | |
| 14 // <---------------> <---------------> <---------------> <---------------> | |
| 15 // r1 r2 r3 r4 | |
| 16 // | |
| 17 // kBlockSize | |
| 18 // <---------------------------------------> | |
| 19 // r5 | |
| 20 // | |
| 21 // The algorithm: | |
| 22 // | |
| 23 // 1) Consume input frames into r0 (r1 is zero-initialized). | |
| 24 // 2) Position kernel centered at start of r0 (r2) and generate output frames | |
| 25 // until kernel is centered at start of r4 or we've finished generating all | |
| 26 // the output frames. | |
| 27 // 3) Copy r3 to r1 and r4 to r2. | |
| 28 // 4) Consume input frames into r5 (zero-pad if we run out of input). | |
| 29 // 5) Goto (2) until all of input is consumed. | |
| 30 // | |
| 31 // Note: we're glossing over how the sub-sample handling works with | |
| 32 // |virtual_source_idx_|, etc. | |
|
Chris Rogers
2012/07/09 21:51:42
I'd add an extra blank line here
DaleCurtis
2012/07/10 01:00:25
Done.
| |
| 33 // MSVC++ requires this to be set before any other includes to get M_PI. | |
| 34 #define _USE_MATH_DEFINES | |
| 35 | |
| 36 #include "media/base/sinc_resampler.h" | |
| 37 | |
| 38 #include <cmath> | |
| 39 | |
| 40 #include "base/logging.h" | |
| 41 | |
| 42 namespace media { | |
| 43 | |
| 44 COMPILE_ASSERT(SincResampler::kKernelSize % 2 == 0, kKernelSize_must_be_even); | |
| 45 COMPILE_ASSERT(SincResampler::kBlockSize > SincResampler::kKernelSize, | |
| 46 kBlockSize_must_be_gt_kKernelSize); | |
| 47 | |
| 48 SincResampler::SincResampler(const ReadCB& read_cb, double io_sample_rate_ratio) | |
| 49 : io_sample_rate_ratio_(io_sample_rate_ratio), | |
| 50 virtual_source_idx_(0), | |
| 51 buffer_primed_(false), | |
| 52 read_cb_(read_cb), | |
| 53 // TODO(dalecurtis): When we switch to AVX/SSE optimization, we'll need to | |
| 54 // allocate with 32-byte alignment and ensure they're sized % 32 bytes. | |
| 55 kernel_storage_(new float[kKernelStorageSize]), | |
| 56 input_buffer_(new float[kBufferSize]) { | |
| 57 memset(kernel_storage_.get(), 0, | |
| 58 sizeof(*kernel_storage_.get()) * kKernelStorageSize); | |
| 59 memset(input_buffer_.get(), 0, sizeof(*input_buffer_.get()) * kBufferSize); | |
| 60 | |
| 61 InitializeKernel(); | |
| 62 } | |
| 63 | |
| 64 SincResampler::~SincResampler() {} | |
| 65 | |
| 66 void SincResampler::InitializeKernel() { | |
|
Ami GONE FROM CHROMIUM
2012/07/03 20:54:42
Any reason this needs to be a function separate fr
DaleCurtis
2012/07/10 01:00:25
Just clarity. It's a bit large to stuff into the
| |
| 67 // Blackman window parameters. | |
| 68 double alpha = 0.16; | |
| 69 double a0 = 0.5 * (1.0 - alpha); | |
| 70 double a1 = 0.5; | |
| 71 double a2 = 0.5 * alpha; | |
| 72 | |
| 73 // |sinc_scale_factor| is basically the normalized cutoff frequency of the | |
| 74 // low-pass filter. | |
| 75 double sinc_scale_factor = | |
| 76 io_sample_rate_ratio_ > 1.0 ? 1.0 / io_sample_rate_ratio_ : 1.0; | |
| 77 | |
| 78 // The sinc function is an idealized brick-wall filter, but since we're | |
| 79 // windowing it the transition from pass to stop does not happen right away. | |
| 80 // So we should adjust the lowpass filter cutoff slightly downward to avoid | |
| 81 // some aliasing at the very high-end. | |
| 82 // TODO(crogers): this value is empirical and to be more exact should vary | |
| 83 // depending on kKernelSize. | |
| 84 sinc_scale_factor *= 0.9; | |
| 85 | |
| 86 // Generates a set of windowed sinc() kernels. | |
| 87 // We generate a range of sub-sample offsets from 0.0 to 1.0. | |
| 88 for (int offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) { | |
|
Ami GONE FROM CHROMIUM
2012/07/03 20:54:42
FWIW this could be a double as well and avoid the
DaleCurtis
2012/07/10 01:00:25
Then it needs a static_cast<int> when indexing int
| |
| 89 double subsample_offset = | |
| 90 static_cast<double>(offset_idx) / kKernelOffsetCount; | |
| 91 | |
| 92 for (int i = 0; i < kKernelSize; ++i) { | |
| 93 // Compute the sinc with offset. | |
| 94 double s = | |
| 95 sinc_scale_factor * M_PI * (i - kKernelSize / 2 - subsample_offset); | |
| 96 double sinc = (!s ? 1.0 : sin(s) / s) * sinc_scale_factor; | |
| 97 | |
| 98 // Compute Blackman window, matching the offset of the sinc(). | |
| 99 double x = (i - subsample_offset) / kKernelSize; | |
| 100 double window = a0 - a1 * cos(2.0 * M_PI * x) + a2 * cos(4.0 * M_PI * x); | |
| 101 | |
| 102 // Window the sinc() function and store at the correct offset. | |
| 103 kernel_storage_[i + offset_idx * kKernelSize] = sinc * window; | |
| 104 } | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 void SincResampler::Resample(float* destination, int frames) { | |
| 109 int remaining_frames = frames; | |
| 110 | |
| 111 // Setup various region pointers in the buffer (see diagram above). | |
| 112 float* r0 = input_buffer_.get() + kKernelSize / 2; | |
| 113 float* r1 = input_buffer_.get(); | |
| 114 float* r2 = r0; | |
| 115 float* r3 = r0 + kBlockSize - kKernelSize / 2; | |
| 116 float* r4 = r0 + kBlockSize; | |
| 117 float* r5 = r0 + kKernelSize / 2; | |
| 118 | |
| 119 // Step (1) -- Prime the input buffer at the start of the input stream. | |
| 120 if (!buffer_primed_) { | |
| 121 read_cb_.Run(r0, kBlockSize + kKernelSize / 2); | |
|
Ami GONE FROM CHROMIUM
2012/07/03 20:54:42
How does this not violate your expectations in the
DaleCurtis
2012/07/10 01:00:25
remaining_frames will be 0 before the next call oc
| |
| 122 buffer_primed_ = true; | |
| 123 } | |
| 124 | |
| 125 // Step (2) -- Resample! | |
| 126 while (remaining_frames) { | |
| 127 while (virtual_source_idx_ < kBlockSize) { | |
| 128 // |virtual_source_idx_| lies in between two kernel offsets so figure out | |
| 129 // what they are. | |
| 130 int source_idx = static_cast<int>(virtual_source_idx_); | |
| 131 double subsample_remainder = virtual_source_idx_ - source_idx; | |
| 132 | |
| 133 double virtual_offset_idx = subsample_remainder * kKernelOffsetCount; | |
| 134 int offset_idx = static_cast<int>(virtual_offset_idx); | |
| 135 | |
| 136 float* k1 = kernel_storage_.get() + offset_idx * kKernelSize; | |
| 137 float* k2 = k1 + kKernelSize; | |
| 138 | |
| 139 // Initialize input pointer based on quantized |virtual_source_idx_|. | |
| 140 float* input_ptr = r1 + source_idx; | |
| 141 | |
| 142 // We'll compute "convolutions" for the two kernels which straddle | |
| 143 // |virtual_source_idx_|. | |
| 144 float sum1 = 0; | |
| 145 float sum2 = 0; | |
| 146 | |
| 147 // Figure out how much to weight each kernel's "convolution". | |
| 148 double kernel_interpolation_factor = virtual_offset_idx - offset_idx; | |
| 149 | |
| 150 // Generate a single output sample. | |
| 151 int n = kKernelSize; | |
| 152 float input; | |
| 153 // TODO(dalecurtis): For initial commit, I've ripped out all the SSE | |
| 154 // optimizations, these definitely need to go back in before release. | |
| 155 while (n--) { | |
| 156 input = *input_ptr++; | |
| 157 sum1 += input * *k1++; | |
| 158 sum2 += input * *k2++; | |
| 159 } | |
| 160 | |
| 161 // Linearly interpolate the two "convolutions". | |
| 162 double result = (1.0 - kernel_interpolation_factor) * sum1 | |
| 163 + kernel_interpolation_factor * sum2; | |
| 164 | |
| 165 *destination++ = result; | |
| 166 | |
| 167 // Advance the virtual index. | |
| 168 virtual_source_idx_ += io_sample_rate_ratio_; | |
| 169 | |
| 170 if (!--remaining_frames) | |
| 171 return; | |
| 172 } | |
| 173 | |
| 174 // Wrap back around to the start. | |
| 175 virtual_source_idx_ -= kBlockSize; | |
| 176 | |
| 177 // Step (3) Copy r3 to r1 and r4 to r2. | |
| 178 // This wraps the last input frames back to the start of the buffer. | |
| 179 memcpy(r1, r3, sizeof(*input_buffer_.get()) * (kKernelSize / 2)); | |
| 180 memcpy(r2, r4, sizeof(*input_buffer_.get()) * (kKernelSize / 2)); | |
| 181 | |
| 182 // Step (4) | |
| 183 // Refresh the buffer with more input. | |
| 184 read_cb_.Run(r5, kBlockSize); | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 } // namespace media | |
| OLD | NEW |