Technical Deep Dive•Feb 2026 (6 min read)
Architecting a Real-Time Audio Streaming Pipeline with FastAPI & Redis
Decoupling stream extraction, transcode jobs, and multi-worker audio piping to achieve sub-second playback startup.
FastAPIPythonRedisAudio ProcessingCelery
How we built a scalable, distributed audio extraction and streaming backend for the Myra music platform, overcoming rate-limits, cold starts, and network jitter.
1. The Challenge of Low-Latency Audio Streaming
When building the backend for Myra Music, our primary performance metric was Time-to-First-Audio (TTFA). Standard approaches that download an entire audio stream, transcode it synchronously on disk, and then return a static URL resulted in 4–8 second delays on cold cache hits.
To achieve sub-second audio startup times across mobile and web clients, we needed a fully streaming architecture where audio chunks begin piping to the client socket within 250 milliseconds of the request.
Core Bottleneck: Synchronous yt-dlp extraction blocks the Python GIL and event loop. Resolution requires asynchronous subprocess offloading and distributed worker queues.
2. The Distributed Pipeline Architecture
We designed a three-layer pipeline:
1. **Edge Gateway (FastAPI ASGI)**: Validates requests, checks two-tier cache (in-memory LRU + Upstash Redis), and immediately dispatches stream jobs.
2. **Worker Pool (Celery + yt-dlp)**: Background extractors resolve direct HLS/DASH media manifests with rotating proxy fallbacks.
3. **Chunked Stream Transcoder**: Pipes raw audio through an FFmpeg subprocess directly into FastAPI's `StreamingResponse` using non-blocking async generators.
stream_service.pypython
import asyncio
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
import redis.asyncio as redis
app = FastAPI()
redis_client = redis.from_url("redis://localhost:6379")
async def audio_stream_generator(audio_url: str):
"""Pipes FFmpeg transcode chunks directly to ASGI response buffer."""
cmd = [
"ffmpeg",
"-reconnect", "1",
"-reconnect_streamed", "1",
"-reconnect_delay_max", "2",
"-i", audio_url,
"-f", "mp3",
"-acodec", "libmp3lame",
"-b:a", "192k",
"-vn",
"pipe:1"
]
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.DEVNULL
)
try:
while True:
chunk = await process.stdout.read(64 * 1024) # 64KB chunks
if not chunk:
break
yield chunk
finally:
if process.returncode is None:
process.kill()
@app.get("/api/v1/stream/{track_id}")
async def stream_track(track_id: str):
cached_url = await redis_client.get(f"stream:{track_id}")
if not cached_url:
cached_url = await resolve_stream_url(track_id)
await redis_client.setex(f"stream:{track_id}", 3600, cached_url)
return StreamingResponse(
audio_stream_generator(cached_url.decode("utf-8")),
media_type="audio/mpeg",
headers={"Accept-Ranges": "bytes", "Cache-Control": "public, max-age=3600"}
)3. Tiered Caching Strategy & Benchmarks
Audio streaming endpoints have high read amplification. We structured the caching strategy into two tiers:
- **L1 In-Memory LRU**: Hot metadata and search queries (sub-1ms retrieval).
- **L2 Redis (Upstash)**: Pre-resolved stream URLs with TTLs aligned with CDN token expirations (5ms retrieval).
Under simulated load tests of 1,500 concurrent listeners, the caching layer absorbed 92% of extraction requests, reducing average server CPU utilization by 65% and lowering TTFA from 4.2s to 380ms.
Key Engineering Takeaways
- Decouple extraction from streaming via Celery task queues to avoid blocking asynchronous ASGI worker loops.
- Implement a two-tier caching layer (in-memory LRU for hot metadata + Upstash Redis for signed audio stream URLs).
- Use chunked transfer encoding (`Transfer-Encoding: chunked`) to start client audio buffers before full transcode completes.
- Build resilient proxy rotation with fallback mechanisms to maintain 99.9% audio stream availability.