Opus decoder is now documented
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
#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/main].
|
||||
}
|
||||
|
||||
@section{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{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{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{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].
|
||||
Reference in New Issue
Block a user