Coder Social home page Coder Social logo

arseneyr / wasm-media-encoders Goto Github PK

View Code? Open in Web Editor NEW
32.0 1.0 4.0 138.93 MB

MP3 and Ogg Vorbis encoders for the browser and Node

License: MIT License

JavaScript 1.70% TypeScript 64.61% Makefile 11.67% C 22.02%
webassembly nodejs encoder mp3 ogg-vorbis browser

wasm-media-encoders's Introduction

wasm-media-encoders

The LAME MP3 and Ogg Vorbis audio encoders, compiled to minimal WebAssembly and ready for use in the browser or Node.

Why?

While some browsers have native encoding using the MediaRecorder API, it only supports a small number of codecs.

This package aims to fill the gap by compiling the reference LAME and Ogg Vorbis encoders to WebAssembly. The resulting package is tiny and fast:

Encoder JS* WASM Combined + Gzipped
MP3 3.3 KiB 130 KiB 66 KiB
Ogg Vorbis 3.3 KiB 440 KiB 158 KiB

*When using ESNext syntax and no polyfills

Installation

yarn add wasm-media-encoders

or

npm install wasm-media-encoders

Getting started

With webpack or .mjs file in Node 12+:

import { createMp3Enoder, createOggEncoder } from "wasm-media-encoders";

createOggEncoder().then((encoder) => {
  /* Configure and use the encoder */
});
createMp3Encoder().then((encoder) => {
  /* Configure and use the encoder */
});

With a <script> tag:

<script src="https://unpkg.com/wasm-media-encoders/dist/umd/WasmMediaEncoder.min.js"></script>
<script>
  // The UMD package will fetch() the WASM binaries from
  // unpkg.com by default to reduce size

  WasmMediaEncoder.createMp3Encoder().then((encoder) => {
    /* Configure and use the encoder */
  });
</script>

Using CommonJS in Node:

const { createMp3Encoder } = require("wasm-media-encoders");
createMp3Encoder().then((encoder) => {
  /* Configure and use the encoder */
});

If you'd like to use ESNext syntax and transpile yourself, use the wasm-media-encoders/esnext path.

Example usage

createMp3Encoder().then((encoder) => {
  encoder.configure({
    sampleRate: 48000,
    channels: 2,
    vbrQuality: 2,
  });

  let outBuffer = new Uint8Array(1024 * 1024);
  let offset = 0;
  let moreData = true;

  while (true) {
    const mp3Data = moreData
      ? encoder.encode([
          pcm_l /* Float32Array of left channel PCM data */,
          pcm_r /* Float32Array of right channel PCM data */,
        ])
      : /* finalize() returns the last few frames */
        encoder.finalize();

    /* mp3Data is a Uint8Array that is still owned by the encoder and MUST be copied */

    if (mp3Data.length + offset > outBuffer.length) {
      const newBuffer = new Uint8Array(mp3Data.length + offset);
      newBuffer.set(outBuffer);
      outBuffer = newBuffer;
    }

    outBuffer.set(mp3Data, offset);
    offset += mp3Data.length;

    if (!moreData) {
      break;
    }

    moreData = false;
  }

  return new Uint8Array(outBuffer.buffer, 0, offset);

  /* Or encoder can be reused without calling createEncoder() again:

  encoder.configure({...new params})
  encoder.encode()
  encoder.finalize() */
});

Reducing bundle size

By default, this package inlines the WASM binary as a base64-encoded data URL. This make importing the encoder easy, but also increases the bundle size by about 30%. With webpack, you can load the WASM directly (found in wasm-media-encoders/wasm/(mp3|ogg).wasm), passing the URL as the second parameter to createEncoder().

Webpack v4

Besides using file-loader, you also need to disable WASM parsing in the webpack config:

import { createEncoder } from "wasm-media-encoders";
import wasm from "file-loader!wasm-media-encoders/wasm/mp3.wasm";

createEncoder("audio/mpeg", wasm).then((encoder) => {
  /* Now mp3.wasm will be copied to output dir by webpack and fetch()ed at runtime */
});
// webpack.config.js

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.wasm$/,
        type: "javascript/auto",
      },
    ],
  },
};

Webpack v5

Using asset modules and URL assets is easy in webpack v5:

import { createEncoder } from "wasm-media-encoders";

createEncoder(
  "audio/mpeg",
  new URL("wasm-media-encoder/wasm/mp3", import.meta.url)
).then((encoder) => {
  /* Now mp3.wasm will be copied to output dir by webpack and fetch()ed at runtime */
});

API

Named exports:

createMp3Encoder(): Promise<WasmMediaEncoder>

createOggEncoder(): Promise<WasmMediaEncoder>

createEncoder(mimeType, wasm, moduleCallback?): Promise<WasmMediaEncoder>

The first two named exports use inline base-64 encoded WASM binaries (or fetch() from unpkg.com in the case of UMD). Tree-shaking on webpack should prevent unused encoders from being included in the final bundle.

Parameter Type Description
mimeType String The MIME type of the encoder to create. Supported values are 'audio/mpeg' (MP3) or 'audio/ogg' (Ogg Vorbis)
wasm String | ArrayBuffer | Uint8Array | WebAssembly.Module A URL, base64 data URL, buffer, or compiled WebAssembly.Module representing the WASM binary for the specific mimeType. The WASM binaries are included in the package under wasm-media-encoders/wasm/(mp3|ogg).wasm. Non-data URLs are not supported in Node; use node-fetch if this is something you need.
moduleCallback ((module: WebAssembly.Module) => void) | undefined Optionally, a callback that will be called with the compiled WebAssembly module. You can cache this module client-side and pass it as the wasm parameter to avoid fetching and compiling it every time.

WasmMediaEncoder

configure(options): void

Configures the encoder. This method can be called at any time to reset the state of the encoder, including after finalize().

The options object is a union of common properties and encoder-specific ones. All common options are required.

Common options:

Property Type Description
channels Number The number of channels to be encoded. Currently only 1 or 2 channels are supported.
sampleRate Number Sample rate of data to be encoded, in hertz.

Options for MIME type audio/mpeg (MP3):

vbrQuality and bitrate are mutually exclusive.

Property Type Default Description
vbrQuality Number | undefined 4.0 Variable Bitrate (VBR) quality for the LAME encoder, from 0.0 (best quality) to 9.999 (worst quality). See here for details.
bitrate Number | undefined Constant bitrate in kbit/s. Valid bitrates are 8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, or 320.

Options for MIME type audio/ogg (Ogg Vorbis):

Property Type Default Description
vbrQuality Number | undefined 3.0 Variable Bitrate (VBR) quality for the Vorbis encoder, from -1.0 (worst quality) to 10.0 (best quality). See here for approximate bitrates.

encode(samples): Uint8Array

Encodes PCM samples and returns a Uint8Array. You may call this method repeatedly to add more samples (e.g. if streaming in PCM data). May return a Uint8Array of length zero. The returned Uint8Array is owned by the encoder and MUST be copied before any other encoder methods are called.

Parameter Type Description
samples Float32Array[] A channels-length array of Float32Array representing the PCM data to encode. Each sample must be in the range of -1.0 to 1.0

finalize(): Uint8Array

Flushes the encoder and returns the last few encoded samples. May return a Uint8Array of length zero. The returned Uint8Array is owned by the encoder and MUST be copied before any other encoder methods are called.

Building

Dev Container

The easiest way to get started is by creating a development container using the included .devcontainer/devcontainer.json. Tools that support dev containers can be found here. Development containers require a Docker or Podman installation. Full instructions for Visual Studio Code can be found here.

Manually setting up environment

To manually set up a development environment, you must install the following prerequisites:

  1. Emscripten SDK (v2.0.8)

    Note: The SDK should also include Node 12

  2. (Optional) Yarn

    npm i -g yarn
    

    The correct version of yarn is checked into the repo under .yarn/releases and is not required to build. However installing yarn will make CLI operations easier.

Building

To create a release build:

make

License

This project is licensed under the terms of the MIT license.

wasm-media-encoders's People

Contributors

arseneyr avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

wasm-media-encoders's Issues

Example is incomplete

Hey, the example in the readme is incomplete. There's a variable moreData that isn't defined or assigned anywhere.

Example usage

createMp3Encoder().then((encoder) => {
  encoder.configure({
    sampleRate: 48000,
    channels: 2,
    vbrQuality: 2,
  });

  let outBuffer = new Uint8Array(1024 * 1024);
  let offset = 0;

  while (true) {
    const mp3Data = moreData
      ? encoder.encode([
          pcm_l /* Float32Array of left channel PCM data */,
          pcm_r /* Float32Array of right channel PCM data */,
        ])
      : /* finalize() returns the last few frames */
        encoder.finalize();

    /* mp3Data is a Uint8Array that is still owned by the encoder and MUST be copied */

    while (mp3Data.length + offset > outBuffer.length) {
      const newBuffer = new Uint8Array(outBuffer.length * 2);
      newBuffer.set(outBuffer);
      outBuffer = newBuffer;
    }

    outBuffer.set(mp3Data, offset);
    offset += mp3Data.length;

    if (!moreData) {
      break;
    }
  }

  return new Uint8Array(outBuffer.buffer, 0, offset);

  /* Or encoder can be reused without calling createEncoder() again:

  encoder.configure({...new params})
  encoder.encode()
  encoder.finalize() */
});

Output sampleRate is always 24KHz

I am trying to use wasm-media-encoders in an application where I am mixing streams. I can set the input sampleRate, but the output sampleRate is always 24KHz. This is fine for playback, as the decoder will use the encoded sampleRate. It causes big problems for live mixing, because the audio context sampleRate has already been set (to 44100). The underlying LAME library allows setting the output sampleRate, so hopefully this is a simple matter of adding this parameter to the wasm-media-encoders.configure() function and passing it on to lame_set_out_samplerate().

Cannot Encode mp3s above ~3MB

I cannot seem to successfully encode large mp3s. I was able to encode a buffer of around 2500000 samples, but once I try a buffer over 10000000 samples I get the following error

Uncaught (in promise) Error: Error while encoding -1
    at M.encode (index.js?2521:1)
    at eval (nf-webaudio.js?5ee4:2009)

Which seems to be due to this.module.enc_encode(this.ref, pcm[0].length); returning -1 in encoder.ts.

Please advise if there are any strategies for handling larger mp3s. I am more or less using the exact implementation of createMp3Encoder from the readme with sample rate 44100 2 channels and 128 bitrate. The error happens the first time encoder.encode(pcmChannels) is called.

Add support for MP3 VBR header

MP3 files encoded with VBR have inaccurate durations due to lack of LAME header. This will require modifying the beginning of the output buffer after finalization.

Add support for WAV

Thank you for this amazing library.

In my app, I'm providing MP3 and WAV export. The purpose of the WAV export is that if users need the audio in a format that my app doesn't provide, the WAV export makes more sense than the MP3 export, because it is lossless and much faster to generate, so it is better to integrate into a workflow where the exported audio is converted into another format using an external tool.

Right now I'm using wav-encoder to generate the WAV. It has two drawbacks:

  • It generates the whole WAV file at once synchronously. There is no support for chunks. This means that my UI is frozen while the file is being generated, and I cannot show the progress to the user.
  • It uses JS, which is probably significantly slower than WASM.

This is why I would like to see WAV support in this library. Unfortunately I have no expertise with WASM, so I'm not able to contribute an implementation.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.