1616
1717package media
1818
19+ /*
20+ #cgo pkg-config: soxr
21+ #include <stdlib.h>
22+ #include <soxr.h>
23+ */
24+ import "C"
25+
1926import (
20- "bytes"
21- "encoding/binary"
27+ "errors"
2228 "fmt"
2329 "runtime"
30+ "slices"
2431 "sync"
25-
26- "github.com/zaf/resample "
32+ "sync/atomic"
33+ "unsafe "
2734)
2835
2936func resampleSize (dstSampleRate , srcSampleRate int , srcSize int ) int {
@@ -38,26 +45,12 @@ func resampleSize(dstSampleRate, srcSampleRate int, srcSize int) int {
3845}
3946
4047func resampleBuffer (dst PCM16Sample , dstSampleRate int , src PCM16Sample , srcSampleRate int ) PCM16Sample {
41- inbuf := make ([]byte , len (src )* 2 )
42- outbuf := bytes .NewBuffer (nil )
43- outbuf .Grow (resampleSize (dstSampleRate , srcSampleRate , len (src )))
44- r , err := resample .New (outbuf , float64 (srcSampleRate ), float64 (dstSampleRate ), 1 , resample .I16 , resample .Quick )
45- if err != nil {
46- panic (err )
47- }
48- for i , v := range src {
49- binary .LittleEndian .PutUint16 (inbuf [i * 2 :], uint16 (v ))
50- }
51- _ , err = r .Write (inbuf )
52- _ = r .Close ()
48+ w := newResampleWriter (NewPCM16BufferWriter (& dst , dstSampleRate ), srcSampleRate )
49+ err := w .WriteSample (src )
50+ _ = w .Close ()
5351 if err != nil {
5452 panic (err )
5553 }
56- buf := outbuf .Bytes ()
57- for i := range len (buf ) / 2 {
58- v := int16 (binary .LittleEndian .Uint16 (buf [i * 2 :]))
59- dst = append (dst , v )
60- }
6154 return dst
6255}
6356
@@ -68,30 +61,26 @@ func newResampleWriter(w WriteCloser[PCM16Sample], sampleRate int) WriteCloser[P
6861 w : w ,
6962 srcRate : srcRate ,
7063 dstRate : dstRate ,
71- buffer : 0 , // set larger buffer for better resampler quality
64+ buffer : 0 , // set larger buffer for better resampler quality (see below)
7265 }
73- quality := resample . Quick
66+ quality := int ( C . SOXR_QQ )
7467 if srcRate > dstRate {
7568 if float64 (srcRate )/ float64 (dstRate ) > 3 {
76- quality = resample . LowQ
69+ quality = int ( C . SOXR_LQ )
7770 }
7871 }
7972 var err error
80- r .r , err = resample . New ( r , float64 ( srcRate ), float64 ( dstRate ), 1 , resample . I16 , quality )
73+ r .r , err = newSoxr ( dstRate , srcRate , quality )
8174 if err != nil {
8275 panic (err )
8376 }
84- runtime .AddCleanup (r , func (rr * resample.Resampler ) {
85- _ = rr .Close ()
86- }, r .r )
8777 return r
8878}
8979
9080type resampleWriter struct {
9181 mu sync.Mutex
9282 w WriteCloser [PCM16Sample ]
93- r * resample.Resampler
94- inbuf []byte
83+ r * soxrResampler
9584 srcRate int
9685 dstRate int
9786 dstFrame int
@@ -113,9 +102,21 @@ func (w *resampleWriter) SampleRate() int {
113102func (w * resampleWriter ) Close () error {
114103 w .mu .Lock ()
115104 defer w .mu .Unlock ()
116- _ = w .r .Close () // flush resampler's internal buffer
117- _ = w .flush (0 ) // flush our own PCM frame buffer
118- return w .w .Close ()
105+ // Flush soxr buffer to our buffer.
106+ var err error
107+ w .buf , _ , err = w .r .Resample (w .buf , nil )
108+ if err != nil {
109+ return err
110+ }
111+ // Close soxr resampler.
112+ _ = w .r .Close ()
113+ // Flush our own PCM frame buffer to the underlying writer.
114+ _ = w .flush (0 )
115+ err2 := w .w .Close ()
116+ if err2 != nil {
117+ err = err2
118+ }
119+ return err
119120}
120121
121122func (w * resampleWriter ) flush (minSize int ) error {
@@ -147,37 +148,121 @@ func (w *resampleWriter) WriteSample(data PCM16Sample) error {
147148 }
148149 w .mu .Lock ()
149150 defer w .mu .Unlock ()
150- if sz := len (data ) * 2 ; cap (w .inbuf ) < sz {
151- w .inbuf = make ([]byte , sz )
152- } else {
153- w .inbuf = w .inbuf [:sz ]
154- }
155- for i , v := range data {
156- binary .LittleEndian .PutUint16 (w .inbuf [i * 2 :], uint16 (v ))
157- }
158- left := w .inbuf
159- // Write converted input to the resampler's buffer.
160- // It will call our own Write method which collects data into a frame buffer.
161- for len (left ) > 0 {
162- n , err := w .r .Write (left )
163- if err != nil {
164- return err
165- }
166- left = left [n :]
151+ var err error
152+ // Write input to the resampler buffer.
153+ w .buf , _ , err = w .r .Resample (w .buf , data )
154+ if err != nil {
155+ return err
167156 }
168157 // Resampler will likely return a short buffer in the first run. In that case, we emit no samples on the first call.
169158 // This will cause a one frame delay for each resampler. Flushing the sampler, however will lead to frame
170159 // discontinuity, and thus - distortions on the frame boundaries.
171-
172160 dstFrame := resampleSize (w .dstRate , w .srcRate , len (data ))
173161 w .dstFrame = max (w .dstFrame , dstFrame )
174162 return w .flush (w .dstFrame * (1 + w .buffer ))
175163}
176164
177- func (w * resampleWriter ) Write (data []byte ) (int , error ) {
178- for i := range len (data ) / 2 {
179- v := int16 (binary .LittleEndian .Uint16 (data [i * 2 :]))
180- w .buf = append (w .buf , v )
165+ type soxrResampler struct {
166+ ptr C.soxr_t
167+ srcRate int
168+ dstRate int
169+ maxIn int
170+ done * atomic.Bool
171+ }
172+
173+ func soxrErr (e C.soxr_error_t ) error {
174+ if e == nil {
175+ return nil
176+ }
177+ defer C .free (unsafe .Pointer (e ))
178+ estr := C .GoString (e )
179+ switch estr {
180+ case "" , "0" :
181+ return nil
182+ }
183+ return errors .New (estr )
184+ }
185+
186+ func newSoxr (dstRate , srcRate int , quality int ) (* soxrResampler , error ) {
187+ ic := C .soxr_io_spec (C .SOXR_INT16_I , C .SOXR_INT16_I )
188+ qc := C .soxr_quality_spec (C .ulong (quality ), 0 )
189+ rc := C .soxr_runtime_spec (1 ) // 1 thread
190+ var e C.soxr_error_t
191+ p := C .soxr_create (C .double (srcRate ), C .double (dstRate ), 1 , & e , & ic , & qc , & rc )
192+ err := soxrErr (e )
193+ if err != nil {
194+ return nil , err
195+ }
196+ // This variable helps avoid double-free on the soxr resampler ptr. See soxrCleanup.
197+ done := new (atomic.Bool )
198+ r := & soxrResampler {
199+ ptr : p ,
200+ dstRate : dstRate ,
201+ srcRate : srcRate ,
202+ done : done ,
203+ }
204+ runtime .AddCleanup (r , func (p C.soxr_t ) {
205+ soxrCleanup (done , p )
206+ }, p )
207+ return r , nil
208+ }
209+
210+ func soxrCleanup (done * atomic.Bool , p C.soxr_t ) {
211+ if done .CompareAndSwap (false , true ) {
212+ C .soxr_delete (p )
213+ }
214+ }
215+
216+ func (r * soxrResampler ) Close () error {
217+ if r .ptr == nil {
218+ return nil
219+ }
220+ soxrCleanup (r .done , r .ptr )
221+ r .ptr = nil
222+ return nil
223+ }
224+
225+ func (r * soxrResampler ) Resample (out PCM16Sample , in PCM16Sample ) (PCM16Sample , int , error ) {
226+ if r .ptr == nil || r .done .Load () {
227+ return out , 0 , errors .New ("resampler is closed" )
228+ }
229+ r .maxIn = max (r .maxIn , len (in ))
230+ dstN := (len (in ) * r .dstRate ) / r .srcRate
231+ if dstN == 0 {
232+ dstN = max (
233+ (r .maxIn * r .dstRate )/ r .srcRate ,
234+ cap (out )- len (out ),
235+ 1024 ,
236+ )
237+ }
238+ // Make sure output has space for new samples. Length is still unchanged.
239+ out = slices .Grow (out , dstN )
240+ // Slice for the unused capacity, which we will write into.
241+ dst := out [len (out ) : len (out )+ dstN ]
242+ total := 0
243+ // Always call at least once (for flush to work), thus not considering len(in) here.
244+ for len (dst ) > 0 {
245+ var read , done C.size_t
246+ var e C.soxr_error_t
247+ if len (in ) != 0 {
248+ e = C .soxr_process (r .ptr , C .soxr_in_t (unsafe .Pointer (& in [0 ])), C .size_t (len (in )), & read , C .soxr_out_t (unsafe .Pointer (& dst [0 ])), C .size_t (len (dst )), & done )
249+ } else {
250+ // Flush, no input.
251+ e = C .soxr_process (r .ptr , nil , 0 , nil , C .soxr_out_t (unsafe .Pointer (& dst [0 ])), C .size_t (len (dst )), & done )
252+ read = 0
253+ }
254+ err := soxrErr (e )
255+ if err != nil {
256+ return out , 0 , err
257+ }
258+ total += int (done )
259+ dst = dst [done :]
260+ in = in [read :]
261+ if len (in ) == 0 {
262+ break
263+ }
181264 }
182- return len (data ), nil
265+ // Finally adjust the length to cover written data.
266+ out = out [:len (out )+ total ]
267+ return out , total , nil
183268}
0 commit comments