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
28namespace android {
29
30ASessionDescription::ASessionDescription()
31    : mIsValid(false) {
32}
33
34ASessionDescription::~ASessionDescription() {
35}
36
37bool 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
48bool 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        ALOGI("%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
164bool ASessionDescription::isValid() const {
165    return mIsValid;
166}
167
168size_t ASessionDescription::countTracks() const {
169    return mTracks.size();
170}
171
172void 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
179bool 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
198void 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
227bool 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
254bool 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)) {
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
279void 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
304bool 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