jjzjj

系统编程之实战小项目-利用LVGL 与 mplayer制作音频播放器

阿熊不凶 2023-08-04 原文

设计目标:利用LVGL 与 mplayer制作音频播放器

功能描述:

1.实现基本的音乐播放器功能 暂停,播放,音量调节 ,音乐切换

2.实现播放列表

3.实现进度条控制音乐

设计方案

基于lvgl9.0库进行设计播放器ui,播放列表,进度条,按钮,利用多线程进行调用mplayer播放器进行音乐暂停,播放,音量调节 ,音乐切换。

系统框架

界面设计:(ui没花太多时间设计,丑勿喷hh)

 

 

 

实现过程

主界面设置:

// ==========主界面 ================

void my_main(void)
{

   //lv_obj_add_state(currentButton, LV_STATE_CHECKED);//向对象添加一个或多个状态。
    lv_obj_t * label;
    lv_obj_t * btn1 = lv_btn_create(lv_scr_act());
    lv_obj_add_event_cb(btn1, event_handler_up, LV_EVENT_CLICKED,"last");

    lv_obj_align(btn1, LV_ALIGN_BOTTOM_MID,-300, 0);
    label = lv_label_create(btn1);
    lv_label_set_text(label, "last");
    lv_obj_center(label);
    lv_obj_t *wplast = lv_img_create(label);
    lv_img_set_src(wplast,"S:/demo/lvgl_img/last.jpg");

    lv_obj_t * btn2 = lv_btn_create(lv_scr_act());
    lv_obj_add_event_cb(btn2, event_handler_dn, LV_EVENT_CLICKED,"next");
    lv_obj_align(btn2, LV_ALIGN_BOTTOM_MID,300, 0);
    label = lv_label_create(btn2);
    lv_label_set_text(label, "next");
    lv_obj_center(label);
    lv_obj_t *wpnext = lv_img_create(label);
    lv_img_set_src(wpnext,"S:/demo/lvgl_img/next.jpg");

    lv_obj_t * btn_start = lv_btn_create(lv_scr_act());
    lv_obj_add_event_cb(btn_start, event_handler_play, LV_EVENT_CLICKED,"play");
    lv_obj_align(btn_start, LV_ALIGN_BOTTOM_MID, 0, 0);
    label = lv_label_create(btn_start);
    lv_label_set_text(label, "play");
    lv_obj_center(label);   
    lv_obj_t *wpplay = lv_img_create(label);
    lv_img_set_src(wpplay,"S:/demo/lvgl_img/play.jpg");

    lv_obj_t * btn_stop = lv_btn_create(lv_scr_act());
    lv_obj_add_event_cb(btn_stop, event_handler_play, LV_EVENT_CLICKED,"stop");
    lv_obj_align(btn_stop, LV_ALIGN_BOTTOM_MID, 0, -60);
    label = lv_label_create(btn_stop);
    lv_label_set_text(label, "stop");
    lv_obj_center(label);   
    lv_obj_t *wpstop = lv_img_create(label);
    lv_img_set_src(wpstop,"S:/demo/lvgl_img/stop.jpg");

    lv_obj_t * btn_fast = lv_btn_create(lv_scr_act());
    lv_obj_add_event_cb(btn_fast, event_handler_play, LV_EVENT_CLICKED,"fast");
    lv_obj_align(btn_fast, LV_ALIGN_BOTTOM_MID,200, 0);
    label = lv_label_create(btn_fast);
    lv_label_set_text(label, "fast");
    lv_obj_center(label); 
    lv_obj_t *wpfast = lv_img_create(label);
    lv_img_set_src(wpfast,"S:/demo/lvgl_img/fast.jpg");    

    lv_obj_t * btn_slow = lv_btn_create(lv_scr_act());
    lv_obj_add_event_cb(btn_slow, event_handler_play, LV_EVENT_CLICKED,"slow");
    lv_obj_align(btn_slow, LV_ALIGN_BOTTOM_MID,-200, 0);
    label = lv_label_create(btn_slow);
    lv_label_set_text(label, "slow");
    lv_obj_center(label); 
    lv_obj_t *wpslow = lv_img_create(label);
    lv_img_set_src(wpslow,"S:/demo/lvgl_img/slow.jpg");    

    lv_obj_t * btn_control = lv_btn_create(lv_scr_act());
    lv_obj_add_event_cb(btn_control, event_handler_play, LV_EVENT_CLICKED,"control+");
    lv_obj_align(btn_control, LV_ALIGN_BOTTOM_LEFT,0, -250);
    label = lv_label_create(btn_control);
    lv_label_set_text(label, "control+");
    lv_obj_center(label); 
    lv_obj_t *wpadd = lv_img_create(label);
    lv_img_set_src(wpadd,"S:/demo/lvgl_img/add.jpg");

    lv_obj_t * btn_control1 = lv_btn_create(lv_scr_act());
    lv_obj_add_event_cb(btn_control1, event_handler_play, LV_EVENT_CLICKED,"control-");
    lv_obj_align(btn_control1, LV_ALIGN_BOTTOM_LEFT,0, -180);
    label = lv_label_create(btn_control1);
            lv_label_set_text(label, "control-");
    lv_obj_center(label); 
    lv_obj_t *wpredu = lv_img_create(label);
   lv_img_set_src(wpredu,"S:/demo/lvgl_img/reduce.jpg");



    /*创建进度滑动条*/
    slider = lv_slider_create(lv_scr_act());
    //设置宽度
    lv_obj_set_content_width(slider,600);

   
    lv_obj_align(slider, LV_ALIGN_BOTTOM_MID,0, -130);; //设置当前空间的原点坐标
    //滑动条的值被改变时发送事件
    lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);

    /*进度条在滑块下面创建一个标签 */
    slider_label = lv_label_create(lv_scr_act());
    lv_label_set_text(slider_label, "0:00");
	//设置位置
    lv_obj_align_to(slider_label, slider, LV_ALIGN_LEFT_MID, -50, 0);

    slider_label1 = lv_label_create(lv_scr_act());
    lv_label_set_text(slider_label1, "0:00");
	//设置位置
    lv_obj_align_to(slider_label1, slider, LV_ALIGN_RIGHT_MID, 40, 0);  

    //进度条
    static lv_style_t style_indic;

    //初始化样式
    lv_style_init(&style_indic);
    lv_style_set_bg_opa(&style_indic, LV_OPA_COVER);
    lv_style_set_bg_color(&style_indic, lv_palette_main(LV_PALETTE_RED));
    lv_style_set_bg_grad_color(&style_indic, lv_palette_main(LV_PALETTE_BLUE));
    lv_style_set_bg_grad_dir(&style_indic, LV_GRAD_DIR_VER);
    //创建进度条
    lv_obj_t * bar1  = lv_bar_create(lv_scr_act());
    lv_obj_add_style(bar1, &style_indic, LV_PART_INDICATOR);
    lv_obj_set_size(bar1, 20, 200);
    lv_obj_align(bar1, LV_ALIGN_RIGHT_MID,-220, -30);
    lv_bar_set_range(bar1, -20, 40);

    //动画
    lv_anim_t a;
    lv_anim_init(&a);
   lv_anim_set_exec_cb(&a, set_temp);//设置一个函数使“var”动画化
   lv_anim_set_time(&a, 300);//设置动画的持续时间
   lv_anim_set_playback_time(&a, 300);//使动画回放到前进方向准备好时
   lv_anim_set_var(&a, bar1);//设置一个变量为animate
   lv_anim_set_values(&a, -20, 40);//设置动画的开始和结束值
   lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);//让动画重复自己。参数给0停止动画。
   lv_anim_start(&a);
   

    //创建进度条
    bar  = lv_bar_create(lv_scr_act());
    lv_obj_add_style(bar, &style_indic, LV_PART_INDICATOR);
    lv_obj_set_size(bar, 20, 200);
    lv_obj_align(bar, LV_ALIGN_LEFT_MID, 190, -50);
    lv_bar_set_range(bar, 0, 100);

    volume = 100;
    lv_bar_set_value(bar, volume, LV_ANIM_OFF);    

    /*进度条在上面创建一个标签 */
     volume_label = lv_label_create(lv_scr_act());
     lv_label_set_text(volume_label, "100");
	 //设置位置
     lv_obj_align_to(volume_label, bar, LV_ALIGN_TOP_MID, 0, -20);

    lv_obj_t * label_imgs = lv_img_create(lv_scr_act());
    lv_obj_set_size(label_imgs,30,30);
    lv_obj_align_to(label_imgs, bar, LV_ALIGN_BOTTOM_MID, 0, 33);
    lv_img_set_src(label_imgs,"S:/demo/yinxiang.jpg");  
      

}

通过按钮触发事件然后新建线程启动播放器mplayer命令:mplayer -slave -quiet -input file=/pipe bsj.mp3 播放下一首需要关闭原有的线程和mplayer进程 killall -9 mplayer

部分代码演示:

//播放暂停快进等按钮操作
static void event_handler_play(lv_event_t * e)
{

    lv_event_code_t code = lv_event_get_code(e);

    //获取传递的参数 
    char *bt = lv_event_get_user_data(e);
        char bufshow[1024] = {0};
    if(code == LV_EVENT_CLICKED) {
        if(strcmp(bt,"play") == 0)
        {
            system("killall -9 mplayer") ; ;//关闭原有的进程
            pthread_create(&tid,NULL,music_task,NULL);
            sleep(1);

            //取消前一个调用的更新线程
            pthread_cancel(getstr_tid);
            pthread_cancel(upbar_tid); 
            pthread_create(&getstr_tid,NULL,music_task_getstr,NULL);
            pthread_create(&upbar_tid,NULL,update_progree_bar,NULL);  
        
        }
         if(strcmp(bt,"stop") == 0)
        { 
            if(stop_state == 0)
            {
                pthread_cancel(getstr_tid);
                pthread_cancel(upbar_tid);        
                char  cmd[1024]={"pause\n"};
                write(fd_pipe,cmd,strlen(cmd));    
                stop_state = 1;
            }else
            {
                pthread_create(&getstr_tid,NULL,music_task_getstr,NULL);
                pthread_create(&upbar_tid,NULL,update_progree_bar,NULL);        
                char  cmd[1024]={"pause\n"};
                write(fd_pipe,cmd,strlen(cmd));     
                stop_state = 0;              
            }
 
        }          

         if(strcmp(bt,"fast") == 0)
        {
            char  cmd[1024]={"seek +5\n"};
            write(fd_pipe,cmd,strlen(cmd));     
        }

         if(strcmp(bt,"slow") == 0)
        {
            char  cmd[1024]={"seek -5\n"};
            write(fd_pipe,cmd,strlen(cmd));        
        }  
        if(strcmp(bt,"control+")== 0 )
        {
            volume ++ ;
            if(volume >= 100) volume =100;
            char  cmd[1024]={0};
            sprintf(cmd,"volume %d 1\n",volume);
            write(fd_pipe,cmd,strlen(cmd));     
            char buf[12] = {0};
            sprintf(buf,"%d",volume);
                 
        } 
        if(strcmp(bt,"control-")==0)
        {
             volume -- ;
            if(volume <= 0) volume =0;           
            char  cmd[1024]={0};
            sprintf(cmd,"volume %d 1\n",volume);
            write(fd_pipe,cmd,strlen(cmd));  
            lv_bar_set_value(bar, volume, LV_ANIM_OFF);  

            char buf[12] = {0};
            sprintf(buf,"%d",volume);

            lv_label_set_text(volume_label, buf);   
                         
        }              
    }
}

开启一个线程不断读取popen打开mplayer输出的东西打印到进度条标签实现进度条时间变化 根据进度条时间调节mplayer的播放时间

部分代码演示:

void *music_task_getstr(void *arg)
{

    //获取歌曲总时间
    char  cmd[1024]={"get_time_length\n"};
    write(fd_pipe,cmd,strlen(cmd));  

      
    while(1)
    {
        char msg[1024] = {0};
        while(1)
        {
                // 通过 fgets 函数可以从管道文件中直接读取到视频播放进程所有输出的内容
                // 然后就可以为所欲为了
            char* p = fgets(msg , 1024 , fp );
            if (p == NULL )
            {
                break ;
            }
            if(strstr(msg,"ANS_LENGTH"))
            {
                sscanf(msg,"ANS_LENGTH=%f",&music_total_time);
                printf("all time :%.2f\n",music_total_time);
                char buf[124] = {0};

                int m = (int)music_total_time / 60;
                int s = (int)music_total_time % 60;
                if(s <= 9)
                {
                    sprintf(buf,"0%d:0%d",m,s);
                }else
                {
                    sprintf(buf,"0%d:%d",m,s);
                }
                printf("slider_label1 buf:%s\n",buf);
                //上锁
                 pthread_mutex_lock(&mux);
                lv_label_set_text(slider_label1, buf);//显示歌曲总时间秒数  150s  150/60  150%60  2分30秒 
                //解锁
                 pthread_mutex_unlock(&mux);                 
            }

            if(strstr(msg,"ANS_TIME_POSITION"))
            {
                sscanf(msg,"ANS_TIME_POSITION=%f",&music_current_time);
                printf("all time :%.2f\n",music_current_time);
            }

            printf("读取到的信息:%s" , msg );
        }
    }
}
void *update_progree_bar(void *arg)
{

    while (1)
    {

        char  cmd[1024]={"get_time_pos\n"};
        write(fd_pipe,cmd,strlen(cmd)); 
               
        char buf[124] = {0};
        int m = (int)music_current_time / 60;
        int s = (int)music_current_time % 60;
        if(s <= 9)
        {
            sprintf(buf,"0%d:0%d",m,s);
        }else
        {
            sprintf(buf,"0%d:%d",m,s);
        }
         pthread_mutex_lock(&mux);
        lv_label_set_text(slider_label, buf);//显示进度秒数  150s  150/60  150%60  2分30秒   
         pthread_mutex_unlock(&mux);      

        //更新滑块位置  0~100   播放时间/总时间
        int location = (music_current_time / music_total_time) *100 ;
         pthread_mutex_lock(&mux);
        lv_slider_set_value(slider,location, LV_ANIM_OFF);  
         pthread_mutex_unlock(&mux); 
        sleep(1);
            
    }
    
}

对比歌曲文件显示歌曲对应的图片信息

部分代码演示:

    //显示对应歌曲的图片    
    if(strstr(all_pic[music_index],"bsj.mp3"))
    {
        lv_img_set_src(wp,"S:/demo/lvgl_img/bsj.jpg");
    }else if(strstr(all_pic[music_index],"yudao.mp3"))
    {
        lv_img_set_src(wp,"S:/demo/lvgl_img/yudao.jpg");
        
    }else if(strstr(all_pic[music_index],"yanyuan.mp3"))
    {
        lv_img_set_src(wp,"S:/demo/lvgl_img/yanyuan.jpg");
    }    

项目优化

出现这个错误:

[Error] (25.685, +8120) _lv_inv_area: detected modifying dirty areas in render (in lv_refr.c line #

考虑到线程安全问题,自己创建的线程调用lv类函数时与lvgl本身刷图像时调用产生了冲突。解决方法直接百度lvgl线程安全

如果需要使用实际的任务或线程,则需要一个互斥锁,该互斥锁应在调用 lv_task_handler 之前被调用,并在其之后释放。同样,必须在与每个LVGL(lv _...)相关的函数调用和代码周围的其他任务和线程中使用相同的互斥锁。这样,就可以在真正的多任务环境中使用LVGL。只需使用互斥锁(mutex)即可避免同时调用 LVGL 函数。

项目难点

第一个是切换音乐 这里卡了有点久 关闭线程一直没用 然后才发现得杀死播放音乐的进程才能达到创建新的线程来播放下一首音乐

第二个是 进度条功能,我的思路是设计三个线程,一个线程用来获取poen打印mplayer的信息 一个线程用来更新时间标签 这里也有问题,在播放下一首的时候需要把之前线程取消再创建

心得体会

在做项目过程中进一步加深系统编程的知识内容的理解,认识到多线程在程序中使用的重要性,代码编写更加规范,提高自己对做项目的整体框架思路 使用lvgl来编写图形界面更加方便 进一步加深自己对框架编程的理解和运用

有关系统编程之实战小项目-利用LVGL 与 mplayer制作音频播放器的更多相关文章

  1. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  2. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  3. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  4. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  5. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  6. 动漫制作技巧如何制作动漫视频 - 2

    动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、

  7. ruby - 在没有基准或时间的情况下用 Ruby 测量用户时间或系统时间 - 2

    因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实

  8. ruby - 以毫秒为单位获取当前系统时间 - 2

    在Ruby中,以毫秒为单位获取自纪元(1970)以来的当前系统时间的正确方法是什么?我试过了Time.now.to_i,好像不是我想要的结果。我需要结果显示毫秒并且使用long类型,而不是float或double。 最佳答案 (Time.now.to_f*1000).to_iTime.now.to_f显示包含十进制数字的时间。要获得毫秒数,只需将时间乘以1000。 关于ruby-以毫秒为单位获取当前系统时间,我们在StackOverflow上找到一个类似的问题:

  9. ruby-on-rails - 如何构建复杂的 Rails 系统 - 2

    关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭8年前。Improvethisquestion我们有以下(以及更多)系统,我们将数据从一个应用推送/拉取到另一个:托管CRM(InsideSales.com)Asterisk电话系统(内部)横幅广告系统(openx,我们托管)潜在客户生成系统(自行开发)电子商务商店(spree,我们托管)工作板(本土)一些工作网站抓取+入站工作提要电子邮件传送系统(如Mailchimp,自主开发)事件管理系统(如eventbrite,自主开发)仪表板系统(大量图表和

  10. ruby-on-rails - Rails 3,在RAILS_ROOT上方显示来自本地文件系统的jpg图片 - 2

    我正在尝试找出一种方法来显示来自不在RAILS_ROOT下(在RedHat或Ubuntu环境中)的已安装文件系统的图像。我不想使用符号链接(symboliclink),因为这个应用程序实际上是通过Tomcat部署的,而当我关闭Tomcat时,Tomcat会尝试跟随符号链接(symboliclink)并删除挂载中的所有图像。由于这些文件的数量和大小,将图像放在public/images下也不是一种选择。我查看了send_file,但它只会显示一张图片。我需要在一个格式良好的页面中显示6个请求的图像。由于膨胀,我宁愿不使用Base64编码,但我不知道如何将图像数据与呈现的页面一起传递下去。

随机推荐