1. 程式人生 > 其它 >流的操作(一)視訊轉音訊引發的血案

流的操作(一)視訊轉音訊引發的血案

https://www.cnblogs.com/wwolf/p/ffmpeg-stream-selection-video-to-audio.html

轉發自白狼棧:檢視原文

有些小夥伴看文章非常細心,對於上一節課不經意提到的一些邊緣細節都比較在意,比如 -acodec、-vcodec、流複製等。其實這些都離不開我們今天要講的重點——流。

說起流,可能有很多小夥伴第一反應是流媒體,但是我們今天要說的是容器內流的型別。通過前面的介紹,相信你對容器內的音訊(audio, a)和視訊(video, v)都有了一些印象。除此之外,容器內流的型別還有字幕(subtitle, s)、附加資料(attachment, t)和普通資料(data, d)。我們重點介紹一下音訊流、視訊流和字幕流。

流的操作,指的是我們可以從輸入檔案中選擇不同的流進行操作,然後輸出我們想要的結果。

舉個例子,家裡有小孩的都應該比較清楚,學校現在有很多英語的配音比賽,大螢幕播放一段視訊,學生在舞臺上配音,非常形象。

在這個場景中,大螢幕上播放的視訊,其實就是無聲視訊。無聲視訊並不是把聲音調到最小,它指的是沒有音訊的視訊,這樣播放的視訊只有畫面。比方說對於前文案例一的素材視訊可以通過 -an 的命令去除音訊流,只保留視訊流即只有畫面(沒有下載的可以點選這裡下載)。

ffmpeg -i r1ori.mp4 -an -y r1-silent.mp4

來看下結果視訊r1-silent.mp4的資訊,沒有了 Stream #0:1(und): Audio 的資訊。

» ffmpeg -i r1-silent.mp4 -hide_banner 
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'r1-silent.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.20.100
  Duration: 00:00:58.53, start: 0.000000, bitrate: 1687 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 544x960, 1684 kb/s, 29.83 fps, 29.83 tbr, 11456 tbn, 59.67 tbc (default)
    Metadata:
      handler_name    : VideoHandler
At least one output file must be specified

現在即使你把音響抱過來,聲音加到最大播放這個視訊,也不會聽到任何聲音。

-an 即 -acodec none。a指的是audio,codec指的是解碼器,-acodec就是音訊解碼器,合起來就是不指定音訊解碼器,回顧下我們在ffmpeg是怎麼轉碼的一文介紹的轉碼流程就很容易理解了。

你應該已經猜到了,類似的我們還可以去除視訊流、字幕流等。

  1. -an 去除音訊流
  2. -vn 去除視訊流
  3. -sn 去除字幕流
  4. -dn 去除資料流

有同學可能注意到了,我們的原視訊的時長是59秒,還不到一分鐘,但是 -an 的一條命令要花上十幾秒的處理時間,太慢了,有沒有辦法優化下?

你仔細思考下,那麼慢,時間花在哪裡了?對,就是重新編碼。

這裡我們只是去除音訊流,有必要重新編碼嗎?沒有,所以如果我們可以把視訊流複製出來是不是就好了?

優化後的命令如下

ffmpeg -i r1ori.mp4 -an -vcodec copy -y r1-silent.mp4

這條命令瞬間就輸出結果了。我們添加了一個引數 -vcodec copy。-vcodec指的是視訊解碼器,v是視訊video,codec是解碼器,後跟解碼器名稱,copy複製輸入的視訊流,不作解碼處理。

同樣,如果我們想提取視訊中的音訊,或者說把視訊轉成音訊,是不是可以用下面這條命令?

ffmpeg -i r1ori.mp4 -vn -c:a copy -y r1-silent.mp3

執行該命令後發現報錯了

[mp3 @ 0x7f97a580f000] Invalid audio stream. Exactly one MP3 audio stream is required. 
Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument

提示我們音訊流無效,原因是codec的引數錯誤。我們看下原視訊的資訊

» ffmpeg -i r1ori.mp4 -hide_banner 
...... 
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 129 kb/s (default) 
......

注意音訊流這行資訊,我們發現音訊流是aac格式的,而我們要輸出的是mp3格式的,-c:a copy 引數意味著我們想要把aac格式的音訊流裝進mp3容器,這是不可行的。aac 音訊流需要一個專用的 aac 容器,mp3 音訊流需要專用的 mp3 容器。

注:aac 和 mp3 都是有失真壓縮音訊編碼格式。

找到原因就好辦了,我們把輸出的mp3格式修改成aac格式

ffmpeg -i r1ori.mp4 -vn -c:a copy -y r1-silent.aac

雖然mp4容器內的音訊流大多數都是aac格式,但是,試想一下如果我們寫好程式,要針對使用者上傳的視訊提取音訊並做儲存,偏偏使用者上傳的原視訊內的音訊是mp3呢?

為了滿足這一場景,我們製作一個含mp3格式的視訊,然後再執行上面的命令試試。

1、把r1ori.mp4視訊內的音訊流轉成mp3

ffmpeg -i r1ori.mp4 -c:a libmp3lame -c:v copy -y r2.mp4
注:由於ffmpeg沒有原生的mp3編碼器,所有我們指定了外部的libmp3lame編碼庫(雖然 -c:a libmp3lame 你也可以把libmp3lame改為mp3,實際上使用的還是libmp3lame)。如果你執行上面的命令報了一個類似這樣的錯誤 ERROR: libmp3lame >= 3.98.3 not found,說明你本地的ffmpeg沒有新增--enable-libmp3lame編譯引數,可以參考這篇文章選擇對應的方式重新安裝ffmpeg;

2、提取該視訊的音訊

ffmpeg -i r2.mp4 -vn -c:a copy -y r1-silent.aac 
報錯:Only AAC streams can be muxed by the ADTS muxer 
Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument

所以,-c:a copy 不是萬能的,也就是說如果我們想讓視訊轉音訊,最好指定一種編碼器,用aac還是libmp3lame?就音質質量而言,我們更推薦libmp3lame,儘管ffmpeg自帶的最好音訊編碼器是aac。

綜上,如果你需要輸出mp3格式的音訊,你可以使用

ffmpeg -i r1ori.mp4 -vn -c:a libmp3lame -y r1-silent.mp3

如果你想輸出aac格式的音訊,你可以使用

ffmpeg -i r1ori.mp4 -vn -c:a aac -y r1-silent.aac
注:新版本的ffmpeg是支援原生aac編碼的,所以可以直接使用 -c:a aac,低版本的ffmpeg像2.x的版本原生aac編碼器是不完全支援的,必須同時指定 -strict -2 才可以使用。

以上,我們介紹了手動指定音訊解碼器,成功的將視訊轉換成了音訊。

既然ffmpeg那麼厲害,那如果我們不手動指定,它能自動幫我們選擇合適的解碼器處理嗎?

非常可以。

以mp3為例,我們試下

ffmpeg -i r1ori.mp4 -y r1-silent.mp3 
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'r1ori.mp4': 
...... 
Stream mapping: Stream #0:1 -> #0:0 (aac (native) -> mp3 (libmp3lame)) 
......

注意看輸出的過程程式碼中包含 Stream mapping 以及其下一行程式碼,可以看出ffmpeg的確自動為我們選擇了libmp3lame解碼器。

如果原視訊有多路音訊流,又該如何操作呢?我們下節課再說。