修改linphone-sdk-android-下篇
前言
接上篇修改linphone-sdk-android-上篇
接中篇修改linphone-sdk-android-中篇
本文是下篇,本篇记录在上篇中提到的问题1排查过程及修复方案,尽量描述排查问题过程中的思路与方向
上篇中说问题1当初认为是linphone的bug,后面看源码及查资料发现可能不是bug,本篇将记录个人的理解
问题
这里再描述下问题1:打开音频编解码G722、G729等时,发起呼叫的INVITE SDP中,没有G722、G729的rtpmap
m=audio 7078 RTP/AVP 96 0 8 9 18 101 97
a=fmtp:18 annexb=yes
a=rtpmap:101 telephone-event/48000
a=rtpmap:97 telephone-event/8000
a=rtcp-fb:* trr-int 1000
a=rtcp-fb:* ccm tmmbr
分析
这里先了解下SDP协议,参考The Session Description Protocol (SDP) (3cx.com)
rtpmap
是Session attribute lines
,即为会话属性行,是对Payload Type
的补充说明,Payload Type
既是m=audio 7078 RTP/AVP 96 0 8 9 18 101 97
中AVP
后面的数字,这些数字是音频编解码对应的代码,对应关系如下:
下表源自Real-Time Transport Protocol (RTP) Parameters (iana.org)
PT | Encoding Name | Audio/Video (A/V) | Clock Rate (Hz) | Channels | Reference [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a6dXj5oX-1650980827516)(https://www.iana.org/assignments/_support/sort_none.gif)] |
---|---|---|---|---|---|
0 | PCMU | A | 8000 | 1 | [RFC3551] |
1 | Reserved | ||||
2 | Reserved | ||||
3 | GSM | A | 8000 | 1 | [RFC3551] |
4 | G723 | A | 8000 | 1 | [Vineet_Kumar][RFC3551] |
5 | DVI4 | A | 8000 | 1 | [RFC3551] |
6 | DVI4 | A | 16000 | 1 | [RFC3551] |
7 | LPC | A | 8000 | 1 | [RFC3551] |
8 | PCMA | A | 8000 | 1 | [RFC3551] |
9 | G722 | A | 8000 | 1 | [RFC3551] |
10 | L16 | A | 44100 | 2 | [RFC3551] |
11 | L16 | A | 44100 | 1 | [RFC3551] |
12 | QCELP | A | 8000 | 1 | [RFC3551] |
13 | CN | A | 8000 | 1 | [RFC3389] |
14 | MPA | A | 90000 | [RFC3551][RFC2250] | |
15 | G728 | A | 8000 | 1 | [RFC3551] |
16 | DVI4 | A | 11025 | 1 | [Joseph_Di_Pol] |
17 | DVI4 | A | 22050 | 1 | [Joseph_Di_Pol] |
18 | G729 | A | 8000 | 1 | [RFC3551] |
19 | Reserved | A | |||
20 | Unassigned | A | |||
21 | Unassigned | A | |||
22 | Unassigned | A | |||
23 | Unassigned | A | |||
24 | Unassigned | V | |||
25 | CelB | V | 90000 | [RFC2029] | |
26 | JPEG | V | 90000 | [RFC2435] | |
27 | Unassigned | V | |||
28 | nv | V | 90000 | [RFC3551] | |
29 | Unassigned | V | |||
30 | Unassigned | V | |||
31 | H261 | V | 90000 | [RFC4587] | |
32 | MPV | V | 90000 | [RFC2250] | |
33 | MP2T | AV | 90000 | [RFC2250] | |
34 | H263 | V | 90000 | [Chunrong_Zhu] | |
35-71 | Unassigned | ? | |||
72-76 | Reserved for RTCP conflict avoidance | [RFC3551] | |||
77-95 | Unassigned | ? | |||
96-127 | dynamic | ? | [RFC3551] |
从表中了解到,Payload Type(PT) code 0 - 95为静态类型,即code对应固定的codec(编解码器),96 - 127为动态codec,即需要在SDP协商过程中确定
接下来追踪下源码,看看SDP中为什么没有rtpmap
先找到Java
层发起呼叫的代码,在Core.java
中有4个发起呼叫的方法
@Nullable
Call invite(@NonNull String var1);@Nullable
Call inviteAddress(@NonNull Address var1);@Nullable
Call inviteAddressWithParams(@NonNull Address var1, @NonNull CallParams var2);@Nullable
Call inviteWithParams(@NonNull String var1, @NonNull CallParams var2);
具体实现在CoreImpl.java
中,查看这个public Call inviteAddress(@NonNull Address addr);
方法吧
private native Call inviteAddress(long nativePtr, Address addr);@Override @Nullable
synchronized public Call inviteAddress(@NonNull Address addr) {return (Call)inviteAddress(nativePtr, addr);
}
Java
层调用了native
层代码,打开编译后生成的linphone_jni.cc
,找到Java_org_linphone_core_CoreImpl_inviteAddress
方法
JNIEXPORT jobject JNICALL Java_org_linphone_core_CoreImpl_inviteAddress(JNIEnv *env, jobject thiz, jlong ptr, jobject addr) {LinphoneCore *cptr = (LinphoneCore*)ptr;if (cptr == nullptr) {bctbx_error("Java_org_linphone_core_CoreImpl_inviteAddress's LinphoneCore C ptr is null!");return 0;}LinphoneAddress* c_addr = nullptr;if (addr) c_addr = (LinphoneAddress*)GetObjectNativePtr(env, addr);jobject jni_result = (jobject)getCall(env, (LinphoneCall *)linphone_core_invite_address(cptr, c_addr), TRUE);return jni_result;
}
在native
层调用了linphone_core_invite_address
这个方法,在IDE中,可以通过Ctrl+左键点击进行跳转,linphone_core_invite_address
位于linphonecore.c
中
LinphoneCall * linphone_core_invite_address(LinphoneCore *lc, const LinphoneAddress *addr){LinphoneCall *call;LinphoneCallParams *p=linphone_core_create_call_params(lc, NULL);linphone_call_params_enable_video(p, linphone_call_params_video_enabled(p) && !!lc->video_policy.automatically_initiate);call=linphone_core_invite_address_with_params (lc,addr,p);linphone_call_params_unref(p);return call;
}
在linphone_core_invite_address
方法中调用了linphone_core_invite_address_with_params
发起呼叫,这个方法较长,删减一些不关心的代码
LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const LinphoneAddress *addr, const LinphoneCallParams *params){const char *from=NULL;LinphoneCall *call;if (!addr) {ms_error("Can't invite a NULL address");return NULL;}parsed_url2=linphone_address_new(from);call=linphone_call_new_outgoing(lc,parsed_url2,addr,cp,proxy);bool defer = Call::toCpp(call)->initiateOutgoing();if (!defer) {if (Call::toCpp(call)->startInvite(nullptr) != 0) {/* The call has already gone to error and released state, so do not return it */call = nullptr;}}return call;
}
在linphone_core_invite_address_with_params
方法中调用linphone_call_new_outgoing
方法创建Call
对象,调用initiateOutgoing
方法初始化发起呼叫并设置当前状态为OutgoingInit
,接下来调用startInvite
方法发起呼叫,startInvite
方法位于call.cpp
中,在其中又调用getActiveSession
方法获取CallSession
,调用CallSession::startInvite
方法
int Call::startInvite (const Address *destination) {return getActiveSession()->startInvite(destination, "");
}
CallSession::startInvite
方法位于call-session.cpp
中,在这个方法中找了半天,没见有与SDP发送相关的逻辑,先去头文件中看看方法原型吧
找了半天也是有点收获的,分析出调用addAdditionalLocalBody
去组装自定义扩展头数据
int CallSession::startInvite (const Address *destination, const string &subject, const Content *content) {L_D();d->subject = subject;/* Try to be best-effort in giving real local or routable contact address */d->setContactOp();string destinationStr;char *realUrl = nullptr;if (destination)destinationStr = destination->asString();else {realUrl = linphone_address_as_string(d->log->to);destinationStr = realUrl;ms_free(realUrl);}char *from = linphone_address_as_string(d->log->from);/* Take a ref because sal_call() may destroy the CallSession if no SIP transport is available */shared_ptr<CallSession> ref = getSharedFromThis();if (content)d->op->setLocalBody(*content);// If a custom Content has been set in the call params, create a multipart body for the INVITEfor (auto& content : d->params->getCustomContents()) {d->op->addAdditionalLocalBody(content);}int result = d->op->call(from, destinationStr, subject);ms_free(from);if (result < 0) {if ((d->state != CallSession::State::Error) && (d->state != CallSession::State::Released)) {// sal_call() may invoke call_failure() and call_released() SAL callbacks synchronously,// in which case there is no need to perform a state change here.d->setState(CallSession::State::Error, "Call failed");}} else {linphone_call_log_set_call_id(d->log, d->op->getCallId().c_str()); /* Must be known at that time */d->setState(CallSession::State::OutgoingProgress, "Outgoing call in progress");}return result;
}
CallSession::startInvite
方法原型为,
virtual int startInvite (const Address *destination, const std::string &subject = "", const Content *content = nullptr);
是个virtual
虚函数,说明有函数复写,在IDE中搜索发现MediaSession
类继承自CallSession
,好的,找到MediaSession
复写的startInvite
方法,方法较长,删除一些不关心的代码
int MediaSession::startInvite (const Address *destination, const string &subject, const Content *content) {L_D();// 删除不关心的代码d->op->setLocalMediaDescription(d->localDesc);int result = CallSession::startInvite(destination, subject, content);if (result < 0) {if (d->state == CallSession::State::Error)d->stopStreams();return result;}return result;
}
在MediaSession::startInvite
中调用setLocalMediaDescription
方法组装本地媒体描述信息,最后再调用父类的CallSession::startInvite
方法继续发起呼叫,好的,现在只关心setLocalMediaDescription
方法,其中op
是SalCallOp
,在IDE中打开call-op.cpp
,找到setLocalMediaDescription
方法,删减一些不关心的代码
int SalCallOp::setLocalMediaDescription (SalMediaDescription *desc) {if (desc) {sal_media_description_ref(desc);belle_sip_error_code error;belle_sdp_session_description_t *sdp = media_description_to_sdp(desc);vector<char> buffer = marshalMediaDescription(sdp, error);belle_sip_object_unref(sdp);if (error != BELLE_SIP_OK)return -1;mLocalBody.setContentType(ContentType::Sdp);mLocalBody.setBody(move(buffer));} else {mLocalBody = Content();}return 0;
}
到这里终于发现与SDP相关的方法了media_description_to_sdp
,继续查看media_description_to_sdp
方法,此方法位于sal_sdp.c
中,方法较长,主要是组装SDP协议数据,比如设置版本、创建源信息,创建会话等,这里删减一些不关心的代码
belle_sdp_session_description_t * media_description_to_sdp(const SalMediaDescription *desc) {belle_sdp_session_description_t* session_desc=belle_sdp_session_description_new();bool_t inet6;belle_sdp_origin_t* origin;int i;char *escaped_username = belle_sip_uri_to_escaped_username(desc->username);if ( strchr ( desc->addr,':' ) !=NULL ) {inet6=1;} else inet6=0;belle_sdp_session_description_set_version ( session_desc,belle_sdp_version_create ( 0 ) );origin = belle_sdp_origin_create ( escaped_username,desc->session_id,desc->session_ver,"IN", inet6 ? "IP6" :"IP4",desc->addr );bctbx_free(escaped_username);belle_sdp_session_description_set_origin ( session_desc,origin );belle_sdp_session_description_set_session_name ( session_desc,belle_sdp_session_name_create ( desc->name[0]!='\0' ? desc->name : "Talk" ) );// 删减不关心的代码for ( i=0; i<desc->nb_streams; i++ ) {stream_description_to_sdp(session_desc, desc, &desc->streams[i]);}return session_desc;
}
分析media_description_to_sdp
方法找到在stream_description_to_sdp
方法中组装数据流信息到SDP协议中,stream_description_to_sdp
方法非常长,此方法主要是组装SDP协议中编解码相关的信息,这里删除大部分不关心的代码
static void stream_description_to_sdp ( belle_sdp_session_description_t *session_desc, const SalMediaDescription *md, const SalStreamDescription *stream ) {// 删减不关心的代码media_desc = belle_sdp_media_description_create ( sal_stream_description_get_type_as_string(stream),stream->rtp_port,1,sal_media_proto_to_string ( stream->proto ),NULL );// 看到payloads字段if (stream->payloads) {for ( pt_it=stream->payloads; pt_it!=NULL; pt_it=pt_it->next ) {pt= ( PayloadType* ) pt_it->data;mime_param= belle_sdp_mime_parameter_create ( pt->mime_type, payload_type_get_number ( pt ), pt->clock_rate, pt->channels>0 ? pt->channels : -1 );belle_sdp_mime_parameter_set_parameters ( mime_param,pt->recv_fmtp );if ( stream->ptime>0 ) {belle_sdp_mime_parameter_set_ptime ( mime_param,stream->ptime );}// 锁定此方法belle_sdp_media_description_append_values_from_mime_parameter ( media_desc,mime_param );belle_sip_object_unref ( mime_param );}} else {/* to comply with SDP we cannot have an empty payload type number list *//* as it happens only when mline is declined with a zero port, it does not matter to put whatever codec*/belle_sip_list_t* format = belle_sip_list_append(NULL,0);belle_sdp_media_set_media_formats(belle_sdp_media_description_get_media(media_desc),format);}// 组装自定义sdp属性if (stream->custom_sdp_attributes) {belle_sdp_session_description_t *custom_desc = (belle_sdp_session_description_t *)stream->custom_sdp_attributes;belle_sip_list_t *l = belle_sdp_session_description_get_attributes(custom_desc);belle_sip_list_t *elem;for (elem = l; elem != NULL; elem = elem->next) {belle_sdp_media_description_add_attribute(media_desc, (belle_sdp_attribute_t *)elem->data);}}// 删减不关心的代码
}
在stream_description_to_sdp
方法中看到payload
字段,马上就要找到了happy~
经过分析,锁定belle_sdp_media_description_append_values_from_mime_parameter
方法,分析此方法,在其中找到组装rtpmap
的源码
void belle_sdp_media_description_append_values_from_mime_parameter(belle_sdp_media_description_t* media_description, const belle_sdp_mime_parameter_t* mime_parameter) {#ifndef BELLE_SDP_FORCE_RTP_MAP /* defined to for RTP map even for static codec*/if (!mime_parameter_is_static(mime_parameter)) {/*dynamic payload*/
#endifif (belle_sdp_mime_parameter_get_channel_count(mime_parameter)>1) {snprintf(atribute_value,MAX_FMTP_LENGTH,"%i %s/%i/%i",belle_sdp_mime_parameter_get_media_format(mime_parameter),belle_sdp_mime_parameter_get_type(mime_parameter),belle_sdp_mime_parameter_get_rate(mime_parameter),belle_sdp_mime_parameter_get_channel_count(mime_parameter));} else {snprintf(atribute_value,MAX_FMTP_LENGTH,"%i %s/%i",belle_sdp_mime_parameter_get_media_format(mime_parameter),belle_sdp_mime_parameter_get_type(mime_parameter),belle_sdp_mime_parameter_get_rate(mime_parameter));}belle_sdp_media_description_set_attribute_value(media_description,"rtpmap",atribute_value);
#ifndef BELLE_SDP_FORCE_RTP_MAP}
#endif// always include fmtp parameters if availableif (belle_sdp_mime_parameter_get_parameters(mime_parameter)) {snprintf(atribute_value,MAX_FMTP_LENGTH,"%i %s",belle_sdp_mime_parameter_get_media_format(mime_parameter),belle_sdp_mime_parameter_get_parameters(mime_parameter));belle_sdp_media_description_set_attribute_value(media_description,"fmtp",atribute_value);}
}
这里先分析下mime_parameter_is_static
方法是干什么的?查看以下源码发现,噢~~,原来是用于判断编解码是否是静态类型(前面提到的Payload Type)
const struct static_payload static_payload_list [] ={/*audio*/{0,1,"PCMU",8000},{3,1,"GSM",8000},{4,1,"G723",8000},{5,1,"DVI4",8000},{6,1,"DVI4",16000},{7,1,"LPC",8000},{8,1,"PCMA",8000},{9,1,"G722",8000},{10,2,"L16",44100},{11,1,"L16",44100},{12,1,"QCELP",8000},{13,1,"CN",8000},{14,1,"MPA",90000},{15,1,"G728",8000},{16,1,"DVI4",11025},{17,1,"DVI4",22050},{18,1,"G729",8000},/*video*/{25,0,"CelB",90000},{26,0,"JPEG",90000},{28,0,"nv",90000},{31,0,"H261",90000},{32,0,"MPV",90000},{33,0,"MP2T",90000},{34,0,"H263",90000}
};static int mime_parameter_is_static(const belle_sdp_mime_parameter_t *param){const struct static_payload* iterator;size_t i;for (iterator = static_payload_list,i=0;i<payload_list_elements;i++,iterator++) {if (iterator->number == param->media_format &&strcasecmp(iterator->type,param->type)==0 &&iterator->channel_count==param->channel_count &&iterator->rate==param->rate ) {return TRUE;}}return FALSE;
}
现在再来分析下belle_sdp_media_description_append_values_from_mime_parameter
方法的意思,大意如下:如果没有定义BELLE_SDP_FORCE_RTP_MAP
这个宏就执行if (!mime_parameter_is_static(mime_parameter))
判断编解码是否是静态类型,如果定义了就不判断是否是静态类型
总结一下就是如果没有定义BELLE_SDP_FORCE_RTP_MAP
这个宏,就不组装静态类型编解码的rtpmap
信息,只组装动态类型编解码的rtpmap
信息,终于找到源头了,真是拨云见日呀
到这里还没完,既然是根据宏定义做的判断,肯定在编译的时候可以配置,先看看能不能找到定义宏的地方,在IDE中全局搜索,在belle-sip
下的CMakeList.txt
中发现
option(ENABLE_RTP_MAP_ALWAYS_IN_SDP "Always include rtpmap in SDP." OFF)if(ENABLE_RTP_MAP_ALWAYS_IN_SDP) set(BELLE_SDP_FORCE_RTP_MAP 1)
endif()
bingo~,真的是到最后了
最后在编译时增加编译配置项
$ cd linphone-sdk/build/
$ cmake -DENABLE_RTP_MAP_ALWAYS_IN_SDP=ON ..
$ cmake --build . --parallel 8
重新编译后拷贝到AS中运行,发起呼叫查看Logcat输出
总结
在源码中看到通过BELLE_SDP_FORCE_RTP_MAP
这个宏控制是否在SDP中包含静态类型编解码的rtpmap
信息,个人猜测是静态类型的编解码信息,是协议中固定的,任何遵循协议的实现方,都可以根据静态类型编解码对应的code
解析出相应的rtpmap
信息,所以在SDP中去掉静态类型编解码器的rtpmap
信息,同时也可以减少发送数据包的大小,减轻网络压力
修改linphone-sdk-android-下篇相关推荐
- Linphone SDK 源码编译
Linphone SDK 源码编译 一.环境准备 编译环境 系统:Ubuntu – 建议使用Ubuntu,其他的系统可能有某些依赖存在问题,编译报错 Android SDK r24.2 Android ...
- Linphone SDK 最新版移植 iOS版
Linphone SDK 最新版移植 iOS版 主要集成了linphone的官网sdk,再结合官网的demo实现了简单的拨打sip电话功能,解决了网上linphone集成复杂,官网demo运行不了,官 ...
- Android SDK Android NDK 官方下载地址(zt)
Android SDK Android NDK 官方下载地址 Android SDK 3.0 Windows http://dl.google.com/android/installer_r10-wi ...
- Error:Android Source Generator: [sdk] Android SDK is not specified.
有时候使用intellij idea 带入android 项目,运行提示Error:Android Source Generator: [sdk] Android SDK is not specifi ...
- 如何修改eclipse 中Android的预览layout布局背景颜色
如何修改eclipse 中Android的预览layout布局背景颜色 最近在学习有关于主题和style方面的东西,不知道自己搞了什么东西,layout文件在预览的时候背景颜色总是黑色的,但是运行出来 ...
- 修改React Native Android'默认字体颜色为黑色
修改React Native Android'默认字体颜色为黑色 找到res/values/styles.xml文件插入代码 <style name="AppTheme" p ...
- 微信用户ios android比例,微信号可以修改了!Android 版正全量上线,iOS 也快了
原标题:微信号可以修改了!Android 版正全量上线,iOS 也快了 被多次问到的微信号修改问题,终于在今天得到了解答. 微信刚刚发布公告:最新 Android 版微信已上线修改微信号的功能,iOS ...
- android Linphone SDK
LinphoneLauncherActivity 是APP的入口组件,在这个组件里,它会启动LinphoneService这个后台服务,然后不断地判断这个后台服务是否已经启动完毕,如果已经启动完毕后, ...
- android studio修改配置文件夹(.android .gradle .AndroidStudio)位置
Android studio安装之后一般默认在c:/user/userid(*****)/;(比如:c:/user/xiaohong/)其他软件在安装中要创建缓存文件夹,那么也会在这个目录创建,比如N ...
- Android编译自定义sdk,java – 使用自定义android.bluetooth.而不是在android studio中默认的sdk android.jar中存在一个...
我想使用自定义android.bluetooth而不是Android SDK附带的android.jar中的presend.我将android.bluetooth编译成单独的jar文件并导入到stud ...
最新文章
- 修改tomcat7默认首页,将tomcat7默认目录指向自己的项目目录
- SpringMVC(二八) 重定向
- 官宣!中央广播电视总台《2021年春节联欢晚会》总导演组公布
- 国际计算机杂志排名2015,中国计算机学会推荐国际学术刊物与期刊(新增列表)2015-12-22-06_48_31...
- 苹果手机还原网络设置会怎样_装维技巧|手机能连wifi但网不通?
- 使用叶神模拟器无法访问本机服务器的问题(报错:java.net.ConnectException: failed to connect to /127.0.0.1 (port 5000) )
- 系统鸿蒙生态链,任正非透露鸿蒙系统细节,比安卓速度快60%,两三年建好生态链...
- Android:TextView控件
- ML--HMM(隐马尔可夫模型及python的实现2)
- 【智驾深谈】从滴滴Uber合并看中国智能出行“三国演义”
- deepstream多路rtsp流,避免相互影响
- 核磁共振设备工作过程人体温度监控中应用的光纤温度传感器
- Module-ScyllaDB技术文档
- 【UE4 附源工程】VR直升机模拟飞行与轰炸制作流程
- 互联网提供的音频/视频服务类型
- proteus原理图转化为pcb
- 深度学习远程服务器配置
- swap函数中的交换 【指针】 学习笔记
- HDU 4545-魔法串
- Excel多人同时编辑的几种方案与比较