大家好,這里是Bingonut的編程筆記。
最近一直在研究Linphone在Linux平臺(tái)的源碼文件,需要修改在視頻通話過程中所使用的視頻流編解碼器(當(dāng)然,這里指的是替換成Linphone原來沒有的編解碼器),但網(wǎng)上并沒有真正說明并解決這個(gè)問題的文章,故留此筆記,造福后世~
注意:前面我會(huì)先著重介紹Mediastreamer2庫(kù)的工作原理。
首先我們必須得明確知道Linphone的主要依賴庫(kù),以找準(zhǔn)目標(biāo)。下面是我做的一張Linphone的依賴關(guān)系圖:
我們這次的目標(biāo)主要為Mediastreamer2庫(kù),這個(gè)開源庫(kù)負(fù)責(zé)在Linphone中接收和發(fā)送所有多媒體流,包括語音/視頻捕獲,編碼和解碼以及渲染。關(guān)于這個(gè)庫(kù)的工作原理,Linphone官方僅有一小段的描述,第一次看的話難免會(huì)摸不著頭腦,這里我便多說一點(diǎn)。
Mediastreamer2庫(kù)的功能是由一串的MSFilter結(jié)構(gòu)完成的,每個(gè)MSFilter結(jié)構(gòu)完成一個(gè)小功能,比如本文的主題,流數(shù)據(jù)的編碼和解碼,MSFilter結(jié)構(gòu)由MSFactory調(diào)用MSFilterDesc(用來描述MSFilter)來創(chuàng)建。舉個(gè)例子,當(dāng)我們?cè)谶M(jìn)行音頻通話時(shí),需要接收音頻數(shù)據(jù),然后進(jìn)行數(shù)據(jù)處理,最后再播放出來,而這個(gè)過程在Mediastreamer2中就是先創(chuàng)建一系列的MSFilter并把它們連接成鏈,“音頻接收MSFilter”接收音頻流數(shù)據(jù),然后傳給“解碼MSFilter”進(jìn)行解碼,然后繼續(xù)把解碼后的數(shù)據(jù)傳遞給下一個(gè)MSFilter。了解了Mediastreamer2庫(kù)的這個(gè)工作模式之后下面就好進(jìn)行說明了:
在Mediastreamer2庫(kù)源碼的src/base/msfactory.c文件中有函數(shù)ms_factory_create_filter_from_desc,其功能為通過MSFilterDesc創(chuàng)建一個(gè)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庫(kù)源碼的src/base/msfilter.c文件中有函數(shù)ms_filter_link,其功能為連接兩個(gè)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;
}
以音頻流為例,通過在這兩個(gè)函數(shù)中添加打印我們能得到這樣兩條線:
MSPulseRead-->MSEqualizer-->MSVolume-->MSAudioMixer-->MSOpusEnc-->MSRtpSend
MSRtpRecv-->MSOpusDec-->MSAudioFlowControl-->MSDtmfGen-->MSVolume-->MSEqualizer-->MSAudioMixer-->MSPulseWrite
這便是在音頻通話過程中使用MSFilter結(jié)構(gòu)連成的兩條處理鏈。此處使用的音頻流編解碼器為Opus。注意:根據(jù)個(gè)人安裝環(huán)境的不同得到的處理鏈會(huì)有差別。
下面,我們進(jìn)入正題,修改音頻/視頻流編解碼器:
前面我們說到ms_factory_create_filter_from_desc函數(shù)的功能是通過MSFilterDesc來創(chuàng)建MSFilter結(jié)構(gòu),那這個(gè)MSFilterDesc是什么呢?這里我就直接公布答案,MSFilterDesc為我們要?jiǎng)?chuàng)建的MSFilter結(jié)構(gòu)的描述,既根據(jù)描述的不同,我們創(chuàng)建的MSFilter結(jié)構(gòu)功能便會(huì)不同。在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
};
我們要替換使用一個(gè)新的編解碼器,首先要做的就是編寫這樣兩個(gè)MSFilterDesc,為什么是兩個(gè)呢?編碼器解碼器各一個(gè)嘛。至于MSFilterDesc更詳細(xì)的編寫,望讀者參考已經(jīng)列舉出的編解碼器MSFilterDesc源碼自行解決。
在編寫完編解碼器的MSFilterDesc及各函數(shù)之后,如何能讓Mediastreamer2庫(kù)找到我們自己添加的這個(gè)編解碼器呢?在vp8編解碼器的ms_vp8_dec_desc 下面有一句MS_FILTER_DESC_EXPORT(ms_vp8_dec_desc),實(shí)際上每個(gè)默認(rèn)的編解碼器MSFilterDesc下面都有類似的這么一句代碼,其作用就是讓Mediastreamer2庫(kù)能找到這個(gè)MSFilterDesc。下面說下Mediastreamer2庫(kù)是如何通過MS_FILTER_DESC_EXPORT找到MSFilterDesc的:
在src目錄下有voipdescs.h文件,里面記錄有所有MSFilterDesc的名字,但該文件并不是一開始就有的,而是在Mediastreamer2庫(kù)編譯過程中創(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},沒錯(cuò),這就是我們熟悉的那個(gè)awk命令--格式化文本。再找到extract-filters-names.awk文件打開:
BEGIN { FS="[()]" ; }; /^\t*MS_FILTER_DESC_EXPORT/{ printf("%s\n", $2) }
看到這個(gè)MS_FILTER_DESC_EXPORT,相信大家應(yīng)該都明白了吧。然后就是文件路徑${SOURCE_FILES}了,在CMakeLists.txt中設(shè)置,這里就交給讀者自己解決吧。
下面最后一步,Linphone是如何知道Mediastreamer2庫(kù)有這些編解碼器的?跟著Linphone初始化的函數(shù)一直往下跟,最終在Linphone源碼庫(kù)的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);
}
這個(gè)函數(shù)也是夠簡(jiǎn)單直接的了,相信大家也都能看懂。完成以上這些步驟之后也就完成編解碼器的替換啦,這里預(yù)祝大家能成功~
如要轉(zhuǎn)載請(qǐng)附上原地址。