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_index_|, etc. | |
| 33 // MSVC++ requires this to be set before any other includes to get M_PI. | |
| 34 #define _USE_MATH_DEFINES | |
|
Chris Rogers
2012/07/02 20:38:14
Not sure if there's any cleaner way to do this?
I
DaleCurtis
2012/07/03 00:36:34
Not that I've found, this is what I used in my oth
| |
| 35 | |
| 36 #include "media/base/sinc_resampler.h" | |
| 37 | |
| 38 #include <cmath> | |
| 39 | |
| 40 #include "base/logging.h" | |
| 41 | |
| 42 namespace media { | |
| 43 | |
| 44 // The kernel size can be adjusted for quality (higher is better). | |
| 45 static const int kKernelSize = 32; | |
|
Chris Rogers
2012/07/02 20:38:14
32 should be fine for now, but you could add a TOD
DaleCurtis
2012/07/03 00:36:34
Done.
| |
| 46 // The kernel offset count is used for interpolation and is the number of | |
| 47 // sub-sample kernel shifts. | |
| 48 static const int kKernelOffsetCount = 32; | |
| 49 | |
| 50 const int SincResampler::kBlockSize = 512; | |
| 51 const int SincResampler::kBufferSize = kBlockSize + kKernelSize; | |
| 52 COMPILE_ASSERT(kKernelSize % 2 == 0, kKernelSize_must_be_even); | |
| 53 COMPILE_ASSERT( | |
| 54 SincResampler::kBlockSize > kKernelSize, kBlockSize_must_be_gt_kKernelSize); | |
| 55 | |
| 56 SincResampler::SincResampler(AudioSourceProvider* provider, double scale_factor) | |
| 57 : scale_factor_(scale_factor), | |
| 58 virtual_source_idx_(0), | |
| 59 buffer_primed_(false), | |
| 60 provider_(provider), | |
| 61 // TODO(dalecurtis): When we switch to AVX/SSE optimization, we'll need to | |
| 62 // allocate with 32-byte alignment and ensure they're sized % 32 bytes. | |
| 63 kernel_storage_(new float[kKernelSize * (kKernelOffsetCount + 1)]), | |
| 64 input_buffer_(new float[kBufferSize]) { | |
| 65 memset(kernel_storage_, 0, | |
| 66 sizeof(*kernel_storage_) * kKernelSize * (kKernelOffsetCount + 1)); | |
| 67 memset(input_buffer_, 0, sizeof(*kernel_storage_) * kBufferSize); | |
| 68 | |
| 69 InitializeKernel(); | |
| 70 } | |
| 71 | |
| 72 SincResampler::~SincResampler() { | |
| 73 delete[] kernel_storage_; | |
| 74 delete[] input_buffer_; | |
| 75 } | |
| 76 | |
| 77 void SincResampler::InitializeKernel() { | |
| 78 // Blackman window parameters. | |
| 79 double alpha = 0.16; | |
| 80 double a0 = 0.5 * (1.0 - alpha); | |
| 81 double a1 = 0.5; | |
| 82 double a2 = 0.5 * alpha; | |
| 83 | |
| 84 // |sinc_scale_factor| is basically the normalized cutoff frequency of the | |
| 85 // low-pass filter. | |
| 86 double sinc_scale_factor = scale_factor_ > 1.0 ? 1.0 / scale_factor_ : 1.0; | |
| 87 | |
| 88 // The sinc function is an idealized brick-wall filter, but since we're | |
| 89 // windowing it the transition from pass to stop does not happen right away. | |
| 90 // So we should adjust the lowpass filter cutoff slightly downward to avoid | |
| 91 // some aliasing at the very high-end. | |
| 92 // TODO(crogers): this value is empirical and to be more exact should vary | |
| 93 // depending on kKernelSize. | |
| 94 sinc_scale_factor *= 0.9; | |
| 95 | |
| 96 // Generates a set of windowed sinc() kernels. | |
| 97 // We generate a range of sub-sample offsets from 0.0 to 1.0. | |
| 98 for (int offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) { | |
| 99 double subsample_offset = | |
| 100 static_cast<double>(offset_idx) / kKernelOffsetCount; | |
| 101 | |
| 102 for (int i = 0; i < kKernelSize; ++i) { | |
| 103 // Compute the sinc with offset. | |
| 104 // TODO(dalecurtis): Is M_PI precise enough here? It was using piDouble. | |
|
Chris Rogers
2012/07/02 20:38:14
I think it should be fine -- in any case the unit
DaleCurtis
2012/07/03 00:36:34
Done.
| |
| 105 double s = | |
| 106 sinc_scale_factor * M_PI * (i - kKernelSize / 2 - subsample_offset); | |
| 107 double sinc = (!s ? 1.0 : sin(s) / s) * sinc_scale_factor; | |
| 108 | |
| 109 // Compute Blackman window, matching the offset of the sinc(). | |
| 110 double x = (i - subsample_offset) / kKernelSize; | |
| 111 double window = a0 - a1 * cos(2.0 * M_PI * x) + a2 * cos(4.0 * M_PI * x); | |
| 112 | |
| 113 // Window the sinc() function and store at the correct offset. | |
| 114 kernel_storage_[i + offset_idx * kKernelSize] = sinc * window; | |
| 115 } | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 void SincResampler::Resample(float* destination, int number_of_frames) { | |
| 120 int remaining_frames = number_of_frames; | |
| 121 | |
| 122 // Setup various region pointers in the buffer (see diagram above). | |
| 123 float* r0 = input_buffer_ + kKernelSize / 2; | |
| 124 float* r1 = input_buffer_; | |
| 125 float* r2 = r0; | |
| 126 float* r3 = r0 + kBlockSize - kKernelSize / 2; | |
| 127 float* r4 = r0 + kBlockSize; | |
| 128 float* r5 = r0 + kKernelSize / 2; | |
| 129 | |
| 130 // Step (1) -- Prime the input buffer at the start of the input stream. | |
| 131 if (!buffer_primed_) { | |
| 132 provider_->ProvideInput(r0, kBlockSize + kKernelSize / 2); | |
| 133 buffer_primed_ = true; | |
| 134 } | |
| 135 | |
| 136 // Step (2) -- Resample! | |
| 137 while (remaining_frames) { | |
| 138 while (virtual_source_idx_ < kBlockSize) { | |
| 139 // |virtual_source_idx_| lies in between two kernel offsets so figure out | |
| 140 // what they are. | |
| 141 int source_idx = static_cast<int>(virtual_source_idx_); | |
| 142 double subsample_remainder = virtual_source_idx_ - source_idx; | |
| 143 | |
| 144 double virtual_offset_idx = subsample_remainder * kKernelOffsetCount; | |
| 145 int offset_idx = static_cast<int>(virtual_offset_idx); | |
| 146 | |
| 147 float* k1 = kernel_storage_ + offset_idx * kKernelSize; | |
| 148 float* k2 = k1 + kKernelSize; | |
| 149 | |
| 150 // Initialize input pointer based on quantized |virtual_source_idx_|. | |
| 151 float* input_ptr = r1 + source_idx; | |
| 152 | |
| 153 // We'll compute "convolutions" for the two kernels which straddle | |
| 154 // |virtual_source_idx_|. | |
| 155 float sum1 = 0; | |
| 156 float sum2 = 0; | |
| 157 | |
| 158 // Figure out how much to weight each kernel's "convolution". | |
| 159 double kernel_interpolation_factor = virtual_offset_idx - offset_idx; | |
| 160 | |
| 161 // Generate a single output sample. | |
| 162 int n = kKernelSize; | |
| 163 float input; | |
| 164 // TODO(dalecurtis): For initial commit, I've ripped out all the SSE | |
| 165 // optimizations, these definitely need to go back in before release. | |
| 166 while (n--) { | |
| 167 input = *input_ptr++; | |
| 168 sum1 += input * *k1++; | |
| 169 sum2 += input * *k2++; | |
| 170 } | |
| 171 | |
| 172 // Linearly interpolate the two "convolutions". | |
| 173 double result = (1.0 - kernel_interpolation_factor) * sum1 | |
| 174 + kernel_interpolation_factor * sum2; | |
| 175 | |
| 176 *destination++ = result; | |
| 177 | |
| 178 // Advance the virtual index. | |
| 179 virtual_source_idx_ += scale_factor_; | |
| 180 | |
| 181 if (!--remaining_frames) | |
| 182 return; | |
| 183 } | |
| 184 | |
| 185 // Wrap back around to the start. | |
| 186 virtual_source_idx_ -= kBlockSize; | |
| 187 | |
| 188 // Step (3) Copy r3 to r1 and r4 to r2. | |
| 189 // This wraps the last input frames back to the start of the buffer. | |
| 190 memcpy(r1, r3, sizeof(*input_buffer_) * (kKernelSize / 2)); | |
| 191 memcpy(r2, r4, sizeof(*input_buffer_) * (kKernelSize / 2)); | |
| 192 | |
| 193 // Step (4) | |
| 194 // Refresh the buffer with more input. | |
| 195 provider_->ProvideInput(r5, kBlockSize); | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 } // namespace media | |
| OLD | NEW |