Как использовать RawDataInput в GPUImage2
Я использую библиотеку захвата медиа под названием NextLevel, которая выплевывает CMSampleBuffer
на каждом кадре. Я хочу взять этот буфер и передать его в GPUImage2 через rawDataInput
и передать его через некоторые фильтры и прочитать его обратно из rawDataOutput
в конце цепочки...
CMSampleBuffer bytes -> rawDataInput -> someFilter -> someotherFilter -> rawDataOutput -> создать CVPixelBuffer для других вещей.
Проблема в том, как преобразовать CMSampleBuffer в массив UInt8, чтобы rawDataInput мог принять его.
У меня есть следующий код, но он безумно медленный... кадр проходит через всю цепочку и до rawDataOuput
,dataAvailableCallback
но так же медленно, как 1 кадр в секунду. Я нашел этот код в сети, понятия не имею, что он делает математически, но, думаю, он неэффективен.
let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
let chromaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1)
let width = CVPixelBufferGetWidth(pixelBuffer)
let height = CVPixelBufferGetHeight(pixelBuffer)
let lumaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
let chromaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1)
let lumaBuffer = lumaBaseAddress?.assumingMemoryBound(to: UInt8.self)
let chromaBuffer = chromaBaseAddress?.assumingMemoryBound(to: UInt8.self)
var rgbaImage = [UInt8](repeating: 0, count: 4*width*height)
for x in 0 ..< width {
for y in 0 ..< height {
let lumaIndex = x+y*lumaBytesPerRow
let chromaIndex = (y/2)*chromaBytesPerRow+(x/2)*2
let yp = lumaBuffer?[lumaIndex]
let cb = chromaBuffer?[chromaIndex]
let cr = chromaBuffer?[chromaIndex+1]
let ri = Double(yp!) + 1.402 * (Double(cr!) - 128)
let gi = Double(yp!) - 0.34414 * (Double(cb!) - 128) - 0.71414 * (Double(cr!) - 128)
let bi = Double(yp!) + 1.772 * (Double(cb!) - 128)
let r = UInt8(min(max(ri,0), 255))
let g = UInt8(min(max(gi,0), 255))
let b = UInt8(min(max(bi,0), 255))
rgbaImage[(x + y * width) * 4] = b
rgbaImage[(x + y * width) * 4 + 1] = g
rgbaImage[(x + y * width) * 4 + 2] = r
rgbaImage[(x + y * width) * 4 + 3] = 255
}
}
self.rawInput.uploadBytes(rgbaImage, size: Size.init(width: Float(width), height: Float(height)), pixelFormat: PixelFormat.rgba)
CVPixelBufferUnlockBaseAddress( pixelBuffer, CVPixelBufferLockFlags(rawValue: 0) );
Обновление 1
Я использую библиотеку камер с именем NextLevel для извлечения кадров камеры (CMSampleBuffer) и подачи их в цепочку фильтров, в данном случае RawDataInput через массив байтов UInt8. Поскольку NextLevel использует luma/chroma, когда это возможно, я прокомментировал 5 строк в https://github.com/NextLevel/NextLevel/blob/master/Sources/NextLevel.swift как прокомментировал @rythmic fishman. Но приведенный выше код сломался бы, поэтому я заменил его следующим.
let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0));
let width = CVPixelBufferGetWidth(pixelBuffer)
let height = CVPixelBufferGetHeight(pixelBuffer)
let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)
let int8Buffer = CVPixelBufferGetBaseAddress(pixelBuffer)?.assumingMemoryBound(to: UInt8.self)
var rgbaImage = [UInt8](repeating: 0, count: 4*width*height)
for i in 0 ..< (width*height*4){
rgbaImage[i] = UInt8((int8Buffer?[i])!)
}
self.rawInput.uploadBytes(rgbaImage, size: Size.init(width: Float(width), height: Float(height)), pixelFormat: PixelFormat.rgba)
CVPixelBufferUnlockBaseAddress(pixelBuffer,CVPixelBufferLockFlags(rawValue: 0))
Этот код работает, когда NextLevel не использует яркость / цветность, но кадры по-прежнему очень и очень медленные, когда отображаются в конце цепочки фильтров с использованием GPUImage RenderView.
Обновление 2
Поэтому я решил сделать собственный RawDataInput.swift на основе Camera.swift из GPUImage2. Поскольку класс Camera принимает кадры с собственной камеры в формате CMSampleBuffer, я подумал... ну, NextLevel выбрасывает точно такие же буферы, я могу скопировать реализацию класса Camera GPUImage2 и удалить все, что мне не нужно, и просто оставить 1 метод, который получает CMSampleBuffer и обрабатывает его. Оказывается, это работает отлично. КРОМЕ... есть лаг (нет пропущенных кадров, просто лаг). Я не знаю, где находится горлышко бутылки, я читал, что обработка / изменение CMSampleBuffers, исходящих из родной камеры, а затем отображающих их... может вызвать задержки, как упоминалось в этом вопросе: как сохранить низкую задержку во время предварительного просмотра видео от AVFoundation?
Я сделал видео о задержке, которую я испытываю... https://www.youtube.com/watch?v=5DQRnOTi4wk
Превью в верхнем углу от NextLevel'spreviewLayer: AVCaptureVideoPreviewLayer
'и отфильтрованный предварительный просмотр представляет собой GPUImage2 Renderview в конце цепочки.. работает на iPhone 6 с разрешением 1920px и 7 фильтрами. Это отставание не происходит с классом GPUImage2 Camera.
Вот пользовательский RawDataInput, который я собрал.
#if os(Linux)
#if GLES
import COpenGLES.gles2
#else
import COpenGL
#endif
#else
#if GLES
import OpenGLES
#else
import OpenGL.GL3
#endif
#endif
import AVFoundation
public enum PixelFormat {
case bgra
case rgba
case rgb
case luminance
func toGL() -> Int32 {
switch self {
case .bgra: return GL_BGRA
case .rgba: return GL_RGBA
case .rgb: return GL_RGB
case .luminance: return GL_LUMINANCE
}
}
}
// TODO: Replace with texture caches where appropriate
public class RawDataInput: ImageSource {
public let targets = TargetContainer()
let frameRenderingSemaphore = DispatchSemaphore(value:1)
let cameraProcessingQueue = DispatchQueue.global(priority:DispatchQueue.GlobalQueuePriority.default)
let captureAsYUV:Bool = true
let yuvConversionShader:ShaderProgram?
var supportsFullYUVRange:Bool = false
public init() {
if captureAsYUV {
supportsFullYUVRange = false
let videoOutput = AVCaptureVideoDataOutput()
let supportedPixelFormats = videoOutput.availableVideoCVPixelFormatTypes
for currentPixelFormat in supportedPixelFormats! {
if ((currentPixelFormat as! NSNumber).int32Value == Int32(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)) {
supportsFullYUVRange = true
}
}
if (supportsFullYUVRange) {
yuvConversionShader = crashOnShaderCompileFailure("Camera"){try sharedImageProcessingContext.programForVertexShader(defaultVertexShaderForInputs(2), fragmentShader:YUVConversionFullRangeFragmentShader)}
} else {
yuvConversionShader = crashOnShaderCompileFailure("Camera"){try sharedImageProcessingContext.programForVertexShader(defaultVertexShaderForInputs(2), fragmentShader:YUVConversionVideoRangeFragmentShader)}
}
} else {
yuvConversionShader = nil
}
}
public func uploadPixelBuffer(_ cameraFrame: CVPixelBuffer ) {
guard (frameRenderingSemaphore.wait(timeout:DispatchTime.now()) == DispatchTimeoutResult.success) else { return }
let bufferWidth = CVPixelBufferGetWidth(cameraFrame)
let bufferHeight = CVPixelBufferGetHeight(cameraFrame)
CVPixelBufferLockBaseAddress(cameraFrame, CVPixelBufferLockFlags(rawValue:CVOptionFlags(0)))
sharedImageProcessingContext.runOperationAsynchronously{
let cameraFramebuffer:Framebuffer
let luminanceFramebuffer:Framebuffer
let chrominanceFramebuffer:Framebuffer
if sharedImageProcessingContext.supportsTextureCaches() {
var luminanceTextureRef:CVOpenGLESTexture? = nil
let _ = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, sharedImageProcessingContext.coreVideoTextureCache, cameraFrame, nil, GLenum(GL_TEXTURE_2D), GL_LUMINANCE, GLsizei(bufferWidth), GLsizei(bufferHeight), GLenum(GL_LUMINANCE), GLenum(GL_UNSIGNED_BYTE), 0, &luminanceTextureRef)
let luminanceTexture = CVOpenGLESTextureGetName(luminanceTextureRef!)
glActiveTexture(GLenum(GL_TEXTURE4))
glBindTexture(GLenum(GL_TEXTURE_2D), luminanceTexture)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE)
luminanceFramebuffer = try! Framebuffer(context:sharedImageProcessingContext, orientation:.portrait, size:GLSize(width:GLint(bufferWidth), height:GLint(bufferHeight)), textureOnly:true, overriddenTexture:luminanceTexture)
var chrominanceTextureRef:CVOpenGLESTexture? = nil
let _ = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, sharedImageProcessingContext.coreVideoTextureCache, cameraFrame, nil, GLenum(GL_TEXTURE_2D), GL_LUMINANCE_ALPHA, GLsizei(bufferWidth / 2), GLsizei(bufferHeight / 2), GLenum(GL_LUMINANCE_ALPHA), GLenum(GL_UNSIGNED_BYTE), 1, &chrominanceTextureRef)
let chrominanceTexture = CVOpenGLESTextureGetName(chrominanceTextureRef!)
glActiveTexture(GLenum(GL_TEXTURE5))
glBindTexture(GLenum(GL_TEXTURE_2D), chrominanceTexture)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE)
chrominanceFramebuffer = try! Framebuffer(context:sharedImageProcessingContext, orientation:.portrait, size:GLSize(width:GLint(bufferWidth / 2), height:GLint(bufferHeight / 2)), textureOnly:true, overriddenTexture:chrominanceTexture)
} else {
glActiveTexture(GLenum(GL_TEXTURE4))
luminanceFramebuffer = sharedImageProcessingContext.framebufferCache.requestFramebufferWithProperties(orientation:.portrait, size:GLSize(width:GLint(bufferWidth), height:GLint(bufferHeight)), textureOnly:true)
luminanceFramebuffer.lock()
glBindTexture(GLenum(GL_TEXTURE_2D), luminanceFramebuffer.texture)
glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_LUMINANCE, GLsizei(bufferWidth), GLsizei(bufferHeight), 0, GLenum(GL_LUMINANCE), GLenum(GL_UNSIGNED_BYTE), CVPixelBufferGetBaseAddressOfPlane(cameraFrame, 0))
glActiveTexture(GLenum(GL_TEXTURE5))
chrominanceFramebuffer = sharedImageProcessingContext.framebufferCache.requestFramebufferWithProperties(orientation:.portrait, size:GLSize(width:GLint(bufferWidth / 2), height:GLint(bufferHeight / 2)), textureOnly:true)
chrominanceFramebuffer.lock()
glBindTexture(GLenum(GL_TEXTURE_2D), chrominanceFramebuffer.texture)
glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_LUMINANCE_ALPHA, GLsizei(bufferWidth / 2), GLsizei(bufferHeight / 2), 0, GLenum(GL_LUMINANCE_ALPHA), GLenum(GL_UNSIGNED_BYTE), CVPixelBufferGetBaseAddressOfPlane(cameraFrame, 1))
}
cameraFramebuffer = sharedImageProcessingContext.framebufferCache.requestFramebufferWithProperties(orientation:.portrait, size:luminanceFramebuffer.sizeForTargetOrientation(.portrait), textureOnly:false)
let conversionMatrix:Matrix3x3
if (self.supportsFullYUVRange) {
conversionMatrix = colorConversionMatrix601FullRangeDefault
} else {
conversionMatrix = colorConversionMatrix601Default
}
convertYUVToRGB(shader:self.yuvConversionShader!, luminanceFramebuffer:luminanceFramebuffer, chrominanceFramebuffer:chrominanceFramebuffer, resultFramebuffer:cameraFramebuffer, colorConversionMatrix:conversionMatrix)
//ONLY RGBA
//let cameraFramebuffer:Framebuffer = sharedImageProcessingContext.framebufferCache.requestFramebufferWithProperties(orientation:.portrait, size:GLSize(width:GLint(bufferWidth), height:GLint(bufferHeight)), textureOnly:true)
//glBindTexture(GLenum(GL_TEXTURE_2D), cameraFramebuffer.texture)
//glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(bufferWidth), GLsizei(bufferHeight), 0, GLenum(GL_BGRA), GLenum(GL_UNSIGNED_BYTE), CVPixelBufferGetBaseAddress(cameraFrame))
CVPixelBufferUnlockBaseAddress(cameraFrame, CVPixelBufferLockFlags(rawValue:CVOptionFlags(0)))
self.updateTargetsWithFramebuffer(cameraFramebuffer)
self.frameRenderingSemaphore.signal()
}
}
public func uploadBytes(_ bytes:[UInt8], size:Size, pixelFormat:PixelFormat, orientation:ImageOrientation = .portrait) {
let dataFramebuffer = sharedImageProcessingContext.framebufferCache.requestFramebufferWithProperties(orientation:orientation, size:GLSize(size), textureOnly:true, internalFormat:pixelFormat.toGL(), format:pixelFormat.toGL())
glActiveTexture(GLenum(GL_TEXTURE1))
glBindTexture(GLenum(GL_TEXTURE_2D), dataFramebuffer.texture)
glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, size.glWidth(), size.glHeight(), 0, GLenum(pixelFormat.toGL()), GLenum(GL_UNSIGNED_BYTE), bytes)
updateTargetsWithFramebuffer(dataFramebuffer)
}
public func transmitPreviousImage(to target:ImageConsumer, atIndex:UInt) {
// TODO: Determine if this is necessary for the raw data uploads
// if let buff = self.dataFramebuffer {
// buff.lock()
// target.newFramebufferAvailable(buff, fromSourceIndex:atIndex)
// }
}
}
Я просто не понимаю, почему это отставание, если оно не отличается от класса GPUImage2 Camera. NextLevel не выполняет никакой другой обработки этих кадров, он просто передает их, так почему задержка?
1 ответ
У меня была такая же проблема, и я потратил слишком много времени на ее решение. Наконец нашел решение. Проблема с задержкой кадров видео связана со стабилизацией видео. Просто используйте эту строку:
NextLevel.shared.videoStabilizationMode = .off
Значение по умолчанию - .auto, и поэтому возникает проблема.