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 // <---------------------------------------> | |
Ami GONE FROM CHROMIUM
2012/07/10 03:23:00
I hope you have tests covering the boundary cases
DaleCurtis
2012/07/10 05:24:48
Did you have something particular in mind? These a
Ami GONE FROM CHROMIUM
2012/07/10 16:48:50
True, but they depend on the magic constants. My
DaleCurtis
2012/07/10 21:38:55
I think the current unit test covers this adequate
| |
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. | |
33 | |
34 // MSVC++ requires this to be set before any other includes to get M_PI. | |
35 #define _USE_MATH_DEFINES | |
36 | |
37 #include "media/base/sinc_resampler.h" | |
38 | |
39 #include <cmath> | |
40 | |
41 #include "base/logging.h" | |
42 | |
43 namespace media { | |
44 | |
45 SincResampler::SincResampler(double io_sample_rate_ratio, const ReadCB& read_cb) | |
46 : io_sample_rate_ratio_(io_sample_rate_ratio), | |
47 virtual_source_idx_(0), | |
48 buffer_primed_(false), | |
49 read_cb_(read_cb), | |
50 // TODO(dalecurtis): When we switch to AVX/SSE optimization, we'll need to | |
51 // allocate with 32-byte alignment and ensure they're sized % 32 bytes. | |
52 kernel_storage_(new float[kKernelStorageSize]), | |
53 input_buffer_(new float[kBufferSize]) { | |
54 DCHECK_EQ(kKernelSize % 2, 0) << "kKernelSize must be even!"; | |
55 DCHECK_GT(kBlockSize, kKernelSize) | |
56 << "kBlockSize must be greater than kKernelSize!"; | |
57 | |
58 memset(kernel_storage_.get(), 0, | |
59 sizeof(*kernel_storage_.get()) * kKernelStorageSize); | |
60 memset(input_buffer_.get(), 0, sizeof(*input_buffer_.get()) * kBufferSize); | |
61 | |
62 InitializeKernel(); | |
63 } | |
64 | |
65 SincResampler::~SincResampler() {} | |
66 | |
67 void SincResampler::InitializeKernel() { | |
68 // Blackman window parameters. | |
69 double alpha = 0.16; | |
Ami GONE FROM CHROMIUM
2012/07/10 03:23:00
static const this and the next three?
DaleCurtis
2012/07/10 21:38:55
Done.
| |
70 double a0 = 0.5 * (1.0 - alpha); | |
71 double a1 = 0.5; | |
72 double a2 = 0.5 * alpha; | |
73 | |
74 // |sinc_scale_factor| is basically the normalized cutoff frequency of the | |
75 // low-pass filter. | |
76 double sinc_scale_factor = | |
77 io_sample_rate_ratio_ > 1.0 ? 1.0 / io_sample_rate_ratio_ : 1.0; | |
78 | |
79 // The sinc function is an idealized brick-wall filter, but since we're | |
80 // windowing it the transition from pass to stop does not happen right away. | |
81 // So we should adjust the lowpass filter cutoff slightly downward to avoid | |
82 // some aliasing at the very high-end. | |
83 // TODO(crogers): this value is empirical and to be more exact should vary | |
84 // depending on kKernelSize. | |
85 sinc_scale_factor *= 0.9; | |
86 | |
87 // Generates a set of windowed sinc() kernels. | |
88 // We generate a range of sub-sample offsets from 0.0 to 1.0. | |
89 for (int offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) { | |
90 double subsample_offset = | |
91 static_cast<double>(offset_idx) / kKernelOffsetCount; | |
92 | |
93 for (int i = 0; i < kKernelSize; ++i) { | |
94 // Compute the sinc with offset. | |
95 double s = | |
96 sinc_scale_factor * M_PI * (i - kKernelSize / 2 - subsample_offset); | |
97 double sinc = (!s ? 1.0 : sin(s) / s) * sinc_scale_factor; | |
98 | |
99 // Compute Blackman window, matching the offset of the sinc(). | |
100 double x = (i - subsample_offset) / kKernelSize; | |
101 double window = a0 - a1 * cos(2.0 * M_PI * x) + a2 * cos(4.0 * M_PI * x); | |
102 | |
103 // Window the sinc() function and store at the correct offset. | |
104 kernel_storage_[i + offset_idx * kKernelSize] = sinc * window; | |
105 } | |
106 } | |
107 } | |
108 | |
109 void SincResampler::Resample(float* destination, int frames) { | |
110 int remaining_frames = frames; | |
111 | |
112 // Setup various region pointers in the buffer (see diagram above). | |
113 float* r0 = input_buffer_.get() + kKernelSize / 2; | |
114 float* r1 = input_buffer_.get(); | |
115 float* r2 = r0; | |
116 float* r3 = r0 + kBlockSize - kKernelSize / 2; | |
117 float* r4 = r0 + kBlockSize; | |
118 float* r5 = r0 + kKernelSize / 2; | |
119 | |
120 // Step (1) -- Prime the input buffer at the start of the input stream. | |
121 if (!buffer_primed_) { | |
122 read_cb_.Run(r0, kBlockSize + kKernelSize / 2); | |
123 buffer_primed_ = true; | |
124 } | |
125 | |
126 // Step (2) -- Resample! | |
127 while (remaining_frames) { | |
128 while (virtual_source_idx_ < kBlockSize) { | |
129 // |virtual_source_idx_| lies in between two kernel offsets so figure out | |
130 // what they are. | |
131 int source_idx = static_cast<int>(virtual_source_idx_); | |
132 double subsample_remainder = virtual_source_idx_ - source_idx; | |
133 | |
134 double virtual_offset_idx = subsample_remainder * kKernelOffsetCount; | |
135 int offset_idx = static_cast<int>(virtual_offset_idx); | |
136 | |
137 float* k1 = kernel_storage_.get() + offset_idx * kKernelSize; | |
138 float* k2 = k1 + kKernelSize; | |
139 | |
140 // Initialize input pointer based on quantized |virtual_source_idx_|. | |
141 float* input_ptr = r1 + source_idx; | |
142 | |
143 // We'll compute "convolutions" for the two kernels which straddle | |
144 // |virtual_source_idx_|. | |
145 float sum1 = 0; | |
146 float sum2 = 0; | |
147 | |
148 // Figure out how much to weight each kernel's "convolution". | |
149 double kernel_interpolation_factor = virtual_offset_idx - offset_idx; | |
150 | |
151 // Generate a single output sample. | |
152 int n = kKernelSize; | |
153 float input; | |
154 // TODO(dalecurtis): For initial commit, I've ripped out all the SSE | |
155 // optimizations, these definitely need to go back in before release. | |
156 while (n--) { | |
157 input = *input_ptr++; | |
158 sum1 += input * *k1++; | |
159 sum2 += input * *k2++; | |
160 } | |
161 | |
162 // Linearly interpolate the two "convolutions". | |
163 double result = (1.0 - kernel_interpolation_factor) * sum1 | |
164 + kernel_interpolation_factor * sum2; | |
165 | |
166 *destination++ = result; | |
167 | |
168 // Advance the virtual index. | |
169 virtual_source_idx_ += io_sample_rate_ratio_; | |
170 | |
171 if (!--remaining_frames) | |
172 return; | |
Ami GONE FROM CHROMIUM
2012/07/10 16:48:50
What happens if virtual_source_idx_ just passed kB
Chris Rogers
2012/07/10 17:37:11
I think this should be OK because the next time Re
DaleCurtis
2012/07/10 21:38:55
Yes, as Chris noted the state will fall through to
| |
173 } | |
174 | |
175 // Wrap back around to the start. | |
176 virtual_source_idx_ -= kBlockSize; | |
177 | |
178 // Step (3) Copy r3 to r1 and r4 to r2. | |
179 // This wraps the last input frames back to the start of the buffer. | |
180 memcpy(r1, r3, sizeof(*input_buffer_.get()) * (kKernelSize / 2)); | |
181 memcpy(r2, r4, sizeof(*input_buffer_.get()) * (kKernelSize / 2)); | |
182 | |
183 // Step (4) | |
184 // Refresh the buffer with more input. | |
185 read_cb_.Run(r5, kBlockSize); | |
186 } | |
187 } | |
188 | |
189 int SincResampler::ChunkSize() { | |
190 return kBlockSize / io_sample_rate_ratio_; | |
191 } | |
192 | |
193 } // namespace media | |
OLD | NEW |