Qt在視頻畫面上繪制動(dòng)態(tài)矩形

說明

Qt可以通過QOpenGLWidget使用OpenGL渲染顯示視頻,在渲染的視頻畫面上歧蕉,有可能需要繪制一些幾何圖案汰蓉。目前試了3種不同的方式在畫面上繪制矩形委煤,記錄下過程堂油。

通過QPainter繪制

Qt的繪制函數(shù)paintEvent(QPaintEvent *event)在QOpenGLWidget中可以繪制,并且和OpenGL的內(nèi)容疊在一起碧绞,只需要在繪制之前先調(diào)用下基類的paintEvent(QPaintEvent *event)即可府框,可以理解為先在畫布上畫好視頻,再在畫布上畫個(gè)矩形讥邻。這種方式靈活性最好迫靖。

void RenderWidget::paintEvent(QPaintEvent *event)
{
    QOpenGLWidget::paintEvent(event);

    qreal offset = sin(m_nCount * 0.1); //m_nCount是渲染的幀數(shù)

    QPainter painter(this);
    painter.setRenderHints(QPainter::SmoothPixmapTransform);
    painter.save();
    painter.setPen(QPen(QColor("#4FE3C1"), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
    painter.drawRect(QRectF(width() * offset, height() * 0.7, width() * 0.25, height() * 0.25));
    painter.restore();
}

通過OpenGL繪制

通過不同的QOpenGLShaderProgram,可以指定不同的著色器程序來實(shí)現(xiàn)矩形的繪制兴使。這種方式繪制的時(shí)候系宜,偏移量這些變化參數(shù)要通過Uniform傳遞給OpenGL的頂點(diǎn)著色器,如果圖形復(fù)雜或者帶3D可以考慮发魄。

void RenderWidget::initializeGL()
{
    initializeOpenGLFunctions();
    const char *vsrc =
            "attribute vec4 vertexIn; \
             attribute vec4 textureIn; \
             varying vec4 textureOut;  \
             void main(void)           \
             {                         \
                 gl_Position = vertexIn; \
                 textureOut = textureIn; \
             }";

    const char *fsrc =
            "varying mediump vec4 textureOut;\
            uniform sampler2D tex_y; \
            uniform sampler2D tex_u; \
            uniform sampler2D tex_v; \
            void main(void) \
            { \
                vec3 yuv; \
                vec3 rgb; \
                yuv.x = texture2D(tex_y, textureOut.st).r; \
                yuv.y = texture2D(tex_u, textureOut.st).r - 0.5; \
                yuv.z = texture2D(tex_v, textureOut.st).r - 0.5; \
                rgb = mat3( 1,       1,         1, \
                            0,       -0.39465,  2.03211, \
                            1.13983, -0.58060,  0) * yuv; \
                gl_FragColor = vec4(rgb, 1); \
            }";


    const char *rcvsrc = "#version 330 core\n \
            layout(location = 0) in vec3 aPos;\n \
            uniform vec2 offsetP; \
            void main(){\n \
                gl_Position = vec4(aPos.x + offsetP.x, aPos.y + offsetP.y, aPos.z, 1.0f);\n \
            }\n ";

    const char *rcfsrc = "#version 330 core\n \
            out vec4 FragColor;\n \
            void main(){\n \
                FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);\n \
            }\n ";

    videoProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,vsrc);
    videoProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,fsrc);
    videoProgram.link();

    rcProgram.addShaderFromSourceCode(QOpenGLShader::Vertex,rcvsrc);
    rcProgram.addShaderFromSourceCode(QOpenGLShader::Fragment,rcfsrc);
    rcProgram.link();

    {
        QOpenGLVertexArrayObject::Binder vaoBind(&rcvao);

        GLfloat rcPoints[]{
            0.25f,  0.25f, 0.0f,  // top right
             0.25f, -0.25f, 0.0f,  // bottom right
            -0.25f, -0.25f, 0.0f,  // bottom left
            -0.25f,  0.25f, 0.0f,   // top left
        };

        rcvbo.create();
        rcvbo.bind();
        rcvbo.allocate(rcPoints, sizeof(rcPoints));

        int attr = -1;
        attr = rcProgram.attributeLocation("aPos");
        rcProgram.setAttributeBuffer(attr, GL_FLOAT, 0, 3, sizeof(GLfloat) * 3);
        rcProgram.enableAttributeArray(attr);
        rcvbo.release();
    }

    {
        QOpenGLVertexArrayObject::Binder vaoBind(&vao);

        GLfloat points[]{
            -1.0f, 1.0f,
             1.0f, 1.0f,
             1.0f, -1.0f,
            -1.0f, -1.0f,

            0.0f,0.0f,
            1.0f,0.0f,
            1.0f,1.0f,
            0.0f,1.0f
        };

        vbo.create();
        vbo.bind();
        vbo.allocate(points,sizeof(points));

        m_pTextureY = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2D);
        m_pTextureU = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2D);
        m_pTextureV = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2D);
        m_pTextureY->create();
        m_pTextureU->create();
        m_pTextureV->create();

        int attr = -1;
        attr = videoProgram.attributeLocation("vertexIn");
        videoProgram.setAttributeBuffer(attr, GL_FLOAT, 0, 2, 2*sizeof(GLfloat));
        videoProgram.enableAttributeArray(attr);

        attr = videoProgram.attributeLocation("textureIn");
        videoProgram.setAttributeBuffer(attr, GL_FLOAT, 2 * 4 * sizeof(GLfloat),2,2*sizeof(GLfloat));
        videoProgram.enableAttributeArray(attr);

        vbo.release();
    }

    videoProgram.release();
    rcProgram.release();
}

void RenderWidget::render(uchar *yuvPtr, int w, int h)
{
    m_nCount++;
//    glDisable(GL_DEPTH_TEST);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    if(nullptr == yuvPtr || 0 >= w || 0 >= h){
        return;
    }

    videoProgram.bind();
    {
        QOpenGLVertexArrayObject::Binder vaoBind(&vao);

        //Y
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, m_pTextureY->textureId());
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED,GL_UNSIGNED_BYTE, yuvPtr);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        //U
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, m_pTextureU->textureId());
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w >> 1, h >> 1, 0, GL_RED,GL_UNSIGNED_BYTE, yuvPtr + w * h);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        //V
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_pTextureV->textureId());
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w >> 1, h >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, yuvPtr + w * h * 5 / 4);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        videoProgram.setUniformValue("tex_y",2);
        videoProgram.setUniformValue("tex_u",1);
        videoProgram.setUniformValue("tex_v",0);
        glDrawArrays(GL_QUADS, 0, 4);
    }
    videoProgram.release();

    rcProgram.bind();
    {
        QOpenGLVertexArrayObject::Binder rcvaoBind(&rcvao);
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

        float offset = sin(m_nCount * 0.1f);
        rcProgram.setUniformValue("offsetP", QVector2D(offset, 0.7f));
        glDrawArrays(GL_QUADS, 0, 4);
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }
    rcProgram.release();
}

void RenderWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

通過AVFilter繪制

ffmpeg的濾鏡有很多強(qiáng)大的功能蜈首,這里就用了個(gè)最簡(jiǎn)單的drawbox, 使用濾鏡的話初始化要做很多設(shè)置,另外我還沒找到在哪設(shè)置可以改變繪制矩形的位置欠母,filter_descr就在初始化的時(shí)候用了一次。這種方式會(huì)直接將繪制內(nèi)容輸出到最后的視頻數(shù)據(jù)上吆寨,如果不是需要保留包含繪制內(nèi)容的視頻的話更傾向于上面兩種方式赏淌。

初始化

  ...
    static const char *filter_descr = "drawbox=x=200:y=400:w=200:h=200:color=blue";

    AVFilterContext* buffersrc_ctx = nullptr;
    AVFilterContext* buffersink_ctx = nullptr;
    AVFilterGraph* filter_graph = nullptr;
  ...

int InitFilters(const char *filters_descr)
{
    int ret;
    const AVFilter *buffersrc  = avfilter_get_by_name("buffer");
    const AVFilter *buffersink = avfilter_get_by_name("buffersink");
    AVFilterInOut *outputs = avfilter_inout_alloc();
    AVFilterInOut *inputs  = avfilter_inout_alloc();
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
    AVBufferSinkParams *buffersink_params;

    filter_graph = avfilter_graph_alloc();

    /* buffer video source: the decoded frames from the decoder will be inserted here. */
    QString args = QString("video_size=%1x%2:pix_fmt=%3:time_base=%4/%5:pixel_aspect=%6/%7")
            .arg(m_pCodecContext->width).arg(m_pCodecContext->height).arg(m_pCodecContext->pix_fmt)
            .arg(/*m_pCodecContext->time_base.num*/1).arg(30)
            .arg(/*m_pCodecContext->sample_aspect_ratio.num*/16).arg(/*m_pCodecContext->sample_aspect_ratio.den*/9);

    ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                                       args.toStdString().c_str(), nullptr, filter_graph);
    if (ret < 0) {
        qDebug() << "Cannot create buffer source " << AVErr2QString(ret);
        return ret;
    }

    /* buffer video sink: to terminate the filter chain. */
    buffersink_params = av_buffersink_params_alloc();
    buffersink_params->pixel_fmts = pix_fmts;
    ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
                                       nullptr, buffersink_params, filter_graph);
    av_free(buffersink_params);
    if (ret < 0) {
        qDebug() << "Cannot create buffer sink " << AVErr2QString(ret);
        return ret;
    }

    /* Endpoints for the filter graph. */
    outputs->name       = av_strdup("in");
    outputs->filter_ctx = buffersrc_ctx;
    outputs->pad_idx    = 0;
    outputs->next       = nullptr;

    inputs->name       = av_strdup("out");
    inputs->filter_ctx = buffersink_ctx;
    inputs->pad_idx    = 0;
    inputs->next       = nullptr;

    if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
                                    &inputs, &outputs, nullptr)) < 0)
        return ret;

    if ((ret = avfilter_graph_config(filter_graph, nullptr)) < 0)
        return ret;
    return 0;
} 
    

解碼后的AVFrame應(yīng)用Filter

    if (av_buffersrc_add_frame(buffersrc_ctx, pFrame) < 0) {
        qDebug("Error while add frame.\n");
        return;
    }

    /* pull filtered pictures from the filtergraph */
    int ret = av_buffersink_get_frame(buffersink_ctx, m_pFrameOut);//frameout中的數(shù)據(jù)就是加好濾鏡的內(nèi)容了
    if (ret < 0)
        return;

運(yùn)行效果

綠的QPainter繪制,紅的OpenGL繪制啄清,藍(lán)色AVFilter繪制

運(yùn)行效果

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末六水,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辣卒,更是在濱河造成了極大的恐慌掷贾,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荣茫,死亡現(xiàn)場(chǎng)離奇詭異想帅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)啡莉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門港准,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咧欣,你說我怎么就攤上這事浅缸。” “怎么了魄咕?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵衩椒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)毛萌,這世上最難降的妖魔是什么苟弛? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮朝聋,結(jié)果婚禮上嗡午,老公的妹妹穿的比我還像新娘。我一直安慰自己冀痕,他們只是感情好荔睹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著言蛇,像睡著了一般僻他。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腊尚,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天吨拗,我揣著相機(jī)與錄音,去河邊找鬼婿斥。 笑死劝篷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的民宿。 我是一名探鬼主播娇妓,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼活鹰!你這毒婦竟也來了哈恰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤志群,失蹤者是張志新(化名)和其女友劉穎着绷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锌云,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荠医,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宾抓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片子漩。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖石洗,靈堂內(nèi)的尸體忽然破棺而出幢泼,到底是詐尸還是另有隱情,我是刑警寧澤讲衫,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布缕棵,位于F島的核電站孵班,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏招驴。R本人自食惡果不足惜篙程,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望别厘。 院中可真熱鬧虱饿,春花似錦、人聲如沸触趴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冗懦。三九已至爽冕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間披蕉,已是汗流浹背颈畸。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留没讲,地道東北人眯娱。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爬凑,于是被迫代替她去往敵國(guó)和親困乒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容