mp3 support, based on mpg123
This commit is contained in:
+25
-8
@@ -1,6 +1,7 @@
|
||||
(module audio-decoder racket/base
|
||||
|
||||
(require "flac-decoder.rkt"
|
||||
"mp3-decoder.rkt"
|
||||
racket/contract
|
||||
racket/string
|
||||
racket/path
|
||||
@@ -24,11 +25,12 @@
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define-struct audio-reader
|
||||
(exts valid? open reader seeker stopper))
|
||||
(exts valid? open reader seeker stopper ao-type))
|
||||
|
||||
;; audiotype, audio-reader
|
||||
(define audio-readers (make-hash))
|
||||
|
||||
;; FLAC
|
||||
(hash-set! audio-readers
|
||||
'flac
|
||||
(make-audio-reader '("flac")
|
||||
@@ -36,14 +38,26 @@
|
||||
flac-open
|
||||
flac-read
|
||||
flac-seek
|
||||
flac-stop))
|
||||
flac-stop
|
||||
'flac))
|
||||
|
||||
;; MP3
|
||||
(hash-set! audio-readers
|
||||
'mp3
|
||||
(make-audio-reader '("mp3")
|
||||
mp3-valid?
|
||||
mp3-open
|
||||
mp3-read
|
||||
mp3-seek
|
||||
mp3-stop
|
||||
'ao))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Known extensions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define known-extensions
|
||||
'("flac"))
|
||||
'("flac" "mp3"))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Register audio reader
|
||||
@@ -89,12 +103,13 @@
|
||||
(let ((reader (find-reader file)))
|
||||
(if (eq? reader #f)
|
||||
#f
|
||||
(audio-reader-valid? file)))
|
||||
((audio-reader-valid? (cadr reader)) file)))
|
||||
#f))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
; cb-stream-info will be called with
|
||||
; - audio-type: symbol?
|
||||
; - audio-type: symbol? (e.g. 'flac, 'mp3)
|
||||
; - ao-type: symbol? - e.g. 'flac, 'ao (ao means the buffer can be used directly by aolib).
|
||||
; - handle: audio-handle?
|
||||
; - meta: hash?
|
||||
; Meta information must at least contain:
|
||||
@@ -106,6 +121,7 @@
|
||||
;
|
||||
; cb-audio will be called with
|
||||
; - audio-type: symbol?
|
||||
; - ao-type: symbol? - e.g. 'flac, 'ao (ao means the buffer can be used directly by aolib).
|
||||
; - handle: audio-handle?
|
||||
; - buf-info: hash?
|
||||
; - buffer: cpointer? - contains data to be fed to ao - must be owned / released by the driver
|
||||
@@ -134,6 +150,7 @@
|
||||
(error (format "Cannot find reader for '~a'" audio-file)))
|
||||
(let* ((reader-type (car reader*))
|
||||
(reader (cadr reader*))
|
||||
(ao-type (audio-reader-ao-type reader))
|
||||
(handle (make-audio-handle reader-type cb-stream-info cb-audio reader #f))
|
||||
)
|
||||
(set-audio-handle-driver-handle!
|
||||
@@ -141,9 +158,9 @@
|
||||
((audio-reader-open reader)
|
||||
file
|
||||
(λ (meta)
|
||||
(cb-stream-info reader-type handle meta))
|
||||
(cb-stream-info reader-type ao-type handle meta))
|
||||
(λ (buf-info audio-buffer buf-len)
|
||||
(cb-audio reader-type handle buf-info audio-buffer buf-len)))
|
||||
(cb-audio reader-type ao-type handle buf-info audio-buffer buf-len)))
|
||||
)
|
||||
handle)
|
||||
)
|
||||
@@ -153,7 +170,7 @@
|
||||
(define/contract (audio-read handle)
|
||||
(-> audio-handle? void?)
|
||||
(let ((reader (audio-reader-reader (audio-handle-driver handle))))
|
||||
(void? (reader (audio-handle-driver-handle handle)))))
|
||||
(void (reader (audio-handle-driver-handle handle)))))
|
||||
|
||||
(define/contract (audio-seek handle percentage)
|
||||
(-> audio-handle? number? void?)
|
||||
|
||||
+6
-1
@@ -124,7 +124,12 @@
|
||||
st)
|
||||
(reader (+ frame-nr 1))))))
|
||||
))
|
||||
(reader 0))))
|
||||
(reader 0)
|
||||
; done reading, delete flac encoder
|
||||
(ffi-handler 'delete)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define (flac-read-meta handle)
|
||||
(let* ((ffi-handler (flac-handle-ffi-decoder-handler handle))
|
||||
|
||||
+15
-1
@@ -520,10 +520,14 @@
|
||||
)
|
||||
|
||||
(define (new)
|
||||
(set! fl (FLAC__stream_decoder_new))
|
||||
(dbg-sound "flac-ffi 'new")
|
||||
(if (eq? fl #f)
|
||||
(set! fl (FLAC__stream_decoder_new))
|
||||
(error "flac handler already initialized (new)"))
|
||||
fl)
|
||||
|
||||
(define (init file)
|
||||
(dbg-sound "flac-ffi 'init")
|
||||
(let ((r (FLAC__stream_decoder_init_file
|
||||
fl
|
||||
file
|
||||
@@ -534,6 +538,15 @@
|
||||
(set! flac-file file)
|
||||
r))
|
||||
|
||||
(define (delete)
|
||||
(dbg-sound "flac-ffi 'delete")
|
||||
(if (eq? fl #f)
|
||||
(error "flac handler has already been deleted")
|
||||
(begin
|
||||
(FLAC__stream_decoder_delete fl)
|
||||
(set! fl #f)))
|
||||
)
|
||||
|
||||
(define (process-single)
|
||||
(FLAC__stream_decoder_process_single fl))
|
||||
|
||||
@@ -583,6 +596,7 @@
|
||||
|
||||
[(eq? cmd 'new) (new)]
|
||||
[(eq? cmd 'init) (init (car args))]
|
||||
[(eq? cmd 'delete) (delete)]
|
||||
[(eq? cmd 'process-single) (process-single)]
|
||||
[(eq? cmd 'get-buffers) (buffer->vectorlist (car args) (cadr args) (caddr args))]
|
||||
|
||||
|
||||
+137
-93
@@ -43,7 +43,7 @@
|
||||
MPG123_NEED_MORE = -10 ;/**< Message: For feed reader: "Feed me more!" (call
|
||||
; mpg123_feed() or mpg123_decode() with some new input data). */
|
||||
MPG123_ERR = -1 ;/**< Generic Error */
|
||||
MPG123_OK=0 ;/**< Success */
|
||||
MPG123_OK = 0 ;/**< Success */
|
||||
MPG123_BAD_OUTFORMAT ;/**< Unable to set up output format! */
|
||||
MPG123_BAD_CHANNEL ;/**< Invalid channel number specified. */
|
||||
MPG123_BAD_RATE ;/**< Invalid sample rate specified. */
|
||||
@@ -91,6 +91,7 @@
|
||||
MPG123_INT_OVERFLOW ;/**< Some integer overflow. */
|
||||
MPG123_BAD_FLOAT ;/**< Floating-point computations work not as expected. */
|
||||
)
|
||||
_int
|
||||
)
|
||||
)
|
||||
|
||||
@@ -98,6 +99,7 @@
|
||||
|
||||
|
||||
; MPG123_EXPORT int mpg123_init (void)
|
||||
; Not relevant anymore
|
||||
(define-libmpg123 mpg123_init
|
||||
(_fun -> _MPG123_Result))
|
||||
|
||||
@@ -157,6 +159,25 @@
|
||||
(define-libmpg123 mpg123_length64
|
||||
(_fun _mpg123_handle -> _int64))
|
||||
|
||||
; MPG123_EXPORT void mpg123_delete (mpg123_handle *mh)
|
||||
(define-libmpg123 mpg123_delete
|
||||
(_fun _mpg123_handle -> _void))
|
||||
|
||||
; MPG123_EXPORT void mpg123_exit (void)
|
||||
; Not relevant anymore
|
||||
(define-libmpg123 mpg123_exit
|
||||
(_fun -> _void))
|
||||
|
||||
; MPG123_EXPORT const char* mpg123_plain_strerror ( int errcode )
|
||||
(define-libmpg123 mpg123_plain_strerror
|
||||
(_fun _MPG123_Result -> _string*/utf-8))
|
||||
|
||||
(define mpg123_int_strerror
|
||||
(get-ffi-obj "mpg123_plain_strerror"
|
||||
lib
|
||||
(_fun _int -> _string*/utf-8)))
|
||||
|
||||
|
||||
|
||||
#|
|
||||
#include <ao/ao.h>
|
||||
@@ -222,112 +243,135 @@ int main(int argc, char *argv[])
|
||||
;; Our interface for decoding to racket
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define (mpg123-ffi-decoder-handler)
|
||||
#t
|
||||
#| (define write-data '())
|
||||
(define meta-data '())
|
||||
(define error-no -1)
|
||||
(define fl #f)
|
||||
(define flac-file #f)
|
||||
(define client-data #f)
|
||||
(define BITS 8)
|
||||
|
||||
(define (write-callback fl frame buffer client-data)
|
||||
(set! write-data (append write-data (list (cons frame buffer))))
|
||||
0)
|
||||
(define (mpg123-ffi-decoder-handler)
|
||||
|
||||
(define (meta-callback fl meta client-data)
|
||||
(let ((meta-clone (FLAC__metadata_object_clone meta)))
|
||||
(unless (eq? meta-clone #f)
|
||||
(set! meta-data (append meta-data (list meta-clone))))))
|
||||
(define mh #f)
|
||||
|
||||
(define (error-callback fl errno client-data)
|
||||
(set! error-no errno)
|
||||
)
|
||||
(define buf-size -1)
|
||||
(define buffer #f)
|
||||
|
||||
(define (new)
|
||||
(set! fl (FLAC__stream_decoder_new))
|
||||
fl)
|
||||
(define rate -1)
|
||||
(define channels -1)
|
||||
(define sample-bits -1)
|
||||
(define sample-bytes -1)
|
||||
(define pcm-length -1)
|
||||
(define encoding -1)
|
||||
(define mp3-file "")
|
||||
|
||||
(define (init file)
|
||||
(let ((r (FLAC__stream_decoder_init_file
|
||||
fl
|
||||
file
|
||||
write-callback
|
||||
meta-callback
|
||||
error-callback
|
||||
client-data)))
|
||||
(set! flac-file file)
|
||||
r))
|
||||
(define (new)
|
||||
(if (eq? mh #f)
|
||||
(let-values ([(h err) (mpg123_new #f)])
|
||||
(when (eq? h #f)
|
||||
(error (format "mpg123_new: ~a" (mpg123_int_strerror err))))
|
||||
(set! mh h)
|
||||
(set! buf-size (mpg123_outblock mh))
|
||||
(set! buffer (malloc buf-size 'atomic-interior ))
|
||||
)
|
||||
(error "mpg123 handle already initialized, delete it first"))
|
||||
#t)
|
||||
|
||||
(define (process-single)
|
||||
(FLAC__stream_decoder_process_single fl))
|
||||
(define (delete)
|
||||
(if (eq? mh #f)
|
||||
(error "mpg123 has already been deleted")
|
||||
(begin
|
||||
(mpg123_delete mh)
|
||||
(set! mh #f)
|
||||
(set! buf-size -1)
|
||||
(set! buffer #f)
|
||||
))
|
||||
#t)
|
||||
|
||||
(define (int-state)
|
||||
(FLAC__stream_decoder_get_state fl))
|
||||
(define (info)
|
||||
(info-sound "file : ~a" mp3-file)
|
||||
(info-sound "buf-size : ~a" buf-size)
|
||||
(info-sound "channels : ~a" channels)
|
||||
(info-sound "sample-bits: ~a" sample-bits)
|
||||
(info-sound "rate : ~a" rate)
|
||||
(info-sound "encoding : ~a" encoding)
|
||||
(info-sound "pcm-length : ~a" pcm-length)
|
||||
(info-sound "duration : ~a" (if (= rate -1)
|
||||
0
|
||||
(exact->inexact
|
||||
(/ pcm-length rate))))
|
||||
#t
|
||||
)
|
||||
|
||||
(define (state)
|
||||
(decoder-state (int-state)))
|
||||
(define (init file)
|
||||
(let ((r (mpg123_open mh file)))
|
||||
(unless (eq? r 'MPG123_OK)
|
||||
(error (format "mpg123_open: ~a" (mpg123_plain_strerror r))))
|
||||
)
|
||||
(let-values ([(fr rate* channels* encoding*) (mpg123_getformat mh)])
|
||||
(unless (eq? fr 'MPG123_OK)
|
||||
(error (format "mpg123_format: ~a" (mpg123_plain_strerror fr))))
|
||||
(set! rate rate*)
|
||||
(set! channels channels*)
|
||||
(set! encoding encoding*)
|
||||
(dbg-sound "mpg123_format: ~a ~a ~a" rate channels encoding)
|
||||
(set! sample-bits (* (mpg123_encsize encoding) BITS))
|
||||
(set! sample-bytes (/ sample-bits 8)))
|
||||
(let ((sr (mpg123_scan mh)))
|
||||
(unless (eq? sr 'MPG123_OK)
|
||||
(error (format "mpg123_scan: ~a" (mpg123_plain_strerror sr))))
|
||||
(set! pcm-length (mpg123_length64 mh)))
|
||||
(set! mp3-file (format "~a" file))
|
||||
#t)
|
||||
|
||||
(define (process-meta-data cb)
|
||||
(for-each (λ (meta-entry)
|
||||
(cb meta-entry)
|
||||
(FLAC__metadata_object_delete meta-entry))
|
||||
meta-data)
|
||||
(set! meta-data '()))
|
||||
(define (mp3-format cb)
|
||||
(cb rate channels sample-bits sample-bytes pcm-length))
|
||||
|
||||
(define (process-write-data cb)
|
||||
(for-each (lambda (d)
|
||||
(cb (car d) (cdr d)))
|
||||
write-data)
|
||||
(set! write-data '()))
|
||||
(define (close)
|
||||
(let ((r (mpg123_close mh)))
|
||||
(unless (eq? r 'MPG123_OK)
|
||||
(error (format "mpg123_close: ~a" (mpg123_plain_strerror r))))
|
||||
(set! channels -1)
|
||||
(set! pcm-length -1)
|
||||
(set! rate -1)
|
||||
(set! sample-bits -1)
|
||||
(set! sample-bytes -1)
|
||||
(set! encoding -1)
|
||||
(set! mp3-file "")
|
||||
#t))
|
||||
|
||||
(define (buffer->vectorlist buffer channels size)
|
||||
(letrec ((for-channels
|
||||
(lambda (channel)
|
||||
(if (< channel channels)
|
||||
(letrec ((v (make-vector size 0))
|
||||
(p (ptr-ref buffer FLAC__int32-pointer channel))
|
||||
(to-vec (lambda (i)
|
||||
(when (< i size)
|
||||
(vector-set! v i (ptr-ref p _int32 i))
|
||||
(to-vec (+ i 1)))))
|
||||
)
|
||||
(to-vec 0)
|
||||
(cons v (for-channels (+ channel 1))))
|
||||
'())))
|
||||
)
|
||||
(for-channels 0)))
|
||||
(define (read cb)
|
||||
(let-values ([(r done) (mpg123_read mh buffer buf-size)])
|
||||
(if (eq? r 'MPG123_DONE)
|
||||
(cb 'done -1 buffer done)
|
||||
(if (eq? r 'MPG123_OK)
|
||||
(let ((pcm-pos (mpg123_tell64 mh)))
|
||||
(cb 'data pcm-pos buffer done))
|
||||
(error (format "mpg123_read: ~a" (mpg123_plain_strerror r)))
|
||||
)
|
||||
)
|
||||
)
|
||||
#t)
|
||||
|
||||
(define (seek-to-sample sample)
|
||||
(FLAC__stream_decoder_seek_absolute fl sample))
|
||||
(define (seek pcm-pos)
|
||||
(let ((r (mpg123_seek64 mh pcm-pos 'seek-set)))
|
||||
(unless (>= r 0)
|
||||
(error (format "mpg123_seek64: ~a" (mpg123_int_strerror r))))
|
||||
#t))
|
||||
|
||||
(lambda (cmd . args)
|
||||
(cond
|
||||
[(eq? cmd 'write-data) write-data]
|
||||
[(eq? cmd 'meta-data) meta-data]
|
||||
(define (tell)
|
||||
(mpg123_tell64 mh))
|
||||
|
||||
[(eq? cmd 'new) (new)]
|
||||
[(eq? cmd 'init) (init (car args))]
|
||||
[(eq? cmd 'process-single) (process-single)]
|
||||
[(eq? cmd 'get-buffers) (buffer->vectorlist (car args) (cadr args) (caddr args))]
|
||||
|
||||
[(eq? cmd 'int-state) (int-state)]
|
||||
[(eq? cmd 'state) (state)]
|
||||
|
||||
[(eq? cmd 'has-write-data?) (not (null? write-data))]
|
||||
[(eq? cmd 'has-meta-data?) (not (null? meta-data))]
|
||||
[(eq? cmd 'has-errno?) (not (= error-no -1))]
|
||||
|
||||
[(eq? cmd 'process-meta-data) (process-meta-data (car args))]
|
||||
[(eq? cmd 'process-write-data) (process-write-data (car args))]
|
||||
[(eq? cmd 'errno) error-no]
|
||||
|
||||
[(eq? cmd 'seek-to-sample) (seek-to-sample (car args))]
|
||||
[(eq? cmd 'file) flac-file]
|
||||
|
||||
[else (error (format "unknown command ~a" cmd))]
|
||||
))
|
||||
|#
|
||||
(λ (cmd . args)
|
||||
(cond
|
||||
[(eq? cmd 'new) (new)]
|
||||
[(eq? cmd 'delete) (delete)]
|
||||
[(eq? cmd 'init) (init (car args))]
|
||||
[(eq? cmd 'close) (close)]
|
||||
[(eq? cmd 'format) (mp3-format (car args))]
|
||||
[(eq? cmd 'info) (info)]
|
||||
[(eq? cmd 'read) (read (car args))]
|
||||
[(eq? cmd 'seek) (seek (car args))]
|
||||
[(eq? cmd 'tell) (tell)]
|
||||
[(eq? cmd 'file) mp3-file]
|
||||
[else (error (format "Unknown command: ~a" cmd))]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
); end of module
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
(module mp3-decoder racket/base
|
||||
|
||||
(require ffi/unsafe
|
||||
"libmpg123-ffi.rkt"
|
||||
"private/utils.rkt")
|
||||
|
||||
(provide mp3-open
|
||||
mp3-valid?
|
||||
mp3-read
|
||||
mp3-stop
|
||||
mp3-seek
|
||||
)
|
||||
|
||||
|
||||
(define-struct mp3-handle
|
||||
(if cb-info cb-audio
|
||||
(stop #:mutable)
|
||||
(seek #:mutable)
|
||||
(reading #:mutable)
|
||||
(format #:mutable)
|
||||
)
|
||||
#:transparent
|
||||
)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Functions to do the good stuff
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define (mp3-valid? mp3-file)
|
||||
#t)
|
||||
|
||||
(define audio-type 'mp3)
|
||||
|
||||
(define (report-format handle current-pcm-pos)
|
||||
((mp3-handle-cb-info handle) (mp3-handle-format handle)))
|
||||
|
||||
(define (give-audio handle info pos buffer size)
|
||||
(let ((h (mp3-handle-format handle)))
|
||||
(hash-set! h 'sample pos)
|
||||
(hash-set! h 'current-time (exact->inexact (/ pos (hash-ref h 'sample-rate))))
|
||||
((mp3-handle-cb-audio handle) h buffer size)))
|
||||
|
||||
(define (mp3-open mp3-file* cb-stream-info cb-audio)
|
||||
(let ((mp3-file (if (path? mp3-file*) (path->string mp3-file*) mp3-file*)))
|
||||
(if (file-exists? mp3-file)
|
||||
(let ((handler (mpg123-ffi-decoder-handler)))
|
||||
(handler 'new)
|
||||
(handler 'init mp3-file)
|
||||
(let ((h (make-mp3-handle handler
|
||||
cb-stream-info
|
||||
cb-audio
|
||||
#f
|
||||
#f
|
||||
#f
|
||||
#f
|
||||
)))
|
||||
(handler 'format
|
||||
(λ (rate channels sample-bits sample-bytes pcm-length)
|
||||
(let ((f (make-hash)))
|
||||
(hash-set! f 'duration (exact->inexact (/ pcm-length rate)))
|
||||
(hash-set! f 'sample-rate rate)
|
||||
(hash-set! f 'channels channels)
|
||||
(hash-set! f 'bits-per-sample sample-bits)
|
||||
(hash-set! f 'bytes-per-sample sample-bytes)
|
||||
(hash-set! f 'total-samples pcm-length)
|
||||
(set-mp3-handle-format! h f)
|
||||
)
|
||||
)
|
||||
)
|
||||
(report-format h 0)
|
||||
h))
|
||||
#f)))
|
||||
|
||||
|
||||
(define (mp3-read handle)
|
||||
(let* ((ffi-handler (mp3-handle-if handle))
|
||||
(cb-info (mp3-handle-cb-info handle))
|
||||
(cb-audio (mp3-handle-cb-audio handle))
|
||||
)
|
||||
(set-mp3-handle-reading! handle #t)
|
||||
(let loop ()
|
||||
(if (eq? (mp3-handle-stop handle) #t)
|
||||
(begin
|
||||
(newline)
|
||||
(dbg-sound "Stopping mp3 decoding")
|
||||
(set-mp3-handle-reading! handle #f)
|
||||
'stopped-reading
|
||||
)
|
||||
(begin
|
||||
(unless (eq? (mp3-handle-seek handle) #f)
|
||||
(dbg-sound "Seeking to ~a" (mp3-handle-seek handle))
|
||||
(ffi-handler 'seek (mp3-handle-seek handle))
|
||||
(set-mp3-handle-seek! handle #f))
|
||||
(ffi-handler 'read
|
||||
(λ (info pos buffer size)
|
||||
(if (eq? info 'done)
|
||||
(set-mp3-handle-stop! handle #t)
|
||||
(give-audio handle info pos buffer size)
|
||||
))
|
||||
)
|
||||
(loop)
|
||||
)
|
||||
))
|
||||
(ffi-handler 'close)
|
||||
(ffi-handler 'delete)
|
||||
)
|
||||
)
|
||||
|
||||
(define (mp3-seek handle percentage)
|
||||
(let ((fmt (mp3-handle-format handle)))
|
||||
(let ((total-samples (hash-ref fmt 'total-samples)))
|
||||
(unless (or
|
||||
(eq? total-samples #f)
|
||||
(= total-samples -1))
|
||||
(let ((sample (inexact->exact (round * (exact->inexact (/ percentage 100.0)) total-samples))))
|
||||
((mp3-handle-if handle) 'seek sample))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define (mp3-stop handle)
|
||||
(set-mp3-handle-stop! handle #t)
|
||||
(while (mp3-handle-reading handle)
|
||||
(sleep 0.01))
|
||||
)
|
||||
|
||||
); end of module
|
||||
+16
-10
@@ -3,6 +3,7 @@
|
||||
"audio-decoder.rkt"
|
||||
simple-log
|
||||
"private/utils.rkt"
|
||||
racket-sprintf
|
||||
;data/queue
|
||||
;racket-sound
|
||||
)
|
||||
@@ -11,7 +12,9 @@
|
||||
(define test-file3-id 3)
|
||||
(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 "/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")
|
||||
)
|
||||
(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")
|
||||
;(set! test-file3 "C:\\Muziek\\Klassiek-Viool\\Janine Jansen\\Janine Jansen - Sibelius en Prokovief 1 (2024)\\02 - Violin Concerto in D Minor, Op. 47 II. Adagio di molto.flac")
|
||||
@@ -26,7 +29,7 @@
|
||||
|
||||
(sl-log-to-display)
|
||||
|
||||
(define (audio-play type handle buf-info buffer buf-len)
|
||||
(define (audio-play type ao-type handle buf-info buffer buf-len)
|
||||
(let* ((sample (hash-ref buf-info 'sample))
|
||||
(rate (hash-ref buf-info 'sample-rate))
|
||||
(second (/ (* sample 1.0) (* rate 1.0)))
|
||||
@@ -36,11 +39,12 @@
|
||||
(bytes-per-sample-all-channels (* channels bytes-per-sample))
|
||||
(duration (hash-ref buf-info 'duration))
|
||||
)
|
||||
(displayln buf-info)
|
||||
;(displayln buf-info)
|
||||
(when (eq? ao-h #f)
|
||||
(set! ao-h (ao-open-live bits-per-sample rate channels 'big-endian)))
|
||||
(set! ao-h (ao-open-live bits-per-sample rate channels 'native-endian)))
|
||||
;(displayln 'ao-play)
|
||||
(ao-play ao-h test-file3-id second duration buffer buf-len type)
|
||||
(ao-play ao-h test-file3-id second duration buffer buf-len ao-type)
|
||||
(set! duration (inexact->exact (round duration)))
|
||||
;(displayln 'done)
|
||||
(let ((second-printer (λ (buf-seconds)
|
||||
(let ((s (inexact->exact (round (ao-at-second ao-h)))))
|
||||
@@ -52,7 +56,8 @@
|
||||
(tseconds (remainder duration 60))
|
||||
(volume (ao-volume ao-h))
|
||||
)
|
||||
(displayln (format "At time: ~a:~a (~a:~a) - ~a - volume: ~a"
|
||||
(info-sound
|
||||
(sprintf "At time: %02d:%02d (%02d:%02d) - %d - volume: %d"
|
||||
minutes seconds
|
||||
tminutes tseconds
|
||||
buf-seconds
|
||||
@@ -68,7 +73,7 @@
|
||||
bytes-per-sample-all-channels
|
||||
rate))))
|
||||
(if (< buf-seconds-left 2.0)
|
||||
(displayln (format "Seconds in buffer left: ~a" buf-seconds-left))
|
||||
(info-sound "Seconds in buffer left: ~a" buf-seconds-left)
|
||||
(begin
|
||||
(sleep 0.5)
|
||||
(second-printer buf-seconds)
|
||||
@@ -89,9 +94,10 @@
|
||||
)
|
||||
)
|
||||
|
||||
(define (audio-meta type handle meta)
|
||||
(dbg-sound "type: ~a" type)
|
||||
(dbg-sound "meta: ~a" meta))
|
||||
(define (audio-meta type ao-type handle meta)
|
||||
(dbg-sound "type: ~a" type)
|
||||
(dbg-sound "ao-type: ~a" ao-type)
|
||||
(dbg-sound "meta: ~a" meta))
|
||||
|
||||
(define (play)
|
||||
(set! ao-h #f)
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require racket/base
|
||||
(for-label racket/base
|
||||
racket/path
|
||||
"../audio-decoder.rkt"))
|
||||
|
||||
@title{audio-decoder}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule["audio-decoder.rkt"]
|
||||
|
||||
This module provides a small abstraction layer over concrete audio
|
||||
decoders. A decoder backend is selected from the filename extension
|
||||
and is then used through a uniform interface for opening, reading,
|
||||
seeking, and stopping.
|
||||
|
||||
The module also allows additional decoder backends to be registered
|
||||
with @racket[audio-register-reader!].
|
||||
|
||||
@section{Reader registration}
|
||||
|
||||
A reader descriptor stores the extensions handled by a backend
|
||||
together with the procedures used to validate, open, read, seek,
|
||||
and stop that backend.
|
||||
|
||||
@defproc[(make-audio-reader [exts (listof string?)]
|
||||
[valid? procedure?]
|
||||
[open procedure?]
|
||||
[reader procedure?]
|
||||
[seeker procedure?]
|
||||
[stopper procedure?])
|
||||
struct?]{
|
||||
|
||||
Creates a reader descriptor.
|
||||
|
||||
The @racket[exts] list contains the filename extensions handled by the
|
||||
reader, without a leading dot. Matching is case-insensitive.
|
||||
|
||||
The procedures are used as follows:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket[valid?] checks whether a file is valid for this reader;}
|
||||
@item{@racket[open] opens a decoder for a file;}
|
||||
@item{@racket[reader] reads or continues decoding;}
|
||||
@item{@racket[seeker] seeks within the audio stream;}
|
||||
@item{@racket[stopper] stops an active decode loop.}]
|
||||
|
||||
The built-in FLAC backend is registered in this way.
|
||||
}
|
||||
|
||||
@defproc[(audio-register-reader! [type symbol?]
|
||||
[reader struct?])
|
||||
void?]{
|
||||
|
||||
Registers @racket[reader] under @racket[type].
|
||||
|
||||
The extensions declared in @racket[reader] are appended to the list
|
||||
returned by @racket[audio-known-exts?], and the reader becomes
|
||||
available to @racket[audio-open].
|
||||
}
|
||||
|
||||
@section{Audio handles}
|
||||
|
||||
@defproc[(audio-handle? [v any/c]) boolean?]{
|
||||
|
||||
Returns @racket[#t] if @racket[v] is an audio handle, and @racket[#f]
|
||||
otherwise.
|
||||
}
|
||||
|
||||
@defproc[(audio-kind [handle audio-handle?]) symbol?]{
|
||||
|
||||
Returns the reader type stored in @racket[handle].
|
||||
|
||||
For the built-in FLAC backend this value is @racket['flac].
|
||||
}
|
||||
|
||||
@section{Known extensions and validation}
|
||||
|
||||
@defproc[(audio-known-exts?) (listof string?)]{
|
||||
|
||||
Returns the list of known filename extensions.
|
||||
|
||||
The initial list contains @racket["flac"]. Additional extensions are
|
||||
added when readers are registered with
|
||||
@racket[audio-register-reader!].
|
||||
}
|
||||
|
||||
@defproc[(audio-valid-ext? [ext any/c]) boolean?]{
|
||||
|
||||
Returns @racket[#t] if @racket[ext] denotes a known filename
|
||||
extension, and @racket[#f] otherwise.
|
||||
|
||||
The argument is first converted to a string. If it starts with a dot,
|
||||
that dot is removed. Matching is case-insensitive.
|
||||
}
|
||||
|
||||
@defproc[(audio-file-valid? [file (or/c string? path?)]) boolean?]{
|
||||
|
||||
Returns @racket[#t] if @racket[file] has a known extension and matches
|
||||
a registered reader, and @racket[#f] otherwise.
|
||||
|
||||
This procedure first derives the filename extension and checks it with
|
||||
@racket[audio-valid-ext?]. If the extension is known, it then looks up
|
||||
the matching reader and performs the reader-specific validity check.
|
||||
}
|
||||
|
||||
@section{Opening and callbacks}
|
||||
|
||||
@defproc[(audio-open [audio-file (or/c string? path?)]
|
||||
[cb-stream-info procedure?]
|
||||
[cb-audio procedure?])
|
||||
audio-handle?]{
|
||||
|
||||
Opens an audio decoder for @racket[audio-file].
|
||||
|
||||
If @racket[audio-file] is a path, it is converted to a string before
|
||||
it is passed to the backend open procedure.
|
||||
|
||||
This procedure raises an exception if the file is not considered a
|
||||
valid audio file, if the file does not exist, or if no registered
|
||||
reader can be found for the file.
|
||||
|
||||
The returned handle stores the selected reader type, the callback
|
||||
procedures, the reader descriptor, and the driver-specific handle
|
||||
returned by the backend open procedure.
|
||||
|
||||
The callback procedures are wrapped before they are passed to the
|
||||
backend.
|
||||
|
||||
The stream-info callback is called as:
|
||||
|
||||
@racketblock[
|
||||
(cb-stream-info audio-type handle meta)
|
||||
]
|
||||
|
||||
where:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket[audio-type] is the registered reader type, such as
|
||||
@racket['flac];}
|
||||
@item{@racket[handle] is the generic @racket[audio-handle];}
|
||||
@item{@racket[meta] is a hash table with stream metadata.}]
|
||||
|
||||
According to the source comments, @racket[meta] must contain at least:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket['duration] --- duration of the audio in seconds, possibly
|
||||
fractional;}
|
||||
@item{@racket['bits-per-sample] --- number of audio bits per sample;}
|
||||
@item{@racket['channels] --- number of audio channels;}
|
||||
@item{@racket['sample-rate] --- number of samples per second per
|
||||
channel;}
|
||||
@item{@racket['total-samples] --- total number of samples in the
|
||||
audio.}]
|
||||
|
||||
The audio callback is called as:
|
||||
|
||||
@racketblock[
|
||||
(cb-audio audio-type handle buf-info buffer buf-size)
|
||||
]
|
||||
|
||||
where:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket[audio-type] is the registered reader type;}
|
||||
@item{@racket[handle] is the generic @racket[audio-handle];}
|
||||
@item{@racket[buf-info] is a hash table describing the audio buffer;}
|
||||
@item{@racket[buffer] is a native buffer containing audio data;}
|
||||
@item{@racket[buf-size] is the size of that buffer in bytes.}]
|
||||
|
||||
According to the source comments, the buffer is to be owned and
|
||||
released by the decoder driver. The comments also note that the
|
||||
@tt{ao-async} backend copies the data.
|
||||
|
||||
According to the source comments, @racket[buf-info] must contain at
|
||||
least:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket['duration] --- duration of the audio in seconds, possibly
|
||||
fractional;}
|
||||
@item{@racket['bits-per-sample] --- number of audio bits per sample;}
|
||||
@item{@racket['channels] --- number of audio channels;}
|
||||
@item{@racket['sample-rate] --- number of samples per second per
|
||||
channel;}
|
||||
@item{@racket['total-samples] --- total number of samples in the
|
||||
audio;}
|
||||
@item{@racket['sample] --- the current sample to which the audio
|
||||
buffer applies.}]
|
||||
}
|
||||
|
||||
@section{Reading, seeking, and stopping}
|
||||
|
||||
@defproc[(audio-read [handle audio-handle?]) void?]{
|
||||
|
||||
Calls the registered reader procedure for @racket[handle].
|
||||
|
||||
The concrete reader procedure receives the driver-specific handle
|
||||
stored in the generic audio handle. Any result value produced by the
|
||||
backend is discarded.
|
||||
}
|
||||
|
||||
@defproc[(audio-seek [handle audio-handle?]
|
||||
[percentage number?])
|
||||
void?]{
|
||||
|
||||
Calls the registered seek procedure for @racket[handle].
|
||||
|
||||
The @racket[percentage] argument is passed unchanged to the backend
|
||||
seek procedure.
|
||||
|
||||
In this abstraction layer, the parameter represents a relative
|
||||
position in the full audio stream. A backend registered through
|
||||
@racket[audio-register-reader!] is expected to follow that
|
||||
interpretation.
|
||||
}
|
||||
|
||||
@defproc[(audio-stop [handle audio-handle?]) void?]{
|
||||
|
||||
Calls the registered stop procedure for @racket[handle].
|
||||
|
||||
The concrete stop procedure receives the driver-specific handle stored
|
||||
in the generic audio handle.
|
||||
}
|
||||
|
||||
@section{Using custom decoders}
|
||||
|
||||
Custom audio decoders can be integrated by constructing a reader
|
||||
descriptor with @racket[make-audio-reader] and registering it with
|
||||
@racket[audio-register-reader!].
|
||||
|
||||
A backend integrated through this interface should provide:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{a list of handled filename extensions;}
|
||||
@item{a file-validity procedure;}
|
||||
@item{an open procedure that accepts a file path, a stream-info
|
||||
callback, and an audio callback;}
|
||||
@item{a read procedure that accepts the driver-specific handle;}
|
||||
@item{a seek procedure that accepts the driver-specific handle and a
|
||||
numeric relative position;}
|
||||
@item{a stop procedure that accepts the driver-specific handle.}]
|
||||
|
||||
Once registered, files with matching extensions can be opened through
|
||||
@racket[audio-open] in the same way as the built-in FLAC backend.
|
||||
Reference in New Issue
Block a user