本篇博客在上一篇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, ¤t) == 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资源。