前言
在嵌入式 Linux 开发中,音视频播放是非常常见的需求,比如广告机、工业触摸屏、智能家居中控等场景。MPlayer 作为一款轻量级、开源、跨平台的多媒体播放器,对硬件资源要求低,支持几乎所有主流音视频格式,是嵌入式平台的首选方案。
本文基于 RK3399 开发板(NanoPC-T4/SOM-RK3399 通用),从零环境搭建开始,一步步讲解 MPlayer 的基础使用、核心 Slave 模式编程控制,最终实现一个支持自动扫描视频、播放列表、上一首 / 下一首、音量调节、进度跳转的完整视频播放器,所有代码均可直接复制运行。
一、MPlayer 环境快速搭建(免源码编译)
MPlayer 的源码编译需要依赖大量第三方库(FFmpeg、ALSA、x264 等),对新手极不友好。本文直接使用预编译好的完整依赖包,5 分钟即可完成环境搭建,永久生效。
1.1 准备工作
- 准备好预编译包
mplayer-cout.tar.bz2(包含 MPlayer 可执行文件和所有依赖库) - 确保开发板与虚拟机在同一局域网,可通过 SSH 或串口连接
- 开发板已烧录好 Linux 系统,支持 Framebuffer 显示和 ALSA 音频
1.2 安装步骤
步骤 1:传输预编译包到开发板
# 方法1:通过SFTP传输(推荐) sftp root@开发板IP put mplayer-cout.tar.bz2 /root/work/ # 方法2:通过NFS共享目录 cp /mnt/nfs/mplayer-cout.tar.bz2 /root/work/步骤 2:解压并配置环境
# 进入工作目录 cd /root/work/ # 解压压缩包 tar -jxvf mplayer-cout.tar.bz2 cd mplayer-cout/ # 1. 复制可执行文件到系统路径 cp bin/mplayer /bin/ # 2. 复制所有依赖库到系统库路径(必须加-r递归复制) cp -r lib/* /lib/ # 3. 验证安装是否成功 mplayer -version # 输出版本信息即表示安装成功步骤 3:配置默认音频输出(解决没声音问题)
# 编辑ALSA配置文件 vi /etc/asound.conf写入以下内容(RK3399 通用):
pcm.!default { type hw card 0 device 0 } ctl.!default { type hw card 0 }保存退出后,重启开发板使配置生效。
二、MPlayer 基础命令与播放测试
环境搭建完成后,先通过终端命令测试 MPlayer 的基本功能,熟悉常用参数和控制命令。
2.1 核心播放参数
| 参数 | 功能说明 | 常用示例 |
|---|---|---|
-vo fbdev2 | 指定视频输出为 Framebuffer(LCD 屏幕) | -vo fbdev2 |
-ao alsa | 指定音频输出为 ALSA | -ao alsa |
-vf rotate=N | 视频旋转角度 | -vf rotate=1(顺时针 90 度)-vf rotate=0(正常)-vf rotate=-1(逆时针 90 度) |
-zoom -x W -y H | 强制缩放视频到指定尺寸 | -zoom -x 800 -y 1280 |
-vf scale=W:-3 | 等比例缩放视频 | -vf scale=800:-3(宽度 800,高度自动计算) |
-geometry X:Y | 指定视频左上角坐标 | -geometry 0:0(全屏左上角) |
-slave -quiet | 开启 Slave 模式,关闭冗余打印 | -slave -quiet |
-loop N | 循环播放 N 次 | -loop 0(无限循环) |
2.2 常用播放命令示例
# 1. 基础播放(最常用) mplayer -vo fbdev2 -ao alsa test.mp4 # 2. 顺时针旋转90度播放(竖屏LCD必备) mplayer -vo fbdev2 -ao alsa -vf rotate=1 test.mp4 # 3. 等比例缩放至宽度800播放 mplayer -vo fbdev2 -ao alsa -vf scale=800:-3 test.mp4 # 4. 强制全屏播放(800x1280分辨率) mplayer -vo fbdev2 -ao alsa -zoom -x 800 -y 1280 test.mp4 # 5. 无限循环播放 mplayer -vo fbdev2 -ao alsa -loop 0 test.mp42.3 终端交互式控制命令
播放过程中,可在终端输入以下命令控制播放:
| 命令 | 功能 |
|---|---|
pause | 暂停 / 继续播放 |
volume 50 1 | 设置音量为 50(0-100) |
mute 1 | 静音;mute 0取消静音 |
seek 30 | 跳转到第 30 秒播放 |
get_time_length | 获取视频总时长(秒) |
get_time_pos | 获取当前播放位置(秒) |
quit | 退出播放 |
三、核心:Slave 模式编程控制
实际项目中,我们不可能通过终端手动控制播放器,必须通过 C 语言程序实现自动化控制。MPlayer 的Slave 模式就是为此设计的,它允许程序通过管道向 MPlayer 发送命令,实现完全的编程控制。
3.1 Slave 模式原理
- 普通模式:MPlayer 截获键盘事件,从终端读取控制命令
- Slave 模式:MPlayer 后台运行,从命名管道(FIFO)读取控制命令,不再响应键盘
- 程序通过向命名管道写入命令字符串,即可实现对 MPlayer 的所有控制
3.2 完整 C 语言控制代码
以下代码实现了 MPlayer 的启动、播放、暂停、音量调节、进度跳转、停止等核心功能:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <string.h> #include <signal.h> #define FIFO_PATH "/tmp/mplayer_fifo" static pid_t mplayer_pid = -1; static int fifo_fd = -1; /** * @brief 向MPlayer发送命令 * @param cmd 命令字符串 */ void mplayer_send_cmd(const char *cmd) { if (fifo_fd < 0 || cmd == NULL) { return; } write(fifo_fd, cmd, strlen(cmd)); write(fifo_fd, "\n", 1); // 命令必须以换行符结尾 } /** * @brief 启动MPlayer并进入Slave模式 * @param video_path 视频文件路径 * @return 成功返回0,失败返回-1 */ int mplayer_start(const char *video_path) { // 1. 创建命名管道 unlink(FIFO_PATH); if (mkfifo(FIFO_PATH, 0666) < 0) { perror("mkfifo failed"); return -1; } // 2. 创建子进程运行MPlayer mplayer_pid = fork(); if (mplayer_pid < 0) { perror("fork failed"); unlink(FIFO_PATH); return -1; } // 子进程:执行MPlayer if (mplayer_pid == 0) { execlp("mplayer", "mplayer", "-slave", "-quiet", "-input", "file="FIFO_PATH, "-vo", "fbdev2", "-ao", "alsa", "-zoom", "-x", "800", "-y", "1280", // 根据自己的LCD分辨率修改 video_path, NULL); perror("execlp mplayer failed"); exit(EXIT_FAILURE); } // 父进程:打开命名管道写端 fifo_fd = open(FIFO_PATH, O_WRONLY); if (fifo_fd < 0) { perror("open fifo failed"); kill(mplayer_pid, SIGKILL); unlink(FIFO_PATH); return -1; } printf("MPlayer启动成功,正在播放:%s\n", video_path); return 0; } /** * @brief 停止MPlayer并释放资源 */ void mplayer_stop(void) { if (mplayer_pid > 0) { mplayer_send_cmd("quit"); usleep(500000); // 等待MPlayer正常退出 kill(mplayer_pid, SIGKILL); mplayer_pid = -1; } if (fifo_fd > 0) { close(fifo_fd); fifo_fd = -1; } unlink(FIFO_PATH); printf("MPlayer已停止\n"); } // 测试主函数 int main() { char cmd[128]; // 启动播放 if (mplayer_start("/root/video/test.mp4") < 0) { return -1; } // 简单控制台交互 while (1) { printf("\n===== MPlayer控制菜单 =====\n"); printf("1. 暂停/继续\n"); printf("2. 设置音量(0-100)\n"); printf("3. 跳转到指定秒数\n"); printf("4. 停止并退出\n"); printf("请输入选项:"); fflush(stdout); fgets(cmd, sizeof(cmd), stdin); int choice = atoi(cmd); switch (choice) { case 1: mplayer_send_cmd("pause"); break; case 2: printf("请输入音量(0-100):"); fgets(cmd, sizeof(cmd), stdin); int vol = atoi(cmd); sprintf(cmd, "volume %d 1", vol); mplayer_send_cmd(cmd); break; case 3: printf("请输入跳转秒数:"); fgets(cmd, sizeof(cmd), stdin); int sec = atoi(cmd); sprintf(cmd, "seek %d 0", sec); mplayer_send_cmd(cmd); break; case 4: mplayer_stop(); return 0; default: printf("无效选项\n"); break; } } return 0; }3.3 编译与运行
# 编译代码 aarch64-linux-gnu-gcc mplayer_ctl.c -o mplayer_ctl # 传输到开发板 sftp root@开发板IP put mplayer_ctl /root/ put test.mp4 /root/video/ # 运行程序 chmod +x mplayer_ctl ./mplayer_ctl四、进阶:实现自动扫描视频播放列表
上面的代码只能播放单个视频,实际项目中通常需要自动扫描指定目录下的所有视频文件,生成播放列表,支持上一首 / 下一首切换。这里结合 Linux 文件夹遍历函数实现该功能。
4.1 文件夹遍历核心函数
基于opendir/readdir/closedir实现,自动过滤非视频文件:
#include <dirent.h> #include <string.h> // 视频文件后缀列表 static const char *video_suffix[] = {".mp4", ".avi", ".mpg", ".mov", ".mkv", NULL}; /** * @brief 判断文件是否为视频文件 * @param filename 文件名 * @return 是视频文件返回1,否则返回0 */ static int is_video_file(const char *filename) { const char **suffix = video_suffix; while (*suffix != NULL) { if (strstr(filename, *suffix) != NULL) { return 1; } suffix++; } return 0; } /** * @brief 扫描指定目录下的所有视频文件 * @param dir_path 目录路径 * @param video_list 输出:视频文件路径数组 * @param max_count 最大支持的视频数量 * @return 扫描到的视频文件个数 */ int scan_video_files(const char *dir_path, char **video_list, int max_count) { DIR *dir = opendir(dir_path); if (dir == NULL) { perror("opendir failed"); return 0; } struct dirent *entry; int count = 0; while ((entry = readdir(dir)) != NULL && count < max_count) { // 跳过.和.. if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } // 只处理普通文件 if (entry->d_type == DT_REG && is_video_file(entry->d_name)) { // 拼接完整路径 video_list[count] = malloc(256); snprintf(video_list[count], 256, "%s/%s", dir_path, entry->d_name); count++; } } closedir(dir); return count; } /** * @brief 释放视频列表内存 * @param video_list 视频文件路径数组 * @param count 视频个数 */ void free_video_list(char **video_list, int count) { for (int i = 0; i < count; i++) { free(video_list[i]); } }4.2 播放列表功能整合
在原有代码基础上添加播放列表索引管理,实现上一首 / 下一首切换:
#define MAX_VIDEO_COUNT 100 static char *video_list[MAX_VIDEO_COUNT]; static int video_count = 0; static int current_index = 0; /** * @brief 播放指定索引的视频 * @param index 视频索引 */ void play_video(int index) { if (index < 0 || index >= video_count) { return; } // 停止当前播放 mplayer_stop(); // 播放新视频 current_index = index; mplayer_start(video_list[current_index]); } // 修改主函数,添加播放列表功能 int main() { char cmd[128]; // 扫描视频目录 video_count = scan_video_files("/root/video", video_list, MAX_VIDEO_COUNT); if (video_count == 0) { printf("未找到任何视频文件\n"); return -1; } printf("扫描到%d个视频文件\n", video_count); // 播放第一个视频 play_video(0); // 控制台交互 while (1) { printf("\n===== 视频播放器控制菜单 =====\n"); printf("当前播放:第%d个 / 共%d个\n", current_index + 1, video_count); printf("1. 暂停/继续\n"); printf("2. 设置音量(0-100)\n"); printf("3. 上一首\n"); printf("4. 下一首\n"); printf("5. 停止并退出\n"); printf("请输入选项:"); fflush(stdout); fgets(cmd, sizeof(cmd), stdin); int choice = atoi(cmd); switch (choice) { case 1: mplayer_send_cmd("pause"); break; case 2: printf("请输入音量(0-100):"); fgets(cmd, sizeof(cmd), stdin); int vol = atoi(cmd); sprintf(cmd, "volume %d 1", vol); mplayer_send_cmd(cmd); break; case 3: play_video((current_index - 1 + video_count) % video_count); break; case 4: play_video((current_index + 1) % video_count); break; case 5: mplayer_stop(); free_video_list(video_list, video_count); return 0; default: printf("无效选项\n"); break; } } return 0; }五、常见踩坑与排错指南
5.1 播放视频没有声音
- 检查耳机是否插好,开发板是否有硬件音频输出
- 确认
/etc/asound.conf配置正确,重启开发板 - 测试 ALSA 音频是否正常:
aplay test.wav - 播放时必须添加
-ao alsa参数
5.2 播放视频黑屏
- 确认 Framebuffer 设备存在:
ls /dev/fb0 - 播放时必须添加
-vo fbdev2参数 - 检查视频分辨率是否超过 LCD 分辨率,使用
-zoom -x W -y H强制缩放 - 关闭桌面系统:
systemctl stop lightdm(桌面会占用 Framebuffer)
5.3 Slave 模式命令不响应
- 检查命名管道是否创建成功:
ls /tmp/mplayer_fifo - 发送的命令必须以 ** 换行符
\n** 结尾 - 确保 MPlayer 启动时添加了
-slave -input file=/tmp/mplayer_fifo参数 - 不要在 MPlayer 运行时手动按键盘,会导致 Slave 模式失效
5.4 视频播放卡顿
- 使用 H.264 编码的视频,分辨率不超过 LCD 分辨率
- 关闭 MPlayer 的冗余打印:添加
-quiet参数 - 将视频文件拷贝到开发板本地播放,不要通过 NFS 网络播放
- 降低视频帧率(建议 25fps 以下)
5.5 中文文件名乱码
- 设置开发板系统编码为 UTF-8:
export LANG=en_US.UTF-8 - 视频文件名尽量使用英文和数字,避免中文
本文所有代码均基于标准 C 语言实现,无平台依赖,如果本文对你有帮助,欢迎点赞收藏,有任何问题可在评论区交流讨论。