一,FFmpeg介紹
FFmpeg 是一款流行的開源多媒體處理工具,它可以用于轉(zhuǎn)換、編輯、錄制和流式傳輸音視頻文件。FFmpeg 具有廣泛的應(yīng)用場(chǎng)景,包括視頻編解碼、格式轉(zhuǎn)換、裁剪、合并、濾鏡等等。官網(wǎng):https://ffmpeg.org/
FFmpeg 支持各種常見的音視頻格式,例如 MP4、AVI、FLV、MOV、AAC、MP3、M4A 等等,并且可以通過添加插件支持更多的格式。與其他視頻處理軟件相比,F(xiàn)Fmpeg 優(yōu)勢(shì)在于它的跨平臺(tái)性能好,可以在 Windows、macOS 和 Linux IOS Android等平臺(tái)上運(yùn)行。
FFmpeg 提供了一個(gè)命令行界面(CLI),可以使用它來執(zhí)行各種操作。以下是一些常用的 FFmpeg 命令:
-
裁剪:從視頻中截取指定時(shí)間段的視頻片段。
ffmpeg -i input.mp4 -ss 00:01:00 -t 00:00:30 -c copy output.mp4
-
視頻旋轉(zhuǎn):將視頻順時(shí)針或逆時(shí)針旋轉(zhuǎn)指定角度。
ffmpeg -i input.mp4 -vf "rotate=PI/2" output.mp4
-
視頻拼接:將多個(gè)視頻文件拼接成一個(gè)文件。
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
-
視頻縮放:按比例縮小或放大視頻尺寸。
ffmpeg -i input.mp4 -vf scale=640:-1 output.mp4
-
音頻提?。簩⒁曨l文件中的音頻提取出來。
ffmpeg -i input.mp4 -vn -acodec copy output.aac
-
音頻合并:將多個(gè)音頻文件合并為一個(gè)文件。
ffmpeg -i "concat:input1.mp3|input2.mp3" -acodec copy output.mp3
-
視頻轉(zhuǎn)碼:將一個(gè)視頻文件轉(zhuǎn)換為另一種格式。
ffmpeg -i input.avi -c:v libx264 -preset slow -crf 22 -c:a libmp3lame -b:a 192k output.mp4
-
視頻加減速:將視頻的播放速度加快或減慢。
ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" output.mp4
-
音頻加減速:將視頻中的音頻的播放速度加快或減慢。
ffmpeg -i input.mp4 -vn -af atempo=%.3f output.mp4
-
音頻重采樣
ffmpeg -y -i input.mp4 -vn -ar 44100 -ac 2 output.mp4
-
視頻轉(zhuǎn)Gif并等比例縮放
ffmpeg -y -i input.mp4 -vf scale=320:-2 -r 5 output.gif
-
統(tǒng)計(jì)I幀數(shù)量
ffprobe -show_frames input.mp4 > frames.txt cat frames.txt | grep "pict_type=I" |wc -l
-
視頻信息查看 開發(fā)過程中最常用的命令,沒有之一
ffprobe -v error -show_format -show_streams input.mp4
結(jié)果如下:
[STREAM]
index=0
codec_name=h264
codec_long_name=H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
profile=Baseline
codec_type=video
codec_tag_string=avc1
codec_tag=0x31637661
width=1920
height=1080
coded_width=1920
coded_height=1080
closed_captions=0
film_grain=0
has_b_frames=0
sample_aspect_ratio=1:1
display_aspect_ratio=16:9
pix_fmt=yuvj420p
level=40
color_range=pc
color_space=smpte170m
color_transfer=smpte170m
color_primaries=smpte170m
chroma_location=left
field_order=progressive
refs=1
is_avc=true
nal_length_size=4
id=0x1
r_frame_rate=30/1
avg_frame_rate=40230000/1339297
time_base=1/90000
start_pts=0
start_time=0.000000
duration_ts=1339297
duration=14.881078
bit_rate=20028287
max_bit_rate=N/A
bits_per_raw_sample=8
nb_frames=447
nb_read_frames=N/A
nb_read_packets=N/A
extradata_size=33
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:creation_time=2021-02-27T07:35:19.000000Z
TAG:language=eng
TAG:handler_name=VideoHandle
TAG:vendor_id=[0][0][0][0]
[SIDE_DATA]
side_data_type=Display Matrix
displaymatrix=
00000000: 0 65536 0
00000001: -65536 0 0
00000002: 0 0 1073741824
rotation=-90
[/SIDE_DATA]
[/STREAM]
[STREAM]
index=1
codec_name=aac
codec_long_name=AAC (Advanced Audio Coding)
profile=LC
codec_type=audio
codec_tag_string=mp4a
codec_tag=0x6134706d
sample_fmt=fltp
sample_rate=48000
channels=2
channel_layout=stereo
bits_per_sample=0
initial_padding=0
id=0x2
r_frame_rate=0/0
avg_frame_rate=0/0
time_base=1/48000
start_pts=0
start_time=0.000000
duration_ts=715765
duration=14.911771
bit_rate=96041
max_bit_rate=N/A
bits_per_raw_sample=N/A
nb_frames=699
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:creation_time=2021-02-27T07:35:19.000000Z
TAG:language=eng
TAG:handler_name=SoundHandle
TAG:vendor_id=[0][0][0][0]
[/STREAM]
[FORMAT]
filename=input.mp4
nb_streams=2
nb_programs=0
format_name=mov,mp4,m4a,3gp,3g2,mj2
format_long_name=QuickTime / MOV
start_time=0.000000
duration=14.911771
size=37839486
bit_rate=20300465
probe_score=100
TAG:major_brand=mp42
TAG:minor_version=0
TAG:compatible_brands=isommp42
TAG:creation_time=2021-02-27T07:35:19.000000Z
TAG:com.android.version=8.1.0
[/FORMAT]
在FFmpeg工具中,ffprobe是一種用于分析媒體文件的命令行工具,參數(shù)解釋如下:
profile
在H.264/AVC (Advanced Video Coding)視頻編碼標(biāo)準(zhǔn)中,有以下四種預(yù)定義的profile:
-
Baseline Profile:這是最基本的profile,它定義了一組基本功能,包括單個(gè)參考幀、CAVLC (Context-adaptive Variable Length Coding)、8x8變換等。Baseline Profile可用于低比特率、低延遲和低復(fù)雜度的應(yīng)用。
-
Main Profile:Main Profile擴(kuò)展了Baseline Profile并增加了一些高級(jí)功能,例如B幀、8x8和4x4變換以及熵編碼模式自適應(yīng)技術(shù)。Main Profile可以提供更好的視頻質(zhì)量和壓縮率,適合于中等比特率的應(yīng)用。
-
Extended Profile:Extended Profile擴(kuò)展了Main Profile,并添加了更多的高級(jí)功能,例如8x8和4x4變換、支持更高分辨率、更高的比特率和更豐富的顏色空間。Extended Profile適用于高清視頻和廣電級(jí)別的視頻應(yīng)用。
-
High Profile:High Profile包含所有的H.264/AVC高級(jí)功能,例如無損編碼、多層編碼、亮度調(diào)整等。High Profile通常用于專業(yè)級(jí)別的視頻應(yīng)用,例如數(shù)字電視廣播、藍(lán)光光盤和高清視頻流媒體服務(wù)。
除了上述四個(gè)預(yù)定義的profile之外,H.264/AVC還支持用戶定義的profile,以根據(jù)具體應(yīng)用需求自定義編解碼器的參數(shù)設(shè)置。
編碼level
在H.264/AVC視頻編碼標(biāo)準(zhǔn)中,Level指的是視頻編碼器的限制條件,例如最大分辨率、最高比特率以及其他一些技術(shù)參數(shù)。這些限制條件與profile不同,因?yàn)樗鼈兺ǔJ艿綄?shí)時(shí)處理硬件設(shè)備的限制。以下是H.264/AVC定義的level:
-
Level 1:支持最大352x288像素分辨率和1.5 Mbps的解碼速度。
-
Level 1b:類似于Level 1,但要求使用Baseline Profile。
-
Level 1.1:支持最大352x480像素分辨率和12 Mbps的解碼速度。
-
Level 1.2:支持最大720x480像素分辨率和30 Mbps的解碼速度。
-
Level 1.3:支持最大1280x720像素分辨率和60 Mbps的解碼速度。
-
Level 2:支持最大1920x1080像素分辨率和60 Mbps的解碼速度。
-
Level 2.1:支持最大1920x1080像素分辨率和120 Mbps的解碼速度。
-
Level 2.2:支持最大1920x1080像素分辨率和120 Mbps的解碼速度,并要求使用High Profile。
-
Level 3:支持最大1920x1080像素分辨率和240 Mbps的解碼速度。
-
Level 3.1:支持最大1920x1080像素分辨率和240 Mbps的解碼速度,并要求使用High Profile。
-
Level 3.2:支持最大1920x1080像素分辨率和240 Mbps的解碼速度,并要求使用High Profile和4:2:2色度采樣。
-
Level 4:支持最大2048x2048像素分辨率和480 Mbps的解碼速度。
-
Level 4.1:支持最大2048x2048像素分辨率和1 Gbps的解碼速度。
-
Level 4.2:支持最大2048x2048像素分辨率和1 Gbps的解碼速度,并要求使用High Profile和10位色深。
-
Level 5:支持最大4096x2304像素分辨率和1 Gbps的解碼速度。
-
Level 5.1:支持最大4096x2304像素分辨率和2 Gbps的解碼速度。
-
Level 5.2:支持最大4096x2304像素分辨率和4 Gbps的解碼速度。
需要注意的是,level越高,支持的分辨率和比特率就越高,因此所需的處理能力和存儲(chǔ)空間也越大。同時(shí),實(shí)際可用的level還受到編碼器和解碼器的硬件限制。
DISPOSITION
DISPOSITION是指多媒體文件流(例如音頻、視頻)在其容器中的位置以及是否被默認(rèn)啟用的標(biāo)志。它告訴我們這個(gè)特定的流的角色是什么,并且是否應(yīng)該被自動(dòng)啟用。
以下是一些常見的DISPOSITION標(biāo)志:
default:表示該流是默認(rèn)啟用的。
dub:表示這個(gè)流是一個(gè)雙語(yǔ)版本的語(yǔ)言流。
original:表示這個(gè)流是原始的無損版本。
comment:表示這個(gè)流是注釋版本的。
lyrics:表示這個(gè)流包含歌詞信息。
通過查看DISPOSITION標(biāo)志,我們可以確定每個(gè)音頻或視頻流的作用和啟用狀態(tài),這對(duì)于解析和編輯多媒體文件非常有用。
除了命令行界面之外,F(xiàn)Fmpeg 還提供了許多開發(fā)庫(kù),例如 libavcodec 和 libavformat 等,這些庫(kù)可以幫助您將 FFmpeg 集成到自己的應(yīng)用程序中。
總之,F(xiàn)Fmpeg 是一款強(qiáng)大的多媒體處理工具,可以讓您對(duì)音視頻文件進(jìn)行各種操作。如果您需要在自己的應(yīng)用程序中使用 FFmpeg,可以使用其提供的開發(fā)庫(kù)
二,F(xiàn)Fmpeg編譯
要想把FFmpeg運(yùn)行在移動(dòng)設(shè)備上,就需要使用到交叉編譯,交叉編譯是指在一臺(tái)計(jì)算機(jī)上,使用一個(gè)編譯器將程序或庫(kù)編譯成可以在不同架構(gòu)的計(jì)算機(jī)上運(yùn)行的二進(jìn)制文件。通常情況下,交叉編譯是在開發(fā)人員的計(jì)算機(jī)上完成的,然后將生成的可執(zhí)行文件或庫(kù)文件拷貝到目標(biāo)設(shè)備上運(yùn)行。
2.1 常見的錯(cuò)誤
FFmpeg 交叉編譯一直依賴都是老大難的問題,新手往往要折騰好幾天才能成功編譯
為 Android 編譯 FFmpeg 時(shí),常見的錯(cuò)誤如下:
C compiler test failed.
出現(xiàn)這個(gè)問題一般有兩種情況
- NDK版本不對(duì),NDK 的版本非常重要,因?yàn)椴煌姹镜?NDK 可能與不同版本的 FFmpeg 不兼容。在下載 NDK 時(shí),請(qǐng)確保使用與您要構(gòu)建的 FFmpeg 版本相匹配的 NDK 版本。
-NDK路徑寫錯(cuò)了
ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol
- 在–extra-ldflags 加入-fPIC
- 在鏈接的時(shí)候加入-Bsymbolic,慎重使用
- 注意鏈接的時(shí)候的順序 比如libavutil是每個(gè)庫(kù)都需要的,那么就要放在第一個(gè)鏈接
duplicate symbol
–enable-shared --enable-static 同時(shí)打開,在合并so的時(shí)候會(huì)出現(xiàn)重復(fù)的方法
java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol “cos”
在編譯以及在把.a文件鏈接到SO的時(shí)候沒有把m,z,android庫(kù)鏈接上
2.2 編譯步驟
2.2.1 下載Android SDK
我們一般通過Android Studio下載SDK,官網(wǎng)地址:https://developer.android.com/?hl=zh-cn
運(yùn)行之后打開設(shè)置,找到Android SDK
SDK Platforms我們一般下最新正式版本就好了,這里最新正式版本為33(Android 13),如圖所示:
SDK Tools一般都需要選擇上,如圖所示:
2.2.2下載Android NDK
NDK 我們一般去官網(wǎng)下載對(duì)應(yīng)的版本,地址如下:https://developer.android.com/ndk/downloads?hl=zh-cn
這個(gè)地方我們使用r25c就好了
2.2.3 安裝必備的軟件
MAC需要安裝Xcode Command Line Tools 地址:https://developer.apple.com/download/all/?q=xcode
Linux需要安裝curl git yasm
2.2.4 Start build
A 基礎(chǔ)build
需要下載一個(gè)成品腳本,地址:https://github.com/Javernaut/ffmpeg-android-maker 這個(gè)腳本我親測(cè)在Mac Linux都可以成功Build但是在實(shí)際開發(fā)中還需要做修改(修改完的build腳本工程詳見這里:https://github.com/bookzhan/ffmpeg-android-build)
cd 到腳本目錄,指定SDK目錄,如下:
export ANDROID_SDK_HOME='/Users/guaishou/Library/Android/sdk'
export ANDROID_NDK_HOME='/Users/guaishou/Library/Android/sdk/ndk/AndroidNDK9519653.app/Contents/NDK'
執(zhí)行腳本:./ffmpeg-android-maker.sh
腳本的執(zhí)行時(shí)間比較長(zhǎng),安靜的等待腳本執(zhí)行完成,腳本執(zhí)行完成之后,生成的SO在output目錄下,如圖所示:
可以看到,這個(gè)腳本構(gòu)建是不能直接用于開發(fā),主要有以下問題:
- 可以看到這個(gè)腳本構(gòu)建的結(jié)構(gòu)有多個(gè)SO,這在開發(fā)中顯得不是很優(yōu)雅,我們需要做的是把這些SO合并成一個(gè)SO
- 構(gòu)建出來的SO很大,包大小遭不住
- 在構(gòu)建的時(shí)候我們常用的庫(kù)libmp3lame,libx264(libx264是GPL協(xié)議的,要注意安全合規(guī))沒有編譯進(jìn)去
B 多個(gè)SO合并成一個(gè)SO
要想把多個(gè)SO合并成一個(gè)SO,那么就需要把FFmpeg編譯成靜態(tài)庫(kù),有關(guān)靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)的解釋如下:
靜態(tài)庫(kù)是編譯時(shí)鏈接到應(yīng)用程序中的,它將庫(kù)文件的全部?jī)?nèi)容復(fù)制到應(yīng)用程序中。這意味著在運(yùn)行時(shí),所有的庫(kù)函數(shù)都已經(jīng)存在于應(yīng)用程序中,不需要再進(jìn)行加載。由于靜態(tài)庫(kù)已經(jīng)被完全鏈接到應(yīng)用程序中,因此它們的大小通常比動(dòng)態(tài)庫(kù)更大,但執(zhí)行速度更快,因?yàn)闆]有額外的加載和解析操作。同時(shí),使用靜態(tài)庫(kù)可以避免版本沖突和依賴問題,因?yàn)槊總€(gè)應(yīng)用程序都會(huì)使用自己的一份庫(kù)文件副本。
相反,動(dòng)態(tài)庫(kù)是在運(yùn)行時(shí)動(dòng)態(tài)加載的,只有當(dāng)需要使用庫(kù)函數(shù)時(shí)才會(huì)被加載到內(nèi)存中。由于動(dòng)態(tài)庫(kù)僅在需要時(shí)才加載,因此它們的大小通常比靜態(tài)庫(kù)小,但執(zhí)行速度可能稍慢,因?yàn)樾枰M(jìn)行額外的加載和解析操作。另外,由于多個(gè)應(yīng)用程序可以共享相同的庫(kù)文件,因此動(dòng)態(tài)庫(kù)提供了更好的資源利用率。
總之,靜態(tài)庫(kù)是在編譯時(shí)鏈接到應(yīng)用程序中的,而動(dòng)態(tài)庫(kù)是在運(yùn)行時(shí)動(dòng)態(tài)加載的。靜態(tài)庫(kù)的優(yōu)點(diǎn)是執(zhí)行速度更快,依賴和版本控制更容易,而動(dòng)態(tài)庫(kù)的優(yōu)點(diǎn)是更小的庫(kù)文件大小和更好的資源利用率??梢杂梦募缶Y做簡(jiǎn)單的區(qū)分:靜態(tài)庫(kù)文件后綴為.a, 動(dòng)態(tài)庫(kù)文件后綴為.so
步驟如下:
- 首選我們需要修改ffmpeg-android-maker-master/scripts/ffmpeg/build.sh 文件把–disable-static 改成–enable-static 再次build 的時(shí)候就可以在ffmpeg-android-maker-master/build目錄下看到.a文件已經(jīng)生成了(在調(diào)試腳本的時(shí)候可以在后面加-abis=arm64-v8a 參數(shù),只是構(gòu)建64位的庫(kù),這個(gè)構(gòu)建時(shí)間就會(huì)短很多)
- 合并.a文件
${FAM_CC} -shared -o ${STATIC_LIB_DIR}/${OUTPUT_SO_NAME}
-Wl,--whole-archive,-Bsymbolic
${STATIC_LIB_DIR}/libavutil.a
${STATIC_LIB_DIR}/libavcodec.a
${STATIC_LIB_DIR}/libavfilter.a
${STATIC_LIB_DIR}/libswresample.a
${STATIC_LIB_DIR}/libavformat.a
${STATIC_LIB_DIR}/libswscale.a
-Wl,--no-whole-archive
${FAM_STRIP} --strip-unneeded ${STATIC_LIB_DIR}/${OUTPUT_SO_NAME}
#FAM_CC STATIC_LIB_DIR FAM_STRIP 都是export-build-variables.sh生成的環(huán)境變量,F(xiàn)AM_CC其實(shí)就是clang(aarch64-linux-android21-clang系列) --whole-archive指的是把全部代碼都寫入到so中,不管是否使用到了
#FAM_STRIP --strip-unneeded 標(biāo)識(shí)剔除debug信息,減少so大小,其實(shí)Android在打包的時(shí)候會(huì)對(duì)所有so執(zhí)行這個(gè)操作
#-Bsymbolic是解決ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_tx_tab_32_float'; recompile with -fPIC錯(cuò)誤的關(guān)鍵,但是不要輕易使用,盡量去解決編譯問題,否則在運(yùn)行的時(shí)候會(huì)出問題
#在后面寫入-Wl,--no-whole-archive是為了解決重復(fù)符號(hào)表的問題,表示只對(duì)接下來的目標(biāo)文件執(zhí)行局部符號(hào)綁定。這樣,就可以將不必要的符號(hào)從最終的符號(hào)表中刪除,減小輸出文件的大小,并避免一些潛在的問題。
#需要主要這個(gè)合并的順序?。?!
C FFmpeg裁剪
so合并的文件的問題解決了,但是合并的so會(huì)顯得很大,這是因?yàn)槟J(rèn)是把FFmpeg的代碼全部編譯了,我們需要做一些裁剪,去掉我們不用的代碼
在不知道有那些配置可以選擇的時(shí)候,可以在FFmpeg源碼下執(zhí)行./configure 這樣默認(rèn)會(huì)把所有支持的配置都列出來,然后依次去掉不必要的配置就好
最終scripts/ffmpeg/build.sh文件的樣式如下:
#!/usr/bin/env bash
case $ANDROID_ABI in
x86)
# Disabling assembler optimizations, because they have text relocations
EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --disable-neon --disable-asm"
;;
x86_64)
EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --disable-neon --disable-asm"
;;
armeabi-v7a)
EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --enable-neon --disable-asm --enable-inline-asm"
;;
arm64-v8a)
EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --enable-neon --disable-asm --enable-inline-asm"
;;
esac
if [ "$FFMPEG_GPL_ENABLED" = true ] ; then
EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --enable-gpl"
fi
# Preparing flags for enabling requested libraries
ADDITIONAL_COMPONENTS=
for LIBARY_NAME in ${FFMPEG_EXTERNAL_LIBRARIES[@]}
do
ADDITIONAL_COMPONENTS+=" --enable-$LIBARY_NAME"
case $LIBARY_NAME in
libx264)
ADDITIONAL_COMPONENTS+=" --enable-encoder=libx264"
;;
libmp3lame)
ADDITIONAL_COMPONENTS+=" --enable-decoder=mp3"
;;
*)
echo "Unknown ADDITIONAL_COMPONENTS LIBARY_NAME: $LIBARY_NAME"
;;
esac
done
echo ADDITIONAL_COMPONENTS=${ADDITIONAL_COMPONENTS}
# Referencing dependencies without pkgconfig
DEP_CFLAGS="-I${BUILD_DIR_EXTERNAL}/${ANDROID_ABI}/include"
DEP_LD_FLAGS="-L${BUILD_DIR_EXTERNAL}/${ANDROID_ABI}/lib $FFMPEG_EXTRA_LD_FLAGS"
./configure
--prefix=${BUILD_DIR_FFMPEG}/${ANDROID_ABI}
--enable-cross-compile
--enable-small
--target-os=android
--arch=${TARGET_TRIPLE_MACHINE_ARCH}
--sysroot=${SYSROOT_PATH}
--cc=${FAM_CC}
--cxx=${FAM_CXX}
--ld=${FAM_LD}
--ar=${FAM_AR}
--as=${FAM_CC}
--nm=${FAM_NM}
--ranlib=${FAM_RANLIB}
--strip=${FAM_STRIP}
--extra-cflags="-O3 -fPIC -lm -lz -landroid $DEP_CFLAGS"
--extra-ldflags="$DEP_LD_FLAGS"
--disable-shared
--enable-static
--disable-vulkan
--disable-symver
--disable-doc
--disable-htmlpages
--disable-manpages
--disable-podpages
--disable-txtpages
--disable-ffplay
--disable-ffmpeg
--disable-ffprobe
--disable-avdevice
--disable-bsfs
--disable-devices
--disable-protocols
--disable-postproc
--enable-protocol=file
--enable-protocol=concat
--disable-parsers
--disable-demuxers
--enable-demuxer=mov
--enable-demuxer=mp3
--enable-demuxer=image2
--enable-demuxer=gif
--enable-demuxer=wav
--enable-demuxer=asf
--enable-demuxer=flv
--enable-demuxer=avi
--enable-demuxer=webm_dash_manifest
--enable-demuxer=matroska
--enable-demuxer=mpegts
--disable-decoders
--enable-decoder=aac
--enable-decoder=png
--enable-decoder=h264
--enable-decoder=mp3
--enable-decoder=mjpeg
--enable-decoder=mpeg4
--enable-decoder=gif
--enable-decoder=pcm_s16le
--enable-decoder=hevc
--enable-decoder=msmpeg4v1
--enable-decoder=msmpeg4v2
--enable-decoder=msmpeg4v3
--enable-decoder=wmav1
--enable-decoder=wmav2
--enable-decoder=flv
--enable-decoder=adpcm_swf
--enable-decoder=ac3
--enable-decoder=vp8
--enable-decoder=vorbis
--enable-decoder=mpeg2video
--enable-decoder=mp2
--enable-decoder=indeo4
--enable-decoder=amrnb
--disable-muxers
--enable-muxer=mov
--enable-muxer=mp4
--enable-muxer=image2
--enable-muxer=mp3
--enable-muxer=ipod
--enable-muxer=gif
--disable-encoders
--enable-encoder=aac
--enable-encoder=png
--enable-encoder=mjpeg
--enable-encoder=gif
--enable-swscale
--disable-filters
--enable-filter=crop
--enable-filter=scale
--enable-filter=afade
--enable-filter=atempo
--enable-filter=copy
--enable-filter=aformat
--enable-filter=overlay
--enable-filter=vflip
--enable-filter=hflip
--enable-filter=transpose
--enable-filter=volume
--enable-filter=rotate
--enable-filter=apad
--enable-filter=amerge
--enable-filter=aresample
--enable-filter=setpts
--enable-filter=fps
--enable-filter=palettegen
--enable-filter=paletteuse
--enable-filter=trim
--enable-filter=null
--enable-filter=overlay
--enable-filter=format
--enable-filter=atrim
--enable-filter=split
--enable-filter=amix
--enable-filter=anull
--enable-filter=adelay
--enable-zlib
--enable-jni
--enable-nonfree
--enable-mediacodec
--enable-version3
--pkg-config=${PKG_CONFIG_EXECUTABLE}
${EXTRA_BUILD_CONFIGURATION_FLAGS}
${ADDITIONAL_COMPONENTS} || exit 1
${MAKE_EXECUTABLE} clean
${MAKE_EXECUTABLE} -j${HOST_NPROC}
${MAKE_EXECUTABLE} install
export STATIC_LIB_DIR=${BUILD_DIR_FFMPEG}/${ANDROID_ABI}/lib
export EXTERNAL_LIB_DIR=${INSTALL_DIR}/lib
echo STATIC_LIB_DIR=${STATIC_LIB_DIR}
echo EXTERNAL_LIB_DIR=${EXTERNAL_LIB_DIR}
echo FAM_CC=${FAM_CC}
EXTERNAL_STATIC_LIB_PATH=""
for LIBARY_NAME in ${FFMPEG_EXTERNAL_LIBRARIES[@]}
do
EXTERNAL_STATIC_LIB_PATH+="${EXTERNAL_LIB_DIR}/${LIBARY_NAME}.a "
done
echo EXTERNAL_STATIC_LIB_PATH=${EXTERNAL_STATIC_LIB_PATH}
${FAM_CC} -shared -o ${STATIC_LIB_DIR}/${OUTPUT_SO_NAME}
-Wl,--whole-archive
${EXTERNAL_STATIC_LIB_PATH}
${STATIC_LIB_DIR}/libavutil.a
${STATIC_LIB_DIR}/libavcodec.a
${STATIC_LIB_DIR}/libavfilter.a
${STATIC_LIB_DIR}/libswresample.a
${STATIC_LIB_DIR}/libavformat.a
${STATIC_LIB_DIR}/libswscale.a
-Wl,--no-whole-archive -lm -lz -landroid
OUTPUT_CONFIG_HEADERS_DIR=${OUTPUT_DIR}/include/${ANDROID_ABI}
mkdir -p ${OUTPUT_CONFIG_HEADERS_DIR}
cp config.h ${OUTPUT_CONFIG_HEADERS_DIR}/config.h
${FAM_STRIP} --strip-unneeded ${STATIC_LIB_DIR}/${OUTPUT_SO_NAME}
腳本說明
- 從Android 5.0開始,默認(rèn)情況下所有設(shè)備都支持NEON指令集,在編譯的時(shí)候盡量打開NEON,這樣性能會(huì)更好
- asm指的是匯編優(yōu)化,這樣代碼執(zhí)行性能會(huì)更好,但是在FFmpeg上編譯一般使用inline-asm(內(nèi)聯(lián)匯編),因?yàn)槎胀▍R編需要單獨(dú)編寫成匯編代碼文件,增加了額外的編譯鏈接過程,很容易鏈接失敗,而且內(nèi)聯(lián)匯編執(zhí)行效率更高
D 添加常用的第三方庫(kù)
在音視頻開發(fā)中,我們一般會(huì)使用一些優(yōu)秀的第三方庫(kù),如對(duì)Mp3支持的庫(kù)libmp3lame,H264軟編庫(kù)libx264(在移動(dòng)端我們一般不使用H265進(jìn)行軟編,主要原因在于H265軟編對(duì)性能消耗要比H264大得多,
在執(zhí)行構(gòu)建命令的時(shí)候啟用./ffmpeg-android-maker.sh --enable-libmp3lame --enable-libx264就好了,最后在鏈接的時(shí)候把對(duì)應(yīng)的.a文件鏈接在一起就好了,如上完整的scripts/ffmpeg/build.sh文件
3.Android 開發(fā)環(huán)境搭建
3.1 新建工程
新建工程,選擇Native c++,然后一直點(diǎn)Next就好了
指定ndk版本
ndk版本一般要在各個(gè)開發(fā)者直接統(tǒng)一,寫法為,在build.gradle->android下面寫,如下
ndkVersion ‘25.2.9519653’
3.2 cmake簡(jiǎn)介
其中cpp目錄下的CMakeLists.txt文件是構(gòu)建so的關(guān)鍵文件,俗稱cmake文件,如下:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
project("ffmpegtest")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
ffmpegtest
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
common/BZLogUtil.cpp
native-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
ffmpegtest
# Links the target library to the log library
# included in the NDK.
${log-lib})
3.2.1 cmake基本語(yǔ)法
cmake的基本語(yǔ)法和C類似,都是通過方法名()的方式來使用的如:
project('ffmpegtest') #指定了工程的名稱,非必選
條件語(yǔ)句
if(condition)
# do something
elseif(condition2)
# do something else
else()
# do another thing
endif()
循環(huán)語(yǔ)句
foreach(loop_var RANGE start end [step])
# do something
endforeach()
while(condition)
# do something
endwhile()
函數(shù)
函數(shù)由命令組成,通過 function(func_name [args...])
定義,可以在任意位置調(diào)用。
function(hello_world)
message("Hello World!")
endfunction()
hello_world()
3.2.2 cmake變量的使用與定義
變量在 CMake 中以 $
開頭,有以下幾種類型:
- 內(nèi)置變量:如
${CMAKE_CURRENT_LIST_DIR}
表示當(dāng)前 CMake 文件所在的目錄。 - 環(huán)境變量:如
$ENV{PATH}
表示系統(tǒng)環(huán)境變量 PATH 的值。 - 用戶定義變量:如
set(VAR_NAME value)
,通過${VAR_NAME}
引用。
3.2.3 常見的用法
其實(shí)上面的很少用,我們來看一個(gè)稍微復(fù)雜一點(diǎn)的,工作中掌握這么多就夠用了,如下:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
#這個(gè)是必選的
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#添加宏
if (${CMAKE_BUILD_TYPE} STREQUAL Debug)
add_definitions(-DDEBUG_TYPE)
endif()
#添加整個(gè)目錄的源碼作為編譯對(duì)象
aux_source_directory(./glprogram DIR_GLPROGRAM_SRCS)
aux_source_directory(./mediaedit DIR_MEDIAEDIT_SRCS)
aux_source_directory(./player DIR_PLAYER_SRCS)
aux_source_directory(./soundtouch DIR_SOUNDTOUCH_SRCS)
add_library( # Sets the name of the library.
bzmedia
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
./jni/android_bzmedia.cpp
./jni/ffmpeg_base_info.cpp
./jni/OnActionListener.cpp
./jni/ffmpeg_audio_player.cpp
./jni/video_edit_sdk.cpp
./jni/jni_VideoFrameGetter.cpp
./jni/video_recorder_jni.cpp
./jni/android_gl_program.cpp
./jni/jni_BZRenderEngine.cpp;
./jni/video_player_jni.cpp;
./jni/soundtouch_jni.cpp;
./common/BZLogUtil.cpp
./common/bz_time.cpp
./common/JvmManager.cpp
./common/GLUtil.cpp
./common/PngReader.cpp
${DIR_MEDIAEDIT_SRCS}
./glutils/FrameBufferUtils.cpp;
./glutils/GLImageTextureUtil.cpp;
./glutils/GLMatrixUtils.cpp;
./glutils/MatrixVaryTools.cpp;
./glutils/CropTextureUtil.cpp;
./glutils/BZRenderEngine.cpp;
./glutils/VideoTextureManger.cpp;
./glutils/EGLContextUtil.cpp;
./glutils/TextureConvertYUVUtil.cpp;
./glutils/TextureUtil.cpp;
${DIR_GLPROGRAM_SRCS}
${DIR_PLAYER_SRCS}
${DIR_SOUNDTOUCH_SRCS}
./recorder/VideoRecorder.cpp
./utils/AdjustConfigUtil.cpp
./utils/PcmDeque.cpp
./permission/base64.c
./permission/PermissionUtil.cpp
)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
configure_file(${Project_SOURCE_DIR}/lib/${ANDROID_ABI}/libbzffmpeg.so ${Project_BINARY_DIR}/libbzffmpeg.so COPYONLY)
configure_file(${Project_SOURCE_DIR}/lib/${ANDROID_ABI}/libbzffmpegcmd.so ${Project_BINARY_DIR}/libbzffmpegcmd.so COPYONLY)
#ffmpeg
add_library(bzffmpeg-lib
SHARED
IMPORTED)
set_target_properties(bzffmpeg-lib
PROPERTIES IMPORTED_LOCATION
libbzffmpeg.so)
#ffmpegcmd
add_library(bzffmpegcmd-lib
SHARED
IMPORTED)
set_target_properties(bzffmpegcmd-lib
PROPERTIES IMPORTED_LOCATION
libbzffmpegcmd.so)
add_library(yuv-lib
STATIC
IMPORTED)
set_target_properties(yuv-lib
PROPERTIES IMPORTED_LOCATION
${Project_SOURCE_DIR}/lib/${ANDROID_ABI}/libyuv_static.a)
#添加系統(tǒng)庫(kù)
find_library(log-lib log)
find_library(m-lib m)
find_library(z-lib z)
find_library(android-lib android)
find_library(EGL-lib EGL)
find_library(jnigraphics-lib jnigraphics)
find_library(GLES-lib GLESv2)
find_library(OpenSLES-lib OpenSLES)
include_directories(
./
./include/
./common
./bean
./cmdutilt
./include
./utils
./permission
./include/libavcodec
./include/libavdevice
./include/libavfilter
./include/libavformat
./include/libavutil
./include/libpostproc
./include/libswresample
./include/libswscale
./include/libyuv
./include/soundtouch
)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
#把所有庫(kù)連接到最終生成的庫(kù)中
target_link_libraries( # Specifies the target library.
bzmedia
# Links the target library to the log library
# included in the NDK.
bzffmpeg-lib bzffmpegcmd-lib yuv-lib ${log-lib} ${m-lib} ${z-lib} ${android-lib} ${EGL-lib} ${GLES-lib} ${jnigraphics-lib} ${OpenSLES-lib})
3.2.4 配置支持的CPU架構(gòu)
默認(rèn)情況下SO會(huì)生成armeabi-v7a,arm64-v8a,x86,x86_64,如果我們只想生成指定的SO,那么在build.gradle文件android->defaultConfig下配置ndk來過濾就好了,如下:文章來源:http://www.zghlxwxcb.cn/news/detail-498827.html
apply plugin: 'com.android.library'
apply from: '../config.gradle'
android {
compileSdkVersion project.ext.compileSdkVersion
defaultConfig {
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
3.3驗(yàn)證FFmpeg編譯是否成功
測(cè)試工程:https://github.com/bookzhan/ffmpegtest
成功標(biāo)準(zhǔn):文章來源地址http://www.zghlxwxcb.cn/news/detail-498827.html
- 執(zhí)行System.loadLibrary沒有任何報(bào)錯(cuò)
- 能夠成功獲取到編碼器,解碼器,avformat信息,驗(yàn)證代碼如下:
#include <common/BZLogUtil.h>
#include <cstring>
#include "ffmpeg_base_info.h"
void printFFmpegBaseInfo() {
char buffer[4096];
getFFmpegConfigure(buffer);
BZLogUtil::logD(buffer);
memset(buffer, 0, 4096);
getFFmpegSupportProtocol(buffer);
BZLogUtil::logD(buffer);
memset(buffer, 0, 4096);
getFFmpegSupportAVFormat(buffer);
BZLogUtil::logD(buffer);
memset(buffer, 0, 4096);
getFFmpegSupportAVCodec(buffer);
BZLogUtil::logD(buffer);
memset(buffer, 0, 4096);
getFFmpegSupportAVFilter(buffer);
BZLogUtil::logD(buffer);
testLib();
}
int getFFmpegConfigure(char *info) {
return sprintf(info, "%sn", avcodec_configuration());;
}
int getFFmpegSupportProtocol(char *info) {
struct URLProtocol *pup = nullptr;
int ret = 0;
//Input
struct URLProtocol **p_temp = &pup;
avio_enum_protocols((void **) p_temp, 0);
while ((*p_temp) != nullptr) {
ret = sprintf(info, "%s[In ][%10s]n", info, avio_enum_protocols((void **) p_temp, 0));
}
pup = nullptr;
//Output
avio_enum_protocols((void **) p_temp, 1);
while ((*p_temp) != nullptr) {
ret = sprintf(info, "%s[Out][%10s]n", info, avio_enum_protocols((void **) p_temp, 1));
}
return ret;
}
int getFFmpegSupportAVFormat(char *info) {
int ret = 0;
void *opaque_in = nullptr;
void *opaque_out = nullptr;
const AVInputFormat *if_temp = av_demuxer_iterate(&opaque_in);
const AVOutputFormat *of_temp = av_muxer_iterate(&opaque_out);
//Input
while (if_temp != nullptr) {
ret = sprintf(info, "%s[In ][%10s]n", info, if_temp->name);
if_temp = av_demuxer_iterate(&opaque_in);;
}
//Output
while (of_temp != nullptr) {
ret = sprintf(info, "%s[Out][%10s]n", info, of_temp->name);
of_temp = av_muxer_iterate(&opaque_out);
}
return ret;
}
int getFFmpegSupportAVCodec(char *info) {
int ret = 0;
void *opaque = nullptr;
const AVCodec *c_temp = av_codec_iterate(&opaque);
while (c_temp != nullptr) {
if (av_codec_is_decoder(c_temp) != 0) {
ret = sprintf(info, "%s[Dec]", info);
} else {
ret = sprintf(info, "%s[Enc]", info);
}
switch (c_temp->type) {
case AVMEDIA_TYPE_VIDEO:
sprintf(info, "%s[Video]", info);
break;
case AVMEDIA_TYPE_AUDIO:
sprintf(info, "%s[Audio]", info);
break;
default:
sprintf(info, "%s[Other]", info);
break;
}
ret = sprintf(info, "%s[%10s]n", info, c_temp->name);
c_temp = av_codec_iterate(&opaque);
}
return ret;
}
int getFFmpegSupportAVFilter(char *info) {
int ret = 0;
void *opaque = nullptr;
const AVFilter *f_temp = av_filter_iterate(&opaque);
while (nullptr != f_temp) {
ret = sprintf(info, "%s[%10s]n", info, f_temp->name);
f_temp = av_filter_iterate(&opaque);
}
return ret;
}
int testLib() {
const AVCodec *avCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (nullptr == avCodec) {
BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_H264 未發(fā)現(xiàn)");
} else {
BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_H264 正常");
}
const AVCodec *avCodecAAC = avcodec_find_decoder(AV_CODEC_ID_AAC);
if (nullptr == avCodecAAC) {
BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_AAC 未發(fā)現(xiàn)");
} else {
BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_AAC 正常");
}
const AVCodec *avCodecEncoder = avcodec_find_encoder(AV_CODEC_ID_H264);
if (nullptr == avCodecEncoder) {
BZLogUtil::logD("avcodec_find_encoder AV_CODEC_ID_H264 未發(fā)現(xiàn)");
} else {
BZLogUtil::logD("avcodec_find_encoder AV_CODEC_ID_H264 正常");
}
const AVCodec *avCodecDecoderMp3 = avcodec_find_decoder(AV_CODEC_ID_MP3);
if (nullptr == avCodecDecoderMp3) {
BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_MP3 未發(fā)現(xiàn)");
} else {
BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_MP3 正常");
}
const AVCodec *avCodecAACEncoder = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (nullptr == avCodecAACEncoder) {
BZLogUtil::logD("avcodec_find_encoder AV_CODEC_ID_AAC 未發(fā)現(xiàn)");
} else {
BZLogUtil::logD("avcodec_find_encoder AV_CODEC_ID_AAC 正常");
}
return 0;
}
到了這里,關(guān)于Android音視頻開發(fā)實(shí)戰(zhàn)01-環(huán)境搭建的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!