• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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