目录
[1、 项目需求](#1、 项目需求)
[1.1 要求](#1.1 要求)
[1.2 功能介绍](#1.2 功能介绍)
[2、 核心设计](#2、 核心设计)
[2.1 技术栈选型](#2.1 技术栈选型)
[2.2 整体架构](#2.2 整体架构)
[3、 核心功能代码实现](#3、 核心功能代码实现)
[3.1 查看播放列表](#3.1 查看播放列表)
[3.2 播放、暂停、停止与切歌](#3.2 播放、暂停、停止与切歌)
[3.6 快进与定位](#3.6 快进与定位)
[3.8 播放模式与自动连播(核心技术)](#3.8 播放模式与自动连播(核心技术))
[3.12 终端模式设置(无回车按键获取)](#3.12 终端模式设置(无回车按键获取))
[4、 附录](#4、 附录)
[4.1 mplayer的安装](#4.1 mplayer的安装)
[4.2 程序的编译与执行](#4.2 程序的编译与执行)
[4.3 成果展示](#4.3 成果展示)
在嵌入式与系统编程的学习中,动手实现一个能实际运行的项目是巩固知识的最佳途径。本文将完整还原一个Linux命令行音视频播放器 的构建过程,从需求分析、架构设计到每一行关键代码的实现。我们将利用mplayer 作为播放后端,通过C语言实现播放控制核心逻辑,涉及进程控制、进程间通信(IPC)、信号处理、文件I/O等核心系统编程概念。
1、 项目需求
1.1 要求
开发平台:Linux操作系统
实现语言:C语言
播放核心:基于开源播放器mplayer(slave模式)
界面形式:命令行交互界面
1.2 功能介绍
人机交互:提供清晰的控制菜单,接收键盘按键指令。
功能1:查看扫描到的播放列表。
功能2:开始播放与暂停播放。
功能3:停止播放。
功能4/5:播放上一首/下一首。
功能6:快进/快退。
功能7:精确定位到视频的某一时间点。
功能8:切换播放模式(单曲循环、列表循环、随机播放)。
功能9:退出播放器程序。
2、 核心设计
2.1 技术栈选型
控制层:C语言程序,负责逻辑与交互。
播放层:mplayer,负责音视频解码与输出。
通信桥梁:FIFO(命名管道)。
进程管理:fork()、exec()、SIGCHLD信号。
2.2 整体架构
cpp
C程序 (父进程)
|
| 1. fork() + exec() 启动
| 2. 通过FIFO发送控制命令
V
mplayer (子进程)
|
| 播放音视频
V
声卡/显示器
工作流程 :程序作为"大脑",负责展示列表、接收用户按键。一旦需要播放,它就fork出一个子进程,并让子进程"变身"(exec)为mplayer来播放指定文件。所有控制命令(暂停、切歌等)都通过一个名为/tmp/myfifo的管道发送给mplayer。
3、 核心功能代码实现
本章节将深入核心的function.c文件,逐块解析关键代码。
3.1 查看播放列表
首先,程序需要扫描目录,建立播放列表。
cpp
// 功能:递归扫描指定目录,查找媒体文件
void Find_meiti(const char *path) {
DIR *dir = opendir(path);
if (dir == NULL) { perror("opendir"); return; }
struct dirent *entry;
char fullpath[1024];
while ((entry = readdir(dir)) != NULL) {
// 跳过 `.` 和 `..`
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
// 判断类型:目录则递归,文件则判断后缀
if (entry->d_type == DT_DIR) {
Find_meiti(fullpath); // 递归扫描子目录
} else if (entry->d_type == DT_REG) { // 常规文件
// 提取文件后缀并转换为小写
char *dot = strrchr(entry->d_name, '.');
if (dot) {
for (char *p = dot; *p; ++p) *p = tolower(*p);
// 检查是否为支持的媒体格式
if (strcmp(dot, ".mp4")==0 || strcmp(dot, ".avi")==0 ||
strcmp(dot, ".flv")==0 || strcmp(dot, ".mkv")==0 ||
strcmp(dot, ".mp3")==0 || strcmp(dot, ".wav")==0) {
if (video_Count < 1000) { // 防止数组越界
strncpy(videos[video_Count], fullpath, 1023);
videos[video_Count][1023] = '\0';
video_Count++;
}
}
}
}
}
closedir(dir);
}
关键点:
opendir/readdir/closedir:用于遍历目录。
d_type:判断条目是目录(DT_DIR)还是文件(DT_REG)。
strrchr:从字符串右侧查找字符,用于找到文件名中的最后一个点.以提取后缀。将后缀统一转为小写,使判断更健壮(避免.MP4无法识别)。
3.2 播放、暂停、停止与切歌
这些功能的核心都是向FIFO管道写入特定的控制命令字符串。mplayer在slave模式下定义了一套命令。
cpp
// 功能:播放指定序号的媒体文件
void PlayMedia(int index) {
// 1. 创建FIFO管道(如果不存在)
if (access("/tmp/myfifo", F_OK) == -1) {
if (mkfifo("/tmp/myfifo", 0666) == -1) {
perror("mkfifo error");
return;
}
}
// 2. 创建子进程
pid_t pid = fork();
if (pid == 0) { // 子进程
// 使用execlp"变身"为mplayer
execlp("mplayer", "mplayer", "-slave", "-quiet",
"-input", "file=/tmp/myfifo",
videos[index], NULL);
// 如果execlp失败
perror("execlp mplayer failed");
exit(1);
} else if (pid > 0) { // 父进程
player_pid = pid; // 记录子进程PID
current_video_num = index; // 记录当前播放序号
} else {
perror("fork failed");
}
}
// 功能:向FIFO发送命令的通用函数
void send_command_to_mplayer(const char *cmd) {
int fd = open("/tmp/myfifo", O_WRONLY);
if (fd != -1) {
write(fd, cmd, strlen(cmd));
close(fd);
} else {
// 处理管道打开失败,可能播放器未启动
}
}
// 功能3.2 开始/暂停
void Play_or_Pause1() {
send_command_to_mplayer("pause\n"); // \n 是命令结束符
printf("发送 暂停/继续 指令\n");
}
// 功能3.3 停止
void Stop1() {
send_command_to_mplayer("stop\n");
printf("发送 停止 指令\n");
}
// 功能3.4 播放上一首
void Prev1() {
int prev_index = (current_video_num - 1 + video_Count) % video_Count;
// 先停止当前播放
send_command_to_mplayer("stop\n");
// 短暂延迟,确保资源释放
usleep(500000);
// 播放上一首
PlayMedia(prev_index);
}
// 功能3.5 播放下一首
void Next1() {
int next_index = (current_video_num + 1) % video_Count;
send_command_to_mplayer("stop\n");
usleep(500000);
PlayMedia(next_index);
}
3.6 快进与定位
快进和定位本质都是发送seek命令,但参数不同。
cpp
// 功能3.6 快进(快退)
void Fast1(int seconds) { // 传入正数快进,负数快退
char cmd[32];
// seek 命令格式: seek <秒数> [<模式>]
// 模式 0 相对时间 | 1 绝对时间 | 2 按百分比
snprintf(cmd, sizeof(cmd), "seek %d 0\n", seconds);
send_command_to_mplayer(cmd);
printf("发送 快进 %d 秒 指令\n", seconds);
}
// 功能3.7 定位(跳到指定百分比)
void Seek_To_Percent1(float percent) {
if (percent < 0) percent = 0;
if (percent > 100) percent = 100;
char cmd[32];
// 跳到视频总时长的指定百分比位置
snprintf(cmd, sizeof(cmd), "seek %.2f 2\n", percent);
send_command_to_mplayer(cmd);
printf("发送 定位到 %.1f%% 指令\n", percent);
}
3.8 播放模式与自动连播(核心技术)
这是本项目的精华,通过捕获SIGCHLD信号来实现播放结束后的自动行为。
cpp
// 全局变量定义播放模式
enum PlayMode { MODE_LIST_LOOP, MODE_SINGLE_LOOP, MODE_RANDOM };
enum PlayMode play_mode = MODE_LIST_LOOP;
// SIGCHLD 信号处理函数
void sigchld_handler(int sig) {
int status;
pid_t pid;
// 使用 WNOHANG 非阻塞地等待任何子进程
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (pid == player_pid) { // 确认是mplayer进程退出了
player_pid = -1;
printf("\n播放结束。");
// 根据播放模式决定下一首
int next_index = current_video_num;
switch (play_mode) {
case MODE_LIST_LOOP: // 列表循环
next_index = (current_video_num + 1) % video_Count;
break;
case MODE_SINGLE_LOOP: // 单曲循环
// next_index 不变
break;
case MODE_RANDOM: // 随机播放
srand(time(NULL));
next_index = rand() % video_Count;
break;
}
// 如果播放列表不为空,则播放下一首
if (video_Count > 0) {
printf(" 根据模式,即将播放下一首...\n");
sleep(1); // 给用户一点观察时间
PlayMedia(next_index);
}
}
}
}
// 在主函数中设置信号处理
int main() {
// ... 其他初始化代码 ...
signal(SIGCHLD, sigchld_handler); // 注册信号处理器
// ... 主循环 ...
}
3.12 终端模式设置(无回车按键获取)
为了实现"按任意键继续"或实时检测按键(如按空格暂停),需要将终端从默认的行缓冲模式 设置为原始模式。
cpp
#include <termios.h>
struct termios orig_termios; // 保存原始终端设置
// 功能:将终端设置为原始模式
void enable_raw_mode() {
struct termios raw;
tcgetattr(STDIN_FILENO, &orig_termios); // 备份原始设置
raw = orig_termios;
// 关闭规范模式和回显
raw.c_lflag &= ~(ICANON | ECHO);
// 设置超时:read() 等待至少1个字符,但不会无限等待
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 1; // 0.1秒超时
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
}
// 功能:恢复原始终端设置
void disable_raw_mode() {
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
}
// 功能:检测是否有按键键入(非阻塞)
int kbhit() {
int byteswaiting;
ioctl(STDIN_FILENO, FIONREAD, &byteswaiting);
return byteswaiting > 0;
}
// 在主循环中使用的例子
int main() {
// ... 初始化 ...
enable_raw_mode(); // 进入原始模式
while (1) {
display_menu(); // 显示菜单
if (kbhit()) { // 检测是否有按键
char ch = getchar(); // 读取按键,无需回车
switch (ch) {
case ' ': Play_or_Pause1(); break;
case 'n': Next1(); break;
case 'q': goto end; // 退出循环
// ... 处理其他按键 ...
}
}
usleep(100000); // 避免CPU占用过高
}
end:
disable_raw_mode(); // 必须恢复终端设置!
return 0;
}
4、 附录
4.1 mplayer的安装
在Ubuntu/Debian系统上:
bash
sudo apt update
sudo apt install mplayer
在CentOS/RHEL系统上:
sudo yum install mplayer
4.2 程序的编译与执行
-
编译:
gcc -o myplayer main.c function.c -
执行:
./myplayer -
程序启动后:
-
程序会自动扫描当前目录下的媒体文件。
-
显示操作菜单(例如:
[1] 播放列表 [2] 暂停/继续 [3] 下一首 ...)。 -
根据提示按键进行操作。
-
4.3 成果展示
一个功能完备的命令行播放器就此诞生。它虽然没有图形界面,但你可以通过键盘完全控制播放过程,体验"极客"式的交互。当我在终端中运行它,并成功用按键控制视频的播放、暂停、切换时,我对Linux进程、IPC、信号等抽象概念有了更具体理解。
项目价值 :这不仅仅是一个播放器,更是一个Linux系统编程的微型综合实验场。你可以在此基础上继续扩展,例如增加播放列表管理、音量控制、网络流媒体播放等功能,每一次扩展都是对系统编程能力的深入锻炼。