大家好,這里是Bingonut的編程筆記告抄。
最近一直在研究Linphone在Linux平臺的源碼文件塑顺,需要修改在視頻通話過程中所使用的視頻流編解碼器(當然须误,這里指的是替換成Linphone原來沒有的編解碼器)劲腿,但網(wǎng)上并沒有真正說明并解決這個問題的文章旭绒,故留此筆記,造福后世~
注意:前面我會先著重介紹Mediastreamer2庫的工作原理焦人。
首先我們必須得明確知道Linphone的主要依賴庫挥吵,以找準目標。下面是我做的一張Linphone的依賴關(guān)系圖:我們這次的目標主要為Mediastreamer2庫花椭,這個開源庫負責在Linphone中接收和發(fā)送所有多媒體流忽匈,包括語音/視頻捕獲,編碼和解碼以及渲染个从。關(guān)于這個庫的工作原理脉幢,Linphone官方僅有一小段的描述歪沃,第一次看的話難免會摸不著頭腦嗦锐,這里我便多說一點。
Mediastreamer2庫的功能是由一串的MSFilter結(jié)構(gòu)完成的沪曙,每個MSFilter結(jié)構(gòu)完成一個小功能奕污,比如本文的主題,流數(shù)據(jù)的編碼和解碼液走,MSFilter結(jié)構(gòu)由MSFactory調(diào)用MSFilterDesc(用來描述MSFilter)來創(chuàng)建碳默。舉個例子,當我們在進行音頻通話時缘眶,需要接收音頻數(shù)據(jù)嘱根,然后進行數(shù)據(jù)處理,最后再播放出來巷懈,而這個過程在Mediastreamer2中就是先創(chuàng)建一系列的MSFilter并把它們連接成鏈该抒,“音頻接收MSFilter”接收音頻流數(shù)據(jù),然后傳給“解碼MSFilter”進行解碼顶燕,然后繼續(xù)把解碼后的數(shù)據(jù)傳遞給下一個MSFilter凑保。了解了Mediastreamer2庫的這個工作模式之后下面就好進行說明了:
在Mediastreamer2庫源碼的src/base/msfactory.c文件中有函數(shù)ms_factory_create_filter_from_desc冈爹,其功能為通過MSFilterDesc創(chuàng)建一個MSFilter結(jié)構(gòu)。
MSFilter *ms_factory_create_filter_from_desc(MSFactory* factory, MSFilterDesc *desc){
MSFilter *obj;
obj=(MSFilter *)ms_new0(MSFilter,1);
ms_mutex_init(&obj->lock,NULL);
obj->desc=desc;
if (desc->ninputs>0) obj->inputs=(MSQueue**)ms_new0(MSQueue*,desc->ninputs);
if (desc->noutputs>0) obj->outputs=(MSQueue**)ms_new0(MSQueue*,desc->noutputs);
if (factory->statistics_enabled){
obj->stats=find_or_create_stats(factory,desc);
}
obj->factory=factory;
if (obj->desc->init!=NULL)
obj->desc->init(obj);
return obj;
}
在Mediastreamer2庫源碼的src/base/msfilter.c文件中有函數(shù)ms_filter_link欧引,其功能為連接兩個MSFilter 結(jié)構(gòu)频伤。
int ms_filter_link(MSFilter *f1, int pin1, MSFilter *f2, int pin2){
MSQueue *q;
ms_message("ms_filter_link: %s:%p,%i-->%s:%p,%i",f1->desc->name,f1,pin1,f2->desc->name,f2,pin2);
ms_return_val_if_fail(pin1<f1->desc->noutputs, -1);
ms_return_val_if_fail(pin2<f2->desc->ninputs, -1);
ms_return_val_if_fail(f1->outputs[pin1]==NULL,-1);
ms_return_val_if_fail(f2->inputs[pin2]==NULL,-1);
q=ms_queue_new(f1,pin1,f2,pin2);
f1->outputs[pin1]=q;
f2->inputs[pin2]=q;
return 0;
}
以音頻流為例,通過在這兩個函數(shù)中添加打印我們能得到這樣兩條線:
MSPulseRead-->MSEqualizer-->MSVolume-->MSAudioMixer-->MSOpusEnc-->MSRtpSend
MSRtpRecv-->MSOpusDec-->MSAudioFlowControl-->MSDtmfGen-->MSVolume-->MSEqualizer-->MSAudioMixer-->MSPulseWrite
這便是在音頻通話過程中使用MSFilter結(jié)構(gòu)連成的兩條處理鏈芝此。此處使用的音頻流編解碼器為Opus憋肖。注意:根據(jù)個人安裝環(huán)境的不同得到的處理鏈會有差別。
下面癌蓖,我們進入正題瞬哼,修改音頻/視頻流編解碼器:
前面我們說到ms_factory_create_filter_from_desc函數(shù)的功能是通過MSFilterDesc來創(chuàng)建MSFilter結(jié)構(gòu),那這個MSFilterDesc是什么呢租副?這里我就直接公布答案坐慰,MSFilterDesc為我們要創(chuàng)建的MSFilter結(jié)構(gòu)的描述,既根據(jù)描述的不同用僧,我們創(chuàng)建的MSFilter結(jié)構(gòu)功能便會不同结胀。在src/audiofilters目錄與src/videofilters目錄中有很多以編解碼器命名的.c文件,比如前面提到的msopus.c责循,或是vp8.c等糟港,這里opus是音頻流編解碼器,vp8是視頻流編解碼器院仿。以解碼器為例分別有這樣的描述:
MSFilterDesc ms_opus_enc_desc = {
MS_OPUS_ENC_ID,
MS_OPUS_ENC_NAME,
MS_OPUS_ENC_DESCRIPTION,
MS_OPUS_ENC_CATEGORY,
MS_OPUS_ENC_ENC_FMT,
MS_OPUS_ENC_NINPUTS,
MS_OPUS_ENC_NOUTPUTS,
ms_opus_enc_init, //解碼器初始化函數(shù)
ms_opus_enc_preprocess, //預(yù)處理函數(shù)
ms_opus_enc_process, //解碼處理函數(shù)
ms_opus_enc_postprocess, //后處理函數(shù)
ms_opus_enc_uninit, //去初始化函數(shù)
ms_opus_enc_methods,
MS_OPUS_ENC_FLAGS
};
MSFilterDesc ms_vp8_dec_desc = {
MS_VP8_DEC_ID,
MS_VP8_DEC_NAME,
MS_VP8_DEC_DESCRIPTION,
MS_VP8_DEC_CATEGORY,
MS_VP8_DEC_ENC_FMT,
MS_VP8_DEC_NINPUTS,
MS_VP8_DEC_NOUTPUTS,
dec_init,
dec_preprocess,
dec_process,
dec_postprocess,
dec_uninit,
dec_methods,
MS_VP8_DEC_FLAGS
};
我們要替換使用一個新的編解碼器秸抚,首先要做的就是編寫這樣兩個MSFilterDesc,為什么是兩個呢歹垫?編碼器解碼器各一個嘛剥汤。至于MSFilterDesc更詳細的編寫,望讀者參考已經(jīng)列舉出的編解碼器MSFilterDesc源碼自行解決排惨。
在編寫完編解碼器的MSFilterDesc及各函數(shù)之后吭敢,如何能讓Mediastreamer2庫找到我們自己添加的這個編解碼器呢?在vp8編解碼器的ms_vp8_dec_desc 下面有一句MS_FILTER_DESC_EXPORT(ms_vp8_dec_desc),實際上每個默認的編解碼器MSFilterDesc下面都有類似的這么一句代碼暮芭,其作用就是讓Mediastreamer2庫能找到這個MSFilterDesc鹿驼。下面說下Mediastreamer2庫是如何通過MS_FILTER_DESC_EXPORT找到MSFilterDesc的:
在src目錄下有voipdescs.h文件,里面記錄有所有MSFilterDesc的名字辕宏,但該文件并不是一開始就有的畜晰,而是在Mediastreamer2庫編譯過程中創(chuàng)建的,誰定義創(chuàng)建的呢瑞筐?同樣src目錄下的generate_descs_header.cmake凄鼻。
set(ABS_SOURCE_FILES )
string(REPLACE " " ";" SOURCE_FILES ${SOURCE_FILES})
foreach(SOURCE_FILE ${SOURCE_FILES})
list(APPEND ABS_SOURCE_FILES "${INPUT_DIR}/${SOURCE_FILE}")
endforeach()
execute_process(
COMMAND ${AWK_PROGRAM} -f "${AWK_SCRIPTS_DIR}/extract-filters-names.awk" ${ABS_SOURCE_FILES}
OUTPUT_FILE "${OUTPUT_DIR}/${TYPE}descs.txt"
)
execute_process(
COMMAND ${AWK_PROGRAM} -f "${AWK_SCRIPTS_DIR}/define-filters.awk"
INPUT_FILE "${OUTPUT_DIR}/${TYPE}descs.txt"
OUTPUT_FILE "${OUTPUT_DIR}/${TYPE}descs-tmp1.h"
)
execute_process(
COMMAND ${AWK_PROGRAM} -f "${AWK_SCRIPTS_DIR}/define-ms_${TYPE}_filter_descs.awk"
INPUT_FILE "${OUTPUT_DIR}/${TYPE}descs.txt"
OUTPUT_FILE "${OUTPUT_DIR}/${TYPE}descs-tmp2.h"
)
file(READ "${OUTPUT_DIR}/${TYPE}descs-tmp1.h" DESCS1)
file(READ "${OUTPUT_DIR}/${TYPE}descs-tmp2.h" DESCS2)
set(NEW_DESCS "${DESCS1}${DESCS2}")
if(EXISTS "${OUTPUT_DIR}/${TYPE}descs.h")
file(READ "${OUTPUT_DIR}/${TYPE}descs.h" OLD_DESCS)
endif()
if(OLD_DESCS)
if(NOT OLD_DESCS STREQUAL "${NEW_DESCS}")
file(WRITE "${OUTPUT_DIR}/${TYPE}descs.h" "${NEW_DESCS}")
endif()
else()
file(WRITE "${OUTPUT_DIR}/${TYPE}descs.h" "${NEW_DESCS}")
endif()
file(REMOVE
"${OUTPUT_DIR}/${TYPE}descs.txt"
"${OUTPUT_DIR}/${TYPE}descs-tmp1.h"
"${OUTPUT_DIR}/${TYPE}descs-tmp2.h"
)
從cmake代碼中我們能看到${AWK_PROGRAM},沒錯,這就是我們熟悉的那個awk命令--格式化文本野宜。再找到extract-filters-names.awk文件打開:
BEGIN { FS="[()]" ; }; /^\t*MS_FILTER_DESC_EXPORT/{ printf("%s\n", $2) }
看到這個MS_FILTER_DESC_EXPORT扫步,相信大家應(yīng)該都明白了吧。然后就是文件路徑${SOURCE_FILES}了匈子,在CMakeLists.txt中設(shè)置河胎,這里就交給讀者自己解決吧。
下面最后一步虎敦,Linphone是如何知道Mediastreamer2庫有這些編解碼器的游岳?跟著Linphone初始化的函數(shù)一直往下跟,最終在Linphone源碼庫的coreapi/linphonecore.c中找到linphone_core_register_default_codecs
static void linphone_core_register_default_codecs(LinphoneCore *lc){
const char *aac_fmtp162248, *aac_fmtp3244;
bool_t opus_enabled=TRUE;
/*default enabled audio codecs, in order of preference*/
#if defined(__arm__) || defined(_M_ARM)
/*hack for opus, that needs to be disabed by default on ARM single processor, otherwise there is no cpu left for video processing*/
//if (ms_get_cpu_count()==1) opus_enabled=FALSE;
if (ms_factory_get_cpu_count(lc->factory)==1) opus_enabled=FALSE;
#endif
linphone_core_register_payload_type(lc,&payload_type_opus,"useinbandfec=1",opus_enabled);
linphone_core_register_payload_type(lc,&payload_type_silk_wb,NULL,TRUE);
linphone_core_register_payload_type(lc,&payload_type_speex_wb,"vbr=on",TRUE);
linphone_core_register_payload_type(lc,&payload_type_speex_nb,"vbr=on",TRUE);
linphone_core_register_payload_type(lc,&payload_type_pcmu8000,NULL,TRUE);
linphone_core_register_payload_type(lc,&payload_type_pcma8000,NULL,TRUE);
/* Text codecs in order or preference (RED first (more robust), then T140) */
linphone_core_register_payload_type(lc, &payload_type_t140_red, NULL, TRUE);
linphone_core_register_payload_type(lc, &payload_type_t140, NULL, TRUE);
/*other audio codecs, not enabled by default, in order of preference*/
linphone_core_register_payload_type(lc,&payload_type_gsm,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_g722,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_ilbc,"mode=30",FALSE);
linphone_core_register_payload_type(lc,&payload_type_amr,"octet-align=1",FALSE);
linphone_core_register_payload_type(lc,&payload_type_amrwb,"octet-align=1",FALSE);
linphone_core_register_payload_type(lc,&payload_type_g729,"annexb=yes",TRUE);
/* For AAC, we use a config value to determine if we ought to support SBR. Since it is not offically supported
* for the mpeg4-generic mime type, setting this flag to 1 will break compatibility with other clients. */
if( lp_config_get_int(lc->config, "misc", "aac_use_sbr", FALSE) ) {
ms_message("Using SBR for AAC");
aac_fmtp162248 = "config=F8EE2000; constantDuration=512; indexDeltaLength=3; indexLength=3; mode=AAC-hbr; profile-level-id=76; sizeLength=13; streamType=5; SBR-enabled=1";
aac_fmtp3244 = "config=F8E82000; constantDuration=512; indexDeltaLength=3; indexLength=3; mode=AAC-hbr; profile-level-id=76; sizeLength=13; streamType=5; SBR-enabled=1";
} else {
aac_fmtp162248 = "config=F8EE2000; constantDuration=512; indexDeltaLength=3; indexLength=3; mode=AAC-hbr; profile-level-id=76; sizeLength=13; streamType=5";
aac_fmtp3244 = "config=F8E82000; constantDuration=512; indexDeltaLength=3; indexLength=3; mode=AAC-hbr; profile-level-id=76; sizeLength=13; streamType=5";
}
linphone_core_register_payload_type(lc,&payload_type_aaceld_16k,aac_fmtp162248,FALSE);
linphone_core_register_payload_type(lc,&payload_type_aaceld_22k,aac_fmtp162248,FALSE);
linphone_core_register_payload_type(lc,&payload_type_aaceld_32k,aac_fmtp3244,FALSE);
linphone_core_register_payload_type(lc,&payload_type_aaceld_44k,aac_fmtp3244,FALSE);
linphone_core_register_payload_type(lc,&payload_type_aaceld_48k,aac_fmtp162248,FALSE);
linphone_core_register_payload_type(lc,&payload_type_isac,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_speex_uwb,"vbr=on",FALSE);
linphone_core_register_payload_type(lc,&payload_type_silk_nb,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_silk_mb,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_silk_swb,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_g726_16,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_g726_24,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_g726_32,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_g726_40,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_aal2_g726_16,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_aal2_g726_24,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_aal2_g726_32,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_aal2_g726_40,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_codec2,NULL,FALSE);
linphone_core_register_payload_type(lc,&payload_type_bv16,NULL,FALSE);
#ifdef VIDEO_ENABLED
/*default enabled video codecs, in order of preference*/
linphone_core_register_payload_type(lc,&payload_type_vp8,NULL,TRUE);
linphone_core_register_payload_type(lc,&payload_type_h264,"profile-level-id=42801F",TRUE);
linphone_core_register_payload_type(lc,&payload_type_mp4v,"profile-level-id=3",TRUE);
linphone_core_register_payload_type(lc,&payload_type_h263_1998,"CIF=1;QCIF=1",FALSE);
linphone_core_register_payload_type(lc,&payload_type_h263,NULL,FALSE);
#endif
/*register all static payload types declared in av_profile of oRTP, if not already declared above*/
linphone_core_register_static_payloads(lc);
}
這個函數(shù)也是夠簡單直接的了其徙,相信大家也都能看懂胚迫。完成以上這些步驟之后也就完成編解碼器的替換啦,這里預(yù)祝大家能成功~
如要轉(zhuǎn)載請附上原地址唾那。