如何优化PowerShell视频编码脚本,基于CPU使用率实现并行任务?
最优实现方案及脚本修改建议
核心思路
- 替换
Start-Job为Start-ThreadJob:线程作业开销远低于传统后台作业,更适配本地文件转码这类CPU密集型任务 - 动态并发控制:同时维持3-4个运行任务,仅当CPU整体使用率低于80%时启动新任务,充分利用闲置资源
- 任务队列管理:遍历生成待处理文件列表后,循环监控运行任务状态,完成一个就补充一个新任务,直到所有文件处理完毕
具体脚本修改步骤
1. 定义基础配置参数
先设置转码、质量控制和并发相关的固定参数:
$sourceFolder = "C:\Your\FFV1_Folder_Path" $outputFolder = "C:\Your\H264_Output_Path" $initialCRF = 28 $vmafThreshold = 90 $maxConcurrentJobs = 4 $cpuUsageThreshold = 80 $ffmpegPath = "ffmpeg.exe" # 若ffmpeg不在系统PATH,需填写绝对路径 # 自动创建输出目录 if (-not (Test-Path $outputFolder)) { New-Item -ItemType Directory -Path $outputFolder | Out-Null }
2. 筛选FFV1编码的MOV文件
遍历源文件夹,只保留真正的FFV1编码文件,避免误处理:
$sourceFiles = Get-ChildItem -Path $sourceFolder -Filter "*.mov" -File | ForEach-Object { # 通过ffprobe验证视频编码为FFV1 $codec = & $ffmpegPath -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 $_.FullName if ($codec -eq "ffv1") { $_ } }
3. 并行转码与动态控制逻辑
核心循环处理任务队列,结合CPU监控启动新任务:
$runningJobs = @() $currentFileIndex = 0 while ($currentFileIndex -lt $sourceFiles.Count -or $runningJobs.Count -gt 0) { # 清理已完成的任务,获取处理结果 $completedJobs = $runningJobs | Where-Object { $_.State -eq "Completed" } foreach ($job in $completedJobs) { $result = Receive-Job $job Write-Host "处理完成: $($result.SourceFile) | 最终CRF: $($result.FinalCRF) | VMAF分数: $($result.VMAFScore)" Remove-Job $job $runningJobs = $runningJobs | Where-Object { $_ -ne $job } } # 尝试启动新任务 if ($currentFileIndex -lt $sourceFiles.Count -and $runningJobs.Count -lt $maxConcurrentJobs) { # 获取最近10秒CPU平均使用率,避免瞬时波动影响判断 $avgCpuUsage = (Get-Counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 10 | Select-Object -ExpandProperty CounterSamples | Select-Object -ExpandProperty CookedValue) | Measure-Object -Average | Select-Object -ExpandProperty Average if ($avgCpuUsage -lt $cpuUsageThreshold) { $targetFile = $sourceFiles[$currentFileIndex] $currentFileIndex++ # 启动线程作业处理单个文件的转码+VMAF校验流程 $job = Start-ThreadJob -ArgumentList $targetFile, $outputFolder, $initialCRF, $vmafThreshold, $ffmpegPath -ScriptBlock { param($sourceFile, $outputDir, $startCRF, $vmafMinScore, $ffmpeg) $baseName = $sourceFile.BaseName $tempOutput = Join-Path $outputDir "$baseName_temp.mp4" $finalOutput = Join-Path $outputDir "$baseName.mp4" $currentCRF = $startCRF $vmafScore = 0 do { # 执行H.264转码 & $ffmpeg -i $sourceFile.FullName -c:v libx264 -crf $currentCRF -preset medium -c:a copy $tempOutput -y -hide_banner -loglevel error # 计算VMAF分数 $vmafLog = & $ffmpeg -i $sourceFile.FullName -i $tempOutput -lavfi "[0:v]setpts=PTS-STARTPTS[ref];[1:v]setpts=PTS-STARTPTS[dist];[dist][ref]libvmaf=log_fmt=json" -f null - -hide_banner -loglevel error $vmafScore = [double]($vmafLog | Select-String -Pattern '"mean":\s*(\d+\.?\d*)' | ForEach-Object { $_.Matches.Groups[1].Value }) # 调整CRF:分数低于阈值则降低CRF(提升质量),最低限制为18 if ($vmafScore -lt $vmafMinScore) { $currentCRF = [math]::Max(18, $currentCRF - 2) } else { break } # 删除临时文件,准备重新转码 if (Test-Path $tempOutput) { Remove-Item $tempOutput -Force } } while ($currentCRF -ge 18) # 重命名临时文件为最终输出 if (Test-Path $tempOutput) { Rename-Item $tempOutput $finalOutput -Force } # 返回处理结果 [PSCustomObject]@{ SourceFile = $sourceFile.FullName FinalCRF = $currentCRF VMAFScore = $vmafScore } } $runningJobs += $job Write-Host "启动转码任务: $($targetFile.FullName) | 初始CRF: $initialCRF" } } # 短暂休眠,避免循环占用过多CPU Start-Sleep -Seconds 2 }
4. 关键注意事项
- FFmpeg环境要求:必须使用编译了
libvmaf的FFmpeg版本,否则无法计算VMAF分数 - CRF调整策略:示例中采用每次减2的步长,可根据实际需求调整(比如减1更精细),同时设置最低CRF=18防止无限循环
- CPU监控精度:取10秒平均值是为了过滤瞬时CPU峰值,避免误判系统负载
- 资源隔离:线程作业会共享进程内存,但每个任务处理独立文件,不会出现文件冲突问题
内容的提问来源于stack exchange,提问作者Andy Croon
相关产品推荐
相关产品推荐

