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 |