Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Let's make an Immersive Video with APMP
Search
Shingo Tamaki
August 10, 2025
Technology
0
23
Let's make an Immersive Video with APMP
This document summarizes a brief explanation of APMP, which was announced at WWDC25.
Shingo Tamaki
August 10, 2025
Tweet
Share
More Decks by Shingo Tamaki
See All by Shingo Tamaki
Use Gemini CLI from Claude Code as part of Sub Agent
tamaki
1
200
Firebase Studioで始めるモバイルアプリ開発入門
tamaki
0
23
Introduction to Claude Code Action
tamaki
0
710
AIエージェントを使ったiOSアプリ開発を試してみた
tamaki
0
140
沖縄モバイルアプリ開発勉強会#1
tamaki
0
130
iOSアプリ開発を始めよう
tamaki
0
210
詳解xcresult.pdf
tamaki
0
400
メルペイでのリグレッションテスト自動化推進のこれまでとこれから
tamaki
0
780
What do you want to test with UI Test v2
tamaki
2
920
Other Decks in Technology
See All in Technology
Lazy Constant - finalフィールドの遅延初期化
skrb
0
130
やり方は一つだけじゃない、正解だけを目指さず寄り道やその先まで自分流に楽しむ趣味プログラミングの探求 2025-11-15 YAPC::Fukuoka
sugyan
1
440
ZOZOTOWNカート決済リプレイス ── モジュラモノリスという過渡期戦略
zozotech
PRO
0
140
Rubyist入門: The Way to The Timeless Way of Programming
snoozer05
PRO
6
340
Data & AIの未来とLakeHouse
ishikawa_satoru
0
720
設計は最強のプロンプト - AI時代に武器にすべきスキルとは?-
kenichirokimura
1
350
Flutterで実装する実践的な攻撃対策とセキュリティ向上
fujikinaga
1
340
AWS資格は取ったけどIAMロールを腹落ちできてなかったので、年内に整理してみた
hiro_eng_
0
200
Redux → Recoil → Zustand → useSyncExternalStore: 状態管理の10年とReact本来の姿
zozotech
PRO
9
4.4k
Spring Boot利用を前提としたJavaライブラリ開発方法の提案
kokihoshihara
PRO
2
140
ステートレスなLLMでステートフルなAI agentを作る - YAPC::Fukuoka 2025
gfx
6
720
バクラクの AI-BPO を支える AI エージェント 〜とそれを支える Bet AI Guild〜
tomoaki25
2
660
Featured
See All Featured
How to Ace a Technical Interview
jacobian
280
24k
A Modern Web Designer's Workflow
chriscoyier
697
190k
Facilitating Awesome Meetings
lara
57
6.6k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Typedesign – Prime Four
hannesfritz
42
2.9k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
54k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.2k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
34
2.3k
The Art of Programming - Codeland 2020
erikaheidi
56
14k
Rails Girls Zürich Keynote
gr2m
95
14k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.1k
Imperfection Machines: The Place of Print at Facebook
scottboms
269
13k
Transcript
͓खݩͷಈըΛAPMPʹ ม͓ͯ͠खܰʹΠϚʔ γϰͳମݧΛ࡞Ζ͏ʂ
None
visionOSͰݟΔ͜ͱ͕Ͱ͖Δಈըͷछྨ
Apple Projected Media Profile
Apple Projected Media Profile 360°ಈըͷΑ͏ͳӨܕϝσΟΞΛѻ͏ͨΊʹISOBMFFͷ֦ு ͱͯ͠࡞ΒΕͨϓϩϑΝΠϧ
ISOBMFF (ISO Base Media File Format) ߏ ISOBMFF File ftyp
(File Type Box) ϑΝΠϧλΠϓɺϒϥϯυɺޓੑใ moov (Movie Box) - ϝλσʔλίϯςφ mvhd (Movie Header) શମͷ࣌ؒใ trak (Track Box) τϥοΫใ mvex (Movie Extends) ϑϥάϝϯτ༻֦ு tkhd ϔομʔ mdia ϝσΟΞ mdhd hdlr minf mdat (Media Data Box) ࣮ࡍͷϝσΟΞσʔλʢԻɾө૾ɾࣈນͳͲʣ ௨ৗϑΝΠϧͷେ෦ΛΊΔ free/skip (Free Space) ະ༻ྖҬ udta (User Data) Ϣʔβʔσʔλ ͦͷଞͷBox ֦ுɾΧελϜBox ɿBox ֊ߏΛ࣋ͪɺ֤Box size (4bytes) + type (4bytes) + data Ͱߏ͞ΕΔ ISOBMFFɺQTFFͱ • ISOBMFFϏσΦΦʔσΟΦͳͲ ͷϚϧνϝσΟΞσʔλΛؚΉϑΝΠ ϧͷҰൠతͳߏ͕ఆٛ͞Εͨࠃࡍ ج४ͷϑΥʔϚοτ • QTFF͕ϕʔε • MP4ISOBMFFʹج͍࣮ͮͨࡍͷ ϑΝΠϧܗࣜ
ISOBMFFͷ֦ுʹͭ ͍ͯ • Apple͕ۭؒϏσΦͷͨΊʹՃ͠ ֦ͨு: vexu(Video Extended Usage)
APMPʹରԠ͢ΔͱͲ͏ͳΔͷ͔ʁ • visonOSͷඪ४ͷࣸਅΞϓϦࣗͷΞϓϦ͔ΒΠϚʔγϒ ͳಈըͷ࠶ੜ͕Ͱ͖ΔΑ͏ʹͳΔ
APMPܗࣜͷಈըͷ࡞Γํ 1. CLIπʔϧʢavconvertʣ·ͨmacOSʢbetaʣͷػೳΛͬ ͯม͢Δ 2. طଘͷҰ෦ͷΧϝϥͰͬͨಈըࣗಈͰมͰ͖Δ 3. APIΛͬͯม͢Δ(Appleͷαϯϓϧίʔυ͋Γ)
1. avconvert
2. ࣗಈม 1. ·ͣInsta360͔Go ProΛߪೖ͠·͠ΐ͏ 2. Insta360 StudioͰMP4ग़ྗ͠·͢ 3. AirDropͰAVPʢvisionOS
26 betaʣૹΔ 4. visionOSͰ֘ϑΝΠϧΛ։͘ࡍʹม͢Δ͔Ͳ͏͔μΠΞ ϩά͕ग़ͯ͘ΔͷͰબͿ
3. APIΛͬͯม͢Δ AppleͷαϯϓϧίʔυProjectedMediaConversionΛ༻͠· ͢ https://developer.apple.com/documentation/AVFoundation/ converting-projected-video-to-apple-projected-media-profile
ॲཧͷྲྀΕ 1. ݩಈըಡΈࠐΈ 2. ύοΩϯάઃఆ 3. ѹॖϓϩύςΟઃఆ 4. Τϯίʔμʔͷઃఆ 5.
ॻ͖ग़͠
ύοΩϯά ཱମࢹʢstereoscopicʣͷ߹ύοΩ ϯά͕ҎԼͷ͍ͣΕ͔Ͱ͋Δ͔Λࢦఆ͠ ·͢ • SideBySide • OverUnder
ύοΩϯά if let viewPackingKind = projectedMediaMetadata.viewPackingKind { isFramePacked = true
if viewPackingKind.caseInsensitiveCompare("SideBySide") == .orderedSame { horizontalScale = 2.0 isSideBySide = true } else if viewPackingKind.caseInsensitiveCompare("OverUnder") == .orderedSame { verticalScale = 2.0 } } let eyeFrameSize = CGSize(width: sourceVideoFrameSize.width / horizontalScale, height: sourceVideoFrameSize.height / verticalScale) ... let cropRectDict = [ kCVImageBufferCleanApertureHorizontalOffsetKey: apertureHorizontalOffset, kCVImageBufferCleanApertureVerticalOffsetKey: apertureVerticalOffset, kCVImageBufferCleanApertureWidthKey: eyeFrameSize.width, kCVImageBufferCleanApertureHeightKey: eyeFrameSize.height ]
ѹॖϓϩύςΟͷઃఆ let MVHEVCVideoLayerIDs = [0, 1] let MVHEVCViewIDs = [0,
1] let MVHEVCLeftAndRightViewIDs = [0, 1] ... let stereoCompressionProperties: [CFString: Any] = [ kVTCompressionPropertyKey_MVHEVCVideoLayerIDs: MVHEVCVideoLayerIDs, kVTCompressionPropertyKey_MVHEVCViewIDs: MVHEVCViewIDs, kVTCompressionPropertyKey_MVHEVCLeftAndRightViewIDs: MVHEVCLeftAndRightViewIDs, kVTCompressionPropertyKey_HasLeftStereoEyeView: true, kVTCompressionPropertyKey_HasRightStereoEyeView: true ]
ѹॖϓϩύςΟͷઃఆʢ2ʣ let projectionKind = projectedMediaMetadata.projectionKind ... compressionProperties[kVTCompressionPropertyKey_ProjectionKind] = kCMFormatDescriptionProjectionKind_HalfEquirectangular ...
let baselineInMicrometers = UInt32(1000.0 * baselineInMillimeters) compressionProperties[kVTCompressionPropertyKey_StereoCameraBaseline] = baselineInMicrometers ... let encodedHorizontalFOV = UInt32(1000.0 * horizontalFOV) compressionProperties[kVTCompressionPropertyKey_HorizontalFieldOfView] = encodedHorizontalFOV
Τϯίʔμʔͷઃఆ let outputSettings: [String: Any] = [ AVVideoCodecKey: AVVideoCodecType.hevc, AVVideoWidthKey:
eyeFrameSize.width, AVVideoHeightKey: eyeFrameSize.height, AVVideoCompressionPropertiesKey: compressionProperties ]
ॻ͖ग़͠ for (layerID, eye) in zip(MVHEVCVideoLayerIDs, eyes) { let pixelBuffer
= try pixelBufferPool.makeMutablePixelBuffer() ... ΓऔΓൣғࢉग़ॲཧʢলུʣ ... CVBufferSetAttachment(imageBuffer, kCVImageBufferCleanApertureKey, cropRectDict as CFDictionary, CVAttachmentMode.shouldPropagate) VTSessionSetProperty(session, key: kVTPixelTransferPropertyKey_ScalingMode, value: kVTScalingMode_CropSourceToCleanAperture) pixelBuffer.withUnsafeBuffer { cvPixelBuffer in guard VTPixelTransferSessionTransferImage(session, from: imageBuffer, to: cvPixelBuffer) == noErr else { fatalError("Error during pixel transfer session for layer \(layerID)") } } // Create and append a tagged buffer for this eye. let tags: [CMTag] = [.videoLayerID(Int64(layerID)), .stereoView(eye)] taggedBuffers.append(.init(tags: tags, content: .pixelBuffer(.init(pixelBuffer)))) }
ॻ͖ग़͠ // Create and append a tagged buffer for this
eye. let tags: [CMTag] = [.videoLayerID(Int64(layerID)), .stereoView(eye)] taggedBuffers.append(.init(tags: tags, content: .pixelBuffer(.init(pixelBuffer))))
ग़དྷ্͕Γʂ
!
ࣗݾհ ۄ৴ޛ iOS DeveloperʢϑϦʔϥϯεʣ ԭೄࡏॅ ίϛϡχςΟ׆ಈ - try! Swift TokyoӡӦ
- ԭೄϞόΠϧΞϓϦ։ൃษڧձӡӦ
Okinawa.swiftͲ͏Ͱ͠ΐ͏ʁ
Ҏ্