odin-voice 1.0.0
Python bindings for the 4Players ODIN Voice SDK
License: MIT
Requires Python: >=3.10
Keywords: voice, chat, audio, voip, odin

# odin-voice

Python bindings for the [4Players ODIN](https://www.4players.io/odin/) Voice SDK - a cross-platform solution for integrating real-time voice chat into your applications.

## What is ODIN?

ODIN is a cross-platform SDK that integrates real-time voice chat into multiplayer games, applications and websites. It supports both managed cloud hosting through 4Players and self-hosted deployments, giving developers flexibility in their deployment strategy.

### Key Features

- **Real-Time Voice Chat** - Low-latency audio transmission with Opus codec compression and automatic sample rate conversion
- **Audio Processing** - Voice Activity Detection (VAD), echo cancellation, noise suppression and automatic gain control
- **Proximity Chat** - Distance-based audio filtering that automatically adjusts based on peer positions
- **End-to-End Encryption** - Secure voice communication with customizable room passwords
- **Connection Pooling** - Efficient multi-room management for complex applications
- **Flexible Pipeline** - Extensible audio pipeline architecture supporting custom effects

## Installation

```bash
pip install odin-voice
```

## Quick Start

```python
import asyncio
import json
import time

from odin_voice import (
    OdinConnectionPool,
    OdinTokenGenerator,
    OdinEncoder,
    OdinDecoder,
    OdinAudioEncoderConfig,
    OdinAudioDecoderConfig,
)

async def main():
    # Generate a token (in production, do this server-side)
    generator = OdinTokenGenerator.from_access_key("your-access-key")
    nbf = int(time.time())
    token = generator.sign(json.dumps({
        "rid": "my-room",
        "uid": "user-123",
        "nbf": nbf,
        "exp": nbf + 300,
    }))

    async with OdinConnectionPool() as pool:
        room = pool.join_room(
            gateway="gateway.odin.4players.io",
            token=token,
        )

        # Wait for connection
        own_peer_id, media_ids = await room.joined()
        print(f"Joined as peer {own_peer_id} with media ids {media_ids}")

        # Create encoder for outgoing audio
        encoder = OdinEncoder(OdinAudioEncoderConfig(sample_rate=48000))

        # Start media stream for the first media id we got
        room.send_rpc([2, "StartMedia", {"media_id": media_ids[0], "properties": {"kind": "audio"}}])

        # Create decoder for incoming audio using remote media id
        decoder = OdinDecoder(OdinAudioDecoderConfig(media_id=1, sample_rate=48000, stereo=False))

        # Handle events and audio in parallel
        async def handle_events():
            async for rpc in room.rpcs():
                print(f"Event: {rpc}")

        async def handle_audio():
            async for datagram in room.datagrams():
                decoder.push(datagram.data)
                samples, is_silent = decoder.pop(960) # 20ms at 48kHz mono
                # Process samples...

        await asyncio.gather(handle_events(), handle_audio())

asyncio.run(main())
```

## Token Generation

Tokens authenticate users and grant access to rooms. **Generate tokens server-side only** - never embed access keys in client code.

```python
import json
import time

from odin_voice import OdinTokenGenerator

# Create from your access key
generator = OdinTokenGenerator.from_access_key("your-access-key")

# Or generate a new key pair (share public_key with 4Players)
generator = OdinTokenGenerator.generate()
print(f"Access Key: {generator.access_key}")  # Keep secret!
print(f"Public Key: {generator.public_key}")  # Share with 4Players

# Create a token
nbf = int(time.time())
token = generator.sign(json.dumps({
    "rid": "my-room",     # Room ID
    "uid": "user-id",     # User ID
    "aud": "gateway",     # Audience: "gateway" or "sfu"
    "nbf": nbf,           # Not before (Unix timestamp)
    "exp": nbf + 300,     # Expiration (Unix timestamp)
}))
```

## End-to-End Encryption

Enable E2EE by creating a cipher with a shared password. All peers must use the same password.

```python
from odin_voice import OdinCipher, OdinCryptoPeerStatus

# Create cipher with password
cipher = OdinCipher("shared-secret")

# Join room with encryption
room = pool.join_room(gateway=gateway, token=token, cipher=cipher)

# Check peer encryption status
status = cipher.peer_status(peer_id)
match status:
    case OdinCryptoPeerStatus.Encrypted:
        print("Peer is using the same password - secure!")
    case OdinCryptoPeerStatus.PasswordMismatch:
        print("Peer is using a different password")
    case OdinCryptoPeerStatus.Unencrypted:
        print("Peer is not using encryption")

# Change password at runtime
cipher.set_password("new-password")
```

## Audio Processing Pipeline

Add effects like VAD and noise suppression to encoders/decoders:

```python
from odin_voice import (
    OdinEncoder,
    OdinAudioEncoderConfig,
    OdinVadConfig,
    OdinApmConfig,
    OdinNoiseSuppressionLevel,
    OdinGainControllerVersion
)

encoder = OdinEncoder(OdinAudioEncoderConfig())
pipeline = encoder.pipeline

# Add Voice Activity Detection with default settings
vad_config = OdinVadConfig()
vad_id = pipeline.insert_vad(0, vad_config)

# Add automatic gain control, noise suppression and echo cancellation 
apm_config = OdinApmConfig(
    gain_controller_version=OdinGainControllerVersion.V2,
    noise_suppression_level=OdinNoiseSuppressionLevel.High,
    echo_canceller=True,
)
apm_id = pipeline.insert_apm(1, 48000, False, apm_config)

# Remove effects when needed
pipeline.remove_effect(vad_id)
```

## Connection Statistics

Monitor connection quality:

```python
stats = room.stats
if stats:
    print(f"RTT: {stats.rtt:.1f}ms")
    print(f"Packet Loss: {stats.udp_tx_loss:.1f}%")
    print(f"RX Datagrams: {stats.udp_rx_datagrams}")
    print(f"TX Datagrams: {stats.udp_tx_datagrams}")
```

## Documentation

For full documentation, visit [docs.4players.io](https://docs.4players.io).