1 /*
2  * Copyright © 2016 Mozilla Foundation
3  *
4  * This program is made available under an ISC-style license.  See the
5  * accompanying file LICENSE for details.
6  */
7 
8 #if !defined(CUBEB_RESAMPLER_INTERNAL)
9 #define CUBEB_RESAMPLER_INTERNAL
10 
11 #include <cmath>
12 #include <cassert>
13 #include <algorithm>
14 #include <memory>
15 #ifdef CUBEB_GECKO_BUILD
16 #include "mozilla/UniquePtr.h"
17 // In libc++, symbols such as std::unique_ptr may be defined in std::__1.
18 // The _LIBCPP_BEGIN_NAMESPACE_STD and _LIBCPP_END_NAMESPACE_STD macros
19 // will expand to the correct namespace.
20 #ifdef _LIBCPP_BEGIN_NAMESPACE_STD
21 #define MOZ_BEGIN_STD_NAMESPACE _LIBCPP_BEGIN_NAMESPACE_STD
22 #define MOZ_END_STD_NAMESPACE _LIBCPP_END_NAMESPACE_STD
23 #else
24 #define MOZ_BEGIN_STD_NAMESPACE namespace std {
25 #define MOZ_END_STD_NAMESPACE }
26 #endif
27 MOZ_BEGIN_STD_NAMESPACE
28   using mozilla::DefaultDelete;
29   using mozilla::UniquePtr;
30   #define default_delete DefaultDelete
31   #define unique_ptr UniquePtr
32 MOZ_END_STD_NAMESPACE
33 #endif
34 #include "cubeb/cubeb.h"
35 #include "cubeb_utils.h"
36 #include "cubeb-speex-resampler.h"
37 #include "cubeb_resampler.h"
38 #include "cubeb_log.h"
39 #include <stdio.h>
40 
41 /* This header file contains the internal C++ API of the resamplers, for testing. */
42 
43 // When dropping audio input frames to prevent building
44 // an input delay, this function returns the number of frames
45 // to keep in the buffer.
46 // @parameter sample_rate The sample rate of the stream.
47 // @return A number of frames to keep.
48 uint32_t min_buffered_audio_frame(uint32_t sample_rate);
49 
50 int to_speex_quality(cubeb_resampler_quality q);
51 
52 struct cubeb_resampler {
53   virtual long fill(void * input_buffer, long * input_frames_count,
54                     void * output_buffer, long frames_needed) = 0;
55   virtual long latency() = 0;
~cubeb_resamplercubeb_resampler56   virtual ~cubeb_resampler() {}
57 };
58 
59 /** Base class for processors. This is just used to share methods for now. */
60 class processor {
61 public:
processor(uint32_t channels)62   explicit processor(uint32_t channels)
63     : channels(channels)
64   {}
65 protected:
frames_to_samples(size_t frames)66   size_t frames_to_samples(size_t frames) const
67   {
68     return frames * channels;
69   }
samples_to_frames(size_t samples)70   size_t samples_to_frames(size_t samples) const
71   {
72     assert(!(samples % channels));
73     return samples / channels;
74   }
75   /** The number of channel of the audio buffers to be resampled. */
76   const uint32_t channels;
77 };
78 
79 template<typename T>
80 class passthrough_resampler : public cubeb_resampler
81                             , public processor {
82 public:
83   passthrough_resampler(cubeb_stream * s,
84                         cubeb_data_callback cb,
85                         void * ptr,
86                         uint32_t input_channels,
87                         uint32_t sample_rate);
88 
89   virtual long fill(void * input_buffer, long * input_frames_count,
90                     void * output_buffer, long output_frames);
91 
latency()92   virtual long latency()
93   {
94     return 0;
95   }
96 
drop_audio_if_needed()97   void drop_audio_if_needed()
98   {
99     uint32_t to_keep = min_buffered_audio_frame(sample_rate);
100     uint32_t available = samples_to_frames(internal_input_buffer.length());
101     if (available > to_keep) {
102       internal_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
103     }
104   }
105 
106 private:
107   cubeb_stream * const stream;
108   const cubeb_data_callback data_callback;
109   void * const user_ptr;
110   /* This allows to buffer some input to account for the fact that we buffer
111    * some inputs. */
112   auto_array<T> internal_input_buffer;
113   uint32_t sample_rate;
114 };
115 
116 /** Bidirectional resampler, can resample an input and an output stream, or just
117  * an input stream or output stream. In this case a delay is inserted in the
118  * opposite direction to keep the streams synchronized. */
119 template<typename T, typename InputProcessing, typename OutputProcessing>
120 class cubeb_resampler_speex : public cubeb_resampler {
121 public:
122   cubeb_resampler_speex(InputProcessing * input_processor,
123                         OutputProcessing * output_processor,
124                         cubeb_stream * s,
125                         cubeb_data_callback cb,
126                         void * ptr);
127 
128   virtual ~cubeb_resampler_speex();
129 
130   virtual long fill(void * input_buffer, long * input_frames_count,
131                     void * output_buffer, long output_frames_needed);
132 
latency()133   virtual long latency()
134   {
135     if (input_processor && output_processor) {
136       assert(input_processor->latency() == output_processor->latency());
137       return input_processor->latency();
138     } else if (input_processor) {
139       return input_processor->latency();
140     } else {
141       return output_processor->latency();
142     }
143   }
144 
145 private:
146   typedef long(cubeb_resampler_speex::*processing_callback)(T * input_buffer, long * input_frames_count, T * output_buffer, long output_frames_needed);
147 
148   long fill_internal_duplex(T * input_buffer, long * input_frames_count,
149                             T * output_buffer, long output_frames_needed);
150   long fill_internal_input(T * input_buffer, long * input_frames_count,
151                            T * output_buffer, long output_frames_needed);
152   long fill_internal_output(T * input_buffer, long * input_frames_count,
153                             T * output_buffer, long output_frames_needed);
154 
155   std::unique_ptr<InputProcessing> input_processor;
156   std::unique_ptr<OutputProcessing> output_processor;
157   processing_callback fill_internal;
158   cubeb_stream * const stream;
159   const cubeb_data_callback data_callback;
160   void * const user_ptr;
161   bool draining = false;
162 };
163 
164 /** Handles one way of a (possibly) duplex resampler, working on interleaved
165  * audio buffers of type T. This class is designed so that the number of frames
166  * coming out of the resampler can be precisely controled. It manages its own
167  * input buffer, and can use the caller's output buffer, or allocate its own. */
168 template<typename T>
169 class cubeb_resampler_speex_one_way : public processor {
170 public:
171   /** The sample type of this resampler, either 16-bit integers or 32-bit
172    * floats. */
173   typedef T sample_type;
174   /** Construct a resampler resampling from #source_rate to #target_rate, that
175    * can be arbitrary, strictly positive number.
176    * @parameter channels The number of channels this resampler will resample.
177    * @parameter source_rate The sample-rate of the audio input.
178    * @parameter target_rate The sample-rate of the audio output.
179    * @parameter quality A number between 0 (fast, low quality) and 10 (slow,
180    * high quality). */
cubeb_resampler_speex_one_way(uint32_t channels,uint32_t source_rate,uint32_t target_rate,int quality)181   cubeb_resampler_speex_one_way(uint32_t channels,
182                                 uint32_t source_rate,
183                                 uint32_t target_rate,
184                                 int quality)
185   : processor(channels)
186   , resampling_ratio(static_cast<float>(source_rate) / target_rate)
187   , source_rate(source_rate)
188   , additional_latency(0)
189   , leftover_samples(0)
190   {
191     int r;
192     speex_resampler = speex_resampler_init(channels, source_rate,
193                                            target_rate, quality, &r);
194     assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
195 
196     uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler);
197     const size_t LATENCY_SAMPLES = 8192;
198     T input_buffer[LATENCY_SAMPLES] = {};
199     T output_buffer[LATENCY_SAMPLES] = {};
200     uint32_t input_frame_count = input_latency;
201     uint32_t output_frame_count = LATENCY_SAMPLES;
202     assert(input_latency * channels <= LATENCY_SAMPLES);
203     speex_resample(
204       input_buffer,
205       &input_frame_count,
206       output_buffer,
207       &output_frame_count);
208   }
209 
210   /** Destructor, deallocate the resampler */
~cubeb_resampler_speex_one_way()211   virtual ~cubeb_resampler_speex_one_way()
212   {
213     speex_resampler_destroy(speex_resampler);
214   }
215 
216   /* Fill the resampler with `input_frame_count` frames. */
input(T * input_buffer,size_t input_frame_count)217   void input(T * input_buffer, size_t input_frame_count)
218   {
219     resampling_in_buffer.push(input_buffer,
220                               frames_to_samples(input_frame_count));
221   }
222 
223   /** Outputs exactly `output_frame_count` into `output_buffer`.
224     * `output_buffer` has to be at least `output_frame_count` long. */
output(T * output_buffer,size_t output_frame_count)225   size_t output(T * output_buffer, size_t output_frame_count)
226   {
227     uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
228     uint32_t out_len = output_frame_count;
229 
230     speex_resample(resampling_in_buffer.data(), &in_len,
231                    output_buffer, &out_len);
232 
233     /* This shifts back any unresampled samples to the beginning of the input
234        buffer. */
235     resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
236 
237     return out_len;
238   }
239 
output_for_input(uint32_t input_frames)240   size_t output_for_input(uint32_t input_frames)
241   {
242     return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length()))
243                          / resampling_ratio);
244   }
245 
246   /** Returns a buffer containing exactly `output_frame_count` resampled frames.
247     * The consumer should not hold onto the pointer. */
output(size_t output_frame_count,size_t * input_frames_used)248   T * output(size_t output_frame_count, size_t * input_frames_used)
249   {
250     if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) {
251       resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
252     }
253 
254     uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
255     uint32_t out_len = output_frame_count;
256 
257     speex_resample(resampling_in_buffer.data(), &in_len,
258                    resampling_out_buffer.data(), &out_len);
259 
260     if (out_len < output_frame_count) {
261       LOGV("underrun during resampling: got %u frames, expected %zu", (unsigned)out_len, output_frame_count);
262       // silence the rightmost part
263       T* data = resampling_out_buffer.data();
264       for (uint32_t i = frames_to_samples(out_len); i < frames_to_samples(output_frame_count); i++) {
265         data[i] = 0;
266       }
267     }
268 
269     /* This shifts back any unresampled samples to the beginning of the input
270        buffer. */
271     resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
272     *input_frames_used = in_len;
273 
274     return resampling_out_buffer.data();
275   }
276 
277   /** Get the latency of the resampler, in output frames. */
latency()278   uint32_t latency() const
279   {
280     /* The documentation of the resampler talks about "samples" here, but it
281      * only consider a single channel here so it's the same number of frames. */
282     int latency = 0;
283 
284     latency =
285       speex_resampler_get_output_latency(speex_resampler) + additional_latency;
286 
287     assert(latency >= 0);
288 
289     return latency;
290   }
291 
292   /** Returns the number of frames to pass in the input of the resampler to have
293    * exactly `output_frame_count` resampled frames. This can return a number
294    * slightly bigger than what is strictly necessary, but it guaranteed that the
295    * number of output frames will be exactly equal. */
input_needed_for_output(int32_t output_frame_count)296   uint32_t input_needed_for_output(int32_t output_frame_count) const
297   {
298     assert(output_frame_count >= 0); // Check overflow
299     int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
300     int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
301     float input_frames_needed =
302       (output_frame_count - unresampled_frames_left) * resampling_ratio
303         - resampled_frames_left;
304     if (input_frames_needed < 0) {
305       return 0;
306     }
307     return (uint32_t)ceilf(input_frames_needed);
308   }
309 
310   /** Returns a pointer to the input buffer, that contains empty space for at
311    * least `frame_count` elements. This is useful so that consumer can directly
312    * write into the input buffer of the resampler. The pointer returned is
313    * adjusted so that leftover data are not overwritten.
314    */
input_buffer(size_t frame_count)315   T * input_buffer(size_t frame_count)
316   {
317     leftover_samples = resampling_in_buffer.length();
318     resampling_in_buffer.reserve(leftover_samples +
319                                  frames_to_samples(frame_count));
320     return resampling_in_buffer.data() + leftover_samples;
321   }
322 
323   /** This method works with `input_buffer`, and allows to inform the processor
324       how much frames have been written in the provided buffer. */
written(size_t written_frames)325   void written(size_t written_frames)
326   {
327     resampling_in_buffer.set_length(leftover_samples +
328                                     frames_to_samples(written_frames));
329   }
330 
drop_audio_if_needed()331   void drop_audio_if_needed()
332   {
333     // Keep at most 100ms buffered.
334     uint32_t available = samples_to_frames(resampling_in_buffer.length());
335     uint32_t to_keep = min_buffered_audio_frame(source_rate);
336     if (available > to_keep) {
337       resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep));
338     }
339   }
340 private:
341   /** Wrapper for the speex resampling functions to have a typed
342     * interface. */
speex_resample(float * input_buffer,uint32_t * input_frame_count,float * output_buffer,uint32_t * output_frame_count)343   void speex_resample(float * input_buffer, uint32_t * input_frame_count,
344                       float * output_buffer, uint32_t * output_frame_count)
345   {
346 #ifndef NDEBUG
347     int rv;
348     rv =
349 #endif
350       speex_resampler_process_interleaved_float(speex_resampler,
351                                                 input_buffer,
352                                                 input_frame_count,
353                                                 output_buffer,
354                                                 output_frame_count);
355     assert(rv == RESAMPLER_ERR_SUCCESS);
356   }
357 
speex_resample(short * input_buffer,uint32_t * input_frame_count,short * output_buffer,uint32_t * output_frame_count)358   void speex_resample(short * input_buffer, uint32_t * input_frame_count,
359                       short * output_buffer, uint32_t * output_frame_count)
360   {
361 #ifndef NDEBUG
362     int rv;
363     rv =
364 #endif
365       speex_resampler_process_interleaved_int(speex_resampler,
366                                               input_buffer,
367                                               input_frame_count,
368                                               output_buffer,
369                                               output_frame_count);
370     assert(rv == RESAMPLER_ERR_SUCCESS);
371   }
372   /** The state for the speex resampler used internaly. */
373   SpeexResamplerState * speex_resampler;
374   /** Source rate / target rate. */
375   const float resampling_ratio;
376   const uint32_t source_rate;
377   /** Storage for the input frames, to be resampled. Also contains
378    * any unresampled frames after resampling. */
379   auto_array<T> resampling_in_buffer;
380   /* Storage for the resampled frames, to be passed back to the caller. */
381   auto_array<T> resampling_out_buffer;
382   /** Additional latency inserted into the pipeline for synchronisation. */
383   uint32_t additional_latency;
384   /** When `input_buffer` is called, this allows tracking the number of samples
385       that were in the buffer. */
386   uint32_t leftover_samples;
387 };
388 
389 /** This class allows delaying an audio stream by `frames` frames. */
390 template<typename T>
391 class delay_line : public processor {
392 public:
393   /** Constructor
394    * @parameter frames the number of frames of delay.
395    * @parameter channels the number of channels of this delay line.
396    * @parameter sample_rate sample-rate of the audio going through this delay line */
delay_line(uint32_t frames,uint32_t channels,uint32_t sample_rate)397   delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate)
398     : processor(channels)
399     , length(frames)
400     , leftover_samples(0)
401     , sample_rate(sample_rate)
402   {
403     /* Fill the delay line with some silent frames to add latency. */
404     delay_input_buffer.push_silence(frames * channels);
405   }
406   /** Push some frames into the delay line.
407    * @parameter buffer the frames to push.
408    * @parameter frame_count the number of frames in #buffer. */
input(T * buffer,uint32_t frame_count)409   void input(T * buffer, uint32_t frame_count)
410   {
411     delay_input_buffer.push(buffer, frames_to_samples(frame_count));
412   }
413   /** Pop some frames from the internal buffer, into a internal output buffer.
414    * @parameter frames_needed the number of frames to be returned.
415    * @return a buffer containing the delayed frames. The consumer should not
416    * hold onto the pointer. */
output(uint32_t frames_needed,size_t * input_frames_used)417   T * output(uint32_t frames_needed, size_t * input_frames_used)
418   {
419     if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
420       delay_output_buffer.reserve(frames_to_samples(frames_needed));
421     }
422 
423     delay_output_buffer.clear();
424     delay_output_buffer.push(delay_input_buffer.data(),
425                              frames_to_samples(frames_needed));
426     delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
427     *input_frames_used = frames_needed;
428 
429     return delay_output_buffer.data();
430   }
431   /** Get a pointer to the first writable location in the input buffer>
432    * @parameter frames_needed the number of frames the user needs to write into
433    * the buffer.
434    * @returns a pointer to a location in the input buffer where #frames_needed
435    * can be writen. */
input_buffer(uint32_t frames_needed)436   T * input_buffer(uint32_t frames_needed)
437   {
438     leftover_samples = delay_input_buffer.length();
439     delay_input_buffer.reserve(leftover_samples + frames_to_samples(frames_needed));
440     return delay_input_buffer.data() + leftover_samples;
441   }
442   /** This method works with `input_buffer`, and allows to inform the processor
443       how much frames have been written in the provided buffer. */
written(size_t frames_written)444   void written(size_t frames_written)
445   {
446     delay_input_buffer.set_length(leftover_samples +
447                                   frames_to_samples(frames_written));
448   }
449   /** Drains the delay line, emptying the buffer.
450    * @parameter output_buffer the buffer in which the frames are written.
451    * @parameter frames_needed the maximum number of frames to write.
452    * @return the actual number of frames written. */
output(T * output_buffer,uint32_t frames_needed)453   size_t output(T * output_buffer, uint32_t frames_needed)
454   {
455     uint32_t in_len = samples_to_frames(delay_input_buffer.length());
456     uint32_t out_len = frames_needed;
457 
458     uint32_t to_pop = std::min(in_len, out_len);
459 
460     delay_input_buffer.pop(output_buffer, frames_to_samples(to_pop));
461 
462     return to_pop;
463   }
464   /** Returns the number of frames one needs to input into the delay line to get
465    * #frames_needed frames back.
466    * @parameter frames_needed the number of frames one want to write into the
467    * delay_line
468    * @returns the number of frames one will get. */
input_needed_for_output(int32_t frames_needed)469   uint32_t input_needed_for_output(int32_t frames_needed) const
470   {
471     assert(frames_needed >= 0); // Check overflow
472     return frames_needed;
473   }
474   /** Returns the number of frames produces for `input_frames` frames in input */
output_for_input(uint32_t input_frames)475   size_t output_for_input(uint32_t input_frames)
476   {
477     return input_frames;
478   }
479   /** The number of frames this delay line delays the stream by.
480    * @returns The number of frames of delay. */
latency()481   size_t latency()
482   {
483     return length;
484   }
485 
drop_audio_if_needed()486   void drop_audio_if_needed()
487   {
488     size_t available = samples_to_frames(delay_input_buffer.length());
489     uint32_t to_keep = min_buffered_audio_frame(sample_rate);
490     if (available > to_keep) {
491       delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
492     }
493   }
494 private:
495   /** The length, in frames, of this delay line */
496   uint32_t length;
497   /** When `input_buffer` is called, this allows tracking the number of samples
498       that where in the buffer. */
499   uint32_t leftover_samples;
500   /** The input buffer, where the delay is applied. */
501   auto_array<T> delay_input_buffer;
502   /** The output buffer. This is only ever used if using the ::output with a
503    * single argument. */
504   auto_array<T> delay_output_buffer;
505   uint32_t sample_rate;
506 };
507 
508 /** This sits behind the C API and is more typed. */
509 template<typename T>
510 cubeb_resampler *
cubeb_resampler_create_internal(cubeb_stream * stream,cubeb_stream_params * input_params,cubeb_stream_params * output_params,unsigned int target_rate,cubeb_data_callback callback,void * user_ptr,cubeb_resampler_quality quality)511 cubeb_resampler_create_internal(cubeb_stream * stream,
512                                 cubeb_stream_params * input_params,
513                                 cubeb_stream_params * output_params,
514                                 unsigned int target_rate,
515                                 cubeb_data_callback callback,
516                                 void * user_ptr,
517                                 cubeb_resampler_quality quality)
518 {
519   std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr;
520   std::unique_ptr<cubeb_resampler_speex_one_way<T>> output_resampler = nullptr;
521   std::unique_ptr<delay_line<T>> input_delay = nullptr;
522   std::unique_ptr<delay_line<T>> output_delay = nullptr;
523 
524   assert((input_params || output_params) &&
525          "need at least one valid parameter pointer.");
526 
527   /* All the streams we have have a sample rate that matches the target
528      sample rate, use a no-op resampler, that simply forwards the buffers to the
529      callback. */
530   if (((input_params && input_params->rate == target_rate) &&
531       (output_params && output_params->rate == target_rate)) ||
532       (input_params && !output_params && (input_params->rate == target_rate)) ||
533       (output_params && !input_params && (output_params->rate == target_rate))) {
534     LOG("Input and output sample-rate match, target rate of %dHz", target_rate);
535     return new passthrough_resampler<T>(stream, callback,
536                                         user_ptr,
537                                         input_params ? input_params->channels : 0,
538                                         target_rate);
539   }
540 
541   /* Determine if we need to resampler one or both directions, and create the
542      resamplers. */
543   if (output_params && (output_params->rate != target_rate)) {
544     output_resampler.reset(
545         new cubeb_resampler_speex_one_way<T>(output_params->channels,
546                                              target_rate,
547                                              output_params->rate,
548                                              to_speex_quality(quality)));
549     if (!output_resampler) {
550       return NULL;
551     }
552   }
553 
554   if (input_params && (input_params->rate != target_rate)) {
555     input_resampler.reset(
556         new cubeb_resampler_speex_one_way<T>(input_params->channels,
557                                              input_params->rate,
558                                              target_rate,
559                                              to_speex_quality(quality)));
560     if (!input_resampler) {
561       return NULL;
562     }
563   }
564 
565   /* If we resample only one direction but we have a duplex stream, insert a
566    * delay line with a length equal to the resampler latency of the
567    * other direction so that the streams are synchronized. */
568   if (input_resampler && !output_resampler && input_params && output_params) {
569     output_delay.reset(new delay_line<T>(input_resampler->latency(),
570                                          output_params->channels,
571                                          output_params->rate));
572     if (!output_delay) {
573       return NULL;
574     }
575   } else if (output_resampler && !input_resampler && input_params && output_params) {
576     input_delay.reset(new delay_line<T>(output_resampler->latency(),
577                                         input_params->channels,
578                                         output_params->rate));
579     if (!input_delay) {
580       return NULL;
581     }
582   }
583 
584   if (input_resampler && output_resampler) {
585     LOG("Resampling input (%d) and output (%d) to target rate of %dHz", input_params->rate, output_params->rate, target_rate);
586     return new cubeb_resampler_speex<T,
587                                      cubeb_resampler_speex_one_way<T>,
588                                      cubeb_resampler_speex_one_way<T>>
589                                        (input_resampler.release(),
590                                         output_resampler.release(),
591                                         stream, callback, user_ptr);
592   } else if (input_resampler) {
593     LOG("Resampling input (%d) to target and output rate of %dHz", input_params->rate, target_rate);
594     return new cubeb_resampler_speex<T,
595                                      cubeb_resampler_speex_one_way<T>,
596                                      delay_line<T>>
597                                       (input_resampler.release(),
598                                        output_delay.release(),
599                                        stream, callback, user_ptr);
600   } else {
601     LOG("Resampling output (%dHz) to target and input rate of %dHz", output_params->rate, target_rate);
602     return new cubeb_resampler_speex<T,
603                                      delay_line<T>,
604                                      cubeb_resampler_speex_one_way<T>>
605                                       (input_delay.release(),
606                                        output_resampler.release(),
607                                        stream, callback, user_ptr);
608   }
609 }
610 
611 #endif /* CUBEB_RESAMPLER_INTERNAL */
612