128 lines
4.3 KiB
Racket
128 lines
4.3 KiB
Racket
#lang scribble/manual
|
|
|
|
@(require racket/base
|
|
(for-label racket/base
|
|
racket/contract
|
|
racket/path
|
|
"../ffmpeg-decoder.rkt"))
|
|
|
|
@title{FFmpeg Decoder}
|
|
@author{@author+email["Hans Dijkema" "hans@dijkewijk.nl"]}
|
|
|
|
@defmodule[(file "../ffmpeg-decoder.rkt")]
|
|
|
|
This module provides an audio decoder based on the FFmpeg audio shim. It
|
|
uses the lower-level @racketmodname[racket-sound/ffmpeg-ffi] module and presents a
|
|
callback-based decoder interface comparable to the other audio decoders.
|
|
|
|
The native FFmpeg layer decodes audio to signed 32-bit interleaved PCM.
|
|
The decoder therefore reports 32 bits per sample and 4 bytes per sample
|
|
when no more specific information is available.
|
|
|
|
@defproc[(ffmpeg-valid? [audio-file any/c]) boolean?]{
|
|
Returns @racket[#t].
|
|
|
|
This predicate is deliberately weak. Existence and extension checks are
|
|
expected to be performed by the generic audio-decoder layer. Actual file
|
|
validation is done when the FFmpeg shim opens the file.
|
|
}
|
|
|
|
@defproc[(ffmpeg-open [audio-file (or/c path? string?)]
|
|
[cb-stream-info procedure?]
|
|
[cb-audio procedure?])
|
|
(or/c any/c #f)]{
|
|
Opens @racket[audio-file] and returns an opaque decoder handle, or
|
|
@racket[#f] if the file does not exist.
|
|
|
|
If @racket[audio-file] is a path, it is converted to a string before it
|
|
is passed to the native layer.
|
|
|
|
The @racket[cb-stream-info] callback is called with a mutable hash that
|
|
describes the stream. The @racket[cb-audio] callback is called with the
|
|
same kind of hash, a PCM buffer pointer and the buffer size in bytes.
|
|
}
|
|
|
|
@defproc[(ffmpeg-read [handle any/c]) any/c]{
|
|
Starts reading and decoding audio from @racket[handle].
|
|
|
|
This function loops until decoding reaches the end of the stream or
|
|
until @racket[ffmpeg-stop] requests termination. During the read loop,
|
|
pending seek requests made with @racket[ffmpeg-seek] are applied before
|
|
the next native read.
|
|
|
|
The stream-info callback is called when format information becomes
|
|
available. The audio callback is called as:
|
|
|
|
@racketblock[
|
|
(cb-audio info buffer size)
|
|
]
|
|
|
|
where @racket[info] is a mutable hash, @racket[buffer] is a pointer to
|
|
interleaved signed 32-bit PCM data, and @racket[size] is the size of the
|
|
buffer in bytes.
|
|
|
|
When reading stops, the native FFmpeg instance is closed and deleted.
|
|
}
|
|
|
|
@defproc[(ffmpeg-seek [handle any/c]
|
|
[percentage real?])
|
|
void?]{
|
|
Requests a seek operation.
|
|
|
|
The @racket[percentage] argument is interpreted as a percentage of the
|
|
total number of samples in the stream. Fractional percentages are
|
|
allowed. The actual seek is performed by @racket[ffmpeg-read] before the
|
|
next native read call.
|
|
|
|
If the total sample count is unknown or invalid, no seek request is made.
|
|
}
|
|
|
|
@defproc[(ffmpeg-stop [handle any/c]) void?]{
|
|
Requests the read loop to stop.
|
|
|
|
This function waits until @racket[ffmpeg-read] has left its read loop.
|
|
It polls the internal reading flag with a short sleep interval.
|
|
}
|
|
|
|
@section{Stream Information}
|
|
|
|
The stream-info and audio callbacks receive a mutable hash. The decoder
|
|
stores at least the following keys:
|
|
|
|
@itemlist[
|
|
#:style 'compact
|
|
@item{@racket['sample-rate]}
|
|
@item{@racket['channels]}
|
|
@item{@racket['bits-per-sample]}
|
|
@item{@racket['bytes-per-sample]}
|
|
@item{@racket['total-samples]}
|
|
@item{@racket['duration]}
|
|
]
|
|
|
|
For audio callbacks, the hash is also updated with:
|
|
|
|
@itemlist[
|
|
#:style 'compact
|
|
@item{@racket['sample], the current sample position}
|
|
@item{@racket['current-time], the current time in seconds}
|
|
]
|
|
|
|
If the native layer omits format values, the decoder fills in the most
|
|
recent known values. Initial defaults are 44100 Hz, 2 channels, 32 bits
|
|
per sample and 4 bytes per sample.
|
|
|
|
@section{Decoding Model}
|
|
|
|
The decoder keeps a small Racket handle around the native FFmpeg handler.
|
|
The handle stores the callbacks, stop and seek state, the current reading
|
|
state and the current format hash.
|
|
|
|
Seeking is asynchronous with respect to @racket[ffmpeg-seek]: the
|
|
function only records the requested target sample. The read loop applies
|
|
the pending seek request before decoding the next block.
|
|
|
|
@section{Notes}
|
|
|
|
The FFmpeg shim output is expected to be signed 32-bit interleaved PCM.
|
|
This keeps the decoder interface suitable for a playback pipeline that
|
|
feeds decoded audio to libao. |