ubuntu GStreamer + QT多媒体播放器开发(四)

本篇博客在上一篇ubuntu GStreamer + QT多媒体播放器开发(三)的基础上主要修改新增以下几点:
(1)播放过程中实时更新进度条以及播放时间。
(2)拖动进度条实现seek功能。
(3)QT UI替换为正点原子的videopalyer 样式布局。

1 播放过程中实时更新进度条以及播放时间

播放过程中实时更新进度条以及播放时间需要新增获取档案duration 和position 的接口,在mmplayer lib中新增两个接口。
MMPlayerGetDuration接口用以获取档案duration,接口里最终调用gstreamer 中gst_element_query_duration函数。

int MMPlayerGetDuration(HANDLE_ID hanldeId, gint64 *duration)
{
    
    
    LOG_ENTER();
    int ret = 0;

    //find handle
    if (hanldeId != mediaHandle->handleId)
    {
    
    
        LOG_ERROR ("user hanlde id (%d) do not match media handle id(%d).\n", hanldeId, mediaHandle->handleId);
        ret = -1;
        goto end;
    }

    if (!gst_element_query_duration (mediaHandle->pipeline, GST_FORMAT_TIME, duration))
    {
    
    
        LOG_ERROR ("get media duration fail.\n");
        ret = -1;
    }

    LOG_INFO ("get media duration = %d.\n", *duration);
    

end:
    LOG_OUT();
    return ret;
}

MMPlayerGetPostion接口用以获取档案当前的播放位置,接口里最终调用gstreamer 中gst_element_query_position函数。

int MMPlayerGetPostion(HANDLE_ID hanldeId, gint64 *postion)
{
    
    
    //LOG_ENTER();
    int ret = 0;
    
    //find handle
    if (hanldeId != mediaHandle->handleId)
    {
    
    
        LOG_ERROR ("user hanlde id (%d) do not match media handle id(%d).\n", hanldeId, mediaHandle->handleId);
        ret = -1;
        goto end;
    }

    if (!gst_element_query_position (mediaHandle->pipeline, GST_FORMAT_TIME, postion))
    {
    
    
        LOG_ERROR ("get media posion fail.\n");
        ret = -1;
    }

     LOG_INFO ("get media postion = %d.\n", postion);
    

end:
    //LOG_OUT();
    return ret;
}

实时更新的话借助于QT QTimer,pipeline 处于playing状态时每1s调用一次MMPlayerGetPostion接口,更新进度条的和时间显示label的值。

    timer = new QTimer();
    connect(timer, SIGNAL(timeout()),
            this, SLOT(refreshSlider()));
void PlayerWindow::refreshSlider() {
    
    
    gint64 current = GST_CLOCK_TIME_NONE;
    QString mediaDuration;
    int second = 0;
    int minute = 0;
    QString mediaPosition;


    LOG_INFO("IN \n");
    if (pstHandle->handleStatus == PLAYING_STATUS)
    {
    
    
        if (!GST_CLOCK_TIME_IS_VALID(totalDuration) || !totalDuration) {
    
    
            if (MMPlayerGetDuration(pstHandle->handleId,&totalDuration) == 0) {
    
    
                durationSlider->setRange(0, totalDuration/GST_SECOND);
                mediaDuration.clear();
                second  = totalDuration / GST_SECOND;
                minute = second / 60;

                if (minute >= 10)
                    mediaDuration = QString::number(minute, 10);
                else
                    mediaDuration = "0" + QString::number(minute, 10);

                if (second >= 10)
                    mediaDuration = mediaDuration
                            + ":" + QString::number(second, 10);
                else
                    mediaDuration = mediaDuration
                            + ":0" + QString::number(second, 10);

                label[1]->setText("/" + mediaDuration);
            }
        }

        if (MMPlayerGetPostion (pstHandle->handleId, &current) == 0) {
    
    
            LOG_INFO("%ld / %ld\n", current/GST_SECOND, totalDuration/GST_SECOND);
            durationSlider->setValue(current/GST_SECOND);
            LOG_INFO("get current %d\n", current/GST_SECOND);
            /* 显示现在播放的时间 */
            mediaPosition.clear();
            second  = current / GST_SECOND;
            minute = second / 60;

            if (minute >= 10)
                mediaPosition = QString::number(minute, 10);
            else
                mediaPosition = "0" + QString::number(minute, 10);

            if (second >= 10)
                mediaPosition = mediaPosition
                        + ":" + QString::number(second, 10);
            else
                mediaPosition = mediaPosition
                        + ":0" + QString::number(second, 10);
            label[0]->setText(mediaPosition);
        }
    }
}

2 拖动进度条实现seek功能

实现seek先封装MMPlayerDoSeek 接口:

int MMPlayerDoSeek(HANDLE_ID hanldeId, gint64 seekPos)
{
    
    
    LOG_ENTER();
    int ret = 0;

    //find handle
    if (hanldeId != mediaHandle->handleId)
    {
    
    
        LOG_ERROR ("user hanlde id (%d) do not match media handle id(%d).\n", hanldeId, mediaHandle->handleId);
        ret = -1;
        goto end;
    }

    ret = gst_element_seek_simple (mediaHandle->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH ,
                  seekPos);
end:
    LOG_OUT();
    return ret;
}

UI 层连接slider的信号与槽即可:

    connect(durationSlider, SIGNAL(sliderReleased()),
            this, SLOT(durationSliderReleased()));

槽函数中调用MMPlayerDoSeek接口:

void PlayerWindow::durationSliderReleased()
{
    
    
    gint64 current = GST_CLOCK_TIME_NONE;

    timer->stop();
    gint64 pos = durationSlider->sliderPosition();
    LOG_INFO("seek to: %ld\n", pos);
    MMPlayerDoSeek (pstHandle->handleId, pos * GST_SECOND);
    timer->start(1000);
}

3 正点原子QT UI 替换

之前做的UI丑爆了,偶然间发现正点原子的嵌入式liunx example里有一个videoplayer,UI 使用了qss 布局,效果比较好看。正点原子QT 代码下载地址:

https://alientek-linux.coding.net/public/imx6ull/01_Soure_Code/git/files

videoplayer 源码在“ 12、Qt开发指南例程源码/Embedded-Qt-Tutorial/Qt/02/15_videoplayer“ 目录,正点原子原本运行效果:

在这里插入图片描述
UI挺好看的,但是底下的按钮栏覆盖在video区域了,所以看起来video上下没有居中播放,暂时不管,先移植过来再说。
正点原子这个UI 使用了很多小图片作为按钮,QT中需要使用图片资源和qss 时需要添加resource,添加方式自行百度一下。添加完成后一定要在cmake中add resource,否则会找不到这些图片:

qt5_add_resources(qrc_FILES res.qrc)

add_executable(gst_player
  main.cpp
  playerwindow.cpp
  playerwindow.h
  common/common.h
  common/common.cpp
  libs/mediaPlayer.h
  ${qrc_FILES}
)

这样 qss 中通过border-image将按钮背景设置为图片:

QPushButton#btn_play {
    
    
    border-image:url(:/icons/btn_play1.png);
}

QPushButton#btn_play:hover {
    
    
    border-image:url(:/icons/btn_play2.png);
}

QPushButton#btn_play:checked {
    
    
    border-image:url(:/icons/btn_pause1.png);
}

QPushButton#btn_play:checked:hover {
    
    
    border-image:url(:/icons/btn_pause2.png);
}

最后在QT main函数中将qss 加载进来:

    /*load qss file*/
    QFile file(":/style.qss");

    /* 判断文件是否存在 */
    if (file.exists() ) {
    
    
        LOG_INFO("find qss file\n");
        /* 以只读的方式打开 */
        file.open(QFile::ReadOnly);
        /* 以字符串的方式保存读出的结果 */
        QString styleSheet = QLatin1String(file.readAll());
        /* 设置全局样式 */
        qApp->setStyleSheet(styleSheet);
        /* 关闭文件 */
        file.close();
    }
    else
    {
    
    
        LOG_INFO("not find qss file\n");
    }

移植的时候顺便把那个看起来video上下没有居中播放的问题修复了一下,主要改下布局,改布局基本没啥可说的,需要注意的是QMainWindow类不能使用QHBoxLayout和QVBoxLayout,需要新建一个QWidget类mainWidget,调用setCentralWidget(mainWidget),把 mainWidget 放在QMainWindow 中心区域,在mainWidget 上setLayout,这样就可以在QMainWindow中实现布局了。

void PlayerWindow::videoLayout()
{
    
    
    /* 设置位置与大小,这里固定为800, 480 */
    //this->setGeometry(0, 0, 800, 480);
    //this->setMinimumSize(800, 480);
    //this->setMaximumSize(800, 480);
    QPalette pal;
    pal.setColor(QPalette::WindowText, Qt::white);

    for (int i = 0; i < 3; i++) {
    
    
        /* 水平容器 */
        hWidget[i] = new QWidget();
        hWidget[i]->setAutoFillBackground(true);
        /* 水平布局 */
        hBoxLayout[i] = new QHBoxLayout();
    }

    for (int i = 0; i < 2; i++) {
    
    
        /* 垂直容器 */
        vWidget[i] = new QWidget();
       // vWidget[i]->setAutoFillBackground(true);
        /* 垂直布局 */
        //vBoxLayout[i] = new QVBoxLayout();
    }
    vBoxLayout[0] = new QVBoxLayout();
    for (int i = 0; i < 2; i++) {
    
    
        label[i] = new QLabel();
    }

    for (int i = 0; i < 6; i++) {
    
    
        pushButton[i] = new QPushButton();
        pushButton[i]->setMaximumSize(44, 44);
        pushButton[i]->setMinimumSize(44, 44);
    }
    QWidget *mainWidget = new QWidget();
    /* 设置 */
    vWidget[0]->setObjectName("vWidget0");
    vWidget[1]->setObjectName("vWidget1");
    hWidget[1]->setObjectName("hWidget1");
    hWidget[2]->setObjectName("hWidget2");
    pushButton[0]->setObjectName("btn_play");
    pushButton[1]->setObjectName("btn_previous");
    pushButton[2]->setObjectName("btn_next");
    pushButton[3]->setObjectName("btn_volumedown");
    pushButton[4]->setObjectName("btn_volumeup");
    pushButton[5]->setObjectName("btn_screen");


    QFont font;

    font.setPixelSize(18);
    label[0]->setFont(font);
    label[1]->setFont(font);

    pal.setColor(QPalette::WindowText, Qt::white);
    label[0]->setPalette(pal);
    label[1]->setPalette(pal);

    label[0]->setText("00:00");
    label[1]->setText("/00:00");

    durationSlider = new QSlider(Qt::Horizontal);
    durationSlider->setMaximumHeight(15);
    durationSlider->setObjectName("durationSlider");

    volumeSlider = new QSlider(Qt::Horizontal);
    volumeSlider->setRange(0, 100);
    volumeSlider->setMaximumWidth(80);
    volumeSlider->setObjectName("volumeSlider");
    volumeSlider->setValue(50);

    listWidget = new QListWidget();
    listWidget->setObjectName("listWidget");
    listWidget->setVerticalScrollBarPolicy(
                Qt::ScrollBarAlwaysOff);
    listWidget->setHorizontalScrollBarPolicy(
                Qt::ScrollBarAlwaysOff);
    listWidget->setFocusPolicy(Qt::NoFocus);
    listWidget->setMinimumSize(200, 480);
    listWidget->setMaximumWidth(200);

    videoWidget = new QWidget();

    videoWidget->setStyleSheet("border-image: none;"
                               "background: transparent;"
                               "border:none");

    /* H0布局 */
    vWidget[0]->setMinimumSize(200, 480);
    vWidget[0]->setMaximumWidth(200);
    videoWidget->setMinimumSize(200, 480);
    videoWidget->setObjectName("videoLayer");


    hBoxLayout[0]->addWidget(videoWidget);
    hBoxLayout[0]->addWidget(listWidget);

    //hWidget[0]->setLayout(hBoxLayout[0]);

    setCentralWidget(mainWidget);

    vBoxLayout[0] -> addLayout(hBoxLayout[0]);

    /* V0布局 */


    /* V1布局 */
    /* 底板部件布局 */
    //hWidget[1]->setMaximumHeight(15);
    //hWidget[2]->setMinimumHeight(65);

    /* H1布局 进度条栏*/
    hBoxLayout[1]->addWidget(durationSlider);
    hBoxLayout[1]->setContentsMargins(0, 0, 0, 0);
    hWidget[1]->setFixedHeight(20);
    hWidget[1]->setLayout(hBoxLayout[1]);
    //vBoxLayout[0] -> addLayout(hBoxLayout[1]);

    /* H2布局 底部按钮栏*/
    QSpacerItem *hSpacer0 = new
            QSpacerItem(300, 80,
                        QSizePolicy::Expanding,
                        QSizePolicy::Maximum);

    hBoxLayout[2]->addSpacing(20);
    hBoxLayout[2]->addWidget(pushButton[0]);
    hBoxLayout[2]->addSpacing(10);
    hBoxLayout[2]->addWidget(pushButton[1]);
    hBoxLayout[2]->addWidget(pushButton[2]);
    hBoxLayout[2]->addSpacing(10);
    hBoxLayout[2]->addWidget(pushButton[3]);
    hBoxLayout[2]->addWidget(volumeSlider);
    hBoxLayout[2]->addWidget(pushButton[4]);
    hBoxLayout[2]->addWidget(label[0]);
    hBoxLayout[2]->addWidget(label[1]);
    hBoxLayout[2]->addSpacerItem(hSpacer0);
    hBoxLayout[2]->addWidget(pushButton[5]);
    hBoxLayout[2]->addSpacing(20);
    hBoxLayout[2]->setContentsMargins(0, 0, 0, 0);
    hBoxLayout[2]->setAlignment(Qt::AlignLeft | Qt::AlignTop);



    hWidget[2]->setLayout(hBoxLayout[2]);
    hWidget[2]->setFixedHeight(50);

    vBoxLayout[0]->addWidget(hWidget[1]);
    vBoxLayout[0]->addWidget(hWidget[2]);
    //vBoxLayout[0]->addLayout(hBoxLayout[2]);

    mainWidget->setLayout(vBoxLayout[0]);

}

最终实现效果如下:
在这里插入图片描述

在这里插入图片描述
代码github地址:https://github.com/zhenghaiyang123/gst_player.git, 本篇博客对应 v0.4 tag。
后续需要实现的功能,本系列博客近期不会继续更新,先去研究一下硬件解码和gstreamer集成:
(1)右侧播放列表初始化,点击播放列表切换播放内容
(2)上一首和下一首切换功能
(3)音量调节功能
最后说一下,之所以选择gstreamer作为播放框架,是因为很多芯片厂商的硬件解码都是和gstreamer集成,搞嵌入式音视频开发必须要使用芯片提供的硬件解码功能,否则软件解码太占用CPU资源。

猜你喜欢

转载自blog.csdn.net/qq_38694388/article/details/125137651