FLAC data stream encoder and decoder compiled to JavaScript using emscripten.
Features
- available as pure JavaScript, JavaScript+binary, JavaScript+WASM
- can be used in browsers as well as in
node.js
- encode/decode data all-at-once (~ file) or chunk-by-chunk (~ stream)
- supported container formats: native FLAC container (
*.flac
), OGG container (*.ogg
,*.oga
) - support for FLAC metadata extraction when decoding (STREAMINFO, VORBIS_COMMENT, PICTURE, CUESHEET, SEEKTABLE)
Complied from
libFLAC
(staticC
library) version: 1.3.3
Used librarylibogg
(staticC
library) version: 1.3.4
Used compilerEmscripten
version: 1.39.19
Used compilerEmscripten
toolchain: LLVM (upstream)
IMPORTANT changes for version
5.x
: simplified naming scheme and library location!
See details in 'CHANGELOG.md'.
Encoder Demo
Try the Encoding Demo for encoding *.wav
files to FLAC.
Or try the speech-to-flac demo that encodes the audio stream from a microphone to FLAC.
Decoder Demo
Try the Decoding Demo for decoding *.flac
files to *.wav
files.
TODO example for decoding a FLAC audio stream (i.e. where data/size is not known beforehand).
API Documentation
See doc/index.html for the API documentation.
- Usage
- Building
- Contributors
- Acknowledgments
- License
For immediate use, the /dist
sub-directory contains the compiled
files for the libflac.js
JavaScript library, as well as a minified
version and a development (with additional/extend debug output) version.
For more details, see section Library Variants.
Include the library file, e.g. if library file(s) libflac.js
is in the same
directory as the referencing HTML file:
<script src="libflac.js" type="text/javascript"></script>
Import the library file, e.g. if library file(s) libflac.js
is in the same
directory as the referencing worker script file:
importScripts('libflac.js');
In Node.js
:
install with npm
# install from npm
npm install --save libflacjs
# install latest from master branch
npm install --save git+https://github.com/mmig/libflac.js.git
then, use factory method for loading one of the library variants:
//load default/release asm.js variant:
var Flac = require('libflacjs')();
// use one of the optimization-variants:
// * <empty> / "release"
// * "min"
// * "dev"
// use one of the technology-variants:
// * <empty> / "asmjs"
// * "wasm"
//
// can be combined with dot, e.g. "min.wasm":
var FlacFactory = require('libflacjs');
var Flac = FlacFactory('min.wasm');
Flac.on('ready', function(event){
...
Alternatively, instead of loading via the factory method, the library variants
can also be require
d directly:
// for example:
var Flac = require('libflacjs/dist/libflac.js');
// or e.g. the WASM variant:
var Flac = require('libflacjs/dist/libflac.wasm.js');
For reactjs
:
install with npm
(see above), and require()
the library file directly, like
// for example:
var Flac = require('libflacjs/dist/libflac.js');
// or e.g. the WASM variant:
var Flac = require('libflacjs/dist/libflac.wasm.js');
NOTE
min
andwasm
variants will most likely require additional configuration of the build system, see also section about asyncwebpack
integration
For Angular
(TypeScript
):
install with npm
(see above), and import
the library file directly, like
// for example:
import * as Flac from 'libflacjs/dist/libflac';
// or e.g. the WASM variant:
import * as Flac from 'libflacjs/dist/libflac.wasm';
NOTE unfortunately, current typings do not allow to set Flac.onready
when imported this way.
This limitation can be worked around by casting to any
, i.e.
(Flac.onready as any) = (evt: Flac.ReadyEvent) => console.log('Flac is ready now: ', evt.target);
Or use Flac.on('ready', ...
instead, or import with require
statement, e.g. like
import * as FlacModule from 'libflacjs/dist/index.d';//import declaration file for typings
declare var require: Function;//NOTE most likely, the require function needs to be explicitly declared, if other envorinments than node are targeted
const Flac: typeof FlacModule = require('libflacjs/dist/libflac.js');
NOTE
min
andwasm
variants will most likely require additional configuration of the build system, see also section about asyncwebpack
integration
When using libflac.js
from a WebWorker in a webpack
project, the worker-loader
plugin is required, e.g. install with
npm install --save-dev worker-loader
Then include a rule in the webpack
configuration, so that the file with the WebWorker
implementation will be built as a seperate script:
in the module.rules
array add an entry, e.g. if the file name is flacworker.js
something similar to
{
//this must match the file-name of the worker script:
test: /\bflacworker\.js$/i,
use: {
loader: 'worker-loader',
options: { name: 'worker-[name].[hash].js' }
}
},
See section Async Initialization with webpack
for additional details, in case the included library variant includes a binary or *.wasm
file.
Then for creating the WebWorker instance use something like
// var flacWorker = new Worker('flacworker.js'); //<- normal way to create a WebWorker instance
var flacWorker = require('./flacworker.js')(); //<- create a WebWorker instance with webpack worker-loader plugin
flacWorker.onmessage = function(event) {
console.log('received message from flacWorker ', event.data);
}
flacWorker.postMessage(...
In the WebWorker script itself, do load the libflac.js
library like
//importScripts('libflac.js');//<- normal way to load a script within a WebWorker
// for including a "single file variant" of libflac.js, e.g. the standard version:
var Flac = require('libflacjs/dist/libflac.js');
// OR for including a .wasm variant, e.g standard-wasm (for binary of min-version include its *.mem file):
require.resolve('libflacjs/dist/libflac.wasm.wasm') // <- force webpack to include the binary file
var Flac = require('libflacjs/dist/libflac.wasm.js')
self.onmessage = function(event) {
console.log('received message from main thread ', event.data);
}
Including dynamically loaded libflac.js
:
Some variants of the libflac.js
library are loaded asynchronously
(e.g. minimized/optimized variants may load a separate binary file during initialization of the library).
In this case, you have to make sure, not to use libflac.js
before it has been completely loaded / initialized.
Code example:
//either use Flac.on() or set handler Flac.onready:
Flac.on('ready', function(event){
var libFlac = event.target;
//NOTE: Flac === libFlac
//execute code that uses libflac.js:
someFunctionForProcessingFLAC();
};
//... or set handler
Flac.onready = function(event){
var libFlac = event.target;
//NOTE: Flac === libFlac
//execute code that uses libflac.js:
someFunctionForProcessingFLAC();
};
// IMPORTANT: if execution environment does not support Object.defineProperty, then
// setting the handler will have no effect, if Flac is already initialized.
// In this case, the ready-state needs to be checked, and if already TRUE,
// the handler-code should be triggered immediately insteady of setting
// the handler.
if( !Flac.isReady() ){
Flac.onready = function(event){
var libFlac = event.target;
//NOTE: Flac === libFlac
//call function that uses libflac.js:
someFunctionForProcessingFLAC();
};
} else {
//execute code that uses libflac.js:
someFunctionForProcessingFLAC();
}
NOTE: If Object.defineProperty()
is not supported in the execution environment,
then the onready()
handler will not be called, when the library already
has been initialized before assigning it to Flac.onready
(i.e. when
Flac.isReady()
returns true
).
In this case, you should check Flac.isReady()
and provide alternative code
execution to the onready()
function, in case Flac.isReady()
is true
(or use Flac.on('ready', ...)
instead).
Variants of the libflac.js
library that are loaded asynchronously do usually also load some additional files.
If the library-file is not loaded from the default location ("page root"), but from a sub-directory/-path, you need to let the library know, so that it searches for the additional files, that it needs to load, in that sub-directory/-path.
For this, the path/location must be stored in the global variable FLAC_SCRIPT_LOCATION
before the libflac.js
library is loaded.
If FLAC_SCRIPT_LOCATION
is given as string
, it specifies the path to the libflac.js
files (see examples below), e.g.
//location example as string:
FLAC_SCRIPT_LOCATION = 'libs/';
Note, that the path/location should end with a slash ("/"
), e.g. 'some/path/'
(however, the library will try to automatically add a slash, if it is missing).
If FLAC_SCRIPT_LOCATION
is given as an object, it specifies mappings of the file-names to the file-paths of the libflac.js
files (see examples below), e.g.
//location example as object/mapping:
FLAC_SCRIPT_LOCATION = {
'libflac.min.js.mem': 'libs/flac.mem'
};
An example for specifying the path/location at libs/
in an HTML file:
<script type="text/javascript">window.FLAC_SCRIPT_LOCATION = 'libs/';</script>
<script src="libs/libflac.js" type="text/javascript"></script>
Or example for specifying the path/location at libs/
in a WebWorker script:
self.FLAC_SCRIPT_LOCATION = 'libs/';
importScripts('libs/libflac.js');
Or example for specifying the path/location at libs/
in Node.js script:
process.env.FLAC_SCRIPT_LOCATION = './libs/';
var Flac = require('./libs/libflac.js');
Example for specifying custom path and file-name via mapping (originalFileName -> <newPath/newFileName>
):
in this case, the file-name(s) of the additionally required files (e.g. *.mem
or .wasm
files)
need to be mapped to the custom path/file-name(s), that is,
for all the required files of the used library variant (see details below).
self.FLAC_SCRIPT_LOCATION = {
'libflac.min.js.mem': 'libs/flac.mem'
};
importScripts('libs/flac.min.js');
When using libflac.js
in a webpack
build process and a library variant with binary files
(e.g. min variant with *.mem
files or wasm variant with *.wasm
files) is targeted,
then the file-loader
plugin for webpack
is required, e.g. install with
npm install --save-dev file-loader
Then include a rule in the webpack
configuration, so that the file with the binary files will be included with the correct file names that libflac.js
expects:
in the module.rules
array add an entry, e.g. if the file name is flacworker.js
something similar to
{
test: /\.(wasm|mem)$/i,
use: {
loader: 'file-loader',
options: {
//NOTE binary file must be included with its original file name,
// so that libflac.js lib can find it:
name: function(file) {
return path.basename(file)
}
}
},
},
Alternatively to using the exact file name of the binary files, FLAC_SCRIPT_LOCATION
could be configured to use the file name generated by file-loader
plugin, see details above for configuring FLAC_SCRIPT_LOCATION
There are multiple variants available for the library, that are compiled with different
settings for debug-output and code optimization, namely debug
, min
, and the
default (release) library variants.
In addition, for each of these variants, there is now a wasm
variant (WebAssembly) available:
the old/default variants are compiled for asm.js
which is "normal" JavaScript, with some
optimizations that browsers can take advantage of by specifically supporting asm.js
(e.g. FireFox).
(from the Emscripten documentation)
WebAssembly is a new binary format for executing code on the web, allowing much faster start times (smaller download, much faster parsing in browsers)
In short, the (old) asm.js
is backwards compatible, since it is simply JavaScript
(and browsers that specifically support it, can execute it optimized/more efficiently),
while the new WebAssembly
format requires more recent/modern browsers, but is generally
more efficient with regard to code size and execution time.
simple detection of WASM
support in browser:
var Flac;
if(typeof WebAssembly === 'object' && WebAssembly){
//load wasm-based library
Flac = require('libflac.min.wasm.js');
//or, for example, in worker script: importScripts('libflac.min.wasm.js');
} else {
//load asm.js-based library
Flac = require('libflac.min.js');
//or, for example, in worker script: importScripts('libflac.min.js');
}
NOTE the
WebAssembly
variant does not create/encode "binary-perfect" FLAC files compared to the other library variants, or compared to the FLAC command-line tool.
More specifically, comparing the encoding results byte-by-byte with encoding results from theasm.js
variants, or separately encoded data using the FLAC command-line tool, results are different for theWebAssembly
variant. However, the reverse operation, decoding these "binary-different" FLAC files (usingWebAssembly
, orasm.js
or the command-line tool) results in the same WAV data again.
It seems, theWebAssembly
variant chooses different frame-sizes while encoding; e.g. the max. frame-size may differ from when encoding with theasm.js
variant or with the command-line tool.
NOTES for dynamically loaded library variants:
- the corresponding required files must be included in the same directory as the library/JavaScript file
- the additional required files file must not be renamed (or the library/JavaScript file must be edited accordingly)
- see also the section above for handling dynamically loaded library variants, and, if appropriate, the section for including dynamically loaded libraries from a sub-path/location
(see /dist
)
- ASM.js Variant:
libflac.js
(required)
- WebAssembly variant (dynamically loaded):
libflac.wasm.js
(required)libflac.wasm.wasm
(required; will be loaded by the library)libflac.wasm.js.symbols
(optional; contains renaming information)
(see /dist
)
- ASM.js Variant (dynamically loaded):
libflac.min.js
(required)libflac.min.js.mem
(required; will be loaded by the library)libflac.min.js.symbols
(optional; contains renaming information)
- WebAssembly variant (dynamically loaded):
libflac.min.wasm.js
(required)libflac.min.wasm.wasm
(required; will be loaded by the library)libflac.min.wasm.js.symbols
(optional; contains renaming information)
(see /dist
)
- ASM.js Variant:
libflac.dev.js
(required)currently not supported by LLVM toolchainlibflac.dev.js.map
(optional; mapping to C code)libflac.dev.js.symbols
(optional; contains renaming information)
- WebAssembly variant (dynamically loaded):
libflac.dev.wasm.js
(required)libflac.dev.wasm.wasm
(required; will be loaded by the library)libflac.dev.wasm.js.map
(optional; mapping to C code)
Generally, libflac.js
supports a subset of the libflac encoding interface for encoding audio data to FLAC (no full support yet!).
Supported encoding types:
- encode from
PCM
data all-at-once - encode from
PCM
data chunk-by-chunk (i.e. streaming)
Supported target containers:
- native
FLAC
container OGG
transport container
See example/encode.html for a small example,
on how to encode a WAV
file.
For a larger example on how to encode audio data from the microphone see the Speech to FLAC example.
Basic steps for encoding:
- create encoder
- specify encoding parameters, like channels, sampling rate, compression level etc.
- initialize encoder
- for native FLAC container or OGG container
- specify write callback and/or other optional callback(s)
- encode data (chunks)
- finish encoding
- delete encoder
Encoding example using the utility class Encoder
const Flac = require('libflacjs')();
//or as import (see section "Including libflac.js" for more details):
// import * as flacFactory from 'libflacjs';
// const Flac = flacFactory();
const Encoder = require('libflacjs/lib/encoder').Encoder;
//or as import:
//import { Encoder } from 'libflacjs/lib/encoder';
//helper function for converting interleaved audio to list of channel-audio arrays
//(for actual code, see example in tools/test/util/utils-enc.ts):
// function deinterleave(Int32Array, channels) => Int32Array[]
//NOTE if async-library variant is used, should wait for initialization:
//Flac.onready(function(){ ...
const data = new Int32Array(someAudioData);//<- someAudioData: PCM audio data converted to Int32Array samples
const encodingMode = 'interleaved';// "interleaved" | "channels"
const encoder = new Encoder(Flac, {
sampleRate: sampleRate, // number, e.g. 44100
channels: channels, // number, e.g. 1 (mono), 2 (stereo), ...
bitsPerSample: bitsPerSample, // number, e.g. 8 or 16 or 24
compression: compressionLevel, // number, value between [0, 8] from low to high compression
verify: true, // boolean (OPTIONAL)
isOgg: false // boolean (OPTIONAL), if encoded FLAC should be wrapped in OGG container
});
if(encodingMode === 'interleaved'){
//encode interleaved audio data (call multiple times for multiple audio chunks, i.e. "streaming")
encoder.encode(data);
//NOTE if data is TypedArray other than Int32Array then optional argument numberOfSamples MUST be given:
//encoder.encode(data, numberOfSamples);
} else {
//if necessary, de-interleave data into channels-array
// i.e. a list/array of Int32Arrays, one for each channel (list.length corresponds to channels); see comments about function deinterleave above
const list = deinterleave(data, channels);// should return an list of Int32Arrays which's length corresponds to the number of channels
//do encode to FLAC (call multiple times for multiple audio chunks, i.e. "streaming")
encoder.encode(list);
//NOTE if data was TypedArray other than Int32Array then optional argument numberOfSamples MUST be given:
//encoder.encode(list, numberOfSamples);
}
encoder.encode();//<- finalize encoding by invoking encode() without arguments
//get the encoded data:
const encData = encoder.getSamples();
const metadata = encoder.metadata;
encoder.destroy();
// or encoder.reset() for reusing the encoder instance
// -> do something with the encoded FLAC data encData and metadata
// e.g. update header with final metadata & create FLAC file Blob:
const exportFlacFile = require('libflacjs/lib/utils').exportFlacFile;
const flacBlob = exportFlacFile(encData, metadata, /* if encode in OGG container: */ false);
Encoding example using the library functions directly
//prerequisite: loaded libflac.js & available via variable Flac
//NOTE if async-library variant is used, should wait for initialization:
//Flac.onready(function(){ ...
var flac_encoder,
CHANNELS = 1,
SAMPLERATE = 44100,
COMPRESSION = 5,
BPS = 16,
VERIFY = false,
BLOCK_SIZE = 0,
flac_ok = 1,
USE_OGG = false;
////////
// [1] CREATE -> IN param: config { ... } (encoding parameters)
//overwrite default configuration from config object
COMPRESSION = config.compression;
BPS = config.bps;
SAMPLERATE = config.samplerate;
CHANNELS = config.channels;
VERIFY = config.isVerify;//verification can be disabled for speeding up encoding process
BLOCK_SIZE = config.blockSize;
USE_OGG = config.useOgg;
//init encoder
flac_encoder = Flac.create_libflac_encoder(SAMPLERATE, CHANNELS, BPS, COMPRESSION, 0, VERIFY, BLOCK_SIZE);
if (flac_encoder == 0){
return;
}
////////
// [2] INIT -> OUT: encBuffer (encoded data), metaData (OPTIONALLY, FLAC metadata)
//for storing the encoded FLAC data
var encBuffer = [];
//for storing the encoding FLAC metadata summary
var metaData;
// [2] (a) setup writing (encoded) output data
var write_callback_fn = function(encodedData /*Uint8Array*/, bytes, samples, current_frame){
//store all encoded data "pieces" into a buffer
encBuffer.push(encodedData);
};
// [2] (b) optional callback for receiving metadata
function metadata_callback_fn(data){
// data -> [example] {
// min_blocksize: 4096,
// max_blocksize: 4096,
// min_framesize: 14,
// max_framesize: 5408,
// sampleRate: 44100,
// channels: 2,
// bitsPerSample: 16,
// total_samples: 267776,
// md5sum: "50d4d469448e5ea75eb44ab6b7f111f4"
//}
console.info('meta data: ', data);
metaData = data;
}
// [2] (c) initialize to either write to native-FALC or to OGG container
var status_encoder;
if(!USE_OGG){
// encode to native FLAC container
status_encoder = Flac.init_encoder_stream(flac_encoder,
write_callback_fn, //required callback(s)
metadata_callback_fn //optional callback(s)
);
} else {
// encode to OGG container
status_encoder = Flac.init_encoder_ogg_stream(flac_encoder,
write_callback_fn, //required callback(s)
metadata_callback_fn //optional callback(s)
);
}
flac_ok &= (status_encoder == 0);
////////
// [3] ENCODE -> IN: for this example, a PCM Float32 audio, single channel (mono) stream
// buffer (Float32Array)
// ... repeat encoding step [3] as often as necessary
//convert input data to signed int data, in correspondence to the bps setting (i.e. in this case int32)
// see API docs on FLAC__stream_encoder_process_interleaved() for more details
var buf_length = buffer.length;
var buffer_i32 = new Int32Array(buf_length);
var view = new DataView(buffer_i32.buffer);
var volume = 1;
var index = 0;
for (var i = 0; i < buf_length; i++){
view.setInt32(index, (buffer[i] * (0x7FFF * volume)), true);
index += 4;
}
var flac_return = Flac.FLAC__stream_encoder_process_interleaved(flac_encoder, buffer_i32, buf_length);
if (flac_return != true){
console.log("Error: FLAC__stream_encoder_process_interleaved returned false. " + flac_return);
}
// encoding mode: either interleaved samples or array of channel-samples
var mode = 'interleaved';// "interleaved" | "channels"
// do encode the audio data ...
var flac_return;
if(mode === 'interleaved'){
//VARIANT 1: encode interleaved channels: TypedArray -> [ch1_sample1, ch2_sample1, ch1_sample1, ch2_sample2, ch2_sample3, ...
flac_return = Flac.FLAC__stream_encoder_process_interleaved(flac_encoder, buffer_i32, buf_length);
} else {
//VARIANT 2: encode channels array: TypedArray[] -> [ [ch1_sample1, ch1_sample2, ch1_sample3, ...], [ch2_sample1, ch2_sample2, ch2_sample3, ...], ...]
//code example for splitting an interleaved Int32Array into its channels:
var ch_buf_i32 = new Array(CHANNELS).fill(null).map(function(){ return new Uint32Array(buf_length/CHANNELS); });
for(var i=0; i < buf_length; i += CHANNELS){
for(var j=0; j < CHANNELS; ++j){
ch_buf_i32[j][i / CHANNELS] = buffer_i32[i + j];
}
}
// ... encode the array of channel-data:
flac_return = Flac.FLAC__stream_encoder_process(flac_encoder, ch_buf_i32, buf_length / CHANNELS);
}
////////
// [4] FINISH ENCODING
flac_ok &= Flac.FLAC__stream_encoder_finish(flac_encoder);
console.log("flac finish: " + flac_ok);
////////
// [5] DESTROY: delete encoder
//after usage: free up all resources for the encoder
Flac.FLAC__stream_encoder_delete(flac_encoder);
////////
// [6] ... do something with the encoded data, e.g.
// merge "encoded pieces" in encBuffer into one single Uint8Array...
Generally, libflac.js
supports a subset of the libflac decoding interface for decoding audio data from FLAC (no full support yet!).
Supported decoding types:
- decode from
FLAC
data toPCM
data all-at-once - decode from
FLAC
data toPCM
chunk-by-chunk (i.e. streaming)
Supported source containers:
- native
FLAC
container OGG
transport container
See example/decode.html for a small example,
on how to decode a FLAC
file.
Basic steps for decoding:
- create decoder
- specify if checksum verification should be processed
- initialize decoder
- specify if source is native FLAC container or OGG container
- specify read and write callback and/or other optional callback(s)
- start decoding data (chunks)
- finish decoding
- delete decoder
Decoding example using the utility class Decoder
const Flac = require('libflacjs')();
//or as import (see section "Including libflac.js" for more details):
// import * as flacFactory from 'libflacjs';
// const Flac = flacFactory();
const Decoder = require('libflacjs/lib/decoder').Decoder;
//or as import:
//import { Decoder } from 'libflacjs/lib/decoder';
//NOTE if async-library variant is used, should wait for initialization:
//Flac.onready(function(){ ...
const binData = new Uint8Array(someFlacData);// <- someFlacData: binary FLAC data
const decodingMode = 'single';// "single" | "chunked"
const decoder = new Decoder(Flac, {
verify: true, // boolean (OPTIONAL)
isOgg: false // boolean (OPTIONAL), if FLAC audio is wrapped in OGG container
});
if(decodingMode === 'single'){
//use as-single-chunk mode: invoke decode once with the complete FLAC data
decoder.decode(binData);
} else {
//use multiple-chunks mode ("streaming"): invoke decodeChunk(...) for each chunk...
decoder.decodeChunk(binData);
//... and finalize decoding by invoking decodeChunk() without arguments:
decoder.decodeChunk();
}
//get data as non-interleaved samples, i.e. array of channels data:
const decData = decoder.getSamples(/* return interleaved samples? */ false);// <- returns Uint8Array[]
//or: get decoded data as interleaved samples:
// const decData = decoder.getSamples(/* return interleaved samples? */ true);// <- returns Uint8Array
const metadata = decoder.metadata;
decoder.destroy();
// or decoder.reset() for reusing the decoder instance
// -> do something with the decoded PCM audio data decData and metadata
// e.g. create WAV file Blob:
const exportWavFile = require('libflacjs/lib/utils').exportWavFile;
const wavBlob = exportWavFile(encData, metadata.sampleRate, metadata.channels, metadata.bitsPerSample);
Decoding example using the library functions directly
//prerequisite: loaded libflac.js & available via variable Flac
//NOTE if async-library variant is used, should wait for initialization:
//Flac.onready(function(){ ...
var VERIFY = true,
USE_OGG = false;
////////
// [1] CREATE -> IN: config { ... } (decoding parameters)
//overwrite default configuration from config object
VERIFY = config.isVerify;//verification can be disabled for speeding up decoding process
//decode from native FLAC container or from OGG container
USE_OGG = config.isOgg;
// create decoder
var flac_decoder = Flac.create_libflac_decoder(VERIFY);
if (flac_decoder == 0){
return;
}
////////
// [2] INIT -> OUT: decBuffer (decoded data), metaData (OPTIONALLY, FLAC metadata)
// IN: flacData Uint8Array (FLAC data)
// [2] (a) setup reading input data
var currentDataOffset = 0;
var size = flacData.buffer.byteLength;
//function that will be called for reading the input (FLAC) data:
function read_callback_fn(bufferSize){
var end = currentDataOffset === size? -1 : Math.min(currentDataOffset + bufferSize, size);
var _buffer;
var numberOfReadBytes;
if(end !== -1){
_buffer = flacData.subarray(currentDataOffset, end);
numberOfReadBytes = end - currentDataOffset;
currentDataOffset = end;
} else {
//nothing left to read: return zero read bytes (indicates end-of-stream)
numberOfReadBytes = 0;
}
return {buffer: _buffer, readDataLength: numberOfReadBytes, error: false};
}
// [2] (b) setup writing (decoded) output data
//for "buffering" the decoded data:
var decBuffer = [];
//for storing the decoded FLAC metadata
var metaData;
//function that will be called for decoded output data (WAV audio)
function write_callback_fn(channelsBuffer, frameHeader){
// channelsBuffer is an Array of the decoded audio data (Uint8Array):
// the length of array corresponds to the channels, i.e. there is an Uint8Array for each channel
// frameHeader -> [example] {
// bitsPerSample: 8
// blocksize: 4096
// channelAssignment: 0
// channels: 2
// crc: 0
// number: 204800
// numberType: "samples"
// sampleRate: 44100
// subframes: undefined // -> needs to be enabled via
// // Flac.setOptions(flac_decoder, {analyseSubframes: true})
// // -> see API documentation
//}
decBuffer.push(channelsBuffer);
}
// [2] (c) optional callbacks for receiving details about errors and/or metadata
function error_callback_fn(err, errMsg, client_data){
console.error('decode error callback', err, errMsg);
}
function metadata_callback_fn(data){
// data -> [example] {
// min_blocksize: 4096,
// max_blocksize: 4096,
// min_framesize: 14,
// max_framesize: 5408,
// sampleRate: 44100,
// channels: 2,
// bitsPerSample: 16,
// total_samples: 267776,
// md5sum: "50d4d469448e5ea75eb44ab6b7f111f4"
//}
console.info('meta data: ', data);
metaData = data;
}
// [2] (d) intialize for reading from native-FLAC or from OGG container
var flac_ok = 1;
var status_decoder;
if(!USE_OGG){
// decode from native FLAC container
status_decoder = Flac.init_decoder_stream(
flac_decoder,
read_callback_fn, write_callback_fn, //required callback(s)
error_callback_fn, metadata_callback_fn //optional callback(s)
);
} else {
// decode from OGG container
status_decoder = Flac.init_decoder_ogg_stream(
flac_decoder,
read_callback_fn, write_callback_fn, //required callback(s)
error_callback_fn, metadata_callback_fn //optional callback(s)
);
}
flac_ok &= status_decoder == 0;
if(flac_ok != 1){
return;
}
////////
// [3] DECODE -> IN: FLAC audio data (see above, the read-callack)
// ... repeat encoding step [3] as often as necessary
// example for chunk-by-chunk (stream mode) or all-at-once decoding (file mode)
var mode = 'stream';// 'stream' | 'file'
var state = 0;
var flac_return = 1;
if(mode == 'stream'){
// VARIANT 1: decode chunks of flac data, one-by-one
//request to decode data chunks until end-of-stream is reached:
while(state <= 3 && flac_return != false){
flac_return &= Flac.FLAC__stream_decoder_process_single(flac_decoder);
state = Flac.FLAC__stream_decoder_get_state(flac_decoder);
}
flac_ok &= flac_return != false;
} else if(mode == 'file'){
// VARIANT 2: decode complete data stream, all-at-once
flac_return &= Flac.FLAC__stream_decoder_process_until_end_of_stream(flac_decoder);
//optionally: retrieve status
state = Flac.FLAC__stream_decoder_get_state(flac_decoder);
}
if (flac_return != true){
return;
}
////////
// [4] FINISH DECODING
// finish Decoding
flac_ok &= Flac.FLAC__stream_decoder_finish(flac_decoder);
////////
// [5] DESTROY: delete dencoder
// alternatively reset the decoder, and then re-initialize for re-using the decoder instance
//after usage: free up all resources for the decoder
Flac.FLAC__stream_decoder_delete(flac_decoder);
////////
// [6] ... do something with the decoded data, e.g.
// merge "decoded pieces" in decBuffer into a single data stream and add WAV header...
Example for extracting metadata when decoding FLAC audio
// prerequisites: loaded & initialized Flac library
//... create decoder flacDecoder (see code examples above)
//enable all metadata types:
Flac.FLAC__stream_decoder_set_metadata_respond_all(flacDecoder);
//or enable only seek table metadata:
Flac.FLAC__stream_decoder_set_metadata_respond(flacDecoder, 3);
// example seek table metadata (see docs for details):
// {
// num_points: 1,
// points: [{
// frame_samples: 4096,
// sample_number: 0,
// stream_offset: 0
// }]
// }
//or enable only vorbis comment metadata:
Flac.FLAC__stream_decoder_set_metadata_respond(flacDecoder, 4);
// example vorbis comment metadata:
// {
// vendor_string: "reference libFLAC 1.3.3 20190804",
// num_comments: 1,
// comments: ["TRACKNUMBER=2/9"]
// }
//or enable only cue sheet metadata:
Flac.FLAC__stream_decoder_set_metadata_respond(flacDecoder, 5);
// example cue sheet metadata (see docs for details):
// {
// is_cd: 0,
// lead_in: 88200,
// media_catalog_number: "1234567890123",
// num_tracks: 2,
// tracks: [{
// isrc: "",
// num_indices: 1,
// indices: [{offset: 0, number: 1}],
// number: 1,
// offset: 0,
// pre_emphasis: false,
// type: "AUDIO"
// }, {
// isrc: "",
// num_indices: 0,
// indices: [],
// number: 170,
// offset: 267776,
// pre_emphasis: false,
// type: "AUDIO"
// }]
// }
//or enable only all picture metadata:
Flac.FLAC__stream_decoder_set_metadata_respond(flacDecoder, 6);
// example picture metadata:
// {
// type: 3, // image type (see docs FLAC__StreamMetadata_Picture_Type)
// mime_type: "image/jpeg", //the mime type
// description: "Cover image for the track",
// width: 1144, // the image width in pixel
// height: 1144, // the image height in pixel
// depth: 24, // the depth in bits
// colors: 0, // colors (e.g. for GIF images)
// data_length: 45496, // the size of the binary image data (in bytes)
// data: Uint8Array // the binary image data
// }
//the metadata callback which stores the metadata in a list:
var streamMetadata, metadataList = [];
function metadata_callback_fn(data, dataBlock){
if(data){
// the stream metadata:
streamMetadata = data;
} else {
// other metadata types:
metadataList.push(dataBlock);
// dataBlock[example]:
// {
// data: METADATA, // the metadata, e.g. stream info, seek table, vorbis comment, picture,...
// isLast: 0, // wether the metadata block is the last block befor the audio data
// length: 1032, // the length/size of the metadata (in byte)
// type: 4, // metadata type, [0, 6] (higher metadata types are as of yet UNKNOWN)
// }
}
}
//... initilize decoder flacDecoder with metadata_callback_fn,
// and decode flac data (see code examples above)
See the doc/index.html for the API documentation.
Building libflac.js requires that emscripten is installed and configured.
See the emscripten documentation and its main site for an introduction, tutorials etc.
For changing the targeted libflac version, modify the Makefile
:
...
FLAC_VERSION:=1.3.2
...
In order to build libflac.js, make sure you have emscripten installed (with toolchain LLVM/upstream
; default toolchain since version 1.39.x).
When running make
, the build process will download the sources for the
FLAC
and OGG
libraries, extract them, and build the JavaScript version of libflac.
If necessary, activate the appropriate emscripten
toolchain (e.g. llvm
or the older fastcomp
toolchain; default is llvm
)
# list versions
emsdk list
# activate a specific version with llvm toolchain
# NOTE update Makefile if necessary with selected toolchain
# TOOL_CHAIN:=$(TOOL_CHAIN_LLVM)
emsdk activate <version>
# activate a specific version with fastcomp toolchain
# NOTE update Makefile if necessary with selected toolchain
# TOOL_CHAIN:=$(TOOL_CHAIN_FASTCOMP)
emsdk activate <version>-fastcomp
NOTE when activating a toolchain,
emsdk
will print some information on how to set the correct enviornment variables, e.g.... To conveniently access the selected set of tools from the command line, consider adding the following directories to PATH, or call 'source <path>/emsc/emsdk_env.sh' to do this for you. ...
even when not changing a toolset via
emsdk activate ...
you may need to update/export the variables for the emsdk toolchain
Start build process by executing the Makefile
:
make
(build process was tested on Unbuntu 18.04)
The API for libflac.js (e.g. exported functions) are mainly specified in libflac_post.js
.
Functions that will be exported/used from the native libflac
implementation need to be declared in
the compile option -s EXPORTED_FUNCTIONS='[...]'
(see variable EMCC_OPTS:=...
in Makefile
);
note, when manually editing EXPORTED_FUNCTIONS
, that the function-names must be prefixed with _
, i.e. for
function the_function
, the string for the exported function would be _the_function
.
There is a helper script that will try to extract the compile option from libflac_post.js
(i.e. the list of functions that need to be declared).
Run the script with Node.js
in tools/
(and copy&paste the output value):
cd tools
node extract_EXPORTED_FUNCTIONS.js
IMPORTANT: the helper script extracts function names that are invoked by Module.ccall()
or Module.cwrap()
.
If invoked dynamically (i.e. use of variable instead of string), add a DEV comment
where the function is explicitly stated as string, e.g.
//DEV comment for exported-functions script:
// Module.ccall('FLAC__stream_decoder_init_stream'
// Module.ccall('FLAC__stream_decoder_init_ogg_stream'
var func_name = test? 'FLAC__stream_decoder_init_stream' : 'FLAC__stream_decoder_init_ogg_stream';
Module.ccall(
func_name,
For more details and/or build instructions for older libflac.js
versions, see
CHANGELOG.md
See CONTRIBUTORS
for list of contributors.
This project was inspired by Krennmair's libmp3lame-js project for JS mp3 encoding.
libflac.js is compiled from the reference implementation of FLAC (BSD license); the additional resources and wrapper-code of this project is published under the MIT license (see file LICENSE).