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 
ASessionDescription()30 ASessionDescription::ASessionDescription()
31     : mIsValid(false) {
32 }
33 
~ASessionDescription()34 ASessionDescription::~ASessionDescription() {
35 }
36 
setTo(const void * data,size_t size)37 bool ASessionDescription::setTo(const void *data, size_t size) {
38     mIsValid = parse(data, size);
39 
40     if (!mIsValid) {
41         mTracks.clear();
42         mFormats.clear();
43     }
44 
45     return mIsValid;
46 }
47 
parse(const void * data,size_t size)48 bool ASessionDescription::parse(const void *data, size_t size) {
49     mTracks.clear();
50     mFormats.clear();
51 
52     mTracks.push(Attribs());
53     mFormats.push(AString("[root]"));
54 
55     AString desc((const char *)data, size);
56 
57     size_t i = 0;
58     for (;;) {
59         ssize_t eolPos = desc.find("\n", i);
60 
61         if (eolPos < 0) {
62             break;
63         }
64 
65         AString line;
66         if ((size_t)eolPos > i && desc.c_str()[eolPos - 1] == '\r') {
67             // We accept both '\n' and '\r\n' line endings, if it's
68             // the latter, strip the '\r' as well.
69             line.setTo(desc, i, eolPos - i - 1);
70         } else {
71             line.setTo(desc, i, eolPos - i);
72         }
73 
74         if (line.empty()) {
75             i = eolPos + 1;
76             continue;
77         }
78 
79         if (line.size() < 2 || line.c_str()[1] != '=') {
80             return false;
81         }
82 
83         ALOGV("%s", line.c_str());
84 
85         switch (line.c_str()[0]) {
86             case 'v':
87             {
88                 if (strcmp(line.c_str(), "v=0")) {
89                     return false;
90                 }
91                 break;
92             }
93 
94             case 'a':
95             case 'b':
96             {
97                 AString key, value;
98 
99                 ssize_t colonPos = line.find(":", 2);
100                 if (colonPos < 0) {
101                     key = line;
102                 } else {
103                     key.setTo(line, 0, colonPos);
104 
105                     if (key == "a=fmtp" || key == "a=rtpmap"
106                             || key == "a=framesize") {
107                         ssize_t spacePos = line.find(" ", colonPos + 1);
108                         if (spacePos < 0) {
109                             return false;
110                         }
111 
112                         key.setTo(line, 0, spacePos);
113 
114                         colonPos = spacePos;
115                     }
116 
117                     value.setTo(line, colonPos + 1, line.size() - colonPos - 1);
118                 }
119 
120                 key.trim();
121                 value.trim();
122 
123                 ALOGV("adding '%s' => '%s'", key.c_str(), value.c_str());
124 
125                 mTracks.editItemAt(mTracks.size() - 1).add(key, value);
126                 break;
127             }
128 
129             case 'm':
130             {
131                 ALOGV("new section '%s'",
132                      AString(line, 2, line.size() - 2).c_str());
133 
134                 mTracks.push(Attribs());
135                 mFormats.push(AString(line, 2, line.size() - 2));
136                 break;
137             }
138 
139             default:
140             {
141                 AString key, value;
142 
143                 ssize_t equalPos = line.find("=");
144 
145                 key = AString(line, 0, equalPos + 1);
146                 value = AString(line, equalPos + 1, line.size() - equalPos - 1);
147 
148                 key.trim();
149                 value.trim();
150 
151                 ALOGV("adding '%s' => '%s'", key.c_str(), value.c_str());
152 
153                 mTracks.editItemAt(mTracks.size() - 1).add(key, value);
154                 break;
155             }
156         }
157 
158         i = eolPos + 1;
159     }
160 
161     return true;
162 }
163 
isValid() const164 bool ASessionDescription::isValid() const {
165     return mIsValid;
166 }
167 
countTracks() const168 size_t ASessionDescription::countTracks() const {
169     return mTracks.size();
170 }
171 
getFormat(size_t index,AString * value) const172 void ASessionDescription::getFormat(size_t index, AString *value) const {
173     CHECK_GE(index, 0u);
174     CHECK_LT(index, mTracks.size());
175 
176     *value = mFormats.itemAt(index);
177 }
178 
findAttribute(size_t index,const char * key,AString * value) const179 bool ASessionDescription::findAttribute(
180         size_t index, const char *key, AString *value) const {
181     CHECK_GE(index, 0u);
182     CHECK_LT(index, mTracks.size());
183 
184     value->clear();
185 
186     const Attribs &track = mTracks.itemAt(index);
187     ssize_t i = track.indexOfKey(AString(key));
188 
189     if (i < 0) {
190         return false;
191     }
192 
193     *value = track.valueAt(i);
194 
195     return true;
196 }
197 
getFormatType(size_t index,unsigned long * PT,AString * desc,AString * params) const198 void ASessionDescription::getFormatType(
199         size_t index, unsigned long *PT,
200         AString *desc, AString *params) const {
201     AString format;
202     getFormat(index, &format);
203 
204     const char *lastSpacePos = strrchr(format.c_str(), ' ');
205     CHECK(lastSpacePos != NULL);
206 
207     char *end;
208     unsigned long x = strtoul(lastSpacePos + 1, &end, 10);
209     CHECK_GT(end, lastSpacePos + 1);
210     CHECK_EQ(*end, '\0');
211 
212     *PT = x;
213 
214     char key[20];
215     snprintf(key, sizeof(key), "a=rtpmap:%lu", x);
216     if (findAttribute(index, key, desc)) {
217         snprintf(key, sizeof(key), "a=fmtp:%lu", x);
218         if (!findAttribute(index, key, params)) {
219             params->clear();
220         }
221     } else {
222         desc->clear();
223         params->clear();
224     }
225 }
226 
getDimensions(size_t index,unsigned long PT,int32_t * width,int32_t * height) const227 bool ASessionDescription::getDimensions(
228         size_t index, unsigned long PT,
229         int32_t *width, int32_t *height) const {
230     *width = 0;
231     *height = 0;
232 
233     char key[20];
234     snprintf(key, sizeof(key), "a=framesize:%lu", PT);
235     AString value;
236     if (!findAttribute(index, key, &value)) {
237         return false;
238     }
239 
240     const char *s = value.c_str();
241     char *end;
242     *width = strtoul(s, &end, 10);
243     CHECK_GT(end, s);
244     CHECK_EQ(*end, '-');
245 
246     s = end + 1;
247     *height = strtoul(s, &end, 10);
248     CHECK_GT(end, s);
249     CHECK_EQ(*end, '\0');
250 
251     return true;
252 }
253 
getDurationUs(int64_t * durationUs) const254 bool ASessionDescription::getDurationUs(int64_t *durationUs) const {
255     *durationUs = 0;
256 
257     CHECK(mIsValid);
258 
259     AString value;
260     if (!findAttribute(0, "a=range", &value)) {
261         return false;
262     }
263 
264     if (strncmp(value.c_str(), "npt=", 4) && strncmp(value.c_str(), "npt:", 4)) {
265         return false;
266     }
267 
268     float from, to;
269     if (!parseNTPRange(value.c_str() + 4, &from, &to)) {
270         return false;
271     }
272 
273     *durationUs = (int64_t)((to - from) * 1E6);
274 
275     return true;
276 }
277 
278 // static
ParseFormatDesc(const char * desc,int32_t * timescale,int32_t * numChannels)279 void ASessionDescription::ParseFormatDesc(
280         const char *desc, int32_t *timescale, int32_t *numChannels) {
281     const char *slash1 = strchr(desc, '/');
282     CHECK(slash1 != NULL);
283 
284     const char *s = slash1 + 1;
285     char *end;
286     unsigned long x = strtoul(s, &end, 10);
287     CHECK_GT(end, s);
288     CHECK(*end == '\0' || *end == '/');
289 
290     *timescale = x;
291     *numChannels = 1;
292 
293     if (*end == '/') {
294         s = end + 1;
295         unsigned long x = strtoul(s, &end, 10);
296         CHECK_GT(end, s);
297         CHECK_EQ(*end, '\0');
298 
299         *numChannels = x;
300     }
301 }
302 
303 // static
parseNTPRange(const char * s,float * npt1,float * npt2)304 bool ASessionDescription::parseNTPRange(
305         const char *s, float *npt1, float *npt2) {
306     if (s[0] == '-') {
307         return false;  // no start time available.
308     }
309 
310     if (!strncmp("now", s, 3)) {
311         return false;  // no absolute start time available
312     }
313 
314     char *end;
315     *npt1 = strtof(s, &end);
316 
317     if (end == s || *end != '-') {
318         // Failed to parse float or trailing "dash".
319         return false;
320     }
321 
322     s = end + 1;  // skip the dash.
323 
324     if (*s == '\0') {
325         *npt2 = FLT_MAX;  // open ended.
326         return true;
327     }
328 
329     if (!strncmp("now", s, 3)) {
330         return false;  // no absolute end time available
331     }
332 
333     *npt2 = strtof(s, &end);
334 
335     if (end == s || *end != '\0') {
336         return false;
337     }
338 
339     return *npt2 > *npt1;
340 }
341 
342 }  // namespace android
343 
344