我在直播完後通常會錄製出 *.mkv
這種影片格式的檔案,但是我無法透過 Camtasia Studio 對該 MKV 的檔案進行後製 (不支援),一定要先將影片轉換為 *.mp4
或其他支援的格式才能編輯。但是上網查找轉換格式的工具,大多都是需要付費的,很多人不知道的是,其實大部分的轉換工作都可以透過 FFmpeg 命令列工具來完成。今天這篇文章我就來介紹一下這套強大的轉檔工具,順便分享幾個我常用的命令與參數。
簡介 FFmpeg
FFmpeg 是一個開放原始碼的自由軟體,它包含了音訊和視訊多種格式的錄影、轉檔、串流功能,同時也是一個音訊與視訊格式轉換函式庫(Library),許多開源的工具都是基於 FFmpeg 打造的。
FFmpeg
這個單詞中的 FF
指的是 Fast Forward
(快轉) 的意思。
FFmpeg
主要由以下幾個元件組成:
-
命令列應用程式 (CLI)
ffmpeg
:用於對視訊檔案或音訊檔案轉換格式
ffplay
:一個簡單的播放器,基於 SDL 與 FFmpeg
函式庫
ffprobe
:用於顯示媒體檔案的資訊,見 MediaInfo
-
函式庫 (Libraries)
libavcodec
:包含全部 FFmpeg
音訊/視訊編解碼函式庫(Codec)
libavformat
:包含 demuxers
和 muxer
函式庫
libavutil
:包含一些工具函式庫
libpostproc
:對於視訊做前處理的函式庫
libswscale
:對於影像作縮放的函式庫
libavfilter
libswresample
libavresample
我之前一直都覺得,為什麼這個世界已經有 FFmpeg
這樣強大的工具,還會有這麼多付費的影音轉檔工具存在呢?其實是影音轉檔牽涉的知識實在是太太太太廣了,如果真的研究下去,你會發現這門技術跟無底洞沒甚麼兩樣,太複雜了,永遠學不完的感覺。不信嗎?你可以看看以下三個命令的說明就知道了!😅
-
ffmpeg
完整文件請看 ffmpeg 與 ffmpeg-all
ffmpeg -help
ffmpeg -help 輸出結果
-
ffprobe
完整文件請看 ffprobe 與 ffprobe-all
ffprobe -help
-
ffplay
完整文件請看 ffplay 與 ffplay-all
ffplay -help
ffplay -help 輸出結果
ffprobe -help 輸出結果
安裝 FFmpeg 工具
使用 Chocolatey 來安裝 ffmpeg 套件應該是最簡單的了:
choco install ffmpeg -y
查看版本資訊:
-
ffmpeg
ffmpeg -version
ffmpeg version 5.0.1-essentials_build-www.gyan.dev Copyright (c) 2000-2022 the FFmpeg developers
built with gcc 11.2.0 (Rev7, Built by MSYS2 project)
configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-lzma --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-sdl2 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-libass --enable-libfreetype --enable-libfribidi --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libgme --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libtheora --enable-libvo-amrwbenc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-librubberband
libavutil 57. 17.100 / 57. 17.100
libavcodec 59. 18.100 / 59. 18.100
libavformat 59. 16.100 / 59. 16.100
libavdevice 59. 4.100 / 59. 4.100
libavfilter 8. 24.100 / 8. 24.100
libswscale 6. 4.100 / 6. 4.100
libswresample 4. 3.100 / 4. 3.100
libpostproc 56. 3.100 / 56. 3.100
-
ffplay
ffplay -version
ffplay version 5.0.1-essentials_build-www.gyan.dev Copyright (c) 2003-2022 the FFmpeg developers
-
ffprobe
ffprobe -version
ffprobe version 5.0.1-essentials_build-www.gyan.dev Copyright (c) 2007-2022 the FFmpeg developers
使用 ffmpeg
進行影音格式轉檔
-
將 MKV (視訊) 轉 MP4 (視訊) 格式
ffmpeg -i 'test.mkv' -c copy 'test.mp4'
如果上述命令有問題,才改用以下命令執行:
ffmpeg -i 'test.mkv' 'test.mp4'
事實上 FFmpeg 支援超多種格式,任何格式你都可以試著轉轉看,我很少遇到無法轉換的格式。
-
將 MP4 (視訊) 轉 MP3 (音訊) 格式
ffmpeg -i 'test.mp4' 'test.mp3'
事實上 FFmpeg 支援超多種「音訊」格式,任何影音檔都可以試著轉轉看,我很少遇到無法轉換的格式。
使用 ffmpeg
進行影片裁切 (剪片)
使用 FFmpeg 剪片時,可以大致分成兩種剪法:
-
使用 codec copy
快速剪片
使用 FFmpeg 剪片時,在不需要進行影音格式轉換的前提下,可以使用 codec copy
的方式來剪片,剪輯速度非常快,我有個影片長達兩個多小時(02:18:57
),使用 ffmpeg
命令搭配 -c copy
參數做影片裁切,只需要執行 3.4
秒就可以剪片完畢,是不是超快!👍
不過,速度快縱然是他的優點,但缺點是影片可能會在開頭或結尾會呈現幾秒鐘的黑畫面或停格畫面 (看你用什麼影音播放器而定),這是因為如果你切割的時間點沒有 keyframe 關鍵影格,剪出來的影片會比原本想剪輯的片段還長一些,會有一點怪怪的!
開頭第一幀(first frame)如果不是 Keyframe 的話,在 Windows Media Player 播放影片會很怪,一開始就只有聲音,影片是停格的畫面(停在第一幀 Keyframe 的畫面)。但是在 PotPlayer 就看不到這個問題,應該是有些影音播放器會自動修正這種詭異的影片格式。相關資訊:https://trac.ffmpeg.org/wiki/Seeking
-
使用 re-encode
精準剪片
你只要在使用 ffmpeg
命令剪片時,不要使用 -c copy
參數做影片裁切,預設就會對整部影片全部重新解碼再編碼,然後「精準」的剪出你要的影片長度。
不過,剪出來的影片品質最沒問題,但缺點是剪片的時間就會長很多很多了!
以下我說明剪片時重要的幾個參數:
-
使用 -ss
指定開始時間 (時間單位格式:HH:MM:SS.MICROSECONDS
)
-
使用 -to
指定結束時間 (時間單位格式:HH:MM:SS.MICROSECONDS
)
-
使用 -t
指定影片剪接的時間長度 (單位: 秒)
-
使用 -i
指定影片檔名
-
使用 -c
指定 Codec 名稱
若 Codec 指定為 copy
的話,則代表 Stream copy 或 codec copy
的意思,從 copy
的名字也可以看的出來,這種 Codec 就是不做編碼與解碼,直接複製的意思,所以剪片的速度非常快。這種用法特別適用於不需要轉換影片格式的情境。
你可以透過以下命令查詢 FFmpeg 支援的 Codec 有哪些,真的相當多!
ffmpeg -codecs
因為 Codec 還有區分多種類型,所以 ffmpeg -codecs
命令會顯示以下提示,讓你知道不同 Codec 可以用在哪邊:
Codecs:
D..... = Decoding supported
.E.... = Encoding supported
..V... = Video codec
..A... = Audio codec
..S... = Subtitle codec
...I.. = Intra frame-only codec
....L. = Lossy compression
.....S = Lossless compression
-------
D.VI.S 012v Uncompressed 4:2:2 10-bit
D.V.L. 4xm 4X Movie
D.VI.S 8bps QuickTime 8BPS video
D.V..S aasc Autodesk RLE
D.V.L. agm Amuse Graphics Movie
D.VIL. aic Apple Intermediate Codec
DEVI.S alias_pix Alias/Wavefront PIX image
DEVIL. amv AMV Video
...
...
注意: 你只要不加上 -c
參數,就可以做到剪片的同時直接轉檔(換影片檔格式)!🔥
以下是使用範例:
-
剪掉特定時間點之前的片段
使用 -c copy
參數指定 Codec (編解碼函式庫) 為 copy
,就可以做到快速剪片,把指定時間點前的片段裁掉,並產生新的影片檔。
ffmpeg -ss '00:28:16' -i 'test.mp4' -c copy 'test_new.mp4'
如果要明確指定 Video (-vcodec
) 與 Audio (-acodec
) 的 Codec,也可這樣執行:
# 完整的命令
ffmpeg -ss '00:28:16' -i 'test.mp4' -vcodec copy -acodec copy 'test_new.mp4'
# 簡寫的命令
ffmpeg -ss '00:28:16' -i 'test.mp4' -c:v copy -c:a copy 'test_new.mp4'
請記得: 不加上 codec 參數時,剪輯出來的影片比較沒問題,但執行時間較長。
-
剪掉特定時間點之後的片段
ffmpeg -to '00:51:38' -i 'test.mp4' -c copy 'test_new.mp4'
-
剪掉特定時間點之前與特定時間點之後的片段
ffmpeg -ss '00:28:16' -to '00:51:38' -i 'test.mp4' -c copy 'test_new.mp4'
-
取得特定時間點之後的一段時間的片段
使用 -t
可以指定一個時間長度(duration),單位為「秒數」。例如以下命令可以剪出從 00:28:16
開始後 5 分鐘的影片!
ffmpeg -ss '00:28:16' -t '300' -i 'test.mp4' -c copy 'test_new.mp4'
使用 ffmpeg
進行影片截圖
透過 ffmpeg
截圖有許多方式:
-
Input seeking
先決定要截圖的時間位置,直接讀取 test.mp4
的該時間點位置的畫面:
ffmpeg -ss '01:20:29' -i test.mp4 -frames:v 1 out1.jpg
這種截圖方式非常快,幾乎都可以在一秒內完成截圖,而且從 FFmpeg 2.1 開始,截圖已經非常精準,應該不需要第二種 Output seeking 的方法!🔥
-
Output seeking
先對 test.mp4
進行影片解碼,再依據要截圖的時間位置,讀取該時間點位置的畫面:
ffmpeg -i test.mp4 -ss '01:20:29' -frames:v 1 out2.jpg
這種截圖方式非常非常慢,但優點一樣是「精準」截圖,因為他要一幀一幀的解開畫面,到指定位置才能進行截圖!
-
直接將影片中所有影格(Frame)都擷取成 PNG 圖片
ffmpeg -i test.mp4 'test-screenshot-%04d.png'
-
直接將影片中每一秒的畫面擷取成 PNG 圖片
ffmpeg -i test.mp4 -vf fps=1 'test-screenshot-%04d.png'
-
直接將影片中每 5 秒的畫面擷取成 PNG 圖片
ffmpeg -i test.mp4 -vf fps=1/5 'test-screenshot-%04d.png'
-
直接將影片中每 5 秒的畫面擷取成 PNG 圖片
ffmpeg -ss '00:28:16' -to '00:51:38' -i test.mp4 -vf fps=1/5 'test-screenshot-%04d.png'
請記得不要將 -i
參數放在 -ss
參數之前,否則截圖速度會超級慢!🔥
使用 ffprobe
分析影片內容
-
分析一個影片檔的摘要資訊
ffprobe 'test.mp4' 2>&1
預設該命令會輸出版本與檔案資訊在 STDERR
的管道,你可以用 2>&1
改輸出到 STDOUT
!
該影片總長度為 02:18:57.33
,每秒偵率為 496 kb/s,有兩個 Stream 頻道,一個為 Video (h264
),一個為 Audio (aac
)
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf59.16.100
Duration: 02:18:57.33, start: 0.000000, bitrate: 496 kb/s
Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x1080 [SAR 1:1 DAR 16:9], 358 kb/s, 30 fps, 30 tbr, 15360 tbn (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 126 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
-
分析一個影片檔的格式資訊
ffprobe -v error -show_format 'test.mkv'
這裡的 -v error
主要用來隱藏版本與檔案資訊,也就是隱藏 STDERR
管道輸出的意思。
[FORMAT]
filename=test.mkv
nb_streams=2
nb_programs=0
format_name=matroska,webm
format_long_name=Matroska / WebM
start_time=0.000000
duration=8337.333000
size=2743626385
bit_rate=2632617
probe_score=100
TAG:ENCODER=Lavf58.76.100
[/FORMAT]
這裡的 nb_streams
是 number of streams
的意思。
-
分析一個影片檔的音軌、影像軌資訊
顯示所有 Streams 資訊
ffprobe -v error -show_streams 'test.mkv'
[STREAM]
index=0
codec_name=h264
codec_long_name=H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
profile=High
codec_type=video
codec_tag_string=[0][0][0][0]
codec_tag=0x0000
width=1920
height=1080
coded_width=1920
coded_height=1080
closed_captions=0
film_grain=0
has_b_frames=1
sample_aspect_ratio=1:1
display_aspect_ratio=16:9
pix_fmt=yuv420p
level=40
color_range=tv
color_space=bt709
color_transfer=bt709
color_primaries=bt709
chroma_location=left
field_order=progressive
refs=1
is_avc=true
nal_length_size=4
id=N/A
r_frame_rate=30/1
avg_frame_rate=30/1
time_base=1/1000
start_pts=0
start_time=0.000000
duration_ts=N/A
duration=N/A
bit_rate=N/A
max_bit_rate=N/A
bits_per_raw_sample=8
nb_frames=N/A
nb_read_frames=N/A
nb_read_packets=N/A
extradata_size=57
DISPOSITION:default=1
DISPOSITION:dub=0
DISPOSITION:original=0
DISPOSITION:comment=0
DISPOSITION:lyrics=0
DISPOSITION:karaoke=0
DISPOSITION:forced=0
DISPOSITION:hearing_impaired=0
DISPOSITION:visual_impaired=0
DISPOSITION:clean_effects=0
DISPOSITION:attached_pic=0
DISPOSITION:timed_thumbnails=0
DISPOSITION:captions=0
DISPOSITION:descriptions=0
DISPOSITION:metadata=0
DISPOSITION:dependent=0
DISPOSITION:still_image=0
TAG:DURATION=02:18:57.333000000
[/STREAM]
[STREAM]
index=1
codec_name=aac
codec_long_name=AAC (Advanced Audio Coding)
profile=LC
codec_type=audio
codec_tag_string=[0][0][0][0]
codec_tag=0x0000
sample_fmt=fltp
sample_rate=48000
channels=2
channel_layout=stereo
bits_per_sample=0
id=N/A
r_frame_rate=0/0
avg_frame_rate=0/0
time_base=1/1000
start_pts=0
start_time=0.000000
duration_ts=N/A
duration=N/A
bit_rate=N/A
max_bit_rate=N/A
bits_per_raw_sample=N/A
nb_frames=N/A
nb_read_frames=N/A
nb_read_packets=N/A
extradata_size=2
DISPOSITION:default=1
DISPOSITION:dub=0
DISPOSITION:original=0
DISPOSITION:comment=0
DISPOSITION:lyrics=0
DISPOSITION:karaoke=0
DISPOSITION:forced=0
DISPOSITION:hearing_impaired=0
DISPOSITION:visual_impaired=0
DISPOSITION:clean_effects=0
DISPOSITION:attached_pic=0
DISPOSITION:timed_thumbnails=0
DISPOSITION:captions=0
DISPOSITION:descriptions=0
DISPOSITION:metadata=0
DISPOSITION:dependent=0
DISPOSITION:still_image=0
TAG:title=simple_aac
TAG:DURATION=02:18:57.216000000
[/STREAM]
你也可以透過以下參數選擇你想顯示的 Streams 資訊:
# 顯示所有影像軌資訊
ffprobe -v error -show_streams -select_streams v 'test.mkv'
# 顯示第0個影像軌資訊
ffprobe -v error -show_streams -select_streams v:0 'test.mkv'
# 顯示所有音軌資訊
ffprobe -v error -show_streams -select_streams a 'test.mkv'
# 顯示第0個音軌資訊
ffprobe -v error -show_streams -select_streams a:0 'test.mkv'
-
顯示每個封包(Packet)的詳細資訊
顯示總共的封包(Packet)統計
ffprobe -v error -show_streams -select_streams v:0 -count_packets 'test.mkv'
其中 nb_frames
代表影片的總封包數,而 nb_read_packets
代表已讀取到的總封包數。
顯示每一個封包的詳細資訊
ffprobe -v error -show_streams -select_streams v:0 -show_packets 'test.mkv'
-
顯示每個影格(Frame)的資訊
顯示總共的影格(Frame)統計
ffprobe -v error -show_streams -select_streams v:0 -count_frames 'test.mkv'
其中 nb_frames
代表影片的總影格數,而 nb_read_frames
代表已讀取到的總影格數。
顯示每一個影格(Frame)的詳細資訊
ffprobe -v error -show_streams -select_streams v:0 -show_frames 'test.mkv'
其中一個 FRAME 的資訊如下:
[FRAME]
media_type=video
stream_index=0
key_frame=0
pts=6144
pts_time=0.400000
pkt_dts=6144
pkt_dts_time=0.400000
best_effort_timestamp=6144
best_effort_timestamp_time=0.400000
pkt_duration=512
pkt_duration_time=0.033333
pkt_pos=325207
pkt_size=160
width=1920
height=1080
pix_fmt=yuv420p
sample_aspect_ratio=1:1
pict_type=B
coded_picture_number=136
display_picture_number=0
interlaced_frame=0
top_field_first=0
repeat_pict=0
color_range=tv
color_space=bt709
color_primaries=bt709
color_transfer=bt709
chroma_location=left
[/FRAME]
-
取得一個影片中所有關鍵影格的位置 (時間)
我用 -show_entries 'frame=pts_time'
參數來選取 Frame 中的 pts_time
欄位資訊,也就是該影格屬於影片中的時間點:
ffprobe -v error -select_streams 'v:0' -show_entries 'frame=pts_time,format=size' -of 'csv=p=0' -sexagesimal -skip_frame nokey 'test.mp4'
其中 -sexagesimal
是為了顯示 HH:MM:SS.MICROSECONDS
這種格式,若不加上就是 SS.MICROSECONDS
格式(以秒數為主)。
其中 -of 'csv=p=0'
是為了輸出 CSV 格式,而 p=0
代表的意思是「輸出時不顯示欄位名稱,僅輸出 frame 的時間值」。
其中 -skip_frame nokey
是為了排除「非關鍵影格」的 Frame!
-
找出特定時間點附近的關鍵影格位置 (時間)
如果我們想剪出 00:28:16
到 00:51:38
這個時間點為例,可以透過以下命令找出這兩個時間點最接近的關鍵影格位置:
ffprobe -v error -select_streams 'v:0' -show_entries 'frame=pts_time' -of 'csv=p=0' -sexagesimal -read_intervals '00:28:16%+#1' 'test.mp4'
# 0:28:11.900000
ffprobe -v error -select_streams 'v:0' -show_entries 'frame=pts_time' -of 'csv=p=0' -sexagesimal -read_intervals '00:51:38%+#1' 'test.mp4'
# 0:51:36.366667
這裡的 -read_intervals
的 %+#1
代表從時間點開始往後讀取一個 packet,但是 ffmpeg
在跳轉的時候,只能從「關鍵影格」(Keyframe)開始讀,因此回傳的時間將會從最近的(早一點的時間)一個關鍵影格時間點。所以你想從 00:28:16
開始擷取影片,但事實上你只能從 0:28:11.900000
開始擷取, 由於該參數很多變化,建議看官網文件。
然後再改用以下命令裁剪影片,就可以剪出精準的影片長度了,這個新影片檔的第一個 frame (0:00:00.000000
) 就一定是 keyframe:
ffmpeg -ss '0:28:11.900000' -to '0:51:36.366667' -i 'test.mp4' -c copy 'test2.mp4'
-
取得音量增益資訊 (dB gain)
顯示所有 Audio 相關的 Metadata,這可以看出你有多少資訊/欄位/TAG可以用
ffprobe -v error -f lavfi -i 'amovie=test_new.mp4,astats=metadata=1:reset=1' -show_frames
以下可以從 test.mp4
擷取出所有聲音的 Profile 並輸出到 test_AudioProbe.csv
檔案中:
# 輸出成 CSV 格式
ffprobe -v error -f lavfi -i 'amovie=test_new.mp4,astats=metadata=1:reset=1' -show_entries 'frame=pts_time:frame_tags=lavfi.astats.Overall.RMS_level,lavfi.astats.1.RMS_level,lavfi.astats.2.RMS_level' -of 'csv=p=0' > test_AudioProbe.csv
輸出檔案內容為 CSV 格式,欄位內容如下:
- 聲音出現的秒數 (audio frame time in seconds)
- 整體音量的 dB 數 (overall RMS dB volume for that frame)
- 第一聲道的 dB 數 (the 3rd column RMS volume for the first channel)
- 第二聲道的 dB 數 (the last column the RMS volume for the 2nd channel) (如果有的話)
你也可以輸出成 JSON 格式,方便程式分析
# 輸出成 JSON 格式
ffprobe -v error -f lavfi -i 'amovie=test_new.mp4,astats=metadata=1:reset=1' -show_entries 'frame=pts_time:frame_tags=lavfi.astats.Overall.RMS_level,lavfi.astats.1.RMS_level,lavfi.astats.2.RMS_level' -of 'json' > test_AudioProbe.json
-
取得影片的時間長度 (Stream duration)
ffprobe -v error -select_streams v:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 test.mp4
# 輸出: 253.600000
ffprobe -v error -select_streams v:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 -sexagesimal test.mp4
# 輸出: 0:04:13.600000
其中 -sexagesimal
是為了顯示 HH:MM:SS.MICROSECONDS
這種格式,若不加上就是 SS.MICROSECONDS
格式(以秒數為主)。
-
取得影片的解析度 (Resolution)
ffprobe -v error -select_streams v:0 -show_entries stream=height,width -of csv=s=x:p=0 test.mp4
# 輸出: 1920x1080
-
取得影片的 Frame Rate
ffprobe -v error -select_streams v:0 -show_entries stream=avg_frame_rate -of default=noprint_wrappers=1:nokey=1 test.mp4
# 輸出: 2517000/41951
注意: Frame Rate 有可能是變動的,而且一個影片檔可能會有多個 Video Streams
使用 ffplay
播放影音內容
總結
FFmpeg 這水真的很深,我研究出本文的各種用法,是我曾經用過的,其實就已經可以衍生出數十種不同的影音應用,但本文的介紹可能講不到 FFmpeg 的 10% 吧,像是字幕的 Codec 就沒有講到,他可以把字幕寫入到影片中,但因為我還沒有這種需求,所以就沒特別去研究這部分的用法!
相關連結