1 /*
2 * Copyright (C) 2010 The Android Open Source Project
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 //#define LOG_NDEBUG 0
18 #define LOG_TAG "ASessionDescription"
19 #include <utils/Log.h>
20
21 #include "ASessionDescription.h"
22
23 #include <media/stagefright/foundation/ADebug.h>
24 #include <media/stagefright/foundation/AString.h>
25
26 #include <stdlib.h>
27
28 namespace android {
29
30 constexpr unsigned kDefaultAs = 960; // kbps?
31
ASessionDescription()32 ASessionDescription::ASessionDescription()
33 : mIsValid(false) {
34 }
35
~ASessionDescription()36 ASessionDescription::~ASessionDescription() {
37 }
38
setTo(const void * data,size_t size)39 bool ASessionDescription::setTo(const void *data, size_t size) {
40 mIsValid = parse(data, size);
41
42 if (!mIsValid) {
43 mTracks.clear();
44 mFormats.clear();
45 }
46
47 return mIsValid;
48 }
49
parse(const void * data,size_t size)50 bool ASessionDescription::parse(const void *data, size_t size) {
51 mTracks.clear();
52 mFormats.clear();
53
54 mTracks.push(Attribs());
55 mFormats.push(AString("[root]"));
56
57 AString desc((const char *)data, size);
58
59 size_t i = 0;
60 for (;;) {
61 ssize_t eolPos = desc.find("\n", i);
62
63 if (eolPos < 0) {
64 break;
65 }
66
67 AString line;
68 if ((size_t)eolPos > i && desc.c_str()[eolPos - 1] == '\r') {
69 // We accept both '\n' and '\r\n' line endings, if it's
70 // the latter, strip the '\r' as well.
71 line.setTo(desc, i, eolPos - i - 1);
72 } else {
73 line.setTo(desc, i, eolPos - i);
74 }
75
76 if (line.empty()) {
77 i = eolPos + 1;
78 continue;
79 }
80
81 if (line.size() < 2 || line.c_str()[1] != '=') {
82 return false;
83 }
84
85 ALOGV("%s", line.c_str());
86
87 switch (line.c_str()[0]) {
88 case 'v':
89 {
90 if (strcmp(line.c_str(), "v=0")) {
91 return false;
92 }
93 break;
94 }
95
96 case 'a':
97 case 'b':
98 {
99 AString key, value;
100
101 ssize_t colonPos = line.find(":", 2);
102 if (colonPos < 0) {
103 key = line;
104 } else {
105 key.setTo(line, 0, colonPos);
106
107 if (key == "a=fmtp" || key == "a=rtpmap"
108 || key == "a=framesize" || key == "a=extmap") {
109 ssize_t spacePos = line.find(" ", colonPos + 1);
110 if (spacePos < 0) {
111 return false;
112 }
113
114 key.setTo(line, 0, spacePos);
115
116 colonPos = spacePos;
117 }
118
119 value.setTo(line, colonPos + 1, line.size() - colonPos - 1);
120 }
121
122 key.trim();
123 value.trim();
124
125 ALOGV("adding '%s' => '%s'", key.c_str(), value.c_str());
126
127 mTracks.editItemAt(mTracks.size() - 1).add(key, value);
128 break;
129 }
130
131 case 'm':
132 {
133 ALOGV("new section '%s'",
134 AString(line, 2, line.size() - 2).c_str());
135
136 mTracks.push(Attribs());
137 mFormats.push(AString(line, 2, line.size() - 2));
138 break;
139 }
140
141 default:
142 {
143 AString key, value;
144
145 ssize_t equalPos = line.find("=");
146 /* The condition 'if (line.size() < 2 || line.c_str()[1] != '=')' a few lines above
147 * ensures '=' is at position 1. However for robustness we do the following check.
148 */
149 if (equalPos < 0) {
150 return false;
151 }
152
153 key = AString(line, 0, equalPos + 1);
154 value = AString(line, equalPos + 1, line.size() - equalPos - 1);
155
156 key.trim();
157 value.trim();
158
159 ALOGV("adding '%s' => '%s'", key.c_str(), value.c_str());
160
161 mTracks.editItemAt(mTracks.size() - 1).add(key, value);
162 break;
163 }
164 }
165
166 i = eolPos + 1;
167 }
168
169 return true;
170 }
171
isValid() const172 bool ASessionDescription::isValid() const {
173 return mIsValid;
174 }
175
countTracks() const176 size_t ASessionDescription::countTracks() const {
177 return mTracks.size();
178 }
179
getFormat(size_t index,AString * value) const180 void ASessionDescription::getFormat(size_t index, AString *value) const {
181 CHECK_GE(index, 0u);
182 CHECK_LT(index, mTracks.size());
183
184 *value = mFormats.itemAt(index);
185 }
186
findAttribute(size_t index,const char * key,AString * value) const187 bool ASessionDescription::findAttribute(
188 size_t index, const char *key, AString *value) const {
189 CHECK_GE(index, 0u);
190 CHECK_LT(index, mTracks.size());
191
192 value->clear();
193
194 const Attribs &track = mTracks.itemAt(index);
195 ssize_t i = track.indexOfKey(AString(key));
196
197 if (i < 0) {
198 return false;
199 }
200
201 *value = track.valueAt(i);
202
203 return true;
204 }
205
getCvoExtMap(size_t index,int32_t * cvoExtMap) const206 bool ASessionDescription::getCvoExtMap(
207 size_t index, int32_t *cvoExtMap) const {
208 CHECK_GE(index, 0u);
209 CHECK_LT(index, mTracks.size());
210
211 AString key, value;
212 *cvoExtMap = 0;
213
214 const Attribs &track = mTracks.itemAt(index);
215 for (size_t i = 0; i < track.size(); i++) {
216 value = track.valueAt(i);
217 if (value.size() > 0 && strcmp(value.c_str(), "urn:3gpp:video-orientation") == 0) {
218 key = track.keyAt(i);
219 break;
220 }
221 }
222
223 if (key.size() > 0) {
224 const char *colonPos = strrchr(key.c_str(), ':');
225 colonPos++;
226 *cvoExtMap = atoi(colonPos);
227 return true;
228 }
229
230 return false;
231 }
232
getFormatType(size_t index,unsigned long * PT,AString * desc,AString * params) const233 void ASessionDescription::getFormatType(
234 size_t index, unsigned long *PT,
235 AString *desc, AString *params) const {
236 AString format;
237 getFormat(index, &format);
238
239 const char *lastSpacePos = strrchr(format.c_str(), ' ');
240 CHECK(lastSpacePos != NULL);
241
242 char *end;
243 unsigned long x = strtoul(lastSpacePos + 1, &end, 10);
244 CHECK_GT(end, lastSpacePos + 1);
245 CHECK_EQ(*end, '\0');
246
247 *PT = x;
248
249 char key[20];
250 snprintf(key, sizeof(key), "a=rtpmap:%lu", x);
251 if (findAttribute(index, key, desc)) {
252 snprintf(key, sizeof(key), "a=fmtp:%lu", x);
253 if (!findAttribute(index, key, params)) {
254 params->clear();
255 }
256 } else {
257 desc->clear();
258 params->clear();
259 }
260 }
261
getDimensions(size_t index,unsigned long PT,int32_t * width,int32_t * height) const262 bool ASessionDescription::getDimensions(
263 size_t index, unsigned long PT,
264 int32_t *width, int32_t *height) const {
265 *width = 0;
266 *height = 0;
267
268 char key[20];
269 snprintf(key, sizeof(key), "a=framesize:%lu", PT);
270 AString value;
271 if (!findAttribute(index, key, &value)) {
272 return false;
273 }
274
275 const char *s = value.c_str();
276 char *end;
277 *width = strtoul(s, &end, 10);
278 CHECK_GT(end, s);
279 CHECK_EQ(*end, '-');
280
281 s = end + 1;
282 *height = strtoul(s, &end, 10);
283 CHECK_GT(end, s);
284 CHECK_EQ(*end, '\0');
285
286 return true;
287 }
288
getDurationUs(int64_t * durationUs) const289 bool ASessionDescription::getDurationUs(int64_t *durationUs) const {
290 *durationUs = 0;
291
292 CHECK(mIsValid);
293
294 AString value;
295 if (!findAttribute(0, "a=range", &value)) {
296 return false;
297 }
298
299 if (strncmp(value.c_str(), "npt=", 4) && strncmp(value.c_str(), "npt:", 4)) {
300 return false;
301 }
302
303 float from, to;
304 if (!parseNTPRange(value.c_str() + 4, &from, &to)) {
305 return false;
306 }
307
308 *durationUs = (int64_t)((to - from) * 1E6);
309
310 return true;
311 }
312
313 // static
ParseFormatDesc(const char * desc,int32_t * timescale,int32_t * numChannels)314 void ASessionDescription::ParseFormatDesc(
315 const char *desc, int32_t *timescale, int32_t *numChannels) {
316 const char *slash1 = strchr(desc, '/');
317 CHECK(slash1 != NULL);
318
319 const char *s = slash1 + 1;
320 char *end;
321 unsigned long x = strtoul(s, &end, 10);
322 CHECK_GT(end, s);
323 CHECK(*end == '\0' || *end == '/');
324
325 *timescale = x;
326 *numChannels = 1;
327
328 if (*end == '/') {
329 s = end + 1;
330 unsigned long x = strtoul(s, &end, 10);
331 CHECK_GT(end, s);
332 CHECK_EQ(*end, '\0');
333
334 *numChannels = x;
335 }
336 }
337
338 // static
parseNTPRange(const char * s,float * npt1,float * npt2)339 bool ASessionDescription::parseNTPRange(
340 const char *s, float *npt1, float *npt2) {
341 if (s[0] == '-') {
342 return false; // no start time available.
343 }
344
345 if (!strncmp("now", s, 3)) {
346 return false; // no absolute start time available
347 }
348
349 char *end;
350 *npt1 = strtof(s, &end);
351
352 if (end == s || *end != '-') {
353 // Failed to parse float or trailing "dash".
354 return false;
355 }
356
357 s = end + 1; // skip the dash.
358
359 if (*s == '\0') {
360 *npt2 = FLT_MAX; // open ended.
361 return true;
362 }
363
364 if (!strncmp("now", s, 3)) {
365 return false; // no absolute end time available
366 }
367
368 *npt2 = strtof(s, &end);
369
370 if (end == s || *end != '\0') {
371 return false;
372 }
373
374 return *npt2 > *npt1;
375 }
376
377 // static
SDPStringFactory(AString & sdp,const char * ip,bool isAudio,unsigned port,unsigned payloadType,unsigned as,const char * codec,const char * fmtp,int32_t width,int32_t height,int32_t cvoExtMap)378 void ASessionDescription::SDPStringFactory(AString &sdp,
379 const char *ip, bool isAudio, unsigned port, unsigned payloadType,
380 unsigned as, const char *codec, const char *fmtp,
381 int32_t width, int32_t height, int32_t cvoExtMap)
382 {
383 bool isIPv4 = (AString(ip).find("::") == -1) ? true : false;
384 sdp.clear();
385 sdp.append("v=0\r\n");
386
387 sdp.append("a=range:npt=now-\r\n");
388
389 sdp.append("m=");
390 sdp.append(isAudio ? "audio " : "video ");
391 sdp.append(port);
392 sdp.append(" RTP/AVP ");
393 sdp.append(payloadType);
394 sdp.append("\r\n");
395
396 sdp.append("c= IN IP");
397 if (isIPv4) {
398 sdp.append("4 ");
399 } else {
400 sdp.append("6 ");
401 }
402 sdp.append(ip);
403 sdp.append("\r\n");
404
405 sdp.append("b=AS:");
406 sdp.append(as > 0 ? as : kDefaultAs);
407 sdp.append("\r\n");
408
409 sdp.append("a=rtpmap:");
410 sdp.append(payloadType);
411 sdp.append(" ");
412 sdp.append(codec);
413 sdp.append("/");
414 sdp.append(isAudio ? "8000" : "90000");
415 sdp.append("\r\n");
416
417 if (fmtp != NULL) {
418 sdp.append("a=fmtp:");
419 sdp.append(payloadType);
420 sdp.append(" ");
421 sdp.append(fmtp);
422 sdp.append("\r\n");
423 }
424
425 if (!isAudio && width > 0 && height > 0) {
426 sdp.append("a=framesize:");
427 sdp.append(payloadType);
428 sdp.append(" ");
429 sdp.append(width);
430 sdp.append("-");
431 sdp.append(height);
432 sdp.append("\r\n");
433 }
434
435 if (cvoExtMap > 0) {
436 sdp.append("a=extmap:");
437 sdp.append(cvoExtMap);
438 sdp.append(" ");
439 sdp.append("urn:3gpp:video-orientation");
440 sdp.append("\r\n");
441 }
442
443 ALOGV("SDPStringFactory => %s", sdp.c_str());
444 }
445
446 } // namespace android
447
448