diff options
Diffstat (limited to 'src/plugins/avfoundation/mediaplayer/avfvideoframerenderer.mm')
-rw-r--r-- | src/plugins/avfoundation/mediaplayer/avfvideoframerenderer.mm | 330 |
1 files changed, 211 insertions, 119 deletions
diff --git a/src/plugins/avfoundation/mediaplayer/avfvideoframerenderer.mm b/src/plugins/avfoundation/mediaplayer/avfvideoframerenderer.mm index 51f961729..a22ee2b82 100644 --- a/src/plugins/avfoundation/mediaplayer/avfvideoframerenderer.mm +++ b/src/plugins/avfoundation/mediaplayer/avfvideoframerenderer.mm @@ -41,12 +41,20 @@ #include <QtMultimedia/qabstractvideosurface.h> #include <QtGui/QOpenGLFramebufferObject> -#include <QtGui/QWindow> +#include <QtGui/QOpenGLShaderProgram> +#include <QtGui/QOffscreenSurface> + +#include <QtCore/private/qcore_mac_p.h> #ifdef QT_DEBUG_AVF #include <QtCore/qdebug.h> #endif +#ifdef Q_OS_MACOS +#import <AppKit/AppKit.h> +#include <CoreVideo/CVOpenGLTextureCache.h> +#endif + #import <CoreVideo/CVBase.h> #import <AVFoundation/AVFoundation.h> @@ -54,15 +62,23 @@ QT_USE_NAMESPACE AVFVideoFrameRenderer::AVFVideoFrameRenderer(QAbstractVideoSurface *surface, QObject *parent) : QObject(parent) - , m_videoLayerRenderer(nullptr) - , m_surface(surface) - , m_offscreenSurface(nullptr) , m_glContext(nullptr) - , m_currentBuffer(1) + , m_offscreenSurface(nullptr) + , m_surface(surface) + , m_textureCache(nullptr) + , m_videoOutput(nullptr) , m_isContextShared(true) { + m_videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:@{ + (NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA), + (NSString *)kCVPixelBufferOpenGLCompatibilityKey: @YES + }]; + [m_videoOutput setDelegate:nil queue:nil]; + +#ifdef Q_OS_MACOS m_fbo[0] = nullptr; m_fbo[1] = nullptr; +#endif } AVFVideoFrameRenderer::~AVFVideoFrameRenderer() @@ -71,81 +87,200 @@ AVFVideoFrameRenderer::~AVFVideoFrameRenderer() qDebug() << Q_FUNC_INFO; #endif - [m_videoLayerRenderer release]; - delete m_fbo[0]; - delete m_fbo[1]; + [m_videoOutput release]; + if (m_textureCache) + CFRelease(m_textureCache); delete m_offscreenSurface; delete m_glContext; + +#ifdef Q_OS_MACOS + delete m_fbo[0]; + delete m_fbo[1]; +#endif } -GLuint AVFVideoFrameRenderer::renderLayerToTexture(AVPlayerLayer *layer) +#ifdef Q_OS_MACOS +GLuint AVFVideoFrameRenderer::renderLayerToFBO(AVPlayerLayer *layer, QSize *size) { - //Is layer valid - if (!layer) + QCFType<CVOGLTextureRef> texture = renderLayerToTexture(layer, size); + if (!texture) return 0; - //If the glContext isn't shared, it doesn't make sense to return a texture for us - if (m_offscreenSurface && !m_isContextShared) - return 0; + Q_ASSERT(size); - QOpenGLFramebufferObject *fbo = initRenderer(layer); + // Do we have FBO's already? + if ((!m_fbo[0] && !m_fbo[0]) || (m_fbo[0]->size() != *size)) { + delete m_fbo[0]; + delete m_fbo[1]; + m_fbo[0] = new QOpenGLFramebufferObject(*size); + m_fbo[1] = new QOpenGLFramebufferObject(*size); + } + + // Switch buffer target + m_currentFBO = !m_currentFBO; + QOpenGLFramebufferObject *fbo = m_fbo[m_currentFBO]; - if (!fbo) + if (!fbo || !fbo->bind()) return 0; - renderLayerToFBO(layer, fbo); - if (m_glContext) - m_glContext->doneCurrent(); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, size->width(), size->height()); + + if (!m_blitter.isCreated()) + m_blitter.create(); + + m_blitter.bind(GL_TEXTURE_RECTANGLE); + m_blitter.blit(CVOpenGLTextureGetName(texture), QMatrix4x4(), QMatrix3x3()); + m_blitter.release(); + glFinish(); + + fbo->release(); return fbo->texture(); } +#endif + +CVOGLTextureRef AVFVideoFrameRenderer::renderLayerToTexture(AVPlayerLayer *layer, QSize *size) +{ + initRenderer(); + + // If the glContext isn't shared, it doesn't make sense to return a texture for us + if (!m_isContextShared) + return nullptr; + + size_t width = 0, height = 0; + auto texture = createCacheTextureFromLayer(layer, width, height); + if (size) + *size = QSize(width, height); + return texture; +} -QImage AVFVideoFrameRenderer::renderLayerToImage(AVPlayerLayer *layer) +CVPixelBufferRef AVFVideoFrameRenderer::copyPixelBufferFromLayer(AVPlayerLayer *layer, + size_t& width, size_t& height) { //Is layer valid if (!layer) { - return QImage(); +#ifdef QT_DEBUG_AVF + qWarning("copyPixelBufferFromLayer: invalid layer"); +#endif + return nullptr; } - QOpenGLFramebufferObject *fbo = initRenderer(layer); + AVPlayerItem *item = layer.player.currentItem; + if (![item.outputs containsObject:m_videoOutput]) + [item addOutput:m_videoOutput]; - if (!fbo) - return QImage(); + CFTimeInterval currentCAFrameTime = CACurrentMediaTime(); + CMTime currentCMFrameTime = [m_videoOutput itemTimeForHostTime:currentCAFrameTime]; + + // Happens when buffering / loading + if (CMTimeCompare(currentCMFrameTime, kCMTimeZero) < 0) + return nullptr; + + if (![m_videoOutput hasNewPixelBufferForItemTime:currentCMFrameTime]) + return nullptr; + + CVPixelBufferRef pixelBuffer = [m_videoOutput copyPixelBufferForItemTime:currentCMFrameTime + itemTimeForDisplay:nil]; + if (!pixelBuffer) { +#ifdef QT_DEBUG_AVF + qWarning("copyPixelBufferForItemTime returned nil"); + CMTimeShow(currentCMFrameTime); +#endif + return nullptr; + } + + width = CVPixelBufferGetWidth(pixelBuffer); + height = CVPixelBufferGetHeight(pixelBuffer); + return pixelBuffer; +} - renderLayerToFBO(layer, fbo); - QImage fboImage = fbo->toImage(); - if (m_glContext) - m_glContext->doneCurrent(); +CVOGLTextureRef AVFVideoFrameRenderer::createCacheTextureFromLayer(AVPlayerLayer *layer, + size_t& width, size_t& height) +{ + CVPixelBufferRef pixelBuffer = copyPixelBufferFromLayer(layer, width, height); + + if (!pixelBuffer) + return nullptr; + + CVOGLTextureCacheFlush(m_textureCache, 0); + + CVOGLTextureRef texture = nullptr; +#ifdef Q_OS_MACOS + CVReturn err = CVOpenGLTextureCacheCreateTextureFromImage(kCFAllocatorDefault, + m_textureCache, + pixelBuffer, + nil, + &texture); +#else + CVReturn err = CVOGLTextureCacheCreateTextureFromImage(kCFAllocatorDefault, m_textureCache, pixelBuffer, nullptr, + GL_TEXTURE_2D, GL_RGBA, + (GLsizei) width, (GLsizei) height, + GL_BGRA, GL_UNSIGNED_BYTE, 0, + &texture); +#endif + + if (!texture || err) { + qWarning() << "CVOGLTextureCacheCreateTextureFromImage failed error:" << err << m_textureCache; + } + + CVPixelBufferRelease(pixelBuffer); - return fboImage; + return texture; } -QOpenGLFramebufferObject *AVFVideoFrameRenderer::initRenderer(AVPlayerLayer *layer) +QImage AVFVideoFrameRenderer::renderLayerToImage(AVPlayerLayer *layer, QSize *size) { + size_t width = 0; + size_t height = 0; + CVPixelBufferRef pixelBuffer = copyPixelBufferFromLayer(layer, width, height); + if (size) + *size = QSize(width, height); + + if (!pixelBuffer) + return QImage(); - //Get size from AVPlayerLayer - m_targetSize = QSize(layer.bounds.size.width, layer.bounds.size.height); + OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer); + if (pixelFormat != kCVPixelFormatType_32BGRA) { +#ifdef QT_DEBUG_AVF + qWarning("CVPixelBuffer format is not BGRA32 (got: %d)", static_cast<quint32>(pixelFormat)); +#endif + return QImage(); + } - QOpenGLContext *shareContext = !m_glContext && m_surface - ? qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>()) - : nullptr; + CVPixelBufferLockBaseAddress(pixelBuffer, 0); + char *data = (char *)CVPixelBufferGetBaseAddress(pixelBuffer); + size_t stride = CVPixelBufferGetBytesPerRow(pixelBuffer); + + // format here is not relevant, only using for storage + QImage img = QImage(width, height, QImage::Format_ARGB32); + for (size_t j = 0; j < height; j++) { + memcpy(img.scanLine(j), data, width * 4); + data += stride; + } + + CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); + CVPixelBufferRelease(pixelBuffer); + return img; +} + +void AVFVideoFrameRenderer::initRenderer() +{ + // even for using a texture directly, we need to be able to make a context current, + // so we need an offscreen, and we shouldn't assume we can make the surface context + // current on that offscreen, so use our own (sharing with it). Slightly + // excessive but no performance penalty and makes the QImage path easier to maintain //Make sure we have an OpenGL context to make current - if ((shareContext && shareContext != QOpenGLContext::currentContext()) - || (!QOpenGLContext::currentContext() && !m_glContext)) { - - //Create Hidden QWindow surface to create context in this thread - delete m_offscreenSurface; - m_offscreenSurface = new QWindow(); - m_offscreenSurface->setSurfaceType(QWindow::OpenGLSurface); - //Needs geometry to be a valid surface, but size is not important - m_offscreenSurface->setGeometry(0, 0, 1, 1); - m_offscreenSurface->create(); + if (!m_glContext) { + //Create OpenGL context and set share context from surface + QOpenGLContext *shareContext = nullptr; + if (m_surface) + shareContext = qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>()); - delete m_glContext; m_glContext = new QOpenGLContext(); - m_glContext->setFormat(m_offscreenSurface->requestedFormat()); - if (shareContext) { m_glContext->setShareContext(shareContext); m_isContextShared = true; @@ -156,83 +291,40 @@ QOpenGLFramebufferObject *AVFVideoFrameRenderer::initRenderer(AVPlayerLayer *lay m_isContextShared = false; } if (!m_glContext->create()) { +#ifdef QT_DEBUG_AVF qWarning("failed to create QOpenGLContext"); - return nullptr; - } - - // CARenderer must be re-created with different current context, so release it now. - // See lines below where m_videoLayerRenderer is constructed. - if (m_videoLayerRenderer) { - [m_videoLayerRenderer release]; - m_videoLayerRenderer = nullptr; +#endif + return; } } - //Need current context - if (m_glContext) - m_glContext->makeCurrent(m_offscreenSurface); - - //Create the CARenderer if needed - if (!m_videoLayerRenderer) { - m_videoLayerRenderer = [CARenderer rendererWithCGLContext: CGLGetCurrentContext() options: nil]; - [m_videoLayerRenderer retain]; - } - - //Set/Change render source if needed - if (m_videoLayerRenderer.layer != layer) { - m_videoLayerRenderer.layer = layer; - m_videoLayerRenderer.bounds = layer.bounds; - } - - //Do we have FBO's already? - if ((!m_fbo[0] && !m_fbo[0]) || (m_fbo[0]->size() != m_targetSize)) { - delete m_fbo[0]; - delete m_fbo[1]; - m_fbo[0] = new QOpenGLFramebufferObject(m_targetSize); - m_fbo[1] = new QOpenGLFramebufferObject(m_targetSize); + if (!m_offscreenSurface) { + m_offscreenSurface = new QOffscreenSurface(); + m_offscreenSurface->setFormat(m_glContext->format()); + m_offscreenSurface->create(); } - //Switch buffer target - m_currentBuffer = !m_currentBuffer; - return m_fbo[m_currentBuffer]; -} - -void AVFVideoFrameRenderer::renderLayerToFBO(AVPlayerLayer *layer, QOpenGLFramebufferObject *fbo) -{ - //Start Rendering - //NOTE: This rendering method will NOT work on iOS as there is no CARenderer in iOS - if (!fbo->bind()) { - qWarning("AVFVideoRender FBO failed to bind"); - return; + // Need current context + m_glContext->makeCurrent(m_offscreenSurface); + + if (!m_textureCache) { +#ifdef Q_OS_MACOS + auto *currentContext = NSOpenGLContext.currentContext; + // Create an OpenGL CoreVideo texture cache from the pixel buffer. + auto err = CVOpenGLTextureCacheCreate( + kCFAllocatorDefault, + nullptr, + currentContext.CGLContextObj, + currentContext.pixelFormat.CGLPixelFormatObj, + nil, + &m_textureCache); +#else + CVReturn err = CVOGLTextureCacheCreate(kCFAllocatorDefault, nullptr, + [EAGLContext currentContext], + nullptr, &m_textureCache); +#endif + if (err) + qWarning("Error at CVOGLTextureCacheCreate %d", err); } - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, m_targetSize.width(), m_targetSize.height()); - - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - - //Render to FBO with inverted Y - glOrtho(0.0, m_targetSize.width(), 0.0, m_targetSize.height(), 0.0, 1.0); - - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - - [m_videoLayerRenderer beginFrameAtTime:CACurrentMediaTime() timeStamp:NULL]; - [m_videoLayerRenderer addUpdateRect:layer.bounds]; - [m_videoLayerRenderer render]; - [m_videoLayerRenderer endFrame]; - - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - - glFinish(); //Rendering needs to be done before passing texture to video frame - - fbo->release(); } |