大家好,这里是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);
}
这个函数也是够简单直接的了,相信大家也都能看懂。完成以上这些步骤之后也就完成编解码器的替换啦,这里预祝大家能成功~
如要转载请附上原地址。
网友评论