Opus decoder is now documented
This commit is contained in:
+62
-12
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
@(require racket/base
|
@(require racket/base
|
||||||
(for-label racket/base
|
(for-label racket/base
|
||||||
|
racket/contract
|
||||||
racket/path
|
racket/path
|
||||||
"../audio-decoder.rkt"))
|
"../audio-decoder.rkt"
|
||||||
|
"../audio-sniffer.rkt"))
|
||||||
|
|
||||||
@title{audio-decoder}
|
@title{audio-decoder}
|
||||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||||
@@ -15,9 +17,9 @@ decoders. A backend is selected from the filename extension and is then
|
|||||||
used through a uniform interface for opening, reading, seeking, and
|
used through a uniform interface for opening, reading, seeking, and
|
||||||
stopping.
|
stopping.
|
||||||
|
|
||||||
The module includes built-in readers for FLAC and MP3, and it allows
|
The module includes built-in readers for FLAC, MP3, Opus via
|
||||||
additional backends to be registered with
|
@tt{libopusfile}, and FFmpeg-backed formats. Additional backends can be
|
||||||
@racket[audio-register-reader!].
|
registered with @racket[audio-register-reader!].
|
||||||
|
|
||||||
@section{Reader registration}
|
@section{Reader registration}
|
||||||
|
|
||||||
@@ -79,8 +81,8 @@ otherwise.
|
|||||||
|
|
||||||
Returns the reader type stored in @racket[handle].
|
Returns the reader type stored in @racket[handle].
|
||||||
|
|
||||||
For the built-in readers this is either @racket['flac] or
|
For the built-in readers this is usually @racket['flac], @racket['mp3],
|
||||||
@racket['mp3].
|
@racket['opusfile], or @racket['ffmpeg].
|
||||||
}
|
}
|
||||||
|
|
||||||
@section{Known extensions and validation}
|
@section{Known extensions and validation}
|
||||||
@@ -89,9 +91,56 @@ For the built-in readers this is either @racket['flac] or
|
|||||||
|
|
||||||
Returns the list of known filename extensions.
|
Returns the list of known filename extensions.
|
||||||
|
|
||||||
The initial list contains @racket["flac"] and @racket["mp3"].
|
The initial list contains the extensions handled by the built-in readers:
|
||||||
Additional extensions are added when readers are registered with
|
@racket["flac"], @racket["mp3"], Opus/Ogg extensions such as
|
||||||
@racket[audio-register-reader!].
|
@racket["opus"], @racket["ogg"], and @racket["oga"], and the extensions
|
||||||
|
handled by the FFmpeg reader. Additional extensions are added when
|
||||||
|
readers are registered with @racket[audio-register-reader!].
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@defproc[(audio-supported-extensions) (listof string?)]{
|
||||||
|
|
||||||
|
Returns the current list of supported filename extensions. This is the
|
||||||
|
same list returned by @racket[audio-known-exts?].
|
||||||
|
}
|
||||||
|
|
||||||
|
@section{Built-in decoder selection}
|
||||||
|
|
||||||
|
The built-in decoder table contains:
|
||||||
|
|
||||||
|
@itemlist[#:style 'compact
|
||||||
|
@item{@racket['flac] for FLAC files;}
|
||||||
|
@item{@racket['mp3] for MP3 files;}
|
||||||
|
@item{@racket['opusfile] for Ogg Opus streams through Xiph
|
||||||
|
@tt{libopusfile};}
|
||||||
|
@item{@racket['ffmpeg] for formats handled by the FFmpeg backend.}]
|
||||||
|
|
||||||
|
When a file is opened, the module first asks
|
||||||
|
@racket[audio-sniff-format/extension] for the detected format. Opus
|
||||||
|
streams are mapped to @racket['opusfile] when @tt{libopusfile} is
|
||||||
|
available and to @racket['ffmpeg] otherwise.
|
||||||
|
|
||||||
|
The Opusfile decoder has one global setting for its PCM output format:
|
||||||
|
@racket[current-opusfile-output-format]. This setting is re-exported by
|
||||||
|
this module for callers that use only the generic decoder API.
|
||||||
|
|
||||||
|
@defthing[current-opusfile-output-format procedure?]{
|
||||||
|
|
||||||
|
A global setting procedure for the Opusfile backend. Called without
|
||||||
|
arguments, it returns the current Opusfile output format. Called with one
|
||||||
|
argument, it sets the output format. Accepted values are @racket['s16]
|
||||||
|
and @racket['s24].
|
||||||
|
|
||||||
|
The default is @racket['s16]. Set it to @racket['s24] before opening or
|
||||||
|
reading an Opus file when the playback pipeline should receive packed
|
||||||
|
signed 24-bit PCM instead of signed 16-bit PCM.
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(opusfile-output-format? [v any/c]) boolean?]{
|
||||||
|
|
||||||
|
Returns @racket[#t] when @racket[v] is one of the supported Opusfile
|
||||||
|
output format symbols: @racket['s16] or @racket['s24].
|
||||||
}
|
}
|
||||||
|
|
||||||
@defproc[(audio-valid-ext? [ext any/c]) boolean?]{
|
@defproc[(audio-valid-ext? [ext any/c]) boolean?]{
|
||||||
@@ -146,7 +195,8 @@ where:
|
|||||||
|
|
||||||
@itemlist[#:style 'compact
|
@itemlist[#:style 'compact
|
||||||
@item{@racket[audio-type] is the registered reader type, such as
|
@item{@racket[audio-type] is the registered reader type, such as
|
||||||
@racket['flac] or @racket['mp3];}
|
@racket['flac], @racket['mp3], @racket['opusfile], or
|
||||||
|
@racket['ffmpeg];}
|
||||||
@item{@racket[ao-type] is the audio-output type stored in the reader,
|
@item{@racket[ao-type] is the audio-output type stored in the reader,
|
||||||
such as @racket['flac] or @racket['ao];}
|
such as @racket['flac] or @racket['ao];}
|
||||||
@item{@racket[handle] is the generic @racket[audio-handle];}
|
@item{@racket[handle] is the generic @racket[audio-handle];}
|
||||||
@@ -255,5 +305,5 @@ A backend integrated through this interface should provide:
|
|||||||
backend produces.}]
|
backend produces.}]
|
||||||
|
|
||||||
Once registered, files with matching extensions can be opened through
|
Once registered, files with matching extensions can be opened through
|
||||||
@racket[audio-open] in the same way as the built-in FLAC and MP3
|
@racket[audio-open] in the same way as the built-in FLAC, MP3,
|
||||||
backends.
|
Opusfile, and FFmpeg backends.
|
||||||
@@ -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