ゲーム配信アプリミラティブに実装され多くのユーザーに利用されている、視聴者からのコメントや各種配信情報をアプリ外で表示する「配信コメントバー」機能の開発の裏側と技術の詳細についてご紹介します。
1J1ΛԠ༻ͨ͠৴ίϝϯτόʔػೳͷ։ൃൿͱٕज़ͷৄղגࣜձࣾϛϥςΟϒɹઍ٢ྑل@OBSV@KQO OBSVKQOaϐΫνϟɾΠϯɾϐΫνϟ
View Slide
͜ͷτʔΫͰ͓͢͠Δ͜ͱɾ1J1جຊͷৼΔ͍ͱ࣮ɾϛϥςΟϒͷ৴ͱʮ৴ίϝϯτόʔʯػೳɾ3%Ͱੜ·Εͨʮ৴ίϝϯτόʔʯɾʮ৴ίϝϯτόʔʯ࣮ͷৄղɾඳըίετͱͷઓ͍
1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊৼΔ͍
ΞϓϦ֎Ͱͷಈըίϯςϯπͷ࠶ੜҠಈ1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊৼΔ͍
ը໘ͷʹӅ͢1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊৼΔ͍
ϐϯνɾΠϯϐϯνɾΞτͰαΠζͷมߋ1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊৼΔ͍
Ϣʔβʔૢ࡞ʹΑΔ։࢝ఀࢭ1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊৼΔ͍
όοΫάϥϯυҠߦͰࣗಈදࣔ1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊৼΔ͍
ઃఆˠϐΫνϟΠϯϐΫνϟͰʮࣗಈతʹ։࢝ʯͷมߋ1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊৼΔ͍
1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊ"71JDUVSF*O1JDUVSF$POUSPMMFS
1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊ"71JDUVSF*O1JDUVSF$POUSPMMFS"71JDUVSF*O1JDUVSF$POUSPMMFSɾ1J1ͷ࣮ͷͨΊʹ͏ɾ"71JDUVSF*O1JDUVSF$POUSPMMFSJ04͔Βଘࡏ͍ͯ͠ΔɹɾJ04࣌ͰҰ෦ͷJ1BEͷΈରԠɹɾJ04͔ΒJ1IPOFͰ1J1͕͑ΔΑ͏ʹɾ$POUFOU4PVSDF͔Βੜ͢Δ˞ɹɾinit(contentSource: AVPictureInPictureController.ContentSource)˞ଞͷΠχγϟϥΠβ͋Δ͕DPOWFOJFODFͳͷͰ͜͜Ͱແࢹ͢Δ
1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊ"71JDUVSF*O1JDUVSF$POUSPMMFS"71JDUVSF*O1JDUVSF$POUSPMMFSͷ֓ཁɾ$POUFOU4PVSDFͷछྨɹɾछྨͷϨΠϠʔ͔ΒੜͰ͖Δ˞ɹɾ"71MBZFS-BZFS͔"74BNQMF#VGGFS%JTQMBZ-BZFS˞"71JDUVSF*O1JDUVSF7JEFP$BMM7JFX$POUSPMMFS͔ΒੜͰ͖Δ͕ಛघͳػೳͰ"QQMFͷڐՄ͕ͳ͍ͱ։ൃͰ͖ͳ͍ͷͰແࢹinit(sampleBufferDisplayLayer: AVSampleBufferDisplayLayer,playbackDelegate: AVPictureInPictureSampleBufferPlaybackDelegate)init(playerLayer: AVPlayerLayer) J04J04
"71MBZFS-BZFSΛ࣮ͬͨ
1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊ"71MBZFS-BZFS४උ$BQBCJMJUJFT#BDLHSPVOE.PEFT1JDUVSFJO1JDUVSFΛ༗ޮʹ͢Δ"7"VEJP4FTTJPOͷΧςΰϦΛQMBZCBDL͔QMBZ"OE3FDPSEʹ͢ΔUSZ"7"VEJP4FTTJPOTIBSFE*OTUBODF TFU$BUFHPSZ QMBZCBDL"7'PVOEBUJPO1J11MBZFS1JDUVSFJO1JDUVSF1MBZCBDLXJUI"7,JU
1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊ"71MBZFS-BZFSߏ"71MBZFS-BZFS6*7JFX$POUSPMMFS6*#VUUPO"71JDUVSF*O1JDUVSF$POUSPMMFS
"71JDUVSF*O1JDUVSF$POUSPMMFSͷॳظԽ// AVPictureInPictureController の生成let controller = AVPictureInPictureController(playerLayer: playerLayer)// バックグラウンド移行時に自動でピクチャ・イン・ピクチャを起動するかcontroller.canStartPictureInPictureAutomaticallyFromInline = trueQMBZFS-BZFS1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊ"71MBZFS-BZFS
Ϣʔβʔૢ࡞ʹΑΔ։࢝ఀࢭ/// ピクチャ・イン・ピクチャの開始@IBAction private func didTapStartButton(_ sender: UIButton) {pictureInPictureController.startPictureInPicture()}/// ピクチャ・イン・ピクチャの停止@IBAction private func didTapStopButton(_ sender: UIButton) {pictureInPictureController.stopPictureInPicture()}1J1 ϐΫνϟɾΠϯɾϐΫνϟͷجຊ"71MBZFS-BZFS
OBSVKQOQJQFYFSDJTFFYBNQMFTQJQCBTJD"71MBZFS-BZFSΛͬͨ1J1ͷαϯϓϧ࣮
"74BNQMF#VGGFS%JTQMBZ-BZFSΛͬͨ1J1ͷαϯϓϧ࣮FYBNQMFTQJQDVTUPN FYBNQMFTQJQDBNFSB
ϛϥςΟϒͷ৴ͱʮ৴ίϝϯτόʔʯػೳ
ϛϥςΟϒͷʮ৴ίϝϯτόʔʯػೳϛϥςΟϒͰͷ৴தͷίϝϯτͱϓογϡ௨ɾϛϥςΟϒ৴ΞϓϦͳͷͰࢹௌऀ͕͍ΔɹɾࢹௌऀςΩετͰίϝϯτΛ͢Δɹɾ৴ऀجຊతʹϛϥςΟϒΞϓϦΛ։͍͍ͯͳ͍ɹɹɾήʔϜͳͲଞͷΞϓϦΛ։͍ͯ৴͍ͯ͠Δɹɾ৴ऀʹࢹௌऀ͔ΒͷίϝϯτΛಧ͚Δඞཁ͕͋ΔɾJ04Ͱैདྷ͔Βϓογϡ௨ͰϢʔβʔʹίϝϯτΛಧ͚͍ͯΔ
ϓογϡ௨ʹΑΔ৴ͷίϝϯτදࣔʢ৴ίϝϯτόʔະ༻ʣ
ϛϥςΟϒͷʮ৴ίϝϯτόʔʯػೳ৴ίϝϯτόʔɾ৴ίϝϯτόʔϓογϡ௨ʹมΘͬͯίϝϯτΛಧ͚Δɹɾ1J1Λ׆༻ͯ͠ಠࣗͷ6*ͰίϝϯτΛಧ͚ΔɹɾৼΔ͍ී௨ͷ1J1ͱಉ͡ɹɾ1J1͕දࣔ͞Ε͍ͯΔؒɺϓογϡ௨දࣔ͞Εͳ͍
৴ίϝϯτόʔʹΑΔ৴ͷίϝϯτදࣔ
ϛϥςΟϒͷʮ৴ίϝϯτόʔʯػೳίϝϯτΪϑτͷ༰ ࠷େߦ ৴ͷܦա࣌ؒࢹௌऀଃΒΕͨΪϑτͷ৴ԻͷPOPGG৴ऀͷΤϞϞ Ξόλʔ৴ऀͷΤϞϞͷഎܠը૾৴ίϝϯτόʔͷཁૉ
3%Ͱੜ·Εͨʮ৴ίϝϯτόʔʯ
3%Ͱੜ·Εͨʮ৴ίϝϯτόʔʯ໌ࣔతʹ3%ͱͯ͠։ൃΛਐΊͨɾੈͷதʹ·ͩͳ͍࣮͕ͩͬͨԿਓ͔ՄೳੑΛײ͍ͯͨ͡ɾԿ͕Ͳ͜·Ͱ࣮Ͱ͖Δ͔͔Βͳ͔ͬͨɾΤϯδχΞ͕ٕज़ͷݕূΛ͠ͳ͕Βओମతʹ։ൃΛਐΊ͍ͯ͘ඞཁ͕͋ͬͨɾࣦഊͯ͠ؾʹ͠ͳ͍
3%։ൃ͔Βੜ·Εͨʮ৴ίϝϯτόʔʯ৴ίϝϯτόʔػೳͷϦϦʔε·ͰɹJ04ͷσϕϩούʔϕʔλ൛ϦϦʔεɹࣾͰϓϩτλΠϓ͕࡞ΒΕͨɹJ04ϦϦʔεɹ3%։࢝ɹJ04ϦϦʔεɹ৴ίϝϯτόʔػೳϦϦʔε
ࣾͰϓϩτλΠϓ͕࡞ΒΕͨɾ"71JDUVSF*O1JDUVSF$POUSPMMFSͱ"74BNQMF#VGGFS%JTQMBZ-BZFSΛΈ߹ΘͤͨϓϩτλΠϓ͕ͻͬͦΓͱ࡞ΒΕͨɾςΩετΛදࣔ͢Δ͚ͩͷγϯϓϧͳ༰͕ͩɺՄೳੑΛײ͡Δʹेͳ࣮ͩͬͨ3%Ͱੜ·Εͨʮ৴ίϝϯτόʔʯ
3%։࢝ɾ6*,JUతͳݟͨΛ࠶ݱͰ͖Δͷ͔ɹɾϝϯςφϯεੑͷ͋Δ࣮͕Ͱ͖Δ͔ɾ3FQMBZ,JU8FC35$Λ༻͍ͯ͠ΔϛϥςΟϒʹΈࠐΊΔ͔ɾٕज़తʹԿ͕Ͱ͖ΔͰ͖ͳ͍Λ1.σβΠφʔʹ͑Δɾࢼ࡞Λ࡞ٕͬͯज़తͳݶքΛ୳͍ͬͯͨ3%Ͱੜ·Εͨʮ৴ίϝϯτόʔʯ
ϏοτϚοϓͷՃ'143%Ͱੜ·Εͨʮ৴ίϝϯτόʔʯ6*,JUͷ࠶ݱࢼ࡞Λ࡞Γͳ͕Βٕज़తͳΛ୳͍ͬͯͨ
J04ϦϦʔεɾ৴தʹϓογϡ௨͕දࣔ͞Εͳ͘ͳΔͱ͍͏ࣄ͕݅ى͖ͨɾ৴தͷϓογϡ௨Λ੍ݶ͢Δػೳ͕Ճ͞Ε͍ͯͨɹɾઃఆΛมߋ͠ͳ͍ͱࢹௌऀͷίϝϯτ͕ಧ͔ͳ͘ͳͬͯ͠·͏ 3%Ͱੜ·Εͨʮ৴ίϝϯτόʔʯσϑΥϧτΦϑ
J04ϦϦʔεɾ1J1ͷίϝϯτදࣔػೳͳΒઃఆؔͳ͑͘Δɾ͜Ε͕͍෩ʹͳΓਖ਼ࣜʹεέδϡʔϧ͕ΒΕͨ 3%Ͱੜ·Εͨʮ৴ίϝϯτόʔʯσϑΥϧτΦϑ
3%Ͱ͑ΒΕͨϦϦʔε·ͰͷಓͷΓɾॳظͷϓϩτλΠϓ͕ଘࡏͨ͠ɾՄೳੑΛ୳ΔͨΊͷ3%Λձ͕ࣾਪਐͨ͠ɹɾ1.σβΠφʔ͔ͳΓڠྗతͩͬͨɾJ04ͷࣄނ͕ޙԡ͠ʹͳͬͨ 3%Ͱੜ·Εͨʮ৴ίϝϯτόʔʯ
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ1J1ͱಈըͷؔ1J1ΞϓϦʹஔ͞ΕͨϨΠϠʔ্Ͱ࠶ੜ͞Ε͍ͯΔಈըΛΞϓϦͷྖҬ֎Ͱ࠶ੜ͢ΔΈຊདྷಈը͕࠶ੜ͞Ε͍ͯΔϨΠϠʔ
ʮ৴ίϝϯτόʔʯ࣮ͷৄղಈը͕࠶ੜ͞Ε͍ͯΔྖҬͭ·Γ1J1͕࠶ੜ͞Ε͍ͯΔظؒɺΞϓϦͷͲ͔͜Ͱಈը͕࠶ੜ͞Ε͍ͯΔ1J1ͱಈըͷؔ
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ৴ίϝϯτόʔͰͲ͏ͳ͍ͬͯΔ͔Ͳ͔͜Βͱͳ͘৴ίϝϯτόʔ͕χϣΩοͱग़͖͍ͯͯΔΑ͏ʹݟ͑Δ
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ৴ίϝϯτόʔͰͲ͏ͳ͍ͬͯΔ͔εϩʔө૾
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ৴ίϝϯτόʔͰͲ͏ͳ͍ͬͯΔ͔͜ͷਧ͖ग़͠ͷը૾ͷཪଆʹΊͪΌͪ͘Όখ͍͞"74BNQMF#VGGFS%JTQMBZ-BZFSΛஔ͍ͯಈըΛ࠶ੜ͍ͯ͠Δ
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ"74BNQMF#VGGFS%JTQMBZ-BZFS্ͰಈըΛ࠶ੜ͢Δɾ"74BNQMF#VGGFS%JTQMBZ-BZFS$.4BNQMF#VGGFSΛड͚औͬͯ༰Λඳը͢Δɹɾ৴ίϝϯτόʔͰɺ$.4BNQMF#VGGFSಈըͷ֤ϑϨʔϜʹରԠ͢Δը૾σʔλΛอ͍࣋ͯ͠Δ˞˞$.4BNQMF#VGGFSಈըͷϑϨʔϜ͚ͩͰͳ͘ɺྫ͑ෳνϟωϧͷԻσʔλͳͲදݱͰ͖Δfunc enqueue(_ sampleBuffer: CMSampleBuffer)
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ1J1ͷඳըʹ༻͍Δ$.4BNQMF#VGGFSͷߏ$.4BNQMF#VGGFSɾ$.4BNQMF#VGGFS෦ʹ$71JYFM#VGGFSΛอ࣋͢Δɹɾ$71JYFM#VGGFSҰຕͷը૾ʹ૬͢ΔσʔλΛ࣋ͭɾ͜ΕΛ"74BNQMF#VGGFS%JTQMBZ-BZFSʹͯ͠දࣔ͢Δ$71JYFM#VGGFSY˞࣮ࡍʹ6*4DSFFONBJOTDBMFഒͷେ͖͞˞"3(#
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ৴ίϝϯτόʔͰ6*,JUͷੈքΛ࠶ݱ͍ͨ͠ɾຊಈը͕ͩ6*,JUͰݟͨΛ࡞͍ͬͯΔΑ͏ʹݟ͍ͤͨɾ৬ਓٕ͗ͯ͢୭ϝϯςφϯεͰ͖ͳ͍ΈͩͱࠔΔɾ"VUP-BZPVU׆༻͍ͨ͠ˠ"VUP-BZPVUͰஔ͞Εͨ6*7JFXͷඳը༰Λ$71JYFM#VGGFSʹॻ͖ࠐΈ͍ͨ
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ6*7JFXͷඳը༰Λ$71JYFM#VGGFSʹॻ͖ࠐΉlet renderer = UIGraphicsImageRenderer(size: size)// UIView → UIImage の変換let uiImage = renderer.image { context inview.layer.render(in: context.cgContext)}// UIImage → CGImage → CIImage → CVPixelBuffer の変換if let cgImage = uiImage.cgImage {let ciImage = CIImage(cgImage: cgImage)CIContext(options: nil).render(ciImage, to: pixelBuffer)}ྫ
ʮ৴ίϝϯτόʔʯ࣮ͷৄղඳը༰ͷߋ৽සɾ৴࣌ؒ))NNTTܗࣜͳͷͰඵʹճߋ৽͢Εेɾίϝϯτ͋·ΓߴසͰߋ৽ͯ͠ಡΊͳ͍ˠඳը༰ͷߋ৽ස '14ͱͨ͠
ʮ৴ίϝϯτόʔʯ࣮ͷৄղඞཁͳॲཧΛ·ͱΊΔͱ ҰఆִؒͰඳը༻ϧʔϓΛΒͤΔ ಈըͷϑϨʔϜʹରԠ͢Δ$.4BNQMF#VGGFSΛੜ͢Δ $.4BNQMF#VGGFSΛϨϯμϥʔʹ͢ ඳըॲཧΛߦ͏ Ճͨ͠$.4BNQMF#VGGFSΛ"74BNQMF#VGGFS%JTQMBZ-BZFSʹ͢
ʮ৴ίϝϯτόʔʯ࣮ͷৄղ"74BNQMF#VGGFS%JTQMBZ-BZFS$POUSPMMFS-PPQFS 4BNQMF#VGGFS'BDUPSZ3FOEFSFSϧʔϓॲཧ '14ͷ੍ޚ $.4BNQMF#VGGFSͷੜ$71JYFM#VGGFSͷՃ1J1ͷ੍ޚ ։࢝ऴྃ FYBNQMFTQJQDVTUPN͜ͷߏͰ࣮͞Ε͍ͯ·͢
ඳըίετͱͷઓ͍
ඳըίετͱͷઓ͍ඳըίετ͕େ͖͗͢Δɾࢼ࡞ஈ֊͔Βඳըίετʹ͕͋Δ͜ͱ͔͍ͬͯͨɾϛϥςΟϒ6OJUZͰΤϞϞΛඳը͍ͯ͠ΔɾϝΠϯεϨουͷେ͖͍ෛՙ6OJUZͷඳըʹӨڹΛ༩͑Δɾը໘ͷେ͖͍6OJUZͷඳըίετେ͖͘ɺϝΠϯεϨουͷෛՙͷӨڹΛड͚͍͢
ඳըίετͱͷઓ͍1J1ͷෛՙͰΤϞϞͷඳըʹࢧো͕ग़͍ͯΔ༷ࢠ
$*$POUFYUͷੜҰ͚ͩ$71JYFM#VGGFS1PPMͷར༻ޮͷΑ͍ඳըํ๏ඳըͷճΛݮΒ͢$71JYFM#VGGFSͷՃΛ͢Δඳըίετͱͷઓ͍ඳըίετͷվળʹ༗ޮͩͬͨରࡦ
FYBNQMFTQJQQFSGPSNBODF1J1ʹؔ࿈͢Δॲཧͷฏۉॲཧ͕࣌ؒܭଌͰ͖·͢
ඳըίετͱͷઓ͍$*$POUFYU$*$POUFYUͷੜҰ͚ͩlet renderer = UIGraphicsImageRenderer(size: size)let uiImage = renderer.image { context inview.layer.render(in: context.cgContext)}if let cgImage = uiImage.cgImage {let ciImage = CIImage(cgImage: cgImage)CIContext(options: nil).render(ciImage, to: pixelBuffer)}
ඳըίετͱͷઓ͍$*$POUFYU$*$POUFYUͷੜҰ͚ͩɾ$*$POUFYUͷੜ͍ɾJ1IPOFNJOJͰ͓Α͔͔ͦΔɾ$PSF*NBHF1SPHSBNNJOH(VJEFʹॻ͍ͯ͋Δɾ࡞ͨ͠ΠϯελϯεΛอ͓࣋ͯ͜͠͏Core Image Programming Guide - Getting the Best Performance
ඳըίετͱͷઓ͍$*$POUFYU$*$POUFYUͷੜҰ͚ͩlazy var context = CIContext(options: nil)// …let renderer = UIGraphicsImageRenderer(size: size)let uiImage = renderer.image { context inview.layer.render(in: context.cgContext)}if let cgImage = uiImage.cgImage {let ciImage = CIImage(cgImage: cgImage)context.render(ciImage, to: pixelBuffer)}
ඳըίετͱͷઓ͍$71JYFM#VGGFS1PPM$71JYFM#VGGFS1PPMͷར༻ɾಈըͷϑϨʔϜʹରԠ͢Δ$.4BNQMF#VGGFSΛੜ͢ΔࡍʹࡐྉͱͳΔ$71JYFM#VGGFS$.4BNQMF#VGGFS$71JYFM#VGGFSY"3(#
ඳըίετͱͷઓ͍$71JYFM#VGGFS1PPM$71JYFM#VGGFS1PPMͷར༻ɾ$71JYFM#VGGFSͷੜ·͋·͍͋ɾd͘Β͍͔͔ΔCVPixelBufferCreate(kCFAllocatorDefault,Int(size.width),Int(size.height),kCVPixelFormatType_32ARBG,attributes,&pixelBufferOut)
ඳըίετͱͷઓ͍$71JYFM#VGGFS1PPM$71JYFM#VGGFS1PPMͷར༻ɾ$71JYFM#VGGFSͷ࠶ར༻ͷͨΊͷ$71JYFM#VGGFS1PPMɹɾ6*$PMMFDUJPO7JFXͱ$FMMͷؔੑɾ$71JYFM#VGGFSͷऔಘ͕ఔͰࡁΉCVPixelBufferPoolCreate(kCFAllocatorDefault, nil, attributes, &pixelBufferPool)CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault,pixelBufferPool,&pixelBufferOut)
FYBNQMFTQJQDVTUPN4BNQMF#VGGFS'BDUPSZTXJGUʹ$71JYFM#VGGFS1PPMΛར༻࣮͕ͨ͋͠Γ·͢
ඳըίετͱͷઓ͍ޮͷΑ͍ඳըํ๏ޮͷΑ͍ඳըํ๏ɾ6*7JFX͔Β6**NBHFͷੜΛ͢Δํ๏͍͔ͭ͋͘ΔɾESBX)JFSBSDIZํࣜͱMBZFSSFOEFSํࣜͰൺֱͯ͠ΈΔ
ESBX)JFSBSDIZํࣜɾESBX)JFSBSDIZΛͬͯ6**NBHFΛੜɾ$71JYFM#VGGFSʹॻ͖ࠐΉ·Ͱd͔͔ΔUIGraphicsBeginImageContextWithOptions(renderingSize, false, 0.0)// 描画view.drawHierarchy(in: frame, afterScreenUpdates: true)let image = UIGraphicsGetImageFromCurrentImageContext()UIGraphicsEndImageContext()if let cgImage = image?.cgImage {renderContext.render(CIImage(cgImage: cgImage), to: pixelBuffer)}ඳըίετͱͷઓ͍ޮͷΑ͍ඳըํ๏
MBZFSSFOEFSํࣜɾMBZFSSFOEFSΛͬͯ6**NBHFΛੜɾ$71JYFM#VGGFSʹॻ͖ࠐΉ·Ͱd͔͔Δlet imageRenderer = UIGraphicsImageRenderer(size: renderingSize)let image = imageRenderer.image { context in// 描画view.layer.render(in: context.cgContext)}if let cgImage = image.cgImage {renderContext.render(CIImage(cgImage: cgImage), to: pixelBuffer)}ඳըίετͱͷઓ͍ޮͷΑ͍ඳըํ๏
ඳըίετͱͷઓ͍ޮͷΑ͍ඳըํ๏ޮͷΑ͍ඳըํ๏ɾESBX)JFSBSDIZํࣜΑΓMBZFSSFOEFSํࣜͷํ͕΄Ͳૣ͍ɾESBX)JFSBSDIZ6*7JFXͷΞχϝʔγϣϯʹैͯ͠ඳըͰ͖ΔͳͲͷಛੑ͕͋Δ͕ɺ͍ɹɾΞχϝʔγϣϯͤ͞ͳ͍ͷͰMBZFSSFOEFSΛ࠾༻
ඳըίετͱͷઓ͍ඳըͷճ·ͩ·ͩඳըͷίετ͕ߴ͍ɾϏϡʔͷඳըॲཧ·ͩக໋తʹίετ͕ߴ͍ɾۙ͘ॲཧ͕͔͔࣌ؒͬͯ͠·͏// 以下のコードで配信コメントバーの全てのビューを// 再起的に描画しようとすると 30[ms] 近くかかってしまうlet uiImage = renderer.image { context inview.layer.render(in: context.cgContext)}
ඳըίετͱͷઓ͍ඳըͷճͲ͜ʹίετ͕͔͔͍ͬͯΔ͔ɾը૾ 6**NBHF7JFXΛඳըͨ͠ͱ͖ͷίετ͕ߴ͍ɹɾը૾ʹෆಁ໌ϚεΫ͔͚͍ͯͨΓ͢Δɾը૾Λ͋Β͔͡Ίॖখͯ͠༻ͯ͠ेͳޮՌͳ͍let uiImage = renderer.image { context in// view の階層が深くなればなるほど時間がかかるview.layer.render(in: context.cgContext)}
ඳըίετͱͷઓ͍ඳըͷճSFOEFS JODPOUFYUΛݺͿճΛݮΒͯ͠Έͨɾ͍ΘΏΔυϩʔίʔϧΛݮΒ͢ɾߋ৽ස͕͍ཁૉɺߴ͍ཁૉΛ6*7JFXͷUBHͰྨ͢Δɾߋ৽ස͕͍ཁૉ·ͱΊͯҰͭͷը૾ʹ͓ͯ͘͠ߋ৽ස͕͍ཁૉ සൟʹߋ৽͞ΕΔཁૉ
ߋ৽ස͕͍ཁૉͷΈ͔Βੜͨ͠ը૾ඞཁͳ͚࣌ͩߋ৽ͯ͠ɺΩϟογϡ͓ͯ͘͠ɾΞόλʔ͕ߋ৽͞Εͨͱ͖ɾഎܠը૾͕ߋ৽͞Εͨͱ͖ɾԻͷ0/0''ͷΓସ͕͑͞Εͨͱ͖ߋ৽ස͕ߴ͍ϏϡʔͷBMQIBΛʹͯ͠6*7JFX͔Β6**NBHFΛੜ͢Δඳըίετͱͷઓ͍ඳըͷճ
ߋ৽ස͕ߴ͍ཁૉΩϟογϡͨ͠ը૾ͷ্ʹελϯϓܗࣜʢΠϝʔδʣͰඳը͍ͯ͘͠ඳըίετͱͷઓ͍ඳըͷճfunc renderRedrawnContents(on view: UIView, in context: CGContext) {if view.tag == DrawingPolicy.redrawn.tag {view.layer.draw(in: context)}for subview in view.subviews {let origin = subview.frame.origincontext.translateBy(x: origin.x, y: origin.y)renderRedrawnContents(on: subview, in: context)context.translateBy(x: -origin.x, y: -origin.y)}}ݸผʹඳը
Ұຕֆͷߋ৽ͱίϝϯτόʔͷඳըͷྲྀΕͷ֓೦ਤ// エモモや背景などが更新された時だけ一枚絵を再生成するif needsToUpdateStableContents {updateStableContentsImage()}let uiImage = renderer.image { context in// 更新頻度の低い要素をまとめた画像の描画renderStableContents(in: context.cgContext)// 更新頻度の高い要素は個別に描画するrenderRedrawnContents(on: view, in: context.cgContext)}ඳըίετͱͷઓ͍ඳըͷճ
ඳըͷճΛݮΒ͢ޮՌɾඳըʹ͔͔Δ͕࣌ؒd͔Βdʹվળ͞Εͨɾ͜ͷվળʹΑͬͯը໘ͷνϥ͖ͭશͰͳ͘ͳͬͨɾ࣮͜ͷ࣮2"ͷ࠷ऴͰͨ͠ʢʂʣ.JSSBUJW5FDI#MPH৴ίϝϯτόʔʙ1J1ඳըύϑΥʔϚϯεͱͷ͖߹͍ํඳըίετͱͷઓ͍ඳըͷճ
ඳըίετͱͷઓ͍$71JYFM#VGGFSͷՃΛ͢Δ͞ΒʹඳըॲཧͷશମΛݟͯ͠ΈΔɾ7JFXͷ༰͕$71JYFM#VGGFSʹඳը͞ΕΔ·Ͱͷܦ࿏ɾͲ͏͍ͬͨத͕ؒੜ͞Ε͍ͯΔ͔let imageRenderer = UIGraphicsImageRenderer(size: renderingSize)let image = imageRenderer.image { context in// 描画view.layer.render(in: context.cgContext)}if let cgImage = image.cgImage {renderContext.render(CIImage(cgImage: cgImage), to: pixelBuffer)}
let imageRenderer = UIGraphicsImageRenderer(size: renderingSize)let image = imageRenderer.image { context in// 描画view.layer.render(in: context.cgContext)}if let cgImage = image.cgImage {renderContext.render(CIImage(cgImage: cgImage), to: pixelBuffer)}ඳըίετͱͷઓ͍$71JYFM#VGGFSͷՃΛ͢Δ6*7JFXˠ$($POUFYUˠ6**NBHFˠ$**NBHFˠ$71JYFM#VGGFS
ඳըίετͱͷઓ͍$71JYFM#VGGFSͷՃΛ͢Δ$.4BNQMF#VGGFS$71JYFM#VGGFSY"3(#6*7JFX $($POUFYU 6**NBHF $**NBHFඳը ඳը
ඳըίετͱͷઓ͍$71JYFM#VGGFSͷՃΛ͢Δ$71JYFM#VGGFSͱ$($POUFYUͷؔɾ$71JYFM#VGGFS"3(#ܗࣜͷσʔλΛอ͍࣋ͯ͠Δɾ$($POUFYU"3(#ܗࣜͷσʔλΛࢀরͯ͠ੜ͢Δ͜ͱ͕Ͱ͖Δ
ඳըίετͱͷઓ͍$71JYFM#VGGFSͷՃΛ͢ΔCVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)// CVPixelBuffer が保持しているデータのアドレスを取得するlet data = CVPixelBufferGetBaseAddress(pixelBuffer)CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)// CVPixelBuffer が保持しているデータを参照した CGContext を生成するguard let context = CGContext(data: data,width: width,height: height,bitsPerComponent: 8,bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),space: CGColorSpaceCreateDeviceRGB(),bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue // ARGB) else {return}
ඳըίετͱͷઓ͍$71JYFM#VGGFSͷՃΛ͢Δ$.4BNQMF#VGGFS$71JYFM#VGGFSY"3(#$($POUFYU
ඳըίετͱͷઓ͍$71JYFM#VGGFSͷՃΛ͢Δ$.4BNQMF#VGGFS$71JYFM#VGGFSY"3(#6*7JFX $($POUFYUඳը
ඳըίετͱͷઓ͍$71JYFM#VGGFSͷՃΛ͢Δ$.4BNQMF#VGGFS$71JYFM#VGGFSY"3(#6*7JFX $($POUFYUඳը6*7JFX $($POUFYU 6**NBHF $**NBHFඳը ඳըϝϞϦͷ֬อ్தͷॲཧΛݮΒ͢͜ͱͰɺdͷඳըίετ͕ݮͰ͖Δ
ඳըίετͱͷઓ͍ඳըίετͱͷઓ͍ͷ݁Ռɾ࠷ѱͷঢ়ଶͰϑϨʔϜ͋ͨΓఔͷඳըίετ͕͔͔͕ͬͨɺdఔʹվળͨ͠ɾը໘ͷେ͖͍Ͱ6OJUZͷඳըͳͲʹࢧোͳ͘ɺগͳ͍ෛՙͰ৴ίϝϯτόʔͷػೳ͕࣮ݱͰ͖ͨ
·ͱΊɾ৴ίϝϯτόʔ3%ʹΑͬͯੜ·Εֵͨ৽తͳػೳɹɾ1J1্ʹ6*,JUతͳݟͨΛ࠶ݱͰ͖ͨɹɹɾ"VUP-BZPVUͰஔ͞ΕɺϝϯςφϯεੑΛ୲อͨ͠ɹɾෛՙඞཁ࠷ݶʹ͑ΒΕͨɾ3%Λͨ͠Ձ͕͋ͬͨɹɾϓϨογϟʔେ͖͔͕ͬͨɺಘΒΕΔܦݧଟ͔ͬͨɹɾ৽͍͠ػೳ͕ੜ·Εɺٕज़ൃ৴ʹܨ͕ͬͨ
͋Γ͕ͱ͏͍͟͝·ͨ͠