# Dolby Vision Profile 7 on Apple TV

> Tomo TV converts Dolby Vision Profile 7 UHD disc rips to Profile 8.1 on Apple TV by rewriting four fields in each frame's RPU NAL, without decoding the base layer. The enhancement layer is dropped, while the mapping curves, MMR constants and color metadata travel unchanged. AVPlayer then draws what Console.app confirms as hdrMode = Dolby.

- ID: LAB-001
- Area: Playback engine · Dolby Vision
- Problem: UHD disc rips use Dolby Vision Profile 7, a dual-layer format Apple TV cannot decode as Dolby Vision.
- Solution: DolbyVisionConverter rewrites each frame's RPU NAL to single-layer Profile 8.1, at stream-copy cost.
- Status: In production
- Published: 2026-10-05
- Author: Keiver Hernandez
- URL: https://keiver.dev/lab/dolby-vision-profile-7-apple-tv
- Source: https://github.com/keiver/tomotv

Dolby Vision on a UHD Blu-ray is Profile 7, a dual-layer format where a base HEVC track pairs with a separate enhancement layer. Apple decodes no dual-layer Dolby Vision, so those rips reached the TV as plain HDR10 no matter how the playlist was labeled. We added Profile 7 support to Tomo TV by converting each frame's RPU (Reference Processing Unit) to Profile 8.1 inside our on-device FFmpeg build. The RPU is the per-frame NAL unit carrying tone-mapping curves. The base layer is never decoded or re-encoded.

## Apple TV plays one layer; Dolby Vision disc rips carry two

Two Dolby Vision profiles matter here. Profile 8 is a single HEVC layer with its Dolby Vision metadata riding inside it. Profile 7 is the UHD disc format: it carries the base layer alongside a separate enhancement layer (EL). Apple's decoders have no EL path. Without conversion, the EL is ignored and the base layer plays as HDR10.

## Four metadata fields separate Dolby Vision Profile 7 from 8.1 on Apple TV

The two profiles are closer than they look. We read the actual transform from dovi_tool's committed output rather than a written description, comparing the raw bytes of dual-layer and converted RPUs. The mapping curves, MMR (Multi-Matrix Reshaping) constants and color metadata travel across untouched. Four things change. The EL resampling flag and residual flag are cleared, the NLQ (non-linear quantizer) used by the EL is zeroed out, and the profile is restated as 8 with compatibility id 1. The container's configuration record is restated as single-layer 8.1 too.

The conversion path looks like this:

```mermaid
flowchart LR
    A["Profile 7 input<br/>dual-layer"] --> B["EL NAL type 63<br/>dropped"]
    A --> C["Base layer<br/>stream copy"]
    A --> D["RPU NAL<br/>per frame"]
    D --> E["Four fields<br/>rewritten"]
    C --> F["Profile 8.1<br/>to AVPlayer"]
    E --> F
```

The last two of the four changes, the quantizer and the profile:

[packages/tomo-engine/ios/LocalRemuxer/DolbyVisionConverter.swift:170-179](https://github.com/keiver/tomotv/blob/8d75511f63afcc20a72d6c24ed98db2be02080df/packages/tomo-engine/ios/LocalRemuxer/DolbyVisionConverter.swift#L170-L179)

```swift
if let mapping = UnsafeMutablePointer(mutating: av_dovi_get_mapping(metadata)) {
    mapping.pointee.nlq_method_idc = AV_DOVI_NLQ_NONE
    mapping.pointee.nlq_pivots = (0, 0)
    mapping.pointee.nlq = (AVDOVINLQParams(), AVDOVINLQParams(), AVDOVINLQParams())
}

context.enable = 1
context.cfg.dv_profile = 8
context.cfg.dv_bl_signal_compatibility_id = 1
context.cfg.el_present_flag = 0
```

## Our fixtures passed; a real disc proved the EL detection wrong

Our first cut detected the enhancement layer by its `nuh_layer_id`, dropping NAL units where the layer id was greater than zero. That rule passed every hand-built test. Twelve frames off a real 4K Profile 7 source carried 12 RPUs and 43 enhancement-layer NAL units, every one on layer id 0. The EL uses HEVC unspecified NAL type 63, not a higher layer id. The fix is one condition: drop any NAL of type 63, or any NAL with `nuh_layer_id > 0`.

The tests that anchor the code compare our output against dovi_tool's reference for both MEL (minimal enhancement layer, identity reshaping curves) and FEL (full enhancement layer, real MMR constants). A careless conversion that flattened FEL's mapping curves would still pass a MEL-only suite.

## The in-app badge tells you nothing; Console.app does

Tomo TV shows a Dolby Vision badge on the player screen. That badge comes from the server's metadata for the file, not from the playback pipeline. The badge appeared even when Profile 7 files were playing back as HDR10. The only measurement that counts is `hdrMode = Dolby` in Console.app during playback on the device.

![The Info panel in Tomo TV's player on Apple TV, with 4K and Dolby Vision badges under the title](/lab/dolby-vision-profile-7-apple-tv/dolby-vision-badge.webp "The Dolby Vision badge in the player's Info panel. It repeats the server's metadata for the file, so it shows even when the TV is getting HDR10.")

This is the line the Apple TV logged for a Profile 7 disc rip after we shipped the conversion. Our own process writes it as playback opens, and only with Match Dynamic Range turned on in the Apple TV's settings:

```text
TomoTV  -[AVSharedDisplayManager setPreferredDisplayCriteria:forScene:]
  Requesting display configuration { refreshRate = 23.98; hdrMode = Dolby; }
```

`hdrMode = Dolby` is tvOS asking the TV for a Dolby Vision mode. Before the conversion, the same files reached the TV as HDR10.

## Why we did not add libdovi or re-encode

FFmpeg returns `AVERROR_PATCHWELCOME` when asked to handle Profile 7, describing enhancement layer coding as unsupported. Clearing the two flags FFmpeg's profile classifier reads does make it report Profile 8, but the output will not parse back because the mapping data is still in dual-layer form. Getting FFmpeg to do this conversion requires libdovi, a separate native library for Dolby Vision bitstream work.

We chose to read dovi_tool's own committed transform and write the conversion directly in our build. The base layer never touches a decoder. The conversion adds no dependency. A source the converter declines (non-HEVC, annex B extradata, no RPU present) fails the session cleanly rather than delivering a stream that contradicts its own manifest. [When a Jellyfin client should transcode](/lab/when-should-a-jellyfin-client-transcode) covers when and why the server picks up.

---

The lesson extends to any format pairing where the content does not change but the container's signalling does. When two profiles differ by a fixed set of metadata fields and the payload is otherwise identical, the rewrite costs nothing the stream copy does not already pay. The field list comes from the reference tool's committed output, not from a specification document, because the document describes intent and the committed output describes what a real player accepted. What happens to the soundtrack of the same rips is in [Dolby Atmos and TrueHD on Apple TV](/lab/dolby-atmos-truehd-apple-tv), and the app itself is at [Tomo TV](/lab/tomotv).

Drafted with Claude from Tomo TV's code and device measurements. Reviewed, edited and verified by Keiver Hernandez.

Cite as: Keiver Hernandez, "Dolby Vision Profile 7 on Apple TV", keiver.dev/lab/dolby-vision-profile-7-apple-tv, 2026-10-05.
