Skip to content

Instantly share code, notes, and snippets.

@tsuzukihashi
Last active November 27, 2023 00:13
Show Gist options
  • Save tsuzukihashi/97e379a42e32cc0647aa7a4770d2d9a6 to your computer and use it in GitHub Desktop.
Save tsuzukihashi/97e379a42e32cc0647aa7a4770d2d9a6 to your computer and use it in GitHub Desktop.
UIView to CMSampleBuffer (UIViewをCMSampleBufferに変換するExtension)
import UIKit
import CoreMedia
extension UIView {
func toCMSampleBuffer() -> CMSampleBuffer? {
let scale: CGFloat = UIScreen.main.scale
let size: CGSize = .init(width: bounds.width * scale, height: bounds.height * scale)
guard let pixelBuffer = makeCVPicelBuffer(scale: scale, size: size) else { return nil }
defer {
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
}
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
guard let context = makeCGContext(scale: scale, size: size, pixelBuffer: pixelBuffer) else { return nil }
layer.render(in: context)
guard let formatDescription = makeCMFormatDescription(pixelBuffer: pixelBuffer) else { return nil }
do {
return try CMSampleBuffer(
imageBuffer: pixelBuffer,
formatDescription: formatDescription,
sampleTiming: getCMSampleTimingInfo()
)
} catch {
assertionFailure("Failed to create CMSampleBuffer: \(error)")
return nil
}
}
private func makeCVPicelBuffer(scale: CGFloat, size: CGSize) -> CVPixelBuffer? {
var pixelBuffer: CVPixelBuffer?
let createPixelBufferResult: CVReturn = CVPixelBufferCreate(
kCFAllocatorDefault,
Int(size.width),
Int(size.height),
kCVPixelFormatType_32ARGB,
[
kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue!,
kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue!,
kCVPixelBufferIOSurfacePropertiesKey: [:] as CFDictionary,
] as CFDictionary,
&pixelBuffer
)
if createPixelBufferResult != kCVReturnSuccess {
assertionFailure("Failed to create CVPixelBuffer: \(createPixelBufferResult)")
return nil
}
return pixelBuffer
}
private func makeCGContext(scale: CGFloat, size: CGSize, pixelBuffer: CVPixelBuffer) -> CGContext? {
guard let context: CGContext = .init(
data: CVPixelBufferGetBaseAddress(pixelBuffer),
width: Int(size.width),
height: Int(size.height),
bitsPerComponent: 8,
bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
) else { return nil }
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: scale, y: -scale)
return context
}
private func makeCMFormatDescription(pixelBuffer: CVPixelBuffer) -> CMFormatDescription? {
var formatDescription: CMFormatDescription?
let createImageBufferResult: CVReturn = CMVideoFormatDescriptionCreateForImageBuffer(
allocator: kCFAllocatorDefault,
imageBuffer: pixelBuffer,
formatDescriptionOut: &formatDescription
)
if createImageBufferResult != kCVReturnSuccess {
assertionFailure("Failed to create CMFormatDescription: \(createImageBufferResult)")
return nil
}
return formatDescription
}
private func getCMSampleTimingInfo() -> CMSampleTimingInfo {
let currentTime: CMTime = .init(
seconds: CACurrentMediaTime(),
preferredTimescale: 60
)
let timingInfo: CMSampleTimingInfo = .init(
duration: .init(seconds: 1, preferredTimescale: 60),
presentationTimeStamp: currentTime,
decodeTimeStamp: currentTime
)
return timingInfo
}
}
@fukemy
Copy link

fukemy commented Dec 26, 2022

hi, thanks for usefull gist. Did you tried to apply webrtc call with PIP mode?

@chockenberry
Copy link

This was very helpful - domo arigato!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment