本篇博客在上一篇ubuntu GStreamer + QT多媒体播放器开发(二)的基础上主要修改新增以下几点:
(1)log 分为trace、info、debug、warn、error五个级别打印;
(2)mmplayer lib新增MMPlayerPause 接口。
(3)QT(UI)层更改为通过control thread 控制播放、暂停、停止等功能,通过command queue实现异步响应UI 点击事件;
(4)QT 窗口退出时增加资源释放的流程。
(5)优化QT project cmake file.
以下对新增的部分简单进行说明。

1 log分级打印

项目开发过程中,日志系统必不可少。对于c语言项目,平常的练习和简单的开发过程中可以直接用 printf 打印程一些调试信息,但是对于大一点的项目,必须分级打印,这样在开发阶段可以把所以log信息打出来,方便调试,而项目正式上线以后只保留一些关键log,提高程序运行效率,log分级打印要点实现过程如下:
(1)定义log打印级别枚举:

/*log level*/
typedef enum {LOG_LEVEL_OFF,LOG_LEVEL_ERROR,LOG_LEVEL_WARN,LOG_LEVEL_INFO,LOG_LEVEL_DEBUG,LOG_LEVEL_TRACE,LOG_LEVEL_ALL,
} LOG_LEVEL;

(2)宏定义不同等级的log打印函数,根据设置的log_level来判断是否打印该级别的log,打印log的宏函数可以通过printf或g_print(glib 提供的打印函数)实现。

#define LIB_NAME "mmplayer"static LOG_LEVEL log_level;#define LOG_ENTER()  \
do {                            \if (log_level >= LOG_LEVEL_TRACE) {    \g_print("[%s][IN][%s][%d]\n", LIB_NAME, __FUNCTION__, __LINE__); \}  \
} while(0)#define LOG_OUT() \
do {                            \if (log_level >= LOG_LEVEL_TRACE) {    \g_print("[%s][OUT][%s][%d]\n", LIB_NAME, __FUNCTION__, __LINE__);    \}  \
} while(0)#define LOG_DEBUG(format,...) \
do {                            \if (log_level >= LOG_LEVEL_DEBUG) {    \g_print("[%s][DEBUG][%s][%d]"format"", LIB_NAME, __FUNCTION__, __LINE__, ##__VA_ARGS__);   \}  \
} while(0)#define LOG_INFO(format,...)  \
do {                            \if (log_level >= LOG_LEVEL_INFO) { \g_print("[%s][INFO][%s][%d]"format"", LIB_NAME, __FUNCTION__, __LINE__, ##__VA_ARGS__);    \}  \
} while(0)#define LOG_WARN(format,...)  \
do {                            \if (log_level >= LOG_LEVEL_WARN) { \g_print("[%s][WARN][%s][%d]"format"", LIB_NAME, __FUNCTION__, __LINE__, ##__VA_ARGS__);    \}  \
} while(0)#define LOG_ERROR(format,...) \
do {                            \if (log_level >= LOG_LEVEL_ERROR) {    \g_print("[%s][ERROR][%s][%d]"format"", LIB_NAME, __FUNCTION__, __LINE__, ##__VA_ARGS__);   \}  \
} while(0)

(3)初始化的时候设置log级别:

log_level = pstInitParam->logLevel;

印出的log 如下:

[userplayer][INFO][main][38]malloc MEM(0x558bd56563b0)
[mmplayer][OUT][MMPlayerInit][119]
[mmplayer][INFO][handle_element_added][334]elementName uridecodebin3-0.
[mmplayer][INFO][handle_element_added][334]elementName urisourcebin0.
[mmplayer][INFO][handle_element_added][334]elementName source.
[mmplayer][INFO][handle_element_added][334]elementName typefindelement0.
[userplayer][INFO][hanleCallBackEvent][114]recive event type (0)
[userplayer][INFO][hanleCallBackEvent][119]player init OK!
[userplayer][INFO][main][63]recive palyer init OK msg
[userplayer][INFO][_palyer_control_thread][151]control thread enter

2 mmplayer lib新增MMPlayerPause 接口

MMPlayerPause实现暂停播放的功能,主要把pipeline状态设置为GST_STATE_PAUSED,内容比较简单。

int MMPlayerPause(HANDLE_ID hanldeId)
{GstStateChangeReturn stateRet;int ret = 0;LOG_ENTER();if (!mediaHandle || !mediaHandle->pipeline){LOG_ERROR ("handle or pipeline is NULL.\n");ret = -1;goto end;}stateRet = gst_element_set_state (mediaHandle->pipeline, GST_STATE_PAUSED);if (stateRet == GST_STATE_CHANGE_FAILURE) {LOG_ERROR ("Unable to set the pipeline to the pause state.\n");ret = -1;goto end;}LOG_OUT();
end:if (ret != 0){g_main_loop_quit (mediaHandle->main_loop);}return ret;
}

3 UI层通过control thread 控制播放、暂停、停止等功能

设计思路:创建一个command queue,点击UI上的按钮时将对应type的cmd和一些data push到queue中,QT中创建一个control thread ,取出cmd queue中的cmd ,根据cmd type 调用mmplayer lib中接口实现播放控制,这样可以实现异步响应UI 操作,可以提升一些耗时比较长场景的用户体验。
cmd type 枚举定义:

typedef enum {CMD_PLAY,CMD_PAUSE,CMD_RESUME,CMD_STOP,CMD_SEEK,CMD_ALL,
} MM_PLAYER_CMD_TYPE;

cmd queue数据结构的定义:

#define MAX_QUEUE_ESIZE 4
typedef struct _ST_PLAYER_CMD
{MM_PLAYER_CMD_TYPE type;int seekTime;int volumeValue;
} ST_PLAYER_CMD;typedef struct _ST_PLAYER_CMD_QUEUE
{ST_PLAYER_CMD *cmd[MAX_QUEUE_ESIZE];int front;int rear;int length;
} ST_PLAYER_CMD_QUEUE;

cmd queue中存放的是ST_PLAYER_CMD 结构体,该结构体存放的是cmd type和一下user data,如做seek时需要设置seek time等,后期需要其他数据再扩充。
接下来定义操作cmd queue的函数,主要涉及queue 初始化、是否为空、是否已满、push cmd 到queue、从queue 中pop一个cmd、 销毁queue,详细的原理可以参考queue数据结构。

bool cmdQueueInit(ST_PLAYER_CMD_QUEUE **cmdQueue);
bool cmdQueueIsEmpty(ST_PLAYER_CMD_QUEUE *cmdQueue);
bool cmdQueueIsFull(ST_PLAYER_CMD_QUEUE *cmdQueue);
bool cmdQueuePush(ST_PLAYER_CMD_QUEUE *cmdQueue);
ST_PLAYER_CMD *cmdQueuePop(ST_PLAYER_CMD_QUEUE *cmdQueue);
bool cmdQueueDeInit(ST_PLAYER_CMD_QUEUE *cmdQueue);
bool createCmdAndPush(ST_USER_HANDLE *userHandle, MM_PLAYER_CMD_TYPE type, void *data);

初始化queque,注意传入的参数是cmd queue指针的指针:

bool cmdQueueInit(ST_PLAYER_CMD_QUEUE **cmdQueue)
{bool ret = false;ST_PLAYER_CMD_QUEUE *retQueue = NULL;if (NULL == cmdQueue){LOG_ERROR("cmd queue is NULL\n");goto end;}retQueue = (ST_PLAYER_CMD_QUEUE *)g_malloc(sizeof(ST_PLAYER_CMD_QUEUE));if (retQueue == NULL){LOG_ERROR("malloc cmd queue error\n");goto end;}memset(retQueue->cmd, 0, sizeof(retQueue->cmd));retQueue->rear = retQueue->front = 0;retQueue->length = 0;*cmdQueue = retQueue;ret = true;
end:return ret;
}

判断queue是否为空:

bool cmdQueueIsEmpty(ST_PLAYER_CMD_QUEUE *cmdQueue)
{bool ret = false;if (NULL == cmdQueue){LOG_ERROR("cmd queue is NULL\n");goto end;}ret = (cmdQueue->rear == cmdQueue->front ? true : false);end:return ret;
}

判断queue是否已满:

bool cmdQueueIsFull(ST_PLAYER_CMD_QUEUE *cmdQueue)
{bool ret = false;if (NULL == cmdQueue){LOG_ERROR("cmd queue is NULL\n");goto end;}ret = ((cmdQueue->rear + 1) % MAX_QUEUE_ESIZE == cmdQueue->front ? true : false);end:return ret;
}

向queue中push 一个cmd:

bool cmdQueuePush(ST_PLAYER_CMD_QUEUE *cmdQueue, ST_PLAYER_CMD *cmd)
{bool ret = false;if (NULL == cmdQueue || NULL == cmd){LOG_ERROR("cmd queue is NULL\n");goto end;}if (cmdQueueIsFull(cmdQueue)){LOG_ERROR("cmd queue is full, throw cmd (%d)\n", cmd->type);goto end;}cmdQueue->cmd[cmdQueue->rear] = cmd;cmdQueue->rear = (cmdQueue->rear + 1) % MAX_QUEUE_ESIZE;ret = true;LOG_INFO ("push cmd(%d) to queue success\n", cmd->type);end:return ret;
}

从queue中取出一个cmd:

ST_PLAYER_CMD *cmdQueuePop(ST_PLAYER_CMD_QUEUE *cmdQueue)
{ST_PLAYER_CMD *ret = NULL;if (NULL == cmdQueue){LOG_ERROR("cmd queue is NULL\n");goto end;}if (cmdQueue->rear == cmdQueue->front){LOG_ERROR("cmd queue is empty\n");goto end;}ret = cmdQueue->cmd[cmdQueue->front];cmdQueue->cmd[cmdQueue->front] = NULL;cmdQueue->front = (cmdQueue->front + 1) % MAX_QUEUE_ESIZE;end:return ret;
}

销毁queue:

bool cmdQueueDeInit(ST_PLAYER_CMD_QUEUE *cmdQueue)
{bool ret = false;int i = 0;if (NULL == cmdQueue){LOG_ERROR("cmd queue is NULL\n");goto end;}LOG_INFO("start free cmd\n");for (i = 0; i < MAX_QUEUE_ESIZE; i++){if (cmdQueue->cmd[i]){LOG_INFO ("free cmd(%p)\n", cmdQueue->cmd[i]);g_free(cmdQueue->cmd[i]);cmdQueue->cmd[i] = NULL;}}cmdQueue->rear = cmdQueue->front = 0;cmdQueue->length = 0;ret = true;
end:return ret;
}

UI 向cmd queue中写cmd时不直接调用cmdQueuePop函数,而是基于该函数封装一个createCmdAndPush函数,这样为了扩充后期复杂的cmd,如seek、倍速播放。

bool createCmdAndPush(ST_USER_HANDLE *userHandle, MM_PLAYER_CMD_TYPE type, void *data)
{bool ret = false;ST_PLAYER_CMD *cmd = NULL;if (NULL == userHandle || type > CMD_ALL){LOG_ERROR("invalid param\n");goto end;}cmd = (ST_PLAYER_CMD *)g_malloc(sizeof(ST_PLAYER_CMD));if (cmd == NULL){LOG_ERROR("malloc cmd error\n");goto end;}cmd->type = type;switch (type){case CMD_PLAY:{break;}case CMD_PAUSE:{break;}case CMD_RESUME:{break;}case CMD_STOP:{break;}case CMD_SEEK:{break;}default:{break;}}g_mutex_lock(&userHandle->queueMutex);ret = cmdQueuePush(userHandle->cmdQueue, cmd);g_cond_signal(&userHandle->queueCond);g_mutex_unlock(&userHandle->queueMutex);end:if (ret == false && cmd != NULL){//need free cmd when errorg_free(cmd);cmd = NULL;ret = false;}return ret;
}

需要注意的是createCmdAndPush函数中向queue中写cmd时必须使用mutex,再配合信号量使用。
UI按钮点击时要push cmd到queue中调用createCmdAndPush函数即可。

void PlayerWindow::onPlayClicked() {bool ret = false;MM_PLAYER_CMD_TYPE type = CMD_PLAY;if (NULL == pstHandle){LOG_ERROR("pst is NULL\n");goto end;}ret = createCmdAndPush(pstHandle, type, NULL);if (!ret){LOG_ERROR("start play error\n");}end:LOG_OUT();
}

以上过程实现cmd 的push,main函数中需要初始化queue以及cmd queue使用的mutex和cond,并创建control thread取出queue中的cmd:

    //init cmd queueg_mutex_init(&userHandle->queueMutex);g_cond_init(&userHandle->queueCond);cmdQueueInit(&userHandle->cmdQueue);userHandle->controlThread = g_thread_new("control_thread", _palyer_control_thread, userHandle);if (!userHandle->controlThread){LOG_ERROR ("create control thread fail.\n");}

_palyer_control_thread的内容如下,取出cmd queue并调用mmplayer lib中的接口,这样一个异步操作就实现了,最后thread 退出时记得销毁cmd queue和queue使用的mutex 和cond。

void *_palyer_control_thread(void* Parameter)
{ST_USER_HANDLE *pstUserHandle = (ST_USER_HANDLE *)Parameter;ST_PLAYER_CMD *curCmd = NULL;if (NULL == pstUserHandle){LOG_ERROR ("handle is NULL.\n");}LOG_INFO("control thread enter\n");while(pstUserHandle->handleStatus < ERROR_STATUS){while (!cmdQueueIsEmpty(pstUserHandle->cmdQueue)){LOG_INFO("queue not empty\n");g_mutex_lock(&pstUserHandle->queueMutex);curCmd = cmdQueuePop(pstUserHandle->cmdQueue);g_mutex_unlock(&pstUserHandle->queueMutex);if (curCmd == NULL){LOG_INFO("cmd is empty\n");break;}switch (curCmd->type){case CMD_PLAY:{MMPlayerPlay(pstUserHandle->handleId);break;}case CMD_PAUSE:{MMPlayerPause(pstUserHandle->handleId);break;}case CMD_RESUME:{break;}case CMD_STOP:{MMPlayerStop(pstUserHandle->handleId);break;}case CMD_SEEK:{break;}default:break;}//need free cmd hereif (curCmd){g_free(curCmd);}}if (cmdQueueIsEmpty(pstUserHandle->cmdQueue)){//wait new cmd push in cmd queueLOG_INFO("start wait new cmd...\n");g_mutex_lock(&pstUserHandle->queueMutex);g_cond_wait(&pstUserHandle->queueCond, &pstUserHandle->queueMutex);g_mutex_unlock(&pstUserHandle->queueMutex);LOG_INFO("wait new cmd done\n");}}LOG_INFO("quit control thread\n");cmdQueueDeInit(pstUserHandle->cmdQueue);//control thead stop free cmd queueif (pstUserHandle->cmdQueue){g_free(pstUserHandle->cmdQueue);pstUserHandle->cmdQueue = NULL;}g_mutex_clear(&pstUserHandle->queueMutex);g_cond_clear(&pstUserHandle->queueCond);g_thread_unref(pstUserHandle->controlThread);pstUserHandle->controlThread = NULL;//emit quit signalg_mutex_lock(&userHandle->quitMutex);g_cond_signal(&userHandle->quitCond);g_mutex_unlock(&userHandle->quitMutex);}

4 QT 窗口退出时增加资源释放的流程

程序中创建了一个player thread ,一个control thread以及一些handle 资源,点击QT 窗口退出时必须保证两个thread都退出,申请的资源都free调,否则会造成内存泄漏。资源释放流程设计如下
(1)关闭窗口时会触发closeEvent,在closeEvent 中push 一个stop cmd :

void PlayerWindow::closeEvent(QCloseEvent *event)
{bool ret = false;MM_PLAYER_CMD_TYPE type = CMD_STOP;if (NULL == pstHandle){LOG_ERROR("pst is NULL\n");goto end;}ret = createCmdAndPush(pstHandle, type, NULL);...
}

(2)control thread 中取出stop cmd后调用MMPlayerStop函数,MMPlayerStop函数调用后退出main loop,退出paly thread ,释放mmplayer lib中的相关资源,释放完毕后向UI层返回PLAYER_STOP_OK event

    mediaHandle->handleInfo.handleStatus = STOP_STATUS;if (mediaHandle->hanlecallBackFn){(mediaHandle->hanlecallBackFn)(PLAYER_STOP_OK ,(void *)&mediaHandle->handleInfo);}

(3)UI收到PLAYER_STOP_OK event后将handleStatus 设置为ERROR_STATUS,并调用g_cond_signal,让control thread 退出。

        case PLAYER_STOP_OK:{LOG_INFO("pipeline deinit OK! \n");userHandle->handleStatus = ERROR_STATUS;g_mutex_lock(&userHandle->queueMutex);g_cond_signal(&userHandle->queueCond);g_mutex_unlock(&userHandle->queueMutex);break;}

(4)为了让closeEvent等待所有资源释放完毕,使用了条件变量,closeEvent中发送完stop cmd 后开始等待条件变量,control thread 退出后发送信号量,让closeEvent结束等待。
closeEvent:

    ret = createCmdAndPush(pstHandle, type, NULL);if (!ret){LOG_ERROR("start play error\n");}g_mutex_lock(&pstHandle->quitMutex);g_cond_wait(&pstHandle->quitCond, &pstHandle->quitMutex);g_mutex_unlock(&pstHandle->quitMutex);

control thread:

    //emit quit signalg_mutex_lock(&userHandle->quitMutex);g_cond_signal(&userHandle->quitCond);g_mutex_unlock(&userHandle->quitMutex);

这样就可以安全释放所用申请的资料了。

5 优化QT project cmake file

项目编译的时候先进player_lib中执行make 编译mmplayer lib,编译后会自动把lib的so和头文件copy到QT project 目录。QT project 使用了cmake编译,cmake文件如下:

cmake_minimum_required(VERSION 3.5)
project(gst_player LANGUAGES CXX C)set(CMAKE_INCLUDE_CURRENT_DIR ON)set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_PROJECT_NAME gst_player)set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)find_package(PkgConfig REQUIRED)
pkg_check_modules(gstreamer REQUIRED IMPORTED_TARGET gstreamer-1.0)
pkg_check_modules(gstreamer-video REQUIRED IMPORTED_TARGET gstreamer-video-1.0)
pkg_check_modules(gobject REQUIRED IMPORTED_TARGET gobject-2.0)
pkg_check_modules(glib REQUIRED IMPORTED_TARGET glib-2.0)# shell中编译打开以下注释,Qt5_DIR设置为QT安装路径
#set(Qt5_DIR "/home/zhy/Qt5.13.2/5.13.2/gcc_64/lib/cmake/Qt5")
#find_package (Qt5Widgets)
#find_package (Qt5Core)
#find_package (Qt5Gui)# qt create中编译,shell下编译需注释
find_package(Qt5 REQUIRED COMPONENTS Widgets )include_directories(${PROJECT_SOURCE_DIR}/common/)
include_directories(${PROJECT_SOURCE_DIR}/libs/)
link_directories(${PROJECT_SOURCE_DIR}/libs/)
find_library (libpath mmplayer ${PROJECT_SOURCE_DIR}/libs/)if (${libpath} STREQUAL "libpath-NOTFOUND")message (STATUS "required mmplayer library but not found!")
else()message (STATUS "libpath library found in ${libpath}")
endif()add_executable(gst_playermain.cppplayerwindow.cppplayerwindow.hcommon/common.hcommon/common.cpplibs/mediaPlayer.h
)target_link_libraries(gst_player PRIVATE Qt5::Widgets PkgConfig::gstreamer PkgConfig::gstreamer-video PkgConfig::gobject PkgConfig::glib mmplayer)

项目github地址:https://github.com/zhenghaiyang123/gst_player.git,本篇博客对应的tag为v0.3。

ubuntu GStreamer + QT多媒体播放器开发(三)相关推荐

  1. 04 Qt音视频多媒体播放器开发,Qt6.2.3 multimedia

    Q6.2.3 multimedia 音视频多媒体播放器开发 步骤: Qt6.2版本中已经有了播放器的Demo,亲测可用.先下载Qt并安装Qt6.2.3,或者更新到Qt6.2.3,直接在欢迎页找到Med ...

  2. java 媒体播放器_Java多媒体播放器(三)

    No.3 vlcj 一.简介 The vlcj project first gives you Java bindings to just about all of the native functi ...

  3. Qt应用程序开发三:写日志文件

    开发过程中写日志文件是必不可少 Qt中自带qInstallMessageHandler来进行日志的收集 使用方法 第一步:设置qInstallMessageHandler的回调函数: #include ...

  4. 基于QT开发的多媒体播放器

    基于QT开发的多媒体播放器 描述: 使用QT软件设计一个多媒体播放器,实现视频及音乐的播放,暂停,快进,快退,音量的调整. 歌词的实时显示和视频全屏播放等基本功能. 功能: 1.音乐系统:提供播放.暂 ...

  5. Qt+MPlayer音乐播放器开发笔记(二):交叉编译MPlayer以及部署到开发板播放演示

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/119991329 长期持续带来更多项目与技术分享,咨询请 ...

  6. Ubuntu 安装 Qt 开发环境 简单实现

    2019独角兽企业重金招聘Python工程师标准>>> Ubuntu 安装 Qt 开发环境 简单实现是本文要介绍的内容,内容很短,取其精华,详细介绍Qt 类库的说明,先来看内容. 一 ...

  7. Ubuntu 安装 Qt 开发环境(转)

    Ubuntu 安装 Qt 开发环境 简单实现是本文要介绍的内容,内容很短,取其精华,详细介绍Qt 类库的说明,先来看内容. (转http://mobile.51cto.com/symbian-2718 ...

  8. Qt开发技术:Qt富文本(三)Qt支持的HTML子集(查询手册)以及涉及的类

    若该文为原创文章,未经允许不得转载 原博主博客地址:https://blog.csdn.net/qq21497936 原博主博客导航:https://blog.csdn.net/qq21497936/ ...

  9. PX4-AutoPilot教程-0-使用VMware虚拟机安装Ubuntu系统并搭建PX4开发环境(ROS+mavros+jMAVSim+gazebo+QGC+QT)

    使用VMware虚拟机安装Ubuntu系统并搭建PX4开发环境 本教程使用VMware虚拟机安装Ubuntu18.04系统(官方推荐使用版本),搭建PX4固件版本为v1.9.2,飞控板为pixhawk ...

  10. 【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发

    一.前言 本博客将讲解如何用Qt+OpenCV开发一款图片查看器的Windows应用程序,其实不用OpenCV也能开发出这类软件,作者目的是为了学习Qt+OpenCV开发项目,所以会使用OpenCV, ...

最新文章

  1. mysql text index_MySQL 全文索引(fulltext index)
  2. POJ2402+模拟
  3. [NOTE] WebGoat v8.2.2学习笔记
  4. UVA - 11882Biggest Number dfs+期望剪枝
  5. 当我们在谈论cpu指令乱序的时候,究竟在谈论什么?
  6. 在给定约束下可以使用a,b和c形成的字符串数
  7. windows商店_Windows记事本应用现在可以从Microsoft Store中获得
  8. 斐波纳契回调线_斐波那契回调线(黄金分割线)神级操作-经典
  9. html文档不是本地电脑,电脑浏览器打不开本地html文件
  10. Good Bye 2017
  11. css3制作俩面翻转盒子效果
  12. 【剑指offer】29、顺时针打印矩阵
  13. UVA 679 小球掉落 思维 + 数据结构
  14. 【博客427】通过redfish协议操控服务器
  15. 史上最全 | 编程入门指南
  16. 线性代数学习笔记——第三十二讲——向量混合积的概念与性质
  17. 程序猿,你关心过自己的健康吗?
  18. 博客园自定义背景图片
  19. 【新手教程】51Sim-One Cloud 2.0 构建标准案例2.0场景
  20. 历年美元对人民币汇率表

热门文章

  1. 计算机毕业设计Java自行车在线租赁管理系统2021(源码+系统+mysql数据库+Lw文档)
  2. Latex beamer 常用操作记录
  3. 特征值篇2——特征子空间
  4. 人到中年,没事多休息,有空多赚钱!
  5. 计算机音量程序是哪个键,电脑如何设置音量快捷键
  6. 学计算机物理去戴维斯还是伦斯勒理工学院好,为什么伦斯勒理工学院评价那么高?...
  7. NLP - 词法分析
  8. Electron 创建任务栏图标以及任务栏图标右键菜单
  9. my opencv voyage
  10. 澳门科技大学计算机专业研究生,澳门科技大学 计算机专业