# libflac.js **Repository Path**: ziiwee/libflac.js ## Basic Information - **Project Name**: libflac.js - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-01-18 - **Last Updated**: 2024-06-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [libflac.js][0] ========== [![npm](https://img.shields.io/npm/v/libflacjs)](https://www.npmjs.com/package/libflacjs) [![GitHub package.json version](https://img.shields.io/github/package-json/v/mmig/libflac.js/master)][0] [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/emscripten-core/emscripten?color=green&label=emscripten%40latest)][1] [![libFLAC version](https://img.shields.io/badge/libFLAC-1.3.3-yellow)][6] [![libogg version](https://img.shields.io/badge/libogg-1.3.4-yellow)][18] [FLAC][6] 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` (static `C` library) version: 1.3.3\ > Used library `libogg` (static `C` library) version: 1.3.4\ > Used compiler `Emscripten` version: 1.39.19\ > Used compiler `Emscripten` 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][14] for encoding `*.wav` files to FLAC. Or try the [speech-to-flac][12] [demo][13] that encodes the audio stream from a microphone to FLAC. __Decoder Demo__ Try the [Decoding Demo][15] 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][16] for the API documentation. ---- - [Usage](#usage) - [Including libflac.js](#including-libflacjs) - [Browser](#browser) - [WebWorker](#webworker) - [Node](#node) - [React/webpack](#reactwebpack) - [Angular/webpack](#angularwebpack) - [WebWorker with webpack](#webworker-with-webpack) - [Async Initialization](#async-initialization) - [Including Dynamically Loaded libflac.js from Non-Default Location](#including-dynamically-loaded-libflacjs-from-non-default-location) - [Async Initialization with webpack](#async-initialization-with-webpack) - [Library Variants](#library-variants) - [_default_ vs `min` vs `dev`](#default-vs-min-vs-dev) - [`asm.js` vs `WASM`](#asmjs-vs-wasm) - [Example `WASM` Feature Detection](#example-wasm-feature-detection) - [Variants and Notes](#variants-and-notes) - [Default Library:](#default-library) - [Minified Library:](#minified-library) - [Development Library:](#development-library) - [Encoding with libflac.js](#encoding-with-libflacjs) - [Encoding Example](#encoding-example) - [Barebones Encoding Example](#barebones-encoding-example) - [Decoding with libflac.js](#decoding-with-libflacjs) - [Decoding Example](#decoding-example) - [Barebones Decoding Example](#barebones-decoding-example) - [Decoding Metadata Example](#decoding-metadata-example) - [API](#api) - [Building](#building) - [Build *nix (libflac 1.3.0 or later)](#build-nix-libflac-130-or-later) - [Changing The Library API](#changing-the-library-api) - [Legacy Build Instructions](#legacy-build-instructions) - [Contributors](#contributors) - [Acknowledgments](#acknowledgments) - [License](#license) ## Usage ------ ### Including libflac.js 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](#library-variants). #### Browser Include the library file, e.g. if library file(s) `libflac.js` is in the same directory as the referencing HTML file: ```html ``` #### WebWorker Import the library file, e.g. if library file(s) `libflac.js` is in the same directory as the referencing worker script file: ```javascript importScripts('libflac.js'); ``` #### Node In `Node.js`: install with `npm` ```bash # 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: ```javascript //load default/release asm.js variant: var Flac = require('libflacjs')(); // use one of the optimization-variants: // * / "release" // * "min" // * "dev" // use one of the technology-variants: // * / "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: ```javascript // for example: var Flac = require('libflacjs/dist/libflac.js'); // or e.g. the WASM variant: var Flac = require('libflacjs/dist/libflac.wasm.js'); ``` #### React/webpack For `reactjs`: install with `npm` (see above), and `require()` the library file directly, like ```javascript // 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` and `wasm` variants will most likely require > additional configuration of the build system, see also > section about [async `webpack` integration](#async-initialization-with-webpack) #### Angular/webpack For `Angular` (`TypeScript`): install with `npm` (see above), and `import` the library file directly, like ```typescript // 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. ```typescript (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 ```typescript 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` and `wasm` variants will most likely require > additional configuration of the build system, see also > section about [async `webpack` integration](#async-initialization-with-webpack) #### WebWorker with webpack When using `libflac.js` from a WebWorker in a `webpack` project, the `worker-loader` plugin is required, e.g. install with ```bash 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 ```javascript { //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](#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 ```javascript // 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 ```javascript //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); } ``` ### Async Initialization 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: ```javascript //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). #### Including Dynamically Loaded libflac.js from Non-Default Location 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. ```javascript //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. ```javascript //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: ```html ``` Or example for specifying the path/location at `libs/` in a WebWorker script: ```javascript self.FLAC_SCRIPT_LOCATION = 'libs/'; importScripts('libs/libflac.js'); ``` Or example for specifying the path/location at `libs/` in Node.js script: ```javascript process.env.FLAC_SCRIPT_LOCATION = './libs/'; var Flac = require('./libs/libflac.js'); ``` Example for specifying custom path and file-name via mapping (`originalFileName -> `): 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). ```javascript self.FLAC_SCRIPT_LOCATION = { 'libflac.min.js.mem': 'libs/flac.mem' }; importScripts('libs/flac.min.js'); ``` #### Async Initialization with webpack 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 ```javascript { 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`_ ### Library Variants #### _default_ vs `min` vs `dev` 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. #### `asm.js` vs `WASM` 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][17]) > 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. #### Example `WASM` Feature Detection simple detection of `WASM` support in browser: ```javascript 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'); } ``` #### Variants and Notes > 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 the `asm.js` variants, or separately encoded data using the FLAC command-line tool, results are different for the `WebAssembly` variant. However, the reverse operation, decoding these "binary-different" FLAC files (using `WebAssembly`, or `asm.js` or the command-line tool) results in the same WAV data again. _It seems, the `WebAssembly` variant chooses different frame-sizes while encoding; e.g. the max. frame-size may differ from when encoding with the `asm.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 #### Default Library: _(see [`/dist`](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) #### Minified Library: _(see [`/dist`](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) #### Development Library: _(see [`/dist`](dist))_ * ASM.js Variant: * `libflac.dev.js` (**required**) * ~~`libflac.dev.js.map` (_optional_; mapping to C code)~~ _currently not supported by LLVM toolchain_ * `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) ### Encoding with libflac.js Generally, `libflac.js` supports a subset of the [libflac encoding interface][8] 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][10] 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][9] example. Basic steps for encoding: 1. create encoder * specify encoding parameters, like channels, sampling rate, compression level etc. 2. initialize encoder * for native FLAC container or OGG container * specify write callback and/or other optional callback(s) 3. encode data (chunks) 4. finish encoding 5. delete encoder #### Encoding Example Encoding example using the utility class `Encoder` ```javascript 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); ``` #### Barebones Encoding Example Encoding example using the library functions directly ```javascript //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... ``` ### Decoding with libflac.js Generally, `libflac.js` supports a subset of the [libflac decoding interface][7] for decoding audio data from FLAC (no full support yet!). Supported decoding types: * decode from `FLAC` data to `PCM` data all-at-once * decode from `FLAC` data to `PCM` chunk-by-chunk (i.e. _streaming_) Supported source containers: * native `FLAC` container * `OGG` transport container See [example/decode.html][11] for a small example, on how to decode a `FLAC` file. Basic steps for decoding: 1. create decoder * specify if checksum verification should be processed 2. initialize decoder * specify if source is native FLAC container or OGG container * specify read and write callback and/or other optional callback(s) 3. start decoding data (chunks) 4. finish decoding 5. delete decoder #### Decoding Example Decoding example using the utility class `Decoder` ```javascript 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); ``` #### Barebones Decoding Example Decoding example using the library functions directly ```javascript //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... ``` #### Decoding Metadata Example Example for extracting metadata when decoding FLAC audio ```javascript // 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) ``` ### API See the [doc/index.html][16] for the API documentation. ## Building ------ Building libflac.js requires that [emscripten][1] is installed and configured. See the [emscripten documentation][3] and its [main site][2] for an introduction, tutorials etc. For changing the targeted libflac version, modify the `Makefile`: ``` ... FLAC_VERSION:=1.3.2 ... ``` ### Build *nix (libflac 1.3.0 or later) 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`) ```bash # 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 # activate a specific version with fastcomp toolchain # NOTE update Makefile if necessary with selected toolchain # TOOL_CHAIN:=$(TOOL_CHAIN_FASTCOMP) emsdk activate -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 /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`: ```bash make ``` (build process was tested on Unbuntu 18.04) ### Changing The Library API 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](tools/extract_EXPORTED_FUNCTIONS.js) 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. ```javascript //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, ``` ### Legacy Build Instructions For more details and/or build instructions for older `libflac.js` versions, see [CHANGELOG.md][19] ## Contributors ------ See `CONTRIBUTORS` for list of contributors. ## Acknowledgments ------ This project was inspired by Krennmair's [libmp3lame-js][5] project for [JS mp3][5] encoding. ## License ------- 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). [0]: https://github.com/mmig/libflac.js [1]: https://github.com/kripken/emscripten [2]: https://kripken.github.io/emscripten-site [3]: https://kripken.github.io/emscripten-site/docs [4]: https://kripken.github.io/emscripten-site/docs/getting_started/getting_started_with_emscripten_and_vs2010.html [5]: https://github.com/akrennmair/libmp3lame-js [6]: https://xiph.org/flac/ [7]: https://xiph.org/flac/api/group__flac__stream__decoder.html [8]: https://xiph.org/flac/api/group__flac__stream__encoder.html [9]: https://github.com/mmig/speech-to-flac [10]: example/encode.html [11]: example/decode.html [12]: https://github.com/mmig/speech-to-flac [13]: https://mmig.github.io/speech-to-flac/ [14]: https://mmig.github.io/libflac.js/example/encode.html [15]: https://mmig.github.io/libflac.js/example/decode.html [16]: https://mmig.github.io/libflac.js/doc/ [17]: http://kripken.github.io/emscripten-site/docs/compiling/WebAssembly.html#webassembly [18]: https://xiph.org/ogg/ [19]: https://github.com/mmig/libflac.js/blob/master/CHANGELOG.md