audio_io
A Flutter plugin for cross-platform real-time audio streaming. Provides low-latency audio input/output with simple Stream-based API for audio processing, recording, and visualization.
Live demo
Try the PCM16 streaming + Gemini Live example in your browser: wamf.github.io/audio_io — paste your own Gemini API key, allow the microphone, and talk.
Features
- Real-time audio streaming from microphone to Flutter
- Audio output/playback through speakers
- Cross-platform support (iOS, macOS, Android, Web, Linux, Windows)
- Simple Stream-based API
- PCM16 byte streams at 16/24/48 kHz for realtime voice APIs (e.g. Gemini Live)
- Configurable audio latency modes
- Optional dedicated audio isolate on FFI platforms
- Consistent data format across all platforms (Float64, 48kHz, mono)
- Low-latency audio processing
- Volume level monitoring
Platform Support
| Platform | Status | Implementation |
|---|---|---|
| iOS | ✅ Supported | Native (AVAudioEngine) |
| macOS | ✅ Supported | Native (AVAudioEngine) |
| Android | ✅ Supported | FFI (miniaudio) |
| Web | ✅ Supported | Web Audio API |
| Linux | ✅ Supported | FFI (miniaudio) |
| Windows | ✅ Supported | FFI (miniaudio) |
Getting Started
Installation
Add audio_io to your pubspec.yaml:
dependencies:
audio_io: ^0.5.0
iOS Setup
Add microphone usage description to your Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to the microphone for audio processing.</string>
macOS Setup
- Add microphone usage description to your
macos/Runner/Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to the microphone for audio processing.</string>
- Enable audio input in your entitlements files:
macos/Runner/DebugProfile.entitlementsmacos/Runner/Release.entitlements
<key>com.apple.security.device.audio-input</key>
<true/>
Android Setup
Add microphone permission to your android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
Request the microphone permission at runtime (for example with the
permission_handler package) before calling start(). If permission has
not been granted, start() throws an AudioIoException whose
isPermissionDenied is true.
Usage
import 'package:audio_io/audio_io.dart';
// Get the audio instance
final audioIo = AudioIo.instance;
// Configure latency (optional)
await audioIo.requestLatency(AudioIoLatency.Balanced);
// Start audio processing
await audioIo.start();
// Listen to input audio stream
audioIo.input.listen((audioData) {
// Process audio data (List<double>)
print('Received ${audioData.length} samples');
// Calculate volume level (RMS)
final sum = audioData.fold<double>(
0.0, (sum, sample) => sum + sample * sample);
final rms = sqrt(sum / audioData.length);
});
// Send audio to output (echo example)
audioIo.input.listen((data) {
audioIo.output.add(data);
});
// Stop audio processing
await audioIo.stop();
Latency Configuration
The plugin supports three latency modes:
enum AudioIoLatency {
Realtime, // Lowest latency (~1.5ms buffer)
Balanced, // Balanced latency/CPU (~3ms buffer)
Powersave, // Lower CPU usage (~6ms buffer)
}
// Set before starting audio
await audioIo.requestLatency(AudioIoLatency.Realtime);
PCM16 byte streams (realtime voice APIs)
Realtime voice APIs typically speak little-endian PCM16 at specific sample
rates (Gemini Live expects 16 kHz in / 24 kHz out). startWith configures
byte streams in the rate and format the API expects, while the engine keeps
its internal 48 kHz contract — resampling and conversion are handled for
you, on the audio rendering thread where the platform supports it:
final audioIo = AudioIo.instance;
await audioIo.startWith(const AudioIoConfig(
format: AudioIoFormat.pcm16,
sampleRate: AudioIoSampleRate.rate16000,
));
// Microphone as PCM16 bytes at 16 kHz
audioIo.inputBytes.listen(api.sendAudio);
// Play PCM16 bytes (decode + resample handled internally)
api.audioResponses.listen(audioIo.outputBytes.add);
// Barge-in: discard audio queued for playback but not yet rendered
await audioIo.clearOutput();
See example_gemini_live for a complete voice conversation app, or try it
in the browser at wamf.github.io/audio_io.
Threading (optional dedicated audio isolate)
By default the audio transport runs on the main isolate, which suits most apps and every platform. On the FFI back ends (Android, Windows, Linux) you can opt into a dedicated audio isolate so device polling and native buffer copies are unaffected by main-isolate jank (heavy widget builds, GC):
await audioIo.startWith(const AudioIoConfig(
threading: AudioIoThreading.audioIsolate,
));
Platforms without dedicated-isolate support (iOS, macOS, web) silently fall
back to main-isolate operation. The input / output streams still surface
on the main isolate in both modes, so listener callbacks run there; move
heavy DSP out of the listener if it competes with UI work.
Audio Format
All platforms use a consistent audio format:
- Sample Rate: 48kHz (may adapt to device capabilities)
- Channels: Mono (1 channel)
- Data Type: Double precision floats (Float64/double)
- Stream Format: Chunks of audio samples as
List<double> - Internal Processing: Platform-specific (Float32 on native platforms)
Requirements
- Dart SDK: >=3.0.0 <4.0.0
- Flutter: >=3.10.0
- iOS: 13.0 or higher
- macOS: 10.15 or higher
License
See LICENSE file for details.