Documentation added.
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require (for-label racket/base
|
||||
racket/contract
|
||||
racket/path
|
||||
"../audio-encoder.rkt"))
|
||||
|
||||
@title{Audio Encoding}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule[racket-audio/audio-encoder]
|
||||
|
||||
The @racketmodname[racket-audio/audio-encoder] module provides the high level
|
||||
file-to-file encoding pipeline. It reuses the existing decoder environment to
|
||||
read the input file and sends the decoded PCM stream to a selected encoder
|
||||
backend. The built-in backends are Opus, implemented with @tt{libopusenc}, and
|
||||
FLAC, implemented with @tt{libFLAC}.
|
||||
|
||||
This module is intended as the public encoding API. The concrete backend
|
||||
modules are small FFI backends; applications normally call @racket[audio-encode]
|
||||
instead of using those modules directly.
|
||||
|
||||
@section{Pipeline}
|
||||
|
||||
Encoding is organised as a streaming pipeline:
|
||||
|
||||
@racketblock[
|
||||
input file
|
||||
;; decoded by audio-decoder.rkt
|
||||
-> PCM buffers
|
||||
;; optional conversion for FLAC
|
||||
-> encoder backend
|
||||
-> output file]
|
||||
|
||||
The encoder is selected from @racket[#:encoder] or, when that argument is not
|
||||
provided, from the output filename extension. The initial built-in encoders are
|
||||
@racket['opus] for @filepath{.opus} and @filepath{.oga} files, and
|
||||
@racket['flac] for @filepath{.flac} files.
|
||||
|
||||
The PCM stream is not collected in memory. Each decoded buffer is forwarded to
|
||||
the selected backend. FLAC encoding may insert a PCM conversion step when the
|
||||
settings request a different sample rate, channel count, or bit depth. Opus
|
||||
encoding feeds floating-point PCM to @tt{libopusenc}; sample-rate conversion for
|
||||
Opus is left to @tt{libopusenc}.
|
||||
|
||||
@section{Encoding a file}
|
||||
|
||||
@defproc[(audio-encode [input-file path-string?]
|
||||
[output-file path-string?]
|
||||
[settings hash?]
|
||||
[#:encoder encoder (or/c symbol? #f) #f]
|
||||
[#:copy-tags? copy-tags? boolean? #t]
|
||||
[#:progress-callback progress-callback
|
||||
(or/c procedure? #f) #f])
|
||||
hash?]{
|
||||
Encodes @racket[input-file] to @racket[output-file] and returns a result hash.
|
||||
The @racket[settings] hash is interpreted by the selected backend.
|
||||
|
||||
When @racket[encoder] is @racket[#f], the backend is inferred from the output
|
||||
file extension. Pass @racket['opus] or @racket['flac] to force a backend.
|
||||
|
||||
When @racket[copy-tags?] is true, common textual tags and an embedded picture
|
||||
are copied from the source file to the destination file. Opus comments and
|
||||
cover art are written before encoding starts through @tt{libopusenc}. FLAC
|
||||
metadata is copied after the encoded file has been written, using the TagLib
|
||||
wrapper.
|
||||
|
||||
When @racket[progress-callback] is a procedure, it is called with a progress
|
||||
hash during encoding. Progress is based on the number of input frames read from
|
||||
the decoder, not on the number of frames written by the encoder. This matters
|
||||
for resampling, because output frame counts can differ from input frame counts.}
|
||||
|
||||
@racketblock[
|
||||
(audio-encode "input.flac"
|
||||
"output.opus"
|
||||
(hash 'bitrate 224000
|
||||
'vbr? #t
|
||||
'complexity 10)
|
||||
#:encoder 'opus)
|
||||
|
||||
(audio-encode "input-96k.flac"
|
||||
"output-48k.flac"
|
||||
(hash 'sample-rate 48000
|
||||
'bits-per-sample 24
|
||||
'compression-level 8)
|
||||
#:encoder 'flac)]
|
||||
|
||||
@section{Result hash}
|
||||
|
||||
The result hash contains the following keys:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket['encoder], the selected backend symbol;}
|
||||
@item{@racket['input] and @racket['output], the source and destination paths;}
|
||||
@item{@racket['input-format], the final decoded input format hash seen by the
|
||||
pipeline;}
|
||||
@item{@racket['output-format], the resolved backend output format hash;}
|
||||
@item{@racket['frames-read], the number of input frames consumed;}
|
||||
@item{@racket['frames-written], the number of frames accepted by the backend;}
|
||||
@item{@racket['tag-copy], a hash describing how metadata was handled.}]
|
||||
|
||||
The @racket['tag-copy] hash contains a @racket['method] key. For Opus the
|
||||
method is @racket['libopusenc-comments], because metadata must be supplied to
|
||||
@tt{libopusenc} before the encoder writes the OpusTags packet. For FLAC the
|
||||
method is @racket['taglib-post-copy], because the encoded file is tagged after
|
||||
encoding.
|
||||
|
||||
@section{Progress callback}
|
||||
|
||||
The progress callback receives a hash with at least these keys:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket['phase], such as @racket['format], @racket['audio],
|
||||
@racket['finished-encoding], or @racket['finished];}
|
||||
@item{@racket['frames-read] and @racket['frames-written];}
|
||||
@item{@racket['total-frames], when the decoder reported a known input length;}
|
||||
@item{@racket['progress], a number between @racket[0.0] and @racket[1.0] when
|
||||
@racket['total-frames] is known, otherwise @racket[#f];}
|
||||
@item{@racket['input-format] and, after the backend has opened,
|
||||
@racket['output-format].}]
|
||||
|
||||
A simple command-line style progress callback can print a percentage on one
|
||||
line:
|
||||
|
||||
@racketblock[
|
||||
(define (show-progress h)
|
||||
(let ((p (hash-ref h 'progress #f)))
|
||||
(when (number? p)
|
||||
(printf "\rprogress: ~a%" (round (* 100 p)))
|
||||
(flush-output))))]
|
||||
|
||||
@section{Opus settings}
|
||||
|
||||
The Opus backend uses @tt{libopusenc}. The input PCM is converted to interleaved
|
||||
floating-point samples in the range @racket[-1.0] to @racket[1.0] and written
|
||||
with @tt{ope_encoder_write_float}. The source sample rate is passed to
|
||||
@tt{libopusenc}; @tt{libopusenc} performs the required internal resampling for
|
||||
Opus output.
|
||||
|
||||
The following settings are recognised:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket['bitrate], bitrate in bits per second. The default is
|
||||
@racket[160000].}
|
||||
@item{@racket['vbr?], whether variable bitrate is enabled. The default is
|
||||
@racket[#t].}
|
||||
@item{@racket['constrained-vbr?], whether constrained VBR is enabled. The
|
||||
default is @racket[#f].}
|
||||
@item{@racket['complexity], encoder complexity. The default is @racket[10].}
|
||||
@item{@racket['comment-padding], Opus comment padding in bytes. The default
|
||||
is @racket[512].}
|
||||
@item{@racket['signal], optionally @racket['auto], @racket['voice], or
|
||||
@racket['music].}
|
||||
@item{@racket['lsb-depth], optionally passed to the encoder as the source
|
||||
least significant bit depth.}
|
||||
@item{@racket['comments], an optional hash of Opus comment strings. When
|
||||
@racket[#:copy-tags?] is true, @racket[audio-encode] fills this from the
|
||||
source tags.}
|
||||
@item{@racket['picture], an optional picture value from @racketmodname[racket-audio/taglib].
|
||||
When @racket[#:copy-tags?] is true, @racket[audio-encode] fills this
|
||||
from the source tags.}]
|
||||
|
||||
The first backend version supports mono and stereo input.
|
||||
|
||||
@section{FLAC settings}
|
||||
|
||||
The FLAC backend uses the @tt{libFLAC} stream encoder. It writes interleaved
|
||||
integer PCM samples through the FLAC encoder API. When the requested output
|
||||
format differs from the decoded input format, @racketmodname[racket-audio/private/pcm-converter]
|
||||
uses the existing FFmpeg @tt{swresample} layer from
|
||||
@racketmodname[racket-audio/ffmpeg-definitions] to perform PCM normalisation.
|
||||
|
||||
The following settings are recognised:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket['compression-level], FLAC compression level. The default is
|
||||
@racket[5].}
|
||||
@item{@racket['verify?], whether the FLAC encoder verifies encoded output. The
|
||||
default is @racket[#f].}
|
||||
@item{@racket['blocksize], explicit FLAC block size. The default is
|
||||
@racket[0], meaning the library default.}
|
||||
@item{@racket['sample-rate] or @racket['target-sample-rate], target sample rate
|
||||
in Hz. Use @racket['source] or omit the key to keep the source rate.}
|
||||
@item{@racket['channels] or @racket['target-channels], target channel count.
|
||||
Use @racket['source] or omit the key to keep the source channel count.}
|
||||
@item{@racket['bits-per-sample] or @racket['target-bits-per-sample], target
|
||||
bit depth. Use @racket['source] or omit the key to keep the source bit
|
||||
depth.}]
|
||||
|
||||
For example, a 24-bit 96 kHz FLAC file can be transcoded to 24-bit 48 kHz FLAC
|
||||
with:
|
||||
|
||||
@racketblock[
|
||||
(audio-encode "input-96k.flac"
|
||||
"output-48k.flac"
|
||||
(hash 'sample-rate 48000
|
||||
'bits-per-sample 24
|
||||
'compression-level 8)
|
||||
#:encoder 'flac)]
|
||||
|
||||
@section{Encoder registration}
|
||||
|
||||
@defproc[(audio-supported-encoder-extensions) (listof string?)]{
|
||||
Returns the extensions supported by the currently registered encoders. The
|
||||
initial list includes @racket["flac"], @racket["opus"], and @racket["oga"].}
|
||||
|
||||
@defproc[(make-audio-encoder [exts (listof string?)]
|
||||
[open procedure?]
|
||||
[write procedure?]
|
||||
[finish procedure?]
|
||||
[settings procedure?])
|
||||
audio-encoder?]{
|
||||
Creates an encoder descriptor. The descriptor is used by
|
||||
@racket[audio-register-encoder!] to register a backend.
|
||||
|
||||
The @racket[open] procedure receives the output file, settings hash, and input
|
||||
format hash. The @racket[write] procedure receives the backend handle, buffer
|
||||
format hash, byte buffer, and byte length, and returns the number of frames
|
||||
accepted by the backend. The @racket[finish] procedure finalises and releases
|
||||
the backend handle. The @racket[settings] procedure resolves backend defaults
|
||||
against the input format and returns the output format hash.}
|
||||
|
||||
@defproc[(audio-encoder? [v any/c]) boolean?]{
|
||||
Returns @racket[#t] when @racket[v] is an encoder descriptor.}
|
||||
|
||||
@defproc[(audio-register-encoder! [type symbol?]
|
||||
[encoder audio-encoder?])
|
||||
void?]{
|
||||
Registers @racket[encoder] under @racket[type]. The encoder's extensions are
|
||||
used for extension-based selection in @racket[audio-encode].}
|
||||
@@ -0,0 +1,91 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require (for-label racket/base
|
||||
racket/path
|
||||
"../encoder-test.rkt"))
|
||||
|
||||
@title{Encoder Test Program}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule[racket-audio/encoder-test]
|
||||
|
||||
The @racketmodname[racket-audio/encoder-test] module is a small integration test
|
||||
and command-line wrapper around @racketmodname[racket-audio/audio-encoder]. It
|
||||
is useful for checking that the native encoder libraries are available and that
|
||||
a concrete source file can be transcoded to Opus or FLAC.
|
||||
|
||||
The module depends on @filepath{tests.rkt} for its default input file. For
|
||||
portable tests, pass an explicit input file.
|
||||
|
||||
@section{Program use}
|
||||
|
||||
Run the test module directly to encode the default test file to a temporary
|
||||
Opus file:
|
||||
|
||||
@verbatim{
|
||||
racket encoder-test.rkt
|
||||
}
|
||||
|
||||
Useful command-line examples:
|
||||
|
||||
@verbatim{
|
||||
racket encoder-test.rkt --encoder opus --input input.flac --output output.opus --bitrate-kbps 224
|
||||
|
||||
racket encoder-test.rkt --encoder flac --input input-96k.flac --output output-48k.flac --sample-rate 48000 --bits-per-sample 24 --compression-level 8
|
||||
}
|
||||
|
||||
The program prints the selected encoder, settings, percentage progress, and a
|
||||
summary of the result hash returned by @racket[audio-encode]. Progress is based
|
||||
on input frames read from the decoder.
|
||||
|
||||
@section{Program options}
|
||||
|
||||
The command-line wrapper accepts these options:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@tt{-e}, @tt{--encoder}: @tt{opus} or @tt{flac}.}
|
||||
@item{@tt{-i}, @tt{--input}: input audio file.}
|
||||
@item{@tt{-o}, @tt{--output}: output audio file.}
|
||||
@item{@tt{--sample-rate}: target sample rate or @tt{source}.}
|
||||
@item{@tt{--bits-per-sample}: target FLAC bit depth or @tt{source}.}
|
||||
@item{@tt{--bitrate-kbps}: Opus bitrate in kbit/s.}
|
||||
@item{@tt{--compression-level}: FLAC compression level.}
|
||||
@item{@tt{--no-tags}: disable copying tags and embedded pictures.}]
|
||||
|
||||
@section{Racket functions}
|
||||
|
||||
@defproc[(encoder-test [input-file path-string?]
|
||||
[output-file (or/c path-string? #f)]
|
||||
[encoder (or/c symbol? string?)]
|
||||
[settings hash?]
|
||||
[#:copy-tags? copy-tags? boolean? #t])
|
||||
hash?]{
|
||||
Runs one encode test and prints a human-readable summary. The return value is
|
||||
the result hash produced by @racket[audio-encode]. When @racket[output-file] is
|
||||
@racket[#f], a temporary output path is chosen from the encoder kind.}
|
||||
|
||||
@defproc[(encoder-test-opus [input-file path-string?]
|
||||
[output-file (or/c path-string? #f) #f]
|
||||
[#:bitrate-kbps bitrate-kbps exact-positive-integer? 160]
|
||||
[#:sample-rate sample-rate (or/c exact-positive-integer? 'source) 'source]
|
||||
[#:copy-tags? copy-tags? boolean? #t])
|
||||
hash?]{
|
||||
Encodes @racket[input-file] to an Opus file using @racket[encoder-test]. The
|
||||
bitrate argument is expressed in kbit/s and is converted to the @racket['bitrate]
|
||||
setting used by the Opus backend.
|
||||
|
||||
The @racket[sample-rate] argument is normally @racket['source]. Opus encoding
|
||||
passes the input rate to @tt{libopusenc}; @tt{libopusenc} performs the internal
|
||||
resampling required for Opus output.}
|
||||
|
||||
@defproc[(encoder-test-flac [input-file path-string?]
|
||||
[output-file (or/c path-string? #f) #f]
|
||||
[#:compression-level compression-level exact-nonnegative-integer? 8]
|
||||
[#:sample-rate sample-rate (or/c exact-positive-integer? 'source) 'source]
|
||||
[#:bits-per-sample bits-per-sample (or/c exact-positive-integer? 'source) 'source]
|
||||
[#:copy-tags? copy-tags? boolean? #t])
|
||||
hash?]{
|
||||
Encodes @racket[input-file] to a FLAC file using @racket[encoder-test]. When
|
||||
@racket[sample-rate] or @racket[bits-per-sample] is not @racket['source], the
|
||||
FLAC pipeline requests the corresponding output format from
|
||||
@racketmodname[racket-audio/audio-encoder].}
|
||||
@@ -21,6 +21,8 @@
|
||||
@include-section["audio-player.scrbl"]
|
||||
@include-section["audio-sniffer.scrbl"]
|
||||
@include-section["taglib.scrbl"]
|
||||
@include-section["audio-encoder.scrbl"]
|
||||
@include-section["encoder-test.scrbl"]
|
||||
@include-section["play-test.scrbl"]
|
||||
@include-section["audio-placed-player.scrbl"]
|
||||
@include-section["audio-decoder.scrbl"]
|
||||
|
||||
Reference in New Issue
Block a user