Blog

Batch diarization processes a recording after it ends. Streaming diarization labels speakers while the conversation is still happening. This quickstart gets you from an API key to live speaker labels in Python, first against an audio file, then against a live microphone.
The model is Live-1, pyannoteAI's streaming speaker diarization model, generally available since July 2026. On the published streaming benchmark it records 19.8% DER across the full DIHARD III set, against 31.3% for Speechmatics real-time v2, 39.1% for Deepgram Nova 3, and 39.2% for AssemblyAI Universal Streaming v3.
By the end of this guide, you will have a streaming client that opens a WebSocket, sends audio in real time, and prints segments like these as they arrive:
Before you start
You will need a pyannoteAI account and an API key from the dashboard. The account documentation covers key creation and team setup.
Live-1 is a paid, generally available product. Per the pricing page as of September 2026:
Developer | Starter | Enterprise | |
|---|---|---|---|
Plan fee | €19.00/month, including €19 of usage credit | €99.00/month, including €99 of usage credit | Custom |
Live-1 streaming | €0.198 per audio hour | €0.170 per audio hour | Volume-based |
A few specifications shape the code:
Sample rate: 16 kHz
Channels: mono
Encoding: float32 PCM
Chunk size: 100 ms per WebSocket message, so 1,600 samples, or 6,400 bytes at float32
Algorithmic latency: sub-300 ms
Install the libraries this quickstart uses:
websockets handles the connection, sounddevice captures microphone audio, numpy converts sample formats, and requests creates the stream.
Set your API key as an environment variable so it stays out of your code:
Step 1: create a stream, then connect to it
Streaming uses two steps rather than one. You POST to the create stream endpoint to create a session, and the response contains the WebSocket URL to stream audio into. There is no hard-coded socket address to guess.
The returned URL carries a single-use token scoped to that one session, so the WebSocket connection itself needs no additional authentication.
Step 2: handle the diarization events
Live-1 is event-driven. Rather than emitting finished segments, it tells you when a speaker starts and when a speaker stops, so your application always knows who currently holds the floor.
The two event types are diarization_speaker_start and diarization_speaker_end, both documented in the streaming API reference. Each message carries a data object holding a timestamp in seconds from the start of the stream and a speaker label:
This handler turns those events back into printable segments by holding each speaker's start time until their turn closes.
Holding open turns in a dictionary rather than a single variable matters, because more than one speaker can be active at once. Overlapping speech produces two open turns, and both close independently.
Step 3: stream an audio file
A file is the easiest first test because it is deterministic. The trick is to send chunks at the pace they would play, so the server sees a realistic real-time stream rather than a burst.
This example assumes a 16 kHz mono WAV. There is a conversion command in the production notes for any other format. WAV files store 16-bit integers, so each chunk is converted to float32 before it goes on the wire.
Two coroutines run together. send_file reads the file in 100 ms slices and paces them with asyncio.sleep, simulating a live feed. receive_labels reads events as the server emits them. Running both concurrently is what makes the exchange real-time: labels appear while the file is still streaming rather than after it finishes.
Pacing matters for a second reason. The server enforces a maximum 5-second buffer, so pushing a file faster than real time closes the connection. The streaming tutorial lists that limit alongside the other stream constraints.
Step 4: switch to a live microphone
The same connection logic works for live audio. The only change is the source.
sounddevice delivers fixed-size blocks through a callback that runs on a separate thread, so each block is handed to the asyncio loop through a thread-safe queue. Requesting dtype="float32" means the samples arrive in the format Live-1 expects, with no conversion step.
Setting blocksize=SAMPLES_PER_CHUNK makes sounddevice hand you exactly 100 ms per callback, so each block maps to one message with no extra buffering. Run it, talk, and bring in a second voice. You will watch SPEAKER_00 and SPEAKER_01 separate in real time.
Reading the results
Speaker labels stay consistent across the whole session, so SPEAKER_00 early in the conversation is the same voice as SPEAKER_00 later. Live-1 maintains that consistency through a speaker tracking layer that holds state across chunks, which is what lets you accumulate per-speaker state, drive turn-taking logic, or attribute a live transcript.
Two limits are worth knowing before you build on the labels. Live-1 tracks up to 8 speakers simultaneously, and any speakers beyond that get merged into one label. The labels themselves are anonymous, per-stream identifiers, so resolving one to a named person is a job for voiceprints and speaker identification on the batch side.
To pair labels with words, run a streaming transcription alongside this and commit each completed transcript segment to whichever speaker held the floor. The streaming diarized transcription tutorial walks through that pattern end to end, including the detail that matters most: disable the transcription model's own turn detection so speaker boundaries come from the audio rather than from the word stream.
Production notes
Audio format. Live-1 expects 16 kHz mono float32 PCM. Convert any other source first. For files:
ffmpeg -i input.mp3 -ar 16000 -ac 1 output_16k_mono.wav.Latency. Live-1's algorithmic latency is sub-300 ms. Your end-to-end figure adds network round-trip and your own compute on top, so measure it on your real path rather than assuming the model figure.
Cost. Pricing is shown per hour, and billing is per second of audio actually processed. Send 156 seconds and you are billed 156 seconds. Send 1h00m10s and you are billed 3,610 seconds rather than two hours. A 20-second minimum applies to each successful stream, and only jobs that reach
succeededare billed at all.Overlap. More than one speaker can be active simultaneously, which is why the handler above tracks open turns in a dictionary. Code that assumes a single current speaker will drop the second voice in an interruption.
Reconnection. Networks drop. Wrap the connection in a retry loop with backoff, and decide whether a dropped session resumes or restarts. A restart means new speaker labels, so downstream state has to be reconciled.
Idle timeout. Streams close after 5 seconds with no audio received. Send silence rather than nothing during natural pauses you want to keep open.
Shutdown. Close the socket cleanly so the final events flush.
Library version. Recent
websocketsreleases useadditional_headerswhere older releases useextra_headers. This quickstart passes authentication on thePOSTrather than on the socket, which sidesteps the difference.
The endpoint, audio format, chunk size, and event names used throughout follow the streaming API reference and the streaming diarization tutorial, which are the references to check when you extend this beyond the two examples here.
Where to go next
You now have a streaming client that turns live audio into real-time speaker labels. The natural next step is wiring it into a voice agent, where knowing who is speaking drives turn-taking and routing, or into a live captioning surface where each line carries its speaker as it scrolls. The streaming diarized transcription tutorial is the shortest path to the second one.
For recorded audio, the batch diarization endpoint shares the same speaker intelligence model and is the better fit.
Want to see it before you build? Run it in the API playground on your own audio, then come back here to put it in your stack.
