#lang scribble/manual @(require racket/base (for-label racket/base racket/contract racket/path "../opusfile-decoder.rkt")) @title{opusfile-decoder} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @defmodule[racket-audio/opusfile-decoder] This module provides an Opus decoder backend based on Xiph @tt{libopusfile}. It opens Ogg Opus files, reports stream information through a callback, streams decoded interleaved PCM buffers, and supports stopping and seeking. The module is intended to be used through @racketmodname[racket-audio/audio-decoder], but its procedures can also be used directly. Opus decoding produces 48 kHz PCM. The original input rate stored in an Opus file, if present, is not the decoder output sample rate. @section{Availability} @defproc[(opusfile-available?) boolean?]{ Returns @racket[#t] if @tt{libopusfile} and the native procedures used by this backend could be loaded, and @racket[#f] otherwise. The generic @racketmodname[racket-audio/audio-decoder] module prefers this decoder for Opus streams when it is available. If it is not available, Opus files may still be handled by the FFmpeg backend if that backend is available. } @section{Output format setting} @defproc[(opusfile-output-format? [v any/c]) boolean?]{ Returns @racket[#t] when @racket[v] is one of the supported Opus decoder output formats: @racket['s16] or @racket['s24]. } @defthing[current-opusfile-output-format procedure?]{ A global output-format setting for this decoder. Called without arguments, it returns the current output format. Called with one argument, it sets the current output format and returns the new value. The accepted values are: @itemlist[#:style 'compact @item{@racket['s16] --- signed 16-bit interleaved PCM. This is the default. The backend uses @tt{op_read}.} @item{@racket['s24] --- packed signed 24-bit interleaved PCM in native byte order. The backend uses @tt{op_read_float} and converts the float samples to 24-bit PCM.}] The setting is global. It is read when stream format hashes are produced and when audio buffers are decoded. For normal use, set it before opening or reading an Opus file. Example: @racketblock[ (current-opusfile-output-format 's24) ] This binding is also re-exported by @racketmodname[racket-audio/audio-decoder] and by @racketmodname[racket-audio]. } @section[#:tag "opusfile-decoder-validation"]{Validation} @defproc[(opusfile-valid? [audio-file any/c]) boolean?]{ Returns @racket[#t] when @tt{libopusfile} is available and @racket[audio-file] exists. This predicate is deliberately small. Detailed validation is performed when the file is opened by @racket[opusfile-open]. The generic decoder layer also performs extension and existence checks before opening a file. } @section[#:tag "opusfile-decoder-opening"]{Opening} @defproc[(opusfile-open [audio-file (or/c path? string?)] [cb-stream-info procedure?] [cb-audio procedure?]) (or/c struct? #f)]{ Opens @racket[audio-file] with @tt{libopusfile} and returns an opaque Opus decoder handle. If @racket[audio-file] is a path, it is converted with @racket[path->string]. If the file does not exist, the result is @racket[#f]. If @tt{libopusfile} cannot be loaded, an exception is raised. If the file exists but cannot be opened by @tt{libopusfile}, an exception is raised with the native Opusfile error code. The stream-info callback is called once after the file has been opened: @racketblock[ (cb-stream-info info) ] where @racket[info] is a mutable hash containing at least: @itemlist[#:style 'compact @item{@racket['duration] --- duration in seconds, based on the total number of decoded PCM samples when available;} @item{@racket['sample-rate] --- always @racket[48000];} @item{@racket['channels] --- number of decoded channels;} @item{@racket['bits-per-sample] --- @racket[16] for @racket['s16] and @racket[24] for @racket['s24];} @item{@racket['bytes-per-sample] --- @racket[2] for @racket['s16] and @racket[3] for @racket['s24];} @item{@racket['sample-format] --- the value of @racket[current-opusfile-output-format];} @item{@racket['total-samples] --- total number of decoded PCM samples, or the value reported by @tt{libopusfile}.}] } @section[#:tag "opusfile-decoder-reading"]{Reading} @defproc[(opusfile-read [handle struct?]) any/c]{ Starts the decode loop for @racket[handle]. The loop repeatedly decodes audio blocks and invokes the audio callback: @racketblock[ (cb-audio info buffer size) ] where @racket[info] is the mutable stream-info hash, @racket[buffer] is an interleaved PCM buffer, and @racket[size] is the buffer size in bytes. Before each callback, the info hash is updated in place with: @itemlist[#:style 'compact @item{@racket['sample] --- the current decoded sample position;} @item{@racket['current-time] --- the current decoded time in seconds.}] The buffer format is determined by @racket[current-opusfile-output-format]. In @racket['s16] mode the buffer contains signed 16-bit PCM. In @racket['s24] mode the buffer contains packed signed 24-bit PCM. The loop also checks for a pending seek request. If a seek has been requested with @racket[opusfile-seek], it is applied before the next read from @tt{libopusfile}. The loop terminates at end-of-stream or when a stop has been requested with @racket[opusfile-stop]. After termination, the underlying @tt{libopusfile} handle is freed. } @section[#:tag "opusfile-decoder-seeking"]{Seeking} @defproc[(opusfile-seek [handle struct?] [percentage number?]) void?]{ Requests a seek within the stream. The @racket[percentage] argument represents a position relative to the full stream, where @racket[0] is the start and @racket[100] is the end. The value may be fractional and is clamped to that range. If the total sample count is known and non-zero, the procedure computes a target decoded sample and stores it as a pending seek request. The actual native seek is performed later by @racket[opusfile-read]. } @section[#:tag "opusfile-decoder-stopping"]{Stopping} @defproc[(opusfile-stop [handle struct?]) void?]{ Requests termination of an active @racket[opusfile-read] loop. The procedure sets an internal stop flag and waits until the read loop has terminated, sleeping briefly between checks. } @section[#:tag "opusfile-decoder-notes"]{Notes} The Opusfile backend is registered in @racketmodname[racket-audio/audio-decoder] as reader type @racket['opusfile] for files and streams recognized as Opus. The generic decoder callbacks therefore receive @racket['opusfile] as @racket[audio-type] and @racket['ao] as @racket[ao-type].