This commit is contained in:
2026-06-10 01:42:08 +02:00
parent 4f95e57a96
commit 5c719ea4a0
10 changed files with 681 additions and 72 deletions
+5 -5
View File
@@ -14,8 +14,7 @@ backends:
- libao
- libFLAC
- mpg123
- FFmpeg libraries, including libavutil, libavcodec, libavformat and
libswresample
- FFmpeg libraries, including libavutil, libavcodec and libavformat
For encoding, also install:
@@ -23,10 +22,10 @@ For encoding, also install:
- libopus
- libogg
- TagLib with the C binding, usually provided as `taglib` / `taglib_c`
- libsoxr, used by `resampler.rkt` and encoder-side PCM sample-rate conversion
The Opus encoder backend uses libopusenc directly. The FLAC encoder backend
uses libFLAC directly. FLAC sample-rate conversion uses the existing FFmpeg
swresample layer. Metadata and cover-art copying use the TagLib wrapper; the
uses libFLAC directly. FLAC sample-rate conversion uses `resampler.rkt`, backed by libsoxr. Metadata and cover-art copying use the TagLib wrapper; the
public `taglib.rkt` API also supports read-write tag editing.
@@ -43,7 +42,7 @@ sudo apt install \
ffmpeg \
libavutil-dev libavcodec-dev libavformat-dev libswresample-dev \
libogg-dev libopus-dev libopusenc-dev libopusfile-dev \
libtag1-dev
libsoxr-dev libtag1-dev
```
For Opus encoding, `libopusenc-dev` is required. For Opus decoding through
@@ -60,6 +59,7 @@ brew install mpg123
brew install ffmpeg
brew install opus
brew install libopusenc
brew install libsoxr
brew install taglib
```
+2 -1
View File
@@ -68,7 +68,8 @@
[else "libao"]))
(linux-lib-versions '("4" #f))))
(define-ffi-definer define-ao libao)
(define-ffi-definer define-ao libao
#:default-make-fail make-not-available)
(define _ao-device (_cpointer/null 'ao-device))
(define _ao-option (_cpointer/null 'ao-option))
+21 -58
View File
@@ -2,7 +2,7 @@
(require ffi/unsafe
"utils.rkt"
"../ffmpeg-definitions.rkt")
"../resampler.rkt")
(provide pcm-conversion-needed?
make-pcm-converter
@@ -15,7 +15,7 @@
(define S32-BYTES 4)
(define-struct pcm-converter (swr-ctx in-layout out-layout input-format output-format channels in-rate out-rate closed?)
(define-struct pcm-converter (resampler input-format output-format channels in-rate out-rate closed?)
#:mutable
#:constructor-name make-raw-pcm-converter)
@@ -32,7 +32,7 @@
(int-bytes->integer bs #t (system-big-endian?) start (+ start bytes)))
(define (native-signed-set! bs start bytes value)
(integer->integer-bytes value bytes #t (system-big-endian?) bs start))
(integer->int-bytes value bytes #t (system-big-endian?) bs start))
(define (clamp-s32 v)
(cond [(< v -2147483648) -2147483648]
@@ -55,21 +55,6 @@
(native-signed-set! out out-off S32-BYTES (expand-sample-to-s32 sample in-bits))))
out)]))
(define (bytes->native bs size)
(let ((p (malloc size 'atomic-interior)))
(memcpy p 0 bs 0 size)
p))
(define (native->bytes p size)
(let ((bs (make-bytes size)))
(memcpy bs 0 p 0 size)
bs))
(define (make-plane ptr)
(let ((planes (malloc _pointer 1 'atomic-interior)))
(ptr-set! planes _pointer 0 ptr)
planes))
(define (source-value v source)
(if (eq? v 'source) source v))
@@ -123,36 +108,26 @@
(channels-out (target-channels settings input-format))
(rate-in (hash-ref input-format 'sample-rate))
(rate-out (target-sample-rate settings input-format))
(in-layout (ffmpeg-make-default-channel-layout channels-in))
(out-layout (ffmpeg-make-default-channel-layout channels-out)))
(let-values (((ret ctx) (swr_alloc_set_opts2 #f
out-layout AV_SAMPLE_FMT_S32 rate-out
in-layout AV_SAMPLE_FMT_S32 rate-in
0 #f)))
(when (< ret 0) (error 'make-pcm-converter "swr_alloc_set_opts2 failed: ~a" ret))
(let ((ret-init (swr_init ctx)))
(when (< ret-init 0) (error 'make-pcm-converter "swr_init failed: ~a" ret-init))
(make-raw-pcm-converter ctx in-layout out-layout input-format (make-output-format input-format settings)
channels-out rate-in rate-out #f)))))
(quality (hash-ref/default settings 'resampler-quality 'hq))
(phase (hash-ref/default settings 'resampler-phase 'linear))
(steep? (hash-ref/default settings 'resampler-steep-filter? #f))
(out-format (make-output-format input-format settings)))
(unless (= channels-in channels-out)
(error 'make-pcm-converter
"SoXR PCM converter only supports unchanged channel count; got input ~a and output ~a"
channels-in channels-out))
(let ((r (make-resampler rate-in rate-out channels-in
#:input-format 's32
#:output-format 's32
#:quality quality
#:phase phase
#:steep-filter? steep?)))
(make-raw-pcm-converter r input-format out-format channels-out rate-in rate-out #f))))
(define (ensure-open! c who)
(when (or (not (pcm-converter? c)) (pcm-converter-closed? c))
(error who "PCM converter is closed")))
(define (convert* c in-bytes in-samples)
(let* ((channels (pcm-converter-channels c))
(max-out-samples (swr_get_out_samples (pcm-converter-swr-ctx c) in-samples)))
(cond [(<= max-out-samples 0) (values #"" 0)]
[else
(let* ((out-size (* max-out-samples channels S32-BYTES))
(out-ptr (malloc out-size 'atomic-interior))
(out-planes (make-plane out-ptr))
(in-ptr (and in-bytes (bytes->native in-bytes (bytes-length in-bytes))))
(in-planes (and in-ptr (make-plane in-ptr)))
(out-samples (swr_convert (pcm-converter-swr-ctx c) out-planes max-out-samples in-planes in-samples)))
(when (< out-samples 0) (error 'pcm-converter-convert "swr_convert failed: ~a" out-samples))
(values (native->bytes out-ptr (* out-samples channels S32-BYTES)) out-samples))])))
(define (pcm-converter-convert c buffer size buf-info)
(ensure-open! c 'pcm-converter-convert)
(let* ((in-bits (hash-ref/default buf-info 'bits-per-sample (hash-ref (pcm-converter-input-format c) 'bits-per-sample)))
@@ -160,27 +135,15 @@
(in-channels (hash-ref (pcm-converter-input-format c) 'channels))
(in-samples (quotient (quotient size in-bytes) in-channels))
(s32 (pcm-bytes->s32-bytes buffer size in-bits)))
(convert* c s32 in-samples)))
(resampler-convert (pcm-converter-resampler c) s32 (* in-samples in-channels S32-BYTES))))
(define (pcm-converter-drain c)
(ensure-open! c 'pcm-converter-drain)
(let* ((ctx (pcm-converter-swr-ctx c))
(delay (swr_get_delay ctx (pcm-converter-out-rate c)))
(channels (pcm-converter-channels c)))
(cond [(<= delay 0) (values #"" 0)]
[else
(let* ((out-size (* delay channels S32-BYTES))
(out-ptr (malloc out-size 'atomic-interior))
(out-planes (make-plane out-ptr))
(out-samples (swr_convert ctx out-planes delay #f 0)))
(when (< out-samples 0) (error 'pcm-converter-drain "swr_convert drain failed: ~a" out-samples))
(values (native->bytes out-ptr (* out-samples channels S32-BYTES)) out-samples))])))
(resampler-drain (pcm-converter-resampler c)))
(define (pcm-converter-close! c)
(when (and (pcm-converter? c) (not (pcm-converter-closed? c)))
(set-pcm-converter-swr-ctx! c (swr_free (pcm-converter-swr-ctx c)))
(ffmpeg-channel-layout-uninit! (pcm-converter-in-layout c))
(ffmpeg-channel-layout-uninit! (pcm-converter-out-layout c))
(resampler-close! (pcm-converter-resampler c))
(set-pcm-converter-closed?! c #t))
#t)
+6 -4
View File
@@ -178,14 +178,14 @@
(loop (+ n 1) (cons (number->string n) acc)))))
(define (ffmpeg-lib-versions kind)
(case (system-type 'os)
(case (system-type 'os*)
[(linux)
(let ((v (hash-ref valid-ffmpeg-versions kind)))
(versions-high-to-low (car v) (cadr v)))]
[else '(#f)]))
(define (linux-lib-versions versions [default '(#f)])
(case (system-type 'os)
(case (system-type 'os*)
[(linux) versions]
[else default]))
@@ -213,7 +213,7 @@
"
" libavcodec60 libavutil58 libswresample4 libavformat60 \
"
" libogg0 libopus0 libopusenc0 libopusfile0 libtag1v5
" libogg0 libopus0 libopusenc0 libopusfile0 libsoxr0 libtag1v5
"
"
"
@@ -223,7 +223,7 @@
"
"libavutil-dev, libswresample-dev, libavformat-dev, libogg-dev,
"
"libopus-dev, libopusenc-dev, libopusfile-dev and libtag1-dev.
"libopus-dev, libopusenc-dev, libopusfile-dev, libsoxr-dev and libtag1-dev.
"
"
"
@@ -244,6 +244,8 @@
" brew install opus
"
" brew install libopusenc
"
" brew install libsoxr
"
" brew install taglib
"
+285
View File
@@ -0,0 +1,285 @@
(module resampler racket/base
(require ffi/unsafe
"soxr-ffi.rkt"
"private/utils.rkt")
(provide resampler-available?
resampler-version
make-resampler
resampler?
resampler-input-rate
resampler-output-rate
resampler-channels
resampler-input-format
resampler-output-format
resampler-convert
resampler-drain
resampler-clear!
resampler-close!
resample-bytes
pcm-format?
pcm-format-sample-bytes
pcm-format->soxr-datatype
quality->soxr-recipe)
(define-struct resampler
(ctx input-rate output-rate channels input-format output-format
soxr-input-format soxr-output-format input-sample-bytes output-sample-bytes closed?)
#:mutable
#:constructor-name make-raw-resampler)
(define (resampler-available?) (soxr-available?))
(define (resampler-version) (soxr-version))
(define (normalize-format fmt who)
(cond [(or (eq? fmt 's16) (eq? fmt 'int16) (eq? fmt 'int16-interleaved)) 's16]
[(or (eq? fmt 's24) (eq? fmt 'int24) (eq? fmt 'int24-interleaved)) 's24]
[(or (eq? fmt 's32) (eq? fmt 'int32) (eq? fmt 'int32-interleaved)) 's32]
[(or (eq? fmt 'float32) (eq? fmt 'f32) (eq? fmt 'float)) 'float32]
[(or (eq? fmt 'float64) (eq? fmt 'f64) (eq? fmt 'double)) 'float64]
[else (raise-argument-error who "(or/c 's16 's24 's32 'float32 'float64)" fmt)]))
(define (pcm-format? fmt)
(with-handlers ([exn:fail? (lambda (e) #f)])
(normalize-format fmt 'pcm-format?)
#t))
(define (pcm-format-sample-bytes fmt)
(case (normalize-format fmt 'pcm-format-sample-bytes)
[(s16) 2]
[(s24) 3]
[(s32 float32) 4]
[(float64) 8]))
(define (pcm-format-actual-sample-bytes fmt)
;; SoXR has no packed 24-bit datatype. The wrapper expands s24 to s32
;; before processing and packs s32 back to s24 afterwards when requested.
(case (normalize-format fmt 'pcm-format-actual-sample-bytes)
[(s24) 4]
[else (pcm-format-sample-bytes fmt)]))
(define (pcm-format->soxr-datatype fmt)
(case (normalize-format fmt 'pcm-format->soxr-datatype)
[(s16) SOXR_INT16_I]
[(s24 s32) SOXR_INT32_I]
[(float32) SOXR_FLOAT32_I]
[(float64) SOXR_FLOAT64_I]))
(define (quality->soxr-recipe q)
;; The '*-bitq' recipes are resampling precision recipes; they do not mean
;; that the PCM output is 16/24/32-bit. Output format is controlled by
;; #:output-format.
(cond [(integer? q) q]
[(or (eq? q 'qq) (eq? q 'quick)) SOXR_QQ]
[(or (eq? q 'lq) (eq? q 'low)) SOXR_LQ]
[(or (eq? q 'mq) (eq? q 'medium)) SOXR_MQ]
[(or (eq? q 'hq) (eq? q 'high)) SOXR_HQ]
[(or (eq? q 'vhq) (eq? q 'very-high)) SOXR_VHQ]
[(eq? q '16-bit) SOXR_16_BITQ]
[(eq? q '20-bit) SOXR_20_BITQ]
[(eq? q '24-bit) SOXR_24_BITQ]
[(eq? q '28-bit) SOXR_28_BITQ]
[(eq? q '32-bit) SOXR_32_BITQ]
[else (raise-argument-error 'quality->soxr-recipe
"(or/c 'qq 'lq 'mq 'hq 'vhq '16-bit '20-bit '24-bit '28-bit '32-bit exact-integer?)" q)]))
(define (phase->flags phase)
(cond [(or (eq? phase 'linear) (eq? phase #f)) SOXR_LINEAR_PHASE]
[(or (eq? phase 'intermediate) (eq? phase 'medium)) SOXR_INTERMEDIATE_PHASE]
[(or (eq? phase 'minimum) (eq? phase 'min)) SOXR_MINIMUM_PHASE]
[else (raise-argument-error 'make-resampler "(or/c 'linear 'intermediate 'minimum)" phase)]))
(define (check-error who err)
(when err (error who "~a" err)))
(define (bytes->native bs size)
(let ((p (malloc size 'atomic-interior)))
(memcpy p 0 bs 0 size)
p))
(define (native->bytes p size)
(let ((bs (make-bytes size)))
(memcpy bs 0 p 0 size)
bs))
(define (native-signed-ref bs start bytes)
(int-bytes->integer bs #t (system-big-endian?) start (+ start bytes)))
(define (native-signed-set! bs start bytes value)
(integer->int-bytes value bytes #t (system-big-endian?) bs start))
(define (clamp-s32 v)
(cond [(< v -2147483648) -2147483648]
[(> v 2147483647) 2147483647]
[else v]))
(define (clamp-s24 v)
(cond [(< v -8388608) -8388608]
[(> v 8388607) 8388607]
[else v]))
(define (s24-bytes->s32-bytes buffer size)
(let* ((sample-count (quotient size 3))
(out (make-bytes (* sample-count 4))))
(for ([i (in-range sample-count)])
(let* ((in-off (* i 3))
(out-off (* i 4))
(sample (native-signed-ref buffer in-off 3)))
(native-signed-set! out out-off 4 (clamp-s32 (arithmetic-shift sample 8)))))
out))
(define (s32-bytes->s24-bytes buffer size)
(let* ((sample-count (quotient size 4))
(out (make-bytes (* sample-count 3))))
(for ([i (in-range sample-count)])
(let* ((in-off (* i 4))
(out-off (* i 3))
(sample (native-signed-ref buffer in-off 4)))
(native-signed-set! out out-off 3 (clamp-s24 (arithmetic-shift sample -8)))))
out))
(define (subbytes/size bs size)
(if (= size (bytes-length bs)) bs (subbytes bs 0 size)))
(define (prepare-input-bytes r buffer size)
(case (resampler-input-format r)
[(s24) (s24-bytes->s32-bytes buffer size)]
[else (subbytes/size buffer size)]))
(define (finish-output-bytes r buffer size)
(case (resampler-output-format r)
[(s24) (s32-bytes->s24-bytes buffer size)]
[else (subbytes/size buffer size)]))
(define (output-frame-capacity r in-frames)
(let* ((ratio (/ (resampler-output-rate r) (resampler-input-rate r)))
(delay (if (resampler-ctx r) (max 0.0 (soxr-delay (resampler-ctx r))) 0.0))
(n (ceiling (+ 256 delay (* in-frames ratio)))))
(max 256 (inexact->exact n))))
(define (make-resampler input-rate output-rate channels
#:input-format [input-format 's32]
#:output-format [output-format 's32]
#:quality [quality 'hq]
#:phase [phase 'linear]
#:steep-filter? [steep-filter? #f]
#:scale [scale 1.0]
#:no-dither? [no-dither? #f]
#:num-threads [num-threads 1])
(unless (resampler-available?)
(error 'make-resampler "libsoxr is not available"))
(unless (and (integer? channels) (> channels 0))
(raise-argument-error 'make-resampler "exact-positive-integer?" channels))
(let* ((in-fmt (normalize-format input-format 'make-resampler))
(out-fmt (normalize-format output-format 'make-resampler))
(itype (pcm-format->soxr-datatype in-fmt))
(otype (pcm-format->soxr-datatype out-fmt))
(io-flags (if no-dither? SOXR_NO_DITHER 0))
(quality-flags (bitwise-ior (phase->flags phase) (if steep-filter? SOXR_STEEP_FILTER 0)))
(io-spec (soxr-io-spec itype otype #:scale scale #:flags io-flags))
(quality-spec (soxr-quality-spec (quality->soxr-recipe quality) #:flags quality-flags))
(runtime-spec (soxr-runtime-spec #:num-threads num-threads)))
(let-values (((ctx err) (soxr-create input-rate output-rate channels
#:io-spec io-spec
#:quality-spec quality-spec
#:runtime-spec runtime-spec)))
(check-error 'make-resampler err)
(unless ctx (error 'make-resampler "libsoxr returned no resampler context"))
(make-raw-resampler ctx input-rate output-rate channels in-fmt out-fmt itype otype
(pcm-format-actual-sample-bytes in-fmt)
(pcm-format-actual-sample-bytes out-fmt)
#f))))
(define (ensure-open! r who)
(when (or (not (resampler? r)) (resampler-closed? r))
(error who "resampler is closed")))
(define (check-frame-size who size frame-bytes)
(unless (= (remainder size frame-bytes) 0)
(error who "buffer size ~a is not a whole number of interleaved frames of ~a bytes" size frame-bytes)))
(define (resampler-convert r buffer [size (bytes-length buffer)])
(ensure-open! r 'resampler-convert)
(let* ((nominal-in-frame-bytes (* (resampler-channels r) (pcm-format-sample-bytes (resampler-input-format r))))
(actual-in-frame-bytes (* (resampler-channels r) (resampler-input-sample-bytes r))))
(check-frame-size 'resampler-convert size nominal-in-frame-bytes)
(cond [(zero? size) (values #"" 0)]
[else
(let* ((in-frames (quotient size nominal-in-frame-bytes))
(prepared (prepare-input-bytes r buffer size))
(in-ptr0 (bytes->native prepared (bytes-length prepared))))
(let loop ((offset-frames 0) (remaining in-frames) (pieces '()) (total-frames 0))
(cond [(zero? remaining) (values (apply bytes-append (reverse pieces)) total-frames)]
[else
(let* ((out-cap (output-frame-capacity r remaining))
(out-size (* out-cap (resampler-channels r) (resampler-output-sample-bytes r)))
(out-ptr (malloc out-size 'atomic-interior))
(in-ptr (ptr-add in-ptr0 (* offset-frames actual-in-frame-bytes))))
(let-values (((err idone odone) (soxr-process (resampler-ctx r) in-ptr remaining out-ptr out-cap)))
(check-error 'resampler-convert err)
(when (and (> remaining 0) (zero? idone) (zero? odone))
(error 'resampler-convert "libsoxr made no progress"))
(let* ((raw-size (* odone (resampler-channels r) (resampler-output-sample-bytes r)))
(raw (native->bytes out-ptr raw-size))
(piece (finish-output-bytes r raw raw-size)))
(loop (+ offset-frames idone) (- remaining idone)
(if (zero? odone) pieces (cons piece pieces))
(+ total-frames odone)))))])))])))
(define (resampler-drain r)
(ensure-open! r 'resampler-drain)
(let loop ((pieces '()) (total-frames 0))
(let* ((delay (max 0.0 (soxr-delay (resampler-ctx r))))
(out-cap (max 4096 (inexact->exact (ceiling (+ delay 256)))))
(out-size (* out-cap (resampler-channels r) (resampler-output-sample-bytes r)))
(out-ptr (malloc out-size 'atomic-interior)))
(let-values (((err idone odone) (soxr-process (resampler-ctx r) #f 0 out-ptr out-cap)))
(check-error 'resampler-drain err)
(cond [(zero? odone) (values (apply bytes-append (reverse pieces)) total-frames)]
[else
(let* ((raw-size (* odone (resampler-channels r) (resampler-output-sample-bytes r)))
(raw (native->bytes out-ptr raw-size))
(piece (finish-output-bytes r raw raw-size)))
(loop (cons piece pieces) (+ total-frames odone)))])))))
(define (resampler-clear! r)
(ensure-open! r 'resampler-clear!)
(check-error 'resampler-clear! (soxr-clear (resampler-ctx r)))
#t)
(define (resampler-close! r)
(when (and (resampler? r) (not (resampler-closed? r)))
(soxr-delete (resampler-ctx r))
(set-resampler-ctx! r #f)
(set-resampler-closed?! r #t))
#t)
(define (resample-bytes buffer input-rate output-rate channels
#:size [size (bytes-length buffer)]
#:input-format [input-format 's32]
#:output-format [output-format 's32]
#:quality [quality 'hq]
#:phase [phase 'linear]
#:steep-filter? [steep-filter? #f]
#:scale [scale 1.0]
#:no-dither? [no-dither? #f]
#:num-threads [num-threads 1])
(let ((r (make-resampler input-rate output-rate channels
#:input-format input-format
#:output-format output-format
#:quality quality
#:phase phase
#:steep-filter? steep-filter?
#:scale scale
#:no-dither? no-dither?
#:num-threads num-threads)))
(dynamic-wind
void
(lambda ()
(let-values (((a af) (resampler-convert r buffer size)))
(let-values (((b bf) (resampler-drain r)))
(values (bytes-append a b) (+ af bf)))))
(lambda () (resampler-close! r)))))
) ; end of module
+4 -3
View File
@@ -166,9 +166,10 @@ The first backend version supports mono and stereo input.
The FLAC backend uses the @tt{libFLAC} stream encoder. It writes interleaved
integer PCM samples through the FLAC encoder API. When the requested output
format differs from the decoded input format, @racketmodname[racket-audio/private/pcm-converter]
uses the existing FFmpeg @tt{swresample} layer from
@racketmodname[racket-audio/ffmpeg-definitions] to perform PCM normalisation.
sample rate differs from the decoded input format, @racketmodname[racket-audio/private/pcm-converter]
uses @racketmodname[racket-audio/resampler], backed by @tt{libsoxr}, to perform
PCM sample-rate conversion. Channel-count conversion is not handled by this
SoXR path; keep the source channel count for encoder-side conversion.
The following settings are recognised:
+1
View File
@@ -22,6 +22,7 @@
@include-section["audio-sniffer.scrbl"]
@include-section["taglib.scrbl"]
@include-section["audio-encoder.scrbl"]
@include-section["resampler.scrbl"]
@include-section["encoder-test.scrbl"]
@include-section["play-test.scrbl"]
@include-section["audio-placed-player.scrbl"]
+101
View File
@@ -0,0 +1,101 @@
#lang scribble/manual
@title{PCM resampling with SoXR}
@defmodule[racket-audio/resampler]
This module provides a small Racket wrapper around @tt{libsoxr}. It is meant
for interleaved PCM buffers such as stereo @tt{L R L R ...} data. It supports
sample-rate conversion and the PCM datatypes that SoXR can process directly:
@racket['s16], @racket['s32], @racket['float32] and @racket['float64]. Packed
24-bit PCM is supported by the wrapper by expanding @racket['s24] input to
@racket['s32] before calling SoXR and packing @racket['s32] output back to
@racket['s24] afterwards.
SoXR does not perform general channel-layout conversion in this wrapper. Use it
for unchanged channel counts, for example mono-to-mono or stereo-to-stereo.
@defproc[(resampler-available?) boolean?]{
Returns @racket[#t] when @tt{libsoxr} was loaded.
}
@defproc[(resampler-version) string?]{
Returns the version string reported by @tt{libsoxr}.
}
@defproc[(make-resampler
[input-rate exact-positive-integer?]
[output-rate exact-positive-integer?]
[channels exact-positive-integer?]
[#:input-format input-format symbol? 's32]
[#:output-format output-format symbol? 's32]
[#:quality quality symbol? 'hq]
[#:phase phase symbol? 'linear]
[#:steep-filter? steep-filter? boolean? #f]
[#:scale scale real? 1.0]
[#:no-dither? no-dither? boolean? #f]
[#:num-threads num-threads exact-positive-integer? 1])
resampler?]{
Creates a streaming resampler. The supported PCM formats are @racket['s16],
@racket['s24], @racket['s32], @racket['float32] and @racket['float64].
The quality value selects a SoXR resampling recipe. Useful values are
@racket['qq], @racket['lq], @racket['mq], @racket['hq], @racket['vhq],
@racket['16-bit], @racket['20-bit], @racket['24-bit], @racket['28-bit] and
@racket['32-bit]. The @racket['24-bit] quality recipe is a filter precision
setting; it is not the same thing as 24-bit PCM output. Use
@racket[#:output-format 's24] for packed 24-bit output.
}
@defproc[(resampler-convert
[r resampler?]
[buffer bytes?]
[size exact-nonnegative-integer? (bytes-length buffer)])
(values bytes? exact-nonnegative-integer?)]{
Feeds interleaved PCM bytes to the resampler and returns converted PCM bytes and
the number of output frames. A frame contains one sample for each channel.
}
@defproc[(resampler-drain [r resampler?])
(values bytes? exact-nonnegative-integer?)]{
Flushes delayed output after the last input block and returns converted bytes and
frames.
}
@defproc[(resampler-clear! [r resampler?]) boolean?]{
Clears the resampler state so the same instance can be reused for a fresh signal
with the same configuration.
}
@defproc[(resampler-close! [r resampler?]) boolean?]{
Releases the native SoXR resampler. Calling this more than once is harmless.
}
@defproc[(resample-bytes
[buffer bytes?]
[input-rate exact-positive-integer?]
[output-rate exact-positive-integer?]
[channels exact-positive-integer?]
[#:size size exact-nonnegative-integer? (bytes-length buffer)]
[#:input-format input-format symbol? 's32]
[#:output-format output-format symbol? 's32]
[#:quality quality symbol? 'hq]
[#:phase phase symbol? 'linear]
[#:steep-filter? steep-filter? boolean? #f]
[#:scale scale real? 1.0]
[#:no-dither? no-dither? boolean? #f]
[#:num-threads num-threads exact-positive-integer? 1])
(values bytes? exact-nonnegative-integer?)]{
Convenience function for one buffer. It creates a resampler, processes the
input, drains delayed samples and closes the native state.
}
@defproc[(pcm-format? [v any/c]) boolean?]{
Returns whether @racket[v] is one of the supported PCM format symbols.
}
@defproc[(pcm-format-sample-bytes [fmt symbol?]) exact-positive-integer?]{
Returns the packed sample size in bytes for @racket[fmt]. For @racket['s24]
this is @racket[3], even though the wrapper internally expands to 32-bit samples
for SoXR.
}
+255
View File
@@ -0,0 +1,255 @@
(module soxr-ffi racket/base
(require ffi/unsafe
ffi/unsafe/define
"private/utils.rkt")
(provide soxr-available?
soxr-version
soxr-create
soxr-process
soxr-delay
soxr-clear
soxr-delete
soxr-error
soxr-engine
soxr-io-spec
soxr-quality-spec
soxr-runtime-spec
soxr-error-ptr->string
soxr-datatype-size
_soxr_t
_soxr_io_spec
_soxr_io_spec-pointer
soxr_io_spec?
soxr_io_spec-itype
soxr_io_spec-otype
soxr_io_spec-scale
soxr_io_spec-flags
set-soxr_io_spec-scale!
set-soxr_io_spec-flags!
_soxr_quality_spec
_soxr_quality_spec-pointer
soxr_quality_spec?
soxr_quality_spec-precision
soxr_quality_spec-phase_response
soxr_quality_spec-passband_end
soxr_quality_spec-stopband_begin
soxr_quality_spec-flags
set-soxr_quality_spec-precision!
set-soxr_quality_spec-phase_response!
set-soxr_quality_spec-passband_end!
set-soxr_quality_spec-stopband_begin!
set-soxr_quality_spec-flags!
_soxr_runtime_spec
_soxr_runtime_spec-pointer
soxr_runtime_spec?
soxr_runtime_spec-log2_min_dft_size
soxr_runtime_spec-log2_large_dft_size
soxr_runtime_spec-coef_size_kbytes
soxr_runtime_spec-num_threads
soxr_runtime_spec-flags
set-soxr_runtime_spec-num_threads!
set-soxr_runtime_spec-flags!
SOXR_FLOAT32
SOXR_FLOAT64
SOXR_INT32
SOXR_INT16
SOXR_SPLIT
SOXR_FLOAT32_I
SOXR_FLOAT64_I
SOXR_INT32_I
SOXR_INT16_I
SOXR_FLOAT32_S
SOXR_FLOAT64_S
SOXR_INT32_S
SOXR_INT16_S
SOXR_TPDF
SOXR_NO_DITHER
SOXR_QQ
SOXR_LQ
SOXR_MQ
SOXR_HQ
SOXR_VHQ
SOXR_16_BITQ
SOXR_20_BITQ
SOXR_24_BITQ
SOXR_28_BITQ
SOXR_32_BITQ
SOXR_LINEAR_PHASE
SOXR_INTERMEDIATE_PHASE
SOXR_MINIMUM_PHASE
SOXR_STEEP_FILTER
SOXR_ROLLOFF_SMALL
SOXR_ROLLOFF_MEDIUM
SOXR_ROLLOFF_NONE
SOXR_HI_PREC_CLOCK
SOXR_DOUBLE_PRECISION
SOXR_VR
SOXR_COEF_INTERP_AUTO
SOXR_COEF_INTERP_LOW
SOXR_COEF_INTERP_HIGH)
;; libsoxr is normally libsoxr.so.0 on Linux and libsoxr.dylib on macOS.
;; The Windows build supplied with racket-audio may be either soxr.dll or
;; libsoxr.dll, so both stems are tried through the existing get-lib helper.
(define libsoxr
(get-lib (case (system-type 'os)
[(windows) '("soxr" "libsoxr")]
[else '("soxr" "libsoxr")])
(case (system-type 'os*)
[(linux) '("0" #f)]
[else '(#f)])))
(define-ffi-definer def-soxr libsoxr #:default-make-fail make-not-available)
(define _soxr_t (_cpointer/null 'soxr))
;; soxr.h structs. These are passed by pointer to soxr_create, while the
;; constructor functions in the C API return initialized structs by value.
(define-cstruct _soxr_io_spec
([itype _int]
[otype _int]
[scale _double]
[e _pointer]
[flags _ulong]))
(define-cstruct _soxr_quality_spec
([precision _double]
[phase_response _double]
[passband_end _double]
[stopband_begin _double]
[e _pointer]
[flags _ulong]))
(define-cstruct _soxr_runtime_spec
([log2_min_dft_size _uint]
[log2_large_dft_size _uint]
[coef_size_kbytes _uint]
[num_threads _uint]
[e _pointer]
[flags _ulong]))
;; Datatypes supported by libsoxr. The *_I values are channel-interleaved;
;; those are what resampler.rkt uses for normal PCM byte strings.
(define SOXR_FLOAT32 0)
(define SOXR_FLOAT64 1)
(define SOXR_INT32 2)
(define SOXR_INT16 3)
(define SOXR_SPLIT 4)
(define SOXR_FLOAT32_I SOXR_FLOAT32)
(define SOXR_FLOAT64_I SOXR_FLOAT64)
(define SOXR_INT32_I SOXR_INT32)
(define SOXR_INT16_I SOXR_INT16)
(define SOXR_FLOAT32_S SOXR_SPLIT)
(define SOXR_FLOAT64_S 5)
(define SOXR_INT32_S 6)
(define SOXR_INT16_S 7)
(define SOXR_TPDF 0)
(define SOXR_NO_DITHER 8)
(define SOXR_QQ 0)
(define SOXR_LQ 1)
(define SOXR_MQ 2)
(define SOXR_16_BITQ 3)
(define SOXR_20_BITQ 4)
(define SOXR_24_BITQ 5)
(define SOXR_28_BITQ 6)
(define SOXR_32_BITQ 7)
(define SOXR_HQ SOXR_20_BITQ)
(define SOXR_VHQ SOXR_28_BITQ)
(define SOXR_LINEAR_PHASE #x00)
(define SOXR_INTERMEDIATE_PHASE #x10)
(define SOXR_MINIMUM_PHASE #x30)
(define SOXR_STEEP_FILTER #x40)
(define SOXR_ROLLOFF_SMALL 0)
(define SOXR_ROLLOFF_MEDIUM 1)
(define SOXR_ROLLOFF_NONE 2)
(define SOXR_HI_PREC_CLOCK 8)
(define SOXR_DOUBLE_PRECISION 16)
(define SOXR_VR 32)
(define SOXR_COEF_INTERP_AUTO 0)
(define SOXR_COEF_INTERP_LOW 2)
(define SOXR_COEF_INTERP_HIGH 3)
(define (soxr-datatype-size t)
(case (bitwise-and t 3)
[(0) 4]
[(1) 8]
[(2) 4]
[(3) 2]
[else (error 'soxr-datatype-size "invalid datatype ~a" t)]))
(define (soxr-error-ptr->string p)
(and p (cast p _pointer _string/utf-8)))
(def-soxr soxr_version/raw (_fun -> _string/utf-8) #:c-id soxr_version)
(def-soxr soxr_io_spec/raw (_fun _int _int -> _soxr_io_spec) #:c-id soxr_io_spec)
(def-soxr soxr_quality_spec/raw (_fun _ulong _ulong -> _soxr_quality_spec) #:c-id soxr_quality_spec)
(def-soxr soxr_runtime_spec/raw (_fun _uint -> _soxr_runtime_spec) #:c-id soxr_runtime_spec)
(def-soxr soxr_create/raw
(_fun _double _double _uint
(errp : (_ptr o _pointer))
_soxr_io_spec-pointer/null
_soxr_quality_spec-pointer/null
_soxr_runtime_spec-pointer/null
-> (ctx : _soxr_t)
-> (values ctx errp))
#:c-id soxr_create)
(def-soxr soxr_process/raw
(_fun _soxr_t
_pointer _size (idone : (_ptr o _size))
_pointer _size (odone : (_ptr o _size))
-> (err : _pointer)
-> (values err idone odone))
#:c-id soxr_process)
(def-soxr soxr-delay (_fun _soxr_t -> _double) #:c-id soxr_delay)
(def-soxr soxr-clear (_fun _soxr_t -> (err : _pointer) -> (soxr-error-ptr->string err)) #:c-id soxr_clear)
(def-soxr soxr-error (_fun _soxr_t -> (err : _pointer) -> (soxr-error-ptr->string err)) #:c-id soxr_error)
(def-soxr soxr-engine (_fun _soxr_t -> _string/utf-8) #:c-id soxr_engine)
(def-soxr soxr-delete (_fun _soxr_t -> _void) #:c-id soxr_delete)
(define (soxr-available?) (and libsoxr #t))
(define (soxr-version)
(soxr_version/raw))
(define (soxr-io-spec itype otype #:scale [scale 1.0] #:flags [flags 0])
(let ((s (soxr_io_spec/raw itype otype)))
(set-soxr_io_spec-scale! s scale)
(set-soxr_io_spec-flags! s flags)
s))
(define (soxr-quality-spec recipe #:flags [flags 0])
(soxr_quality_spec/raw recipe flags))
(define (soxr-runtime-spec #:num-threads [num-threads 1])
(soxr_runtime_spec/raw num-threads))
(define (soxr-create input-rate output-rate channels #:io-spec [io-spec #f]
#:quality-spec [quality-spec #f] #:runtime-spec [runtime-spec #f])
(let-values (((ctx err) (soxr_create/raw (exact->inexact input-rate)
(exact->inexact output-rate)
channels
io-spec
quality-spec
runtime-spec)))
(values ctx (soxr-error-ptr->string err))))
(define (soxr-process ctx in ilen out olen)
(let-values (((err idone odone) (soxr_process/raw ctx in ilen out olen)))
(values (soxr-error-ptr->string err) idone odone)))
) ; end of module
+1 -1
View File
@@ -60,7 +60,7 @@
(define zlib (get-lib '("zlib" "libz") (linux-lib-versions '("1" #f))))
(define libtag (get-lib '("tag" "libtag") (linux-lib-versions '("2" #f) '("2" #f))))
(define libtag_c (get-lib '("tag_c" "libtag_c") (linux-lib-versions '("2" #f) '("#2" #f))))
(define libtag_c (get-lib '("tag_c" "libtag_c") (linux-lib-versions '("2" #f) '("2" #f))))
(define-ffi-definer define-tag-c-lib libtag_c
#:default-make-fail make-not-available)