前言
在嵌入式 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.mp4
2.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 的启动、播放、暂停、音量调节、进度跳转、停止等核心功能:
cpp
#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实现,自动过滤非视频文件:
cpp
#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 播放列表功能整合
在原有代码基础上添加播放列表索引管理,实现上一首 / 下一首切换:
cpp
#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 语言实现,无平台依赖,如果本文对你有帮助,欢迎点赞收藏,有任何问题可在评论区交流讨论。