# Music Assistant Audio Analysis Provider Architecture: Loudness Measurement Explained

> Explore the Music Assistant audio analysis provider architecture. Learn how loudness measurement uses FFmpeg's ebur128 filter for ReplayGain normalization.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: architecture
- Published: 2026-06-21

---

**Music Assistant implements loudness analysis through the `LoudnessAnalysisProvider` class, which uses FFmpeg's ebur128 filter to measure integrated loudness, loudness range, and true-peak values for ReplayGain-compatible volume normalization.**

Music Assistant is an open-source media server that requires precise loudness data to enable volume normalization across heterogeneous audio hardware. The loudness measurement system is built as a modular provider within the `music-assistant/server` repository, implementing the `AudioAnalysisProvider` interface to analyze PCM streams in real-time. This architecture separates the analysis logic from playback, allowing concurrent loudness computation without blocking the main event loop.

## Core Architecture Components

The loudness analysis provider consists of several specialized components that work together to process audio streams and extract EBU R 128-compliant metrics.

### LoudnessAnalysisProvider

The **`LoudnessAnalysisProvider`** class in [`music_assistant/providers/loudness_analysis/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/loudness_analysis/provider.py) serves as the main entry point. It implements the `AudioAnalysisProvider` interface and manages the lifecycle of loudness analysis sessions. Each session spawns an independent FFmpeg subprocess configured with the `ebur128=framelog=verbose` filter to compute loudness statistics according to the EBU R 128 standard.

### FFmpeg Async Wrapper

The **`FFMpeg`** helper class located in [`music_assistant/helpers/ffmpeg.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/ffmpeg.py) provides an asyncio-compatible interface to the ffmpeg binary. It handles:

- Streaming raw PCM data via stdin
- Collecting stderr logs in a `log_history` deque
- Managing process lifecycle asynchronously

This wrapper enables non-blocking audio processing, allowing the server to handle multiple concurrent analysis sessions.

### AudioAnalysisData Model

The **`AudioAnalysisData`** class defined in [`music_assistant/models/audio_analysis.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/audio_analysis.py) stores the computed metrics:

- `loudness_integrated`: Overall program loudness in LUFS
- `loudness_range`: Loudness Range (LRA) in LU
- `true_peak`: True peak level in dBTP

### ReplayGain Tag Integration

When the `write_replaygain_tags` configuration option is enabled, the provider writes track-gain metadata back to source files using functions in [`music_assistant/helpers/tags.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/tags.py). This enables persistent volume normalization across playback sessions.

## Processing Pipeline

The loudness analysis follows a strict five-stage pipeline to ensure accurate measurements and resource cleanup.

### 1. Session Initialization

When a stream requests analysis, the `_start_analysis` method validates that `volume_normalization_mode` is enabled for the player. If normalization is disabled, the analysis is skipped to conserve resources.

For valid sessions, the provider creates an FFmpeg instance with the ebur128 filter and stores it in the `_data` dictionary under a unique `session_id`:

```python
ffmpeg = FFMpeg(
    audio_input="-",
    input_format=audio_format,
    output_format=audio_format,
    audio_output="NULL",
    filter_params=["ebur128=framelog=verbose"],
    collect_log_history=True,
    loglevel="info",
)
await ffmpeg.start()
self._data[session_id] = LoudnessSessionData(ffmpeg=ffmpeg)

```

### 2. PCM Chunk Feeding

As audio data flows through the system, `process_pcm_chunk` forwards raw PCM buffers to the FFmpeg process:

```python
await data.ffmpeg.write(pcm_chunk)

```

The provider enforces a hard cap of `MAX_DURATION_SECONDS` (600 seconds) to prevent unbounded resource consumption on long-running streams.

### 3. EOF Handling

When the stream ends or hits the duration limit, `_send_eof` writes an EOF marker to ffmpeg's stdin:

```python
await data.ffmpeg.write_eof()

```

This operation is idempotent, ensuring clean process termination even if called multiple times.

### 4. Log Parsing and Finalization

The `_finalize` method orchestrates result extraction:

1. Waits for FFmpeg to exit: `await data.ffmpeg.wait()`
2. Retrieves the complete log history from `data.ffmpeg.log_history`
3. Parses metrics using `_parse_ebur128_metrics`
4. Validates results against safety thresholds:
   - Minimum duration: `MIN_DURATION_SECONDS` (10 seconds)
   - Minimum loudness: `LOUDNESS_MEASUREMENT_MIN_LUFS` (filters near-silence)

Valid measurements are rounded and encapsulated in an `AudioAnalysisData` instance:

```python
metrics = _parse_ebur128_metrics(data.ffmpeg.log_history)
loudness, loudness_range, true_peak = metrics
analysis = AudioAnalysisData(
    loudness_integrated=round(loudness, 2),
    loudness_range=round(loudness_range, 2) if loudness_range else None,
    true_peak=round(true_peak, 2) if true_peak else None,
)

```

### 5. Post-Analysis Tag Writing

If `write_replaygain_tags` is enabled in the provider manifest ([`music_assistant/providers/loudness_analysis/manifest.json`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/loudness_analysis/manifest.json)), the `post_analysis` method calculates the track gain based on a -18 LUFS reference:

```python
track_gain_db = -18.0 - analysis.loudness_integrated
await write_replaygain_track_gain(streamdetails.path, track_gain_db)

```

## Key Design Characteristics

The Music Assistant audio analysis provider architecture exhibits several critical design patterns that ensure reliability and standard compliance.

### Asynchronous Processing

The entire pipeline operates under `async`/`await`, preventing analysis tasks from blocking the server's main loop. This allows dozens of concurrent loudness measurements without degrading playback performance.

### Log-Based Metric Extraction

Rather than implementing a custom loudness algorithm, the provider relies on FFmpeg's `ebur128` filter verbose output. This guarantees strict compliance with the EBU R 128 standard while minimizing code complexity and maintenance overhead.

### Session Isolation

Each analysis receives a dedicated FFmpeg subprocess encapsulated in a `LoudnessSessionData` object. This isolation prevents stream cross-contamination and simplifies resource cleanup.

### Fail-Safe Defaults

The provider silently skips analysis for short clips (<10s), disabled normalization, or implausibly low loudness values. These safeguards protect users from extreme gain corrections that could damage audio equipment or hearing.

## Integration Example

Below is a typical implementation pattern for client code triggering loudness analysis:

```python

# Initialize analysis session

session_id = await mass.audio_analysis.start(
    streamdetails,
    audio_format=streamdetails.format,
    provider="loudness_analysis",
)

# Stream PCM chunks as they arrive

await mass.audio_analysis.process_pcm_chunk(session_id, pcm_chunk)

# Finalize and retrieve results

analysis = await mass.audio_analysis.finalize(session_id)

if analysis and analysis.loudness_integrated is not None:
    print(f"Loudness: {analysis.loudness_integrated} LUFS")
    print(f"LRA: {analysis.loudness_range} LU")
    print(f"True peak: {analysis.true_peak} dBTP")

```

## Summary

- **Music Assistant** implements loudness analysis as a modular provider in [`music_assistant/providers/loudness_analysis/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/loudness_analysis/provider.py), following the `AudioAnalysisProvider` interface.
- The **`LoudnessAnalysisProvider`** uses FFmpeg's `ebur128` filter to compute integrated loudness, loudness range, and true-peak values according to EBU R 128 standards.
- **Session isolation** ensures each analysis runs in a separate subprocess with independent resource management and a 600-second maximum duration cap.
- **Safety validations** discard measurements from short streams or near-silence content to prevent invalid gain calculations.
- **Optional ReplayGain tag writing** persists loudness metadata to source files when `write_replaygain_tags` is enabled in the provider configuration.

## Frequently Asked Questions

### How does Music Assistant calculate loudness values?

Music Assistant delegates loudness calculation to FFmpeg's `ebur128` filter running in verbose mode. The `LoudnessAnalysisProvider` streams PCM data to FFmpeg via stdin, captures the stderr logs, and parses them using `_parse_ebur128_metrics` to extract integrated loudness (LUFS), loudness range (LU), and true-peak (dBTP) values.

### What is the maximum duration for loudness analysis?

The provider enforces a hard limit of `MAX_DURATION_SECONDS` (600 seconds) per analysis session. This prevents unbounded resource usage on long-running streams or radio sources. Additionally, the provider requires a minimum of `MIN_DURATION_SECONDS` (10 seconds) of audio data to produce valid measurements.

### Can Music Assistant write ReplayGain tags to audio files?

Yes. When the `write_replaygain_tags` configuration option is enabled in the provider manifest, Music Assistant calculates track gain using the formula `-18.0 - loudness_integrated` and writes the value to the source file's metadata. This functionality is implemented in [`music_assistant/helpers/tags.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/tags.py) and supports persistent volume normalization across different playback sessions.

### Why does the provider skip analysis for some streams?

The provider automatically skips loudness measurement in three scenarios: when the player's `volume_normalization_mode` is disabled, when the audio duration is less than 10 seconds, or when the measured integrated loudness falls below `LOUDNESS_MEASUREMENT_MIN_LUFS`. These safeguards prevent resource waste and protect against extreme gain adjustments for near-silent or invalid content.