2025 Audio & VideoSpatial Computing
WWDC25 · 20 min · Audio & Video / Spatial Computing
Learn about the Apple Projected Media Profile
Dive into the Apple Projected Media Profile (APMP) and see how APMP enables 180º/360º and Wide FoV projections in QuickTime and MP4 files using Video Extended Usage signaling. We’ll provide guidance on using OS-provided frameworks and tools to convert, read/write, edit, and encode media containing APMP. And we’ll review Apple Positional Audio Codec’s (APAC) capabilities for creating and delivering spatial audio content for the most immersive experiences.
Watch at developer.apple.com ↗Chapters
- 0:00 — Introduction
- 1:12 — Non-rectilinear video fundamentals
- 3:42 — Apple Projected Media Profile specification
- 5:59 — APMP content capturing and workflows
- 8:33 — Asset conversion capabilities
- 10:45 — APMP video reading
- 11:47 — APMP video editing
- 13:42 — APMP video publishing
- 16:14 — Apple Positional Audio Codec
Code shown on screen · 6 snippets
Recognize spherical v1/v2 equirectangular content
// Convert spherical v1/v2 RFC 180/360 equirectangular content
import AVFoundation
func wasConvertedFromSpherical(url: URL) -> Bool {
let assetOptions = [AVURLAssetShouldParseExternalSphericalTagsKey: true]
let urlAsset = AVURLAsset(url: url, options: assetOptions)
// simplified for sample, assume first video track
let track = try await urlAsset.loadTracks(withMediaType: .video).first!
// Retrieve formatDescription from video track, simplified for sample assume first format description
let formatDescription = try await videoTrack.load(.formatDescriptions).first
// Detect if formatDescription includes extensions synthesized from spherical
let wasConvertedFromSpherical = formatDescription.extensions[.convertedFromExternalSphericalTags]
return wasConvertedFromSpherical
} Convert wide FOV content from supported cameras
// Convert wide-FOV content from recognized camera models
import ImmersiveMediaSupport
func upliftIntoParametricImmersiveIfPossible(url: URL) -> AVMutableMovie {
let movie = AVMutableMovie(url: url)
let assetInfo = try await ParametricImmersiveAssetInfo(asset: movie)
if (assetInfo.isConvertible) {
guard let newDescription = assetInfo.requiredFormatDescription else {
fatalError("no format description for convertible asset")
}
let videoTracks = try await movie.loadTracks(withMediaType: .video)
guard let videoTrack = videoTracks.first,
let currentDescription = try await videoTrack.load(.formatDescriptions).first
else {
fatalError("missing format description for video track")
}
// presumes that format already compatible for intended use case (delivery or production)
// for delivery then if not already HEVC should transcode for example
videoTrack.replaceFormatDescription(currentDescription, with: newDescription)
}
return movie
} Recognize Projected & Immersive Video
// Determine if an asset contains any tracks with nonRectilinearVideo and if so, whether any are AIV
import AVFoundation
func classifyProjectedMedia( movieURL: URL ) async -> (containsNonRectilinearVideo: Bool, containsAppleImmersiveVideo: Bool) {
let asset = AVMovie(url: movieURL)
let assistant = AVAssetPlaybackAssistant(asset: asset)
let options = await assistant.playbackConfigurationOptions
// Note contains(.nonRectilinearProjection) is true for both APMP & AIV, while contains(.appleImmersiveVideo) is true only for AIV
return (options.contains(.nonRectilinearProjection), options.contains(.appleImmersiveVideo))
} Perform projection or viewPacking processing
import AVFoundation
import CoreMedia
// Perform projection or viewPacking specific processing
func handleProjectionAndViewPackingKind(_ movieURL: URL) async throws {
let movie = AVMovie(url: movieURL)
let track = try await movie.loadTracks(withMediaType: .video).first!
let mediaCharacteristics = try await track.load(.mediaCharacteristics)
// Check for presence of non-rectilinear projection
if mediaCharacteristics.contains(.indicatesNonRectilinearProjection) {
let formatDescriptions = try await track.load(.formatDescriptions)
for formatDesc in formatDescriptions {
if let projectionKind = formatDesc.extensions[.projectionKind] {
if projectionKind == .projectionKind(.equirectangular) {
// handle equirectangular (360) video
} else if projectionKind == .projectionKind(.halfEquirectangular) {
// handle 180 video
} else if projectionKind == .projectionKind(.parametricImmersive) {
// handle parametric wfov video
} else if projectionKind == .projectionKind(.appleImmersiveVideo) {
// handle AIV
}
}
if let viewPackingKind = formatDesc.extensions[.viewPackingKind] {
if viewPackingKind == .viewPackingKind(.sideBySide) {
// handle side by side
} else if viewPackingKind == .viewPackingKind(.overUnder) {
// handle over under
}
}
}
}
} Specify outputBufferDescription for a stereoscopic pair
var config = try await AVVideoComposition.Configuration(for: asset)
config.outputBufferDescription = [[.stereoView(.leftEye)], [.stereoView(.rightEye)]]
let videoComposition = AVVideoComposition(configuration: config) Finish an asyncVideoCompositionRequest with tagged buffers
func startRequest(_ asyncVideoCompositionRequest: AVAsynchronousVideoCompositionRequest) {
var taggedBuffers: [CMTaggedDynamicBuffer] = []
let MVHEVCLayerIDs = [0, 1]
let eyes: [CMStereoViewComponents] = [.leftEye, .rightEye]
for (layerID, eye) in zip(MVHEVCLayerIDs, eyes) {
// take a monoscopic image and convert it to a z=0 stereo image with identical content for each eye
let pixelBuffer = asyncVideoCompositionRequest.sourceReadOnlyPixelBuffer(byTrackID: 0)
let tags: [CMTag] = [.videoLayerID(Int64(layerID)), .stereoView(eye)]
let buffer = CMTaggedDynamicBuffer(tags: tags, content: .pixelBuffer(pixelBuffer!))
taggedBuffers.append(buffer)
}
asyncVideoCompositionRequest.finish(withComposedTaggedBuffers: taggedBuffers)
} Resources
- QuickTime and ISO Base Media File Formats and Spatial and Immersive Media
- Converting projected video to Apple Projected Media Profile
- Apple HEVC Stereo Video Interoperability Profile
- Using Apple’s HTTP Live Streaming (HLS) Tools
- Core Media
- HTTP Live Streaming
- HTTP Live Streaming (HLS) authoring specification for Apple devices
- AVFoundation
Related sessions
-
26 min -
26 min -
17 min -
10 min -
15 min -
29 min