Files
racket-audio/scrbl/ffmpeg-ffi.scrbl
T
2026-05-04 13:27:02 +02:00

166 lines
4.9 KiB
Racket

#lang scribble/manual
@(require racket/base
(for-label racket/base
racket/contract
racket/path
"../ffmpeg-ffi.rkt"))
@title{FFmpeg FFI}
@author{@author+email["Hans Dijkema" "hans@dijkewijk.nl"]}
@defmodule[racket-audio/ffmpeg-ffi]
This module provides the low-level Racket FFI binding for the native
FFmpeg audio shim. The native shim exposes an opaque FFmpeg instance and
keeps all decoder state inside that instance.
The output format of the native shim is signed 32-bit interleaved PCM.
The buffer returned by the native layer is copied into Racket-managed
memory before it is passed to higher layers.
@defproc[(fmpg-ffi-decoder-handler) procedure?]{
Creates a new FFmpeg decoder command handler.
The returned procedure manages one native FFmpeg instance. Commands are
sent as a symbol followed by command-specific arguments.
@itemlist[
#:style 'compact
@item{@racket['new] creates the native FFmpeg instance and returns @racket[#t].}
@item{@racket['delete] frees the native FFmpeg instance and returns @racket[#t].}
@item{@racket['init] opens a file and fetches stream and metadata information.}
@item{@racket['close] closes the currently opened file.}
@item{@racket['format] calls a format callback with the current stream format.}
@item{@racket['info] writes stream information to the sound logger.}
@item{@racket['read] decodes the next audio block.}
@item{@racket['seek] seeks to an absolute PCM sample position.}
@item{@racket['tell] returns the current PCM sample position.}
@item{@racket['file] returns the currently opened filename.}
@item{@racket['metadata] returns a hash with file metadata.}
]
}
@section{Command Interface}
The command handler is used as follows:
@racketblock[
(define h (fmpg-ffi-decoder-handler))
(h 'new)
(h 'init filename)
(h 'read audio-callback format-callback)
(h 'close)
(h 'delete)
]
The @racket['new] command must be called before @racket['init]. A
handler owns at most one native FFmpeg instance. Calling @racket['new]
twice without @racket['delete] raises an error.
@section{Format Callback}
The @racket['format] command and the first @racket['read] call report
the stream format by calling the supplied callback as follows:
@racketblock[
(format-callback pcm-pos
sample-rate
channels
bits-per-sample
bytes-per-sample
pcm-length)
]
The @racket[pcm-pos] argument is the current PCM sample position.
The @racket[pcm-length] argument is the total number of PCM samples, or
@racket[-1] when this is not known.
@section{Reading Audio}
The @racket['read] command decodes one audio block. It expects an audio
callback and a format callback:
@racketblock[
(h 'read audio-callback format-callback)
]
On the first read, the format callback is called before audio data is
returned. If decoding produces data, the audio callback is called as:
@racketblock[
(audio-callback 'data pcm-pos buffer size)
]
The @racket[pcm-pos] argument is the absolute sample position of the
first sample frame in the buffer. The @racket[buffer] argument points to
a copied PCM buffer, and @racket[size] is the buffer size in bytes.
When the stream ends, the callback is called as:
@racketblock[
(audio-callback 'done -1 #f 0)
]
The command returns @racket[#t].
@section{Seeking}
The @racket['seek] command takes an absolute PCM sample position:
@racketblock[
(h 'seek pcm-pos)
]
The sample position is converted to milliseconds using the current
sample rate and is then passed to the native FFmpeg shim. After seeking,
the current PCM position is updated from the native decoder.
@section{Metadata}
The @racket['metadata] command returns a mutable hash with the following
keys:
@itemlist[
#:style 'compact
@item{@racket['title]}
@item{@racket['author]}
@item{@racket['album]}
@item{@racket['genre]}
@item{@racket['comment]}
@item{@racket['copyright]}
@item{@racket['year]}
@item{@racket['track]}
@item{@racket['bitrate]}
@item{@racket['duration-ms]}
@item{@racket['audio-streams]}
]
Missing string fields are returned as empty strings. Missing numeric
fields are returned as @racket[-1].
@section{Native Library}
The module loads a shared library named @racket["ffmpeg_audio"] or
@racket["libffmpeg_audio"] using @racket[get-lib].
The native layer is expected to provide an instance-only FFmpeg API.
The relevant C-side properties are:
@itemlist[
#:style 'compact
@item{decoder state is stored in an opaque @tt{fmpg_instance};}
@item{output is signed 32-bit interleaved PCM;}
@item{the native buffer remains valid only until the next decode, seek,
close or free call;}
@item{Racket copies the buffer before passing it upward.}
]
@section{Errors}
Native failures are reported as Racket errors. Examples include failure
to allocate the native instance, failure to open a file and failure to
seek to a requested sample position.
Unknown commands also raise an error.