Я написал несколько статей на эту тему, в том числе Как программно создать устройство RingCentral, PJSIP и RingCentral (часть 1, часть 2, часть 3, часть 4) и Использование SIP поверх TCP. и RTP для создания устройства RingCentral . Сегодня я напишу один о создании софтфона, потому что это непростая задача, и она заслуживает нескольких хороших статей или руководств. На этот раз мы будем использовать новый язык программирования: GoLang. Я хотел бы разделить эту статью на 3 подтемы:
- Формат сообщения SIP
- Регистрация софтфона
- Пион WebRTC
Формат сообщения SIP
Очень важно правильно настроить формат сообщения SIP. SIP-сервер не ответит, если вы отправите сообщение в неправильном формате. Прежде всего, разрывы строк в сообщении SIP - это \r\n
вместо \n
или \r
. Это хитрая ловушка. Я потратил дни на устранение странных проблем, и, наконец, это оказалось проблемой разрывов строк.
Правильное сообщение SIP состоит из 3 частей: темы, заголовков и тела. Позвольте мне показать вам образец сообщения SIP:
INVITE sip:3cea8c06-e425-424e-b775-d41d84893e7a@147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;transport=ws SIP/2.0 Via: SIP/2.0/WSS 199.255.120.223:8083;branch=z9hG4bK5Z5LTdovOUy To: "WIRELESS CALLER" <sip:17206666666*[email protected]> From: "WIRELESS CALLER" <sip:[email protected]>;tag=10.13.123.201-5070-51f012c3-aa83-42a Call-Id: 90370e2a-dfe7-4f5a-8a4d-c5bc3b65fc71 CSeq: 470226345 INVITE Max-Forwards: 67 Contact: <sip:[email protected]:8083;transport=wss> Content-Type: application/sdp Call-Info: <[email protected]>;purpose=info P-rc: <Msg><Hdr SID="35741438756848" Req="168655817232901423367701" Cmd="6" From="#[email protected]:5060" To="17206666666*11115"/><Bdy SrvLvl="-149699523" SrvLvlExt="406" Phn="+16506666666" Nm="WIRELESS CALLER" ToPhn="+16502886382" ToNm="Tyler Liu" RecUrl=""/></Msg> p-rc-api-call-info: callAttributes=reject,send-vm p-rc-api-ids: party-id=p-0a7c8d86db8a434fa4a0d029a9909f19-2;session-id=s-0a7c8d86db8a434fa4a0d029a9909f19 User-Agent: RC_SIPWRP_123.201 Content-Length: 877 v=0 o=- 5880443394298904479 7252956430269573836 IN IP4 199.255.120.232 s=SmcSip c=IN IP4 199.255.120.232 t=0 0 m=audio 21886 RTP/SAVPF 109 111 18 0 8 9 96 101 a=rtpmap:109 OPUS/16000 a=fmtp:109 useinbandfec=1 a=rtcp-fb:109 ccm tmmbr a=rtpmap:111 OPUS/48000/2 a=fmtp:111 useinbandfec=1 a=rtcp-fb:111 ccm tmmbr a=rtpmap:18 g729/8000 a=fmtp:18 annexb=no a=rtpmap:0 pcmu/8000 a=rtpmap:8 pcma/8000 a=rtpmap:9 g722/8000 a=rtpmap:96 ilbc/8000 a=fmtp:96 mode=20 a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-15 a=sendrecv a=rtcp:21887 a=rtcp-mux a=setup:actpass a=fingerprint:sha-1 E0:B2:83:1B:E2:0A:19:8D:A2:8C:CE:B3:60:3A:BC:17:83:67:28:81 a=ice-ufrag:MQSXD3LG a=ice-pwd:eLQMLNpXeCXEfoU38A6xzBCRLk a=candidate:lVt5YyAxexOkz5l5 1 UDP 2130706431 199.255.120.232 21886 typ host a=candidate:lVt5YyAxexOkz5l5 2 UDP 2130706430 199.255.120.232 21887 typ host
Тема - это первая строка: INVITE sip:3cea8c06-e425–424e-b775-d41d84893e7a@147b2c77–4f94–4572–85a1-fbdb2efb6aff.invalid;transport=ws SIP/2.0
Самое первое сообщение в последовательности обычно начинается с глагола, например INVITE
и REGISTER
. Сообщения, отвечающие на глагольные сообщения, не начинаются с глагола, но содержат код статуса и сообщение статуса, например SIP/2.0 100 Trying
, SIP/2.0 401 Unauthorized
и SIP/2.0 200 OK
Заголовки состоят из нескольких пар ключ-значение:
Via: SIP/2.0/WSS 199.255.120.223:8083;branch=z9hG4bK5Z5LTdovOUy To: "WIRELESS CALLER" <sip:17206666666*[email protected]> From: "WIRELESS CALLER" <sip:[email protected]>;tag=10.13.123.201-5070-51f012c3-aa83-42a Call-Id: 90370e2a-dfe7-4f5a-8a4d-c5bc3b65fc71 CSeq: 470226345 INVITE Max-Forwards: 67 Contact: <sip:[email protected]:8083;transport=wss> Content-Type: application/sdp Call-Info: <[email protected]>;purpose=info P-rc: <Msg><Hdr SID="35741438756848" Req="168655817232901423367701" Cmd="6" From="#[email protected]:5060" To="17206666666*11115"/><Bdy SrvLvl="-149699523" SrvLvlExt="406" Phn="+16506666666" Nm="WIRELESS CALLER" ToPhn="+16502886382" ToNm="Tyler Liu" RecUrl=""/></Msg> p-rc-api-call-info: callAttributes=reject,send-vm p-rc-api-ids: party-id=p-0a7c8d86db8a434fa4a0d029a9909f19-2;session-id=s-0a7c8d86db8a434fa4a0d029a9909f19 User-Agent: RC_SIPWRP_123.201 Content-Length: 877
Заголовки с SIP-сервера содержат довольно много волшебной пользовательской информации, такой как P-rc
, p-rc-api-ids
и p-rc-api-call-info
. В этой статье они нам не нужны, и, честно говоря, я не знаю, для чего они полезны. Возможно, некоторым приложениям RingCentral они могут понадобиться для правильной работы. Некоторая информация заголовков может быть выведена из тела, например Content-Type
и Content-Length
. Создавая SIP-сообщение с нуля, не забывайте, что эти заголовки выводятся из тела.
Тело отделяется от заголовков одной пустой строкой:
v=0 o=- 5880443394298904479 7252956430269573836 IN IP4 199.255.120.232 s=SmcSip c=IN IP4 199.255.120.232 t=0 0 m=audio 21886 RTP/SAVPF 109 111 18 0 8 9 96 101 a=rtpmap:109 OPUS/16000 a=fmtp:109 useinbandfec=1 a=rtcp-fb:109 ccm tmmbr a=rtpmap:111 OPUS/48000/2 a=fmtp:111 useinbandfec=1 a=rtcp-fb:111 ccm tmmbr a=rtpmap:18 g729/8000 a=fmtp:18 annexb=no a=rtpmap:0 pcmu/8000 a=rtpmap:8 pcma/8000 a=rtpmap:9 g722/8000 a=rtpmap:96 ilbc/8000 a=fmtp:96 mode=20 a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-15 a=sendrecv a=rtcp:21887 a=rtcp-mux a=setup:actpass a=fingerprint:sha-1 E0:B2:83:1B:E2:0A:19:8D:A2:8C:CE:B3:60:3A:BC:17:83:67:28:81 a=ice-ufrag:MQSXD3LG a=ice-pwd:eLQMLNpXeCXEfoU38A6xzBCRLk a=candidate:lVt5YyAxexOkz5l5 1 UDP 2130706431 199.255.120.232 21886 typ host a=candidate:lVt5YyAxexOkz5l5 2 UDP 2130706430 199.255.120.232 21887 typ host
Обратите внимание, что тело может быть пустой строкой. И даже если это пустая строка, ее все равно нужно отделить от заголовков пустой строкой, что приведет к появлению двух пустых строк в конце сообщения. Иначе просто не получится. Некоторые SIP-серверы могут быть более терпимыми, но RingCentral SIP этого не терпит.
Регистрация софтфона
Эта часть аналогична тому, что мы делали в Как программно создать устройство RingCentral. Обязательно прочитайте эту статью, чтобы освежить в памяти изменения - здесь я просто покажу вам пример потока сообщений и некоторый ключевой исходный код на GoLang.
REGISTER sip:sip.ringcentral.com SIP/2.0 CSeq: 8082 REGISTER Call-ID: 2f8d335a-037b-429f-ab91-3927bc98b2a2 Contact: <sip:3cea8c06-e425-424e-b775-d41d84893e7a@147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;transport=ws>;expires=600 Via: SIP/2.0/WSS 147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;branch=z9hG4bKe328d513-9671-415d-add2-f2d3d760d4b0 From: <sip:17206666666*[email protected]>;tag=b8731b49-0e4a-435b-abd0-273967eb27e2 To: <sip:17206666666*[email protected]> Content-Length: 0 User-Agent: github.com/ringcentral/ringcentral-softphone-go 2021/02/23 09:20:43 ↓↓↓ SIP/2.0 100 Trying Via: SIP/2.0/WSS 147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;branch=z9hG4bKe328d513-9671-415d-add2-f2d3d760d4b0;received=98.33.76.34 To: <sip:17206666666*[email protected]> From: <sip:17206666666*[email protected]>;tag=b8731b49-0e4a-435b-abd0-273967eb27e2 Call-ID: 2f8d335a-037b-429f-ab91-3927bc98b2a2 CSeq: 8082 REGISTER Content-Length: 0 2021/02/23 09:20:43 ↓↓↓ SIP/2.0 401 Unauthorized Via: SIP/2.0/WSS 147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;branch=z9hG4bKe328d513-9671-415d-add2-f2d3d760d4b0;received=98.33.76.34 To: <sip:17206666666*[email protected]>;tag=78ea291e5127de07f0581c77565a331b-46b0 From: <sip:17206666666*[email protected]>;tag=b8731b49-0e4a-435b-abd0-273967eb27e2 Call-ID: 2f8d335a-037b-429f-ab91-3927bc98b2a2 CSeq: 8082 REGISTER WWW-Authenticate: Digest realm="sip.ringcentral.com", nonce="YDU6l2A1OWtVYiAg/aSv5gkAVmQxjhN5" Content-Length: 0 2021/02/23 09:20:43 ↑↑↑ REGISTER sip:sip.ringcentral.com SIP/2.0 Call-ID: 2f8d335a-037b-429f-ab91-3927bc98b2a2 Via: SIP/2.0/TCP 147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;branch=z9hG4bK373f2f6c-220b-43dc-9e04-4921336965e5 From: <sip:17206666666*[email protected]>;tag=b8731b49-0e4a-435b-abd0-273967eb27e2 Content-Length: 0 Authorization: Digest algorithm=MD5, username="802398804016", realm="sip.ringcentral.com", nonce="YDU6l2A1OWtVYiAg/aSv5gkAVmQxjhN5", uri="sip:sip.ringcentral.com", response="ddc9fa7d6f41b3865dc0be334800aa1c" CSeq: 8083 REGISTER Contact: <sip:3cea8c06-e425-424e-b775-d41d84893e7a@147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;transport=ws>;expires=600 To: <sip:17206666666*[email protected]> User-Agent: github.com/ringcentral/ringcentral-softphone-go 2021/02/23 09:20:43 ↓↓↓ SIP/2.0 100 Trying Via: SIP/2.0/TCP 147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;branch=z9hG4bK373f2f6c-220b-43dc-9e04-4921336965e5;received=98.33.76.34 To: <sip:17206666666*[email protected]> From: <sip:17206666666*[email protected]>;tag=b8731b49-0e4a-435b-abd0-273967eb27e2 Call-ID: 2f8d335a-037b-429f-ab91-3927bc98b2a2 CSeq: 8083 REGISTER Content-Length: 0 2021/02/23 09:20:43 ↓↓↓ SIP/2.0 200 OK Via: SIP/2.0/TCP 147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;branch=z9hG4bK373f2f6c-220b-43dc-9e04-4921336965e5;received=98.33.76.34 To: <sip:17206666666*[email protected]>;tag=2a77cbf960ac867417503d7f78a9c521-05f6 From: <sip:17206666666*[email protected]>;tag=b8731b49-0e4a-435b-abd0-273967eb27e2 Call-ID: 2f8d335a-037b-429f-ab91-3927bc98b2a2 CSeq: 8083 REGISTER Contact: <sip:3cea8c06-e425-424e-b775-d41d84893e7a@147b2c77-4f94-4572-85a1-fbdb2efb6aff.invalid;transport=ws>;expires=52 Content-Length: 0
Исходный код GoLang для создания заголовка Authorization
на основе значения nonce
:
// GenerateResponse generate response field in the authorization header func GenerateResponse(username string, password string, realm string, method string, uri string, nonce string) string { ha1 := md5.Sum([]byte(fmt.Sprintf("%s:%s:%s", username, realm, password))) ha2 := md5.Sum([]byte(fmt.Sprintf("%s:%s", method, uri))) response := md5.Sum([]byte(fmt.Sprintf("%x:%s:%x", ha1, nonce, ha2))) return fmt.Sprintf("%x", response) } // GenerateAuthorization generate the authorization header func GenerateAuthorization(sipInfo ringcentral.SIPInfoResponse, method string, nonce string) string { return fmt.Sprintf( `Digest algorithm=MD5, username="%s", realm="%s", nonce="%s", uri="sip:%s", response="%s"`, sipInfo.AuthorizationId, sipInfo.Domain, nonce, sipInfo.Domain, GenerateResponse(sipInfo.AuthorizationId, sipInfo.Password, sipInfo.Domain, method, "sip:"+sipInfo.Domain, nonce), ) }
Пион WebRTC
Pion WebRTC - это чистая Go реализация API WebRTC. Я покажу вам код, чтобы заставить Pion WebRTC работать с RingCentral. Первый шаг - создать peerConnection
с mediaEngine
:
Затем нам нужно немного изменить тело SDP:
var re = regexp.MustCompile(`\r\na=rtpmap:111 OPUS/48000/2\r\n`) sdp := re.ReplaceAllString(inviteMessage.Body, "\r\na=rtpmap:111 OPUS/48000/2\r\na=mid:0\r\n")
Вышеупомянутое изменение предназначено для обхода проблемы в Pion WebRTC. Затем нам нужно установить предложение и ответ для SIP:
Обратите внимание на функцию GatheringCompletePromise
:
GatheringCompletePromise - это специальная вспомогательная функция для Pion, которая возвращает канал, который закрывается после завершения сбора. Эта функция может быть полезна в тех случаях, когда вы не можете получить своих ICE-кандидатов. Лучше не использовать эту функцию, а вместо этого подбирать кандидатов. Если вы воспользуетесь этой функцией, вы увидите более длительное время запуска соединения. Однако при подключении вызова вы не увидите никакого воздействия.
Наконец, нам нужно обработать входящую звуковую дорожку:
peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { log.Println("OnTrack") if softphone.OnTrack != nil { softphone.OnTrack(track) } })
Как обращаться со звуковой дорожкой, зависит от требований вашего бизнеса. Например, вы можете сохранить аудио в виде файла:
Исходный код
Для получения работоспособного и протестированного исходного кода, пожалуйста, обратитесь к RingCentral Softphone SDK для GoLang. И обычно вам не нужно создавать программный телефон с нуля, вы просто используете наш SDK, чтобы быстро создать программный телефон:
Резюме
В сегодняшней статье мы рассмотрели технические детали создания программного телефона RingCentral в GoLang. Мы начали с введения в структуру сообщения SIP, за которым последовало краткое объяснение процесса регистрации SIP. И, наконец, мы поделились некоторыми техническими подробностями о Pion WebRTC. Спасибо за чтение.
Пожалуйста, дайте нам знать, что вы думаете, оставив свои вопросы и комментарии ниже. Чтобы узнать больше о других функциях, обязательно посетите наш сайт для разработчиков, а если вы когда-нибудь столкнетесь с затруднениями, обязательно посетите наш форум разработчиков.
Хотите быть в курсе и узнавать о новых API и функциях? Присоединяйтесь к нашей программе Game Changer и получайте отличные награды за развитие своих навыков и изучение RingCentral больше!