ogg-opus-decoder
Advanced tools
Comparing version 0.0.1 to 1.0.0
{ | ||
"name": "ogg-opus-decoder", | ||
"version": "0.0.1", | ||
"version": "1.0.0", | ||
"description": "Web Assembly streaming Ogg Opus decoder", | ||
@@ -24,3 +24,4 @@ "main": "dist/ogg-opus-decoder.min.js", | ||
"WebAssembly", | ||
"Wasm" | ||
"Wasm", | ||
"WebWorker" | ||
], | ||
@@ -27,0 +28,0 @@ "author": "Ethan Halsall", |
208
README.md
@@ -1,153 +0,123 @@ | ||
# Ogg Opus Decoder | ||
# `ogg-opus-decoder` | ||
`OggOpusDecoder` is an Emscripten JavaScript WebAssembly (Wasm) library for immediately decoding Ogg Opus audio streams (URLs or files) in chunks without waiting for the complete file to download, copy, or read. [`libopusfile`](https://opus-codec.org/docs/opusfile_api-0.7/index.html) is the underlying C library used for decoding. `OggOpusDecoder` provides a lightweight JavaScript API for decoding Opus audio streams at near-native speeds. | ||
`ogg-opus-decoder` is a Web Assembly Ogg Opus audio decoder based on [`libopusfile`](https://github.com/xiph/opusfile). | ||
## Attribution | ||
* `OggOpusDecoder` (this project) is based on [AnthumChris/opus-stream-decoder](https://github.com/AnthumChris/opus-stream-decoder). This fork has been optimized for size and for simple bundling in web applications: | ||
* Everything is bundled in a single minified Javascript file for ease of use. | ||
* WASM binary is encoded inline using yEnc binary encoding and compressed using DEFLATE to significantly reduce bundle size. | ||
* WASM compiler options are tuned for best possible size and performance. | ||
* `tiny-inflate` is included from [foliojs/tiny-inflate](https://github.com/foliojs/tiny-inflate) and is used to decompress the WASM binary. | ||
See the [homepage](https://github.com/eshaz/wasm-audio-decoders) of this repository for more Web Assembly audio decoders like this one. | ||
# Usage | ||
## Installing | ||
* Install from [NPM](https://www.npmjs.com/package/ogg-opus-decoder). | ||
``` | ||
npm i ogg-opus-decoder | ||
``` | ||
Pre-compiled binaries and full examples are included in the `dist/` folder. The `OggOpusDecoder` API was designed to be simple and the pseudocode below explains its complete usage: | ||
```javascript | ||
import { OggOpusDecoder } from 'ogg-opus-decoder'; | ||
If using a front-end build system, you can obtain `OggOpusDecoder` via `require` or `import` syntaxes: | ||
const decoder = new OggOpusDecoder(); | ||
``` | ||
* Or download the [build](https://github.com/eshaz/wasm-audio-decoders/tree/master/src/ogg-opus-decoder/dist) and include it as a script. | ||
```html | ||
<script src="ogg-opus-decoder.min.js"></script> | ||
<script> | ||
const decoder = new OggOpusDecoder(); | ||
</script> | ||
``` | ||
```js | ||
const { OggOpusDecoder } = require('ogg-opus-decoder'); | ||
import { OggOpusDecoder } from 'ogg-opus-decoder'; | ||
``` | ||
## Usage | ||
Otherwise, include the script before you instantiate `OggOpusDecoder`. | ||
1. Create a new instance and wait for the WASM to finish compiling. Decoding can be done on the main thread synchronously, or in a webworker asynchronously. | ||
```javascript | ||
<script src="ogg-opus-decoder.min.js"></script> | ||
<script> | ||
// instantiate with onDecode callback that fires when OggOpusFile data is decoded | ||
const decoder = new OggOpusDecoder({onDecode, onDecodeAll}); | ||
**Main thread synchronous decoding** | ||
```javascript | ||
import { OggOpusDecoder } from 'ogg-opus-decoder'; | ||
// Loop through your Opus data calling decode() multiple times. Pass a Uint8Array | ||
try { | ||
while(...) { | ||
decoder.ready.then(_ => decoder.decode(UINT8_DATA_TO_DECODE)); | ||
} | ||
} catch (e) { | ||
decoder.ready.then(_ => decoder.free()); | ||
} | ||
const decoder = new OggOpusDecoder(); | ||
// free up the decoder's memory in WebAssembly (also resets decoder for reuse) | ||
decoder.ready.then(_ => decoder.free()); | ||
// wait for the WASM to be compiled | ||
await decoder.ready; | ||
``` | ||
// after free() is called, you could reuse the decoder for another file | ||
try { ... decoder.ready.then(_ => decoder.decode(UINT8_DATA_TO_DECODE) } ... | ||
**Web Worker asynchronous decoding** | ||
```javascript | ||
import { OggOpusDecoderWebWorker } from 'ogg-opus-decoder'; | ||
/* Receives decoded Float32Array PCM audio in left/right arrays. | ||
* sampleRate is always 48000 and both channels would always contain data if | ||
* samplesDecoded > 0. Mono Opus files would decoded identically into both | ||
* left/right channels and multichannel Opus files would be downmixed to 2 channels. | ||
*/ | ||
const decoder = new OggOpusDecoderWebWorker(); | ||
// Called for each decoded Opus frame | ||
function onDecode ({channelData, samplesDecoded, sampleRate}) { | ||
const left = channelData[0]; | ||
const right = channelData[1]; | ||
console.log(`Decoded ${samplesDecoded} samples`); | ||
// play back the left/right audio, write to a file, etc | ||
} | ||
// wait for the WASM to be compiled | ||
await decoder.ready; | ||
``` | ||
// Called when all data passed into decode has been processed | ||
function onDecodeAll ({channelData, samplesDecoded, sampleRate}) { | ||
const left = channelData[0]; | ||
const right = channelData[1]; | ||
console.log(`Decoded ${samplesDecoded} samples`); | ||
// play back the left/right audio, write to a file, etc | ||
} | ||
</script> | ||
``` | ||
1. Begin decoding Ogg Opus data. | ||
After instantiating `OggOpusDecoder`, `decode()` should be called repeatedly until you're done reading the stream. You __must__ start decoding from the beginning of the file. Otherwise, a valid Ogg Opus file will not be discovered by `libopusfile` for decoding. `decoder.ready` is a Promise that resolves once the underlying WebAssembly module is fetched from the network and instantiated, so ensure you always wait for it to resolve. `free()` should be called when done decoding, when `decode()` throws an error, or if you wish to "reset" the decoder and begin decoding a new file with the same instance. `free()` releases the allocated Wasm memory. | ||
```javascript | ||
// Decode an individual Opus frame | ||
const {channelData, samplesDecoded, sampleRate} = decoder.decode(oggOpusData); | ||
``` | ||
#### Performance | ||
`OggOpusDecoder` is highly optimized and is sometimes faster than the native Opus decoding ability of the browser. To avoid any blocking operations on your main thread, you can run this in a Web Worker to keep CPU decoding computations on a separate browser thread. | ||
* **NOTE:** When decoding chained Ogg files (i.e. streaming) the first two Ogg packets of the next chain must be present when decoding. Errors will be returned by libopusfile if these initial Ogg packets are incomplete. | ||
When decoding in batches where latency is not a concern, use the `onDecodeAll` callback which is called when all data that has been passed into `decode` has been decoded. | ||
1. When done decoding, reset the decoder to decode a new stream, or free up the memory being used by the WASM module if you have no more audio to decode. | ||
Additionally, `onDecode` will be called thousands of times while decoding Opus files. Keep your `onDecode` callbacks lean. The multiple calls result intentionally because of Opus' unmatched low-latency decoding advantage ([read more](https://opus-codec.org/comparison/#bitratelatency-comparison))—audio is decoded as soon as possible . For example, a 60-second Opus file encoded with a 20ms frame/packet size would yield 3,000 `onDecode` calls (60 * 1000 / 20), because the underlying `libopusfile` C decoding function [`op_read_float_stereo()`](https://opus-codec.org/docs/opusfile_api-0.7/group__stream__decoding.html#ga9736f96563500c0978f56f0fd6bdad83) currently decodes one frame at a time during my tests. | ||
```javascript | ||
// `reset()` clears the decoder state and allows you do decode a new stream of Ogg Opus data. | ||
decoder.reset(); | ||
# Building | ||
// `free()` de-allocates the memory used by the decoder. You will need to create a new instance after calling `free()` to start decoding again. | ||
decoder.free(); | ||
``` | ||
The `dist/` folder will contain all required files, tests, and examples after building. | ||
## API | ||
### Download Ogg, Opus, and Opusfile C libraries: | ||
``` | ||
$ git submodule update --init | ||
``` | ||
Decoded audio is always returned in the below structure. | ||
_TODO: consider moving this to Makefile_ | ||
### Install Emscripten | ||
Emscripten is used to compile the C libraries to be compatible with WebAssembly. This repo was tested with 2.0.25. | ||
* [Emscripten Installation Instructions](https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html#installation-instructions) | ||
### Run the Build | ||
```javascript | ||
{ | ||
channelData: [ | ||
leftAudio, // Float32Array of PCM samples for the left channel | ||
rightAudio // Float32Array of PCM samples for the right channel | ||
], | ||
samplesDecoded: 1234, // number of PCM samples that were decoded | ||
sampleRate: 48000 // sample rate of the decoded PCM | ||
} | ||
``` | ||
$ make clean && make -j8 | ||
``` | ||
Each Float32Array within `channelData` can be used directly in the WebAudio API for playback. | ||
### Build Errors | ||
## `OggOpusDecoder` | ||
#### Error: "autoreconf: command not found" | ||
Class that decodes Ogg Opus data synchronously on the main thread. | ||
`$ brew install automake` | ||
### Getters | ||
* `decoder.ready` *async* | ||
* Returns a promise that is resolved when the WASM is compiled and ready to use. | ||
#### "./autogen.sh: No such file or directory" | ||
### Methods | ||
`$ brew install autogen` | ||
* `decoder.decode(oggOpusData)` | ||
* `opusFrame` Uint8Array containing Ogg Opus data. | ||
* Returns decoded audio. | ||
* `decoder.reset()` *async* | ||
* Resets the decoder so that a new stream of Ogg Opus data can be decoded. | ||
* `decoder.free()` | ||
* De-allocates the memory used by the decoder. | ||
* After calling `free()`, the current instance is made unusable, and a new instance will need to be created to decode additional Ogg Opus data. | ||
# Tests & Examples | ||
## `OggOpusDecoderWebWorker` | ||
Two tests exist that will decode an Ogg Opus File with `OggOpusDecoder`. Both tests output "decoded _N_ samples." on success. | ||
Class that decodes Ogg Opus data asynchronously within a WebWorker. Decoding is performed in a separate, non-blocking thread. Each new instance spawns a new worker allowing you to run multiple workers for concurrent decoding of multiple streams. | ||
### NodeJS Test | ||
### Getters | ||
* `decoder.ready` *async* | ||
* Returns a promise that is resolved when the WASM is compiled and ready to use. | ||
This test writes two decoded left/right PCM audio data to files in `tmp/`. [Install NodeJS](https://nodejs.org/en/download/) and run: | ||
``` | ||
$ make test-wasm-module | ||
``` | ||
### Methods | ||
### HTML Browser Test | ||
This test uses `fetch()` to decode a URL file stream in chunks. Serve the `dist/` folder from a web server and open `test-ogg-opus-decoder.html` in the browser. HTTP/HTTPS schemes are required for Wasm to load—opening it directly with `file://` probably won't work. | ||
You can also run `SimpleHTTPServer` and navigate to http://localhost:8000/test-ogg-opus-decoder.html | ||
``` | ||
$ cd dist | ||
$ python -m SimpleHTTPServer 8000 | ||
``` | ||
# Developing | ||
### Emscripten Wasm Module | ||
See files `src/*.{js,html}` and use `$ make` and `$ make clean` to build into `dist/` | ||
### `OggOpusDecoder` C interface | ||
See C files `src/ogg_opus_decoder*` and use `$ make native-decode-test`, which allows you to compile and test almost instantly. `native-decode-test` is a fast workflow that ensures things work properly independently of Emscripten and Wasm before you integrate it. | ||
You'll need to install `libopusfile` binaries natively on your system (on Mac use `$ brew install opusfile`). Then, declare environment variables with the locations of the installed `libopusfile` dependencies required by `native-decode-test` before running: | ||
``` | ||
$ export OPUS_DIR=/usr/local/Cellar/opus/1.2.1 | ||
$ export OPUSFILE_DIR=/usr/local/Cellar/opusfile/0.10 | ||
$ make native-decode-test | ||
``` | ||
Note: If you see error "fatal error: 'stdarg.h' file not found", try running from a new terminal window that does not have Emscripten initialized. | ||
* `decoder.decode(oggOpusData)` *Async | ||
* `opusFrame` Uint8Array containing Ogg Opus data. | ||
* Returns a promise that resolves with the decoded audio. | ||
* `decoder.reset()` *async* | ||
* Resets the decoder so that a new stream of Ogg Opus data can be decoded. | ||
* `decoder.free()` | ||
* De-allocates the memory used by the decoder and terminates the WebWorker. | ||
* After calling `free()`, the current instance is made unusable, and a new instance will need to be created to decode additional Ogg Opus data. |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
135281
448
1
124