一、项目要求
.基于Mplayer的视频播放器
1.需求分析:
1.该程序能够加载指定路径下所有的音视频文件
(.mp3 .mp4 .avi .rm .rmvb .flv .wma)
2.能够通过界面操作视频播放器
支持:
上键
下键
回车
ESC
支持:
焦点定位
1.一级页面功能如下:
+-------------------+
| 视频播放器 |
|-------------------|
|1.查看播放列表 |
|2.开始/暂停 |
|3.停止 |
|4.上一个 |
|5.下一个 |
|6.快进 |
|7.定位 |
|8.播放方式 |
|9.退出 |
+-------------------+
查看播放列表二级页面如下:
+-------------------+
| 视频播放器 |
|-------------------|
|1.张三的歌.mp4 |
|2.李四的曲.avi |
|... |
+-------------------+
定位的二级页面
+-------------------+
| 视频播放器 |
+-------------------+
定位:XX:XX:XX
3.在一级页面启动播放列表功能,进入二级页面,
焦点定位到对应歌曲按下enter播放歌曲,按下
ESC按键能够返回一级页面
4.在一级页面启动下一个功能,能够播放下一个音
视频文件,如果到达最末尾则提示:最后一首歌曲
5.在一级页面启动上一个功能,能够播放上一个音
视频文件,如果到达最前面则提示:第一首歌曲
6.在一级页面启动快进功能,能够按倍速播放,
第一次选择启动2倍速播放
第二次选择启动4倍速播放
第三次选择返回1倍速播放(默认)
周而复始
7.在一级页面启动开始功能,
如果音视频正在播放,则暂停播放
如果音视频正在暂停,则继续播放
如果音视频文件没有播放,则按照
播放方式(1.顺序循环 2.单曲循环 3.随机播放)
播放歌曲
8.在一级页面启动停止功能:
如果音视频文件没有播放,则提示:未播放音视频文件
如果音视频正在播放,则停止当前播放内容
9.在一级页面启动定位功能:
如果定位在音视频文件播放时间内,则跳到对应位置播放
如果定位位置超过文件播放时间,则提示:定位错误
10.播放方式
在一级页面启动播放方式功能:
第一次选择启动单曲循环
第二次选择启动随机播放
第三次选择返回顺序循环(默认)
11.在一级页面启动退出功能:
则程序退出
二、思考内容:
1.如何使用颜色打印?
printf颜色打印

cpp
printf("\033[0m \033[属性代码1;属性代码2;属性代码3...m字符串,\033[0m");
printf("\033[0m \033[格式命令如:闪烁;背景颜色;字体颜色m我是中国人 \033[0m\n");
printf("\033[0m \033[5;46;31m我是中国人! \033[0m\n");
//每一步骤的意义
printf("\033[0m");//清空设置
printf("\033[5;35;43m");//填写颜色设置
printf("我是中国人!\n");//要打印的内容
printf("\033[0m");//清空设置
在使用完属性代码进行输出后,一定要用\033[0m重置一下,否则会影响到后续的输出。

需要注意的是用vscode进行闪烁属性的时候,无法正常闪烁,用虚拟机里面的终端就可以正常使用。
2.如何操作焦点位置?
刚开始打印的位置就是start 到end之间的内容

然后focus进行向下移动,移动到end的位置

当走到end的之后 start ,end 和focus一起向下移动。
功能示例代码如下:
cpp
void MenuChoose(void)
{
char ch = 0;
int medialen = 20;
ch = getchar();
if ('w' == ch)
{
if (focus > pos_start)
{
focus--;
}
else if (focus == pos_start && pos_start == 0)
{
return;
}
else if (focus == pos_start)
{
pos_start--;
pos_end--;
focus--;
}
}
else if ('s' == ch)
{
if (focus < pos_end)
{
focus++;
}
else if (focus == pos_end && pos_end == medialen)
{
return;
}
else if (focus == pos_end)
{
pos_start++;
pos_end++;
focus++;
}
}
return;
}
3.如何从终端接收上键?下键?回车?ESC按键?
怎么接收上 下 键?上下不是一个字符。




ESC是 ^[ 看作是 : ESC + \n 对应的ascii码是27 和 10;
DOWN是^[ [B 看作是:ESC + [ + B 对应的ascii码是27 91 66
UP是^[ [B 看作是:ESC + [ + A 对应的ascii码是27 91 65
判断按键代码示例如下:
cpp
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char str[32] = {0};
while (1)
{
memset(str, 0, sizeof(str));
fgets(str, sizeof(str), stdin);
if (27 == str[0] && 10 == str[1])
{
printf("ESC键!\n");
return;
}
else if (10 == str[0])
{
printf("回车键!\n");
}
else if (27 == str[0] && 91 == str[1])
{
if (65 == str[2])
{
printf("UP键!\n");
}
else if (66 == str[2])
{
printf("DOWN键!\n");
}
}
}
return 0;
}
4.如何加载音视频文件?
调用mplyer通过音视频文件所在的路径来加载文件。
5.如何打印界面?
cpp
#include <stdio.h>
typedef struct media
{
int id;
char medianame[512];
}MEDIA_CFG_S;
typedef struct menuinfo
{
int id;
int pos_start;
int pos_end;
int focus;
}MENU_CFG_S;
MEDIA_CFG_S medialist[32] = {
{1, "张三的歌"},
{2, "张三的歌"},
{3, "张三的歌"},
{4, "张三的歌"},
{5, "张三的歌"},
{6, "张三的歌"},
{7, "张三的歌"},
{8, "张三的歌"},
{9, "张三的歌"},
{10, "张三的歌"},
{11, "张三的歌"},
{12, "张三的歌"},
{13, "张三的歌"},
{14, "张三的歌"},
{15, "张三的歌"},
{16, "张三的歌"},
{17, "张三的歌"},
{18, "张三的歌"},
{19, "张三的歌"},
{20, "张三的歌"},
{21, "张三的歌"},
};
int pos_start; //界面起始位置
int pos_end = 10; //界面结束位置
int focus; //界面焦点位置
int menulevel = 2; //一级界面
void MenuShow(void)
{
int i = 0;
if (menulevel == 2)
{
printf("+-----------------------------+\n");
printf("| 音视频播放器 |\n");
printf("|-----------------------------|\n");
for (i = pos_start; i <= pos_end; i++)
{
if (i == focus)
{
printf("|\033[30;43m%2d.%-30s\033[0m|\n", medialist[i].id, medialist[i].medianame);
}
else
{
printf("|%2d.%-30s|\n", medialist[i].id, medialist[i].medianame);
}
}
printf("+-----------------------------+\n");
}
else
{
}
return;
}
void MenuChoose(void)
{
char ch = 0;
int medialen = 20;
ch = getchar();
if ('w' == ch)
{
if (focus > pos_start)
{
focus--;
}
else if (focus == pos_start && pos_start == 0)
{
return;
}
else if (focus == pos_start)
{
pos_start--;
pos_end--;
focus--;
}
}
else if ('s' == ch)
{
if (focus < pos_end)
{
focus++;
}
else if (focus == pos_end && pos_end == medialen)
{
return;
}
else if (focus == pos_end)
{
pos_start++;
pos_end++;
focus++;
}
}
return;
}
int main(int argc, const char *argv[])
{
while (1)
{
system("clear");
MenuShow();
MenuChoose();
}
return 0;
}
这里就是打印界面,每按一次上下键都会打印一次完整的显示,所以调用system("clear");将之前打印的清除掉,然后在进行打印,这样就只保存了最后一次的打印。
6.如何存储所有的音视频文件?
用一个二维的数组,每一行都存储一个音视频文件的路径
7.如何控制mplayer播放器?
fork + exec来进行播放,也就是创建子进程来播放mplayer。
示例代码如下:
cpp
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
pid_t pid;
int fd = 0;
char tmpbuff[1024] = {0};
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
execlp("mplayer", "mplayer", "-slave", "-input", "file=/tmp/myfifo", "../1.flv", "-quiet", NULL);
}
else if (pid > 0)
{
fd = open("/tmp/myfifo", O_RDWR);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
fgets(tmpbuff, sizeof(tmpbuff), stdin);
//pause\n
write(fd, tmpbuff, strlen(tmpbuff));
}
close(fd);
}
return 0;
}
那怎么控制mplayer?
在mplayer工作的时候,可以指定一个从属模式设置一个管道,打开这个管道然后写命令。
三、软件编写
1.查看播放列表:在指定目录下遍历文件,找到以媒体文件结尾的文件名,将其路径存储在变量中
2.开始/暂停: fork + exec(mplayer)
3.停止:向管道中写入命令
4.上一首:
5.下一首:
6.快进:向管道中写入命令
7.定位:向管道中写入命令
8.播放方式:SIGCHLD证明有子进程结束了
9.退出:进程任务结束,如果mplayer正在播放,将mplayer结束

1.head.h
cpp
#ifndef __HEAD_H__
#define __HEAD_H__
extern int LoadMediaFile(char *pDirName);
extern void ShowMediaList(void);
extern int StopMedia(void);
extern int PlayMedia(int MediaIndex);
extern int ExecUserChoose(int No);
extern int GetUserChoose(void);
extern void ShowMenu(void);
extern int PauseMedia(void);
extern int SetMediaSpeed(void);
extern int SetMediaPosition(void);
extern void SwitchMediaMode(void);
extern void sigchild_handler(int signo);
extern char MediaList[100][256]; //存放媒体文件列表
extern int CurMediaLen; //当前加载的媒体文件个数
//是否为播放上一首
extern int IsPlayPre;
//是否为播放下一首
extern int IsPlayNext;
#endif
2.main.c
cpp
/***************************************************
*
* 主函数
*
**************************************************/
#include <stdio.h>
#include "head.h"
int main(void)
{
int No = 0;
while (1)
{
CurMediaLen = LoadMediaFile("/home/linux/Music");
ShowMenu();
No = GetUserChoose();
if (9 == No)
{
break;
}
ExecUserChoose(No);
}
return 0;
}
3.menu.c
cpp
/***************************************************
*
* 界面相关代码
*
**************************************************/
#include "head.h"
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
/***************************************************
*函数名:ShowMenu
*功 能:
* 显示界面
*参数:
* void
*返回值:
* void
***************************************************/
void ShowMenu(void)
{
printf("===========================================\n");
printf("1.查看播放列表\n");
printf("2.开始/暂停\n");
printf("3.停止\n");
printf("4.上一首\n");
printf("5.下一首\n");
printf("6.快进\n");
printf("7.定位\n");
printf("8.播放方式\n");
printf("9.退出\n");
printf("===========================================\n");
return;
}
/***************************************************
*函数名:GetUserChoose
*功 能:
* 获得用户的选择
*参数:
* void
*返回值:
* 用户选择的选项编号
***************************************************/
int GetUserChoose(void)
{
int No = 0;
printf("请选择:\n");
scanf("%d", &No);
return No;
}
/***************************************************
*函数名:PlayNext
*功 能:
* 播放下一首
*参数:
* void
*返回值:
* void
***************************************************/
void PlayNext(void)
{
StopMedia();
IsPlayNext = 1;
return;
}
/***************************************************
*函数名:PlayPre
*功 能:
* 播放下一首
*参数:
* void
*返回值:
* void
***************************************************/
void PlayPre(void)
{
StopMedia();
IsPlayPre = 1;
return;
}
/***************************************************
*函数名:ExecUserChoose
*功 能:
* 执行用户选择
*参数:
* No: 用户选择的选项编号
*返回值:
* 成功返回0
* 失败返回-1
***************************************************/
int ExecUserChoose(int No)
{
if (1 == No)
{
ShowMediaList();
}
else if (2 == No)
{
PauseMedia();
}
else if (3 == No)
{
signal(SIGCHLD, SIG_DFL);
StopMedia();
wait(NULL);
signal(SIGCHLD, sigchild_handler);
}
else if (4 == No)
{
PlayPre();
}
else if (5 == No)
{
PlayNext();
}
else if (6 == No)
{
SetMediaSpeed();
}
else if (7 == No)
{
SetMediaPosition();
}
else if (8 == No)
{
SwitchMediaMode();
}
else if (9 == No)
{
signal(SIGCHLD, SIG_DFL);
StopMedia();
}
return 0;
}
4.mplayer.c
cpp
/***************************************************
*
* 媒体相关代码
*
**************************************************/
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include "head.h"
//存放媒体文件列表
char MediaList[100][256] = {0};
//当前加载的媒体文件个数
int CurMediaLen = 0;
//当前播放的媒体的编号
int CurMediaIndex = 0;
//Mplayer播放方式
typedef enum MediaMode
{
MPLAYER_MODE_NEXT = 1,
MPLAYER_MODE_SINGLE = 2,
MPLAYER_MODE_RANDOM = 3,
}MPLAYER_MODE_EN;
//当前播放模式
MPLAYER_MODE_EN CurMediaMode = MPLAYER_MODE_NEXT;
//Mplayer播放速度
typedef enum MediaSpeed
{
MPLAYER_SPEED_ONE = 1,
MPLAYER_SPEED_TWO = 2,
MPLAYER_SPEED_FOUR = 4,
}MPLAYER_SPEED_EN;
//当前播放速度
MPLAYER_SPEED_EN CurMediaSpeed = MPLAYER_SPEED_ONE;
//Mplayer播放状态
typedef enum MediaStat
{
MPLAYER_STAT_FREE, //空闲状态
MPLAYER_STAT_PLAY, //播放状态
MPLAYER_STAT_PAUSE, //暂停状态
}MPLAYER_STAT_EN;
//当前播放状态
MPLAYER_STAT_EN CurMediaStat = MPLAYER_STAT_FREE;
//是否为播放上一首
int IsPlayPre = 0;
//是否为播放下一首
int IsPlayNext = 0;
/***************************************************
*函数名:IsMediaFile
*功 能:
* 判断是否为媒体文件
*参数:
* pfilename: 文件名
*返回值:
* 成功返回 1
* 失败返回 0
***************************************************/
int IsMediaFile(char *pfilename)
{
char *ptmp = NULL;
//helloworld.mp4
ptmp = pfilename + strlen(pfilename);
while (ptmp >= pfilename && *ptmp != '.')
{
ptmp--;
}
if (!strcmp(ptmp, ".mp3") || !strcmp(ptmp, ".mp4") || !strcmp(ptmp, ".flv"))
{
return 1;
}
return 0;
}
/***************************************************
*函数名:LoadMediaFile
*功 能:
* 加载指定目录下所有的媒体(.mp3、.mp4、.avi、.flv)文件
*参数:
* pDirName: 存放媒体文件的目录
*返回值:
* 成功返回实际加载媒体文件个数
* 失败返回 -1
***************************************************/
int LoadMediaFile(char *pDirName)
{
//1.遍历目录文件下的所有文件名
DIR *dp = NULL;
struct dirent *pp = NULL;
int cnt = 0;
dp = opendir(pDirName);
if (NULL == dp)
{
return -1;
}
while (1)
{
pp = readdir(dp);
if (NULL == pp)
{
break;
}
if ('.' == *pp->d_name)
{
continue;
}
if (IsMediaFile(pp->d_name))
{
sprintf(MediaList[CurMediaLen], "%s/%s", pDirName, pp->d_name);
CurMediaLen++;
cnt++;
}
}
closedir(dp);
return cnt;
}
/***************************************************
*函数名:sigchild_handler
*功 能:
* 播放完毕处理函数
*参数:
* signo:信号的编号
*返回值:
* void
***************************************************/
void sigchild_handler(int signo)
{
wait(NULL);
if (IsPlayNext)
{
if (CurMediaIndex == CurMediaLen)
{
printf("已经到最末尾了!\n");
return;
}
else
{
PlayMedia(CurMediaIndex+1);
}
IsPlayNext = 0;
return;
}
if (IsPlayPre)
{
if (CurMediaIndex == 0)
{
printf("已经到最开头了!\n");
return;
}
else
{
PlayMedia(CurMediaIndex-1);
}
IsPlayPre = 0;
return;
}
if (CurMediaMode == MPLAYER_MODE_NEXT)
{
if (CurMediaIndex == CurMediaLen)
{
printf("已经到最末尾了!\n");
return;
}
else
{
PlayMedia(CurMediaIndex+1);
}
}
else if (CurMediaMode == MPLAYER_MODE_SINGLE)
{
PlayMedia(CurMediaIndex);
}
else if (CurMediaMode == MPLAYER_MODE_RANDOM)
{
srand(time(NULL));
PlayMedia(rand() % CurMediaLen);
}
return;
}
/***************************************************
*函数名:PlayMedia
*功 能:
* 播放媒体文件
*参数:
* MediaIndex:媒体文件的编号
*返回值:
* 成功返回 0
* 失败返回 -1
***************************************************/
int PlayMedia(int MediaIndex)
{
pid_t pid;
signal(SIGCHLD, sigchild_handler);
pid = fork();
if (-1 == pid)
{
return -1;
}
if (0 == pid)
{
mkfifo("/tmp/myfifo", 0664);
close(1);
close(2);
execlp("mplayer", "mplayer", "-slave", "-input", "file=/tmp/myfifo", MediaList[MediaIndex], NULL);
}
CurMediaIndex = MediaIndex;
CurMediaStat = MPLAYER_STAT_PLAY;
CurMediaSpeed = MPLAYER_SPEED_ONE;
return 0;
}
/***************************************************
*函数名:ShowMediaList
*功 能:
* 打印媒体列表
*参数:
* void
*返回值:
* void
***************************************************/
void ShowMediaList(void)
{
int i = 0;
int No = -1;
for (i = 0; i < CurMediaLen; i++)
{
printf("%d.%s\n", i+1, MediaList[i]);
}
while (1)
{
printf("请选择播放的歌曲编号(输入0返回上一级)\n");
scanf("%d", &No);
if (0 == No)
{
break;
}
PlayMedia(No-1);
}
return;
}
/***************************************************
*函数名:StopMedia
*功 能:
* 停止播放媒体文件
*参数:
* void
*返回值:
* 成功返回 0
* 失败返回 -1
***************************************************/
int StopMedia(void)
{
int fd = 0;
fd = open("/tmp/myfifo", O_RDWR);
if (-1 == fd)
{
return -1;
}
write(fd, "stop\n", 5);
CurMediaStat = MPLAYER_STAT_FREE;
close(fd);
return 0;
}
/***************************************************
*函数名:PauseMedia
*功 能:
* 暂停媒体文件
*参数:
* void
*返回值:
* 成功返回 0
* 失败返回 -1
***************************************************/
int PauseMedia(void)
{
int fd = 0;
fd = open("/tmp/myfifo", O_RDWR);
if (-1 == fd)
{
return -1;
}
write(fd, "pause\n", 6);
close(fd);
if (MPLAYER_STAT_PLAY == CurMediaStat)
{
CurMediaStat = MPLAYER_STAT_PAUSE;
}
else if (MPLAYER_STAT_PAUSE == CurMediaStat)
{
CurMediaStat = MPLAYER_STAT_PLAY;
}
return 0;
}
/***************************************************
*函数名:SetMediaSpeed
*功 能:
* 设置媒体播放速度
*参数:
* void
*返回值:
* 成功返回 0
* 失败返回 -1
***************************************************/
int SetMediaSpeed(void)
{
int fd = 0;
fd = open("/tmp/myfifo", O_RDWR);
if (-1 == fd)
{
return -1;
}
if (MPLAYER_SPEED_ONE == CurMediaSpeed)
{
write(fd, "speed_set 2\n", 12);
CurMediaSpeed = MPLAYER_SPEED_TWO;
}
else if (MPLAYER_SPEED_TWO == CurMediaSpeed)
{
write(fd, "speed_set 4\n", 12);
CurMediaSpeed = MPLAYER_SPEED_FOUR;
}
else if (MPLAYER_SPEED_FOUR == CurMediaSpeed)
{
write(fd, "speed_set 1\n", 12);
CurMediaSpeed = MPLAYER_SPEED_ONE;
}
close(fd);
return 0;
}
/***************************************************
*函数名:SetMediaPosition
*功 能:
* 设置媒体播放位置
*参数:
* void
*返回值:
* 成功返回 0
* 失败返回 -1
***************************************************/
int SetMediaPosition(void)
{
int fd = 0;
int n = 0;
char cmdbuf[1024] = {0};
fd = open("/tmp/myfifo", O_RDWR);
if (-1 == fd)
{
return -1;
}
printf("请输入秒数:\n");
scanf("%d", &n);
sprintf(cmdbuf, "seek %d 2\n", n);
write(fd, cmdbuf, strlen(cmdbuf));
close(fd);
return 0;
}
/***************************************************
*函数名:SwitchMediaMode
*功 能:
* 切换媒体播放模式
*参数:
* void
*返回值:
* 成功返回 0
* 失败返回 -1
***************************************************/
void SwitchMediaMode(void)
{
if (CurMediaMode == MPLAYER_MODE_NEXT)
{
printf("切换为:单曲循环\n");
CurMediaMode = MPLAYER_MODE_SINGLE;
}
else if (CurMediaMode == MPLAYER_MODE_SINGLE)
{
printf("切换为:随机播放\n");
CurMediaMode = MPLAYER_MODE_RANDOM;
}
else if (CurMediaMode == MPLAYER_MODE_RANDOM)
{
printf("切换为:顺序播放\n");
CurMediaMode = MPLAYER_MODE_NEXT;
}
return;
}
5.makefile
cpp
a.out:main.c menu.c mplayer.c
gcc $^ -o $@
四、工作方式
