Merge branch 'main' of https://git.dijkewijk.nl/hans/racket-audio
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
all:
|
||||||
|
@echo "make clean to cleanup bak/~ files"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *~ *.bak scrbl/*~ scrbl/*.bak private/*~ private/*.bak
|
||||||
+16
-1
@@ -351,6 +351,14 @@
|
|||||||
(define (volume percentage)
|
(define (volume percentage)
|
||||||
(set! req-volume percentage))
|
(set! req-volume percentage))
|
||||||
|
|
||||||
|
(define (ao-buf-ms)
|
||||||
|
(ao-playback-buf-ms))
|
||||||
|
|
||||||
|
(define (ao-buf-ms! ms)
|
||||||
|
(let ((the-ms (if (< ms 50) 50 (if (> ms 1000) 1000 ms))))
|
||||||
|
(ao-set-playback-buf-ms! the-ms)
|
||||||
|
(ao-buf-ms)))
|
||||||
|
|
||||||
(define (state msg cb . force)
|
(define (state msg cb . force)
|
||||||
(let ((h (make-hash)))
|
(let ((h (make-hash)))
|
||||||
(with-mutex ao-mutex
|
(with-mutex ao-mutex
|
||||||
@@ -384,7 +392,7 @@
|
|||||||
|
|
||||||
(let ((m-id (hash-ref h 'at-music-id)))
|
(let ((m-id (hash-ref h 'at-music-id)))
|
||||||
(unless (and (null? force) (or (eq? m-id #f) (= m-id 0)))
|
(unless (and (null? force) (or (eq? m-id #f) (= m-id 0)))
|
||||||
(cb (list 'state h))))
|
(cb (list 'state (list h player-state)))))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -418,6 +426,7 @@
|
|||||||
(cond
|
(cond
|
||||||
((eq? cmd 'quit) (do-rpc
|
((eq? cmd 'quit) (do-rpc
|
||||||
(stop-and-cleanup)
|
(stop-and-cleanup)
|
||||||
|
(set! player-state 'quit)
|
||||||
(state "quit" evt 'force)
|
(state "quit" evt 'force)
|
||||||
'(quit)))
|
'(quit)))
|
||||||
((eq? cmd 'init) (do-rpc
|
((eq? cmd 'init) (do-rpc
|
||||||
@@ -474,6 +483,12 @@
|
|||||||
(let ((st #f))
|
(let ((st #f))
|
||||||
(state "'state command" (λ (s) (set! st s)) 'force)
|
(state "'state command" (λ (s) (set! st s)) 'force)
|
||||||
st)))
|
st)))
|
||||||
|
((eq? cmd 'ao-buf-ms)
|
||||||
|
(do-rpc
|
||||||
|
(if (null? (cdr data))
|
||||||
|
(list (ao-buf-ms))
|
||||||
|
(list (ao-buf-ms! (cadr data))))
|
||||||
|
))
|
||||||
(else
|
(else
|
||||||
(do-rpc
|
(do-rpc
|
||||||
(list 'error (format "Unknown command ~a" cmd))))
|
(list 'error (format "Unknown command ~a" cmd))))
|
||||||
|
|||||||
+15
-4
@@ -30,6 +30,8 @@
|
|||||||
audio-file
|
audio-file
|
||||||
audio-play?
|
audio-play?
|
||||||
audio-buf-seconds!
|
audio-buf-seconds!
|
||||||
|
audio-ao-buf-ms!
|
||||||
|
audio-ao-buf-ms
|
||||||
audio-known-exts?
|
audio-known-exts?
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,7 +130,7 @@
|
|||||||
(cmd-put (cons cmd args)) (ret-get))))
|
(cmd-put (cons cmd args)) (ret-get))))
|
||||||
|
|
||||||
(let* ((handle #f)
|
(let* ((handle #f)
|
||||||
(cb-state* (λ (st) (cb-state handle st)))
|
(cb-state* (λ (st st-hash) (cb-state handle st st-hash)))
|
||||||
(cb-eof* (λ () (cb-eof-stream handle))))
|
(cb-eof* (λ () (cb-eof-stream handle))))
|
||||||
(set! handle (make-audio-play #t
|
(set! handle (make-audio-play #t
|
||||||
cb-state* cb-eof*
|
cb-state* cb-eof*
|
||||||
@@ -142,10 +144,11 @@
|
|||||||
(let loop ()
|
(let loop ()
|
||||||
(if (audio-play-valid? handle)
|
(if (audio-play-valid? handle)
|
||||||
(let ((e (evt-get 500)))
|
(let ((e (evt-get 500)))
|
||||||
(cond ((eq? e #f) (loop))
|
(cond ((eq? e #f) (void))
|
||||||
((is-event? e 'state)
|
((is-event? e 'state)
|
||||||
(set-audio-play-state! handle (evt-data e))
|
(let ((data (evt-data e)))
|
||||||
(cb-state* (evt-data e)))
|
(set-audio-play-state! handle (car data))
|
||||||
|
(cb-state* (cadr data) (car data))))
|
||||||
((is-event? e 'audio-done) (cb-eof*))
|
((is-event? e 'audio-done) (cb-eof*))
|
||||||
((is-event? e 'exception)
|
((is-event? e 'exception)
|
||||||
(err-sound "audio-player: exception event: ~a" e))
|
(err-sound "audio-player: exception event: ~a" e))
|
||||||
@@ -270,6 +273,14 @@
|
|||||||
(until (if (< max min) (+ min 1) (if (> max 30) 30 max))))
|
(until (if (< max min) (+ min 1) (if (> max 30) 30 max))))
|
||||||
((audio-play-rpc handle) 'buf-seconds from until)))
|
((audio-play-rpc handle) 'buf-seconds from until)))
|
||||||
|
|
||||||
|
(define/contract (audio-ao-buf-ms! handle ms)
|
||||||
|
(-> audio-play? integer? (or/c integer? boolean?))
|
||||||
|
((audio-play-rpc handle) 'ao-buf-ms ms))
|
||||||
|
|
||||||
|
(define/contract (audio-ao-buf-ms handle)
|
||||||
|
(-> audio-play? (or/c integer? boolean?))
|
||||||
|
((audio-play-rpc handle) 'ao-buf-ms))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+9
-24
@@ -1378,15 +1378,12 @@
|
|||||||
(define (call-with-swr-output-buffer max-bytes proc)
|
(define (call-with-swr-output-buffer max-bytes proc)
|
||||||
(early-return
|
(early-return
|
||||||
;; Both allocations are native because swr_convert reads/writes C pointers.
|
;; Both allocations are native because swr_convert reads/writes C pointers.
|
||||||
((tmp (malloc max-bytes 'raw) ? (eq? tmp #f) => #f)
|
((tmp (malloc max-bytes 'atomic-interior) ? (eq? tmp #f) => #f)
|
||||||
(out-planes (malloc _pointer 1 'raw)
|
(out-planes (malloc _pointer 1 'atomic-interior)
|
||||||
? (eq? out-planes #f) => #f
|
? (eq? out-planes #f) => #f)
|
||||||
~ (free tmp))
|
|
||||||
;; Interleaved output has one plane; out-planes[0] points to tmp.
|
;; Interleaved output has one plane; out-planes[0] points to tmp.
|
||||||
(do (ptr-set! out-planes _pointer 0 tmp))
|
(do (ptr-set! out-planes _pointer 0 tmp))
|
||||||
(result (proc tmp out-planes)))
|
(result (proc tmp out-planes)))
|
||||||
(free out-planes)
|
|
||||||
(free tmp)
|
|
||||||
result)
|
result)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1511,27 +1508,20 @@
|
|||||||
? (<= delay 0) => produced)
|
? (<= delay 0) => produced)
|
||||||
(max-bytes (av_samples_get_buffer_size #f channels delay FMPG_OUTPUT_FMT 1)
|
(max-bytes (av_samples_get_buffer_size #f channels delay FMPG_OUTPUT_FMT 1)
|
||||||
? (<= max-bytes 0) => produced)
|
? (<= max-bytes 0) => produced)
|
||||||
(tmp (malloc max-bytes 'raw)
|
(tmp (malloc max-bytes 'atomic-interior)
|
||||||
? (eq? tmp #f) => -1)
|
? (eq? tmp #f) => -1)
|
||||||
(out-planes (malloc _pointer 1 'raw)
|
(out-planes (malloc _pointer 1 'atomic-interior)
|
||||||
? (eq? out-planes #f) => -1
|
? (eq? out-planes #f) => -1)
|
||||||
~ (free tmp))
|
|
||||||
|
|
||||||
(do (ptr-set! out-planes _pointer 0 tmp))
|
(do (ptr-set! out-planes _pointer 0 tmp))
|
||||||
|
|
||||||
;; Null input with 0 samples asks swresample to flush delayed output.
|
;; Null input with 0 samples asks swresample to flush delayed output.
|
||||||
(out-samples (swr_convert swr-ctx out-planes delay #f 0)
|
(out-samples (swr_convert swr-ctx out-planes delay #f 0)
|
||||||
? (<= out-samples 0) => produced
|
? (<= out-samples 0) => produced)
|
||||||
~ (begin
|
|
||||||
(free out-planes)
|
|
||||||
(free tmp)))
|
|
||||||
|
|
||||||
(used-bytes (av_samples_get_buffer_size #f channels out-samples
|
(used-bytes (av_samples_get_buffer_size #f channels out-samples
|
||||||
FMPG_OUTPUT_FMT 1)
|
FMPG_OUTPUT_FMT 1)
|
||||||
? (< used-bytes 0) => produced
|
? (< used-bytes 0) => produced)
|
||||||
~ (begin
|
|
||||||
(free out-planes)
|
|
||||||
(free tmp)))
|
|
||||||
|
|
||||||
;; If this is the first output, the buffer belongs to the current next-sample-pos.
|
;; If this is the first output, the buffer belongs to the current next-sample-pos.
|
||||||
(do
|
(do
|
||||||
@@ -1542,17 +1532,12 @@
|
|||||||
sample-rate*)))))
|
sample-rate*)))))
|
||||||
|
|
||||||
(appended? (append-bytes! dec tmp used-bytes)
|
(appended? (append-bytes! dec tmp used-bytes)
|
||||||
? (not appended?) => -1
|
? (not appended?) => -1)
|
||||||
~ (begin
|
|
||||||
(free out-planes)
|
|
||||||
(free tmp)))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
(ds-last-samples! dec (+ (ds-last-samples dec) out-samples))
|
(ds-last-samples! dec (+ (ds-last-samples dec) out-samples))
|
||||||
(ds-next-sample-pos! dec (+ (ds-next-sample-pos dec)
|
(ds-next-sample-pos! dec (+ (ds-next-sample-pos dec)
|
||||||
out-samples))
|
out-samples))
|
||||||
(free out-planes)
|
|
||||||
(free tmp)
|
|
||||||
(loop 1))
|
(loop 1))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -197,7 +197,7 @@
|
|||||||
;; Playback buffer to send to libao in milliseconds
|
;; Playback buffer to send to libao in milliseconds
|
||||||
;; -------------------------------------------------------------------------
|
;; -------------------------------------------------------------------------
|
||||||
|
|
||||||
(define ao-buf-ms 150) ;; Playback buffer of 0.15s
|
(define ao-buf-ms 350) ;; Playback buffer of 0.35s
|
||||||
|
|
||||||
(define (ao-playback-buf-ms)
|
(define (ao-playback-buf-ms)
|
||||||
ao-buf-ms)
|
ao-buf-ms)
|
||||||
|
|||||||
@@ -36,11 +36,54 @@ all other procedures in this module.
|
|||||||
The @racket[cb-state] callback is called as:
|
The @racket[cb-state] callback is called as:
|
||||||
|
|
||||||
@racketblock[
|
@racketblock[
|
||||||
(cb-state player state-hash)]
|
(cb-state player current-player-state state-hash)]
|
||||||
|
|
||||||
where @racket[player] is the player handle and @racket[state-hash] is the most
|
where @racket[player] is the player handle,
|
||||||
recent state snapshot received from the worker side. The callback is called
|
@racket[current-player-state] is the logical player state reported by the
|
||||||
from the event thread created by @racket[make-audio-player].
|
worker, and @racket[state-hash] is the most recent state snapshot received from
|
||||||
|
the worker side. The callback is called from the event thread created by
|
||||||
|
@racket[make-audio-player].
|
||||||
|
|
||||||
|
The worker-side player state is one of the following symbols:
|
||||||
|
|
||||||
|
@itemlist[
|
||||||
|
#:style 'compact
|
||||||
|
|
||||||
|
@item{@racket['stopped] -- no stream is currently playing. This is the
|
||||||
|
initial state of the placed player. The player also enters this state after
|
||||||
|
@racket[audio-stop!] or after the decoder has reached the end of the stream
|
||||||
|
and the libao output queue has drained.}
|
||||||
|
|
||||||
|
@item{@racket['playing] -- a stream is active. The decoder may still be
|
||||||
|
reading from the input file, or the decoder may already have finished while
|
||||||
|
libao is still playing queued PCM samples.}
|
||||||
|
|
||||||
|
@item{@racket['paused] -- playback is paused. The current stream is retained
|
||||||
|
and the libao output side is paused. Resuming playback moves the player back
|
||||||
|
to @racket['playing].}
|
||||||
|
|
||||||
|
@item{@racket['quit] -- the placed player has been asked to terminate. This
|
||||||
|
is the terminal state of the worker.}
|
||||||
|
]
|
||||||
|
|
||||||
|
The wrapper around the placed player may also report these states through
|
||||||
|
@racket[audio-state]:
|
||||||
|
|
||||||
|
@itemlist[
|
||||||
|
#:style 'compact
|
||||||
|
|
||||||
|
@item{@racket['initialized] -- the audio handle has been created, but no
|
||||||
|
worker-side state snapshot has been received yet.}
|
||||||
|
|
||||||
|
@item{@racket['invalid] -- the audio handle is no longer valid. This happens
|
||||||
|
after @racket[audio-quit!] or when the underlying place or thread has stopped.}
|
||||||
|
]
|
||||||
|
|
||||||
|
The @racket[state-hash] contains the detailed playback state reported by the
|
||||||
|
worker. It includes values such as the current playback position, stream
|
||||||
|
duration, buffer status, music id, and libao handle validity. Code that only
|
||||||
|
needs the logical playback state should use @racket[current-player-state]
|
||||||
|
instead of extracting it from the hash.
|
||||||
|
|
||||||
The @racket[cb-eof-stream] callback is called as:
|
The @racket[cb-eof-stream] callback is called as:
|
||||||
|
|
||||||
@@ -52,6 +95,8 @@ that the decoder has finished queueing the stream. The audio device may still
|
|||||||
have buffered samples to play, and the logical player state may move to
|
have buffered samples to play, and the logical player state may move to
|
||||||
@racket['stopped] slightly later when the output queue has drained.
|
@racket['stopped] slightly later when the output queue has drained.
|
||||||
|
|
||||||
|
End-of-stream is not represented as a separate player state.
|
||||||
|
|
||||||
When @racket[use-place] is true, @racket[make-audio-player] starts
|
When @racket[use-place] is true, @racket[make-audio-player] starts
|
||||||
@racket[placed-player] with @racket[dynamic-place] and communicates with it
|
@racket[placed-player] with @racket[dynamic-place] and communicates with it
|
||||||
through place channels. When @racket[use-place] is false, the same command loop
|
through place channels. When @racket[use-place] is false, the same command loop
|
||||||
@@ -65,6 +110,7 @@ other active threads in the main VM. Those delays can otherwise be heard as
|
|||||||
clicks, gaps, or stuttering playback. Thread mode is useful for debugging the
|
clicks, gaps, or stuttering playback. Thread mode is useful for debugging the
|
||||||
protocol and callbacks, but it is not the preferred mode for robust playback.}
|
protocol and callbacks, but it is not the preferred mode for robust playback.}
|
||||||
|
|
||||||
|
|
||||||
@defproc[(audio-play? [v any/c]) boolean?]{
|
@defproc[(audio-play? [v any/c]) boolean?]{
|
||||||
Returns @racket[#t] when @racket[v] is a currently valid audio player handle.
|
Returns @racket[#t] when @racket[v] is a currently valid audio player handle.
|
||||||
|
|
||||||
@@ -157,6 +203,40 @@ is lowered to @racket[10]. A @racket[max] below @racket[min] is changed to
|
|||||||
@racket[30]. The worker side applies its own safe ordering and clamping before
|
@racket[30]. The worker side applies its own safe ordering and clamping before
|
||||||
using the values. In normal use the return value is @racket['ok].}
|
using the values. In normal use the return value is @racket['ok].}
|
||||||
|
|
||||||
|
@deftogether[
|
||||||
|
(@defproc[(audio-ao-buf-ms! [handle audio-play?]
|
||||||
|
[ms integer?])
|
||||||
|
(or/c integer? boolean?)]
|
||||||
|
@defproc[(audio-ao-buf-ms [handle audio-play?])
|
||||||
|
(or/c integer? boolean?)])
|
||||||
|
]{
|
||||||
|
|
||||||
|
Sets or queries the libao output buffer size, expressed in milliseconds.
|
||||||
|
|
||||||
|
The @racket[audio-ao-buf-ms!] procedure forwards @racket[ms] to the audio
|
||||||
|
player backend by sending the @racket['ao-buf-ms] RPC command. This hooks
|
||||||
|
into the libao-side buffer configuration and can be used to tune the amount of
|
||||||
|
audio data that the output layer keeps ahead of playback.
|
||||||
|
|
||||||
|
The @racket[audio-ao-buf-ms] procedure queries the currently configured value
|
||||||
|
by sending the same RPC command without a new value.
|
||||||
|
|
||||||
|
The returned value is the value reported by the backend. Normally this is an
|
||||||
|
integer number of milliseconds. A boolean result indicates that the value could
|
||||||
|
not be set or queried, or that the backend reported a non-numeric status.
|
||||||
|
|
||||||
|
Larger buffer values can make playback more robust against short scheduling
|
||||||
|
delays, but also increase latency. Smaller values reduce latency, but may make
|
||||||
|
drop-outs more likely when the decoder or GUI thread is temporarily delayed.
|
||||||
|
|
||||||
|
The value is clamped between 50 and 1000ms.
|
||||||
|
|
||||||
|
@racketblock[
|
||||||
|
(audio-ao-buf-ms! player 500)
|
||||||
|
(audio-ao-buf-ms player)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@section{State snapshots}
|
@section{State snapshots}
|
||||||
|
|
||||||
The player keeps a local cache of the most recent state snapshot received from
|
The player keeps a local cache of the most recent state snapshot received from
|
||||||
|
|||||||
Reference in New Issue
Block a user