APacketSource.cpp revision 386d609dc513e838c7e7c4c46c604493ccd560be
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 "APacketSource"
19#include <utils/Log.h>
20
21#include "APacketSource.h"
22
23#include "ARawAudioAssembler.h"
24#include "ASessionDescription.h"
25
26#include "avc_utils.h"
27
28#include <ctype.h>
29
30#include <media/stagefright/foundation/ABitReader.h>
31#include <media/stagefright/foundation/ABuffer.h>
32#include <media/stagefright/foundation/ADebug.h>
33#include <media/stagefright/foundation/AMessage.h>
34#include <media/stagefright/foundation/AString.h>
35#include <media/stagefright/foundation/base64.h>
36#include <media/stagefright/foundation/hexdump.h>
37#include <media/stagefright/MediaBuffer.h>
38#include <media/stagefright/MediaDefs.h>
39#include <media/stagefright/MetaData.h>
40#include <utils/Vector.h>
41
42namespace android {
43
44static bool GetAttribute(const char *s, const char *key, AString *value) {
45    value->clear();
46
47    size_t keyLen = strlen(key);
48
49    for (;;) {
50        while (isspace(*s)) {
51            ++s;
52        }
53
54        const char *colonPos = strchr(s, ';');
55
56        size_t len =
57            (colonPos == NULL) ? strlen(s) : colonPos - s;
58
59        if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
60            value->setTo(&s[keyLen + 1], len - keyLen - 1);
61            return true;
62        }
63
64        if (colonPos == NULL) {
65            return false;
66        }
67
68        s = colonPos + 1;
69    }
70}
71
72static sp<ABuffer> decodeHex(const AString &s) {
73    if ((s.size() % 2) != 0) {
74        return NULL;
75    }
76
77    size_t outLen = s.size() / 2;
78    sp<ABuffer> buffer = new ABuffer(outLen);
79    uint8_t *out = buffer->data();
80
81    uint8_t accum = 0;
82    for (size_t i = 0; i < s.size(); ++i) {
83        char c = s.c_str()[i];
84        unsigned value;
85        if (c >= '0' && c <= '9') {
86            value = c - '0';
87        } else if (c >= 'a' && c <= 'f') {
88            value = c - 'a' + 10;
89        } else if (c >= 'A' && c <= 'F') {
90            value = c - 'A' + 10;
91        } else {
92            return NULL;
93        }
94
95        accum = (accum << 4) | value;
96
97        if (i & 1) {
98            *out++ = accum;
99
100            accum = 0;
101        }
102    }
103
104    return buffer;
105}
106
107static sp<ABuffer> MakeAVCCodecSpecificData(
108        const char *params, int32_t *width, int32_t *height) {
109    *width = 0;
110    *height = 0;
111
112    AString val;
113    if (!GetAttribute(params, "profile-level-id", &val)) {
114        return NULL;
115    }
116
117    sp<ABuffer> profileLevelID = decodeHex(val);
118    CHECK(profileLevelID != NULL);
119    CHECK_EQ(profileLevelID->size(), 3u);
120
121    Vector<sp<ABuffer> > paramSets;
122
123    size_t numSeqParameterSets = 0;
124    size_t totalSeqParameterSetSize = 0;
125    size_t numPicParameterSets = 0;
126    size_t totalPicParameterSetSize = 0;
127
128    if (!GetAttribute(params, "sprop-parameter-sets", &val)) {
129        return NULL;
130    }
131
132    size_t start = 0;
133    for (;;) {
134        ssize_t commaPos = val.find(",", start);
135        size_t end = (commaPos < 0) ? val.size() : commaPos;
136
137        AString nalString(val, start, end - start);
138        sp<ABuffer> nal = decodeBase64(nalString);
139        CHECK(nal != NULL);
140        CHECK_GT(nal->size(), 0u);
141        CHECK_LE(nal->size(), 65535u);
142
143        uint8_t nalType = nal->data()[0] & 0x1f;
144        if (numSeqParameterSets == 0) {
145            CHECK_EQ((unsigned)nalType, 7u);
146        } else if (numPicParameterSets > 0) {
147            CHECK_EQ((unsigned)nalType, 8u);
148        }
149        if (nalType == 7) {
150            ++numSeqParameterSets;
151            totalSeqParameterSetSize += nal->size();
152        } else  {
153            CHECK_EQ((unsigned)nalType, 8u);
154            ++numPicParameterSets;
155            totalPicParameterSetSize += nal->size();
156        }
157
158        paramSets.push(nal);
159
160        if (commaPos < 0) {
161            break;
162        }
163
164        start = commaPos + 1;
165    }
166
167    CHECK_LT(numSeqParameterSets, 32u);
168    CHECK_LE(numPicParameterSets, 255u);
169
170    size_t csdSize =
171        1 + 3 + 1 + 1
172        + 2 * numSeqParameterSets + totalSeqParameterSetSize
173        + 1 + 2 * numPicParameterSets + totalPicParameterSetSize;
174
175    sp<ABuffer> csd = new ABuffer(csdSize);
176    uint8_t *out = csd->data();
177
178    *out++ = 0x01;  // configurationVersion
179    memcpy(out, profileLevelID->data(), 3);
180    out += 3;
181    *out++ = (0x3f << 2) | 1;  // lengthSize == 2 bytes
182    *out++ = 0xe0 | numSeqParameterSets;
183
184    for (size_t i = 0; i < numSeqParameterSets; ++i) {
185        sp<ABuffer> nal = paramSets.editItemAt(i);
186
187        *out++ = nal->size() >> 8;
188        *out++ = nal->size() & 0xff;
189
190        memcpy(out, nal->data(), nal->size());
191
192        out += nal->size();
193
194        if (i == 0) {
195            FindAVCDimensions(nal, width, height);
196            LOGI("dimensions %dx%d", *width, *height);
197        }
198    }
199
200    *out++ = numPicParameterSets;
201
202    for (size_t i = 0; i < numPicParameterSets; ++i) {
203        sp<ABuffer> nal = paramSets.editItemAt(i + numSeqParameterSets);
204
205        *out++ = nal->size() >> 8;
206        *out++ = nal->size() & 0xff;
207
208        memcpy(out, nal->data(), nal->size());
209
210        out += nal->size();
211    }
212
213    // hexdump(csd->data(), csd->size());
214
215    return csd;
216}
217
218sp<ABuffer> MakeAACCodecSpecificData(const char *params) {
219    AString val;
220    CHECK(GetAttribute(params, "config", &val));
221
222    sp<ABuffer> config = decodeHex(val);
223    CHECK(config != NULL);
224    CHECK_GE(config->size(), 4u);
225
226    const uint8_t *data = config->data();
227    uint32_t x = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
228    x = (x >> 1) & 0xffff;
229
230    static const uint8_t kStaticESDS[] = {
231        0x03, 22,
232        0x00, 0x00,     // ES_ID
233        0x00,           // streamDependenceFlag, URL_Flag, OCRstreamFlag
234
235        0x04, 17,
236        0x40,                       // Audio ISO/IEC 14496-3
237        0x00, 0x00, 0x00, 0x00,
238        0x00, 0x00, 0x00, 0x00,
239        0x00, 0x00, 0x00, 0x00,
240
241        0x05, 2,
242        // AudioSpecificInfo follows
243    };
244
245    sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + 2);
246    memcpy(csd->data(), kStaticESDS, sizeof(kStaticESDS));
247    csd->data()[sizeof(kStaticESDS)] = (x >> 8) & 0xff;
248    csd->data()[sizeof(kStaticESDS) + 1] = x & 0xff;
249
250    // hexdump(csd->data(), csd->size());
251
252    return csd;
253}
254
255// From mpeg4-generic configuration data.
256sp<ABuffer> MakeAACCodecSpecificData2(const char *params) {
257    AString val;
258    unsigned long objectType;
259    if (GetAttribute(params, "objectType", &val)) {
260        const char *s = val.c_str();
261        char *end;
262        objectType = strtoul(s, &end, 10);
263        CHECK(end > s && *end == '\0');
264    } else {
265        objectType = 0x40;  // Audio ISO/IEC 14496-3
266    }
267
268    CHECK(GetAttribute(params, "config", &val));
269
270    sp<ABuffer> config = decodeHex(val);
271    CHECK(config != NULL);
272
273    // Make sure size fits into a single byte and doesn't have to
274    // be encoded.
275    CHECK_LT(20 + config->size(), 128u);
276
277    const uint8_t *data = config->data();
278
279    static const uint8_t kStaticESDS[] = {
280        0x03, 22,
281        0x00, 0x00,     // ES_ID
282        0x00,           // streamDependenceFlag, URL_Flag, OCRstreamFlag
283
284        0x04, 17,
285        0x40,                       // Audio ISO/IEC 14496-3
286        0x00, 0x00, 0x00, 0x00,
287        0x00, 0x00, 0x00, 0x00,
288        0x00, 0x00, 0x00, 0x00,
289
290        0x05, 2,
291        // AudioSpecificInfo follows
292    };
293
294    sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + config->size());
295    uint8_t *dst = csd->data();
296    *dst++ = 0x03;
297    *dst++ = 20 + config->size();
298    *dst++ = 0x00;  // ES_ID
299    *dst++ = 0x00;
300    *dst++ = 0x00;  // streamDependenceFlag, URL_Flag, OCRstreamFlag
301    *dst++ = 0x04;
302    *dst++ = 15 + config->size();
303    *dst++ = objectType;
304    for (int i = 0; i < 12; ++i) { *dst++ = 0x00; }
305    *dst++ = 0x05;
306    *dst++ = config->size();
307    memcpy(dst, config->data(), config->size());
308
309    // hexdump(csd->data(), csd->size());
310
311    return csd;
312}
313
314static size_t GetSizeWidth(size_t x) {
315    size_t n = 1;
316    while (x > 127) {
317        ++n;
318        x >>= 7;
319    }
320    return n;
321}
322
323static uint8_t *EncodeSize(uint8_t *dst, size_t x) {
324    while (x > 127) {
325        *dst++ = (x & 0x7f) | 0x80;
326        x >>= 7;
327    }
328    *dst++ = x;
329    return dst;
330}
331
332static bool ExtractDimensionsMPEG4Config(
333        const sp<ABuffer> &config, int32_t *width, int32_t *height) {
334    *width = 0;
335    *height = 0;
336
337    const uint8_t *ptr = config->data();
338    size_t offset = 0;
339    bool foundVOL = false;
340    while (offset + 3 < config->size()) {
341        if (memcmp("\x00\x00\x01", &ptr[offset], 3)
342                || (ptr[offset + 3] & 0xf0) != 0x20) {
343            ++offset;
344            continue;
345        }
346
347        foundVOL = true;
348        break;
349    }
350
351    if (!foundVOL) {
352        return false;
353    }
354
355    return ExtractDimensionsFromVOLHeader(
356            &ptr[offset], config->size() - offset, width, height);
357}
358
359static sp<ABuffer> MakeMPEG4VideoCodecSpecificData(
360        const char *params, int32_t *width, int32_t *height) {
361    *width = 0;
362    *height = 0;
363
364    AString val;
365    CHECK(GetAttribute(params, "config", &val));
366
367    sp<ABuffer> config = decodeHex(val);
368    CHECK(config != NULL);
369
370    if (!ExtractDimensionsMPEG4Config(config, width, height)) {
371        return NULL;
372    }
373
374    LOGI("VOL dimensions = %dx%d", *width, *height);
375
376    size_t len1 = config->size() + GetSizeWidth(config->size()) + 1;
377    size_t len2 = len1 + GetSizeWidth(len1) + 1 + 13;
378    size_t len3 = len2 + GetSizeWidth(len2) + 1 + 3;
379
380    sp<ABuffer> csd = new ABuffer(len3);
381    uint8_t *dst = csd->data();
382    *dst++ = 0x03;
383    dst = EncodeSize(dst, len2 + 3);
384    *dst++ = 0x00;  // ES_ID
385    *dst++ = 0x00;
386    *dst++ = 0x00;  // streamDependenceFlag, URL_Flag, OCRstreamFlag
387
388    *dst++ = 0x04;
389    dst = EncodeSize(dst, len1 + 13);
390    *dst++ = 0x01;  // Video ISO/IEC 14496-2 Simple Profile
391    for (size_t i = 0; i < 12; ++i) {
392        *dst++ = 0x00;
393    }
394
395    *dst++ = 0x05;
396    dst = EncodeSize(dst, config->size());
397    memcpy(dst, config->data(), config->size());
398    dst += config->size();
399
400    // hexdump(csd->data(), csd->size());
401
402    return csd;
403}
404
405static bool GetClockRate(const AString &desc, uint32_t *clockRate) {
406    ssize_t slashPos = desc.find("/");
407    if (slashPos < 0) {
408        return false;
409    }
410
411    const char *s = desc.c_str() + slashPos + 1;
412
413    char *end;
414    unsigned long x = strtoul(s, &end, 10);
415
416    if (end == s || (*end != '\0' && *end != '/')) {
417        return false;
418    }
419
420    *clockRate = x;
421
422    return true;
423}
424
425APacketSource::APacketSource(
426        const sp<ASessionDescription> &sessionDesc, size_t index)
427    : mInitCheck(NO_INIT),
428      mFormat(new MetaData),
429      mEOSResult(OK),
430      mIsAVC(false),
431      mScanForIDR(true),
432      mRTPTimeBase(0),
433      mNormalPlayTimeBaseUs(0),
434      mLastNormalPlayTimeUs(0) {
435    unsigned long PT;
436    AString desc;
437    AString params;
438    sessionDesc->getFormatType(index, &PT, &desc, &params);
439
440    CHECK(GetClockRate(desc, &mClockRate));
441
442    int64_t durationUs;
443    if (sessionDesc->getDurationUs(&durationUs)) {
444        mFormat->setInt64(kKeyDuration, durationUs);
445    } else {
446        mFormat->setInt64(kKeyDuration, 60 * 60 * 1000000ll);
447    }
448
449    mInitCheck = OK;
450    if (!strncmp(desc.c_str(), "H264/", 5)) {
451        mIsAVC = true;
452
453        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
454
455        int32_t width, height;
456        if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
457            width = -1;
458            height = -1;
459        }
460
461        int32_t encWidth, encHeight;
462        sp<ABuffer> codecSpecificData =
463            MakeAVCCodecSpecificData(params.c_str(), &encWidth, &encHeight);
464
465        if (codecSpecificData != NULL) {
466            if (width < 0) {
467                // If no explicit width/height given in the sdp, use the dimensions
468                // extracted from the first sequence parameter set.
469                width = encWidth;
470                height = encHeight;
471            }
472
473            mFormat->setData(
474                    kKeyAVCC, 0,
475                    codecSpecificData->data(), codecSpecificData->size());
476        } else if (width < 0) {
477            mInitCheck = ERROR_UNSUPPORTED;
478            return;
479        }
480
481        mFormat->setInt32(kKeyWidth, width);
482        mFormat->setInt32(kKeyHeight, height);
483    } else if (!strncmp(desc.c_str(), "H263-2000/", 10)
484            || !strncmp(desc.c_str(), "H263-1998/", 10)) {
485        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
486
487        int32_t width, height;
488        if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
489            mInitCheck = ERROR_UNSUPPORTED;
490            return;
491        }
492
493        mFormat->setInt32(kKeyWidth, width);
494        mFormat->setInt32(kKeyHeight, height);
495    } else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) {
496        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
497
498        int32_t sampleRate, numChannels;
499        ASessionDescription::ParseFormatDesc(
500                desc.c_str(), &sampleRate, &numChannels);
501
502        mFormat->setInt32(kKeySampleRate, sampleRate);
503        mFormat->setInt32(kKeyChannelCount, numChannels);
504
505        sp<ABuffer> codecSpecificData =
506            MakeAACCodecSpecificData(params.c_str());
507
508        mFormat->setData(
509                kKeyESDS, 0,
510                codecSpecificData->data(), codecSpecificData->size());
511    } else if (!strncmp(desc.c_str(), "AMR/", 4)) {
512        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AMR_NB);
513
514        int32_t sampleRate, numChannels;
515        ASessionDescription::ParseFormatDesc(
516                desc.c_str(), &sampleRate, &numChannels);
517
518        mFormat->setInt32(kKeySampleRate, sampleRate);
519        mFormat->setInt32(kKeyChannelCount, numChannels);
520
521        if (sampleRate != 8000 || numChannels != 1) {
522            mInitCheck = ERROR_UNSUPPORTED;
523        }
524    } else if (!strncmp(desc.c_str(), "AMR-WB/", 7)) {
525        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AMR_WB);
526
527        int32_t sampleRate, numChannels;
528        ASessionDescription::ParseFormatDesc(
529                desc.c_str(), &sampleRate, &numChannels);
530
531        mFormat->setInt32(kKeySampleRate, sampleRate);
532        mFormat->setInt32(kKeyChannelCount, numChannels);
533
534        if (sampleRate != 16000 || numChannels != 1) {
535            mInitCheck = ERROR_UNSUPPORTED;
536        }
537    } else if (!strncmp(desc.c_str(), "MP4V-ES/", 8)) {
538        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
539
540        int32_t width, height;
541        if (!sessionDesc->getDimensions(index, PT, &width, &height)) {
542            width = -1;
543            height = -1;
544        }
545
546        int32_t encWidth, encHeight;
547        sp<ABuffer> codecSpecificData =
548            MakeMPEG4VideoCodecSpecificData(
549                    params.c_str(), &encWidth, &encHeight);
550
551        if (codecSpecificData != NULL) {
552            mFormat->setData(
553                    kKeyESDS, 0,
554                    codecSpecificData->data(), codecSpecificData->size());
555
556            if (width < 0) {
557                width = encWidth;
558                height = encHeight;
559            }
560        } else if (width < 0) {
561            mInitCheck = ERROR_UNSUPPORTED;
562            return;
563        }
564
565        mFormat->setInt32(kKeyWidth, width);
566        mFormat->setInt32(kKeyHeight, height);
567    } else if (!strncasecmp(desc.c_str(), "mpeg4-generic/", 14)) {
568        AString val;
569        if (!GetAttribute(params.c_str(), "mode", &val)
570                || (strcasecmp(val.c_str(), "AAC-lbr")
571                    && strcasecmp(val.c_str(), "AAC-hbr"))) {
572            mInitCheck = ERROR_UNSUPPORTED;
573            return;
574        }
575
576        mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
577
578        int32_t sampleRate, numChannels;
579        ASessionDescription::ParseFormatDesc(
580                desc.c_str(), &sampleRate, &numChannels);
581
582        mFormat->setInt32(kKeySampleRate, sampleRate);
583        mFormat->setInt32(kKeyChannelCount, numChannels);
584
585        sp<ABuffer> codecSpecificData =
586            MakeAACCodecSpecificData2(params.c_str());
587
588        mFormat->setData(
589                kKeyESDS, 0,
590                codecSpecificData->data(), codecSpecificData->size());
591    } else if (ARawAudioAssembler::Supports(desc.c_str())) {
592        ARawAudioAssembler::MakeFormat(desc.c_str(), mFormat);
593    } else {
594        mInitCheck = ERROR_UNSUPPORTED;
595    }
596}
597
598APacketSource::~APacketSource() {
599}
600
601status_t APacketSource::initCheck() const {
602    return mInitCheck;
603}
604
605status_t APacketSource::start(MetaData *params) {
606    return OK;
607}
608
609status_t APacketSource::stop() {
610    return OK;
611}
612
613sp<MetaData> APacketSource::getFormat() {
614    return mFormat;
615}
616
617status_t APacketSource::read(
618        MediaBuffer **out, const ReadOptions *) {
619    *out = NULL;
620
621    Mutex::Autolock autoLock(mLock);
622    while (mEOSResult == OK && mBuffers.empty()) {
623        mCondition.wait(mLock);
624    }
625
626    if (!mBuffers.empty()) {
627        const sp<ABuffer> buffer = *mBuffers.begin();
628
629        updateNormalPlayTime_l(buffer);
630
631        MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
632
633        int64_t timeUs;
634        CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
635
636        mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
637
638        memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
639        *out = mediaBuffer;
640
641        mBuffers.erase(mBuffers.begin());
642        return OK;
643    }
644
645    return mEOSResult;
646}
647
648void APacketSource::updateNormalPlayTime_l(const sp<ABuffer> &buffer) {
649    uint32_t rtpTime;
650    CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
651
652    mLastNormalPlayTimeUs =
653        (((double)rtpTime - (double)mRTPTimeBase) / mClockRate)
654            * 1000000ll
655            + mNormalPlayTimeBaseUs;
656}
657
658void APacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
659    int32_t damaged;
660    if (buffer->meta()->findInt32("damaged", &damaged) && damaged) {
661        LOGV("discarding damaged AU");
662        return;
663    }
664
665    if (mScanForIDR && mIsAVC) {
666        // This pretty piece of code ensures that the first access unit
667        // fed to the decoder after stream-start or seek is guaranteed to
668        // be an IDR frame. This is to workaround limitations of a certain
669        // hardware h.264 decoder that requires this to be the case.
670
671        if (!IsIDR(buffer)) {
672            LOGV("skipping AU while scanning for next IDR frame.");
673            return;
674        }
675
676        mScanForIDR = false;
677    }
678
679    Mutex::Autolock autoLock(mLock);
680    mBuffers.push_back(buffer);
681    mCondition.signal();
682}
683
684void APacketSource::signalEOS(status_t result) {
685    CHECK(result != OK);
686
687    Mutex::Autolock autoLock(mLock);
688    mEOSResult = result;
689    mCondition.signal();
690}
691
692void APacketSource::flushQueue() {
693    Mutex::Autolock autoLock(mLock);
694    mBuffers.clear();
695
696    mScanForIDR = true;
697}
698
699int64_t APacketSource::getNormalPlayTimeUs() {
700    Mutex::Autolock autoLock(mLock);
701    return mLastNormalPlayTimeUs;
702}
703
704void APacketSource::setNormalPlayTimeMapping(
705        uint32_t rtpTime, int64_t normalPlayTimeUs) {
706    Mutex::Autolock autoLock(mLock);
707
708    mRTPTimeBase = rtpTime;
709    mNormalPlayTimeBaseUs = normalPlayTimeUs;
710}
711
712int64_t APacketSource::getQueueDurationUs(bool *eos) {
713    Mutex::Autolock autoLock(mLock);
714
715    *eos = (mEOSResult != OK);
716
717    if (mBuffers.size() < 2) {
718        return 0;
719    }
720
721    const sp<ABuffer> first = *mBuffers.begin();
722    const sp<ABuffer> last = *--mBuffers.end();
723
724    int64_t firstTimeUs;
725    CHECK(first->meta()->findInt64("timeUs", &firstTimeUs));
726
727    int64_t lastTimeUs;
728    CHECK(last->meta()->findInt64("timeUs", &lastTimeUs));
729
730    if (lastTimeUs < firstTimeUs) {
731        LOGE("Huh? Time moving backwards? %lld > %lld",
732             firstTimeUs, lastTimeUs);
733
734        return 0;
735    }
736
737    return lastTimeUs - firstTimeUs;
738}
739
740}  // namespace android
741