1. LVGL8 自带的有一个音乐播放器的Demo,效果不错,充分展示了LVGL8的弹性网格布局,动画等效果,来学习GUI是非常不错的,只是这个Demo并不能真正的播放音乐,但是已经把音乐播放的逻辑都给实现了,现在我们就动手给它注入灵魂—-mpv
  2. 最初是用的sox这个号称音频界的瑞士军刀的工具,但是编写过程中碰到一个比较棘手的问题,那就是无法使用管道重定向输出(play本身不支持管道,只能借助sox),也就没办法获取播放时间,会造成进度条时间与实际播放时间有点误差,最后就放弃使用sox了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    if (pid == 0)
    {
    LOG_D("child pid:%d\n", getpid());
    char cmd[32];
    prctl(PR_SET_PDEATHSIG, SIGKILL);
    close(0);
    dup2(pip[1], 1); //标准输出重定向到管道输出
    close(pip[0]);
    sprintf(buf, "./music/%s", _lv_demo_music_get_title(track_id));
    sprintf(cmd, "--start=%d", _time, _lv_demo_music_get_track_length(track_id));
    // execlp("ls", "ls", "./music", NULL);
    // execlp("play", "play", "-p", buf, "trim", cmd, NULL);
    // execlp("sox", "sox", buf, "-p", "|", "play", "-", "trim", cmd, NULL);
    //最后知道怎么使用管道了,但是这是2个进程
    // sox ./music/云非非\ -\ 邂逅.flac -t flac - | play -t flac - &
    // sox ./music/云非非\ -\ 邂逅.flac -t flac - | play - &
    // sox ./music/云非非\ -\ 邂逅.flac -p | play - &
    return 0;
    }
  3. 下边请出我们的主角 MPV

    MPV播放器是什么?

    MPV是著名开源播放器mplayer和mplayer2的一个分支。
    mplayer则是这个地球上最强的播放器(没有之一),跨平台的特性使得windows、mac、linux上都可以见到它的身影,电脑、手机上很多播放器也是基于它开发的,由于mplayer不带界面,所以很多时候你都不知道是它在默默为你工作。
    并且mplayer播放视频时对于资源的消耗往往最少,所以你会发现在一台配置极差的电脑上播放高清电影,mplayer通常是最流畅的,使用快进时最能体现出差距,其他播放器已经画面卡死时,mplayer的画面可能只是感觉到掉帧而已。
    MPV播放器继承这些众多优良特性的同时,添加了内置于窗口的播放控制界面(OSC),对硬解的良好支持,以及其他额外特性。由于口碑很好,使得著名的mplayer前端:smplayer在不久前也添加了对MPV的支持,现在的smplayer你可以在mplayer和MPV2个核心之间切换使用。
    mpv官网的开发文档比较好,参考使用手册都可以使用起来

  4. 下边简单说一下过程
    a.首先要获取播放列表 有2种方法
    方法1:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    int scan_music_list(char *_path)
    {
    char path[128];
    sprintf(path, "ls %s", _path);
    FILE *fp = popen(path, "r");
    if (ferror(fp))
    {
    LOG_D("error\n");
    }
    char buf[128];
    while (!feof(fp))
    {
    fgets(buf, sizeof(buf), fp);
    strncpy(title_list[idx], buf, strlen(buf) - 1); //去掉文件名后的\n
    get_music_info(title_list[idx], idx);
    idx++;
    usleep(1);
    }
    return idx;
    }
     播放列表里的字体是浪漫雅圆,使用freetype渲染的
     方法2:
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    int list_dir(char *path, int depth)
    {
    DIR *dir;
    struct dirent *file;
    struct stat st;
    dir = opendir(path);
    if (!dir)
    {
    LOG_D("open dir %s failed!", path);
    return -1;
    }
    LOG_D("open dir %s ok!", path);
    while ((file = readdir(dir)) != NULL)
    {
    if (strncmp(file->d_name, ".", 1) == 0 || strncmp(file->d_name, "..", 2) == 0)
    {
    continue;
    }
    strcpy(title_list[idx++], file->d_name);
    if (stat(file->d_name, &st) >= 0 && S_ISDIR(st.st_mode) && depth <= 5)
    {
    list_dir(file->d_name, depth + 1);
    }
    }
    closedir(dir);
    return 0;
    }
    b.程序启动时创建一个子进程,一个线程获取播放进度,一个线程获取各种输入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    void lv_demo_music(void)
    {
    lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0x343247), 0);

    music_num = scan_music_list(MUSIC_PATH);
    list = _lv_demo_music_list_create(lv_scr_act());
    ctrl = _lv_demo_music_main_create(lv_scr_act());
    pid = vfork();
    if (pid == 0)
    {
    LOG_D("child pid:%d\n", getpid());
    prctl(PR_SET_PDEATHSIG, SIGKILL);
    execlp("mpv", "mpv", "--quiet", "--no-terminal", "--no-video", "--idle=yes", "--term-status-msg=", "--input-ipc-server=/tmp/mpvsocket", NULL);
    LOG_D("child exit!\n");
    return 0;
    }
    else if (pid > 0)
    {
    LOG_D("parent pid:%d\n", getpid());
    sleep(1);
    close(0);
    act.sa_handler = sigusr1;
    sigfillset(&act.sa_mask);
    act.sa_flags = SA_RESTART; /* don't fiddle with EINTR */
    sigaction(SIGUSR1, &act, NULL);
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "/tmp/mpvsocket");
    fd_mpv = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd_mpv == -1)
    {
    LOG_D("Create socket failed\n");
    }
    if (connect(fd_mpv, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
    LOG_D("Cannot connect to socket %s\n", addr.sun_path);
    }
    if (pthread_create(&mthread, NULL, get_music_percent_pos, NULL) != 0)
    {
    LOG_D("pthread create error!\n");
    return 0;
    }
    // LOG_D("get music pos pthread create ok!\n");
    }
    else
    {
    LOG_D("fork error:\n");
    }

    #if LV_DEMO_MUSIC_AUTO_PLAY
    lv_timer_create(auto_step_cb, 1000, NULL);
    #endif
    }
     这里说明一下mpv里的参数
     "--quiet", //输出尽量少的信息
     "--no-terminal", //不接受终端输入
     "--no-video", //不需要视频
     "--idle=yes", //播放完不能出进程
     "--term-status-msg=", //状态信息不打印
     "--input-ipc-server=/tmp/mpvsocket"//使用sock方式与mpv通信
     这里使用的是mpv推荐的基于socket的JSON-based IPC protocol通信方式
    
    c. 一个线程启动时向mpv发送命令,然后监听事件就可以知道当前进度及状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    void *get_music_percent_pos(void *arg)
    {
    // char cmd[] = "{\"command\": [ \"get_property\", \"playback-time\"] }\n";
    // char cmd[] = "{\"command\": [ \"get_property\", \"percent-pos\"] ,\"request_id\":2}\n";
    char cmd2[] = "{\"command\": [ \"observe_property\",2, \"percent-pos\"]}\n";
    char cmd1[] = "{\"command\": [ \"observe_property\",1,\"time-pos\"]}\n";
    char buf[512];
    cJSON *root;
    cJSON *event;
    cJSON *percent;
    write(fd_mpv, cmd1, strlen(cmd1));
    _msleep(100);
    write(fd_mpv, cmd2, strlen(cmd2));
    memset(buf, 0, sizeof(buf));
    while (1)
    {

    if (read(fd_mpv, buf, sizeof(buf)) > 0)
    {
    LOG_D("--->:%s\n", buf);
    root = cJSON_Parse(buf);
    if (root == NULL)
    {
    LOG_D("cJSON parse error!\n");
    return;
    }
    // LOG_D("cJSON parse ok!\n");
    if (cJSON_HasObjectItem(root, "event"))
    {

    event = cJSON_GetObjectItem(root, "event");
    if (event != NULL)
    {
    if (strcmp(event->valuestring, "start-file") == 0)
    {
    }
    else if (strcmp(event->valuestring, "metadata-update") == 0)
    {
    }
    else if (strcmp(event->valuestring, "file-loaded") == 0)
    {
    }
    else if (strcmp(event->valuestring, "property-change") == 0 && (cJSON_GetObjectItem(root, "id")->valueint == 1))
    {
    percent = cJSON_GetObjectItem(root, "data");
    lv_label_set_text_fmt(time_obj, "%02d:%02d", (int)percent->valuedouble / 60, (int)percent->valuedouble % 60);
    LOG_D("time pos:%d\n", (int)percent->valuedouble);
    }
    else if (strcmp(event->valuestring, "property-change") == 0 && (cJSON_GetObjectItem(root, "id")->valueint == 2))
    {
    percent = cJSON_GetObjectItem(root, "data");
    lv_slider_set_value(slider_obj, (int)percent->valuedouble, LV_ANIM_ON);
    LOG_D("percent pos:%d\n", (int)percent->valuedouble);
    }
    else if (strcmp(event->valuestring, "end-file") == 0)
    {
    LOG_D("end-file\n");
    is_loaded = true;
    _lv_demo_music_album_next(true);
    }
    else if (strcmp(event->valuestring, "playback-restart") == 0)
    {
    }
    else if (strcmp(event->valuestring, "idle") == 0)
    {
    LOG_D("idle\n");
    }
    }
    }

    free(root);
    }
    //_msleep(250);
    }
    pthread_exit(NULL);
    LOG_D("pthread exit:\n");
    }
     这里通信都是采用JSON格式,使用了cJSON来解析送来的数据
    
  5. 这些实现完成后,音乐播放器也就有了灵魂,就是一个真正的音乐播放器了,几乎支持所有音乐格式,我测试的常见的有wav,mp3,flac,ape,dts等