This commit is contained in:
2026-04-22 14:14:26 +02:00
parent 9b0df72d33
commit 25ba461196
+54 -55
View File
@@ -10,37 +10,27 @@
@defmodule[racket-sound/mp3-decoder] @defmodule[racket-sound/mp3-decoder]
This module provides an MP3 decoder implementation for This module provides an MP3 decoder backend. It opens an MP3 file,
@racketmodname[racket-sound/audio-decoder]. It opens an MP3 file,
reports stream information through a callback, streams decoded PCM reports stream information through a callback, streams decoded PCM
buffers, and supports stopping and seeking. buffers, and supports stopping and seeking.
Compared to @racketmodname[racket-sound/flac-decoder], this module The module is intended to be used through
stores its stream information directly in a mutable hash and updates @racketmodname[racket-sound/audio-decoder], but its procedures can also
that hash in place during decoding. be used directly.
@section{Handles}
@defproc[(mp3-handle? [v any/c]) boolean?]{
Returns @racket[#t] if @racket[v] is an MP3 decoder handle, and
@racket[#f] otherwise.
}
@section{Validation} @section{Validation}
@defproc[(mp3-valid? [file any/c]) boolean?]{ @defproc[(mp3-valid? [mp3-file any/c]) boolean?]{
Returns @racket[#t]. Returns #t.
This procedure does not inspect @racket[file]. It exists to satisfy the The current implementation does not inspect mp3-file. This procedure
reader interface expected by exists to satisfy the reader interface used by
@racketmodname[racket-sound/audio-decoder]. @racketmodname[racket-sound/audio-decoder].
In practice, basic validation such as file existence and extension Basic validation such as file existence and extension matching is
checking is already performed by performed in the higher-level module. This procedure therefore acts as
@racketmodname[racket-sound/audio-decoder]. This procedure therefore an additional hook and currently accepts all inputs.
acts only as an additional hook and currently accepts all inputs.
} }
@section{Opening} @section{Opening}
@@ -48,17 +38,16 @@ acts only as an additional hook and currently accepts all inputs.
@defproc[(mp3-open [mp3-file* (or/c path? string?)] @defproc[(mp3-open [mp3-file* (or/c path? string?)]
[cb-stream-info procedure?] [cb-stream-info procedure?]
[cb-audio procedure?]) [cb-audio procedure?])
(or/c mp3-handle? #f)]{ (or/c struct? #f)]{
Opens an MP3 decoder for @racket[mp3-file*]. Opens an MP3 decoder for mp3-file*.
If @racket[mp3-file*] is a path, it is converted with If mp3-file* is a path, it is converted with path->string. If the file
@racket[path->string]. If the file does not exist, the result is does not exist, the result is #f.
@racket[#f].
Otherwise a decoder handle is created and initialized. During Otherwise a decoder handle is created and initialized. During
initialization, stream information is collected and stored in a initialization, stream information is collected and stored in a mutable
mutable hash in the handle. hash in the handle.
The stream-info callback is invoked once, immediately after The stream-info callback is invoked once, immediately after
initialization: initialization:
@@ -67,22 +56,22 @@ initialization:
(cb-stream-info info) (cb-stream-info info)
] ]
where @racket[info] is a mutable hash containing at least: where info is a mutable hash containing at least:
@itemlist[#:style 'compact @itemlist[#:style 'compact
@item{@racket['duration] --- stream duration in seconds;} @item{'duration}
@item{@racket['sample-rate] --- samples per second per channel;} @item{'sample-rate}
@item{@racket['channels] --- number of channels;} @item{'channels}
@item{@racket['bits-per-sample] --- bits per sample;} @item{'bits-per-sample}
@item{@racket['bytes-per-sample] --- bytes per sample;} @item{'bytes-per-sample}
@item{@racket['total-samples] --- total PCM sample count.}] @item{'total-samples}]
} }
@section{Reading} @section{Reading}
@defproc[(mp3-read [handle mp3-handle?]) any/c]{ @defproc[(mp3-read [handle struct?]) any/c]{
Starts the decode loop for @racket[handle]. Starts the decode loop for handle.
The loop repeatedly decodes audio chunks and invokes the audio The loop repeatedly decodes audio chunks and invokes the audio
callback: callback:
@@ -91,20 +80,23 @@ callback:
(cb-audio info buffer size) (cb-audio info buffer size)
] ]
Before each callback, the @racket[info] hash is updated in place with: Before each callback, the info hash is updated in place with:
@itemlist[#:style 'compact @itemlist[#:style 'compact
@item{@racket['sample] --- current PCM sample position;} @item{'sample}
@item{@racket['current-time] --- current playback time in seconds.}] @item{'current-time}]
The loop also checks for a pending seek request. If a seek has been
requested, the stored target sample position is forwarded to the
decoder backend and the request is cleared.
The loop terminates when either: The loop terminates when either:
@itemlist[#:style 'compact @itemlist[#:style 'compact
@item{the backend reports end-of-stream, or} @item{the backend reports end-of-stream}
@item{a stop has been requested via @racket[mp3-stop].}] @item{a stop has been requested via mp3-stop}]
If a stop is detected, the procedure returns If a stop is detected, the procedure returns 'stopped-reading.
@racket['stopped-reading].
After termination, the underlying decoder is closed and released. After termination, the underlying decoder is closed and released.
@@ -113,24 +105,32 @@ The return value is otherwise unspecified.
@section{Seeking} @section{Seeking}
@defproc[(mp3-seek [handle mp3-handle?] @defproc[(mp3-seek [handle struct?]
[percentage number?]) [percentage number?])
void?]{ void?]{
Seeks within the stream by percentage of the total length. Requests a seek within the stream.
The @racket[percentage] argument is interpreted as a value between The percentage argument represents a position relative to the full
@racket[0] and @racket[100]. The target position is computed from the audio stream, where 0 is the start and 100 is the end. The value may be
value stored under @racket['total-samples] in the stream-info hash. fractional.
If the total sample count is unavailable, this procedure has no effect. If the total number of samples is available in the handle, the
procedure computes an absolute target sample and stores it in the
handle as a pending seek request.
The actual seek operation is performed later by mp3-read in its decode
loop.
If the total number of samples is unavailable or equal to -1, this
procedure has no effect.
} }
@section{Stopping} @section{Stopping}
@defproc[(mp3-stop [handle mp3-handle?]) void?]{ @defproc[(mp3-stop [handle struct?]) void?]{
Requests termination of an active @racket[mp3-read] loop. Requests termination of an active mp3-read loop.
The procedure sets an internal stop flag and waits until the read loop The procedure sets an internal stop flag and waits until the read loop
has terminated, sleeping briefly between checks. has terminated, sleeping briefly between checks.
@@ -144,6 +144,5 @@ is updated in place during playback.
The audio buffer passed to the callback is managed by the decoder and The audio buffer passed to the callback is managed by the decoder and
should be treated as transient data. should be treated as transient data.
Although the handle contains a field related to deferred seeking, the Seeking is implemented as a request stored in the handle and executed
current implementation performs seeking directly in by the decode loop, not directly by mp3-seek.
@racket[mp3-seek].