Debugging AVFoundation audio mixes, compositions, and video compositions
At a glance
| Item |
Summary |
| Purpose |
Build a two-clip composition and visualize its tracks, transitions, opacity ramps, and audio ramps during playback. |
| App architecture |
Storyboard UIKit controller coordinates a domain-style editor, AVPlayer, and a custom diagnostic view. |
| Main patterns |
MVC, builder, framework validation delegate, and projection of media objects into drawing data. |
| Project style |
Files are grouped into Editor and DebugView, with the screen controller at the app root. |
Project structure
AVCompositionDebugViewer/
├── PlayerViewController.swift # Screen composition and playback
├── Editor/
│ ├── SimpleEditor.swift # Composition/mix builder
│ └── SimpleEditorUtils.swift # Asset and timestamp helpers
├── DebugView/
│ ├── CompDebugView.swift # Timeline diagnostic view
│ ├── CompDebugViewUtils.swift # AV object-to-drawing transforms
│ └── CompDebugViewCAUtils.swift # Layer drawing helpers
└── Media/ # Two bundled clips
Structure observations
- Editing and visualization are separate subsystems connected through one
AVPlayerItem.
- Helpers are colocated by consumer (
Editor versus DebugView) rather than placed in a generic utilities folder.
- The storyboard owns the controller/view hierarchy; code owns media objects.
Overall architecture
flowchart LR
Storyboard["Storyboard"] --> Controller["PlayerViewController"]
Controller --> Editor["SimpleEditor"]
Editor --> Composition["AVMutableComposition + video composition + audio mix"]
Controller --> Item["AVPlayerItem"]
Composition --> Item
Item --> Player["AVPlayer / AVPlayerViewController"]
Item --> Debug["CompositionDebugView"]
Debug --> Helpers["DebugView transformation helpers"]
Reference code
AVCompositionDebugViewer/PlayerViewController.swift:48 — composition assembled for two consumers
self.editor = SimpleEditor(completion: {
self.playerItem = AVPlayerItem(asset: self.editor.composition())
self.playerItem.videoComposition = self.editor.videoComposition()
self.playerItem.audioMix = self.editor.audioMix()
self.compositionDebugView.synchronize(with: self.playerItem)
})
Interpretation
PlayerViewController is the composition root and MVC controller. SimpleEditor creates the media model. The same configured player item drives playback and diagnostic rendering, so the visualizer examines the exact objects the player uses.
Ownership and state
classDiagram
PlayerViewController *-- SimpleEditor : creates
PlayerViewController *-- AVPlayer : creates
PlayerViewController *-- AVPlayerViewController : creates
PlayerViewController *-- AVPlayerItem : creates after editing
SimpleEditor *-- AVMutableComposition : creates
SimpleEditor *-- AVMutableVideoComposition : creates
SimpleEditor *-- AVMutableAudioMix : creates
CompositionDebugView o-- AVPlayerItem : receives and retains
Ownership evidence
AVCompositionDebugViewer/PlayerViewController.swift:18 and AVCompositionDebugViewer/Editor/SimpleEditor.swift:38 — controller and editor resources
private var playerViewController = AVPlayerViewController()
private var player = AVPlayer()
private var playerItem: AVPlayerItem!
private var editor: SimpleEditor!
private lazy var editorComposition: AVMutableComposition = { /* ... */ }()
private var editorAudioMix = AVMutableAudioMix()
| Owner |
Object or state |
Relationship |
Mutation authority |
| Storyboard/view hierarchy |
PlayerViewController, outlets, debug/player views |
Instantiates and retains UI |
UIKit lifecycle; outlets are weak references |
PlayerViewController |
Editor, player, player item, child player controller |
Creates and retains |
Controller lifecycle and editor completion |
SimpleEditor |
Clips, composition, video composition, audio mix, time ranges |
Creates and builds |
Private editor methods |
CompositionDebugView |
Retained player item and derived drawing arrays |
Receives item, derives snapshots |
synchronize(with:) and private drawing methods |
Class and protocol design
AVCompositionDebugViewer/Editor/SimpleEditor.swift:28 — validation callback boundary
class SimpleEditor: NSObject, AVVideoCompositionValidationHandling {
// ... builder methods ...
func videoComposition(
_ videoComposition: AVVideoComposition,
shouldContinueValidatingAfterFindingEmptyTimeRange timeRange: CMTimeRange
) -> Bool { false }
}
| Type |
Responsibility |
Depends on |
PlayerViewController |
Orchestrate editing, playback, child containment, and debug synchronization |
UIKit, AVKit, SimpleEditor |
SimpleEditor |
Load clips and construct aligned composition, transitions, and audio ramps |
AVFoundation |
CompositionDebugView |
Render composition structure and synchronized scrub animation |
UIKit, Core Animation, AVFoundation |
CompositionTrackSegmentInfo, VideoCompositionStageInfo |
Drawing-oriented snapshots |
CoreMedia and simple values |
AVVideoCompositionValidationHandling |
Report invalid composition details during validation |
AVFoundation protocol |
Access control
| Symbol |
Access |
Verified effect |
Likely rationale |
| Controller’s player, item, editor, and child controller |
private |
Other types cannot replace playback/editing ownership. |
Keeps screen orchestration invariant inside the controller. |
| Editor clips, compositions, time ranges, queues, and builder methods |
private |
Consumers only receive the three finished AV objects. |
Enforces staged construction through SimpleEditor. |
| Debug view drawing layers, derived arrays, context, and geometry |
private |
Callers can synchronize an item but cannot manipulate rendering internals. |
Presents a narrow diagnostic-view API. |
trackSegmentInfo(from:segments:) and ramps(from:) helpers |
private free functions |
Callable only within CompDebugViewUtils.swift. |
Hides lower-level transforms behind file-level functions. |
| App classes and top-level utility functions otherwise |
internal by default |
Reusable only in this app target. |
No external library surface is intended. |
Reference code
AVCompositionDebugViewer/DebugView/CompDebugView.swift:62 — public operation, private render state
class CompositionDebugView: UIView {
private var compositionTrackSegmentInfo = [[CompositionTrackSegmentInfo]]()
private var volumeRampAsPoints = [[CGPoint]]()
private var videoCompositionStages = [VideoCompositionStageInfo]()
func synchronize(with playerItem: AVPlayerItem) { /* derive snapshots */ }
}
Logic ownership and placement
| Logic |
Owning type or file |
Placement rationale |
| Screen and child-controller lifecycle |
PlayerViewController |
UIKit controller responsibility |
| Clip loading and timestamp suitability |
SimpleEditor / SimpleEditorUtils |
Precondition for composition construction |
| Track insertion and transition overlap |
SimpleEditor.createComposition |
Mutates the composition aggregate |
| Video instructions and audio ramps |
SimpleEditor.createVideoCompAndAudioMix |
Builds coordinated outputs from the same ranges |
| Convert AV objects into drawing values |
CompDebugViewUtils.swift |
Pure transformations separated from rendering |
| Timeline drawing and synchronized layer animation |
CompositionDebugView |
View-specific behavior |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| MVC |
AVCompositionDebugViewer/PlayerViewController.swift:14 |
Controller joins storyboard views to media model objects. |
| Builder |
AVCompositionDebugViewer/Editor/SimpleEditor.swift:251, AVCompositionDebugViewer/Editor/SimpleEditor.swift:304 |
Constructs several interdependent AVFoundation artifacts in defined stages. |
| Delegate |
AVCompositionDebugViewer/Editor/SimpleEditor.swift:28, AVCompositionDebugViewer/Editor/SimpleEditor.swift:376 |
Receives detailed validation failures through a framework protocol. |
| Projection/view model values |
AVCompositionDebugViewer/DebugView/CompDebugViewUtils.swift:25, AVCompositionDebugViewer/DebugView/CompDebugViewUtils.swift:119 |
Converts complex AV objects into arrays that are straightforward to draw. |
Naming conventions
SimpleEditor signals sample-scale scope; its accessors use AV object names (composition, videoComposition, audioMix).
- Debug types use
Composition, TrackSegment, and Stage vocabulary from the visualized domain.
- Builder methods use
create...; transformation helpers name the value they return (volumeRampPoints, videoCompStageInfo).
- Files are grouped and named by subsystem responsibility.
Architecture takeaways
- Drive playback and diagnostics from the same configured
AVPlayerItem to avoid mismatched state.
- Keep mutable composition construction behind a small finished-object interface.
- Convert framework graphs into simple drawing snapshots before entering rendering code.
- Framework delegate protocols provide useful diagnostic seams even without app-defined protocols.
Source map
| Source file |
Relevant symbols |
AVCompositionDebugViewer/PlayerViewController.swift |
screen composition and playback |
AVCompositionDebugViewer/Editor/SimpleEditor.swift |
SourceClip, SimpleEditor, validation callbacks |
AVCompositionDebugViewer/Editor/SimpleEditorUtils.swift |
asset/timestamp helpers |
AVCompositionDebugViewer/DebugView/CompDebugView.swift |
drawing value types, CompositionDebugView |
AVCompositionDebugViewer/DebugView/CompDebugViewUtils.swift |
AV object-to-drawing transforms |