aic-sdk 0.22.0

ai-coustics Speech Enhancement SDK
Documentation
# aic-sdk - Rust Bindings for ai-coustics SDK

Rust wrapper for the ai-coustics Speech Enhancement SDK.

For comprehensive documentation, visit [docs.ai-coustics.com](https://docs.ai-coustics.com).

> [!NOTE]
> This SDK requires a license key. Generate your key at [developers.ai-coustics.com]https://developers.ai-coustics.com.

## Installation

Add to your project:

```bash
cargo add aic-sdk --features download-lib
```

## Quick Start

```rust,ignore
use aic_sdk::{include_model, ProcessorConfig, Model, Processor};

// Embed model at compile time (or use Model::from_file to load at runtime)
static MODEL: &'static [u8] = include_model!("/path/to/model.aicmodel");

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Get your license key from the environment variable
    let license_key = std::env::var("AIC_SDK_LICENSE")?;

    // Load the embedded model (or download manually at https://artifacts.ai-coustics.io/)
    let model = Model::from_buffer(MODEL)?;

    // Get optimal configuration based on the selected model
    let config = ProcessorConfig::optimal(&model);

    // Create a processor and initialize it
    let mut processor = Processor::new(&model, &license_key)?.with_config(&config)?;

    // Process mono audio
    let mut audio_block = vec![0.0f32; config.block_size];
    processor.process(&mut audio_block)?;

    Ok(())
}
```

## Usage

### SDK Information

```rust
use aic_sdk;

// Get SDK version
println!("SDK version: {}", aic_sdk::get_sdk_version());

// Get compatible model version
println!("Compatible model version: {}", aic_sdk::get_compatible_model_version());
```

### Loading Models

Download models and find available IDs at [artifacts.ai-coustics.io](https://artifacts.ai-coustics.io/).

#### Load from File

```rust,ignore
use aic_sdk::Model;

let model = Model::from_file("path/to/model.aicmodel")?;
```

#### Embed at Compile Time

```rust,ignore
use aic_sdk::{Model, include_model};

static MODEL: &'static [u8] = include_model!("/path/to/model.aicmodel");
let model = Model::from_buffer(MODEL)?;
```

#### Download from CDN

Enable the `download-model` feature:

```bash
cargo add aic-sdk --features download-lib,download-model
```

```rust,ignore
use aic_sdk::Model;

let model_path = Model::download("quail-vf-2.2-s-16khz", "./models")?;
let model = Model::from_file(&model_path)?;
```

### Model Information

```rust,ignore
// Get model ID
let model_id = model.id();

// Get optimal sample rate for the model
let optimal_rate = model.optimal_sample_rate();

// Get optimal block size for a specific sample rate
let optimal_block_size = model.optimal_block_size(48000);
```

### Configuring the Processor

```rust,ignore
use aic_sdk::{Processor, ProcessorConfig};

// Get optimal configuration for the model
let config = ProcessorConfig::optimal(&model).with_variable_block_size(false);
println!("{:?}", config);  // ProcessorConfig { sample_rate: 48000, block_size: 480, variable_block_size: false }

// Or create from scratch
let config = ProcessorConfig {
    sample_rate: 48000,
    block_size: 480,
    variable_block_size: false,
};

// Processor needs to be initialized before processing

// Option 1: Create and initialize in one step
let processor = Processor::new(&model, &license_key)?.with_config(&config)?;

// Option 2: Create first, then initialize separately
let mut processor = Processor::new(&model, &license_key)?;
processor.initialize(&config)?;
```

### OpenTelemetry

By default, telemetry follows the SDK environment configuration, such as
`AIC_SDK_OTEL_ENABLE`. Use `OtelConfig` when a single processor or VAD needs an explicit
telemetry setting or session ID.

```rust,ignore
use aic_sdk::{OtelConfig, Processor};

let otel = OtelConfig::with_session_id("session-1");
let processor = Processor::with_otel_config(&model, &license_key, &otel)?
    .with_config(&config)?;
```

### Processing Audio

```rust,ignore
let mut audio_block = vec![0.0f32; config.block_size];
processor.process(&mut audio_block)?;
```

### Ending a Session

A telemetry session is closed automatically when the processor is dropped. Call
`terminate_session` when the session has to end at a specific point instead, for example in a
lifecycle event. The processor cannot process audio afterwards.

```rust,ignore
processor.terminate_session()?;
```

The same applies to `Vad::terminate_session` and `Analyzer::terminate_session`.

### Processor Context

The processor context provides thread-safe access to processor parameters and state. You can create multiple contexts and move them to any thread for concurrent parameter updates.

```rust,ignore
use aic_sdk::ProcessorParameter;

// Get processor context
let proc_ctx = processor.context();

// Get the delay applied to the audio in samples
let delay = proc_ctx.audio_delay();

// Reset processor state (clears internal buffers)
proc_ctx.reset()?;

// Set enhancement parameters
proc_ctx.set_parameter(ProcessorParameter::EnhancementLevel, 0.8)?;
proc_ctx.set_parameter(ProcessorParameter::Bypass, 0.0)?;

// Get parameter values
let level = proc_ctx.parameter(ProcessorParameter::EnhancementLevel)?;
println!("Enhancement level: {}", level);
```

### Voice Activity Detection (VAD)

Voice activity detection runs on its own `Vad` instance, created from a dedicated VAD model
(e.g. `vad-2.1-xxs-16khz`). Enhancement models are rejected with
`AicError::ModelTypeUnsupported`.

```rust,ignore
use aic_sdk::{Model, ProcessorConfig, Vad};

let model = Model::from_file("path/to/vad_model.aicmodel")?;
let config = ProcessorConfig::optimal(&model);

let mut vad = Vad::new(&model, &license_key)?.with_config(&config)?;

// Feed mono audio to the detector. The audio block is not modified.
let audio_block = vec![0.0f32; config.block_size];
vad.process(&audio_block)?;
```

When enhancement and VAD run together, feed the VAD the original input audio, not the processor's
enhanced output. Run both on the same block instead of chaining them:

```rust,ignore
let mut audio_block = vec![0.0f32; config.block_size];

vad.process(&audio_block)?; // reads the block, does not modify it
processor.process(&mut audio_block)?; // enhances the block in place
```

Enhancement is designed to change the signal, so running the VAD on its output means detecting
speech in audio that no longer matches what the VAD model expects, and it stacks the processor's
audio delay on top of the VAD's prediction delay.

The VAD context provides thread-safe access to the prediction, the VAD parameters and its state.
You can create multiple contexts and move them to any thread for concurrent parameter updates.

```rust,ignore
use aic_sdk::VadParameter;

// Get VAD context from the VAD
let vad_ctx = vad.context();

// Configure VAD parameters. Sensitivity is the probability threshold of the model output.
vad_ctx.set_parameter(VadParameter::Sensitivity, 0.5)?;
vad_ctx.set_parameter(VadParameter::SpeechHoldDuration, 0.05)?;
vad_ctx.set_parameter(VadParameter::MinimumSpeechDuration, 0.0)?;

// Get parameter values
let sensitivity = vad_ctx.parameter(VadParameter::Sensitivity)?;
println!("VAD sensitivity: {}", sensitivity);

// How many samples the prediction lags behind the input. This delay is not applied to the
// audio, `Vad::process` leaves the buffer untouched.
let delay = vad_ctx.prediction_delay();

// Check for speech (after processing audio through the VAD)
if vad_ctx.is_speech_detected() {
    println!("Speech detected!");
}

// Clear the prediction and all internal state, e.g. when the stream is interrupted
vad_ctx.reset()?;
```

With the `async` feature, `VadAsync` mirrors `ProcessorAsync` for use in async contexts.

### Working with the Analyzer

Instantiate an analyzer pair:

```rust,ignore
let (mut collector, mut analyzer) = aic_sdk::analyzer_pair(&model, &license_key)?;
```

Initialize the collector, similar to the processor initialization

```rust,ignore
let config = ProcessorConfig::optimal(&model);
collector.initialize(&config)?;
```

Buffer the audio using `Collector::buffer`. It mirrors `Processor::process`.
See the `Processing Audio` section for more details.

Analyze the buffered audio in a separate thread:

```rust,ignore
let result = analyzer.analyze_buffered()?;
println!("Risk score: {}", result.risk_score);
```

### Async Processing

Enable the `async` feature to use [`ProcessorAsync`], which offloads
processing to a background thread pool and returns a future. The implementation
is runtime-agnostic and works on any executor (tokio, smol, async-std, ...).
The pool defaults to one thread per logical CPU; override with the
`AIC_NUM_THREADS` environment variable.

```bash
cargo add aic-sdk --features async
```

```rust,ignore
use aic_sdk::{Model, ProcessorAsync, ProcessorConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let license_key = std::env::var("AIC_SDK_LICENSE")?;
    let model = Model::from_file("path/to/model.aicmodel")?;
    let config = ProcessorConfig::optimal(&model);

    let processor = ProcessorAsync::new(&model, &license_key)?
        .with_config(&config)
        .await?;

    // The async API takes ownership of the audio block and returns it back.
    let audio = vec![0.0f32; config.block_size];
    let audio = processor.process(audio).await?;
    Ok(())
}
```

## Examples

See the example files for complete working examples:

- [`examples/enhancement.rs`]examples/enhancement.rs - Basic usage example
- [`examples/vad.rs`]examples/vad.rs - Voice activity detection with a dedicated VAD model
- [`examples/build-time-download`]examples/build-time-download - Download and embed models at compile-time
- [`examples/benchmark.rs`]examples/benchmark.rs - Run multiple processor instances concurrently until the real-time requirements are not met
- [`examples/parallel_async.rs`]examples/parallel_async.rs - Async processing with `ProcessorAsync` across multiple instances (requires `async`)

Run examples with:

```bash
export AIC_SDK_LICENSE="your_license_key_here"
cargo run --example enhancement --features download-lib,download-model
```

## Documentation

- **Full Documentation**: [docs.ai-coustics.com]https://docs.ai-coustics.com
- **Rust API Reference**: [docs.rs/aic-sdk]https://docs.rs/aic-sdk
- **Available Models**: [artifacts.ai-coustics.io]https://artifacts.ai-coustics.io

## Linking the native SDK

By default, `aic-sdk-sys` links the native AIC SDK **statically**. Static linking is the preferred way to use this library because it produces a self-contained binary and avoids runtime library discovery. Two opt-in features link a shared `libaic` instead when you need dynamic loading. They are mutually exclusive; enabling both (e.g. via `--all-features`) selects `runtime-linking`.

| Feature           | Linking                    | How `libaic` is located                                     |
| ----------------- | -------------------------- | ----------------------------------------------------------- |
| _(default)_       | static, at build time      | `AIC_LIB_PATH` directory, or downloaded with `download-lib` |
| `dynamic-linking` | dynamic, at build time     | same to link; then OS loader search at run time             |
| `runtime-linking` | dynamic, lazy on first use | OS loader search by name, or `aic_sdk::load_library(path)`  |

In every mode, point the build at the SDK with `AIC_LIB_PATH=/path/to/aic-sdk/lib`, or enable `download-lib` to fetch it automatically. Prefer the default static link unless you specifically need to ship and load a shared library:

```bash
AIC_SDK_LICENSE="…" cargo run --example enhancement \
  --features "download-lib download-model"
```

### Finding the library at run time

With `dynamic-linking` and `runtime-linking`, the OS dynamic loader must locate `libaic` when the program runs — `download-lib` only covers build time. Point the loader at the SDK `lib` directory, ship the library next to the binary, or install it system-wide:

- **Linux:** `LD_LIBRARY_PATH=/path/to/aic-sdk/lib`, or an rpath (`RUSTFLAGS="-C link-arg=-Wl,-rpath,\$ORIGIN"` + ship `libaic.so` beside the binary).
- **macOS:** `DYLD_LIBRARY_PATH`, `@rpath`/`@loader_path`, or a bundle layout.
- **Windows:** put `aic.dll` next to the `.exe` or on `PATH` (the build-time import lib `aic.lib` and the runtime `aic.dll` may be in different directories).
- **Android:** prefer the default static link. If you opt into dynamic/runtime linking, package `libaic.so` into the APK under the matching ABI directory, such as `lib/arm64-v8a/`.

`runtime-linking` loads `libaic` automatically on the first SDK call, by platform default name (`libaic.so` / `libaic.dylib` / `aic.dll`). To choose an exact file, call `load_library` first; if the library can't be found, that first call panics with a descriptive message:

```rust,ignore
unsafe { aic_sdk::load_library("/path/to/libaic.so")?; } // optional override
```

## License

This Rust wrapper is distributed under the Apache 2.0 license. The core C SDK is distributed under the proprietary AIC-SDK license.

### Third-party notices

[`NOTICE.txt`](NOTICE.txt) lists the third-party software distributed with the SDK, in two parts: the open-source code statically linked into the native `libaic` library, and the third-party Rust crates the bindings depend on. It is generated, not edited by hand. Regenerate with:

```bash
./scripts/generate-notice.sh   # requires: cargo install cargo-about
```

The `libaic` part is mirrored from the SDK release into [`aic-sdk-sys/NOTICE.libaic.txt`](aic-sdk-sys/NOTICE.libaic.txt) and kept in sync by CI; update it from a release's `NOTICE.txt` whenever you bump `aic-sdk-sys/checksum.txt`.