Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Generate DNN weights
run: python generate_weights.py

- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
Expand Down
116 changes: 116 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Release

on:
push:
tags:
- 'v*'

env:
CARGO_TERM_COLOR: always

jobs:
# Build and generate weights on Linux (fast compilation)
generate-weights:
name: Generate DNN Weights
runs-on: ubuntu-latest
outputs:
bin_name: ${{ steps.generate.outputs.bin_name }}
md5_name: ${{ steps.generate.outputs.md5_name }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential

- name: Generate weights
id: generate
run: |
python3 generate_weights.py

# Get the generated filenames
BIN_FILE=$(ls target/model/*.bin)
MD5_FILE=$(ls target/model/*.bin.md5)

echo "bin_name=$(basename $BIN_FILE)" >> $GITHUB_OUTPUT
echo "md5_name=$(basename $MD5_FILE)" >> $GITHUB_OUTPUT

# Display info
echo "Generated files:"
ls -la target/model/
echo ""
echo "MD5 checksum:"
cat $MD5_FILE

- name: Upload weights artifacts
uses: actions/upload-artifact@v4
with:
name: dnn-weights
path: |
target/model/*.bin
target/model/*.bin.md5
retention-days: 1

# Create GitHub release and upload assets
release:
name: Create Release
needs: generate-weights
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- name: Download weights artifacts
uses: actions/download-artifact@v4
with:
name: dnn-weights
path: release-assets

- name: Get version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Release version: $VERSION"

- name: Extract Cargo.toml version
id: cargo_version
run: |
CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
echo "cargo_version=$CARGO_VERSION" >> $GITHUB_OUTPUT
echo "Cargo.toml version: $CARGO_VERSION"

- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: ${{ steps.version.outputs.version }}
body: |
## opus-head-sys ${{ steps.version.outputs.version }}

FFI bindings to the Opus audio codec with DRED and OSCE support.

### DNN Weights

When using DRED or OSCE features, you need to load the DNN weights at runtime using `OPUS_SET_DNN_BLOB()`.

Download the weights file below and load it in your application:
- **${{ needs.generate-weights.outputs.bin_name }}** - DNN weights blob
- **${{ needs.generate-weights.outputs.md5_name }}** - MD5 checksum

### Installation

```toml
[dependencies]
opus-head-sys = "${{ steps.cargo_version.outputs.cargo_version }}"
```

See the [README](https://github.com/xnorpx/rust-opus#readme) for usage instructions.
files: |
release-assets/${{ needs.generate-weights.outputs.bin_name }}
release-assets/${{ needs.generate-weights.outputs.md5_name }}
draft: false
prerelease: ${{ contains(steps.version.outputs.version, '-') }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ Cargo.lock
# VS Code
.vscode/
*.code-workspace

# Python
__pycache__/
15 changes: 10 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ links = "opus"
build = "build.rs"
exclude = [
"*.py",
"*.bin",
"*.bin.gz",
"*.zip",
"__pycache__/",
".github/",
"vendored/opus/.git*",
"vendored/opus/.gitlab-ci.yml",
Expand All @@ -23,6 +27,7 @@ exclude = [
"vendored/opus/dnn/**/*.ipynb",
"vendored/opus/dnn/**/*.tar",
"vendored/opus/dnn/**/*.py",
"vendored/opus/dnn/torch/",
"vendored/opus/scripts/",
"vendored/opus/meson/",
]
Expand All @@ -33,10 +38,10 @@ exclude = [
cmake = "0.1"

[features]
default = ["dred", "osce", "fast-math"]
# Enable DRED (Deep REDundancy) for packet loss resilience
dred = []
# Enable OSCE (Opus Speech Coding Enhancement) for improved quality
osce = []
default = ["dnn", "fast-math"]
# Enable DNN-based features: DRED (Deep REDundancy) and OSCE (Opus Speech Coding Enhancement)
# DRED provides packet loss resilience, OSCE provides improved audio quality
# Requires loading weights at runtime via OPUS_SET_DNN_BLOB()
dnn = []
# Enable fast math optimizations (enables OPUS_FLOAT_APPROX and OPUS_FAST_MATH)
fast-math = []
62 changes: 60 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# opus-head-sys

Rust FFI bindings to the [Opus audio codec](https://opus-codec.org/) with vendored source code, including AI/DNN models.
Rust FFI bindings to the [Opus audio codec](https://opus-codec.org/) with vendored source code, including support for AI/DNN features (DRED, OSCE).

## Overview

Expand All @@ -9,9 +9,10 @@ This crate provides low-level bindings to libopus, built from vendored source co
### Features

- **Vendored source** - Opus is compiled from source, no system dependencies required
- **AI models included** - Ships with Opus 1.5+ DNN models for DRED (Deep Redundancy) and other ML features
- **AI/DNN support** - DRED (Deep Redundancy) and OSCE (Opus Speech Coding Enhancement) via runtime weight loading
- **Full API coverage** - Core, Multistream (surround), and Projection (ambisonics) APIs
- **Static linking** - Single binary with no runtime dependencies on libopus
- **Small crate size** - Under 5MB (DNN weights loaded at runtime, not embedded)

### What this crate is NOT

Expand All @@ -22,6 +23,63 @@ This crate focuses on simplicity and staying close to upstream Opus. The followi

If you need these features, consider other opus-sys crates like [`audiopus_sys`](https://crates.io/crates/audiopus_sys) or [`opus-sys`](https://crates.io/crates/opus-sys).

## Using AI Features (DRED/OSCE)

The AI features require loading DNN weights at runtime. This keeps the crate small while still supporting the full Opus AI capabilities.

### 1. Enable the features

```toml
[dependencies]
opus-head-sys = { version = "0.1", features = ["dred", "osce"] }
```

### 2. Download the weights file

Download `opus_data-<hash>.bin` (~14MB) from the [releases page](https://github.com/xnorpx/rust-opus/releases).

Place it in your project or a known location to load at runtime.

### 3. Load weights at runtime

```rust
use opus_head_sys::*;

// Read the weights file
let weights = std::fs::read("opus_data-<hash>.bin").expect("Failed to read weights");

unsafe {
// Create encoder
let mut error = 0;
let encoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP as i32, &mut error);

// Load DNN weights into encoder
opus_encoder_ctl(
encoder,
OPUS_SET_DNN_BLOB_REQUEST as i32,
weights.as_ptr() as *const std::ffi::c_void,
weights.len() as i32,
);

// Now DRED encoding is available
// Enable DRED:
opus_encoder_ctl(encoder, OPUS_SET_DRED_DURATION_REQUEST as i32, 100); // 100ms of redundancy

// ... encode audio ...

opus_encoder_destroy(encoder);
}
```

The same applies to the decoder for OSCE (speech enhancement) features.

### Why runtime loading?

The DNN weights are ~14MB, which would exceed crates.io's 10MB limit if embedded. Runtime loading also allows:
- Updating weights without recompiling
- Sharing weights across multiple encoder/decoder instances
- Optional AI features (don't load weights if you don't need them)

## Vendored Version

The vendored Opus source is tracked in `vendored/OPUS_VERSION`. Run the update script to sync with upstream:
Expand Down
123 changes: 123 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Release Process

This document describes how to create a new release of `opus-head-sys`.

## Pre-release Checklist

1. **Update version** in `Cargo.toml`
2. **Update CHANGELOG** (if you have one)
3. **Run tests** to ensure everything works:
```bash
cargo test --all-features
cargo clippy --all-features
```
4. **Verify package** builds correctly:
```bash
cargo publish --dry-run
```

## Creating a Release

### 1. Commit Version Changes

```bash
git add Cargo.toml
git commit -m "chore: bump version to X.Y.Z"
git push origin main
```

### 2. Create and Push a Git Tag

The release pipeline triggers on version tags matching `v*`:

```bash
# Create annotated tag
git tag -a v0.1.0 -m "Release v0.1.0"

# Push the tag
git push origin v0.1.0
```

### 3. Release Pipeline

Once you push the tag, the GitHub Actions release pipeline (`.github/workflows/release.yml`) will automatically:

1. **Generate DNN weights** - Compiles and runs the official Opus `write_lpcnet_weights.c` program
2. **Create artifacts**:
- `opus_data-<model_hash>.bin` - DNN weights blob
- `opus_data-<model_hash>.bin.md5` - MD5 checksum
3. **Create GitHub Release** with:
- Release notes
- Attached weight files as downloadable assets

Monitor the workflow at: `https://github.com/xnorpx/rust-opus/actions`

### 4. Publish to crates.io

After the GitHub release is created successfully:

```bash
# Login to crates.io (first time only)
cargo login

# Publish the crate
cargo publish
```

**Note:** The crate excludes the large DNN weight data files (`*_data.c`) to stay under crates.io's 10MB limit. Users need to download weights separately from GitHub releases when using DRED/OSCE features.

## Version Numbering

This project follows [Semantic Versioning](https://semver.org/):

- **MAJOR** (X.0.0) - Incompatible API changes
- **MINOR** (0.X.0) - New features, backwards compatible
- **PATCH** (0.0.X) - Bug fixes, backwards compatible

For pre-releases, use suffixes like `-alpha`, `-beta`, `-rc.1`:
```bash
git tag -a v0.2.0-alpha.1 -m "Pre-release v0.2.0-alpha.1"
```

## Manual Weight Generation

If you need to generate weights locally (e.g., for testing):

```bash
# Requires a C compiler (MSVC, GCC, or Clang)
python generate_weights.py --output ./weights

# Output files:
# weights/opus_data-<hash>.bin - DNN weights blob
# weights/opus_data-<hash>.bin.md5 - MD5 checksum
```

## Troubleshooting

### Publish fails with "crate too large"

Ensure the exclusions in `Cargo.toml` are correct:
```bash
cargo package --list | grep -E "\.c$|data"
```

No `*_data.c` files should appear in the output.

### Release workflow fails

Check the GitHub Actions logs. Common issues:
- Missing C compiler in CI environment
- Opus source not properly checked out (submodules)

### Weights don't match expected hash

The model hash comes from `vendored/opus/autogen.sh`. If Opus updates their models, the hash will change automatically.

## Release History

### v0.1.0
- Initial release
- FFI bindings to Opus codec
- DRED (Deep REDundancy) support
- OSCE (Opus Speech Coding Enhancement) support
- Runtime DNN weight loading via `OPUS_SET_DNN_BLOB()`
Loading