mp3 decoder documented

This commit is contained in:
2026-04-22 14:10:57 +02:00
parent 57eea1b000
commit 9b0df72d33
7 changed files with 200 additions and 39 deletions
+149
View File
@@ -0,0 +1,149 @@
#lang scribble/manual
@(require racket/base
(for-label racket/base
racket/path
"../mp3-decoder.rkt"))
@title{mp3-decoder}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
@defmodule[racket-sound/mp3-decoder]
This module provides an MP3 decoder implementation for
@racketmodname[racket-sound/audio-decoder]. It opens an MP3 file,
reports stream information through a callback, streams decoded PCM
buffers, and supports stopping and seeking.
Compared to @racketmodname[racket-sound/flac-decoder], this module
stores its stream information directly in a mutable hash and updates
that hash in place during decoding.
@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}
@defproc[(mp3-valid? [file any/c]) boolean?]{
Returns @racket[#t].
This procedure does not inspect @racket[file]. It exists to satisfy the
reader interface expected by
@racketmodname[racket-sound/audio-decoder].
In practice, basic validation such as file existence and extension
checking is already performed by
@racketmodname[racket-sound/audio-decoder]. This procedure therefore
acts only as an additional hook and currently accepts all inputs.
}
@section{Opening}
@defproc[(mp3-open [mp3-file* (or/c path? string?)]
[cb-stream-info procedure?]
[cb-audio procedure?])
(or/c mp3-handle? #f)]{
Opens an MP3 decoder for @racket[mp3-file*].
If @racket[mp3-file*] is a path, it is converted with
@racket[path->string]. If the file does not exist, the result is
@racket[#f].
Otherwise a decoder handle is created and initialized. During
initialization, stream information is collected and stored in a
mutable hash in the handle.
The stream-info callback is invoked once, immediately after
initialization:
@racketblock[
(cb-stream-info info)
]
where @racket[info] is a mutable hash containing at least:
@itemlist[#:style 'compact
@item{@racket['duration] --- stream duration in seconds;}
@item{@racket['sample-rate] --- samples per second per channel;}
@item{@racket['channels] --- number of channels;}
@item{@racket['bits-per-sample] --- bits per sample;}
@item{@racket['bytes-per-sample] --- bytes per sample;}
@item{@racket['total-samples] --- total PCM sample count.}]
}
@section{Reading}
@defproc[(mp3-read [handle mp3-handle?]) any/c]{
Starts the decode loop for @racket[handle].
The loop repeatedly decodes audio chunks and invokes the audio
callback:
@racketblock[
(cb-audio info buffer size)
]
Before each callback, the @racket[info] hash is updated in place with:
@itemlist[#:style 'compact
@item{@racket['sample] --- current PCM sample position;}
@item{@racket['current-time] --- current playback time in seconds.}]
The loop terminates when either:
@itemlist[#:style 'compact
@item{the backend reports end-of-stream, or}
@item{a stop has been requested via @racket[mp3-stop].}]
If a stop is detected, the procedure returns
@racket['stopped-reading].
After termination, the underlying decoder is closed and released.
The return value is otherwise unspecified.
}
@section{Seeking}
@defproc[(mp3-seek [handle mp3-handle?]
[percentage number?])
void?]{
Seeks within the stream by percentage of the total length.
The @racket[percentage] argument is interpreted as a value between
@racket[0] and @racket[100]. The target position is computed from the
value stored under @racket['total-samples] in the stream-info hash.
If the total sample count is unavailable, this procedure has no effect.
}
@section{Stopping}
@defproc[(mp3-stop [handle mp3-handle?]) void?]{
Requests termination of an active @racket[mp3-read] loop.
The procedure sets an internal stop flag and waits until the read loop
has terminated, sleeping briefly between checks.
}
@section{Notes}
The stream-info hash is shared between initialization and decoding and
is updated in place during playback.
The audio buffer passed to the callback is managed by the decoder and
should be treated as transient data.
Although the handle contains a field related to deferred seeking, the
current implementation performs seeking directly in
@racket[mp3-seek].