Skip to main content

AudioDecoder

The AudioDecoder class decodes compressed audio data (EncodedAudioChunk) into raw audio samples (AudioData). It supports common audio codecs like AAC, MP3, Opus, and FLAC.

Quick Example

import { AudioDecoder, EncodedAudioChunk } from 'node-webcodecs';

// Create decoder with callbacks
const decoder = new AudioDecoder({
  output: (audioData) => {
    console.log(`Decoded ${audioData.numberOfFrames} samples`);
    console.log(`Format: ${audioData.format}, Rate: ${audioData.sampleRate}Hz`);

    // Process the audio data...

    // IMPORTANT: Always close AudioData when done
    audioData.close();
  },
  error: (e) => console.error('Decode error:', e)
});

// Configure for AAC decoding
decoder.configure({
  codec: 'mp4a.40.2',      // AAC-LC
  sampleRate: 48000,
  numberOfChannels: 2
});

// Decode encoded chunks
const chunk = new EncodedAudioChunk({
  type: 'key',
  timestamp: 0,
  data: aacFrameData
});

decoder.decode(chunk);

// Flush remaining data when done
await decoder.flush();
decoder.close();
Always call audioData.close() in your output callback after processing. AudioData objects hold native memory that JavaScript’s garbage collector cannot reclaim automatically. Failing to close them will cause memory leaks.

Constructor

Creates a new AudioDecoder instance.
new AudioDecoder(init: AudioDecoderInit)
init
AudioDecoderInit
required
Initialization object containing callback functions.

Properties

state
CodecState
required
Current state of the decoder. One of:
  • 'unconfigured' - Initial state, call configure() before decoding
  • 'configured' - Ready to decode chunks
  • 'closed' - Decoder has been closed and cannot be used
decodeQueueSize
number
required
Number of chunks waiting to be decoded. Use this for backpressure management - if the queue grows too large, pause feeding new chunks until it decreases.

Static Methods

isConfigSupported()

Checks whether a given configuration is supported by the decoder without creating an instance.
static isConfigSupported(config: AudioDecoderConfig): Promise<AudioDecoderSupport>
config
AudioDecoderConfig
required
The configuration to test for support.
Returns: Promise<AudioDecoderSupport> - Object indicating whether the config is supported.
import { AudioDecoder } from 'node-webcodecs';

async function checkAACSupport() {
  const support = await AudioDecoder.isConfigSupported({
    codec: 'mp4a.40.2',
    sampleRate: 48000,
    numberOfChannels: 2
  });

  if (support.supported) {
    console.log('AAC decoding is supported!');
  } else {
    console.log('AAC decoding is NOT supported');
  }
}

Instance Methods

configure()

Configures the decoder with codec parameters. Must be called before decode().
configure(config: AudioDecoderConfig): void
config
AudioDecoderConfig
required
Configuration object specifying the codec and audio parameters.
Throws: InvalidStateError if the decoder is closed.
decoder.configure({
  codec: 'mp4a.40.2',      // AAC-LC
  sampleRate: 48000,
  numberOfChannels: 2
});

console.log(decoder.state); // 'configured'
// For AAC from MP4 container, you may need the AudioSpecificConfig
const audioSpecificConfig = new Uint8Array([0x11, 0x90]); // Example for 48kHz stereo AAC-LC

decoder.configure({
  codec: 'mp4a.40.2',
  sampleRate: 48000,
  numberOfChannels: 2,
  description: audioSpecificConfig
});

decode()

Queues an encoded audio chunk for decoding. Decoded frames will be delivered asynchronously to the output callback.
decode(chunk: EncodedAudioChunk): void
chunk
EncodedAudioChunk
required
The encoded audio chunk to decode. Unlike video, most audio chunks are keyframes and can be decoded independently.
Throws:
  • InvalidStateError if the decoder is not configured
  • DataError if the chunk data is malformed
import { AudioDecoder, EncodedAudioChunk } from 'node-webcodecs';

// Assuming you have an array of AAC frames from a parser
const aacFrames: Uint8Array[] = parseAACStream(audioData);

for (let i = 0; i < aacFrames.length; i++) {
  const chunk = new EncodedAudioChunk({
    type: 'key',                        // Audio frames are typically keyframes
    timestamp: i * 21333,               // ~21ms per AAC frame at 48kHz
    duration: 21333,
    data: aacFrames[i]
  });

  decoder.decode(chunk);
}

flush()

Completes all pending decode operations. Call this when you’ve submitted all chunks and need to ensure all output has been delivered.
flush(): Promise<void>
Returns: Promise<void> - Resolves when all queued chunks have been decoded and output. Throws: InvalidStateError if the decoder is not configured.
// After submitting all chunks
await decoder.flush();
console.log('All audio decoded');

// Safe to close now
decoder.close();

reset()

Resets the decoder to the 'unconfigured' state, discarding any pending work. The decoder can be reconfigured after reset.
reset(): void
Throws: InvalidStateError if the decoder is closed.
// Reset current decoding state
decoder.reset();
console.log(decoder.state); // 'unconfigured'

// Reconfigure for different audio
decoder.configure({
  codec: 'opus',
  sampleRate: 48000,
  numberOfChannels: 1
});

close()

Closes the decoder and releases all resources. The decoder cannot be used after calling close().
close(): void
async function decodeAudioFile(chunks: EncodedAudioChunk[]) {
  const decoder = new AudioDecoder({
    output: (data) => {
      processAudio(data);
      data.close();
    },
    error: console.error
  });

  decoder.configure({ codec: 'mp4a.40.2', sampleRate: 48000, numberOfChannels: 2 });

  for (const chunk of chunks) {
    decoder.decode(chunk);
  }

  await decoder.flush();
  decoder.close();  // Clean up resources
}

Interfaces

AudioDecoderConfig

Configuration for the audio decoder.
interface AudioDecoderConfig {
  codec: string;                    // Required: codec identifier
  sampleRate: number;               // Required: sample rate in Hz
  numberOfChannels: number;         // Required: number of channels
  description?: BufferSource;       // Optional: codec-specific config
}
PropertyTypeRequiredDescription
codecstringYesCodec string (e.g., 'mp4a.40.2', 'opus')
sampleRatenumberYesSample rate in Hz (e.g., 44100, 48000)
numberOfChannelsnumberYesNumber of channels (1=mono, 2=stereo)
descriptionBufferSourceNoCodec-specific configuration data

AudioDecoderInit

Initialization object for creating an AudioDecoder.
interface AudioDecoderInit {
  output: (data: AudioData) => void;           // Required: decoded frame callback
  error: (error: DOMException) => void;        // Required: error callback
}
PropertyTypeRequiredDescription
output(data: AudioData) => voidYesCalled with each decoded AudioData
error(error: DOMException) => voidYesCalled when an error occurs

AudioDecoderSupport

Result from isConfigSupported().
interface AudioDecoderSupport {
  supported: boolean;               // Whether the config is supported
  config: AudioDecoderConfig;       // The config that was tested
}
PropertyTypeDescription
supportedbooleantrue if the configuration is supported
configAudioDecoderConfigThe configuration that was tested

Common Codec Strings

CodecStringDescription
AAC-LC'mp4a.40.2'AAC Low Complexity - most common
AAC-HE'mp4a.40.5'AAC High Efficiency (SBR)
AAC-HEv2'mp4a.40.29'AAC High Efficiency v2 (SBR+PS)
MP3'mp3'MPEG-1 Audio Layer 3
Opus'opus'Opus codec
FLAC'flac'Free Lossless Audio Codec
Vorbis'vorbis'Ogg Vorbis

Complete Decoding Example

import { AudioDecoder, EncodedAudioChunk } from 'node-webcodecs';

async function decodeAACStream(aacFrames: Uint8Array[]): Promise<Float32Array[]> {
  const decodedSamples: Float32Array[] = [];

  const decoder = new AudioDecoder({
    output: (audioData) => {
      // Allocate buffer for all channels interleaved
      const samples = new Float32Array(audioData.numberOfFrames * audioData.numberOfChannels);

      // Copy samples for each channel
      for (let ch = 0; ch < audioData.numberOfChannels; ch++) {
        const channelData = new Float32Array(audioData.numberOfFrames);
        audioData.copyTo(channelData, { planeIndex: ch, format: 'f32-planar' });

        // Interleave into output
        for (let i = 0; i < audioData.numberOfFrames; i++) {
          samples[i * audioData.numberOfChannels + ch] = channelData[i];
        }
      }

      decodedSamples.push(samples);
      audioData.close();  // Always close!
    },
    error: (e) => {
      throw new Error(`Decode error: ${e.message}`);
    }
  });

  // Configure decoder
  decoder.configure({
    codec: 'mp4a.40.2',
    sampleRate: 48000,
    numberOfChannels: 2
  });

  // Decode all frames
  let timestamp = 0;
  const frameDuration = 21333; // ~21ms for AAC at 48kHz

  for (const frame of aacFrames) {
    const chunk = new EncodedAudioChunk({
      type: 'key',
      timestamp,
      duration: frameDuration,
      data: frame
    });

    decoder.decode(chunk);
    timestamp += frameDuration;
  }

  // Wait for all decoding to complete
  await decoder.flush();
  decoder.close();

  return decodedSamples;
}

See Also