Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
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
31
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
0
230
Firebase Studioで始めるモバイルアプリ開発入門
tamaki
0
24
Introduction to Claude Code Action
tamaki
0
730
AIエージェントを使ったiOSアプリ開発を試してみた
tamaki
0
140
沖縄モバイルアプリ開発勉強会#1
tamaki
0
130
iOSアプリ開発を始めよう
tamaki
0
210
詳解xcresult.pdf
tamaki
0
400
メルペイでのリグレッションテスト自動化推進のこれまでとこれから
tamaki
0
790
What do you want to test with UI Test v2
tamaki
2
920
Other Decks in Technology
See All in Technology
会社紹介資料 / Sansan Company Profile
sansan33
PRO
11
390k
オープンデータの内製化から分かったGISデータを巡る行政の課題
naokim84
2
1.4k
Introduction to Sansan, inc / Sansan Global Development Center, Inc.
sansan33
PRO
0
2.9k
Playwrightのソースコードに見る、自動テストを自動で書く技術
yusukeiwaki
8
2.7k
“決まらない”NSM設計への処方箋 〜ビットキーにおける現実的な指標デザイン事例〜 / A Prescription for "Stuck" NSM Design: Bitkey’s Practical Case Study
bitkey
PRO
1
360
Eight Engineering Unit 紹介資料
sansan33
PRO
0
5.7k
【5分でわかる】セーフィー エンジニア向け会社紹介
safie_recruit
0
37k
Uncertainty in the LLM era - Science, more than scale
gaelvaroquaux
0
560
生成AI時代の自動E2Eテスト運用とPlaywright実践知_引持力哉
legalontechnologies
PRO
0
110
プラットフォームエンジニアリングとは何であり、なぜプラットフォームエンジニアリングなのか
doublemarket
1
550
命名から始めるSpec Driven
kuruwic
3
840
その設計、 本当に価値を生んでますか?
shimomura
3
190
Featured
See All Featured
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Leading Effective Engineering Teams in the AI Era
addyosmani
8
1.3k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
jQuery: Nuts, Bolts and Bling
dougneiner
65
8.1k
The Language of Interfaces
destraynor
162
25k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.5k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.6k
GraphQLとの向き合い方2022年版
quramy
50
14k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
231
22k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
36
6.2k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.8k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.3k
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Ͳ͏Ͱ͠ΐ͏ʁ
Ҏ্