157 lines
6.1 KiB
Racket
157 lines
6.1 KiB
Racket
(module ffmpeg_decoder racket/base
|
|
|
|
(require ffi/unsafe
|
|
"ffmpeg-ffi.rkt"
|
|
"private/utils.rkt"
|
|
(prefix-in fin: finalizer)
|
|
)
|
|
|
|
(provide ffmpeg-open
|
|
ffmpeg-valid?
|
|
ffmpeg-read
|
|
ffmpeg-stop
|
|
ffmpeg-seek
|
|
)
|
|
|
|
(define-struct ffmpeg-handle
|
|
(if cb-info cb-audio
|
|
(stop #:mutable)
|
|
(seek #:mutable)
|
|
(reading #:mutable)
|
|
(format #:mutable)
|
|
)
|
|
#:transparent
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Functions to do the good stuff
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define (ffmpeg-valid? audio-file)
|
|
;; Keep this deliberately weak, just like mp3-valid?. Existence and
|
|
;; extension checks can be done by the generic audio-decoder layer. The
|
|
;; real validation happens when the FFmpeg shim opens the file.
|
|
#t)
|
|
|
|
(define audio-type 'ffmpeg)
|
|
|
|
(define last-rate 44100) ; An assumption for if we've got nothing
|
|
(define last-channels 2) ; An assumption for if we've got nothing
|
|
(define last-bits 32) ; FFmpeg shim output is always S32
|
|
(define last-bytes 4) ; One S32 sample is four bytes
|
|
|
|
(define (correct-format-hash h)
|
|
(let ((rate (hash-ref h 'sample-rate #f)))
|
|
(when (eq? rate #f)
|
|
(hash-set! h 'sample-rate last-rate)))
|
|
(let ((channels (hash-ref h 'channels #f)))
|
|
(when (eq? channels #f)
|
|
(hash-set! h 'channels last-channels)))
|
|
(let ((bits (hash-ref h 'bits-per-sample #f)))
|
|
(when (eq? bits #f)
|
|
(hash-set! h 'bits-per-sample last-bits)))
|
|
(let ((bytes (hash-ref h 'bytes-per-sample #f)))
|
|
(when (eq? bytes #f)
|
|
(hash-set! h 'bytes-per-sample last-bytes)))
|
|
(let ((total-samples (hash-ref h 'total-samples #f)))
|
|
(when (eq? total-samples #f)
|
|
(hash-set! h 'total-samples 0)
|
|
(hash-set! h 'duration 0))))
|
|
|
|
(define (report-format handle current-pcm-pos)
|
|
(dbg-sound "Reporting ffmpeg format at pcm-pos: ~a" current-pcm-pos)
|
|
(let ((h (ffmpeg-handle-format handle)))
|
|
(set! last-rate (hash-ref h 'sample-rate))
|
|
(set! last-channels (hash-ref h 'channels))
|
|
(set! last-bits (hash-ref h 'bits-per-sample))
|
|
(set! last-bytes (hash-ref h 'bytes-per-sample last-bytes)))
|
|
((ffmpeg-handle-cb-info handle) (ffmpeg-handle-format handle)))
|
|
|
|
(define (give-audio handle info pos buffer size)
|
|
(let ((h (ffmpeg-handle-format handle)))
|
|
(correct-format-hash h)
|
|
(hash-set! h 'sample pos)
|
|
(let ((sample-rate (hash-ref h 'sample-rate last-rate)))
|
|
(hash-set! h 'current-time (exact->inexact (/ pos sample-rate))))
|
|
((ffmpeg-handle-cb-audio handle) h buffer size)))
|
|
|
|
(define (ffmpeg-open audio-file* cb-stream-info cb-audio)
|
|
(let ((audio-file (if (path? audio-file*)
|
|
(path->string audio-file*)
|
|
audio-file*)))
|
|
(if (file-exists? audio-file)
|
|
(let ((handler (fmpg-ffi-decoder-handler)))
|
|
(handler 'new)
|
|
(handler 'init audio-file)
|
|
(let ((h (make-ffmpeg-handle handler
|
|
cb-stream-info
|
|
cb-audio
|
|
#f
|
|
#f
|
|
#f
|
|
(make-hash))))
|
|
h))
|
|
#f)))
|
|
|
|
(define (handle-format handle pcm-pos rate channels sample-bits sample-bytes pcm-length)
|
|
(let ((f (make-hash)))
|
|
(hash-set! f 'duration (if (and (integer? pcm-length)
|
|
(>= pcm-length 0)
|
|
(integer? rate)
|
|
(> rate 0))
|
|
(exact->inexact (/ pcm-length rate))
|
|
0.0))
|
|
(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-ffmpeg-handle-format! handle f))
|
|
(report-format handle pcm-pos))
|
|
|
|
(define (ffmpeg-read handle)
|
|
(let* ((ffi-handler (ffmpeg-handle-if handle))
|
|
(cb-info (ffmpeg-handle-cb-info handle))
|
|
(cb-audio (ffmpeg-handle-cb-audio handle)))
|
|
(set-ffmpeg-handle-reading! handle #t)
|
|
(let loop ()
|
|
(if (eq? (ffmpeg-handle-stop handle) #t)
|
|
(begin
|
|
(dbg-sound "Stopping ffmpeg decoding")
|
|
(set-ffmpeg-handle-reading! handle #f)
|
|
'stopped-reading)
|
|
(begin
|
|
(unless (eq? (ffmpeg-handle-seek handle) #f)
|
|
(dbg-sound "Seeking to ~a" (ffmpeg-handle-seek handle))
|
|
(ffi-handler 'seek (ffmpeg-handle-seek handle))
|
|
(set-ffmpeg-handle-seek! handle #f))
|
|
(ffi-handler 'read
|
|
(lambda (info pos buffer size)
|
|
(if (eq? info 'done)
|
|
(set-ffmpeg-handle-stop! handle #t)
|
|
(give-audio handle info pos buffer size)))
|
|
(lambda (pcm-pos rate channels sample-bits sample-bytes pcm-length)
|
|
(handle-format handle pcm-pos rate channels sample-bits sample-bytes pcm-length)))
|
|
(loop)))))
|
|
(let ((ffi-handler (ffmpeg-handle-if handle)))
|
|
(ffi-handler 'close)
|
|
(ffi-handler 'delete)))
|
|
|
|
(define (ffmpeg-seek handle percentage)
|
|
(let ((fmt (ffmpeg-handle-format handle)))
|
|
(let ((total-samples (hash-ref fmt 'total-samples 0)))
|
|
(unless (or
|
|
(eq? total-samples #f)
|
|
(= total-samples -1))
|
|
(let ((sample (inexact->exact
|
|
(round (* (exact->inexact (/ percentage 100.0))
|
|
total-samples)))))
|
|
(set-ffmpeg-handle-seek! handle sample))))))
|
|
|
|
(define (ffmpeg-stop handle)
|
|
(set-ffmpeg-handle-stop! handle #t)
|
|
(while (ffmpeg-handle-reading handle)
|
|
(sleep 0.01)))
|
|
|
|
); end of module
|