diff --git a/audio-decoder.rkt b/audio-decoder.rkt index f92c3d8..49c510f 100644 --- a/audio-decoder.rkt +++ b/audio-decoder.rkt @@ -2,6 +2,9 @@ (require "flac-decoder.rkt" "mp3-decoder.rkt" + "ffmpeg-decoder.rkt" + "audio-sniffer.rkt" + "private/utils.rkt" racket/contract racket/string racket/path @@ -52,12 +55,38 @@ mp3-stop 'ao)) + ;; FFmpeg decodere + (hash-set! audio-readers + 'ffmpeg + (make-audio-reader '("ogg" "oga" "opus" + "m4a" "mp4" "m4b" + "aac" + "wav" + "aiff" "aif" "aifc" + "wma" + "webm" "mkv" "mka") + ffmpeg-valid? + ffmpeg-open + ffmpeg-read + ffmpeg-seek + ffmpeg-stop + 'ao)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Known extensions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define known-extensions - '("flac" "mp3")) + '("flac" ; FLAC decoder + "mp3" ; mp3 decoder + "ogg" "oga" "opus" + "m4a" "mp4" "m4b" + "aac" + "wav" + "aiff" "aif" "aifc" + "wma" + "webm" "mkv" "mka" ; FFMPEG decoder + )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Register audio reader @@ -92,7 +121,11 @@ (set! ext (format "~a" ext)) (when (string-prefix? ext ".") (set! ext (substring ext 1))) - (not (null? (filter (λ (e) (string-ci=? ext e)) known-extensions))) + (if (not (null? (filter (λ (e) (string-ci=? ext e)) known-extensions))) + #t + (begin + (warn-sound "extension '~a' not in known-extensions '~a'" ext known-extensions) + #f)) ) (define/contract (audio-file-valid? file) @@ -189,17 +222,33 @@ (define (right-reader? reader ext) (not (null? (filter (λ (e) (string-ci=? ext e)) (audio-reader-exts reader))))) + (define reader-for-kind + (make-hash '((mp3 . ffmpeg) ; ffmpeg does a better job on gapless playback... + (flac . flac) + (ogg . ffmpeg) + (vorbis . ffmpeg) + (opus . ffmpeg) + (wav . ffmpeg) + (aiff . ffmpeg) + (mp4 . ffmpeg) + (aac . ffmpeg) + (alac . ffmpeg) + (ac3 . ffmpeg) + (ape . ffmpeg) + (wavpack . ffmpeg) + (wma . ffmpeg) + (matroska . ffmpeg)))) + + (define (find-reader audio-file) - (let* ((f (build-path audio-file)) - (ext (substring (format "~a" (path-get-extension audio-file)) 1))) - (letrec ((f (λ (keys) - (if (null? keys) - #f - (let ((reader (hash-ref audio-readers (car keys)))) - (if (right-reader? reader ext) - (list (car keys) reader) - (f (cdr keys)))))))) - (f (hash-keys audio-readers)) + ; First try to sniff the format + (let ((format (audio-sniff-format/extension audio-file))) + (let ((reader-kind (hash-ref reader-for-kind format #f))) + (if (eq? reader-kind #f) + #f + (let ((reader (hash-ref audio-readers reader-kind))) + (list reader-kind reader)) + ) ) ) ) diff --git a/audio-sniffer.rkt b/audio-sniffer.rkt index 250dfe8..589c424 100644 --- a/audio-sniffer.rkt +++ b/audio-sniffer.rkt @@ -2,7 +2,9 @@ (require racket/contract racket/path - racket/string) + racket/string + racket/runtime-path + ) (provide audio-format? audio-sniff-format @@ -333,4 +335,24 @@ (define/contract (audio-format-matches? file formats) (-> path-string? (listof symbol?) boolean?) - (audio-format-matches?* file formats)) \ No newline at end of file + (audio-format-matches?* file formats)) + +(define-runtime-path audio-tests "tests") + +(define (sniff-test) + (for-each (λ (ext) + (let* ((dir (build-path audio-tests)) + (ext* (format ".~a" ext)) + (files (map (λ (f) + (build-path audio-tests f)) + (filter (λ (f) + (string-suffix? (format "~a" f) ext*)) + (directory-list dir)))) + ) + (for-each (λ (f) + (displayln (format "~a: ~a" f (audio-sniff-format/extension f)))) + files) + ) + ) + '(aac adts flac mp3 ogg ts ac3 aiff m4a mp4 ogx wav wv mp2)) + ) diff --git a/ffmpeg-decoder.rkt b/ffmpeg-decoder.rkt index aa05db3..b0229e0 100644 --- a/ffmpeg-decoder.rkt +++ b/ffmpeg-decoder.rkt @@ -1,7 +1,7 @@ (module ffmpeg_decoder racket/base (require ffi/unsafe - "ffmpeg_ffi.rkt" + "ffmpeg-ffi.rkt" "private/utils.rkt" (prefix-in fin: finalizer) ) diff --git a/ffmpeg-ffi.rkt b/ffmpeg-ffi.rkt index 3d9f528..b44cf63 100644 --- a/ffmpeg-ffi.rkt +++ b/ffmpeg-ffi.rkt @@ -118,6 +118,16 @@ (define-ffmpeg-audio fmpg_timecode (_fun _fmpg_instance -> _double)) + (define-ffmpeg-audio fmpg_ffmpeg_version + (_fun -> _string*/utf-8)) + + (define-ffmpeg-audio fmpg_int_version2string + (_fun _int -> _string*/utf-8)) + + (define-ffmpeg-audio fmpg_compatible_ffmpeg + (_fun -> _int)) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Our interface for decoding to racket ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/libao-async-ffi.rkt b/libao-async-ffi.rkt index ae0a295..543e49c 100644 --- a/libao-async-ffi.rkt +++ b/libao-async-ffi.rkt @@ -8,6 +8,7 @@ ) (provide ao_create_async + ao_real_output_bits_async ao_stop_async ao_play_async ao_is_at_music_id_async @@ -66,6 +67,10 @@ ;extern void *ao_create_async(int bits, int rate, int channel, int byte_format); (define-libao-async ao_create_async(_fun _int _int _int _Endian_t _string/utf-8 -> _libao-async-handle-pointer)) +;extern int ao_real_output_bits(void *handle) +(define-libao-async ao_real_output_bits_async + (_fun _libao-async-handle-pointer -> _int)) + ;extern void ao_stop_async(void *handle); (define-libao-async ao_stop_async(_fun _libao-async-handle-pointer -> _void)) diff --git a/libao.rkt b/libao.rkt index c2f15d0..4a65671 100644 --- a/libao.rkt +++ b/libao.rkt @@ -10,6 +10,7 @@ ) (provide ao-open-live + ao-device-bits ao-open-file ao-play ao-close @@ -35,6 +36,8 @@ (define-struct ao-handle (handle-num [bits #:auto #:mutable] [bytes-per-sample #:auto #:mutable] + [dev-bits #:auto #:mutable] + [dev-bytes-per-sample #:auto #:mutable] [byte-format #:auto #:mutable] [channels #:auto #:mutable] [rate #:auto #:mutable] @@ -113,15 +116,17 @@ (err-sound "ao-open-live - cannote create player") (set-ao-handle-closed! handle #t) handle) - (begin - (info-sound "ao-open-live - created player") + (let ((out-bits (ffi:ao_real_output_bits_async player))) + (info-sound "ao-open-live - created player at ~a bits" out-bits) + (set-ao-handle-dev-bits! handle out-bits) + (set-ao-handle-dev-bytes-per-sample! handle (/ out-bits 8)) (set-ao-handle-closed! handle #f) handle ) ) ) ) - ) + ) (rc:define/contract (ao-close handle) @@ -143,6 +148,10 @@ (define (any? x) #t) +(rc:define/contract (ao-device-bits handle) + (rc:-> ao-handle? integer?) + (ao-handle-dev-bits handle)) + (rc:define/contract (ao-play handle music-id at-time-in-s music-duration-s buffer buf-len buf-type) (rc:-> ao-handle? integer? number? number? any? integer? ao-supported-music-format? void?) (let* ((bytes-per-sample (ao-handle-bytes-per-sample handle)) diff --git a/play-test.rkt b/play-test.rkt index cd3b633..79db44d 100644 --- a/play-test.rkt +++ b/play-test.rkt @@ -4,10 +4,13 @@ simple-log "private/utils.rkt" racket-sprintf + racket/runtime-path ;data/queue ;racket-sound ) +(define-runtime-path tests "tests") + (define test-file3 #f) (define test-file4 #f) (define test-file3-id 3) @@ -15,8 +18,9 @@ (let ((os (system-type 'os))) (when (eq? os 'unix) ;(set! test-file3 "/muziek/Klassiek-Viool/Alina Ibragimova/Paganini_24 Caprices (2021)/24. 24 Caprices, Op 1 - No. 24 in A minor- Tema con variazioni. Quasi presto.flac") - (set! test-file3 "/tmp/test.mp3") - (set! test-file4 "/tmp/test1.mp3") + ;(set! test-file3 "/tmp/test.mp3") + (set! test-file3 (build-path tests "mahler-1.mp3")) + (set! test-file4 (build-path tests "mahler-2.mp3")) ) (when (eq? os 'windows) ;(set! test-file3 "C:\\Muziek\\Klassiek-Strijkkwartet\\Quatuor Zaïde\\Franz\\01 Erlkönig, D. 328 (Arr. For String Quartet by Eric Mouret).flac") @@ -34,6 +38,10 @@ (define current-file-id -1) (define current-audio-h #f) +(define current-bits -1) +(define current-rate -1) +(define current-channels -1) + (sl-log-to-display) (define wav-output-file #f) (define seeked #f) @@ -51,12 +59,43 @@ (when (= (round current-seconds) 10) (when (and (= current-file-id 3) (not seeked)) (set! seeked #t) - (audio-seek current-audio-h 97.0))))) + (let ((perc (exact->inexact (* (/ (- duration 15) duration) 100.0)))) + (info-sound "Seeking to ~a%" perc) + (audio-seek current-audio-h perc)))))) ) + + (when (not (eq? ao-h #f)) + (when (not (and + (= current-bits bits-per-sample) + (= current-rate rate) + (= current-channels channels))) + (ao-close ao-h) + (set! ao-h #f))) + ;(displayln buf-info) (when (eq? ao-h #f) - (set! ao-h (ao-open-file bits-per-sample rate channels 'native-endian wav-output-file))) + + (info-sound "Opening ao handle") + (info-sound "bits-per-sample: ~a" bits-per-sample) + (info-sound "rate : ~a" rate) + (info-sound "channels : ~a" channels) + (info-sound "endian : ~a" 'native-endian) + (info-sound "(optional) file: ~a" wav-output-file) + (sync-log-sound) + + (set! ao-h (ao-open-file bits-per-sample rate channels 'native-endian wav-output-file)) + + (set! current-bits bits-per-sample) + (set! current-rate rate) + (set! current-channels channels) + (info-sound "ao bits per sample: ~a" (ao-device-bits ao-h)) + (sync-log-sound) + ) + ;(displayln 'ao-play) + ;(dbg-sound "Playing audio at ~a" second) + ;(sync-log-sound) + (ao-play ao-h current-file-id second duration buffer buf-len ao-type) (set! duration (inexact->exact (round duration))) ;(displayln 'done) diff --git a/private/utils.rkt b/private/utils.rkt index cc6c946..1315e6d 100644 --- a/private/utils.rkt +++ b/private/utils.rkt @@ -18,6 +18,7 @@ err-sound warn-sound fatal-sound + sync-log-sound ) (sl-def-log racket-sound sound) diff --git a/tests/ff-16b-2c-44100hz.aac b/tests/ff-16b-2c-44100hz.aac new file mode 100644 index 0000000..22871a8 Binary files /dev/null and b/tests/ff-16b-2c-44100hz.aac differ diff --git a/tests/ff-16b-2c-44100hz.ac3 b/tests/ff-16b-2c-44100hz.ac3 new file mode 100644 index 0000000..1d4d43f Binary files /dev/null and b/tests/ff-16b-2c-44100hz.ac3 differ diff --git a/tests/ff-16b-2c-44100hz.adts b/tests/ff-16b-2c-44100hz.adts new file mode 100644 index 0000000..22871a8 Binary files /dev/null and b/tests/ff-16b-2c-44100hz.adts differ diff --git a/tests/ff-16b-2c-44100hz.aiff b/tests/ff-16b-2c-44100hz.aiff new file mode 100644 index 0000000..4c6e073 Binary files /dev/null and b/tests/ff-16b-2c-44100hz.aiff differ diff --git a/tests/ff-16b-2c-44100hz.flac b/tests/ff-16b-2c-44100hz.flac new file mode 100644 index 0000000..00dcdd7 Binary files /dev/null and b/tests/ff-16b-2c-44100hz.flac differ diff --git a/tests/ff-16b-2c-44100hz.m4a b/tests/ff-16b-2c-44100hz.m4a new file mode 100644 index 0000000..dafd820 Binary files /dev/null and b/tests/ff-16b-2c-44100hz.m4a differ diff --git a/tests/ff-16b-2c-44100hz.mp3 b/tests/ff-16b-2c-44100hz.mp3 new file mode 100644 index 0000000..a9a07f2 Binary files /dev/null and b/tests/ff-16b-2c-44100hz.mp3 differ diff --git a/tests/ff-16b-2c-44100hz.mp4 b/tests/ff-16b-2c-44100hz.mp4 new file mode 100644 index 0000000..8d33cd4 Binary files /dev/null and b/tests/ff-16b-2c-44100hz.mp4 differ diff --git a/tests/ff-16b-2c-44100hz.ogg b/tests/ff-16b-2c-44100hz.ogg new file mode 100644 index 0000000..ab646b5 Binary files /dev/null and b/tests/ff-16b-2c-44100hz.ogg differ diff --git a/tests/ff-16b-2c-44100hz.ogx b/tests/ff-16b-2c-44100hz.ogx new file mode 100644 index 0000000..dbc690d Binary files /dev/null and b/tests/ff-16b-2c-44100hz.ogx differ diff --git a/tests/ff-16b-2c-44100hz.ts b/tests/ff-16b-2c-44100hz.ts new file mode 100644 index 0000000..605331b Binary files /dev/null and b/tests/ff-16b-2c-44100hz.ts differ diff --git a/tests/ff-16b-2c-44100hz.wav b/tests/ff-16b-2c-44100hz.wav new file mode 100644 index 0000000..7e7e608 Binary files /dev/null and b/tests/ff-16b-2c-44100hz.wav differ diff --git a/tests/idyll.flac b/tests/idyll.flac new file mode 100755 index 0000000..3bb1b27 Binary files /dev/null and b/tests/idyll.flac differ diff --git a/tests/idyll.m4a b/tests/idyll.m4a new file mode 100755 index 0000000..7b265e9 Binary files /dev/null and b/tests/idyll.m4a differ diff --git a/tests/idyll.mp3 b/tests/idyll.mp3 new file mode 100755 index 0000000..0450279 Binary files /dev/null and b/tests/idyll.mp3 differ diff --git a/tests/idyll.ogg b/tests/idyll.ogg new file mode 100755 index 0000000..39b5a2a Binary files /dev/null and b/tests/idyll.ogg differ diff --git a/tests/idyll.opus b/tests/idyll.opus new file mode 100755 index 0000000..52d4d42 Binary files /dev/null and b/tests/idyll.opus differ diff --git a/tests/mahler-1.mp3 b/tests/mahler-1.mp3 new file mode 100755 index 0000000..bfa1de3 Binary files /dev/null and b/tests/mahler-1.mp3 differ diff --git a/tests/mahler-1.ogg b/tests/mahler-1.ogg new file mode 100755 index 0000000..653cc36 Binary files /dev/null and b/tests/mahler-1.ogg differ diff --git a/tests/mahler-1.opus b/tests/mahler-1.opus new file mode 100755 index 0000000..94ffdcb Binary files /dev/null and b/tests/mahler-1.opus differ diff --git a/tests/mahler-2.mp3 b/tests/mahler-2.mp3 new file mode 100755 index 0000000..86a0b6f Binary files /dev/null and b/tests/mahler-2.mp3 differ diff --git a/tests/mahler-2.ogg b/tests/mahler-2.ogg new file mode 100755 index 0000000..89b8cb9 Binary files /dev/null and b/tests/mahler-2.ogg differ diff --git a/tests/mahler-2.opus b/tests/mahler-2.opus new file mode 100755 index 0000000..068e72b Binary files /dev/null and b/tests/mahler-2.opus differ diff --git a/tests/sample4.mp2 b/tests/sample4.mp2 new file mode 100644 index 0000000..8e675c9 Binary files /dev/null and b/tests/sample4.mp2 differ diff --git a/tests/sample4.wv b/tests/sample4.wv new file mode 100644 index 0000000..f64edf2 Binary files /dev/null and b/tests/sample4.wv differ