라이브스트리밍 테스트영상
라이브 스트리밍 네트워크 예외처리
VOD 실시간 채팅 기능 테스트
GPUPixel 필터 처리 결과는 CVPixelBuffer + CMTime 형태로 나오지만, HaishinKit 송출 입력은 CMSampleBuffer를 요구해 필터 결과를 그대로 RTMP로 주입할 수 없었습니다.
필터 산출물(CVPixelBuffer + CMTime)을 CMSampleBuffer로 변환하는 CoreMedia 어댑터를 구현하고, 변환된 프레임을 MediaMixer.append(sampleBuffer)로 주입해 필터 적용 화면이 그대로 송출되도록 파이프라인을 구성했습니다.
— offscreen 프레임(CVPixelBuffer+CMTime)을 GPUPixel로 전달
// IdolLiveDevice.swift
func videoOffscreenRenderCaptureOutput(
pixelBuffer: CVPixelBuffer,
time: CMTime,
position: AVCaptureDevice.Position
) {
guard position == currentCameraPosition else { return }
if position == .front {
frontGpuPixelManager?.processImageBuffer(imageBuffer: pixelBuffer, timestamp: time)
} else {
backGpuPixelManager?.processImageBuffer(imageBuffer: pixelBuffer, timestamp: time)
}
}
— PixelBuffer+Time → CMSampleBuffer 변환 후 MediaMixer.append로 송출 주입
// IdolLiveGpuPixel.swift
func gpuPixelCaptureOutput(
pixelBuffer: CVPixelBuffer,
time: CMTime,
position: AVCaptureDevice.Position
) {
cameraMananger?.appendFrame(
pixelBuffer: pixelBuffer,
sourcePostion: position,
timestamp: time
)
guard position == currentCameraPosition else { return }
if let sampleBuffer = sampleBuffer(from: pixelBuffer, presentationTime: time, frameRate: 30) {
Task { await haishinkitManager?.mixer?.append(sampleBuffer) }
}
}
func sampleBuffer(
from pixelBuffer: CVPixelBuffer,
presentationTime: CMTime,
frameRate: Int = 30
) -> CMSampleBuffer? {
var sampleBuffer: CMSampleBuffer?
let frameDuration = CMTime(
value: CMTimeValue(1_000_000_000 / frameRate),
timescale: 1_000_000_000
)
var timingInfo = CMSampleTimingInfo(
duration: frameDuration,
presentationTimeStamp: presentationTime,
decodeTimeStamp: presentationTime
)
var formatDesc: CMVideoFormatDescription?
let status = CMVideoFormatDescriptionCreateForImageBuffer(
allocator: kCFAllocatorDefault,
imageBuffer: pixelBuffer,
formatDescriptionOut: &formatDesc
)
guard status == noErr, let formatDesc else { return nil }
let bufferStatus = CMSampleBufferCreateForImageBuffer(
allocator: kCFAllocatorDefault,
imageBuffer: pixelBuffer,
dataReady: true,
makeDataReadyCallback: nil,
refcon: nil,
formatDescription: formatDesc,
sampleTiming: &timingInfo,
sampleBufferOut: &sampleBuffer
)
guard bufferStatus == noErr else { return nil }
return sampleBuffer
}