Linux:Linux命令行音视频播放器

目录

[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. 功能1:查看扫描到的播放列表。

  3. 功能2:开始播放与暂停播放。

  4. 功能3:停止播放。

  5. 功能4/5:播放上一首/下一首。

  6. 功能6:快进/快退。

  7. 功能7:精确定位到视频的某一时间点。

  8. 功能8:切换播放模式(单曲循环、列表循环、随机播放)。

  9. 功能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);
}

关键点

  1. opendir/readdir/closedir:用于遍历目录。

  2. d_type:判断条目是目录(DT_DIR)还是文件(DT_REG)。

  3. strrchr:从字符串右侧查找字符,用于找到文件名中的最后一个点.以提取后缀。

  4. 将后缀统一转为小写,使判断更健壮(避免.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 程序的编译与执行

  1. 编译

    复制代码
    gcc -o myplayer main.c function.c
  2. 执行

    复制代码
    ./myplayer
  3. 程序启动后

    • 程序会自动扫描当前目录下的媒体文件。

    • 显示操作菜单(例如:[1] 播放列表 [2] 暂停/继续 [3] 下一首 ...)。

    • 根据提示按键进行操作。

4.3 成果展示

一个功能完备的命令行播放器就此诞生。它虽然没有图形界面,但你可以通过键盘完全控制播放过程,体验"极客"式的交互。当我在终端中运行它,并成功用按键控制视频的播放、暂停、切换时,我对Linux进程、IPC、信号等抽象概念有了更具体理解。

项目价值 :这不仅仅是一个播放器,更是一个Linux系统编程的微型综合实验场。你可以在此基础上继续扩展,例如增加播放列表管理、音量控制、网络流媒体播放等功能,每一次扩展都是对系统编程能力的深入锻炼。

相关推荐
YYYing.2 小时前
【Linux/C++网络篇(二) 】TCP并发服务器演进史:从多进程到Epoll的进化指南
linux·服务器·网络·c++·tcp/ip
SPC的存折2 小时前
10、Ansible 生产级故障排查与运维最佳实践
linux·运维·ansible
aP8PfmxS22 小时前
Lab3-page tables && MIT6.1810操作系统工程【持续更新】
java·linux·jvm
飞翔的SA2 小时前
MLX‑VLM :Mac本地跑通多模态大模型的开源项目!让图片、音频、视频理解一键上手
人工智能·python·macos·音视频
林姜泽樾2 小时前
linux入门第十八章,IP、主机名、域名解析
linux·服务器·tcp/ip
深念Y2 小时前
从CH341A编程器、SPI Flash到Linux+STM32理解
linux·stm32·flash·bios·固件·编程器·闪存
RisunJan2 小时前
Linux命令-ncftp(增强的的FTP工具)
linux·运维
Shingmc34 小时前
【Linux】线程互斥与同步
linux
Vect__11 小时前
深刻理解进程、线程、程序
linux