#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.