Files
2026-05-04 13:27:02 +02:00

149 lines
3.9 KiB
Racket

#lang scribble/manual
@(require racket/base
(for-label racket/base
racket/path
racket/contract
"../mp3-decoder.rkt"))
@title{mp3-decoder}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
@defmodule[racket-audio/mp3-decoder]
This module provides an MP3 decoder backend. It opens an MP3 file,
reports stream information through a callback, streams decoded PCM
buffers, and supports stopping and seeking.
The module is intended to be used through
@racketmodname[racket-sound/audio-decoder], but its procedures can also
be used directly.
@section{Validation}
@defproc[(mp3-valid? [mp3-file any/c]) boolean?]{
Returns #t.
The current implementation does not inspect mp3-file. This procedure
exists to satisfy the reader interface used by
@racketmodname[racket-sound/audio-decoder].
Basic validation such as file existence and extension matching is
performed in the higher-level module. This procedure therefore acts 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 struct? #f)]{
Opens an MP3 decoder for mp3-file*.
If mp3-file* is a path, it is converted with path->string. If the file
does not exist, the result is #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 info is a mutable hash containing at least:
@itemlist[#:style 'compact
@item{'duration}
@item{'sample-rate}
@item{'channels}
@item{'bits-per-sample}
@item{'bytes-per-sample}
@item{'total-samples}]
}
@section{Reading}
@defproc[(mp3-read [handle struct?]) any/c]{
Starts the decode loop for handle.
The loop repeatedly decodes audio chunks and invokes the audio
callback:
@racketblock[
(cb-audio info buffer size)
]
Before each callback, the info hash is updated in place with:
@itemlist[#:style 'compact
@item{'sample}
@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:
@itemlist[#:style 'compact
@item{the backend reports end-of-stream}
@item{a stop has been requested via mp3-stop}]
If a stop is detected, the procedure returns 'stopped-reading.
After termination, the underlying decoder is closed and released.
The return value is otherwise unspecified.
}
@section{Seeking}
@defproc[(mp3-seek [handle struct?]
[percentage number?])
void?]{
Requests a seek within the stream.
The percentage argument represents a position relative to the full
audio stream, where 0 is the start and 100 is the end. The value may be
fractional.
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}
@defproc[(mp3-stop [handle struct?]) void?]{
Requests termination of an active 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.
Seeking is implemented as a request stored in the handle and executed
by the decode loop, not directly by mp3-seek.