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

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

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

注意:前面我會先著重介紹Mediastreamer2庫的工作原理焦人。

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

我們這次的目標主要為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)載請附上原地址唾那。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末访锻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子闹获,更是在濱河造成了極大的恐慌期犬,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件避诽,死亡現(xiàn)場離奇詭異龟虎,居然都是意外死亡,警方通過查閱死者的電腦和手機沙庐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門鲤妥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拱雏,你說我怎么就攤上這事棉安。” “怎么了古涧?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵垂券,是天一觀的道長花盐。 經(jīng)常有香客問我羡滑,道長,這世上最難降的妖魔是什么算芯? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任柒昏,我火速辦了婚禮,結(jié)果婚禮上熙揍,老公的妹妹穿的比我還像新娘职祷。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布有梆。 她就那樣靜靜地躺著是尖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泥耀。 梳的紋絲不亂的頭發(fā)上饺汹,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機與錄音痰催,去河邊找鬼兜辞。 笑死,一個胖子當著我的面吹牛夸溶,可吹牛的內(nèi)容都是我干的逸吵。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼缝裁,長吁一口氣:“原來是場噩夢啊……” “哼扫皱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捷绑,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤啸罢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后胎食,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扰才,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年厕怜,在試婚紗的時候發(fā)現(xiàn)自己被綠了衩匣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡粥航,死狀恐怖琅捏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情递雀,我是刑警寧澤柄延,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站缀程,受9級特大地震影響搜吧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杨凑,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一滤奈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撩满,春花似錦蜒程、人聲如沸绅你。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忌锯。三九已至,卻和暖如春领炫,著一層夾襖步出監(jiān)牢的瞬間汉规,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工驹吮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留针史,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓碟狞,卻偏偏與公主長得像啄枕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子族沃,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容