linphone

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

2019-12-15  本文已影响0人  Bingount

大家好,这里是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);

}

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

如要转载请附上原地址。

上一篇 下一篇

猜你喜欢

热点阅读