嵌入式小项目:音视频播放器
先叠几层甲:
该项目中很多功能和函数的实现方法肯定有更简单更优化的,本文章里的方法属于比较笨的那种,仅供新手初学者以及博主自己参考,文章中提供的代码及源文件难免会有疏忽、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之内。
嘿嘿嘿,织织,我的织织
放一下猫和老鼠~
放一下音乐
那么本项目就到此结束啦,接下来放出源码:
- 感谢你赐予我前进的力量