2008/03/08

H.264/HE-AAC Encode

最近線上視訊網站有了一些新聞,如以高解析高畫質 DivX 檔案為特色的 Stage6 宣布關閉 (聽說一個月線路費一百萬美金,燒錢啊),和ニコニコ動画 SP1 更進一步支援了 H.264 視訊編碼。據說 2007 年 6 月 YouTube 就有在私底下試驗 H.264 (可以在網址列加上 &fmt=18 開啟),這下子終於可以擺脫 FLV1 那個連影片內文都看不清楚的低畫質了 (FLV4 的 VP6 雖然近一步提升,但果然 H.264 還是更佳啊)

其實上面網站要支援 H.264 根本就不是難事,去年 (2007) 底 Adobe 釋出了 Flash Player 9.0.115.0 修正版本,這個版本就導入了 H.264 支援。換言之關鍵的是你瀏覽器上面的 Flash 元件,所以才聽說有人在ニコニコ動画 SP1 看 H.264 影片需要更新吧?(我個人是去年看到有網站在討論重大安全性更新就手動更新了) 實際試驗手上能用的 Mediaplayer.swf 看看,這個 SWF 檔使用了媒體播放元件,可以線上播放 FLV 檔案,塞給它 MP4 檔吃,一樣可行,證實了我的想法。於是需要調整適應的地方不算很多,可以快速享受 H.264 的高畫質,只不過電腦配備 (CPU、GPU) 不夠好的朋友們可得注意了。

所以要玩的話就得要靠一些工具幫助我們編碼,以前的 FLV1 可以用懶人工具 Riva FLV Encoder 快速編碼,FLV4 時代也有攜帶動畫變換君這種 N 合一的東西可以幫助我們,H.264 雖然也有一些工具 (轉給 PSP 或 iPod 看的),不過我比較喜歡自己找工具來搭配,網路上看到有人用 AviUtil 在轉 (Ref: x264(mp4) encode教學 (採用ニコニコ動画及AviUtl為例)),各位不妨做個參考。

我選擇土砲法,才能充分理解壓縮步驟,準備以下工具:

AviSynth
Frame server,我要用它的 Lanczos4Resize 功能來幫我 Downscale 影片

x264 CLI Encoder
x264 是一套知名的 H.264 壓縮實作,用人家編好的版本來作視訊壓縮

Nero AAC Codec
Nero 大發慈悲,AAC音訊編碼器完全免費!不用可惜

MP4Box
GPAC 套裝的一部分,用來將視訊 H.264/AVC 跟音訊 AAC 包在一起的程式

FFmpeg
老實說前面的工具可以通通不要就用 FFmpeg 就好,但一來沒有學習效果,二來我使用 Unofficial FFmpeg Win32 Builds 進行 x264 壓縮怎麼試怎麼失敗 (好像是函式庫對 Intel 雙核支援的問題?目前作者也沒解決,放很久了),而且似乎也沒有 LanczosResize 可以用,不得已拋棄最簡易方案。這邊還是要用它抽出 WAV 音訊以便讓 Nero AAC Codec 讀取


以上就是主要的程式,流程為:

來源檔案 → Lanczos4Resize (AviSynth) → H.264/AVC (x264) *視訊
└→ WAV (FFmpeg) → AAC (Nero AAC Codec) *音訊

最後再 H.264/AVC + AAC → MP4 (MP4Box),大功告成,其實也不難嘛。

自己為了方便,寫了一個批次檔來負責這一大堆動作,全面自動化。隨著不斷的測試和調整,批次檔的也改的越來越人性化,可以直接拖檔案給它處理,只不過又使用了其他東西 (比如說 Horst Schaeffer 的 Wfolder,選取目標資料夾用輔助程式) 和多了一些判斷,諸位可以無視,畢竟那不是重點。

@ECHO OFF
SETLOCAL
SET BASEPATH=%~dp0
SET VBITRATE=500
REM SET VRES=512, 288
SET VRES=512, 384
SET /a ABITRATE=64 * 1000
SET ACOMPRESSION=-he
SET VCOMPRESSION=qualityEX
REM fast, quality, qualityEX

REM Set destination directory
"%BASEPATH%Wfolder2" "SET DESTDIR=" . "Choose a destination directory" /noquote /backslash > "%TEMP%\result_destfile.bat"
CALL "%TEMP%\result_destfile"
IF NOT %ERRORLEVEL% == 0 GOTO :EOF

ECHO Creating avs script...
IF /I %~x1 == .AVI GOTO TYPE_AVI
IF /I %~x1 == .DIVX GOTO TYPE_AVI
GOTO TYPE_OTHER

:TYPE_AVI
ECHO AviSource("%~1", false).Lanczos4Resize(%VRES%) > "%TEMP%\result_frame.avs"
GOTO IF2

:TYPE_OTHER
ECHO DirectShowSource("%~1", convertfps=true, audio=false).Lanczos4Resize(%VRES%) > "%TEMP%\result_frame.avs"

:IF2
ECHO Encoding video...
IF NOT %VCOMPRESSION% == fast GOTO quality

REM Fast Compression
ECHO Preset: Fast
"%BASEPATH%x264" --threads 2 --progress --pass 1 --bitrate %VBITRATE% --stats "%TEMP%\result_x264.stats" --subme 6 --ref 5 --filter 0:0 --keyint 250 --min-keyint 25 --direct temporal --sar 1:1 --bframes 2 --b-pyramid --weightb --8x8dct --ipratio 1.40 --pbratio 1.30 --qcomp 0.8 --analyse p8x8,i4x4 -o NUL "%TEMP%\result_frame.avs"
"%BASEPATH%x264" --threads 2 --progress --pass 2 --bitrate %VBITRATE% --stats "%TEMP%\result_x264.stats" --subme 6 --ref 5 --filter 0:0 --keyint 250 --min-keyint 25 --direct temporal --sar 1:1 --bframes 2 --b-pyramid --weightb --8x8dct --ipratio 1.40 --pbratio 1.30 --qcomp 0.8 --analyse b8x8,p8x8,p4x4,i4x4 -o "%TEMP%\result_video.mp4" "%TEMP%\result_frame.avs"
GOTO encodeaudio

:quality
IF %VCOMPRESSION% == quality ECHO Preset: Quality
IF %VCOMPRESSION% == qualityEX ECHO Preset: Quality Extreme

REM Quality Compression
"%BASEPATH%x264" --bframes 16 --ref 5 --keyint 250 --min-keyint 25 --b-pyramid --qcomp 1.0 --subme 7 --analyse all --weightb --8x8dct --trellis 2 --me umh --mixed-refs --direct auto --threads 2 --thread-input --no-psnr --no-ssim --no-fast-pskip --no-dct-decimate --progress --pass 1 --bitrate %VBITRATE% --stats "%TEMP%\result_x264.stats" --sar 1:1 --ipratio 1.40 --pbratio 1.30 -o NUL "%TEMP%\result_frame.avs"
IF NOT %VCOMPRESSION% == qualityEX GOTO finalpass
REM Quality Extreme Compression
"%BASEPATH%x264" --bframes 16 --ref 5 --keyint 250 --min-keyint 25 --b-pyramid --qcomp 1.0 --subme 7 --analyse all --weightb --8x8dct --trellis 2 --me umh --mixed-refs --direct auto --threads 2 --thread-input --no-psnr --no-ssim --no-fast-pskip --no-dct-decimate --progress --pass 3 --bitrate %VBITRATE% --stats "%TEMP%\result_x264.stats" --sar 1:1 --ipratio 1.40 --pbratio 1.30 -o NUL "%TEMP%\result_frame.avs"
:finalpass
"%BASEPATH%x264" --bframes 16 --ref 5 --keyint 250 --min-keyint 25 --b-pyramid --qcomp 1.0 --subme 7 --analyse all --weightb --8x8dct --trellis 2 --me umh --mixed-refs --direct auto --threads 2 --thread-input --no-psnr --no-ssim --no-fast-pskip --no-dct-decimate --progress --pass 2 --bitrate %VBITRATE% --stats "%TEMP%\result_x264.stats" --sar 1:1 --ipratio 1.40 --pbratio 1.30 -o "%TEMP%\result_video.mp4" "%TEMP%\result_frame.avs"

:encodeaudio
ECHO Encoding audio...
"%BASEPATH%ffmpeg" -y -i %1 -ar 48000 -ac 2 "%TEMP%\result_audio.wav"
"%BASEPATH%neroAacEnc" -br %ABITRATE% %ACOMPRESSION% -2pass -if "%TEMP%\result_audio.wav" -of "%TEMP%\result_audio.m4a"

ECHO Merging mp4...
"%BASEPATH%MP4Box" -add "%TEMP%\result_video.mp4#video" -add "%TEMP%\result_audio.m4a#audio" -new "%DESTDIR%merged.mp4"

ECHO Job finished.
PAUSE
del "%TEMP%\result_*.*" /Q


我想批次檔用了許多技巧,看不懂也是有可能的,不過還是抓幾個重點來介紹。首先是使用 AviSynth 這一部分,我使用 ECHO 動態了生成 avs 檔案,裡面指定了 Lanczos4Resize 來作畫面縮小的動作。

再來是重頭戲 x264,可以看到指令列跟了長長一串,這就是可以學習到的地方。我不敢保證上面列的參數是最完美的,但我也參考了很多地方如漫遊酷論壇、Doom9等地方的許多討論串,得出每個參數的大致功用,依照推薦所設定的。關鍵還是在「--bitrate %VBITRATE%」這邊,指定視訊流量。另外「--pass」是開啟 Multipass 的選項 (二次編碼),聽說 3pass 還是有一點用,所以我也用上了。

接著是音訊部分,使用 FFmpeg 將來源檔 (注意我沒用 AviSynth 處理)的音訊解成 WAV 後,再給 neroAacEnc 吃。值得一提的是 neroAacEnc 提供兩個執行檔,我使用有 SSE 的版本,CPU 指令集有支援應該比較好吧。關鍵在「-br %ABITRATE% %ACOMPRESSION%」這邊,因為是批次檔所以夾雜變數,範例中會變成「-br 64000 -he」,這表示壓成 64Kbps 的 HE-AAC (-he 為強制壓成 HE-AAC,這種模式下壓縮低流量品質仍十分好),當然也要加上「-2pass」來給它二次編碼用時間換品質,畢竟壓縮 AAC 速度非常快,這點時間算不了什麼。

最後就使用 MP4Box 加入兩個檔案的視訊跟音訊,建立新檔案 merged.mp4,這個檔案就是辛苦的結晶啦,用 VLC、Media Player Classic 或是 KMPlayer 都可以看吧,如果有安裝 ffdshow, MP4Splitter.ax, CoreAVC 等想必會更好,觀看部分我就不特別著墨,反正大家都會裝整合包所以應該都可以看吧。
---
2008/12/12 修改: 更新 Nero AAC Codec 下載位置跟 x264 參數,新版取消 --bime 跟 --b-rdo

2 則留言:

  1. 在 http://oss.netfarm.it/mplayer-win32.php 下載的ffmpeg好像沒這個問題呢…

    回覆刪除
  2. 補充一下,現在的 x264 Encoder 已經取消 --b-rdo 和 --bime 這兩個參數了。
    Nero AAC Encoder 也已經沒有SSE特別編譯版。

    回覆刪除