1. 如何用nginx+ffmpeg實現蘋果HLS協議
//處理window的情況
prepare_app_arguments(&argc,&argv);
optindex=1;
//循環處理命令行參數
while(optindex< argc){
opt = argv[optindex++];
//如果傳入的參數是「-」打頭
if(handleoptions&& opt[0]=='-'&& opt[1]!='\0'){
//如果傳入的參數是「--」打頭
if(opt[1]=='-'&& opt[2]=='\0'){
handleoptions =0;
//略過
continue;
}
//丟棄第一個字元」-」
opt++;
//解析命令行參數
//eg–acodec
//對應的 opt和 argv[optindex]為 「acodec」 「」
if((ret= parse_option(optctx, opt, argv[optindex], options))<0)
exit_program(1);
optindex += ret;
}else{
//此時 opt的值為輸出文件名如 test.ts
if(parse_arg_function)
//處理輸出文件的相關內容,如 struct OutputFile的初始化
parse_arg_function(optctx, opt);
}
}
在此,ffmpeg 默認的處理輸出文件名參數為:
staticvoid opt_output_file(void*optctx,constchar*filename)
2.2處理命令行參數
int parse_option(void*optctx,constchar*opt,constchar*arg, const OptionDef*options)
2.2.1查找匹配的Option
const OptionDef*po;
int bool_val=1;
int*dstcount;
void*dst;
//從全局變數options數組中查找opt對應的OptionDef
po = find_option(options, opt);
//如果未找到且以」no」打頭
//不需要傳遞參數的選項是bool類型的選項,默認為true
//如果需要設置為false,則需要加上」no」,以下的if則是處理這種情況
if(!po->name&& opt[0]=='n'&& opt[1]=='o'){
//去掉開頭的」no」重新查找
po = find_option(options, opt +2);
//如果仍未找到或者找到的選項不是bool類型
if(!(po->name&&(po->flags& OPT_BOOL)))
//報錯
goto unknown_opt;
bool_val =0;
}
//如果未找到且不是以上的」no」打頭情況
if(!po->name)
//尋找默認配置進行處理
po = find_option(options,"default");
//default配置也未找到,報錯
if(!po->name){
unknown_opt:
av_log(NULL, AV_LOG_ERROR,"Unrecognizedoption '%s'\n", opt);
return AVERROR(EINVAL);
}
//如果選項必須有參數但是沒有可用的參數,報錯
if(po->flags& HAS_ARG&&!arg){
av_log(NULL, AV_LOG_ERROR,"Missingargument for option '%s'\n", opt);
return AVERROR(EINVAL);
}
現在來查看一下find_option方法的實現:
staticconst OptionDef*find_option(const OptionDef*po,constchar*name)
根據name在全局變數options數組中查找OptionDef
//這里先處理參數帶有冒號的情況。比如 codec:a codec:v等
constchar*p= strchr(name,':');
int len= p? p- name: strlen(name);
//遍歷options
while(po->name!=NULL){
//比較option的名稱與name是否相符。
//這里 codec 與 codec:a相匹配
if(!strncmp(name, po->name, len)&& strlen(po->name)== len)
break;
po++;
}
return po;
2.2.2尋找選項地址
以下的代碼用於將 void*dst變數賦值。讓dst指向需要賦值的選項地址。
//如果選項在OptionContext中是以偏移量定位或者是 SpecifierOpt*數組的類型
dst= po->flags&(OPT_OFFSET| OPT_SPEC)?
//dst指向從 optctx地址偏移u.off的位置
(uint8_t*)optctx+ po->u.off:
//否則直接指向 OptionDef結構中定義的位置
po->u.dst_ptr;
//如果選項是SpecifierOpt*數組
if(po->flags& OPT_SPEC){
//數組首地址
SpecifierOpt **so= dst;
char*p= strchr(opt,':');
//這里是取得數組的當前長度+1
//請回顧 1.1中的描述:
//SpecifierOpt *xxx;
//int nb_xxx;
//當so指向xxx時刻,so+1指向nb_xxx
dstcount =(int*)(so+1);
//動態增長數組
*so = grow_array(*so,sizeof(**so), dstcount,*dstcount+1);
//將創建的SpecifierOpt結構體中的specifier賦值
//如codec:v 則specifier值為 「v」
(*so)[*dstcount-1].specifier= av_strp(p? p+1:"");
//dst指針指向數組新增的SpecifierOpt中的 u地址
//此時dstcount的值已經變作新數組的長度,亦即原數組長度+1
dst =&(*so)[*dstcount-1].u;
2. 【NGINX入門】3.Nginx的緩存伺服器proxy_cache配置
本文介紹NGINX緩存機制,配置和參數說明。
如圖所示,nginx緩存,可以在一定程度上,減少源伺服器的處理請求壓力。因為靜態文件(比如css,js, 圖片)中,很多都是不經常更新的。nginx使用proxy_cache將用戶的請求緩存到本地一個目錄。下一個相同請求可以直接調取緩存文件,就不用去請求伺服器了。畢竟,IO密集型服務的處理是nginx的強項。
Nginx的緩存加速功能是由proxy_cache(用於反向代理和靜態緩存)和fastcgi_cache(PHP動態緩存)兩個功能模塊完成。
Nginx緩存特點:
先上個例子:
因為我是在一台伺服器上做試驗,所以用了兩個埠 80 和 90 進行模擬兩台伺服器之間的交互。
接下來講一下配置項:
這里我設置了 圖片 、 css 、 js 靜態資源進行緩存。
當用戶輸入 http://wangxiaokai.vip 域名時,解析得到 ip:port 的訪問地址。 port 默認為80。所以頁面請求會被當前server截取到,進行請求處理。
當解析到上述文件名結尾的靜態資源,會到緩存區獲取靜態資源。
如果獲取到對應資源,則直接返回數據。
如果獲取不到,則將請求轉發給 proxy_pass 指向的地址進行處理。
這里直接處理 90 埠接受到的請求,到伺服器本地目錄 /mnt/blog 下抓取資源進行響應。
細心的讀者應該發現,我在第二段例子里,留了個彩蛋 add_header wall "hey!guys!give me a star." 。
add_header 是用於在報頭設置自定義的信息。
所以,如果緩存有效的話,那麼靜態資源返回的報頭,一定會帶上這個信息。
(1)Nginx系列教程(3)nginx緩存伺服器上的靜態文件
https://yq.aliyun.com/articles/752967
(2)proxy_cache
nginx 反向代理之 proxy_cache https://www.cnblogs.com/yyxianren/p/10832172.html
(3)Nginx使用upstream負載均衡和proxy_cache緩存實現反向代理
https://blog.51cto.com/13770206/2163952
3. 雙層nginx 架構之分發層與應用層
一般情況下會部署多個nginx,在裡面都會放一些緩存,但是命中率比較低。因此,會採用分發層與應用層的雙層nginx架構方案。
上圖已經很清楚的描述了nginx 緩存命中率低下的原因,為了解決這個問題,採用相同的id 路由到相同的nginx 伺服器中就可以了。
上圖描述了雙層nginx 架構:分發層與應用層。
最前端的nginx伺服器,被稱之為分發伺服器;後端的nginx伺服器,就稱之為應用伺服器。
在實際的生產環境中,可以大幅度提升nginx本地緩存命中率,減少redis後端的壓力,大量提升服務性能。
4. nginx 緩存機制
Nginx緩存的基本思路
基本思想是利用客戶訪問的時間局部性原理,對客戶已經訪問過的內容在Nginx伺服器本地建立副本,這樣在一段時間內再次訪問該數據,就不需要通過Nginx伺服器再次向後端伺服器發出請求,所以能夠減少Nginx伺服器與後端伺服器之間的網路流量,減輕網路擁塞,同時還能減小數據傳輸延遲,提高用戶訪問速度。同時,當後端伺服器宕機時,Nginx伺服器上的副本資源還能夠回應相關的用戶請求,這樣能夠提高後端伺服器的魯棒性。
對於緩存,我們大概會有以下問題:
(1)緩存文件放在哪兒?
(2)緩存的空間大小是否可以限定?
(3)如何指定哪些請求被緩存?
(4)緩存的有效期是多久?
(5)對於某些請求,是否可以不走緩存?
解決這些問題後,nginx的緩存也就基本配置完成了,下面看詳細配置過程
開啟緩存
要使用緩存,首先要使用 proxy_cache_path 這個指令(必須放在 http 上下文的頂層位置),然後在目標上下文中使用 proxy_cache 指令
配置示例
proxy_cache_path 有兩個必填參數,第一個參數為 緩存目錄,第二個參數keys_zone指定緩存名稱和佔用內存空間的大小(註:示例中的10m是對內存中緩存內容元數據信息大小的限制,如果想限制緩存總量大小,需要用 max_size 參數)
proxy_cache 的參數為之前指定的緩存名稱
緩存管理的相關進程
在緩存工作中有兩個附加進程:
(1)緩存管理器
定期檢查緩存狀態,看緩存總量是否超出限制,如果超出,就移除其中最少使用的部分
(2)緩存載入器
載入器只在nginx啟動後運行一次,把緩存內容的元數據信息載入到內存空間,如果一次性載入全部緩存信息,會大量消耗資源,使nginx在啟動後的幾分鍾里變慢,為避免此問題,有3種載入策略:
loader_threshold – 指定每次載入執行的時間
loader_files – 每次最多載入的數量
loader_sleeps – 每次載入的延時
例如:
proxy_cache_path /data/nginx/cache keys_zone=one:10m loader_threshold=300 loader_files=200;
指定緩存哪些請求
nginx默認會緩存所有 get 和 head 方法的請求結果,緩存的key默認使用請求字元串
(1)自定義key
例如 proxy_cache_key " request_uri cookie_nocache arg_comment;
如果任何一個參數值不為空,或者不等於0,nginx就不會查找緩存,直接進行代理轉發
綜合示例
nginx 緩存機制
三分鍾看懂Nginx伺服器的緩存原理和機制
5. nginx優化以及多級緩存
配置文件:
lua是一個小巧的腳本語言,由標准C編寫而成,幾乎在所有操作系統和平台上都可以編譯運行。其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定製功能。
應用場景:游戲開發、獨立應用腳本、redis中嵌套調用實現類似事務的功能,web容器匯總處理NGINX的過濾緩存等等邏輯
OpenResty是一個基於Nginx與Lua的高性能web平台,由中國人章亦春發起,其內部集成了大量精良的Lua庫、第三方模塊以及大多數的依賴項。用於方便搭建能處理超高並發、擴展性極高的動態Web應用、web服務和動態網關
OpenResty簡單理解成就相當於封裝了NGINX,並且集成了LUA腳本,開發人員只需要簡單的使用其提供了模塊就可以實現相關的邏輯,而不像之前,還需要在NGINX中編寫lua的腳本。
1.拉取一個openresty的鏡像
2.隨便構建一個容器用於拷貝配置文件
3.進入容器,查找配置文件路徑(這里直接給出)
4.退出容器,復制容器中配置文件到宿主機
5.修改配置文件
1、使用Lua查詢Nginx緩存,如果有緩存,則直接將緩存中的分類數據返回
2、如果Nginx緩存中沒有分類數據,則通過Lua腳本查詢Redis,如果Redis中有數據,則將數據存入到Nginx緩存中,並返回查詢到的數據
3、如果Redis中也沒有緩存,則此時通過Lua腳本查詢Mysql,如果Mysql中有數據,將分類數據存入到Redis緩存,並返回數據
6. 如何用nginx+ffmpeg實現蘋果HLS協議
HLS協議的理解nginx ffmpeg的編譯 安裝 調試,以及工具的選擇 使用 gdb等
nginx模塊開發
ffmpeg的開發
重點將集中在 ffmpeg 的開發上。
HLS協議的實現有很多的細節,比如我在實際的開發過程中,就面臨將多種不同格式的視頻源文件(來源於不同的編碼器以及有不同的profile)動態切片輸出。而現有能在網上找到的方式基本都是對視頻文件做了預先處理,比如用ffmpeg將視頻文件先轉換成物理存儲的mpeg2ts文件,然後用nginx進行動態切片輸出。這對開發帶來了很大的困難。
如果我們將問題簡化的話,即 輸入文件為 mp4 (isom512 , 2 channels stereo),那麼最簡單的實現方式是如下命令行:
avconv -i input_file.mp4 -vcodec -acodec -vbsfh264_mp4toannexb –ss00:00:00–t00:00:10 output_file.ts
然後通過 對 參數 –ss00:00:00 –t00:00:10 的調整,獲得多個物理切片,提供給nginx輸出。
這里需要提供一個細節,即 處理的性能。 所以在上述的命令行中,僅僅進行了 remux 而沒有任何 ecode 和 decode 的操作。
我們要做的,就是將這行命令變成 可供 nginx 調用的 api。
當然,任然可以選擇最簡單的作法,nginx模塊裡面調用系統命令。不過這樣子,貌似有點兒寒磣吧。呵呵。
7. windows使用nginx+rtmp推流hls時候,無法生成m3u8,ts文件的解決辦法
主要問題描述:使用ffmpeg進行hls推流的時候,發現無法保存m3u8,ts文件,多度以為是路徑問題的同學看這篇文章就夠了。
windows搭建nginx+rtmp不在詳述,各種文章都寫了。這邊主要針對解釋一下其他文章忽略的一些點。
可以看到, hls 出現了四次,其中兩個是保存m3u8和ts的文件夾。還有兩個對應於ffmpeg推流命令:
請看nginx-rtmp-mole下的 README.md
8. 基於Nginx設置瀏覽器協商緩存過程詳解
一、強緩存與協商緩存的區別
強緩存:瀏覽器不與服務端協商直接取瀏覽器緩存
協商緩存:瀏覽器會先向伺服器確認資源的有效性後才決定是從緩存中取資源還是重新獲取資源
二、協商緩存運作原理
現在有一個這樣的業務情景:後端的靜態資源會不定時地發生更新,而因為瀏覽器默認使用強緩存,會默認從瀏覽器緩存中取到過時的資源。
現在我們希望瀏覽器每次獲取資源的時候都向後端確認資源是否更新,就要設置瀏覽器使用協商緩存
那麼後端如何判斷資源是否更新了呢?這時就要用到Etag和Last-Modified兩項響應頭。
每次收到一個靜態資源的請求時,後端都將資源的最後修改時間(Last-Modified)、根據資源內容計算出來的Etag放在響應頭給前端。
前端收到響應後將這兩項緩存起來,然後在下次請求同樣資源的時候,將這兩項的內容放到If-Modified-Since和If-None-Match這兩項請求頭中。
服務端收到這兩項後,會與資源當前生成的Etag和Last-Modified做比較,如果兩者都一致,說明資源沒有更新,服務端會返回304空響應;否則,說明資源有更新,服務端會將完整的資源內容返回
實現
那麼如何實現這樣一個復雜的過程呢?其實很簡單,只要使用nginx作為靜態資源的伺服器,再在響應頭加上Cache-Control:no-cache就可以了。
下面來分步驟實現一下
1. 使用nginx作為靜態資源的伺服器
在nginx的配置中,將對靜態資源的請求映射到資源的磁碟路徑上
http {
server {
listen 80;
...
location /picture/ {
alias D:/luozixi/tcp_test/picture/;
# alias是重定義路徑
# 比如訪問127.0.0.1/picture/1_new.gif,則會映射為訪問D:/luozixi/tcp_test/picture/1_new.gif
# web應用根本不會收到請求,picture的請求都被nginx處理了
# alias是替換,root是拼接
autoindex on;
}
}
}
2. 重新載入nginx配置
3. 此時,請求靜態資源的時候nginx會自動在response頭中加上Etag和Last-Modified兩項
4. 但是這時發現,如果不配置Cache-Contrl: no-cache,瀏覽器在下次請求這個資源的時候不會將請求發向後端,而是直接從緩存中獲取資源
5. 在nginx中配置
location /picture/ {
add_header Cache-Control no-cache;
alias D:/luozixi/tcp_test/picture/;
}
6.清除瀏覽器緩存後第一次發起請求,會得到一個正常的200 Response,而且響應頭里已經有了Cache-Control: no-cache,表示使用協商緩存
7.再次發起請求後,會發現請求頭已經帶上了If-Modified-Since和If-None-Match兩項
8.服務端(nginx)收到這兩項後,會與資源當前生成的Etag和Last-Modified做比較,如果兩者都一致,說明資源沒有更新,服務端會返回304空響應;否則,說明資源有更新,服務端會將完整的資源內容返回
另外,伺服器驗證If-Modified-Since的方式只是簡單的字元串比較,即使資源的Last-Modified比If-Modified-Since要早,服務端仍認為資源有更新
9.瀏覽器在收到304響應後,會從瀏覽器緩存中取資源。因此速度非常塊
三、no-cache與no-store的區別
no-cache表示不緩存過期資源,緩存會向伺服器進行有效處理確認之後處理資源
而no-store才是真正的不進行緩存。
9. Nginx作為緩存服務
上一篇文章講了Nginx作為代理服務的使用方式,這篇文章我們講一講Nginx作為緩存服務是怎麼工作的,以及實戰的使用。
先看一張圖:
面對第一次客戶端的應用Nginx需要從後端的服務獲取數據,對於後續的請求,Nginx若進行了緩存就不再從後端服務獲取數據。
語法:proxy_cache_path path [levels=levels].只能用在http中。
proxy_cache zone | off。默認是關閉的,可以用在http,server,location中。
訪問zzm這個路徑的時候,會返回配置文件中的spring.s項,具體值可以參考我們的啟動設置:
三個後台金正分別對應6000埠,6001埠,6002埠
所以我們訪問ip:port/zzm的時候會自動去訪問後台
我們首先注釋掉proxy_cache zzm_cache;進行訪問,也就是沒有緩存的情況下,訪問3次:
我們可以看到沒有緩存的情況下,會進行輪詢訪問,每次訪問的結果不一樣,而且我們的緩存路徑什麼都沒有,讓我想起了一首歌空空如也:
好奇的看了下目錄下的文件內容:
add_header Nging-Cache "$upstream_cache_status";
當我們沒有緩存的時候,我們可以看到應答會是MISS:
現在nginx.conf中加入新的配置項:
此時我們怒刷前端頁面,會發現後端日誌如下:
Nginx的緩存服務就講到這里,歡迎大家指正
10. 如何用nginx+ffmpeg實現蘋果HLS協議
avconv -i input_file.mp4 -vcodec -acodec -vbsfh264_mp4toannexb –ss00:00:00 –t00:00:10 output_file.ts
為例說明ffmpeg如何將命令行參數解析處理。
int main(int argc,char**argv)
{
//初始化參數容器
OptionsContext o={0};
//重置參數
reset_options(&o);
//解析參數
parse_options(&o, argc, argv, options,opt_output_file);
}
1.重置參數
staticvoid reset_options(OptionsContext*o)
依次進行了以下步驟:
1.1第一步:釋放特殊類型
釋放所有的 OPT_SPEC(對應struct SpecifierOpt)和 OPT_STRING (對應 char*)類型的 OptionDef
代碼如下:
//指向全局變數options
const OptionDef*po= options;
//遍歷options
while(po->name){
//dest指針指向當前option對應的OptionContext中的位置
void*dst=(uint8_t*)o+ po->u.off;
//判斷是否是SpecifierOpt類型
if(po->flags& OPT_SPEC){
//so指向SpecifierOpt*的首地址
SpecifierOpt **so= dst;
//獲得數組長度
int i,*count=(int*)(so+1);
//循環遍歷SpecifierOpt*數組
for(i=0; i<*count; i++){
//釋放SpecifierOpt的specifier(char*類型)
av_freep(&(*so)[i].specifier);
//如果OPT類型是字元串,釋放SpecifierOpt的u.str(char*類型)
if(po->flags& OPT_STRING)
av_freep(&(*so)[i].u.str);
}
//釋放SpecifierOpt*指針數組
av_freep(so);
//重置計數器
*count=0;
}
//判斷是否是char*類型
elseif(po->flags& OPT_OFFSET&& po->flags& OPT_STRING)
av_freep(dst);
po++;
}
這里需要對OptionContext的內容做一些說明:
OptionContext 包含了在視頻編轉碼過程中需要用到的參數,這些參數來自於命令行的輸入。
參數在OptionContext中的存儲形式有:
#defineOPT_INT 0x0080
#defineOPT_FLOAT 0x0100
#defineOPT_INT64 0x0400
#defineOPT_TIME 0x10000
#defineOPT_DOUBLE 0x20000
等,詳情參見 structOptionDef
在上述代碼中,主要循環釋放的是OPT_SPEC(對應struct SpecifierOpt)和 OPT_STRING
在OptionContext中,OPT_SPEC類型是成對出現的,如下:
typedefstructOptionsContext{
int64_t start_time;
constchar*format;
SpecifierOpt *codec_names;
int nb_codec_names;
SpecifierOpt *audio_channels;
int nb_audio_channels;
即:
SpecifierOpt *xxx_vars;
int nb_xxx_vars; //nb_讀作number_意思是xxx_vars數組的長度
然後我們來分析對SpecifierOpt*數組的遍歷:
SpecifierOpt **so= dst;
int i,*count=(int*)(so+1);
for(i=0; i<*count; i++){
這里可以這么理解:
so —指向—> SpecifierOpt *xxx_vars;
so+1—指向—> int nb_xxx_vars;
so+1 的含義:so是個SpecifierOpt指針,指針+1則移動了sizeof(SpecifierOpt)的位置,即跳到nb_xxx_vars的位置。
1.2釋放其他類型
av_freep(&o->stream_maps);
av_freep(&o->meta_data_maps);
av_freep(&o->streamid_map);
這里說一下 av_freep 的用法。
void av_freep(void*arg)
{
void**ptr=(void**)arg;
av_free(*ptr);
*ptr=NULL;
}
相比傳統的free方法,這里主要多做了一步工作:將釋放free之後,指針設置為NULL
同時,要注意到:
Object *obj;
free(obj);
等價用法為:
av_freep(&obj);
在ffmpeg中,封裝了對應free的方法為:
void av_free(void*ptr)
{
#ifCONFIG_MEMALIGN_HACK
if(ptr)
free((char*)ptr-((char*)ptr)[-1]);
#else
free(ptr);
#endif
}
這里除了考慮內存對齊之外,跟傳統的free方法沒有任何變化。
1.3第三步:設置初始值
memset(o,0,sizeof(*o));
o->mux_max_delay =0.7;
o->recording_time= INT64_MAX;
o->limit_filesize= UINT64_MAX;
o->chapters_input_file= INT_MAX;
不需要過多解釋。
o->mux_max_delay =0.7;
這一行內容以後在視頻切片中會用到。可以調整到更小。
1.4重新初始化特殊參數
uninit_opts();
init_opts();
這兩行代碼對應cmtils.c 文件中的代碼段:
struct SwsContext*sws_opts;
AVDictionary*format_opts,*codec_opts;
void init_opts(void)
{
#if CONFIG_SWSCALE
sws_opts= sws_getContext(16,16,0,16,16,0, SWS_BICUBIC,
NULL,NULL,NULL);
#endif
}
void uninit_opts(void)
{
#ifCONFIG_SWSCALE
sws_freeContext(sws_opts);
sws_opts=NULL;
#endif
av_dict_free(&format_opts);
av_dict_free(&codec_opts);
}
主要進行: SwsContext*sws_opts,AVDictionary*format_opts,*codec_opts三個全局變數的創建和釋放工作。
2.解析命令行參數
void parse_options(void*optctx,int argc,char**argv,const OptionDef *options,void(*parse_arg_function)(void*,constchar*))
void*optctx,——OptionContext
int argc,——命令行參數個數
char**argv,——命令行參數列表
const OptionDef*options,——選項列表
void(*parse_arg_function)(void*,constchar*)——自定義的解析方法
2.1總覽
constchar*opt;
int optindex, handleoptions=1, ret;
//處理window的情況
prepare_app_arguments(&argc,&argv);
optindex=1;
//循環處理命令行參數
while(optindex< argc){
opt = argv[optindex++];
//如果傳入的參數是「-」打頭
if(handleoptions&& opt[0]=='-'&& opt[1]!='\0'){
//如果傳入的參數是「--」打頭
if(opt[1]=='-'&& opt[2]=='\0'){
handleoptions =0;
//略過
continue;
}
//丟棄第一個字元」-」
opt++;
//解析命令行參數
//eg–acodec
//對應的 opt和 argv[optindex]為 「acodec」 「」
if((ret= parse_option(optctx, opt, argv[optindex], options))<0)
exit_program(1);
optindex += ret;
}else{
//此時 opt的值為輸出文件名如 test.ts
if(parse_arg_function)
//處理輸出文件的相關內容,如 struct OutputFile的初始化
parse_arg_function(optctx, opt);
}
}
在此,ffmpeg 默認的處理輸出文件名參數為:
staticvoid opt_output_file(void*optctx,constchar*filename)
2.2處理命令行參數
int parse_option(void*optctx,constchar*opt,constchar*arg, const OptionDef*options)
2.2.1查找匹配的Option
const OptionDef*po;
int bool_val=1;
int*dstcount;
void*dst;
//從全局變數options數組中查找opt對應的OptionDef
po = find_option(options, opt);
//如果未找到且以」no」打頭
//不需要傳遞參數的選項是bool類型的選項,默認為true
//如果需要設置為false,則需要加上」no」,以下的if則是處理這種情況
if(!po->name&& opt[0]=='n'&& opt[1]=='o'){
//去掉開頭的」no」重新查找
po = find_option(options, opt +2);
//如果仍未找到或者找到的選項不是bool類型
if(!(po->name&&(po->flags& OPT_BOOL)))
//報錯
goto unknown_opt;
bool_val =0;
}
//如果未找到且不是以上的」no」打頭情況
if(!po->name)
//尋找默認配置進行處理
po = find_option(options,"default");
//default配置也未找到,報錯
if(!po->name){
unknown_opt:
av_log(NULL, AV_LOG_ERROR,"Unrecognizedoption '%s'\n", opt);
return AVERROR(EINVAL);
}
//如果選項必須有參數但是沒有可用的參數,報錯
if(po->flags& HAS_ARG&&!arg){
av_log(NULL, AV_LOG_ERROR,"Missingargument for option '%s'\n", opt);
return AVERROR(EINVAL);
}
現在來查看一下find_option方法的實現:
staticconst OptionDef*find_option(const OptionDef*po,constchar*name)
根據name在全局變數options數組中查找OptionDef
//這里先處理參數帶有冒號的情況。比如 codec:a codec:v等
constchar*p= strchr(name,':');
int len= p? p- name: strlen(name);
//遍歷options
while(po->name!=NULL){
//比較option的名稱與name是否相符。
//這里 codec 與 codec:a相匹配
if(!strncmp(name, po->name, len)&& strlen(po->name)== len)
break;
po++;
}
return po;
2.2.2尋找選項地址
以下的代碼用於將 void*dst變數賦值。讓dst指向需要賦值的選項地址。
//如果選項在OptionContext中是以偏移量定位或者是 SpecifierOpt*數組的類型
dst= po->flags&(OPT_OFFSET| OPT_SPEC)?
//dst指向從 optctx地址偏移u.off的位置
(uint8_t*)optctx+ po->u.off:
//否則直接指向 OptionDef結構中定義的位置
po->u.dst_ptr;
//如果選項是SpecifierOpt*數組
if(po->flags& OPT_SPEC){
//數組首地址
SpecifierOpt **so= dst;
char*p= strchr(opt,':');
//這里是取得數組的當前長度+1
//請回顧 1.1中的描述:
//SpecifierOpt *xxx;
//int nb_xxx;
//當so指向xxx時刻,so+1指向nb_xxx
dstcount =(int*)(so+1);
//動態增長數組
*so = grow_array(*so,sizeof(**so), dstcount,*dstcount+1);
//將創建的SpecifierOpt結構體中的specifier賦值
//如codec:v 則specifier值為 「v」
(*so)[*dstcount-1].specifier= av_strp(p? p+1:"");
//dst指針指向數組新增的SpecifierOpt中的 u地址
//此時dstcount的值已經變作新數組的長度,亦即原數組長度+1
dst =&(*so)[*dstcount-1].u;