先叠几层甲:

该项目中很多功能和函数的实现方法肯定有更简单更优化的,本文章里的方法属于比较笨的那种,仅供新手初学者以及博主自己参考,文章中提供的代码及源文件难免会有疏忽、bug,仅供参考、学习、交流;除了正常的讨论问题、建议外,还请各位大佬键盘之下给我留点面子qaq

关于GEC6816屏幕的显示和触控、图片的显示,可以参考之前的文章:关于GEC6818屏幕的驱动方法(显示屏、触摸屏) 本文章默认已经成功驱动屏幕。

源码和图片素材会放到文章末尾,仅供参考。

本项目需要用到mplayer播放器程序,因此需要先将mplayer以及其依赖的库移植到开发板上

关于mplayer的移植,可以参考这篇文章:Linux下的mplayer播放器移植与使用 ,本文不做赘述
关于arm-linux-gcc交叉编译环境的搭建,可以参考之前的文章:Windows10+Ubuntu20.04 arm-linux-gcc交叉编译环境搭建

首先绘制界面:

主界面
如果播放的是音乐就在屏幕中间显示这个图片

这里需要提一句,其实是有办法提取mp3中的图片信息并显示在屏幕上的,只是咱偷懒了没想写,感兴趣的话自己去网上搜MP3文件的标签结构吧(

工程文件结构:

img(用于存放图片素材)、main.c(主函数)、lcd.c(屏幕控制相关函数)、ts.c(触摸屏相关函数)、func.c(播放器相关函数)、chardata.c(用于存放各图片文件的路径等字符串,以及位置坐标等)
build.sh里面是编译命令,也可以写Makefile,不过我觉得小工程用不上所以就没写,看自己习惯就好了~

路径、坐标参考:

char *mainbg="img/mainbg.bmp";
char *playmusic="img/playmusic.bmp";
//各按钮的位置坐标
int back_x=115;
int quick_x=215;
int pre_x=315;
int next_x=415;
int play_x=515;
int pause_x=615;

int back_y=404;
int quick_y=404;
int pre_y=404;
int next_y=404;
int play_y=404;
int pause_y=404;

int vup_x=708;
int vup_y=152;
int vdown_x=708;
int vdown_y=242;
//视频播放区域边框左上角
int info_plmu_x=120;
int info_plmu_y=50;

如果要实现播放器,首先需要一个播放列表(文件列表),这里使用双向循环链表来实现

链表相关函数:

看不懂的话可以先去补一补数据结构,这里就没什么可说的了……

typedef struct node
{
    char *data;
    int type;
    int st;//用于识别文件是MP4还是MP3
    struct node *next,*prev;
}Node;

typedef struct head
{
    struct node *first,*last;
    int node_num;
}Head;

Node *create_node(char *a,int b)//创建一个节点
{
    Node *p=malloc(sizeof(Node));
    p->next=NULL;
    p->prev=NULL;
    p->data=a;
    p->type=b;
    p->st=0;
    return p;
}
Head *create_head(void)//创建一个头节点
{
    Head *h=malloc(sizeof(*h));
    h->first=NULL;
    h->last=NULL;
    h->node_num=0;
    return h;
}

有了用于存放文件名的数据结构,接下来我们就要搜索可以播放的媒体文件了,我们指定一个文件夹用于存放MP4、MP3文件,使用opendir打开这个文件夹,再使用readdir读取文件夹内的文件信息,Linux文件IO库中dirent.h提供了一个用于保存文件信息的结构体dirent,当readdir读取到的dirent->d_type==8时说明匹配到普通文件(文件夹之外的),然后使用strcmp判断文件的后缀名是MP4还是MP3,最后添加到链表中。

Head *search_media(char *path)
{
    Head *h=create_head();
    char *filename=NULL;
    struct dirent *dirp=NULL;
    DIR *dir = opendir(path);
    while(dirp = readdir(dir))
    {
        if(dirp->d_type==8)
        {
            filename=dirp->d_name;
            if(strcmp(filename+strlen(filename)-4,".mp4")==0)
            {
                if(h->first==NULL)
                {
                    Node *a=create_node(dirp->d_name,1);
                    h->first=a;
                    h->last=a;
                    a->next=a;
                    a->prev=a;
                    h->node_num++;
                    printf("%s\n",a->data);
                }
                else
                {
                    Node *p=create_node(dirp->d_name,1);
                    h->last->next=p;
                    p->prev=h->last;
                    h->last=p;
                    p->next=h->first;
                    h->first->prev=p;
                    h->node_num++;
                    printf("%s\n",p->data);
                }
            }
            else if(strcmp(filename+strlen(filename)-4,".mp3")==0)
            {
                if(h->first==NULL)
                {
                    Node *b=create_node(dirp->d_name,0);
                    h->first=b;
                    h->last=b;
                    b->next=b;
                    b->prev=b;
                    h->node_num++;
                    printf("%s\n",b->data);
                }
                else
                {
                    Node *c=create_node(dirp->d_name,0);
                    h->last->next=c;
                    c->prev=h->last;
                    h->last=c;
                    c->next=h->first;
                    h->first->prev=c;
                    h->node_num++;
                    printf("%s\n",c->data);
                }
            }
        }
    }
    return h;
}

那么这样我们就成功获取到文件的名字类型并保存下来了,那么要怎么使用mplayer播放呢?

system函数可以执行直接执行命令,所以我们直接使用该函数调用mplayer即可,mplayer支持通过键盘控制,也通过指定管道文件,向管道中写入命令来控制;所以在开始播放时,我们使用system直接执行mplayer的命令,播放时向管道文件中写入命令来控制播放器快进快退,播放暂停等。

命令例:mplayer -volume 2 -slave -quiet -input file=./mplayer.fifo 'media/xxx.mp3'

其中-volume用于指定音量,因为gec6818的耳机孔默认音量过高,不作调整会鼓膜炸裂(
-slave要求mplayer以slave模式运行,不再截获键盘事件
-quiet不让mplayer在播放时输出冗余的信息
-input file=指定有名管道文件的路径
最后就是要播放的文件的路径

在播放视频时还需要额外的参数:
-framedrop 启用丢帧,缓解因为设备性能过低时产生的音画不同步
-geometry x:y 指定视频播放的位置(视频左上角在屏幕上的坐标)
-zoom -x -y 视频的大小(视频的分辨率会被强制拉伸到设置的分辨率)

命令例:mplayer -volume 3 -framedrop -slave -quiet -input file=./mplayer.fifo -geometry 120:50 -zoom -x 560 -y 336 'media/xxx.mp4'

在实际编写时,先使用sprintf拼接命令和链表中的文件名,再放到system中执行。

播放命令函数:

因为我们需要扫描触摸屏的输入,还要播放视频,因此我们需要用到并发,新开一个线程用于运行mplayer,pthread_create需要传入函数指针,因此我们直接定义为void *

void *play_media(Node *name)
{
    while(1)
    {
        char *buf=malloc(1024);
        if(name->type==0)
        {
            load_img(playmusic,120,50);
            sprintf(buf,"mplayer -volume 2 -slave -quiet -input file=./mplayer.fifo 'media/%s'",name->data);
            printf("Now play:%s",name->data);
            system(buf);
            
        }
        else
        {
            sprintf(buf,"mplayer -volume 3 -framedrop -slave -quiet -input file=./mplayer.fifo -geometry 120:50 -zoom -x 560 -y 336 'media/%s'",name->data);
            printf("Now play:%s",name->data);
            system(buf);
        }
        if(name->next->st==1||name->prev->st==1)
        {
            name->prev->st=0;
            name->next->st=0;
            free(buf);
            break;
        }
        free(buf);
        name=name->next;
    }
}

控制命令函数:

关于mplayer的其他命令,请自行翻阅官方文档:http://www.mplayerhq.hu/DOCS/HTML/en/usage.html

void play_ctrl(Head *cmd)
{
    mkfifo("mplayer.fifo",0777);//新建一个管道文件
    int fd;
    close(fd);//防止文件被占用
    fd=open("mplayer.fifo",O_RDWR);
    Node *name=cmd->first;
    pthread_t tid;
    int stap=0;//用于记录播放器是不是刚启动还未播放过
    int is_playing=0;//在播放中吗?
    int is_pause=0;//暂停了吗?
    int x,y;//触摸坐标
    while(1)
    {
        int z=get_touchscreen_index(&x,&y);
        //后退
        if(x>0 && x<50*1.28 && y>0 && y<50*1.28)//点击屏幕左上角时退出播放器
        {
            write(fd,"q\n",strlen("q\n"));//q是退出命令
            name->next->st=1;
            close(fd);
            return 0;
        }
        if(is_playing==1 && x>=back_x*1.28 && x<(back_x+70)*1.28 && y>=back_y*1.28 && y<(back_y+70)*1.28)
        {
            printf("back\n");
            write(fd,"seek -2\n",strlen("seek -2\n"));//seek +-是偏移指定秒数
        }
        //快进
        if(is_playing==1 && x>=quick_x*1.28 && x<(quick_x+70)*1.28 && y>=quick_y*1.28 && y<(quick_y+70)*1.28)
        {
            printf("quick\n");
            write(fd,"seek +2\n",strlen("seek +2\n"));
        }
        //上一首
        if(stap!=0 && x>=pre_x*1.28 && x<(pre_x+70)*1.28 && y>=pre_y*1.28 && y<(pre_y+70)*1.28)
        {
            printf("pre\n");
            name=name->prev;
            name->st=1;
            write(fd,"q\n",strlen("q\n"));
            sleep(1);//等待一秒再创建新线程,防止资源冲突
            pthread_create(&tid,NULL,play_media,name);
        }
        //下一首
        if(stap!=0 && x>=next_x*1.28 && x<(next_x+70)*1.28 && y>=next_y*1.28 && y<(next_y+70)*1.28)
        {
            printf("next\n");
            name=name->next;
            name->st=1;
            write(fd,"q\n",strlen("q\n"));
            sleep(1);//等待一秒再创建新线程,防止资源冲突
            pthread_create(&tid,NULL,play_media,name);
        }
        //播放
        if(stap!=1 && x>=play_x*1.28 && x<(play_x+70)*1.28 && y>=play_y*1.28 && y<(play_y+70)*1.28)
        {
            printf("play\n");
            if(is_pause==1)//如果现在是暂停中就解除暂停
            {
                write(fd,"pause\n",strlen("pause\n"));
            }
            else
            {
                if(stap==0)//第一次启动的话直接播放
                {
                    pthread_create(&tid,NULL,play_media,name);
                    is_playing=1;
                    is_pause=0;
                    stap=1;
                }
                else
                {
                    write(fd,"q\n",strlen("q\n"));//不是第一次启动就把当前播放的文件恢复到第一个文件
                    name=cmd->first;
                    pthread_create(&tid,NULL,play_media,name);
                    is_playing=1;
                    is_pause=0;
                }
                
            }
        }
        //暂停
        if(stap!=0 && x>=pause_x*1.28 && x<(pause_x+70)*1.28 && y>=pause_y*1.28 && y<(pause_y+70)*1.28)
        {
            printf("pause\n");
            write(fd,"pause\n",strlen("pause\n"));//pause是暂停命令
            is_pause=1;
        }
        //音量+
        if(is_playing==1 && x>=vup_x*1.28 && x<(vup_x+70)*1.28 && y>=vup_y*1.28 && y<(vup_y+70)*1.28)
        {
            printf("vol+\n");
            write(fd,"volume +5\n",strlen("volume +5\n"));
        }
        //音量-
        if(is_playing==1 && x>=vdown_x*1.28 && x<(vdown_x+70)*1.28 && y>=vdown_y*1.28 && y<(vdown_y+70)*1.28)
        {
            printf("vol-\n");
            write(fd,"volume -5\n",strlen("volume -5\n"));
        }
    }
}

主函数:

int main()
{
    int fd=lcd_init();
    lcd_clear(0x00ffffff);
    load_img(mainbg,0,0);
    
    Head *file = search_media("media");
    play_ctrl(file);
    lcd_uninit(fd);
    printf("mplay exit\n");
    return 0;
}

好了,这就是播放器的所有核心代码啦,编译后传到开发板运行试试看~
这里要提一句,GEC6818的性能较差,导入的视频最好事先压制一下,并将分辨率调整到800*480之内。

嘿嘿嘿,织织,我的织织
放一下猫和老鼠~
放一下音乐

那么本项目就到此结束啦,接下来放出源码:

点击下载


逸一时,误一世