应用——MPlayer 媒体播放器系统代码详解

MPlayer 媒体播放器系统代码详解

一、程序概览

1.1 程序功能

这是一个基于C语言的命令行媒体播放器控制系统,通过管道(FIFO)与MPlayer进程通信,实现对音频/视频文件的播放控制。

1.2 核心技术

  • 进程间通信(IPC):使用命名管道(FIFO)

  • 多进程编程:fork()创建子进程运行MPlayer

  • 信号处理:处理程序退出信号

  • 文件系统操作:目录扫描、文件过滤

二、代码结构详解

2.1 头文件包含

复制代码
#include <stdio.h>      // 标准输入输出
#include <stdlib.h>     // 标准库函数(exit, atoi等)
#include <unistd.h>     // UNIX标准函数(fork, execlp等)
#include <string.h>     // 字符串处理函数
#include <fcntl.h>      // 文件控制(open等)
#include <sys/types.h>  // 系统数据类型
#include <sys/stat.h>   // 文件状态(mkfifo等)
#include <sys/wait.h>   // 进程等待(waitpid)
#include <signal.h>     // 信号处理
#include <time.h>       // 时间函数
#include <dirent.h>     // 目录操作
#include <errno.h>      // 错误号定义

2.2 数据结构和全局变量

2.2.1 播放列表结构
复制代码
typedef struct {
    char name[10][512];  // 二维数组:最多10个文件,每个路径最长511字符
    int total;           // 实际找到的文件数量
    int current;         // 当前播放的索引(0-based)
} LIST;

设计说明

  • 使用静态数组简化内存管理

  • name[10][512]:限制最大文件数为10,路径长度不超过512字节

  • current用于循环播放时的索引控制

2.2.2 全局变量
复制代码
#define MEDIA_PATH "/home/linux/Music"  // 媒体文件搜索路径

LIST list = {0};         // 播放列表实例,初始化为0
pid_t mplayer_pid = 0;   // MPlayer进程ID,0表示无进程运行
int fifo_fd = -1;        // 管道文件描述符,-1表示未打开

三、核心函数详解

3.1 初始化函数 do_init()

复制代码
void do_init() 
{
    // 1. 打开媒体目录
    DIR *dir = opendir(MEDIA_PATH);
    if (NULL == dir) {
        perror("打开目录失败");
        exit(1);  // 致命错误,直接退出
    }
    
    // 2. 初始化列表
    list.total = 0;
    list.current = 0;
    
    // 3. 遍历目录,筛选媒体文件
    while (1) {
        struct dirent *info = readdir(dir);
        if (NULL == info) {
            break;  // 遍历结束
        }
        
        // 提取文件扩展名(最后3个字符)
        char *ext = &info->d_name[strlen(info->d_name) - 3];
        
        // 检查是否为目标格式
        if (strcmp(ext, "mp4") == 0 || 
            strcmp(ext, "mp3") == 0 || 
            strcmp(ext, "flv") == 0) {
            
            // 构建完整路径并保存
            sprintf(list.name[list.total++], "%s/%s", 
                    MEDIA_PATH, info->d_name);
        }
    }
    
    // 4. 关闭目录
    closedir(dir);
    
    // 5. 创建命名管道(先删除旧的)
    unlink("/tmp/fifo");  // 删除可能存在的旧管道
    
    int ret = mkfifo("/tmp/fifo", 0666);  // 创建新管道,权限rw-rw-rw-
    if (ret == -1 && errno != EEXIST) {   // 创建失败且不是已存在
        perror("mkfifo");
        exit(1);
    }
}

关键点

  1. 简单的扩展名检查:仅检查最后3个字符,假设文件都有正确扩展名

  2. 管道创建:使用/tmp/fifo作为通信管道

  3. 错误处理:目录打开失败和管道创建失败是致命错误

3.2 命令发送函数 send_cmd()

复制代码
void send_cmd(char *cmd) 
{
    // 1. 检查管道是否已打开
    if (fifo_fd < 0) {
        fifo_fd = open("/tmp/fifo", O_WRONLY);  // 只写方式打开
        if (fifo_fd < 0) {
            perror("打开管道失败");
            return;  // 非致命错误,只输出信息
        }
    }
    
    // 2. 格式化命令(添加换行符)
    char buf[256] = {0};
    sprintf(buf, "%s\n", cmd);  // MPlayer要求命令以换行结束
    
    // 3. 发送命令
    int ret = write(fifo_fd, buf, strlen(buf));
    
    // 4. 错误处理
    if (ret < 0) {
        perror("发送命令失败");
        close(fifo_fd);   // 关闭失败的连接
        fifo_fd = -1;     // 标记为需要重新打开
    } else {
        printf("发送命令: %s\n", cmd);  // 成功反馈
    }
}

通信协议

  • 命令格式:命令字符串 + "\n"

  • 管道位置:/tmp/fifo

  • 连接策略:保持连接,失败时重连

3.3 播放函数 do_play()

复制代码
void do_play(char *file) 
{
    // 1. 停止当前运行的MPlayer(如果存在)
    if (mplayer_pid > 0) {
        send_cmd("quit");          // 优雅退出命令
        usleep(100000);            // 等待100ms让MPlayer退出
        kill(mplayer_pid, SIGTERM); // 发送终止信号
        waitpid(mplayer_pid, NULL, 0); // 等待进程结束
        mplayer_pid = 0;           // 清除进程ID记录
    }
    
    // 2. 关闭旧管道连接
    if (fifo_fd >= 0) {
        close(fifo_fd);
        fifo_fd = -1;
    }
    
    // 3. 创建新进程播放
    mplayer_pid = fork();  // 创建子进程
    
    if (mplayer_pid == 0) {  // 子进程
        // 执行MPlayer命令
        execlp("mplayer",          // 使用PATH查找
               "mplayer",          // 程序名
               "-slave",           // 启用从模式(接受命令)
               "-input",           // 指定输入方式
               "file=/tmp/fifo",   // 从管道读取命令
               file,               // 要播放的文件
               "-quiet",           // 减少输出
               NULL);              // 参数结束
        
        // 如果execlp返回,说明执行失败
        perror("启动 mplayer 失败");
        exit(1);  // 子进程退出
    } 
    
    // 父进程继续执行
    printf("开始播放: %s\n", strrchr(file, '/') + 1);  // 显示文件名
}

进程管理策略

  1. 先停止再启动:避免多个MPlayer同时运行

  2. 双重退出机制:先发送quit命令,再发送SIGTERM信号

  3. 进程回收:使用waitpid避免僵尸进程

3.4 列表管理函数

do_list():显示并选择歌曲
复制代码
int do_list()
{
    // 1. 检查列表是否为空
    if (list.total == 0) {
        printf("播放列表为空!\n");
        return -1;
    }
    
    // 2. 显示列表(只显示文件名,不显示完整路径)
    printf("\n=== 歌曲列表 ===\n");
    for (int i = 0; i < list.total; i++) {
        printf("%d. %s\n", i, strrchr(list.name[i], '/') + 1);
        // strrchr找到最后一个'/',+1得到文件名
    }
    
    // 3. 获取用户选择
    printf("输入编号选择歌曲 (0-%d): ", list.total - 1);
    char choose[10] = {0};
    fgets(choose, sizeof(choose), stdin);
    int num = atoi(choose);  // 字符串转整数
    
    // 4. 验证并播放
    if (num >= 0 && num < list.total) {
        list.current = num;          // 更新当前索引
        do_play(list.name[num]);     // 播放选中的文件
        return 0;                    // 成功
    } else {
        printf("无效的选择\n");
        return -1;                   // 失败
    }
}
do_prev()do_next():歌曲切换
复制代码
// 上一首:循环算法
list.current = (list.current - 1 + list.total) % list.total;
// 下一首:循环算法  
list.current = (list.current + 1) % list.total;

循环算法原理

  • % list.total:确保索引在0到total-1范围内

  • + list.total:处理负索引情况

3.5 高级控制函数

do_speed():速度控制
复制代码
int do_speed()
{
    // 显示子菜单
    printf("\n=== 播放速度控制 ===\n");
    printf("1. 加速 1.5x\n");
    printf("2. 减速 0.5x\n");
    printf("3. 恢复正常速度\n");
    printf("输入选择: ");
    
    // 获取选择并发送相应命令
    char input[10];
    fgets(input, sizeof(input), stdin);
    int choice = atoi(input);
    
    switch (choice) {
        case 1: send_cmd("speed_set 1.5"); break;  // 1.5倍速
        case 2: send_cmd("speed_set 0.5"); break;  // 0.5倍速  
        case 3: send_cmd("speed_set 1.0"); break;  // 正常速度
        default: printf("无效选项\n");
    }
    return 0;
}

MPlayer速度命令

  • speed_set 1.5:设置为1.5倍速度

  • speed_set 0.5:设置为0.5倍速度

  • speed_set 1.0:恢复正常速度

do_seek():快进快退
复制代码
int do_seek()
{
    printf("\n=== 快进快退 ===\n");
    printf("1. 前进10秒\n");
    printf("2. 后退10秒\n");
    printf("3. 前进30秒\n");
    printf("4. 后退30秒\n");
    printf("输入选择: ");
    
    char input[10];
    fgets(input, sizeof(input), stdin);
    int choice = atoi(input);
    
    switch (choice) {
        case 1: send_cmd("seek 10"); break;   // 前进10秒
        case 2: send_cmd("seek -10"); break;  // 后退10秒
        case 3: send_cmd("seek 30"); break;   // 前进30秒
        case 4: send_cmd("seek -30"); break;  // 后退30秒
        default: printf("无效选项\n");
    }
    return 0;
}

MPlayer跳转命令

  • seek +N:向前跳转N秒

  • seek -N:向后跳转N秒

3.6 资源管理函数

cleanup():清理资源
复制代码
void cleanup() 
{
    printf("\n正在清理资源...\n");
    
    // 1. 发送退出命令(如果管道已打开)
    if (fifo_fd >= 0) {
        send_cmd("quit");    // 告诉MPlayer退出
        close(fifo_fd);      // 关闭管道
        fifo_fd = -1;        // 标记为未打开
    }
    
    // 2. 终止MPlayer进程(如果存在)
    if (mplayer_pid > 0) {
        kill(mplayer_pid, SIGTERM);          // 发送终止信号
        waitpid(mplayer_pid, NULL, 0);       // 等待进程结束
        mplayer_pid = 0;                     // 清除记录
    }
    
    // 3. 删除管道文件
    unlink("/tmp/fifo");  // 删除命名管道
}

清理顺序

  1. 发送退出命令(优雅退出)

  2. 关闭管道连接

  3. 终止进程(强制退出)

  4. 删除管道文件

handle():信号处理
复制代码
void handle(int sig) 
{
    printf("\n收到信号 %d,正在退出...\n", sig);
    cleanup();  // 清理资源
    exit(0);    // 退出程序
}

处理的信号

  • SIGINT (2):Ctrl+C中断

  • SIGTERM (15):终止信号

3.7 主函数 main()

复制代码
int main(int argc, char **argv)
{
    // 1. 设置信号处理
    signal(SIGINT, handle);   // Ctrl+C处理
    signal(SIGTERM, handle);  // 终止信号处理
    
    // 2. 初始化系统
    do_init();
    
    // 3. 自动播放第一首(如果有文件)
    if (list.total > 0) {
        printf("找到 %d 个媒体文件\n", list.total);
        printf("自动播放第一首歌曲...\n");
        do_play(list.name[0]);  // 播放索引0的文件
        sleep(1);  // 等待MPlayer启动
    } else {
        printf("在 %s 目录中没有找到媒体文件\n", MEDIA_PATH);
        printf("支持格式: .mp4, .mp3, .flv\n");
    }
    
    // 4. 主循环(用户交互)
    while (1) {
        show_menu();  // 显示菜单
        
        char input[256] = {0};
        fgets(input, sizeof(input), stdin);  // 读取用户输入
        input[strcspn(input, "\n")] = 0;     // 去除换行符
        int choice = atoi(input);            // 转换为整数
        
        // 根据选择调用相应功能
        switch (choice) {
            case 1: do_list(); break;   // 列表选择
            case 2: do_pause(); break;  // 暂停/继续
            case 3: do_stop(); break;   // 停止播放
            case 4: do_prev(); break;   // 上一首
            case 5: do_next(); break;   // 下一首
            case 6: do_speed(); break;  // 速度控制
            case 7: do_seek(); break;   // 快进快退
            case 8: do_mode(); break;   // 播放模式
            case 9:                     // 退出程序
                cleanup();
                printf("再见!\n");
                return 0;
            default:                    // 无效输入
                printf("无效选项,请重新输入\n");
        }
    }
    
    return 0;
}

主流程

  1. 初始化

  2. 自动播放

  3. 菜单循环

  4. 退出清理

四、MPlayer命令参考

命令 格式 说明 程序中的使用
pause pause 暂停/继续切换 send_cmd("pause")
stop stop 停止播放 send_cmd("stop")
quit quit 退出MPlayer send_cmd("quit")
seek seek ±N 跳转N秒 send_cmd("seek 10")
speed_set speed_set X 设置速度X倍 send_cmd("speed_set 1.5")
volume volume ±N 调整音量 未使用

五、程序运行流程

5.1 启动流程

复制代码
main()
├── signal()          # 设置信号处理器
├── do_init()         # 初始化
│   ├── 扫描目录      # 查找媒体文件
│   └── 创建管道      # /tmp/fifo
├── 自动播放第一首     # 如果有文件
└── 进入主循环        # 用户交互

5.2 播放流程

复制代码
do_play("文件路径")
├── 停止当前MPlayer(如果存在)
│   ├── send_cmd("quit")
│   ├── kill(SIGTERM)
│   └── waitpid()
├── 关闭旧管道
├── fork()创建子进程
│   └── execlp()启动MPlayer
└── 显示播放信息

5.3 命令发送流程

复制代码
send_cmd("命令")
├── 检查管道是否打开
├── 打开管道(如果需要)
├── 格式化命令(添加\n)
├── write()发送命令
└── 错误处理

六、代码亮点分析

6.1 优点

  1. 健壮的错误处理:区分致命错误和非致命错误

  2. 资源管理完善:正确清理进程和文件

  3. 用户友好:中文界面,清晰提示

  4. 代码复用send_cmd()统一处理命令发送

6.2 潜在问题

  1. 扩展名检查不严谨:只检查最后3个字符

  2. 缓冲区溢出风险 :使用sprintf而非snprintf

  3. 固定数组大小:最多10个文件,路径最长512字节

  4. 缺少MPlayer状态检查:假设MPlayer总是能正常启动

6.3 改进建议

复制代码
// 1. 安全字符串处理
snprintf(buf, sizeof(buf), "%s\n", cmd);

// 2. 更严谨的扩展名检查
char *ext = strrchr(filename, '.');
if (ext && (strcasecmp(ext, ".mp3") == 0 || ...))

// 3. 动态内存分配
char **name = malloc(max_files * sizeof(char*));
for (i = 0; i < max_files; i++) {
    name[i] = malloc(max_path * sizeof(char));
}

七、学习要点总结

7.1 核心技术点

  1. 进程创建与管理:fork()、execlp()、waitpid()

  2. 进程间通信:命名管道(FIFO)

  3. 信号处理:signal()、SIGINT、SIGTERM

  4. 文件系统操作:目录遍历、文件过滤

7.2 编程实践

  1. 模块化设计:功能分离,职责单一

  2. 错误处理策略:分级处理,合理退出

  3. 资源管理:谁申请谁释放原则

  4. 用户交互设计:清晰的菜单和提示

7.3 系统编程概念

  • 进程生命周期管理

  • IPC机制应用

  • 信号与异常处理

  • 文件描述符管理

相关推荐
优选资源分享2 小时前
Qflow v1.6.2 | 开源高效桌面自动化工具
运维·自动化
龘龍龙2 小时前
Python基础学习(六)
开发语言·python·学习
学编程就要猛2 小时前
算法:3.快乐数
java·算法
AI科技星2 小时前
统一场论框架下万有引力常数的量子几何涌现与光速关联
数据结构·人工智能·算法·机器学习·重构
仰泳的熊猫2 小时前
1109 Group Photo
数据结构·c++·算法·pat考试
未来之窗软件服务2 小时前
幽冥大陆(五十八)php1024位密码生成—东方仙盟筑基期
开发语言·算法·仙盟创梦ide·东方仙盟
老兵发新帖2 小时前
ubuntu添加用户完整命令
linux·运维·ubuntu
horizon72742 小时前
如何迁移 WSL2 虚拟机到其他磁盘
linux·ubuntu
悠哉悠哉愿意2 小时前
【EDA学习笔记】电子技术基础知识:基本元件
笔记·嵌入式硬件·学习·eda