Linphone筆記-替換音頻/視頻編解碼器

大家好,這里是Bingonut的編程筆記。

最近一直在研究Linphone在Linux平臺(tái)的源碼文件,需要修改在視頻通話過程中所使用的視頻流編解碼器(當(dāng)然,這里指的是替換成Linphone原來沒有的編解碼器),但網(wǎng)上并沒有真正說明并解決這個(gè)問題的文章,故留此筆記,造福后世~

注意:前面我會(huì)先著重介紹Mediastreamer2庫(kù)的工作原理。

首先我們必須得明確知道Linphone的主要依賴庫(kù),以找準(zhǔn)目標(biāo)。下面是我做的一張Linphone的依賴關(guān)系圖:
依賴關(guān)系圖.jpg

我們這次的目標(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)附上原地址。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容