Metadata-Version: 2.2
Name: avioflow
Version: 0.7.3
Summary: High-performance audio decoding with FFmpeg and C++
Author: lxp3
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: C++
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# AvioFlow

AvioFlow is a high-performance and easy-to-use streaming audio decoding library. 

The avioflow project is build on top of the FFMPEG library.

## Features

- **Audio format**: mp3, opus, flac, ogg, wav, m4a, aac. Anything FFmpeg supports — see the [Supported Formats Reference](doc/supported_formats.md) for the full decoder/encoder list.
- **Flexible Input**: Files, URLs, memory buffers, and real-time streams
- **Hardware Capture**: WASAPI loopback (system audio) and DirectShow (microphones)
- **Resampling**: Built-in sample rate conversion
- **Zero-copy API**: Direct buffer access via `FrameData` for maximum performance
- **Cross-platform**: Windows, Linux, macOS

## Speed

Decode time (best of 10 runs) for `public/wavs/TownTheme.mp3` (MP3, 44.1kHz
stereo, ~97.5s) on a single machine, decoding the full file into memory both
without resampling and with resampling to 16kHz. Numbers are for illustration
on this environment, not a formal cross-platform benchmark.

| Library        | No resample (ms) | Resample to 16kHz (ms) |
| -------------- | ----------------- | ----------------------- |
| **avioflow**       | **141.2**              | **132.6**                    |
| librosa        | 98.7               | 176.7                    |
| soundfile      | 100.6              | N/A (no built-in resampling) |
| torchcodec     | 130.8              | 148.9                    |
| sox (CLI)      | 206.0              | 403.7                    |
| ffmpeg (CLI)   | 167.7              | 198.9                    |

## Supported language

AvioFlow is packaged for several runtime and application environments. The
native core is shared across bindings, so behavior stays consistent whether you
embed it in a C++ service, call it from Python or JavaScript, ship it in a JVM
application, or run it in WebAssembly.

| Language / Runtime | Integration | Install / Consume | Compatibility |
| ------------------ | ----------- | ----------------- | ------------- |
| C++                | Native CMake package | `find_package(avioflow CONFIG REQUIRED)` | Shared/static packages for Linux, macOS, and Windows; Linux packages include both libstdc++ ABI 0 and ABI 1 variants |
| C / other FFI      | C ABI exported by the core | `#include <avioflow-c-api.h>` | Flat `extern "C"` surface with opaque handles; usable from any language with C FFI |
| Python             | pybind11 binding | `pip install avioflow` | Wheels for mainstream desktop/server platforms |
| JavaScript / Node.js | Node-API native addon | `npm install avioflow` | Platform-specific native packages selected by npm |
| Java               | JNI binding | Gradle / Maven | Runtime classifiers for Linux, macOS, and Windows |
| Rust               | C ABI binding | `cargo add avioflow` (see [rust/README.md](rust/README.md)) | Builds the native core from source; FFmpeg linked statically |
| WebAssembly        | WASM build | npm package / web bundle | Browser and WASM-capable runtime support |

## Decoder API Flow

AvioFlow uses the same pull-style output functions for offline and streaming
decoding. The difference is only how input bytes enter the decoder.

### Offline Input

```text
+-----------------------+
| AudioDecoder(options) |
+-----------+-----------+
            |
            v
+-----------------------------+
| load_file(path)             |
| load_buffer(bytes, size)    |
+-----------+-----------------+
            |
            v
+-----------------------------+
| get_frame()                          |  one decoded frame, zero-copy
| get_samples(start, stop)             |  samples in [start, stop), defaults to all
+-----------+-----------------+
            |
            v
+-----------------------------+
| is_finished()               |
+-----------------------------+
```

`get_samples(start_seconds, stop_seconds)` supports offline seek/time-range
decoding: pass a half-open range in seconds to decode only that window, or
call it with no arguments to decode the whole file. It may be called multiple
times on the same decoder to fetch different ranges; each call seeks
independently.

### Streaming Input

```text
+--------------------------------------+
| AudioDecoder(input_format, rate, ch) |
+-----------+--------------------------+
            |
            v
+-----------------------------+
| feed(chunk)                 |  first feed starts stream mode
+-----------+-----------------+
            |
            v
+-----------------------------+
| get_frame()                 |  returns empty if data is incomplete
| get_samples()               |  drains currently available samples (start/stop range not supported here)
+-----------+-----------------+
            | repeat feed/get_* while streaming
            v
+-----------------------------+
| flush()                     |  no more input; drain decoder delay
+-----------+-----------------+
            |
            v
+-----------------------------+
| get_samples() / get_frame() |  drain until is_finished()
+-----------------------------+
```

`flush()` does not discard data. It marks stream input complete so remaining
buffered bytes and codec-delayed frames can be drained.


## Build from Source

### Prerequisites
- CMake 3.20+
- Visual Studio 2022+ (Windows) or GCC 11+ (Linux)

### C++ Build

```bash
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON
cmake --build build --config Release
```

FFmpeg is fetched and configured automatically during the CMake configure step.

Run the tests:

```bash
ctest --test-dir build --output-on-failure
```

---

## Installation

### Python
```bash
pip install avioflow
```

### Java

Gradle users need the main Java API jar plus one native classifier for the target platform:

```kotlin
dependencies {
    implementation("io.github.lxp3:avioflow:0.7.3")
    runtimeOnly("io.github.lxp3:avioflow:0.7.3:linux-x86_64")
}
```

Maven:

```xml
<dependency>
  <groupId>io.github.lxp3</groupId>
  <artifactId>avioflow</artifactId>
  <version>0.3.2</version>
</dependency>
<dependency>
  <groupId>io.github.lxp3</groupId>
  <artifactId>avioflow</artifactId>
  <version>0.3.2</version>
  <classifier>linux-x86_64</classifier>
  <scope>runtime</scope>
</dependency>
```

Native classifiers: `linux-x86_64`, `linux-aarch64`, `macos-x86_64`, `macos-aarch64`, `windows-x86_64`, `windows-aarch64`.


### C++ (CMake)
Download the C++ package for your platform and linkage, then point CMake at the
extracted package root with `CMAKE_PREFIX_PATH`.

```cmake
find_package(avioflow CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE avioflow::avioflow)
```

Release packages are split by linkage and platform:

Linux binaries target glibc 2.28 or newer. Select ABI 0 for the legacy
libstdc++ string ABI or ABI 1 for the C++11 string ABI; the eight Linux C++
archives use the filenames `avioflow-{shared|static}-linux-{x64|arm64}-abi{0|1}.tar.gz`.

- `avioflow-shared-linux-x64-abi1`, `avioflow-shared-linux-x64-abi0`
- `avioflow-static-linux-x64-abi1`, `avioflow-static-linux-x64-abi0`
- `avioflow-shared-linux-arm64-abi1`, `avioflow-shared-linux-arm64-abi0`
- `avioflow-static-linux-arm64-abi1`, `avioflow-static-linux-arm64-abi0`
- `avioflow-shared-macos-x64`, `avioflow-static-macos-x64`
- `avioflow-shared-macos-arm64`, `avioflow-static-macos-arm64`
- `avioflow-shared-win-x64`, `avioflow-static-win-x64`
- `avioflow-shared-win-arm64`, `avioflow-static-win-arm64`

Shared packages include the FFmpeg dynamic libraries needed at runtime. Static
packages include FFmpeg static libraries, transitive static dependency metadata,
and the bundled FFmpeg CMake package, so consumers do not need to configure
FFmpeg separately.

---

## C++ API

### Core Classes

#### `AudioDecoder`

Main class for audio decoding.

```cpp
#include "avioflow-cxx-api.h"
using namespace avioflow;

// Constructor options
AudioStreamOptions options;
options.output_sample_rate = 16000;    // Target sample rate
options.input_format = "s16le";        // For streaming: source format
options.input_sample_rate = 48000;     // For streaming: source rate
options.input_channels = 2;            // For streaming: source channels

AudioDecoder decoder(options);
```

#### Methods

| Method             | Description                                                      |
| ------------------ | ---------------------------------------------------------------- |
| `load_file(source)` | Load file path, URL, or device and return metadata              |
| `load_buffer(data, size)` | Load complete audio bytes from memory                    |
| `feed(data, size)` | Feed stream bytes; first feed starts stream mode                 |
| `flush()`          | Mark stream input complete and allow draining                    |
| `get_frame()`      | Decode next frame, returns `FrameData`                           |
| `get_samples(start_seconds=0.0, stop_seconds=nullopt)` | Decode samples in `[start_seconds, stop_seconds)` (offline mode); with no args, drains all currently available samples |
| `get_metadata()`   | Get audio metadata                                               |
| `is_finished()`    | Check if EOF reached                                             |

#### `FrameData`

Zero-copy frame data structure returned by `get_frame()`.

```cpp
struct FrameData {
    float** data;        // Planar channel pointers: data[channel][sample]
    int num_channels;    // Number of channels
    int num_samples;     // Samples per channel

    operator bool();     // True if valid data
};
```

> ⚠️ **Warning**: `FrameData.data` points to internal buffer, valid only until next `get_frame()` or `get_samples()` call.

### Examples

#### File Decoding (Offline)
```cpp
AudioDecoder decoder({.output_sample_rate = 16000});
decoder.load_file("audio.mp3");

auto samples = decoder.get_samples();  // vector<vector<float>>
std::cout << "Channels: " << samples.size() << std::endl;
std::cout << "Samples: " << samples[0].size() << std::endl;
```

#### Frame-by-Frame Decoding
```cpp
AudioDecoder decoder;
decoder.load_file("audio.mp3");

while (auto frame = decoder.get_frame()) {
    // frame.data[channel][sample]
    for (int c = 0; c < frame.num_channels; c++) {
        process(frame.data[c], frame.num_samples);
    }
}
```

#### Raw PCM Memory Decode
```cpp
// Raw PCM bytes have no container/header, so provide the input format details.
// Use FFmpeg demuxer format names such as "s16le", not codec names like
// "pcm_s16le".
AudioStreamOptions opts;
opts.input_format = "s16le";       // Signed 16-bit little-endian PCM
opts.input_sample_rate = 8000;     // 8 kHz
opts.input_channels = 1;           // Mono

AudioDecoder decoder(opts);
decoder.load_buffer(pcm_bytes, pcm_size); // Full PCM buffer in memory

while (auto frame = decoder.get_frame()) {
    // Output samples are float planar: frame.data[channel][sample]
    process(frame.data[0], frame.num_samples);
}
```

#### Time-Range Decoding (Offline Seek)
```cpp
AudioDecoder decoder;
decoder.load_file("audio.mp3");

// Decode only seconds 10.3 to 20.3
auto samples = decoder.get_samples(10.3, 20.3);

// Can be called again with a different range on the same decoder
auto next_range = decoder.get_samples(30.0, 40.0);
```

#### Streaming Decode (Push-based)
```cpp
AudioStreamOptions opts;
opts.input_format = "s16le";
opts.input_sample_rate = 48000;
opts.input_channels = 2;

AudioDecoder decoder(opts);
decoder.feed(raw_bytes, size);  // First feed starts stream mode

auto samples = decoder.get_samples(); // Decode all buffered data
// Or frame-by-frame:
while (auto frame = decoder.get_frame()) {
    // Process decoded audio...
}
decoder.flush();
```

---

## Python API

### `AudioDecoder`

```python
import avioflow

# Constructor with keyword arguments
decoder = avioflow.AudioDecoder(
    output_sample_rate=16000,    # Optional: target sample rate
    input_format="s16le",        # For streaming: source format
    input_sample_rate=48000,     # For streaming: source rate
    input_channels=2             # For streaming: source channels
)
```

#### Methods

| Method | Returns | Description |
|--------|---------|-------------|
| `load_file(source)` | `Metadata` | Load file, URL, or `pathlib.Path` |
| `load_buffer(data)` | `Metadata` | Load complete bytes-like input |
| `feed(data)` | `None` | Feed streaming bytes |
| `flush()` | `None` | Mark stream input complete |
| `get_frame()` | `ndarray \| None` | Decode next frame |
| `get_samples(start_seconds=0.0, stop_seconds=None)` | `ndarray` | Decode samples in `[start_seconds, stop_seconds)` (offline mode); with no args, drains all currently available samples |
| `is_finished()` | `bool` | Check if EOF |

#### `Metadata`

```python
# Quick metadata inspection without full decoding
meta = avioflow.info("audio.mp3")
print(f"Duration: {meta.duration}s")
print(f"Sample Rate: {meta.sample_rate}Hz")
print(f"Codec: {meta.codec}")

# Encoded audio bytes also work
with open("audio.mp3", "rb") as f:
    meta = avioflow.info(f.read())
```

### Examples

#### File Decoding
```python
decoder = avioflow.AudioDecoder(output_sample_rate=16000)
meta = decoder.load_file("speech.wav")
samples = decoder.get_samples()      # numpy array (channels, samples)
print(f"Shape: {samples.shape}")     # e.g., (1, 160000)
```

#### Time-Range Decoding (Offline Seek)
```python
decoder = avioflow.AudioDecoder()
decoder.load_file("audio.mp3")

# Decode only seconds 10.3 to 20.3
samples = decoder.get_samples(10.3, 20.3)

# Can be called again with a different range on the same decoder
next_range = decoder.get_samples(30.0, 40.0)
```

#### Streaming Decode
```python
decoder = avioflow.AudioDecoder(
    input_format="s16le",
    input_sample_rate=48000,
    input_channels=2
)

while True:
    data = socket.recv(4096)
    if not data:
        decoder.flush()
        break
    decoder.feed(data)
    samples = decoder.get_samples()
    if samples.size > 0:
        process_audio(samples)
```

#### Device Discovery
```python
devices = avioflow.DeviceManager.list_audio_devices()
for dev in devices:
    print(f"{dev.name}: {dev.description}")
```

### Logging
```python
avioflow.set_log_level("debug")  # quiet, error, warning, info, debug, trace
```

---

## Node.js API

### Compatibility

| Runtime           | Version         | Support                        |
| ----------------- | --------------- | ------------------------------ |
| **Node.js**       | 16, 18, 20, 22+ | ✅ Native (N-API)               |
| **Electron**      | All versions    | ✅ Supported (requires rebuild) |
| **Architectures** | x64             | ✅ Linux, Windows               |

### Installation

```bash
npm install avioflow
```

### ESM Import
```javascript
import avioflow from 'avioflow';
```

### Module-level Functions

| Function               | Returns               | Description                                                                |
| ---------------------- | --------------------- | -------------------------------------------------------------------------- |
| `load(path, options?)` | `{metadata, samples}` | **Convenience**: Opens, decodes all samples, and returns both in one call. |
| `listAudioDevices()`   | `DeviceInfo[]`        | List available system audio devices.                                       |
| `setLogLevel(level)`   | `void`                | Set FFmpeg log level ("quiet", "info", "debug", etc.).                     |

### `AudioDecoder`

```javascript
// Constructor with options object
const decoder = new avioflow.AudioDecoder({
    outputSampleRate: 16000,    // Optional: target sample rate
    outputNumChannels: 1,       // Optional: target channels
    inputFormat: 's16le',       // For streaming: source format
    inputSampleRate: 48000,     // For streaming: source rate
    inputChannels: 2            // For streaming: source channels
});
```

#### Methods

| Method         | Returns                    | Description                                       |
| -------------- | -------------------------- | ------------------------------------------------- |
| `loadFile(source)` | `Metadata`            | Load file, URL, or device name. Returns metadata. |
| `loadBuffer(buffer)` | `Metadata`          | Load complete encoded bytes from memory.          |
| `feed(buffer)` | `void`                     | Feed streaming bytes.                             |
| `flush()`      | `void`                     | Mark stream input complete.                       |
| `getFrame()`   | `Float32Array[]` \| `null` | Decode next frame. Returns array of channel data. |
| `getSamples(startSeconds?, stopSeconds?)` | `Float32Array[]` | Decode samples in `[startSeconds, stopSeconds)` (offline mode); with no args, drains all currently available samples. |
| `isFinished()` | `boolean`                  | Check if end of stream reached.                   |

### Examples

#### Quick File Loading (Recommended)
```javascript
// Opens file, resamples to 16kHz mono, and decodes everything
const { metadata, samples } = avioflow.load("audio.mp3", {
    outputSampleRate: 16000,
    outputNumChannels: 1
});

console.log(`Duration: ${metadata.duration}s`);
console.log(`Channels: ${samples.length}, Samples: ${samples[0].length}`);
```

#### Batch Decoding with Decoder Instance
```javascript
const decoder = new avioflow.AudioDecoder({ outputSampleRate: 44100 });
const meta = decoder.loadFile("audio.wav");

// Decodes the entire file into memory
const allSamples = decoder.getSamples();
process(allSamples);
```

#### Time-Range Decoding (Offline Seek)
```javascript
const decoder = new avioflow.AudioDecoder();
decoder.loadFile("audio.mp3");

// Decode only seconds 10.3 to 20.3
const samples = decoder.getSamples(10.3, 20.3);

// Can be called again with a different range on the same decoder
const nextRange = decoder.getSamples(30.0, 40.0);
```

#### Streaming Decode (Real-time)
```javascript
const decoder = new avioflow.AudioDecoder({
    inputFormat: 's16le',
    inputSampleRate: 48000,
    inputChannels: 2
});

socket.on('data', (chunk) => {
    decoder.feed(chunk);

    // Get all samples decoded from this chunk
    const samples = decoder.getSamples();
    if (samples.length > 0) {
        processAudio(samples);
    }
});

socket.on('end', () => {
    decoder.flush();
    const remaining = decoder.getSamples();
    if (remaining.length > 0) {
        processAudio(remaining);
    }
});
```

#### Device Discovery
```javascript
const devices = avioflow.listAudioDevices();
devices.forEach(dev => {
    console.log(`${dev.isOutput ? 'Output' : 'Input'}: ${dev.name} (${dev.description})`);
});
```

---

## Java API

### File Decoding

```java
import io.github.lxp3.avioflow.AudioDecoder;
import io.github.lxp3.avioflow.AudioStreamOptions;

try (AudioDecoder decoder = new AudioDecoder(
        new AudioStreamOptions().outputSampleRate(16000))) {
    decoder.loadFile("audio.mp3");
    float[][] samples = decoder.getSamples();
    System.out.println(samples.length + " channels");

    // Decode only seconds 10.3 to 20.3 (offline mode)
    float[][] range = decoder.getSamples(10.3, 20.3);
}
```

### Encoding

```java
import io.github.lxp3.avioflow.AudioEncoder;
import io.github.lxp3.avioflow.AudioWriteOptions;

AudioEncoder.saveAudio(
    "out.wav",
    samples,
    new AudioWriteOptions()
        .containerFormat("wav")
        .codecName("pcm_s16le")
        .sampleRate(16000)
);
```


---

## Rust API

Full reference: [rust/README.md](rust/README.md).

### File Decoding

```rust
use avioflow::{AudioDecoder, StreamOptions};

let mut decoder = AudioDecoder::new(&StreamOptions::new().output_sample_rate(16000))?;
let metadata = decoder.load_file("audio.mp3")?;
let samples = decoder.get_samples()?;
println!("{} channels at {} Hz", samples.len(), metadata.sample_rate);

// Decode only seconds 10.3 to 20.3
let range = decoder.get_samples_range(10.3, Some(20.3))?;
```

### Resampling and Encoding

```rust
use avioflow::{resample, save_audio, WriteOptions};

let mono_16k = resample(&samples, 44100, 16000, Some(1))?;

save_audio("out.wav", &mono_16k, &WriteOptions::new()
    .container_format("wav")
    .codec_name("pcm_s16le")
    .sample_rate(16000))?;
```


---

## License

MIT License
