Linphone笔记-替换音频/视频编解码器

大家好,这里是Bingonut的编程笔记。

最近一直在研究Linphone在Linux平台的源码文件,需要修改在视频通话过程中所使用的视频流编解码器(当然,这里指的是替换成Linphone原来没有的编解码器),但网上并没有真正说明并解决这个问题的文章,故留此笔记,造福后世~

注意:前面我会先着重介绍Mediastreamer2库的工作原理。

首先我们必须得明确知道Linphone的主要依赖库,以找准目标。下面是我做的一张Linphone的依赖关系图:
依赖关系图.jpg

我们这次的目标主要为Mediastreamer2库,这个开源库负责在Linphone中接收和发送所有多媒体流,包括语音/视频捕获,编码和解码以及渲染。关于这个库的工作原理,Linphone官方仅有一小段的描述,第一次看的话难免会摸不着头脑,这里我便多说一点。

Mediastreamer2库的功能是由一串的MSFilter结构完成的,每个MSFilter结构完成一个小功能,比如本文的主题,流数据的编码和解码,MSFilter结构由MSFactory调用MSFilterDesc(用来描述MSFilter)来创建。举个例子,当我们在进行音频通话时,需要接收音频数据,然后进行数据处理,最后再播放出来,而这个过程在Mediastreamer2中就是先创建一系列的MSFilter并把它们连接成链,“音频接收MSFilter”接收音频流数据,然后传给“解码MSFilter”进行解码,然后继续把解码后的数据传递给下一个MSFilter。了解了Mediastreamer2库的这个工作模式之后下面就好进行说明了:

在Mediastreamer2库源码的src/base/msfactory.c文件中有函数ms_factory_create_filter_from_desc,其功能为通过MSFilterDesc创建一个MSFilter结构。

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文件中有函数ms_filter_link,其功能为连接两个MSFilter 结构。

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;

}

以音频流为例,通过在这两个函数中添加打印我们能得到这样两条线:

MSPulseRead-->MSEqualizer-->MSVolume-->MSAudioMixer-->MSOpusEnc-->MSRtpSend

MSRtpRecv-->MSOpusDec-->MSAudioFlowControl-->MSDtmfGen-->MSVolume-->MSEqualizer-->MSAudioMixer-->MSPulseWrite

这便是在音频通话过程中使用MSFilter结构连成的两条处理链。此处使用的音频流编解码器为Opus。注意:根据个人安装环境的不同得到的处理链会有差别。

下面,我们进入正题,修改音频/视频流编解码器:

前面我们说到ms_factory_create_filter_from_desc函数的功能是通过MSFilterDesc来创建MSFilter结构,那这个MSFilterDesc是什么呢?这里我就直接公布答案,MSFilterDesc为我们要创建的MSFilter结构的描述,既根据描述的不同,我们创建的MSFilter结构功能便会不同。在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,                    //解码器初始化函数

ms_opus_enc_preprocess,       //预处理函数

ms_opus_enc_process,            //解码处理函数

ms_opus_enc_postprocess,     //后处理函数

ms_opus_enc_uninit,                //去初始化函数

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更详细的编写,望读者参考已经列举出的编解码器MSFilterDesc源码自行解决。

在编写完编解码器的MSFilterDesc及各函数之后,如何能让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库编译过程中创建的,谁定义创建的呢?同样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,相信大家应该都明白了吧。然后就是文件路径${SOURCE_FILES}了,在CMakeLists.txt中设置,这里就交给读者自己解决吧。

下面最后一步,Linphone是如何知道Mediastreamer2库有这些编解码器的?跟着Linphone初始化的函数一直往下跟,最终在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);

}

这个函数也是够简单直接的了,相信大家也都能看懂。完成以上这些步骤之后也就完成编解码器的替换啦,这里预祝大家能成功~

如要转载请附上原地址。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容