NodeAV

repository·main·Indexed 43 days ago

https://github.com/seydx/node-av

Native Node.js bindings for FFmpeg with full TypeScript support, providing both a high-level API for common multimedia workflows (Demuxer, Decoder, Encoder, Muxer, and pipeline) and a low-level API for direct FFmpeg C API access. Version 6.2.0-beta.23 supports high-performance audio/video processing, transcoding, device capture, and hardware acceleration (CUDA, VAAPI).

Tokens
81.2K
Snippets
175
Records
243
Agent score
57%

What's inside NodeAV

  1. Manage media resources with the Disposable pattern

    main

    To prevent memory leaks and ensure proper cleanup of native FFmpeg resources, use the using keyword (Disposable pattern) or manual close() calls. Using await using ensures that Demuxer, Decoder, and other resource-heavy objects are automatically cleaned up when they go out of scope.

    // Automatic cleanup with 'using'
    {
      await using media = await Demuxer.open('input.mp4');
      using decoder = await Decoder.create(media.video());
      // Resources automatically cleaned up at end of scope
    }
    
    // Manual cleanup
    const media = await Demuxer.open('input.mp4');
    try {
      // Process media
    } finally {
      await media.close();
    }
  2. Understand Sync vs Async operations

    main

    NodeAV provides two modes for media operations:

    1. Async methods (Default): Non-blocking operations using N-API's AsyncWorker. Methods like decode(), encode(), read(), and packets() return Promises or AsyncGenerators. These allow the Node.js event loop to remain responsive.
    2. Sync methods: Direct FFmpeg calls with a Sync suffix (e.g., decodeSync(), encodeSync(), readSync(), packetsSync()). These block the event loop but avoid the overhead of AsyncWorker, making them faster for strictly sequential processing where concurrency is not required.
  3. Understand the difference between High-Level and Low-Level APIs

    main

    The node-av library provides two distinct API levels for handling audio/video tasks:

    1. High-Level API (node-av/api): This is the recommended starting point for most use cases. It provides abstracted, developer-friendly components such as Demuxer, Decoder, Encoder, Muxer, and the pipeline() function. It manages much of the complexity of FFmpeg orchestration for you.

    2. Low-Level API (node-av/lib): These are direct FFmpeg bindings. Use this level if you need fine-grained control over FFmpeg internals or if you are porting existing FFmpeg C code to Node.js. Many examples in this category are direct ports of official FFmpeg C examples.

    When choosing an API, start with the High-Level API unless your specific requirement necessitates direct access to FFmpeg's underlying structures.

  4. Optimize imports and tree shaking

    main

    The library is structured into multiple entry points to allow for optimal tree shaking. Choose the entry point that matches your needs:

    • node-av/api: High-Level API (Recommended for most users: Demuxer, Muxer, Decoder, Encoder).
    • node-av/lib: Low-Level API (Direct FFmpeg bindings: FormatContext, CodecContext, Frame, Packet).
    • node-av/constants: FFmpeg constants (e.g., AV_CODEC_ID_H264).
    • node-av/layouts: Audio channel layouts (e.g., AV_CHANNEL_LAYOUT_STEREO).
    // High-Level API
    import { Demuxer, Muxer, Decoder, Encoder } from 'node-av/api';
    
    // Low-Level API
    import { FormatContext, CodecContext, Frame, Packet } from 'node-av/lib';
    
    // Constants
    import { AV_PIX_FMT_YUV420P, AV_CODEC_ID_H264 } from 'node-av/constants';
    
    // Layouts
    import { AV_CHANNEL_LAYOUT_STEREO, AV_CHANNEL_LAYOUT_5POINT1 } from 'node-av/layouts';
  5. Configure Hardware Acceleration

    main

    NodeAV supports all hardware acceleration methods available in FFmpeg. You can use HardwareContext.auto() to automatically detect the best available hardware, or manually specify a hardware type using HardwareContext.create() with constants like AV_HWDEVICE_TYPE_CUDA or AV_HWDEVICE_TYPE_VAAPI.

    import { HardwareContext } from 'node-av/api';
    import { FF_ENCODER_LIBX264 } from 'node-av/constants';
    
    // Automatically detect best available hardware
    const hw = HardwareContext.auto();
    console.log(`Using hardware: ${hw.deviceTypeName}`);
    
    // Use with decoder
    const decoder = await Decoder.create(stream, {
      hardware: hw
    });
    
    // Use with encoder (use hardware-specific codec)
    const encoderCodec = hw?.getEncoderCodec('h264') ?? FF_ENCODER_LIBX264;
    const encoder = await Encoder.create(encoderCodec, {
      decoder,
    });
    import { AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_VAAPI } from 'node-av/constants';
    
    // Use specific hardware type
    const cuda = HardwareContext.create(AV_HWDEVICE_TYPE_CUDA);
    const vaapi = HardwareContext.create(AV_HWDEVICE_TYPE_VAAPI, '/dev/dri/renderD128');
  6. Install platform-specific binaries for cross-platform packaging

    main

    When building for a target platform different from your host (e.g., building Windows binaries on Linux), use npm's --os and --cpu flags. This tells the installation process to fetch the correct optionalDependencies and FFmpeg binaries.

    # Install Windows x64 binaries while running on Linux
    npm install --os=win32 --cpu=x64
  7. Capture devices using DeviceAPI

    main

    Use DeviceAPI to capture video, audio, or screen content directly from system devices. The API handles platform-specific backends automatically:

    • macOS: AVFoundation (Video/Audio/Screen)
    • Linux: V4L2 (Video), ALSA (Audio), x11grab (Screen)
    • Windows: DirectShow (Video/Audio), GDIGrab (Screen)
    import { DeviceAPI } from 'node-av/api';
    
    // List devices and query capabilities
    const devices = await DeviceAPI.list();
    const modes = await DeviceAPI.modes(devices.find(d => d.type === 'video')!.name);
    
    // Camera
    await using camera = await DeviceAPI.openCamera({ videoDevice: 0, width: 1280, height: 720, frameRate: 30 });
    
    // Microphone
    await using mic = await DeviceAPI.openMicrophone({ audioDevice: 0, sampleRate: 48000, channels: 2 });
    
    // Combined video + audio (macOS/Windows)
    await using device = await DeviceAPI.openDevice({ videoDevice: 0, audioDevice: 0, width: 1280, height: 720, frameRate: 30 });
    
    // Screen capture
    await using screen = await DeviceAPI.openScreen({ frameRate: 30, drawMouse: true });
    
    // Screen capture with system audio (macOS 13.0+)
    await using screen2 = await DeviceAPI.openScreen({
      frameRate: 30,
      avfoundation: { captureSystemAudio: true, audioSampleRate: 48000, audioChannels: 2 },
    });
  8. Use the Low-Level API for direct FFmpeg C API access

    main

    The Low-Level API provides direct access to FFmpeg's C APIs with minimal abstractions. It is ideal when you need granular control over FFmpeg functionality, such as manually managing FormatContext, CodecContext, Packet, and Frame lifecycles. Note that this API requires careful resource management, often using the using keyword (Disposable pattern) to ensure memory is freed correctly.

    import { AVERROR_EOF, AVMEDIA_TYPE_VIDEO } from 'node-av/constants';
    import { Codec, CodecContext, FFmpegError, FormatContext, Frame, Packet, Rational } from 'node-av/lib';
    
    // Open input file
    await using ifmtCtx = new FormatContext();
    
    let ret = await ifmtCtx.openInput('input.mp4');
    FFmpegError.throwIfError(ret, 'Could not open input file');
    
    ret = await ifmtCtx.findStreamInfo();
    FFmpegError.throwIfError(ret, 'Could not find stream info');
    
    // Find video stream
    const videoStreamIndex = ifmtCtx.findBestStream(AVMEDIA_TYPE_VIDEO);
    const videoStream = ifmtCtx.streams?.[videoStreamIndex];
    
    if (!videoStream) {
      throw new Error('No video stream found');
    }
    
    // Create codec
    const codec = Codec.findDecoder(videoStream.codecpar.codecId);
    if (!codec) {
      throw new Error('Codec not found');
    }
    
    // Allocate codec context for the decoder
    using decoderCtx = new CodecContext();
    decoderCtx.allocContext3(codec);
    
    ret = decoderCtx.parametersToContext(videoStream.codecpar);
    FFmpegError.throwIfError(ret, 'Could not copy codec parameters to decoder context');
    
    // Open decoder context
    ret = await decoderCtx.open2(codec, null);
    FFmpegError.throwIfError(ret, 'Could not open codec');
    
    // Process packets
    using packet = new Packet();
    packet.alloc();
    
    using frame = new Frame();
    frame.alloc();
    
    while (true) {
      let ret = await ifmtCtx.readFrame(packet);
      if (ret < 0) {
        break;
      }
    
      if (packet.streamIndex === videoStreamIndex) {
        // Send packet to decoder
        ret = await decoderCtx.sendPacket(packet);
        if (ret < 0 && ret !== AVERROR_EOF) {
          FFmpegError.throwIfError(ret, 'Error sending packet to decoder');
        }
    
        // Receive decoded frames
        while (true) {
          const ret = await decoderCtx.receiveFrame(frame);
          if (ret === AVERROR_EOF || ret < 0) {
            break;
          }
    
          console.log(`Decoded frame ${frame.pts}, size: ${frame.width}x${frame.height}`);
    
          // Process frame data...
    
          frame.unref();
        }
      }
    
      packet.unref();
    }
    
    // Flush the decoder
    ret = await decoderCtx.sendPacket(null);
    if (ret < 0 && ret !== AVERROR_EOF) {
      FFmpegError.throwIfError(ret, 'Failed to flush decoder');
    }
    
    while (true) {
      const ret = await decoderCtx.receiveFrame(frame);
      if (ret === AVERROR_EOF || ret < 0) {
        break;
      }
    
      console.log(`Decoded frame ${frame.pts}, size: ${frame.width}x${frame.height}`);
    
      // Process frame data...
    
      frame.unref();
    }
  9. Explore High-Level API examples

    main

    High-level examples (prefixed with api-*) demonstrate common multimedia workflows using the node-av/api module. Key capabilities demonstrated include:

    • Transcoding & Processing: api-encode-decode, api-hw-transcode (hardware accelerated), api-sw-transcode (software), and api-filter-complex (applying filters).
    • Streaming & Network: api-dash, api-hw-rtsp, api-rtp, and api-webrtc-pip.
    • File Handling: api-fmp4, api-frame-extract, and api-snapshot.
    • Hardware Acceleration: api-hw-codecs, api-hw-raw, and api-scaler-hw.
    • Pipeline Management: api-pipeline-progress and api-pipeline-raw-muxing.

    Most high-level examples are located in the examples/api-*.ts files.

  10. Run standalone Browser and Electron projects

    main

    The repository includes small standalone projects for specific environments:

    • Browser: Examples like browser-fmp4 and browser-webrtc are located in examples/browser/.
    • Electron: Examples for building with electron-builder and electron-forge are located in examples/electron/.

    These are separate projects from the main node-av library usage and should be treated as independent application templates.

  11. Process media streams from various sources

    main

    NodeAV supports multiple input methods via the Demuxer API:

    • Files or Network: Pass a file path or a network URL (e.g., rtsp://...).
    • Buffers: Pass a Node.js Buffer directly to Demuxer.open().
    • Custom I/O Callbacks: Implement IOInputCallbacks to provide custom read and seek logic.
    • Raw Media: Open raw video (e.g., YUV) or raw audio (e.g., PCM) by specifying the type, dimensions, pixel format, or sample rate.
    // From Files or Network
    const media = await Demuxer.open('input.mp4');
    // or
    const media = await Demuxer.open('rtsp://example.com/stream');
    // From Buffers
    import { readFile } from 'fs/promises';
    const buffer = await readFile('input.mp4');
    const media = await Demuxer.open(buffer);
    // Custom I/O Callbacks
    import type { IOInputCallbacks, IOOutputCallbacks } from 'node-av/api';
    
    const inputCallbacks: IOInputCallbacks = {
      read: (size: number) => {
        return buffer; // or null for EOF
      },
      seek: (offset: bigint, whence: number) => {
        return newPosition;
      }
    };
    
    await using input = await Demuxer.open(inputCallbacks, { format: 'mp4' });
    // Raw video input
    const rawVideo = await Demuxer.open({
      type: 'video',
      input: 'input.yuv',
      width: 1280,
      height: 720,
      pixelFormat: AV_PIX_FMT_YUV420P,
      frameRate: { num: 30, den: 1 }
    });
    
    // Raw audio input
    const rawAudio = await Demuxer.open({
      type: 'audio',
      input: 'input.pcm',
      sampleRate: 48000,
      channels: 2,
      sampleFormat: AV_SAMPLE_FMT_S16
    }, { format: 's16le' });