返回

FFmpeg 视频转码 MP4 教程及 PHP 集成实践

php

使用 FFmpeg 将视频文件转码为 MP4 格式

用户上传视频到网站服务器是常见的需求。HTML5 的 <video> 标签提供了在网页中播放视频的功能,但浏览器的兼容性参差不齐。MP4 格式凭借其良好的兼容性和压缩率,成为 Web 视频播放的主流选择。对于非 MP4 格式的视频文件,我们需要进行转码。本文主要讲解如何使用 FFmpeg 这一强大工具来满足此项需求。

FFmpeg 简介和安装

FFmpeg 是一款开源、跨平台的音视频处理工具。 包含多个核心组件: ffmpeg命令行工具,进行音视频的编码和转换; ffprobe,查询多媒体文件; 以及 ffplay,简易的多媒体播放器。 另外还有功能强大的 libavcodec, libavformat, libavutil等API 供软件开发者使用。功能相当齐全, 适合不同的业务场景。 依靠它可以完成多种格式视频的解码, 压缩编码, 格式转换, 直播推流等, 因此受到开发人员广泛使用。

安装方式很简单。对于各种Linux系统,通常可以使用包管理器进行安装:

  • Debian / Ubuntu: sudo apt update && sudo apt install ffmpeg
  • Fedora / CentOS / RHEL: sudo dnf install ffmpeg
  • Arch Linux / Manjaro: sudo pacman -S ffmpeg

在 macOS 上,可以考虑用 Homebrew:

  • brew install ffmpeg

安装完成后,打开命令行终端,输入 ffmpeg -version。如果正确显示版本信息,说明安装成功。

使用 FFmpeg 进行视频转码

FFmpeg 功能强大,其命令行参数也比较复杂。针对通用的视频转码到 MP4 的场景,可以按照以下模板操作:

ffmpeg -i input.avi -c:v libx264 -c:a aac -movflags +faststart output.mp4

各参数的作用:

  • -i input.avi: 指定输入文件的路径。将 input.avi 替换为实际的文件路径和名称。
  • -c:v libx264: 指定视频编码器。libx264 是目前非常流行的 H.264 编码器。
  • -c:a aac: 指定音频编码器。AAC 是比较常见的音频编码格式。
  • -movflags +faststart: 将主要的 metadata 信息从视频文件的尾部移到开始。加快播放速度,尤其针对流媒体应用有明显好处。
  • output.mp4: 指定输出文件的路径和名称。可以更改后缀以及文件名称来定义自己的输出文件格式和名字。

这条命令能够满足大部分转码需求。它会将输入的视频文件(例如 input.avi)转换为使用 H.264 视频编码和 AAC 音频编码的 MP4 文件(output.mp4)。
其他细节可以修改参数来实现。 例如调整视频的分辨率, 更改输出质量的参数等。

PHP 集成 FFmpeg

直接调用系统命令,在后端处理时最为简便实用。在服务器安全方面,可以做好充足的配置再执行外部命令,一般不会有什么大问题。

PHP 的 exec() 函数

exec() 是 PHP 中用于执行外部命令的函数。 我们可以使用它来调用 FFmpeg 进行视频转码。

以下是如何修改已有的上传脚本代码。 主要增加转码逻辑。

<?php

//  设置安全的环境变量
putenv('PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin');
    
function is_video($filename) {
    $video_formats = array(
        "mpeg", "mp4", "mov", "avi", "dat", "flv", "3gp", "webm", "wmv", "mkv" // 增加 webm、wmv 和 mkv 格式支持。
    );
    $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    return in_array($ext, $video_formats);
}

function sanitize_filename($filename) {
    $filename = strtr($filename,
    'ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðòóôõöùúûüýÿ',
    'AAAAAACEEEEIIIIOOOOOUUUUYaaaaaaceeeeiiiioooooouuuuyy');
    $filename = preg_replace('/([^.a-z0-9]+)/i', '_', $filename);
    return $filename;
}

if(file_exists($_FILES['media-vid']['tmp_name']) && is_uploaded_file($_FILES['media-vid']['tmp_name']))
{
    $targetvid = md5(time());
    $target_dirvid = "videos/";
    
    // 检查文件是否为视频文件,允许更多的常见视频格式。
    if (!is_video($_FILES["media-vid"]["name"])) {
        echo "Invalid Video Format!";
        exit;
    }

    // 检查文件大小。注意单位是 byte, 500MB
    if ($_FILES["media-vid"]["size"] > 524288000) {
        echo "Sorry, your file is too large.";
        exit;
    }
    
    $original_file = $target_dirvid . $targetvid . basename($_FILES["media-vid"]["name"]);
    $original_file = sanitize_filename($original_file); // 上传之前做好文件名称的安全处理。 规避不必要风险。
    
    // 先将原始文件移动到指定位置。再判断格式和转码,避免存储时数据混乱。
    if (!move_uploaded_file($_FILES["media-vid"]["tmp_name"], $original_file)) {
        echo "Sorry, there was an error uploading your file. Please retry.";
        exit;
    }

    // 判断原始文件的格式
    $video_type = strtolower(pathinfo($original_file, PATHINFO_EXTENSION));
    if ($video_type != "mp4") {
        // 进行视频转码
        $output_file = $target_dirvid . $targetvid . ".mp4";
        $output_file = sanitize_filename($output_file);

        // FFmpeg 命令,进行视频转码,注意安全转义。
        $ffmpeg_cmd = "ffmpeg -i ".escapeshellarg($original_file)." -c:v libx264 -c:a aac -movflags +faststart ".escapeshellarg($output_file)." 2>&1";
        
        $output = "";
        $return_val = 0;

        exec($ffmpeg_cmd, $output, $return_val);

        if ($return_val == 0) {
            // 转码成功,删除原始文件。如果用户只需要处理后的文件的话。根据业务逻辑而定。
            unlink($original_file); 
            $vid = $output_file;
            $nbvid = 1;
        } else {
            // 转码失败,输出错误信息。方便排查。
            echo "Sorry, there was an error converting your video.";
            error_log("FFmpeg error: " . implode("\n", $output)); 
            exit; // 转码失败后要删除上传的原始文件
            unlink($original_file);
        }
    } else {
        // 已经是 MP4 格式,不需要转码
        $vid = $original_file;
        $nbvid = 1;
    }

    // 将视频的存储路径插入到数据库里面即可。
    // Database insertion logic, 待补充. 
}
?>

其它方式: 使用 FFmpeg 的扩展库,比如PHP-FFMpeg

相比 exec 调用命令的方式, 安装专用的库更为规范一些.

使用扩展库: 好处:可以像操作其他的PHP API一样调用函数, 处理效率也会高一些. 不好的地方就是扩展可能兼容性会比较受限.安装需要编译安装,或者从第三方获得, 这都可能带来部署不便或安全性风险。

这种情况下需要服务器安装FFmpeg库, 并启用对应的PHP扩展模块。 可以参考不同系统的安装方法进行操作。
一般涉及源码编译或者yum/apt等工具进行操作。
例如:

#  Debian / Ubuntu 等Linux 系统上操作
sudo apt install php-ffmpeg

# 或者源码编译
# phpize
# ./configure
# make && make install

# 修改php.ini, 加入 extension=ffmpeg.so  等等。

PHP-FFmpeg库依赖PHP和FFmpeg,并且要求ffmpeg的头文件在编译安装扩展时存在. 配置上可能需要摸索。 有时会引入第三方源等等. 这里不做重点讲述。 有特殊要求再考虑这种方案。

安全建议

因为涉及系统调用,PHP 服务器的权限管理非常重要. 文件名称也必须要过滤. 以下是相关的安全措施:

  • 对用户输入的文件名,做校验和转义处理。 使用正则表达式对文件名格式过滤; 文件后缀一定要做检测。避免脚本注入等风险.
  • 运行 PHP 的用户权限,设置专门的用户,而不是root之类的高危账号。这样降低了被攻破带来的系统性风险.
  • 严格设置路径权限.防止FFmpeg操作非期望的目录; 对于用户上传, 临时目录和永久存储, 也设置不同的权限加以区分, 便于追踪。
  • 开启 open_basedir 配置, 仅允许在有限目录内活动,防止越权访问。 例如配置如下: open_basedir = /var/www/html/upload/ , 假设/var/www/html/upload 是文件上传处理脚本所在的目录,以及上传存储的目录。 将权限限制在需要的目录内部。
  • 错误处理很重要, 不给前端暴露太多的后端信息. 排查问题可以通过写入后端log实现. 方便发现非法访问记录等。
  • 使用专门的文件存储服务。对于需要上传大量视频数据的应用, 将视频这类数据交给文件服务器或者对象存储来处理,这样更为方便安全。也更容易做文件数据的维护。

通过服务器,PHP 环境,以及文件系统目录权限的多重限制,可以大幅提升文件处理业务的安全性. 对于用户数据, 要及时备份, 防止机器或者软件异常带来数据丢失. 根据具体的业务需求以及运营环境,安全配置可能有差别。按需选用合理的参数来控制风险。