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