Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PiPを応用した配信コメントバー機能の開発秘話と技術の詳解 / pip_streaming_comment_bar

naru-jpn
September 10, 2022

PiPを応用した配信コメントバー機能の開発秘話と技術の詳解 / pip_streaming_comment_bar

ゲーム配信アプリミラティブに実装され多くのユーザーに利用されている、視聴者からのコメントや各種配信情報をアプリ外で表示する「配信コメントバー」機能の開発の裏側と技術の詳細についてご紹介します。

naru-jpn

September 10, 2022
Tweet

More Decks by naru-jpn

Other Decks in Programming

Transcript

  1. "71JDUVSF*O1JDUVSF$POUSPMMFSͷॳظԽ // AVPictureInPictureController の生成 let controller = AVPictureInPictureController(playerLayer: playerLayer) //

    バックグラウンド移行時に自動でピクチャ・イン・ピクチャを起動するか controller.canStartPictureInPictureAutomaticallyFromInline = true QMBZFS-BZFS 1J1 ϐΫνϟɾΠϯɾϐΫνϟ ͷجຊ"71MBZFS-BZFS
  2. Ϣʔβʔૢ࡞ʹΑΔ։࢝ఀࢭ /// ピクチャ・イン・ピクチャの開始 @IBAction private func didTapStartButton(_ sender: UIButton) {

    pictureInPictureController.startPictureInPicture() } /// ピクチャ・イン・ピクチャの停止 @IBAction private func didTapStopButton(_ sender: UIButton) { pictureInPictureController.stopPictureInPicture() } 1J1 ϐΫνϟɾΠϯɾϐΫνϟ ͷجຊ"71MBZFS-BZFS
  3. ʮ഑৴ίϝϯτόʔʯ࣮૷ͷৄղ 6*7JFXͷඳը಺༰Λ$71JYFM#VGGFSʹॻ͖ࠐΉ let renderer = UIGraphicsImageRenderer(size: size) // UIView →

    UIImage の変換 let uiImage = renderer.image { context in view.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) } ྫ
  4. ʮ഑৴ίϝϯτόʔʯ࣮૷ͷৄղ "74BNQMF#VGGFS%JTQMBZ-BZFS $POUSPMMFS -PPQFS 4BNQMF#VGGFS'BDUPSZ 3FOEFSFS ϧʔϓॲཧ '14 ͷ੍ޚ $.4BNQMF#VGGFSͷੜ੒

    $71JYFM#VGGFSͷՃ޻ 1J1ͷ੍ޚ ։࢝ऴྃ      FYBNQMFTQJQDVTUPN͸͜ͷߏ੒Ͱ࣮૷͞Ε͍ͯ·͢
  5. ඳըίετͱͷઓ͍$*$POUFYU $*$POUFYUͷੜ੒͸Ұ౓͚ͩ let renderer = UIGraphicsImageRenderer(size: size) let uiImage =

    renderer.image { context in view.layer.render(in: context.cgContext) } if let cgImage = uiImage.cgImage { let ciImage = CIImage(cgImage: cgImage) CIContext(options: nil).render(ciImage, to: pixelBuffer) }
  6. ඳըίετͱͷઓ͍$*$POUFYU $*$POUFYUͷੜ੒͸Ұ౓͚ͩ lazy var context = CIContext(options: nil) // …

    let renderer = UIGraphicsImageRenderer(size: size) let uiImage = renderer.image { context in view.layer.render(in: context.cgContext) } if let cgImage = uiImage.cgImage { let ciImage = CIImage(cgImage: cgImage) context.render(ciImage, to: pixelBuffer) }
  7. ESBX)JFSBSDIZํࣜ ɾESBX)JFSBSDIZΛ࢖ͬͯ6**NBHFΛੜ੒ ɾ$71JYFM#VGGFSʹॻ͖ࠐΉ·Ͱd<NT>͔͔Δ 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) } ඳըίετͱͷઓ͍ޮ཰ͷΑ͍ඳըํ๏
  8. MBZFSSFOEFSํࣜ ɾMBZFSSFOEFSΛ࢖ͬͯ6**NBHFΛੜ੒ ɾ$71JYFM#VGGFSʹॻ͖ࠐΉ·Ͱd<NT>͔͔Δ 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) } ඳըίετͱͷઓ͍ޮ཰ͷΑ͍ඳըํ๏
  9. ߋ৽ස౓͕ߴ͍ཁૉ͸Ωϟογϡͨ͠ը૾ͷ্ʹ ελϯϓܗࣜʢΠϝʔδʣͰඳը͍ͯ͘͠ ඳըίετͱͷઓ͍ඳըͷճ਺ 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.origin context.translateBy(x: origin.x, y: origin.y) renderRedrawnContents(on: subview, in: context) context.translateBy(x: -origin.x, y: -origin.y) } } ݸผʹඳը
  10. Ұຕֆͷߋ৽ͱίϝϯτόʔͷඳըͷྲྀΕͷ֓೦ਤ // エモモや背景などが更新された時だけ一枚絵を再生成する if needsToUpdateStableContents { updateStableContentsImage() } let uiImage

    = renderer.image { context in // 更新頻度の低い要素をまとめた画像の描画 renderStableContents(in: context.cgContext) // 更新頻度の高い要素は個別に描画する renderRedrawnContents(on: view, in: context.cgContext) } ඳըίετͱͷઓ͍ඳըͷճ਺
  11. ඳըίετͱͷઓ͍$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) }
  12. 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
  13. ඳըίετͱͷઓ͍$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 }
  14. ඳըίετͱͷઓ͍$71JYFM#VGGFSͷՃ޻Λ޻෉͢Δ $.4BNQMF#VGGFS $71JYFM#VGGFS Y "3(# 6*7JFX $($POUFYU ඳը 6*7JFX $($POUFYU

    6**NBHF $**NBHF ඳը ඳը ϝϞϦͷ֬อ΍్தͷॲཧΛݮΒ͢͜ͱͰɺd<NT>ͷ ඳըίετ͕࡟ݮͰ͖Δ