本文目录导读:

针对PHP项目中的会议录制文件存储、转码与管理,这是一个典型的视频处理与媒资管理场景,下面从架构设计、技术选型到代码实现,提供一套完整的解决方案。
整体架构设计
录制文件上传 → 存储系统 → 异步转码队列 → 转码处理 → 转码结果存储 → 播放/管理
核心组件:
- 存储层:对象存储(OSS/S3)或本地NAS
- 转码引擎:FFmpeg(命令行工具或PHP扩展)
- 队列系统:Redis + PHP队列(如 Laravel Queue / 自定义)
- 数据库:MySQL/PostgreSQL 记录元数据与转码状态
- 播放器:Video.js / HLS.js(支持HLS流)
存储方案
推荐:对象存储(阿里云OSS / AWS S3 / MinIO)
// 使用阿里云OSS SDK示例 use OSS\OssClient; $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint); // 上传原始录制文件 $ossClient->uploadFile($bucket, 'recordings/2025/04/original/meeting_123.mp4', $localFilePath);
优势:弹性扩容、CDN加速、支持分片上传大文件。
本地存储(小规模场景)
$basePath = '/data/recordings/';
$datePath = date('Y/m/d');
$targetPath = $basePath . $datePath . '/' . $fileName;
move_uploaded_file($_FILES['file']['tmp_name'], $targetPath);
注意:需要配置Nginx/Apache的访问权限与防盗链。
文件命名规范
function generateFileName($meetingId, $extension = 'mp4') {
$timestamp = time();
$uuid = uniqid();
return sprintf('%s_%s_%s.%s', $meetingId, $timestamp, $uuid, $extension);
}
转码方案
转码目标(推荐输出格式)
| 用途 | 格式 | 说明 |
|---|---|---|
| 播放 | HLS (.m3u8) | 自适应码率,支持分段加载 |
| 播放 | MP4 | 兼容老设备 |
| 缩略图 | JPEG/PNG | 视频封面 |
| 音频 | MP3/AAC | 语音回放 |
使用 FFmpeg 转码(核心)
class VideoTranscoder {
private $ffmpegPath = '/usr/bin/ffmpeg';
public function transcodeToHLS($inputFile, $outputDir) {
$cmd = sprintf(
'%s -i %s -c:v libx264 -c:a aac -strict -2 -f hls -hls_time 10 -hls_list_size 0 -hls_segment_filename %s/segment_%%03d.ts %s/playlist.m3u8',
$this->ffmpegPath,
escapeshellarg($inputFile),
escapeshellarg($outputDir),
escapeshellarg($outputDir)
);
exec($cmd, $output, $returnCode);
return $returnCode === 0;
}
public function generateThumbnail($inputFile, $outputFile, $time = '00:00:05') {
$cmd = sprintf(
'%s -i %s -ss %s -vframes 1 -vf scale=320:-1 %s',
$this->ffmpegPath,
escapeshellarg($inputFile),
$time,
escapeshellarg($outputFile)
);
exec($cmd, $output, $returnCode);
return $returnCode === 0;
}
}
多码率自适应转码(高级)
public function transcodeAdaptive($inputFile, $outputBase) {
// 生成 360p, 720p, 1080p
$profiles = [
['width' => 640, 'height' => 360, 'bitrate' => '800k', 'suffix' => '360p'],
['width' => 1280, 'height' => 720, 'bitrate' => '2500k', 'suffix' => '720p'],
['width' => 1920, 'height' => 1080, 'bitrate' => '5000k', 'suffix' => '1080p'],
];
foreach ($profiles as $p) {
// 每个分辨率生成独立的HLS目录
}
// 最后合并生成主playlist.m3u8
}
异步任务队列管理
数据库表结构设计
CREATE TABLE recordings (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
meeting_id VARCHAR(64) NOT NULL,
original_filename VARCHAR(255),
original_path VARCHAR(500), -- 原始文件路径/OSS key
file_size BIGINT,
duration INT, -- 视频时长(秒)
status ENUM('pending', 'uploaded', 'transcoding', 'completed', 'failed') DEFAULT 'pending',
hls_path VARCHAR(500), -- HLS播放地址
thumbnail_path VARCHAR(500), -- 缩略图路径
created_at DATETIME,
updated_at DATETIME
);
CREATE TABLE transcoding_jobs (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
recording_id BIGINT,
job_type ENUM('hls', 'thumbnail', 'audio'),
status ENUM('queued', 'processing', 'completed', 'failed'),
output_path VARCHAR(500),
error_message TEXT,
created_at DATETIME,
completed_at DATETIME
);
队列处理流程(基于Redis)
// 1. 上传完成后,将转码任务推入队列
$redis->lpush('transcode:queue', json_encode([
'recording_id' => 123,
'input_path' => '/path/to/original.mp4',
'output_dir' => '/data/hls/meeting_123/'
]));
// 2. Worker进程从队列中取出并执行
while ($job = $redis->rpop('transcode:queue')) {
$data = json_decode($job, true);
$transcoder = new VideoTranscoder();
$result = $transcoder->transcodeToHLS($data['input_path'], $data['output_dir']);
// 更新数据库状态
updateRecordingStatus($data['recording_id'], $result ? 'completed' : 'failed');
}
管理后台功能
文件管理界面(示例逻辑)
// 获取录制列表
$recordings = DB::table('recordings')
->where('meeting_id', $meetingId)
->orderBy('created_at', 'desc')
->get();
// 显示播放/下载/删除操作
foreach ($recordings as $rec) {
echo '<a href="' . $rec->hls_path . '">播放</a>';
echo '<a href="/download?id=' . $rec->id . '">下载</a>';
echo '<button onclick="deleteRecording(' . $rec->id . ')">删除</button>';
}
// 删除时,需同时清理OSS/本地文件
function deleteRecording($id) {
$rec = DB::table('recordings')->find($id);
// 删除原始文件
Storage::delete($rec->original_path);
// 删除HLS目录
Storage::deleteDirectory(dirname($rec->hls_path));
// 删除数据库记录
DB::table('recordings')->delete($id);
}
状态监控与重试机制
// 检测失败任务
$failedJobs = DB::table('transcoding_jobs')
->where('status', 'failed')
->where('created_at', '>', now()->subHours(1))
->get();
foreach ($failedJobs as $job) {
// 重试逻辑:最多重试3次
if ($job->retry_count < 3) {
$redis->lpush('transcode:queue', $job->payload);
DB::table('transcoding_jobs')
->where('id', $job->id)
->increment('retry_count');
}
}
播放器集成
<!-- HLS播放器示例 -->
<link href="https://cdn.jsdelivr.net/npm/video.js@7/dist/video-js.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/video.js@7/dist/video.min.js"></script>
<video id="my-video" class="video-js vjs-default-skin" controls width="800" height="450">
<source src="https://your-domain.com/recordings/meeting_123/playlist.m3u8" type="application/x-mpegURL">
</video>
<script>
var player = videojs('my-video');
</script>
关键注意事项
| 问题 | 解决方案 |
|---|---|
| 大文件上传超时 | 使用分片上传(OSS分片 / 前端分块) |
| 转码CPU/内存消耗 | 使用进程队列 + 限制并发数(如 Supervisor) |
| 存储成本优化 | 设置生命周期规则:原始文件自动删除,保留HLS |
| 播放卡顿 | 使用CDN加速 + 多码率自适应 |
| 安全性 | 播放地址加签(OSS防盗链 / 动态Token) |
推荐技术栈(生产级)
- 存储:阿里云OSS + CDN
- 转码服务:阿里云/腾讯云媒体处理服务(代替自建FFmpeg)
- 队列:Laravel Horizon / RabbitMQ
- 框架:Laravel(自带Queue、Storage、ORM等)
如果预算有限,可以用 FFmpeg + Redis队列 + Nginx 搭建轻量方案,能满足中小规模(日均1000+会议)需求。
需要更具体的代码实现(如分片上传、转码进度回调等)可以进一步沟通。