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)
|
||||
(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)
|
||||
(let ((h (make-hash)))
|
||||
(with-mutex ao-mutex
|
||||
@@ -384,7 +392,7 @@
|
||||
|
||||
(let ((m-id (hash-ref h 'at-music-id)))
|
||||
(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
|
||||
((eq? cmd 'quit) (do-rpc
|
||||
(stop-and-cleanup)
|
||||
(set! player-state 'quit)
|
||||
(state "quit" evt 'force)
|
||||
'(quit)))
|
||||
((eq? cmd 'init) (do-rpc
|
||||
@@ -474,6 +483,12 @@
|
||||
(let ((st #f))
|
||||
(state "'state command" (λ (s) (set! st s)) 'force)
|
||||
st)))
|
||||
((eq? cmd 'ao-buf-ms)
|
||||
(do-rpc
|
||||
(if (null? (cdr data))
|
||||
(list (ao-buf-ms))
|
||||
(list (ao-buf-ms! (cadr data))))
|
||||
))
|
||||
(else
|
||||
(do-rpc
|
||||
(list 'error (format "Unknown command ~a" cmd))))
|
||||
|
||||
+15
-4
@@ -30,6 +30,8 @@
|
||||
audio-file
|
||||
audio-play?
|
||||
audio-buf-seconds!
|
||||
audio-ao-buf-ms!
|
||||
audio-ao-buf-ms
|
||||
audio-known-exts?
|
||||
)
|
||||
|
||||
@@ -128,7 +130,7 @@
|
||||
(cmd-put (cons cmd args)) (ret-get))))
|
||||
|
||||
(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))))
|
||||
(set! handle (make-audio-play #t
|
||||
cb-state* cb-eof*
|
||||
@@ -142,10 +144,11 @@
|
||||
(let loop ()
|
||||
(if (audio-play-valid? handle)
|
||||
(let ((e (evt-get 500)))
|
||||
(cond ((eq? e #f) (loop))
|
||||
(cond ((eq? e #f) (void))
|
||||
((is-event? e 'state)
|
||||
(set-audio-play-state! handle (evt-data e))
|
||||
(cb-state* (evt-data e)))
|
||||
(let ((data (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 'exception)
|
||||
(err-sound "audio-player: exception event: ~a" e))
|
||||
@@ -270,6 +273,14 @@
|
||||
(until (if (< max min) (+ min 1) (if (> max 30) 30 max))))
|
||||
((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)
|
||||
(early-return
|
||||
;; Both allocations are native because swr_convert reads/writes C pointers.
|
||||
((tmp (malloc max-bytes 'raw) ? (eq? tmp #f) => #f)
|
||||
(out-planes (malloc _pointer 1 'raw)
|
||||
? (eq? out-planes #f) => #f
|
||||
~ (free tmp))
|
||||
((tmp (malloc max-bytes 'atomic-interior) ? (eq? tmp #f) => #f)
|
||||
(out-planes (malloc _pointer 1 'atomic-interior)
|
||||
? (eq? out-planes #f) => #f)
|
||||
;; Interleaved output has one plane; out-planes[0] points to tmp.
|
||||
(do (ptr-set! out-planes _pointer 0 tmp))
|
||||
(result (proc tmp out-planes)))
|
||||
(free out-planes)
|
||||
(free tmp)
|
||||
result)
|
||||
)
|
||||
|
||||
@@ -1511,27 +1508,20 @@
|
||||
? (<= delay 0) => produced)
|
||||
(max-bytes (av_samples_get_buffer_size #f channels delay FMPG_OUTPUT_FMT 1)
|
||||
? (<= max-bytes 0) => produced)
|
||||
(tmp (malloc max-bytes 'raw)
|
||||
(tmp (malloc max-bytes 'atomic-interior)
|
||||
? (eq? tmp #f) => -1)
|
||||
(out-planes (malloc _pointer 1 'raw)
|
||||
? (eq? out-planes #f) => -1
|
||||
~ (free tmp))
|
||||
(out-planes (malloc _pointer 1 'atomic-interior)
|
||||
? (eq? out-planes #f) => -1)
|
||||
|
||||
(do (ptr-set! out-planes _pointer 0 tmp))
|
||||
|
||||
;; Null input with 0 samples asks swresample to flush delayed output.
|
||||
(out-samples (swr_convert swr-ctx out-planes delay #f 0)
|
||||
? (<= out-samples 0) => produced
|
||||
~ (begin
|
||||
(free out-planes)
|
||||
(free tmp)))
|
||||
? (<= out-samples 0) => produced)
|
||||
|
||||
(used-bytes (av_samples_get_buffer_size #f channels out-samples
|
||||
FMPG_OUTPUT_FMT 1)
|
||||
? (< used-bytes 0) => produced
|
||||
~ (begin
|
||||
(free out-planes)
|
||||
(free tmp)))
|
||||
? (< used-bytes 0) => produced)
|
||||
|
||||
;; If this is the first output, the buffer belongs to the current next-sample-pos.
|
||||
(do
|
||||
@@ -1542,17 +1532,12 @@
|
||||
sample-rate*)))))
|
||||
|
||||
(appended? (append-bytes! dec tmp used-bytes)
|
||||
? (not appended?) => -1
|
||||
~ (begin
|
||||
(free out-planes)
|
||||
(free tmp)))
|
||||
? (not appended?) => -1)
|
||||
)
|
||||
|
||||
(ds-last-samples! dec (+ (ds-last-samples dec) out-samples))
|
||||
(ds-next-sample-pos! dec (+ (ds-next-sample-pos dec)
|
||||
out-samples))
|
||||
(free out-planes)
|
||||
(free tmp)
|
||||
(loop 1))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
;; 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)
|
||||
ao-buf-ms)
|
||||
|
||||
@@ -36,11 +36,54 @@ all other procedures in this module.
|
||||
The @racket[cb-state] callback is called as:
|
||||
|
||||
@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
|
||||
recent state snapshot received from the worker side. The callback is called
|
||||
from the event thread created by @racket[make-audio-player].
|
||||
where @racket[player] is the player handle,
|
||||
@racket[current-player-state] is the logical player state reported by the
|
||||
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:
|
||||
|
||||
@@ -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
|
||||
@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
|
||||
@racket[placed-player] with @racket[dynamic-place] and communicates with it
|
||||
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
|
||||
protocol and callbacks, but it is not the preferred mode for robust playback.}
|
||||
|
||||
|
||||
@defproc[(audio-play? [v any/c]) boolean?]{
|
||||
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
|
||||
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}
|
||||
|
||||
The player keeps a local cache of the most recent state snapshot received from
|
||||
|
||||
Reference in New Issue
Block a user