1 |
|
%%============================================================================== |
2 |
|
%% Copyright 2018 Erlang Solutions Ltd. |
3 |
|
%% |
4 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
%% you may not use this file except in compliance with the License. |
6 |
|
%% You may obtain a copy of the License at |
7 |
|
%% |
8 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
9 |
|
%% |
10 |
|
%% Unless required by applicable law or agreed to in writing, software |
11 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
12 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
|
%% See the License for the specific language governing permissions and |
14 |
|
%% limitations under the License. |
15 |
|
%%============================================================================== |
16 |
|
%% |
17 |
|
%% @author Michal Piotrowski <michal.piotrowski@erlang-solutions.com> |
18 |
|
-module(jingle_to_sdp). |
19 |
|
|
20 |
|
%% @doc this modules translates jingle stanza to a sdp packet |
21 |
|
%% |
22 |
|
|
23 |
|
-export([from_media/1]). |
24 |
|
-export([parse_transport_element/1]). |
25 |
|
|
26 |
|
-include("mongoose.hrl"). |
27 |
|
-include("jlib.hrl"). |
28 |
|
-include_lib("nksip/include/nksip.hrl"). |
29 |
|
|
30 |
|
-type rtphdr_ext() :: {ID :: binary(), URI :: binary(), Senders :: binary()}. |
31 |
|
|
32 |
|
-type source() :: {SSRC :: binary(), [{binary(), binary()}]}. |
33 |
|
|
34 |
|
-type content() :: #{media := undefined | binary(), |
35 |
|
name := undefined | binary(), |
36 |
|
protocol := binary(), |
37 |
|
description := description(), |
38 |
|
transport := transport(), |
39 |
|
senders := binary()}. |
40 |
|
|
41 |
|
-type description() :: #{codecs := [codec()], |
42 |
|
rtphdr_ext := [rtphdr_ext()], |
43 |
|
rtcp_mux := boolean(), |
44 |
|
sources := [source()]}. |
45 |
|
|
46 |
|
-type transport() :: #{ufrag := undefined | binary(), |
47 |
|
pwd := undefined | binary(), |
48 |
|
fingerprint => fingerprint(), |
49 |
|
candidates => candidate()}. |
50 |
|
|
51 |
|
-type codec() :: #{id := binary(), |
52 |
|
name := binary(), |
53 |
|
clock_rate := binary(), |
54 |
|
channels := binary(), |
55 |
|
params := [param()], %% basic codec parameters. |
56 |
|
rtcp_fb_params := rtcp_fb_param()}. |
57 |
|
|
58 |
|
-type param() :: {binary(), binary()}. |
59 |
|
-type rtcp_fb_param() :: [binary()]. |
60 |
|
|
61 |
|
-type fingerprint() :: {Hash :: undefined | binary(), Setup :: undefined | binary(), |
62 |
|
Fingerprint :: binary()}. |
63 |
|
|
64 |
|
-type candidate() :: any(). |
65 |
|
|
66 |
|
-export_type([content/0]). |
67 |
|
|
68 |
|
-spec from_media(exml:element()) -> content(). |
69 |
|
from_media(#xmlel{name = <<"content">>} = JingleContent) -> |
70 |
60 |
Content = #{name => exml_query:attr(JingleContent, <<"name">>), |
71 |
|
protocol => <<"UDP/TLS/RTP/SAVPF">>, |
72 |
|
senders => decode_senders(JingleContent)}, |
73 |
60 |
parse_content_children(Content, JingleContent#xmlel.children). |
74 |
|
|
75 |
|
-spec parse_content_children(map(), list()) -> content(). |
76 |
|
parse_content_children(Content, []) -> |
77 |
60 |
Content; |
78 |
|
parse_content_children(Content, [Child | Rest]) -> |
79 |
120 |
NewContent = parse_content_child(Child, Content), |
80 |
120 |
parse_content_children(NewContent, Rest). |
81 |
|
|
82 |
|
|
83 |
|
parse_content_child(#xmlel{name = <<"description">>} = DescriptionEl, Content) -> |
84 |
60 |
Description = #{codecs => [], |
85 |
|
rtphdr_ext => [], |
86 |
|
rtcp_mux => false, |
87 |
|
sources => []}, |
88 |
60 |
Media = exml_query:attr(DescriptionEl, <<"media">>), |
89 |
60 |
NewDescription = parse_description_children(Description, DescriptionEl#xmlel.children), |
90 |
60 |
Content#{description => NewDescription, media => Media}; |
91 |
|
parse_content_child(#xmlel{name = <<"transport">>} = TransportEl, Content) -> |
92 |
60 |
NewTransport = parse_transport_element(TransportEl), |
93 |
60 |
Content#{transport => NewTransport}; |
94 |
|
parse_content_child(_, Content) -> |
95 |
:-( |
Content. |
96 |
|
|
97 |
|
-spec parse_transport_element(exml:element()) -> transport(). |
98 |
|
parse_transport_element(TransportEl) -> |
99 |
60 |
Ufrag = exml_query:attr(TransportEl, <<"ufrag">>), |
100 |
60 |
Pwd = exml_query:attr(TransportEl, <<"pwd">>), |
101 |
60 |
Transport = #{ufrag => Ufrag, |
102 |
|
pwd => Pwd, |
103 |
|
candidates => []}, |
104 |
60 |
parse_transport_children(Transport, TransportEl#xmlel.children). |
105 |
|
|
106 |
|
-spec parse_description_children(map(), list()) -> description(). |
107 |
|
parse_description_children(Desc, []) -> |
108 |
60 |
Desc; |
109 |
|
parse_description_children(Desc, [Child | Rest]) -> |
110 |
756 |
NewDesc = parse_description_child(Child, Desc), |
111 |
756 |
parse_description_children(NewDesc, Rest). |
112 |
|
|
113 |
|
parse_description_child(#xmlel{name = <<"payload-type">>} = Payload, Description) -> |
114 |
540 |
fill_codec(Description, Payload); |
115 |
|
parse_description_child(#xmlel{name = <<"rtp-hdrext">>} = RTPHdrExtEl, Description) -> |
116 |
132 |
RTPHdrExts = maps:get(rtphdr_ext, Description), |
117 |
132 |
RTPHdrExt = decode_rtp_hdr_ext(RTPHdrExtEl), |
118 |
132 |
Description#{rtphdr_ext := [RTPHdrExt | RTPHdrExts]}; |
119 |
|
parse_description_child(#xmlel{name = <<"rtcp-mux">>}, Description) -> |
120 |
51 |
Description#{rtcp_mux := true}; |
121 |
|
parse_description_child(#xmlel{name = <<"source">>} = SourceEl, Description) -> |
122 |
33 |
fill_source(Description, SourceEl); |
123 |
|
parse_description_child(_, Content) -> |
124 |
:-( |
Content. |
125 |
|
|
126 |
|
fill_codec(#{codecs := Codecs} = Desc, Payload) -> |
127 |
540 |
ID = exml_query:attr(Payload, <<"id">>), |
128 |
540 |
Name = exml_query:attr(Payload, <<"name">>), |
129 |
540 |
ClockRate = exml_query:attr(Payload, <<"clockrate">>), |
130 |
540 |
Channels = exml_query:attr(Payload, <<"channels">>, <<"1">>), |
131 |
540 |
Codec = #{id => ID, |
132 |
|
name => Name, |
133 |
|
clock_rate => ClockRate, |
134 |
|
channels => Channels, |
135 |
|
params => [], |
136 |
|
rtcp_fb_params => []}, |
137 |
540 |
FinalCodec = parse_payload_children(Codec, Payload#xmlel.children), |
138 |
540 |
Desc#{codecs := [FinalCodec | Codecs]}. |
139 |
|
|
140 |
|
parse_payload_children(Codec, []) -> |
141 |
540 |
Codec; |
142 |
|
parse_payload_children(Codec, [Child | Rest]) -> |
143 |
402 |
UpdatedCodec = parse_payload_child(Child, Codec), |
144 |
402 |
parse_payload_children(UpdatedCodec, Rest). |
145 |
|
|
146 |
|
parse_payload_child(#xmlel{name = <<"parameter">>} = BasicParam, |
147 |
|
#{params := Params} = Codec) -> |
148 |
147 |
Name = exml_query:attr(BasicParam, <<"name">>), |
149 |
147 |
Value = exml_query:attr(BasicParam, <<"value">>), |
150 |
147 |
NewParams = [{Name, Value} | Params], |
151 |
147 |
Codec#{params := NewParams}; |
152 |
|
parse_payload_child(#xmlel{name = <<"rtcp-fb">>} = RTCPParam, |
153 |
|
#{rtcp_fb_params := Params} = Codec) -> |
154 |
255 |
Param = decode_rtcp_fb_param(RTCPParam), |
155 |
255 |
NewParams = [Param | Params], |
156 |
255 |
Codec#{rtcp_fb_params := NewParams}; |
157 |
|
parse_payload_child(_, Codec) -> |
158 |
:-( |
Codec. |
159 |
|
|
160 |
|
decode_rtcp_fb_param(RTCPParam) -> |
161 |
255 |
Type = exml_query:attr(RTCPParam, <<"type">>), |
162 |
255 |
case exml_query:attr(RTCPParam, <<"subtype">>) of |
163 |
|
undefined -> |
164 |
159 |
[Type]; |
165 |
|
SubType -> |
166 |
96 |
[Type, SubType] |
167 |
|
end. |
168 |
|
|
169 |
|
decode_rtp_hdr_ext(RTPHdrExtEl) -> |
170 |
132 |
ID = exml_query:attr(RTPHdrExtEl, <<"id">>), |
171 |
132 |
URI = exml_query:attr(RTPHdrExtEl, <<"uri">>), |
172 |
132 |
Senders = exml_query:attr(RTPHdrExtEl, <<"senders">>, <<"both">>), |
173 |
132 |
{ID, URI, sender_to_sdp_attr(Senders)}. |
174 |
|
|
175 |
|
fill_source(#{sources := Sources} = Desc, SourceEl) -> |
176 |
33 |
Params = exml_query:subelements(SourceEl, <<"parameter">>), |
177 |
33 |
KV = [source_param_to_kv(Param) || Param <- Params], |
178 |
33 |
ID = exml_query:attr(SourceEl, <<"ssrc">>), |
179 |
33 |
Desc#{sources := [{ID, KV} | Sources]}. |
180 |
|
|
181 |
|
source_param_to_kv(El) -> |
182 |
63 |
Name = exml_query:attr(El, <<"name">>), |
183 |
63 |
Value = exml_query:attr(El, <<"value">>), |
184 |
63 |
{Name, Value}. |
185 |
|
|
186 |
|
decode_senders(ContentEl) -> |
187 |
60 |
SenderValue = exml_query:attr(ContentEl, <<"senders">>, <<"both">>), |
188 |
60 |
sender_to_sdp_attr(SenderValue). |
189 |
|
|
190 |
162 |
sender_to_sdp_attr(<<"both">>) -> <<"sendrecv">>; |
191 |
:-( |
sender_to_sdp_attr(<<"initiator">>) -> <<"sendonly">>; |
192 |
21 |
sender_to_sdp_attr(<<"responder">>) -> <<"recvonly">>; |
193 |
9 |
sender_to_sdp_attr(<<"none">>) -> <<"inactive">>. |
194 |
|
|
195 |
|
parse_transport_children(Transport, []) -> |
196 |
60 |
Transport; |
197 |
|
parse_transport_children(Transport, [Child | Rest]) -> |
198 |
60 |
NewTransport = parse_transport_child(Child, Transport), |
199 |
60 |
parse_transport_children(NewTransport, Rest). |
200 |
|
|
201 |
|
parse_transport_child(#xmlel{name = <<"fingerprint">>} = FingerprintEl, Transport) -> |
202 |
60 |
Hash = exml_query:attr(FingerprintEl, <<"hash">>), |
203 |
60 |
Setup = exml_query:attr(FingerprintEl, <<"setup">>), |
204 |
60 |
Fingerprint = exml_query:cdata(FingerprintEl), |
205 |
60 |
Transport#{fingerprint => {Hash, Setup, Fingerprint}}; |
206 |
|
parse_transport_child(#xmlel{name = <<"candidate">>, attrs = Attrs}, |
207 |
|
#{candidates := Candidates} = Transport) -> |
208 |
:-( |
NewCandidate = lists:foldl(fun parse_candidate_attr/2, #{}, Attrs), |
209 |
:-( |
Transport#{candidates := [NewCandidate | Candidates]}; |
210 |
|
parse_transport_child(_, Transport) -> |
211 |
:-( |
Transport. |
212 |
|
|
213 |
|
-spec parse_candidate_attr({binary(), any()}, map()) -> map(). |
214 |
|
parse_candidate_attr({<<"foundation">>, Val}, Map) -> |
215 |
:-( |
Map#{foundation => Val}; |
216 |
|
parse_candidate_attr({<<"component">>, Val}, Map) -> |
217 |
:-( |
Map#{component => Val}; |
218 |
|
parse_candidate_attr({<<"generation">>, Val}, Map) -> |
219 |
:-( |
Map#{generation => Val}; |
220 |
|
parse_candidate_attr({<<"id">>, Val}, Map) -> |
221 |
:-( |
Map#{id => Val}; |
222 |
|
parse_candidate_attr({<<"ip">>, Val}, Map) -> |
223 |
:-( |
Map#{ip => Val}; |
224 |
|
parse_candidate_attr({<<"network">>, Val}, Map) -> |
225 |
:-( |
Map#{network => Val}; |
226 |
|
parse_candidate_attr({<<"port">>, Val}, Map) -> |
227 |
:-( |
Map#{port => Val}; |
228 |
|
parse_candidate_attr({<<"priority">>, Val}, Map) -> |
229 |
:-( |
Map#{priority => Val}; |
230 |
|
parse_candidate_attr({<<"protocol">>, Val}, Map) -> |
231 |
:-( |
Map#{protocol => Val}; |
232 |
|
parse_candidate_attr({<<"rel-addr">>, Val}, Map) -> |
233 |
:-( |
Map#{raddr => Val}; |
234 |
|
parse_candidate_attr({<<"rel-port">>, Val}, Map) -> |
235 |
:-( |
Map#{rport => Val}; |
236 |
|
parse_candidate_attr({<<"tcptype">>, Val}, Map) -> |
237 |
:-( |
Map#{tcptype => Val}; |
238 |
|
parse_candidate_attr({<<"type">>, Val}, Map) -> |
239 |
:-( |
Map#{type => Val}; |
240 |
|
parse_candidate_attr(_, Map) -> |
241 |
:-( |
Map. |