FFmpeg4入门10:软解并使用QML播放视频(YUV420P转OpenGL)

QML显示视频无法用之前的方法实现,我结果多次尝试过后终于找到了可以使用的方法。

解码流程图为:

flow

解码函数调用流程图为:

flow

流程架构如下图:

flow

分为三个部分

软解码

主要流程和之前的一样,只是少了格式转换和多了数据填充部分,关键代码如下:

while(av_read_frame(fmtCtx,pkt)>=0){
    
    
    if(pkt->stream_index == videoStreamIndex){
    
    
        if(avcodec_send_packet(videoCodecCtx,pkt)>=0){
    
    
            int ret;
            while((ret=avcodec_receive_frame(videoCodecCtx,yuvFrame))>=0){
    
    
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                    return;
                else if (ret < 0) {
    
    
                    fprintf(stderr, "Error during decoding\n");
                    continue;
                }

                m_yuvData.Y.resize(yuvFrame->linesize[0]*yuvFrame->height);
                m_yuvData.Y =QByteArray((char*)yuvFrame->data[0],m_yuvData.Y.size());
                m_yuvData.U.resize(yuvFrame->linesize[1]*yuvFrame->height/2);
                m_yuvData.U =QByteArray((char*)yuvFrame->data[1],m_yuvData.Y.size());
                m_yuvData.V.resize(yuvFrame->linesize[2]*yuvFrame->height/2);
                m_yuvData.V =QByteArray((char*)yuvFrame->data[2],m_yuvData.Y.size());
                m_yuvData.yLineSize = yuvFrame->linesize[0];
                m_yuvData.uLineSize = yuvFrame->linesize[1];
                m_yuvData.vLineSize = yuvFrame->linesize[2];
                m_yuvData.height = yuvFrame->height;

                frameBuffer.append(m_yuvData);

                QThread::msleep(24);
            }
        }
        av_packet_unref(pkt);
    }
}

主要是将解码后的YUV(YUV420P)数据复制到指定的结构体中,并排入缓冲队列中。

OpenGL纹理渲染

//新建变量,并设置参数
I420Render::I420Render()
{
    
    
    mTexY = new QOpenGLTexture(QOpenGLTexture::Target2D);
    mTexY->setFormat(QOpenGLTexture::LuminanceFormat);
    mTexY->setMinificationFilter(QOpenGLTexture::Nearest);
    mTexY->setMagnificationFilter(QOpenGLTexture::Nearest);
    mTexY->setWrapMode(QOpenGLTexture::ClampToEdge);

    mTexU = new QOpenGLTexture(QOpenGLTexture::Target2D);
    mTexU->setFormat(QOpenGLTexture::LuminanceFormat);
    mTexU->setMinificationFilter(QOpenGLTexture::Nearest);
    mTexU->setMagnificationFilter(QOpenGLTexture::Nearest);
    mTexU->setWrapMode(QOpenGLTexture::ClampToEdge);

    mTexV = new QOpenGLTexture(QOpenGLTexture::Target2D);
    mTexV->setFormat(QOpenGLTexture::LuminanceFormat);
    mTexV->setMinificationFilter(QOpenGLTexture::Nearest);
    mTexV->setMagnificationFilter(QOpenGLTexture::Nearest);
    mTexV->setWrapMode(QOpenGLTexture::ClampToEdge);
}
//设置着色语言
void I420Render::init()
{
    
    
    initializeOpenGLFunctions();
    const char *vsrc =
            "attribute vec4 vertexIn; \
             attribute vec2 textureIn; \
             varying vec2 textureOut;  \
             void main(void)           \
             {                         \
                 gl_Position = vertexIn; \
                 textureOut = textureIn; \
             }";

    const char *fsrc =
            "varying mediump vec2 textureOut;\n"
            "uniform sampler2D textureY;\n"
            "uniform sampler2D textureU;\n"
            "uniform sampler2D textureV;\n"
            "void main(void)\n"
            "{\n"
            "vec3 yuv; \n"
            "vec3 rgb; \n"
            "yuv.x = texture2D(textureY, textureOut).r; \n"
            "yuv.y = texture2D(textureU, textureOut).r - 0.5; \n"
            "yuv.z = texture2D(textureV, textureOut).r - 0.5; \n"
            "rgb = mat3( 1,       1,         1, \n"
                        "0,       -0.3455,  1.779, \n"
                        "1.4075, -0.7169,  0) * yuv; \n"
            "gl_FragColor = vec4(rgb, 1); \n"
            "}\n";

    m_program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,vsrc);
    m_program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,fsrc);
    m_program.bindAttributeLocation("vertexIn",0);
    m_program.bindAttributeLocation("textureIn",1);
    m_program.link();
    m_program.bind();

	//图片的顶点坐标
    vertices << QVector2D(-1.0f,1.0f)
             << QVector2D(1.0f,1.0f)
             << QVector2D(1.0f,-1.0f)
             << QVector2D(-1.0f,-1.0f);
    //纹理坐标
    textures << QVector2D(0.0f,1.f)
             << QVector2D(1.0f,1.0f)
             << QVector2D(1.0f,0.0f)
             << QVector2D(0.0f,0.0f);
}
//设置纹理信息,只需要设置一次
void I420Render::updateTextureInfo(int w, int h)
{
    
    
    mTexY->setSize(w,h);
    mTexY->allocateStorage(QOpenGLTexture::Red,QOpenGLTexture::UInt8);

    mTexU->setSize(w/2,h/2);
    mTexU->allocateStorage(QOpenGLTexture::Red,QOpenGLTexture::UInt8);

    mTexV->setSize(w/2,h/2);
    mTexV->allocateStorage(QOpenGLTexture::Red,QOpenGLTexture::UInt8);

    mTextureAlloced=true;
}
//更新纹理数据,每次解码一帧后就调用此函数
void I420Render::updateTextureData(const YUVData &data)
{
    
    
    if(data.Y.size()<=0 || data.U.size()<=0 || data.V.size()<=0) return;

    QOpenGLPixelTransferOptions options;
    options.setImageHeight(data.height);

    options.setRowLength(data.yLineSize);
    mTexY->setData(QOpenGLTexture::Luminance,QOpenGLTexture::UInt8,data.Y.data(),&options);

    options.setRowLength(data.uLineSize);
    mTexU->setData(QOpenGLTexture::Luminance,QOpenGLTexture::UInt8,data.U.data(),&options);

    options.setRowLength(data.vLineSize);
    mTexV->setData(QOpenGLTexture::Luminance,QOpenGLTexture::UInt8,data.V.data(),&options);
}
//绘制纹理数据
void I420Render::paint()
{
    
    
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDisable(GL_DEPTH_TEST);

    if(!mTextureAlloced) return;

    m_program.bind();
    m_program.enableAttributeArray("vertexIn");
    m_program.setAttributeArray("vertexIn",vertices.constData());
    m_program.enableAttributeArray("textureIn");
    m_program.setAttributeArray("textureIn",textures.constData());

    glActiveTexture(GL_TEXTURE0);
    mTexY->bind();

    glActiveTexture(GL_TEXTURE1);
    mTexU->bind();

    glActiveTexture(GL_TEXTURE2);
    mTexV->bind();

    m_program.setUniformValue("textureY",0);
    m_program.setUniformValue("textureU",1);
    m_program.setUniformValue("textureV",2);
    glDrawArrays(GL_QUADS,0,4);
    m_program.disableAttributeArray("vertexIn");
    m_program.disableAttributeArray("textureIn");
    m_program.release();
}

QML显示部分

此部分需要两个类VideoItem、VideoFboItem,VideoItem是QML调用的接口,而VideoFboItem是由VideoItem自动调用。

VideoFboItem

//************VideoItemRender************//
class VideoFboItem : public QQuickFramebufferObject::Renderer
{
    
    
public:
    VideoFboItem(){
    
    
        m_render.init();
    }

    void render() override{
    
    
        m_render.paint();
        m_window->resetOpenGLState();
    }
    QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override{
    
    
        QOpenGLFramebufferObjectFormat format;
        format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
        format.setSamples(4);
        m_render.resize(size.width(), size.height());
        return new QOpenGLFramebufferObject(size, format);
    }
    void synchronize(QQuickFramebufferObject *item) override{
    
    
        VideoItem *pItem = qobject_cast<VideoItem *>(item);
        if (pItem)
        {
    
    
            if (!m_window)
            {
    
    
                m_window = pItem->window();
            }
            if (pItem->infoDirty())
            {
    
    
                m_render.updateTextureInfo(pItem->videoWidth(), pItem->videoHeght());
                pItem->makeInfoDirty(false);
            }
            ba = pItem->getFrame();
            m_render.updateTextureData(ba);
        }
    }
private:
    I420Render m_render;
    QQuickWindow *m_window = nullptr;

    YUVData ba;
};

render/createFramebufferObject/synchronize这三个函数是直接继承基类重写。render调用OpenGL渲染的paint函数绘制界面,createFramebufferObject写法固定,synchronize从VideoItem的接口中获取需要的信息(图像宽度高度、具体数据)。

这些函数由VideoItem函数自动调用。

VideoItem

此类是直接提供QML调用的接口,并且与Videoitem类由交互。

QQuickFramebufferObject::Renderer *VideoItem::createRenderer() const
{
    
    
    return new VideoFboItem;
}

这样之后VideoItem就会自动调用VideoFboItem的函数。

QML调用

首先在QML格式文件中导入视频模块

import VideoItem 1.0

然后在根组件中插件视频模块对象

VideoItem{
    id:videoitem
    anchors.fill: parent
}

之后如果需要调用视频模块的接口,直接使用videoitem就可以了。

    Button {
        id: button
        x: 29
        y: 27
        text: qsTr("Play")

        onClicked: {
            videoitem.setUrl("/home/jackey/Videos/Sample.mkv")
            videoitem.start()
        }
    }

按钮按下之后调用videoitem的setUrl/start接口。

效果

默认界面为:

default

点击Play按钮后,开始播放视频:

play

以上是PC下默认解码YUV420P显示方法,如果需要NV12(YUV420SP)显示可以使用之前的方法。

源码在Github10.video_decode_by_cpu_display_by_qml下。

猜你喜欢

转载自blog.csdn.net/qq_26056015/article/details/125253975