Skip to content

常规视频处理成流媒体 #22

@Dream4ever

Description

@Dream4ever

需求描述

浏览器加载视频资源时,有时会等待资源文件完全下载完成之后才开始播放。如果在移动端播放体积较大的视频文件(音频文件同理),则可能需要等待较长的时间,这样会严重影响用户体验。

为了改善用户体验,就需要把普通的视频处理成体积很小的多个视频切片及对应的索引文件,用户端选择播放指定时刻的视频,只需要下载很小的一个文件之后即可开始播放。

方案调研

调研过程

用 FFmpeg 编译生成 m3u8 视频列表文件和对应的 ts 视频之后,就可以通过前端的 JS 库播放视频。因为编译生成的每个文件体积很小,用户点击指定时刻的视频,只会下载包含这个时刻的一小段视频,这样就可以实现流畅的点播效果了。

在 Windows 的后端部分,只需在 IIS 中为 m3u8 和 ts 配置 MIME 即可。

参考资料

应用过程

视频预处理

拿到的视频文件,先用格式工厂处理成 MP4 格式的标准的视频资源:在 输出配置 中,选择 AVC 系列的预设配置,保证视频采用 AVC(H264) 编码,比特率使用对应的预设配置即可,比如 AVC 1080p 的比特率设置的就是 3072。音频编码用默认的 AAC 即可,采样率用默认的 44100Hz,比特率用默认的 128

因为拿到的视频文件可能有各种编码,曾经处理过 MPEG2 编码的视频,用 FFmpeg 直接处理后,无法在浏览器中播放,花了很长时间才确定问题的原因处在视频编码上。

MP4 切片

用格式工厂预处理之后,再用 FFmpeg 将视频处理成 ts 切片及对应的 m3u8 索引列表即可。

$ ffmpeg.exe -i 01.mp4 -c:v libx264 -c:a copy -f hls -g 600 -hls_list_size 0 video.m3u8

注意:如果输出文件名在输入文件名之后,有时会导致 hls_list_size 参数无效,fuck!

参数解释:

  • -i :后面跟着需要编译的视频的文件名
  • -hls_list_size :m3u8 播放列表文件中记录的 ts 文件个数,默认为 5,需要改成 0,才能完整保存所有编译出来的文件
  • -g :关键帧的间隔?但是单位并不是秒

批量处理

cls
# 设定 FFmpeg 可执行文件的路径
Set-Location -Path "d:\Software\FFmepg\bin\"
# 设定保存所需处理视频路径的文件所在的位置
$file = "e:\1.txt"
try {
    # 尝试读取文件,失败的话则抛出异常
    $Content = Get-Content $file -ErrorAction Stop
    # 遍历文件中的每一行
    foreach ($Line in $Content) {
        # 每一行用英文半角逗号分隔,读取分隔出来的内容并保存至对应的变量中
        $Path, $Count, $Folder = $Line -split ','
        # 合并变量得到视频文件所在的完整目录
        $VideoFolder = Join-Path $Path $Folder
        # 检查路径是否存在
        if (Test-Path -Path $VideoFolder) {
            # 保存该路径下所有视频文件的文件名
            $VideoNames = Get-ChildItem -Path $VideoFolder -File -Name
            # 遍历每个文件名
            foreach ($VideoName in $VideoNames) {
                # 获取视频文件的完整路径
                $VideoPath = Join-Path $VideoFolder $VideoName
                # 获取视频文件在数组中的索引
                $Index = $VideoNames.IndexOf($VideoName)
                # FFmpeg 处理后生成的文件放在 v1/v2 这样的子文件夹中
                $SubFolder = "v" + ([int]$Index + 1)
                # 设定子文件夹的绝对路径
                $SubFolder = Join-Path $VideoFolder $SubFolder
                # 强制新建文件夹,并且指定不输出结果
                New-Item -ItemType directory -Path $SubFolder -Force | Out-Null
                # 设定 m3u8 文件的绝对路径
                $StreamPath = $SubFolder + "\video.m3u8"
                # 以红色输出文件的绝对路径字符串
                Write-Host -ForegroundColor Red $StreamPath
                # 设定 FFmpeg 执行时所需的参数
                $ArgumentList = '-i "{0}" -c:v libx264 -c:a copy -f hls -g 600 -hls_list_size 0 "{1}"' -f $VideoPath, $StreamPath
                # 以绿色输出执行参数字符串
                Write-Host -ForegroundColor Green -Object $ArgumentList
                # 调用 FFmpeg 开始处理视频,等当前处理进程结束之后,才继续执行后面的代码
                Start-Process -FilePath .\ffmpeg.exe -ArgumentList $ArgumentList -Wait -NoNewWindow
            }
        } else {
            Throw "Path not exist!"
        }
        # 输出换行符,便于查看每次处理的结果
        "`n"
    }
} catch {
    # 输出异常的具体信息
    write-host $_.Exception.Message
    return ""
}

限制单个 ts 文件的体积

Google ffmpeg hls limit ts size,在 [feature request] [hls] hls_flags to limit single_file size 中提到了 -hls_segment_size 这个参数。

再 Google ffmpeg hls_segment_size,在 Ffmpeg hls live streaming - How to generate shorter segments 中提到了应该用 -g 这个参数。把这个参数设置为 30 之后,大部分 ts 文件的时间长度都在 1s 左右。网上还看到 -g 这个参数是设置 keyframe interval 的。

于是再 Google ffmpeg hls keyframe interval value,在 What is the CORRECT way to fix keyframes in FFMPEG for DASH? 这里有详细的讨论。

自己测试 -g 参数不同的值,对于 fps 为 30 的视频,结果如下:

  • -g 设置为 30,视频的时长基本在 1s 左右
  • -g 设置为 90,视频的时长基本在 2s 左右
  • -g 设置为 100 和 120,视频的时长基本在 3s 左右

这么一看,想要设置视频时长,并不能简单地用 fps 乘以时长就作为 -g 的值。

要点总结

关联内容:

Metadata

Metadata

Assignees

No one assigned

    Labels

    ServerThe invisible heroSoftwareAbout installation ande usageUXUser experience

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions