MPEG4Writer.cpp revision c5f0c714dc4225cd2ec305d5ddd297964a3dd3dc
1/* 2 * Copyright (C) 2009 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 "MPEG4Writer" 19#include <utils/Log.h> 20 21#include <arpa/inet.h> 22 23#include <ctype.h> 24#include <pthread.h> 25 26#include <media/stagefright/MPEG4Writer.h> 27#include <media/stagefright/MediaBuffer.h> 28#include <media/stagefright/MetaData.h> 29#include <media/stagefright/MediaDebug.h> 30#include <media/stagefright/MediaDefs.h> 31#include <media/stagefright/MediaErrors.h> 32#include <media/stagefright/MediaSource.h> 33#include <media/stagefright/Utils.h> 34#include <media/mediarecorder.h> 35#include <cutils/properties.h> 36 37#include "include/ESDS.h" 38 39namespace android { 40 41class MPEG4Writer::Track { 42public: 43 Track(MPEG4Writer *owner, const sp<MediaSource> &source); 44 45 ~Track(); 46 47 status_t start(MetaData *params); 48 void stop(); 49 void pause(); 50 bool reachedEOS(); 51 52 int64_t getDurationUs() const; 53 int64_t getEstimatedTrackSizeBytes() const; 54 void writeTrackHeader(int32_t trackID, bool use32BitOffset = true); 55 void bufferChunk(int64_t timestampUs); 56 bool isAvc() const { return mIsAvc; } 57 bool isAudio() const { return mIsAudio; } 58 bool isMPEG4() const { return mIsMPEG4; } 59 void addChunkOffset(off_t offset) { mChunkOffsets.push_back(offset); } 60 61private: 62 MPEG4Writer *mOwner; 63 sp<MetaData> mMeta; 64 sp<MediaSource> mSource; 65 volatile bool mDone; 66 volatile bool mPaused; 67 volatile bool mResumed; 68 bool mIsAvc; 69 bool mIsAudio; 70 bool mIsMPEG4; 71 int64_t mTrackDurationUs; 72 int64_t mEstimatedTrackSizeBytes; 73 int64_t mMaxWriteTimeUs; 74 int32_t mTimeScale; 75 76 pthread_t mThread; 77 78 // mNumSamples is used to track how many samples in mSampleSizes List. 79 // This is to reduce the cost associated with mSampleSizes.size() call, 80 // since it is O(n). Ideally, the fix should be in List class. 81 size_t mNumSamples; 82 List<size_t> mSampleSizes; 83 bool mSamplesHaveSameSize; 84 85 List<MediaBuffer *> mChunkSamples; 86 List<off_t> mChunkOffsets; 87 88 struct StscTableEntry { 89 90 StscTableEntry(uint32_t chunk, uint32_t samples, uint32_t id) 91 : firstChunk(chunk), 92 samplesPerChunk(samples), 93 sampleDescriptionId(id) {} 94 95 uint32_t firstChunk; 96 uint32_t samplesPerChunk; 97 uint32_t sampleDescriptionId; 98 }; 99 List<StscTableEntry> mStscTableEntries; 100 101 List<int32_t> mStssTableEntries; 102 List<int64_t> mChunkDurations; 103 104 struct SttsTableEntry { 105 106 SttsTableEntry(uint32_t count, uint32_t durationUs) 107 : sampleCount(count), sampleDurationUs(durationUs) {} 108 109 uint32_t sampleCount; 110 uint32_t sampleDurationUs; 111 }; 112 List<SttsTableEntry> mSttsTableEntries; 113 114 void *mCodecSpecificData; 115 size_t mCodecSpecificDataSize; 116 bool mGotAllCodecSpecificData; 117 bool mTrackingProgressStatus; 118 119 bool mReachedEOS; 120 int64_t mStartTimestampUs; 121 int64_t mPreviousTrackTimeUs; 122 int64_t mTrackEveryTimeDurationUs; 123 124 static void *ThreadWrapper(void *me); 125 void threadEntry(); 126 127 status_t makeAVCCodecSpecificData( 128 const uint8_t *data, size_t size); 129 130 // Track authoring progress status 131 void trackProgressStatus(int64_t timeUs, status_t err = OK); 132 void initTrackingProgressStatus(MetaData *params); 133 134 // Utilities for collecting statistical data 135 void logStatisticalData(bool isAudio); 136 void findMinAvgMaxSampleDurationMs( 137 int32_t *min, int32_t *avg, int32_t *max); 138 void findMinMaxChunkDurations(int64_t *min, int64_t *max); 139 140 void getCodecSpecificDataFromInputFormatIfPossible(); 141 142 Track(const Track &); 143 Track &operator=(const Track &); 144}; 145 146#define USE_NALLEN_FOUR 1 147 148MPEG4Writer::MPEG4Writer(const char *filename) 149 : mFile(fopen(filename, "wb")), 150 mUse32BitOffset(true), 151 mPaused(false), 152 mStarted(false), 153 mOffset(0), 154 mMdatOffset(0), 155 mEstimatedMoovBoxSize(0), 156 mInterleaveDurationUs(1000000) { 157 CHECK(mFile != NULL); 158} 159 160MPEG4Writer::MPEG4Writer(int fd) 161 : mFile(fdopen(fd, "wb")), 162 mUse32BitOffset(true), 163 mPaused(false), 164 mStarted(false), 165 mOffset(0), 166 mMdatOffset(0), 167 mEstimatedMoovBoxSize(0), 168 mInterleaveDurationUs(1000000) { 169 CHECK(mFile != NULL); 170} 171 172MPEG4Writer::~MPEG4Writer() { 173 stop(); 174 175 for (List<Track *>::iterator it = mTracks.begin(); 176 it != mTracks.end(); ++it) { 177 delete *it; 178 } 179 mTracks.clear(); 180} 181 182status_t MPEG4Writer::addSource(const sp<MediaSource> &source) { 183 Track *track = new Track(this, source); 184 mTracks.push_back(track); 185 186 return OK; 187} 188 189status_t MPEG4Writer::startTracks(MetaData *params) { 190 for (List<Track *>::iterator it = mTracks.begin(); 191 it != mTracks.end(); ++it) { 192 status_t err = (*it)->start(params); 193 194 if (err != OK) { 195 for (List<Track *>::iterator it2 = mTracks.begin(); 196 it2 != it; ++it2) { 197 (*it2)->stop(); 198 } 199 200 return err; 201 } 202 } 203 return OK; 204} 205 206int64_t MPEG4Writer::estimateMoovBoxSize(int32_t bitRate) { 207 // This implementation is highly experimental/heurisitic. 208 // 209 // Statistical analysis shows that metadata usually accounts 210 // for a small portion of the total file size, usually < 0.6%. 211 // Currently, lets set to 0.4% for now. 212 213 // The default MIN_MOOV_BOX_SIZE is set to 0.4% x 1MB, 214 // where 1MB is the common file size limit for MMS application. 215 // The default MAX _MOOV_BOX_SIZE value is based on about 4 216 // minute video recording with a bit rate about 3 Mbps, because 217 // statistics also show that most of the video captured are going 218 // to be less than 3 minutes. 219 220 // If the estimation is wrong, we will pay the price of wasting 221 // some reserved space. This should not happen so often statistically. 222 static const int32_t factor = mUse32BitOffset? 1: 2; 223 static const int64_t MIN_MOOV_BOX_SIZE = 4 * 1024; // 4 KB 224 static const int64_t MAX_MOOV_BOX_SIZE = (180 * 3000000 * 6LL / 8000); 225 int64_t size = MIN_MOOV_BOX_SIZE; 226 227 if (mMaxFileSizeLimitBytes != 0) { 228 size = mMaxFileSizeLimitBytes * 4 / 1000; 229 } else if (mMaxFileDurationLimitUs != 0) { 230 if (bitRate <= 0) { 231 // We could not estimate the file size since bitRate is not set. 232 size = MIN_MOOV_BOX_SIZE; 233 } else { 234 size = ((mMaxFileDurationLimitUs * bitRate * 4) / 1000 / 8000000); 235 } 236 } 237 if (size < MIN_MOOV_BOX_SIZE) { 238 size = MIN_MOOV_BOX_SIZE; 239 } 240 241 // Any long duration recording will be probably end up with 242 // non-streamable mp4 file. 243 if (size > MAX_MOOV_BOX_SIZE) { 244 size = MAX_MOOV_BOX_SIZE; 245 } 246 247 LOGI("limits: %lld/%lld bytes/us, bit rate: %d bps and the estimated" 248 " moov size %lld bytes", 249 mMaxFileSizeLimitBytes, mMaxFileDurationLimitUs, bitRate, size); 250 return factor * size; 251} 252 253status_t MPEG4Writer::start(MetaData *param) { 254 if (mFile == NULL) { 255 return UNKNOWN_ERROR; 256 } 257 258 int32_t use64BitOffset; 259 if (param && 260 param->findInt32(kKey64BitFileOffset, &use64BitOffset) && 261 use64BitOffset) { 262 mUse32BitOffset = false; 263 } 264 265 // System property can overwrite the file offset bits parameter 266 char value[PROPERTY_VALUE_MAX]; 267 if (property_get("media.stagefright.record-64bits", value, NULL) 268 && (!strcmp(value, "1") || !strcasecmp(value, "true"))) { 269 mUse32BitOffset = false; 270 } 271 272 mStartTimestampUs = -1; 273 274 if (mStarted) { 275 if (mPaused) { 276 mPaused = false; 277 return startTracks(param); 278 } 279 return OK; 280 } 281 282 if (!param || 283 !param->findInt32(kKeyTimeScale, &mTimeScale)) { 284 mTimeScale = 1000; 285 } 286 CHECK(mTimeScale > 0); 287 LOGV("movie time scale: %d", mTimeScale); 288 289 mStreamableFile = true; 290 mWriteMoovBoxToMemory = false; 291 mMoovBoxBuffer = NULL; 292 mMoovBoxBufferOffset = 0; 293 294 beginBox("ftyp"); 295 { 296 int32_t fileType; 297 if (param && param->findInt32(kKeyFileType, &fileType) && 298 fileType != OUTPUT_FORMAT_MPEG_4) { 299 writeFourcc("3gp4"); 300 } else { 301 writeFourcc("isom"); 302 } 303 } 304 writeInt32(0); 305 writeFourcc("isom"); 306 writeFourcc("3gp4"); 307 endBox(); 308 309 mFreeBoxOffset = mOffset; 310 311 if (mEstimatedMoovBoxSize == 0) { 312 int32_t bitRate = -1; 313 if (param) { 314 param->findInt32(kKeyBitRate, &bitRate); 315 } 316 mEstimatedMoovBoxSize = estimateMoovBoxSize(bitRate); 317 } 318 CHECK(mEstimatedMoovBoxSize >= 8); 319 fseeko(mFile, mFreeBoxOffset, SEEK_SET); 320 writeInt32(mEstimatedMoovBoxSize); 321 write("free", 4); 322 323 mMdatOffset = mFreeBoxOffset + mEstimatedMoovBoxSize; 324 mOffset = mMdatOffset; 325 fseeko(mFile, mMdatOffset, SEEK_SET); 326 if (mUse32BitOffset) { 327 write("????mdat", 8); 328 } else { 329 write("\x00\x00\x00\x01mdat????????", 16); 330 } 331 332 status_t err = startWriterThread(); 333 if (err != OK) { 334 return err; 335 } 336 337 err = startTracks(param); 338 if (err != OK) { 339 return err; 340 } 341 342 mStarted = true; 343 return OK; 344} 345 346void MPEG4Writer::pause() { 347 if (mFile == NULL) { 348 return; 349 } 350 mPaused = true; 351 for (List<Track *>::iterator it = mTracks.begin(); 352 it != mTracks.end(); ++it) { 353 (*it)->pause(); 354 } 355} 356 357void MPEG4Writer::stopWriterThread() { 358 LOGV("stopWriterThread"); 359 360 { 361 Mutex::Autolock autolock(mLock); 362 363 mDone = true; 364 mChunkReadyCondition.signal(); 365 } 366 367 void *dummy; 368 pthread_join(mThread, &dummy); 369} 370 371void MPEG4Writer::stop() { 372 if (mFile == NULL) { 373 return; 374 } 375 376 int64_t maxDurationUs = 0; 377 for (List<Track *>::iterator it = mTracks.begin(); 378 it != mTracks.end(); ++it) { 379 (*it)->stop(); 380 381 int64_t durationUs = (*it)->getDurationUs(); 382 if (durationUs > maxDurationUs) { 383 maxDurationUs = durationUs; 384 } 385 } 386 387 stopWriterThread(); 388 389 // Fix up the size of the 'mdat' chunk. 390 if (mUse32BitOffset) { 391 fseeko(mFile, mMdatOffset, SEEK_SET); 392 int32_t size = htonl(static_cast<int32_t>(mOffset - mMdatOffset)); 393 fwrite(&size, 1, 4, mFile); 394 } else { 395 fseeko(mFile, mMdatOffset + 8, SEEK_SET); 396 int64_t size = mOffset - mMdatOffset; 397 size = hton64(size); 398 fwrite(&size, 1, 8, mFile); 399 } 400 fseeko(mFile, mOffset, SEEK_SET); 401 402 time_t now = time(NULL); 403 const off_t moovOffset = mOffset; 404 mWriteMoovBoxToMemory = true; 405 mMoovBoxBuffer = (uint8_t *) malloc(mEstimatedMoovBoxSize); 406 mMoovBoxBufferOffset = 0; 407 CHECK(mMoovBoxBuffer != NULL); 408 int32_t duration = (maxDurationUs * mTimeScale) / 1E6; 409 410 beginBox("moov"); 411 412 beginBox("mvhd"); 413 writeInt32(0); // version=0, flags=0 414 writeInt32(now); // creation time 415 writeInt32(now); // modification time 416 writeInt32(mTimeScale); // mvhd timescale 417 writeInt32(duration); 418 writeInt32(0x10000); // rate: 1.0 419 writeInt16(0x100); // volume 420 writeInt16(0); // reserved 421 writeInt32(0); // reserved 422 writeInt32(0); // reserved 423 writeInt32(0x10000); // matrix 424 writeInt32(0); 425 writeInt32(0); 426 writeInt32(0); 427 writeInt32(0x10000); 428 writeInt32(0); 429 writeInt32(0); 430 writeInt32(0); 431 writeInt32(0x40000000); 432 writeInt32(0); // predefined 433 writeInt32(0); // predefined 434 writeInt32(0); // predefined 435 writeInt32(0); // predefined 436 writeInt32(0); // predefined 437 writeInt32(0); // predefined 438 writeInt32(mTracks.size() + 1); // nextTrackID 439 endBox(); // mvhd 440 441 int32_t id = 1; 442 for (List<Track *>::iterator it = mTracks.begin(); 443 it != mTracks.end(); ++it, ++id) { 444 (*it)->writeTrackHeader(id, mUse32BitOffset); 445 } 446 endBox(); // moov 447 448 mWriteMoovBoxToMemory = false; 449 if (mStreamableFile) { 450 CHECK(mMoovBoxBufferOffset + 8 <= mEstimatedMoovBoxSize); 451 452 // Moov box 453 fseeko(mFile, mFreeBoxOffset, SEEK_SET); 454 mOffset = mFreeBoxOffset; 455 write(mMoovBoxBuffer, 1, mMoovBoxBufferOffset, mFile); 456 457 // Free box 458 fseeko(mFile, mOffset, SEEK_SET); 459 writeInt32(mEstimatedMoovBoxSize - mMoovBoxBufferOffset); 460 write("free", 4); 461 462 // Free temp memory 463 free(mMoovBoxBuffer); 464 mMoovBoxBuffer = NULL; 465 mMoovBoxBufferOffset = 0; 466 } else { 467 LOGI("The mp4 file will not be streamable."); 468 } 469 470 CHECK(mBoxes.empty()); 471 472 fflush(mFile); 473 fclose(mFile); 474 mFile = NULL; 475 mStarted = false; 476} 477 478status_t MPEG4Writer::setInterleaveDuration(uint32_t durationUs) { 479 mInterleaveDurationUs = durationUs; 480 return OK; 481} 482 483void MPEG4Writer::lock() { 484 mLock.lock(); 485} 486 487void MPEG4Writer::unlock() { 488 mLock.unlock(); 489} 490 491off_t MPEG4Writer::addSample_l(MediaBuffer *buffer) { 492 off_t old_offset = mOffset; 493 494 fwrite((const uint8_t *)buffer->data() + buffer->range_offset(), 495 1, buffer->range_length(), mFile); 496 497 mOffset += buffer->range_length(); 498 499 return old_offset; 500} 501 502static void StripStartcode(MediaBuffer *buffer) { 503 if (buffer->range_length() < 4) { 504 return; 505 } 506 507 const uint8_t *ptr = 508 (const uint8_t *)buffer->data() + buffer->range_offset(); 509 510 if (!memcmp(ptr, "\x00\x00\x00\x01", 4)) { 511 buffer->set_range( 512 buffer->range_offset() + 4, buffer->range_length() - 4); 513 } 514} 515 516off_t MPEG4Writer::addLengthPrefixedSample_l(MediaBuffer *buffer) { 517 off_t old_offset = mOffset; 518 519 size_t length = buffer->range_length(); 520 521#if USE_NALLEN_FOUR 522 uint8_t x = length >> 24; 523 fwrite(&x, 1, 1, mFile); 524 x = (length >> 16) & 0xff; 525 fwrite(&x, 1, 1, mFile); 526 x = (length >> 8) & 0xff; 527 fwrite(&x, 1, 1, mFile); 528 x = length & 0xff; 529 fwrite(&x, 1, 1, mFile); 530#else 531 CHECK(length < 65536); 532 533 uint8_t x = length >> 8; 534 fwrite(&x, 1, 1, mFile); 535 x = length & 0xff; 536 fwrite(&x, 1, 1, mFile); 537#endif 538 539 fwrite((const uint8_t *)buffer->data() + buffer->range_offset(), 540 1, length, mFile); 541 542#if USE_NALLEN_FOUR 543 mOffset += length + 4; 544#else 545 mOffset += length + 2; 546#endif 547 548 return old_offset; 549} 550 551size_t MPEG4Writer::write( 552 const void *ptr, size_t size, size_t nmemb, FILE *stream) { 553 554 const size_t bytes = size * nmemb; 555 if (mWriteMoovBoxToMemory) { 556 off_t moovBoxSize = 8 + mMoovBoxBufferOffset + bytes; 557 if (moovBoxSize > mEstimatedMoovBoxSize) { 558 for (List<off_t>::iterator it = mBoxes.begin(); 559 it != mBoxes.end(); ++it) { 560 (*it) += mOffset; 561 } 562 fseeko(mFile, mOffset, SEEK_SET); 563 fwrite(mMoovBoxBuffer, 1, mMoovBoxBufferOffset, stream); 564 fwrite(ptr, size, nmemb, stream); 565 mOffset += (bytes + mMoovBoxBufferOffset); 566 free(mMoovBoxBuffer); 567 mMoovBoxBuffer = NULL; 568 mMoovBoxBufferOffset = 0; 569 mWriteMoovBoxToMemory = false; 570 mStreamableFile = false; 571 } else { 572 memcpy(mMoovBoxBuffer + mMoovBoxBufferOffset, ptr, bytes); 573 mMoovBoxBufferOffset += bytes; 574 } 575 } else { 576 fwrite(ptr, size, nmemb, stream); 577 mOffset += bytes; 578 } 579 return bytes; 580} 581 582void MPEG4Writer::beginBox(const char *fourcc) { 583 CHECK_EQ(strlen(fourcc), 4); 584 585 mBoxes.push_back(mWriteMoovBoxToMemory? 586 mMoovBoxBufferOffset: mOffset); 587 588 writeInt32(0); 589 writeFourcc(fourcc); 590} 591 592void MPEG4Writer::endBox() { 593 CHECK(!mBoxes.empty()); 594 595 off_t offset = *--mBoxes.end(); 596 mBoxes.erase(--mBoxes.end()); 597 598 if (mWriteMoovBoxToMemory) { 599 int32_t x = htonl(mMoovBoxBufferOffset - offset); 600 memcpy(mMoovBoxBuffer + offset, &x, 4); 601 } else { 602 fseeko(mFile, offset, SEEK_SET); 603 writeInt32(mOffset - offset); 604 mOffset -= 4; 605 fseeko(mFile, mOffset, SEEK_SET); 606 } 607} 608 609void MPEG4Writer::writeInt8(int8_t x) { 610 write(&x, 1, 1, mFile); 611} 612 613void MPEG4Writer::writeInt16(int16_t x) { 614 x = htons(x); 615 write(&x, 1, 2, mFile); 616} 617 618void MPEG4Writer::writeInt32(int32_t x) { 619 x = htonl(x); 620 write(&x, 1, 4, mFile); 621} 622 623void MPEG4Writer::writeInt64(int64_t x) { 624 x = hton64(x); 625 write(&x, 1, 8, mFile); 626} 627 628void MPEG4Writer::writeCString(const char *s) { 629 size_t n = strlen(s); 630 write(s, 1, n + 1, mFile); 631} 632 633void MPEG4Writer::writeFourcc(const char *s) { 634 CHECK_EQ(strlen(s), 4); 635 write(s, 1, 4, mFile); 636} 637 638void MPEG4Writer::write(const void *data, size_t size) { 639 write(data, 1, size, mFile); 640} 641 642bool MPEG4Writer::exceedsFileSizeLimit() { 643 // No limit 644 if (mMaxFileSizeLimitBytes == 0) { 645 return false; 646 } 647 648 int64_t nTotalBytesEstimate = static_cast<int64_t>(mEstimatedMoovBoxSize); 649 for (List<Track *>::iterator it = mTracks.begin(); 650 it != mTracks.end(); ++it) { 651 nTotalBytesEstimate += (*it)->getEstimatedTrackSizeBytes(); 652 } 653 return (nTotalBytesEstimate >= mMaxFileSizeLimitBytes); 654} 655 656bool MPEG4Writer::exceedsFileDurationLimit() { 657 // No limit 658 if (mMaxFileDurationLimitUs == 0) { 659 return false; 660 } 661 662 for (List<Track *>::iterator it = mTracks.begin(); 663 it != mTracks.end(); ++it) { 664 if ((*it)->getDurationUs() >= mMaxFileDurationLimitUs) { 665 return true; 666 } 667 } 668 return false; 669} 670 671bool MPEG4Writer::reachedEOS() { 672 bool allDone = true; 673 for (List<Track *>::iterator it = mTracks.begin(); 674 it != mTracks.end(); ++it) { 675 if (!(*it)->reachedEOS()) { 676 allDone = false; 677 break; 678 } 679 } 680 681 return allDone; 682} 683 684void MPEG4Writer::setStartTimestampUs(int64_t timeUs) { 685 LOGI("setStartTimestampUs: %lld", timeUs); 686 CHECK(timeUs >= 0); 687 Mutex::Autolock autoLock(mLock); 688 if (mStartTimestampUs < 0 || mStartTimestampUs > timeUs) { 689 mStartTimestampUs = timeUs; 690 LOGI("Earliest track starting time: %lld", mStartTimestampUs); 691 } 692} 693 694int64_t MPEG4Writer::getStartTimestampUs() { 695 Mutex::Autolock autoLock(mLock); 696 return mStartTimestampUs; 697} 698 699size_t MPEG4Writer::numTracks() { 700 Mutex::Autolock autolock(mLock); 701 return mTracks.size(); 702} 703 704//////////////////////////////////////////////////////////////////////////////// 705 706MPEG4Writer::Track::Track( 707 MPEG4Writer *owner, const sp<MediaSource> &source) 708 : mOwner(owner), 709 mMeta(source->getFormat()), 710 mSource(source), 711 mDone(false), 712 mPaused(false), 713 mResumed(false), 714 mTrackDurationUs(0), 715 mEstimatedTrackSizeBytes(0), 716 mSamplesHaveSameSize(true), 717 mCodecSpecificData(NULL), 718 mCodecSpecificDataSize(0), 719 mGotAllCodecSpecificData(false), 720 mReachedEOS(false) { 721 getCodecSpecificDataFromInputFormatIfPossible(); 722 723 if (!mMeta->findInt32(kKeyTimeScale, &mTimeScale)) { 724 mTimeScale = 1000; 725 } 726 727 const char *mime; 728 mMeta->findCString(kKeyMIMEType, &mime); 729 mIsAvc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC); 730 mIsAudio = !strncasecmp(mime, "audio/", 6); 731 mIsMPEG4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) || 732 !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC); 733 734 CHECK(mTimeScale > 0); 735} 736 737void MPEG4Writer::Track::getCodecSpecificDataFromInputFormatIfPossible() { 738 const char *mime; 739 CHECK(mMeta->findCString(kKeyMIMEType, &mime)); 740 741 if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { 742 uint32_t type; 743 const void *data; 744 size_t size; 745 if (mMeta->findData(kKeyAVCC, &type, &data, &size)) { 746 mCodecSpecificData = malloc(size); 747 mCodecSpecificDataSize = size; 748 memcpy(mCodecSpecificData, data, size); 749 mGotAllCodecSpecificData = true; 750 } 751 } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) 752 || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) { 753 uint32_t type; 754 const void *data; 755 size_t size; 756 if (mMeta->findData(kKeyESDS, &type, &data, &size)) { 757 ESDS esds(data, size); 758 if (esds.getCodecSpecificInfo(&data, &size) == OK) { 759 mCodecSpecificData = malloc(size); 760 mCodecSpecificDataSize = size; 761 memcpy(mCodecSpecificData, data, size); 762 mGotAllCodecSpecificData = true; 763 } 764 } 765 } 766} 767 768MPEG4Writer::Track::~Track() { 769 stop(); 770 771 if (mCodecSpecificData != NULL) { 772 free(mCodecSpecificData); 773 mCodecSpecificData = NULL; 774 } 775} 776 777void MPEG4Writer::Track::initTrackingProgressStatus(MetaData *params) { 778 LOGV("initTrackingProgressStatus"); 779 mPreviousTrackTimeUs = -1; 780 mTrackingProgressStatus = false; 781 mTrackEveryTimeDurationUs = 0; 782 { 783 int64_t timeUs; 784 if (params && params->findInt64(kKeyTrackTimeStatus, &timeUs)) { 785 LOGV("Receive request to track progress status for every %lld us", timeUs); 786 mTrackEveryTimeDurationUs = timeUs; 787 mTrackingProgressStatus = true; 788 } 789 } 790} 791 792// static 793void *MPEG4Writer::ThreadWrapper(void *me) { 794 LOGV("ThreadWrapper: %p", me); 795 MPEG4Writer *writer = static_cast<MPEG4Writer *>(me); 796 writer->threadFunc(); 797 return NULL; 798} 799 800void MPEG4Writer::bufferChunk(const Chunk& chunk) { 801 LOGV("bufferChunk: %p", chunk.mTrack); 802 Mutex::Autolock autolock(mLock); 803 CHECK_EQ(mDone, false); 804 805 for (List<ChunkInfo>::iterator it = mChunkInfos.begin(); 806 it != mChunkInfos.end(); ++it) { 807 808 if (chunk.mTrack == it->mTrack) { // Found owner 809 it->mChunks.push_back(chunk); 810 mChunkReadyCondition.signal(); 811 return; 812 } 813 } 814 815 CHECK("Received a chunk for a unknown track" == 0); 816} 817 818void MPEG4Writer::writeFirstChunk(ChunkInfo* info) { 819 LOGV("writeFirstChunk: %p", info->mTrack); 820 821 List<Chunk>::iterator chunkIt = info->mChunks.begin(); 822 for (List<MediaBuffer *>::iterator it = chunkIt->mSamples.begin(); 823 it != chunkIt->mSamples.end(); ++it) { 824 825 off_t offset = info->mTrack->isAvc() 826 ? addLengthPrefixedSample_l(*it) 827 : addSample_l(*it); 828 if (it == chunkIt->mSamples.begin()) { 829 info->mTrack->addChunkOffset(offset); 830 } 831 } 832 833 // Done with the current chunk. 834 // Release all the samples in this chunk. 835 while (!chunkIt->mSamples.empty()) { 836 List<MediaBuffer *>::iterator it = chunkIt->mSamples.begin(); 837 (*it)->release(); 838 (*it) = NULL; 839 chunkIt->mSamples.erase(it); 840 } 841 chunkIt->mSamples.clear(); 842 info->mChunks.erase(chunkIt); 843} 844 845void MPEG4Writer::writeChunks() { 846 LOGV("writeChunks"); 847 size_t outstandingChunks = 0; 848 while (!mChunkInfos.empty()) { 849 List<ChunkInfo>::iterator it = mChunkInfos.begin(); 850 while (!it->mChunks.empty()) { 851 CHECK_EQ(OK, writeOneChunk()); 852 ++outstandingChunks; 853 } 854 it->mTrack = NULL; 855 mChunkInfos.erase(it); 856 } 857 mChunkInfos.clear(); 858 LOGD("%d chunks are written in the last batch", outstandingChunks); 859} 860 861status_t MPEG4Writer::writeOneChunk() { 862 LOGV("writeOneChunk"); 863 864 // Find the smallest timestamp, and write that chunk out 865 // XXX: What if some track is just too slow? 866 int64_t minTimestampUs = 0x7FFFFFFFFFFFFFFFLL; 867 Track *track = NULL; 868 for (List<ChunkInfo>::iterator it = mChunkInfos.begin(); 869 it != mChunkInfos.end(); ++it) { 870 if (!it->mChunks.empty()) { 871 List<Chunk>::iterator chunkIt = it->mChunks.begin(); 872 if (chunkIt->mTimeStampUs < minTimestampUs) { 873 minTimestampUs = chunkIt->mTimeStampUs; 874 track = it->mTrack; 875 } 876 } 877 } 878 879 if (track == NULL) { 880 LOGV("Nothing to be written after all"); 881 return OK; 882 } 883 884 if (mIsFirstChunk) { 885 mIsFirstChunk = false; 886 } 887 for (List<ChunkInfo>::iterator it = mChunkInfos.begin(); 888 it != mChunkInfos.end(); ++it) { 889 if (it->mTrack == track) { 890 writeFirstChunk(&(*it)); 891 } 892 } 893 return OK; 894} 895 896void MPEG4Writer::threadFunc() { 897 LOGV("threadFunc"); 898 899 while (!mDone) { 900 { 901 Mutex::Autolock autolock(mLock); 902 mChunkReadyCondition.wait(mLock); 903 CHECK_EQ(writeOneChunk(), OK); 904 } 905 } 906 907 { 908 // Write ALL samples 909 Mutex::Autolock autolock(mLock); 910 writeChunks(); 911 } 912} 913 914status_t MPEG4Writer::startWriterThread() { 915 LOGV("startWriterThread"); 916 917 mDone = false; 918 mIsFirstChunk = true; 919 for (List<Track *>::iterator it = mTracks.begin(); 920 it != mTracks.end(); ++it) { 921 ChunkInfo info; 922 info.mTrack = *it; 923 mChunkInfos.push_back(info); 924 } 925 926 pthread_attr_t attr; 927 pthread_attr_init(&attr); 928 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 929 pthread_create(&mThread, &attr, ThreadWrapper, this); 930 pthread_attr_destroy(&attr); 931 return OK; 932} 933 934status_t MPEG4Writer::Track::start(MetaData *params) { 935 if (!mDone && mPaused) { 936 mPaused = false; 937 mResumed = true; 938 return OK; 939 } 940 941 int64_t startTimeUs; 942 if (params == NULL || !params->findInt64(kKeyTime, &startTimeUs)) { 943 startTimeUs = 0; 944 } 945 946 initTrackingProgressStatus(params); 947 948 sp<MetaData> meta = new MetaData; 949 meta->setInt64(kKeyTime, startTimeUs); 950 status_t err = mSource->start(meta.get()); 951 if (err != OK) { 952 mDone = mReachedEOS = true; 953 return err; 954 } 955 956 pthread_attr_t attr; 957 pthread_attr_init(&attr); 958 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 959 960 mDone = false; 961 mTrackDurationUs = 0; 962 mReachedEOS = false; 963 mEstimatedTrackSizeBytes = 0; 964 965 pthread_create(&mThread, &attr, ThreadWrapper, this); 966 pthread_attr_destroy(&attr); 967 968 return OK; 969} 970 971void MPEG4Writer::Track::pause() { 972 mPaused = true; 973} 974 975void MPEG4Writer::Track::stop() { 976 if (mDone) { 977 return; 978 } 979 980 mDone = true; 981 982 void *dummy; 983 pthread_join(mThread, &dummy); 984 985 mSource->stop(); 986} 987 988bool MPEG4Writer::Track::reachedEOS() { 989 return mReachedEOS; 990} 991 992// static 993void *MPEG4Writer::Track::ThreadWrapper(void *me) { 994 Track *track = static_cast<Track *>(me); 995 996 track->threadEntry(); 997 998 return NULL; 999} 1000 1001#include <ctype.h> 1002static void hexdump(const void *_data, size_t size) { 1003 const uint8_t *data = (const uint8_t *)_data; 1004 size_t offset = 0; 1005 while (offset < size) { 1006 printf("0x%04x ", offset); 1007 1008 size_t n = size - offset; 1009 if (n > 16) { 1010 n = 16; 1011 } 1012 1013 for (size_t i = 0; i < 16; ++i) { 1014 if (i == 8) { 1015 printf(" "); 1016 } 1017 1018 if (offset + i < size) { 1019 printf("%02x ", data[offset + i]); 1020 } else { 1021 printf(" "); 1022 } 1023 } 1024 1025 printf(" "); 1026 1027 for (size_t i = 0; i < n; ++i) { 1028 if (isprint(data[offset + i])) { 1029 printf("%c", data[offset + i]); 1030 } else { 1031 printf("."); 1032 } 1033 } 1034 1035 printf("\n"); 1036 1037 offset += 16; 1038 } 1039} 1040 1041 1042status_t MPEG4Writer::Track::makeAVCCodecSpecificData( 1043 const uint8_t *data, size_t size) { 1044 // hexdump(data, size); 1045 1046 if (mCodecSpecificData != NULL) { 1047 LOGE("Already have codec specific data"); 1048 return ERROR_MALFORMED; 1049 } 1050 1051 if (size < 4 || memcmp("\x00\x00\x00\x01", data, 4)) { 1052 LOGE("Must start with a start code"); 1053 return ERROR_MALFORMED; 1054 } 1055 1056 size_t picParamOffset = 4; 1057 while (picParamOffset + 3 < size 1058 && memcmp("\x00\x00\x00\x01", &data[picParamOffset], 4)) { 1059 ++picParamOffset; 1060 } 1061 1062 if (picParamOffset + 3 >= size) { 1063 LOGE("Could not find start-code for pictureParameterSet"); 1064 return ERROR_MALFORMED; 1065 } 1066 1067 size_t seqParamSetLength = picParamOffset - 4; 1068 size_t picParamSetLength = size - picParamOffset - 4; 1069 1070 mCodecSpecificDataSize = 1071 6 + 1 + seqParamSetLength + 2 + picParamSetLength + 2; 1072 1073 mCodecSpecificData = malloc(mCodecSpecificDataSize); 1074 uint8_t *header = (uint8_t *)mCodecSpecificData; 1075 header[0] = 1; 1076 header[1] = 0x42; // profile 1077 header[2] = 0x80; 1078 header[3] = 0x1e; // level 1079 1080#if USE_NALLEN_FOUR 1081 header[4] = 0xfc | 3; // length size == 4 bytes 1082#else 1083 header[4] = 0xfc | 1; // length size == 2 bytes 1084#endif 1085 1086 header[5] = 0xe0 | 1; 1087 header[6] = seqParamSetLength >> 8; 1088 header[7] = seqParamSetLength & 0xff; 1089 memcpy(&header[8], &data[4], seqParamSetLength); 1090 header += 8 + seqParamSetLength; 1091 header[0] = 1; 1092 header[1] = picParamSetLength >> 8; 1093 header[2] = picParamSetLength & 0xff; 1094 memcpy(&header[3], &data[picParamOffset + 4], picParamSetLength); 1095 1096 return OK; 1097} 1098 1099static bool collectStatisticalData() { 1100 char value[PROPERTY_VALUE_MAX]; 1101 if (property_get("media.stagefright.record-stats", value, NULL) 1102 && (!strcmp(value, "1") || !strcasecmp(value, "true"))) { 1103 return true; 1104 } 1105 return false; 1106} 1107 1108void MPEG4Writer::Track::threadEntry() { 1109 int32_t count = 0; 1110 const int64_t interleaveDurationUs = mOwner->interleaveDuration(); 1111 int64_t chunkTimestampUs = 0; 1112 int32_t nChunks = 0; 1113 int32_t nZeroLengthFrames = 0; 1114 int64_t lastTimestampUs = 0; // Previous sample time stamp in ms 1115 int64_t lastDurationUs = 0; // Between the previous two samples in ms 1116 int32_t sampleCount = 1; // Sample count in the current stts table entry 1117 uint32_t previousSampleSize = 0; // Size of the previous sample 1118 int64_t previousPausedDurationUs = 0; 1119 int64_t timestampUs; 1120 sp<MetaData> meta_data; 1121 bool collectStats = collectStatisticalData(); 1122 1123 mNumSamples = 0; 1124 mMaxWriteTimeUs = 0; 1125 status_t err = OK; 1126 MediaBuffer *buffer; 1127 while (!mDone && (err = mSource->read(&buffer)) == OK) { 1128 if (buffer->range_length() == 0) { 1129 buffer->release(); 1130 buffer = NULL; 1131 ++nZeroLengthFrames; 1132 continue; 1133 } 1134 1135 // If the codec specific data has not been received yet, delay pause. 1136 // After the codec specific data is received, discard what we received 1137 // when the track is to be paused. 1138 if (mPaused && !mResumed) { 1139 buffer->release(); 1140 buffer = NULL; 1141 continue; 1142 } 1143 1144 ++count; 1145 1146 int32_t isCodecConfig; 1147 if (buffer->meta_data()->findInt32(kKeyIsCodecConfig, &isCodecConfig) 1148 && isCodecConfig) { 1149 CHECK(!mGotAllCodecSpecificData); 1150 1151 if (mIsAvc) { 1152 status_t err = makeAVCCodecSpecificData( 1153 (const uint8_t *)buffer->data() 1154 + buffer->range_offset(), 1155 buffer->range_length()); 1156 CHECK_EQ(OK, err); 1157 } else if (mIsMPEG4) { 1158 mCodecSpecificDataSize = buffer->range_length(); 1159 mCodecSpecificData = malloc(mCodecSpecificDataSize); 1160 memcpy(mCodecSpecificData, 1161 (const uint8_t *)buffer->data() 1162 + buffer->range_offset(), 1163 buffer->range_length()); 1164 } 1165 1166 buffer->release(); 1167 buffer = NULL; 1168 1169 mGotAllCodecSpecificData = true; 1170 continue; 1171 } else if (!mGotAllCodecSpecificData && 1172 count == 1 && mIsMPEG4 && mCodecSpecificData == NULL) { 1173 // The TI mpeg4 encoder does not properly set the 1174 // codec-specific-data flag. 1175 1176 const uint8_t *data = 1177 (const uint8_t *)buffer->data() + buffer->range_offset(); 1178 1179 const size_t size = buffer->range_length(); 1180 1181 size_t offset = 0; 1182 while (offset + 3 < size) { 1183 if (data[offset] == 0x00 && data[offset + 1] == 0x00 1184 && data[offset + 2] == 0x01 && data[offset + 3] == 0xb6) { 1185 break; 1186 } 1187 1188 ++offset; 1189 } 1190 1191 // CHECK(offset + 3 < size); 1192 if (offset + 3 >= size) { 1193 // XXX assume the entire first chunk of data is the codec specific 1194 // data. 1195 offset = size; 1196 } 1197 1198 mCodecSpecificDataSize = offset; 1199 mCodecSpecificData = malloc(offset); 1200 memcpy(mCodecSpecificData, data, offset); 1201 1202 buffer->set_range(buffer->range_offset() + offset, size - offset); 1203 1204 if (size == offset) { 1205 buffer->release(); 1206 buffer = NULL; 1207 1208 continue; 1209 } 1210 1211 mGotAllCodecSpecificData = true; 1212 } else if (!mGotAllCodecSpecificData && mIsAvc && count < 3) { 1213 // The TI video encoder does not flag codec specific data 1214 // as such and also splits up SPS and PPS across two buffers. 1215 1216 const uint8_t *data = 1217 (const uint8_t *)buffer->data() + buffer->range_offset(); 1218 1219 size_t size = buffer->range_length(); 1220 1221 CHECK(count == 2 || mCodecSpecificData == NULL); 1222 1223 size_t offset = mCodecSpecificDataSize; 1224 mCodecSpecificDataSize += size + 4; 1225 mCodecSpecificData = 1226 realloc(mCodecSpecificData, mCodecSpecificDataSize); 1227 1228 memcpy((uint8_t *)mCodecSpecificData + offset, 1229 "\x00\x00\x00\x01", 4); 1230 1231 memcpy((uint8_t *)mCodecSpecificData + offset + 4, data, size); 1232 1233 buffer->release(); 1234 buffer = NULL; 1235 1236 if (count == 2) { 1237 void *tmp = mCodecSpecificData; 1238 size = mCodecSpecificDataSize; 1239 mCodecSpecificData = NULL; 1240 mCodecSpecificDataSize = 0; 1241 1242 status_t err = makeAVCCodecSpecificData( 1243 (const uint8_t *)tmp, size); 1244 free(tmp); 1245 tmp = NULL; 1246 CHECK_EQ(OK, err); 1247 1248 mGotAllCodecSpecificData = true; 1249 } 1250 1251 continue; 1252 } 1253 1254 if (!mGotAllCodecSpecificData) { 1255 mGotAllCodecSpecificData = true; 1256 } 1257 1258 // Make a deep copy of the MediaBuffer and Metadata and release 1259 // the original as soon as we can 1260 MediaBuffer *copy = new MediaBuffer(buffer->range_length()); 1261 memcpy(copy->data(), (uint8_t *)buffer->data() + buffer->range_offset(), 1262 buffer->range_length()); 1263 copy->set_range(0, buffer->range_length()); 1264 meta_data = new MetaData(*buffer->meta_data().get()); 1265 buffer->release(); 1266 buffer = NULL; 1267 1268 if (mIsAvc) StripStartcode(copy); 1269 1270 size_t sampleSize; 1271 sampleSize = mIsAvc 1272#if USE_NALLEN_FOUR 1273 ? copy->range_length() + 4 1274#else 1275 ? copy->range_length() + 2 1276#endif 1277 : copy->range_length(); 1278 1279 // Max file size or duration handling 1280 mEstimatedTrackSizeBytes += sampleSize; 1281 if (mOwner->exceedsFileSizeLimit()) { 1282 mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0); 1283 break; 1284 } 1285 if (mOwner->exceedsFileDurationLimit()) { 1286 mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0); 1287 break; 1288 } 1289 1290 1291 int32_t isSync = false; 1292 meta_data->findInt32(kKeyIsSyncFrame, &isSync); 1293 1294 CHECK(meta_data->findInt64(kKeyTime, ×tampUs)); 1295 1296//////////////////////////////////////////////////////////////////////////////// 1297 if (mSampleSizes.empty()) { 1298 mStartTimestampUs = timestampUs; 1299 mOwner->setStartTimestampUs(mStartTimestampUs); 1300 } 1301 1302 if (mResumed) { 1303 previousPausedDurationUs += (timestampUs - mTrackDurationUs - lastDurationUs); 1304 mResumed = false; 1305 } 1306 1307 timestampUs -= previousPausedDurationUs; 1308 LOGV("time stamp: %lld and previous paused duration %lld", 1309 timestampUs, previousPausedDurationUs); 1310 if (timestampUs > mTrackDurationUs) { 1311 mTrackDurationUs = timestampUs; 1312 } 1313 1314 mSampleSizes.push_back(sampleSize); 1315 ++mNumSamples; 1316 if (mNumSamples > 2) { 1317 if (lastDurationUs != timestampUs - lastTimestampUs) { 1318 SttsTableEntry sttsEntry(sampleCount, lastDurationUs); 1319 mSttsTableEntries.push_back(sttsEntry); 1320 sampleCount = 1; 1321 } else { 1322 ++sampleCount; 1323 } 1324 } 1325 if (mSamplesHaveSameSize) { 1326 if (mNumSamples >= 2 && previousSampleSize != sampleSize) { 1327 mSamplesHaveSameSize = false; 1328 } 1329 previousSampleSize = sampleSize; 1330 } 1331 lastDurationUs = timestampUs - lastTimestampUs; 1332 lastTimestampUs = timestampUs; 1333 1334 if (isSync != 0) { 1335 mStssTableEntries.push_back(mNumSamples); 1336 } 1337 1338 if (mTrackingProgressStatus) { 1339 if (mPreviousTrackTimeUs <= 0) { 1340 mPreviousTrackTimeUs = mStartTimestampUs; 1341 } 1342 trackProgressStatus(timestampUs); 1343 } 1344 if (mOwner->numTracks() == 1) { 1345 off_t offset = mIsAvc? mOwner->addLengthPrefixedSample_l(copy) 1346 : mOwner->addSample_l(copy); 1347 if (mChunkOffsets.empty()) { 1348 mChunkOffsets.push_back(offset); 1349 } 1350 copy->release(); 1351 copy = NULL; 1352 continue; 1353 } 1354 1355 mChunkSamples.push_back(copy); 1356 if (interleaveDurationUs == 0) { 1357 StscTableEntry stscEntry(++nChunks, 1, 1); 1358 mStscTableEntries.push_back(stscEntry); 1359 bufferChunk(timestampUs); 1360 } else { 1361 if (chunkTimestampUs == 0) { 1362 chunkTimestampUs = timestampUs; 1363 } else { 1364 if (timestampUs - chunkTimestampUs > interleaveDurationUs) { 1365 ++nChunks; 1366 if (collectStats) { 1367 mChunkDurations.push_back(timestampUs - chunkTimestampUs); 1368 } 1369 if (nChunks == 1 || // First chunk 1370 (--(mStscTableEntries.end()))->samplesPerChunk != 1371 mChunkSamples.size()) { 1372 StscTableEntry stscEntry(nChunks, 1373 mChunkSamples.size(), 1); 1374 mStscTableEntries.push_back(stscEntry); 1375 } 1376 bufferChunk(timestampUs); 1377 chunkTimestampUs = timestampUs; 1378 } 1379 } 1380 } 1381 1382 } 1383 1384 if (mSampleSizes.empty()) { 1385 err = UNKNOWN_ERROR; 1386 } 1387 mOwner->trackProgressStatus(this, -1, err); 1388 1389 // Last chunk 1390 if (mOwner->numTracks() == 1) { 1391 StscTableEntry stscEntry(1, mNumSamples, 1); 1392 mStscTableEntries.push_back(stscEntry); 1393 } else if (!mChunkSamples.empty()) { 1394 ++nChunks; 1395 StscTableEntry stscEntry(nChunks, mChunkSamples.size(), 1); 1396 mStscTableEntries.push_back(stscEntry); 1397 bufferChunk(timestampUs); 1398 } 1399 1400 // We don't really know how long the last frame lasts, since 1401 // there is no frame time after it, just repeat the previous 1402 // frame's duration. 1403 if (mNumSamples == 1) { 1404 lastDurationUs = 0; // A single sample's duration 1405 } else { 1406 ++sampleCount; // Count for the last sample 1407 } 1408 SttsTableEntry sttsEntry(sampleCount, lastDurationUs); 1409 mSttsTableEntries.push_back(sttsEntry); 1410 mTrackDurationUs += lastDurationUs; 1411 mReachedEOS = true; 1412 LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames. Max write time: %lld us - %s", 1413 count, nZeroLengthFrames, mNumSamples, mMaxWriteTimeUs, mIsAudio? "audio": "video"); 1414 1415 logStatisticalData(mIsAudio); 1416} 1417 1418void MPEG4Writer::Track::trackProgressStatus(int64_t timeUs, status_t err) { 1419 LOGV("trackProgressStatus: %lld us", timeUs); 1420 if (mTrackEveryTimeDurationUs > 0 && 1421 timeUs - mPreviousTrackTimeUs >= mTrackEveryTimeDurationUs) { 1422 LOGV("Fire time tracking progress status at %lld us", timeUs); 1423 mOwner->trackProgressStatus(this, timeUs - mPreviousTrackTimeUs, err); 1424 mPreviousTrackTimeUs = timeUs; 1425 } 1426} 1427 1428void MPEG4Writer::trackProgressStatus( 1429 const MPEG4Writer::Track* track, int64_t timeUs, status_t err) { 1430 Mutex::Autolock lock(mLock); 1431 int32_t nTracks = mTracks.size(); 1432 CHECK(nTracks >= 1); 1433 CHECK(nTracks < 64); // Arbitrary number 1434 1435 int32_t trackNum = 0; 1436#if 0 1437 // In the worst case, we can put the trackNum 1438 // along with MEDIA_RECORDER_INFO_COMPLETION_STATUS 1439 // to report the progress. 1440 for (List<Track *>::iterator it = mTracks.begin(); 1441 it != mTracks.end(); ++it, ++trackNum) { 1442 if (track == (*it)) { 1443 break; 1444 } 1445 } 1446#endif 1447 CHECK(trackNum < nTracks); 1448 trackNum <<= 16; 1449 1450 // Error notification 1451 // Do not consider ERROR_END_OF_STREAM an error 1452 if (err != OK && err != ERROR_END_OF_STREAM) { 1453 notify(MEDIA_RECORDER_EVENT_ERROR, 1454 trackNum | MEDIA_RECORDER_ERROR_UNKNOWN, 1455 err); 1456 return; 1457 } 1458 1459 if (timeUs == -1) { 1460 // Send completion notification 1461 notify(MEDIA_RECORDER_EVENT_INFO, 1462 trackNum | MEDIA_RECORDER_INFO_COMPLETION_STATUS, 1463 err); 1464 return; 1465 } else { 1466 // Send progress status 1467 notify(MEDIA_RECORDER_EVENT_INFO, 1468 trackNum | MEDIA_RECORDER_INFO_PROGRESS_TIME_STATUS, 1469 timeUs / 1000); 1470 } 1471} 1472 1473void MPEG4Writer::Track::findMinAvgMaxSampleDurationMs( 1474 int32_t *min, int32_t *avg, int32_t *max) { 1475 CHECK(!mSampleSizes.empty()); 1476 int32_t avgSampleDurationMs = mTrackDurationUs / 1000 / mNumSamples; 1477 int32_t minSampleDurationMs = 0x7FFFFFFF; 1478 int32_t maxSampleDurationMs = 0; 1479 for (List<SttsTableEntry>::iterator it = mSttsTableEntries.begin(); 1480 it != mSttsTableEntries.end(); ++it) { 1481 int32_t sampleDurationMs = 1482 (static_cast<int32_t>(it->sampleDurationUs) + 500) / 1000; 1483 if (sampleDurationMs > maxSampleDurationMs) { 1484 maxSampleDurationMs = sampleDurationMs; 1485 } else if (sampleDurationMs < minSampleDurationMs) { 1486 minSampleDurationMs = sampleDurationMs; 1487 } 1488 LOGI("sample duration: %d ms", sampleDurationMs); 1489 } 1490 CHECK(minSampleDurationMs != 0); 1491 CHECK(avgSampleDurationMs != 0); 1492 CHECK(maxSampleDurationMs != 0); 1493 *min = minSampleDurationMs; 1494 *avg = avgSampleDurationMs; 1495 *max = maxSampleDurationMs; 1496} 1497 1498// Don't count the last duration 1499void MPEG4Writer::Track::findMinMaxChunkDurations(int64_t *min, int64_t *max) { 1500 int64_t duration = mOwner->interleaveDuration(); 1501 int64_t minChunkDuration = duration; 1502 int64_t maxChunkDuration = duration; 1503 if (mChunkDurations.size() > 1) { 1504 for (List<int64_t>::iterator it = mChunkDurations.begin(); 1505 it != --mChunkDurations.end(); ++it) { 1506 if (minChunkDuration > (*it)) { 1507 minChunkDuration = (*it); 1508 } else if (maxChunkDuration < (*it)) { 1509 maxChunkDuration = (*it); 1510 } 1511 } 1512 } 1513 *min = minChunkDuration; 1514 *max = maxChunkDuration; 1515} 1516 1517void MPEG4Writer::Track::logStatisticalData(bool isAudio) { 1518 if (mTrackDurationUs <= 0 || mSampleSizes.empty()) { 1519 LOGI("nothing is recorded"); 1520 return; 1521 } 1522 1523 bool collectStats = collectStatisticalData(); 1524 1525 if (collectStats) { 1526 LOGI("%s track - duration %lld us, total %d frames", 1527 isAudio? "audio": "video", mTrackDurationUs, 1528 mNumSamples); 1529 int32_t min, avg, max; 1530 findMinAvgMaxSampleDurationMs(&min, &avg, &max); 1531 LOGI("min/avg/max sample duration (ms): %d/%d/%d", min, avg, max); 1532 if (!isAudio) { 1533 float avgFps = 1000.0 / avg; 1534 float minFps = 1000.0 / max; 1535 float maxFps = 1000.0 / min; 1536 LOGI("min/avg/max frame rate (fps): %.2f/%.2f/%.2f", 1537 minFps, avgFps, maxFps); 1538 } 1539 1540 int64_t totalBytes = 0; 1541 for (List<size_t>::iterator it = mSampleSizes.begin(); 1542 it != mSampleSizes.end(); ++it) { 1543 totalBytes += (*it); 1544 } 1545 float bitRate = (totalBytes * 8000000.0) / mTrackDurationUs; 1546 LOGI("avg bit rate (bps): %.2f", bitRate); 1547 1548 int64_t duration = mOwner->interleaveDuration(); 1549 if (duration != 0) { // If interleaving is enabled 1550 int64_t minChunk, maxChunk; 1551 findMinMaxChunkDurations(&minChunk, &maxChunk); 1552 LOGI("min/avg/max chunk duration (ms): %lld/%lld/%lld", 1553 minChunk, duration, maxChunk); 1554 } 1555 } 1556} 1557 1558void MPEG4Writer::Track::bufferChunk(int64_t timestampUs) { 1559 LOGV("bufferChunk"); 1560 1561 int64_t startTimeUs = systemTime() / 1000; 1562 Chunk chunk(this, timestampUs, mChunkSamples); 1563 mOwner->bufferChunk(chunk); 1564 mChunkSamples.clear(); 1565 int64_t endTimeUs = systemTime() / 1000; 1566 if (mMaxWriteTimeUs < endTimeUs - startTimeUs) { 1567 mMaxWriteTimeUs = endTimeUs - startTimeUs; 1568 } 1569} 1570 1571int64_t MPEG4Writer::Track::getDurationUs() const { 1572 return mTrackDurationUs; 1573} 1574 1575int64_t MPEG4Writer::Track::getEstimatedTrackSizeBytes() const { 1576 return mEstimatedTrackSizeBytes; 1577} 1578 1579void MPEG4Writer::Track::writeTrackHeader( 1580 int32_t trackID, bool use32BitOffset) { 1581 const char *mime; 1582 bool success = mMeta->findCString(kKeyMIMEType, &mime); 1583 CHECK(success); 1584 1585 LOGV("%s track time scale: %d", 1586 mIsAudio? "Audio": "Video", mTimeScale); 1587 1588 1589 time_t now = time(NULL); 1590 int32_t mvhdTimeScale = mOwner->getTimeScale(); 1591 int64_t trakDurationUs = getDurationUs(); 1592 1593 mOwner->beginBox("trak"); 1594 1595 mOwner->beginBox("tkhd"); 1596 // Flags = 7 to indicate that the track is enabled, and 1597 // part of the presentation 1598 mOwner->writeInt32(0x07); // version=0, flags=7 1599 mOwner->writeInt32(now); // creation time 1600 mOwner->writeInt32(now); // modification time 1601 mOwner->writeInt32(trackID); 1602 mOwner->writeInt32(0); // reserved 1603 int32_t tkhdDuration = 1604 (trakDurationUs * mvhdTimeScale + 5E5) / 1E6; 1605 mOwner->writeInt32(tkhdDuration); // in mvhd timescale 1606 mOwner->writeInt32(0); // reserved 1607 mOwner->writeInt32(0); // reserved 1608 mOwner->writeInt16(0); // layer 1609 mOwner->writeInt16(0); // alternate group 1610 mOwner->writeInt16(mIsAudio ? 0x100 : 0); // volume 1611 mOwner->writeInt16(0); // reserved 1612 1613 mOwner->writeInt32(0x10000); // matrix 1614 mOwner->writeInt32(0); 1615 mOwner->writeInt32(0); 1616 mOwner->writeInt32(0); 1617 mOwner->writeInt32(0x10000); 1618 mOwner->writeInt32(0); 1619 mOwner->writeInt32(0); 1620 mOwner->writeInt32(0); 1621 mOwner->writeInt32(0x40000000); 1622 1623 if (mIsAudio) { 1624 mOwner->writeInt32(0); 1625 mOwner->writeInt32(0); 1626 } else { 1627 int32_t width, height; 1628 bool success = mMeta->findInt32(kKeyWidth, &width); 1629 success = success && mMeta->findInt32(kKeyHeight, &height); 1630 CHECK(success); 1631 1632 mOwner->writeInt32(width << 16); // 32-bit fixed-point value 1633 mOwner->writeInt32(height << 16); // 32-bit fixed-point value 1634 } 1635 mOwner->endBox(); // tkhd 1636 1637 int64_t moovStartTimeUs = mOwner->getStartTimestampUs(); 1638 if (mStartTimestampUs != moovStartTimeUs) { 1639 mOwner->beginBox("edts"); 1640 mOwner->beginBox("elst"); 1641 mOwner->writeInt32(0); // version=0, flags=0: 32-bit time 1642 mOwner->writeInt32(2); // never ends with an empty list 1643 1644 // First elst entry: specify the starting time offset 1645 int64_t offsetUs = mStartTimestampUs - moovStartTimeUs; 1646 int32_t seg = (offsetUs * mvhdTimeScale + 5E5) / 1E6; 1647 mOwner->writeInt32(seg); // in mvhd timecale 1648 mOwner->writeInt32(-1); // starting time offset 1649 mOwner->writeInt32(1 << 16); // rate = 1.0 1650 1651 // Second elst entry: specify the track duration 1652 seg = (trakDurationUs * mvhdTimeScale + 5E5) / 1E6; 1653 mOwner->writeInt32(seg); // in mvhd timescale 1654 mOwner->writeInt32(0); 1655 mOwner->writeInt32(1 << 16); 1656 mOwner->endBox(); 1657 mOwner->endBox(); 1658 } 1659 1660 mOwner->beginBox("mdia"); 1661 1662 mOwner->beginBox("mdhd"); 1663 mOwner->writeInt32(0); // version=0, flags=0 1664 mOwner->writeInt32(now); // creation time 1665 mOwner->writeInt32(now); // modification time 1666 mOwner->writeInt32(mTimeScale); // media timescale 1667 int32_t mdhdDuration = (trakDurationUs * mTimeScale + 5E5) / 1E6; 1668 mOwner->writeInt32(mdhdDuration); // use media timescale 1669 // Language follows the three letter standard ISO-639-2/T 1670 // 'e', 'n', 'g' for "English", for instance. 1671 // Each character is packed as the difference between its ASCII value and 0x60. 1672 // For "English", these are 00101, 01110, 00111. 1673 // XXX: Where is the padding bit located: 0x15C7? 1674 mOwner->writeInt16(0); // language code 1675 mOwner->writeInt16(0); // predefined 1676 mOwner->endBox(); 1677 1678 mOwner->beginBox("hdlr"); 1679 mOwner->writeInt32(0); // version=0, flags=0 1680 mOwner->writeInt32(0); // component type: should be mhlr 1681 mOwner->writeFourcc(mIsAudio ? "soun" : "vide"); // component subtype 1682 mOwner->writeInt32(0); // reserved 1683 mOwner->writeInt32(0); // reserved 1684 mOwner->writeInt32(0); // reserved 1685 // Removing "r" for the name string just makes the string 4 byte aligned 1686 mOwner->writeCString(mIsAudio ? "SoundHandle": "VideoHandle"); // name 1687 mOwner->endBox(); 1688 1689 mOwner->beginBox("minf"); 1690 if (mIsAudio) { 1691 mOwner->beginBox("smhd"); 1692 mOwner->writeInt32(0); // version=0, flags=0 1693 mOwner->writeInt16(0); // balance 1694 mOwner->writeInt16(0); // reserved 1695 mOwner->endBox(); 1696 } else { 1697 mOwner->beginBox("vmhd"); 1698 mOwner->writeInt32(0x01); // version=0, flags=1 1699 mOwner->writeInt16(0); // graphics mode 1700 mOwner->writeInt16(0); // opcolor 1701 mOwner->writeInt16(0); 1702 mOwner->writeInt16(0); 1703 mOwner->endBox(); 1704 } 1705 1706 mOwner->beginBox("dinf"); 1707 mOwner->beginBox("dref"); 1708 mOwner->writeInt32(0); // version=0, flags=0 1709 mOwner->writeInt32(1); // entry count (either url or urn) 1710 // The table index here refers to the sample description index 1711 // in the sample table entries. 1712 mOwner->beginBox("url "); 1713 mOwner->writeInt32(1); // version=0, flags=1 (self-contained) 1714 mOwner->endBox(); // url 1715 mOwner->endBox(); // dref 1716 mOwner->endBox(); // dinf 1717 1718 mOwner->beginBox("stbl"); 1719 1720 mOwner->beginBox("stsd"); 1721 mOwner->writeInt32(0); // version=0, flags=0 1722 mOwner->writeInt32(1); // entry count 1723 if (mIsAudio) { 1724 const char *fourcc = NULL; 1725 if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, mime)) { 1726 fourcc = "samr"; 1727 } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_WB, mime)) { 1728 fourcc = "sawb"; 1729 } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime)) { 1730 fourcc = "mp4a"; 1731 } else { 1732 LOGE("Unknown mime type '%s'.", mime); 1733 CHECK(!"should not be here, unknown mime type."); 1734 } 1735 1736 mOwner->beginBox(fourcc); // audio format 1737 mOwner->writeInt32(0); // reserved 1738 mOwner->writeInt16(0); // reserved 1739 mOwner->writeInt16(0x1); // data ref index 1740 mOwner->writeInt32(0); // reserved 1741 mOwner->writeInt32(0); // reserved 1742 int32_t nChannels; 1743 CHECK_EQ(true, mMeta->findInt32(kKeyChannelCount, &nChannels)); 1744 mOwner->writeInt16(nChannels); // channel count 1745 mOwner->writeInt16(16); // sample size 1746 mOwner->writeInt16(0); // predefined 1747 mOwner->writeInt16(0); // reserved 1748 1749 int32_t samplerate; 1750 bool success = mMeta->findInt32(kKeySampleRate, &samplerate); 1751 CHECK(success); 1752 1753 mOwner->writeInt32(samplerate << 16); 1754 if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime)) { 1755 mOwner->beginBox("esds"); 1756 1757 mOwner->writeInt32(0); // version=0, flags=0 1758 mOwner->writeInt8(0x03); // ES_DescrTag 1759 mOwner->writeInt8(23 + mCodecSpecificDataSize); 1760 mOwner->writeInt16(0x0000);// ES_ID 1761 mOwner->writeInt8(0x00); 1762 1763 mOwner->writeInt8(0x04); // DecoderConfigDescrTag 1764 mOwner->writeInt8(15 + mCodecSpecificDataSize); 1765 mOwner->writeInt8(0x40); // objectTypeIndication ISO/IEC 14492-2 1766 mOwner->writeInt8(0x15); // streamType AudioStream 1767 1768 mOwner->writeInt16(0x03); // XXX 1769 mOwner->writeInt8(0x00); // buffer size 24-bit 1770 mOwner->writeInt32(96000); // max bit rate 1771 mOwner->writeInt32(96000); // avg bit rate 1772 1773 mOwner->writeInt8(0x05); // DecoderSpecificInfoTag 1774 mOwner->writeInt8(mCodecSpecificDataSize); 1775 mOwner->write(mCodecSpecificData, mCodecSpecificDataSize); 1776 1777 static const uint8_t kData2[] = { 1778 0x06, // SLConfigDescriptorTag 1779 0x01, 1780 0x02 1781 }; 1782 mOwner->write(kData2, sizeof(kData2)); 1783 1784 mOwner->endBox(); // esds 1785 } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, mime) || 1786 !strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_WB, mime)) { 1787 // 3gpp2 Spec AMRSampleEntry fields 1788 mOwner->beginBox("damr"); 1789 mOwner->writeCString(" "); // vendor: 4 bytes 1790 mOwner->writeInt8(0); // decoder version 1791 mOwner->writeInt16(0x83FF); // mode set: all enabled 1792 mOwner->writeInt8(0); // mode change period 1793 mOwner->writeInt8(1); // frames per sample 1794 mOwner->endBox(); 1795 } 1796 mOwner->endBox(); 1797 } else { 1798 if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) { 1799 mOwner->beginBox("mp4v"); 1800 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) { 1801 mOwner->beginBox("s263"); 1802 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { 1803 mOwner->beginBox("avc1"); 1804 } else { 1805 LOGE("Unknown mime type '%s'.", mime); 1806 CHECK(!"should not be here, unknown mime type."); 1807 } 1808 1809 mOwner->writeInt32(0); // reserved 1810 mOwner->writeInt16(0); // reserved 1811 mOwner->writeInt16(1); // data ref index 1812 mOwner->writeInt16(0); // predefined 1813 mOwner->writeInt16(0); // reserved 1814 mOwner->writeInt32(0); // predefined 1815 mOwner->writeInt32(0); // predefined 1816 mOwner->writeInt32(0); // predefined 1817 1818 int32_t width, height; 1819 bool success = mMeta->findInt32(kKeyWidth, &width); 1820 success = success && mMeta->findInt32(kKeyHeight, &height); 1821 CHECK(success); 1822 1823 mOwner->writeInt16(width); 1824 mOwner->writeInt16(height); 1825 mOwner->writeInt32(0x480000); // horiz resolution 1826 mOwner->writeInt32(0x480000); // vert resolution 1827 mOwner->writeInt32(0); // reserved 1828 mOwner->writeInt16(1); // frame count 1829 mOwner->write(" ", 32); 1830 mOwner->writeInt16(0x18); // depth 1831 mOwner->writeInt16(-1); // predefined 1832 1833 CHECK(23 + mCodecSpecificDataSize < 128); 1834 1835 if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) { 1836 mOwner->beginBox("esds"); 1837 1838 mOwner->writeInt32(0); // version=0, flags=0 1839 1840 mOwner->writeInt8(0x03); // ES_DescrTag 1841 mOwner->writeInt8(23 + mCodecSpecificDataSize); 1842 mOwner->writeInt16(0x0000); // ES_ID 1843 mOwner->writeInt8(0x1f); 1844 1845 mOwner->writeInt8(0x04); // DecoderConfigDescrTag 1846 mOwner->writeInt8(15 + mCodecSpecificDataSize); 1847 mOwner->writeInt8(0x20); // objectTypeIndication ISO/IEC 14492-2 1848 mOwner->writeInt8(0x11); // streamType VisualStream 1849 1850 static const uint8_t kData[] = { 1851 0x01, 0x77, 0x00, 1852 0x00, 0x03, 0xe8, 0x00, 1853 0x00, 0x03, 0xe8, 0x00 1854 }; 1855 mOwner->write(kData, sizeof(kData)); 1856 1857 mOwner->writeInt8(0x05); // DecoderSpecificInfoTag 1858 1859 mOwner->writeInt8(mCodecSpecificDataSize); 1860 mOwner->write(mCodecSpecificData, mCodecSpecificDataSize); 1861 1862 static const uint8_t kData2[] = { 1863 0x06, // SLConfigDescriptorTag 1864 0x01, 1865 0x02 1866 }; 1867 mOwner->write(kData2, sizeof(kData2)); 1868 1869 mOwner->endBox(); // esds 1870 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) { 1871 mOwner->beginBox("d263"); 1872 1873 mOwner->writeInt32(0); // vendor 1874 mOwner->writeInt8(0); // decoder version 1875 mOwner->writeInt8(10); // level: 10 1876 mOwner->writeInt8(0); // profile: 0 1877 1878 mOwner->endBox(); // d263 1879 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { 1880 mOwner->beginBox("avcC"); 1881 mOwner->write(mCodecSpecificData, mCodecSpecificDataSize); 1882 mOwner->endBox(); // avcC 1883 } 1884 1885 mOwner->beginBox("pasp"); 1886 // This is useful if the pixel is not square 1887 mOwner->writeInt32(1 << 16); // hspacing 1888 mOwner->writeInt32(1 << 16); // vspacing 1889 mOwner->endBox(); // pasp 1890 mOwner->endBox(); // mp4v, s263 or avc1 1891 } 1892 mOwner->endBox(); // stsd 1893 1894 mOwner->beginBox("stts"); 1895 mOwner->writeInt32(0); // version=0, flags=0 1896 mOwner->writeInt32(mSttsTableEntries.size()); 1897 for (List<SttsTableEntry>::iterator it = mSttsTableEntries.begin(); 1898 it != mSttsTableEntries.end(); ++it) { 1899 mOwner->writeInt32(it->sampleCount); 1900 int32_t dur = (it->sampleDurationUs * mTimeScale + 5E5) / 1E6; 1901 mOwner->writeInt32(dur); 1902 } 1903 mOwner->endBox(); // stts 1904 1905 if (!mIsAudio) { 1906 mOwner->beginBox("stss"); 1907 mOwner->writeInt32(0); // version=0, flags=0 1908 mOwner->writeInt32(mStssTableEntries.size()); // number of sync frames 1909 for (List<int32_t>::iterator it = mStssTableEntries.begin(); 1910 it != mStssTableEntries.end(); ++it) { 1911 mOwner->writeInt32(*it); 1912 } 1913 mOwner->endBox(); // stss 1914 } 1915 1916 mOwner->beginBox("stsz"); 1917 mOwner->writeInt32(0); // version=0, flags=0 1918 if (mSamplesHaveSameSize) { 1919 List<size_t>::iterator it = mSampleSizes.begin(); 1920 mOwner->writeInt32(*it); // default sample size 1921 } else { 1922 mOwner->writeInt32(0); 1923 } 1924 mOwner->writeInt32(mNumSamples); 1925 if (!mSamplesHaveSameSize) { 1926 for (List<size_t>::iterator it = mSampleSizes.begin(); 1927 it != mSampleSizes.end(); ++it) { 1928 mOwner->writeInt32(*it); 1929 } 1930 } 1931 mOwner->endBox(); // stsz 1932 1933 mOwner->beginBox("stsc"); 1934 mOwner->writeInt32(0); // version=0, flags=0 1935 mOwner->writeInt32(mStscTableEntries.size()); 1936 for (List<StscTableEntry>::iterator it = mStscTableEntries.begin(); 1937 it != mStscTableEntries.end(); ++it) { 1938 mOwner->writeInt32(it->firstChunk); 1939 mOwner->writeInt32(it->samplesPerChunk); 1940 mOwner->writeInt32(it->sampleDescriptionId); 1941 } 1942 mOwner->endBox(); // stsc 1943 mOwner->beginBox(use32BitOffset? "stco": "co64"); 1944 mOwner->writeInt32(0); // version=0, flags=0 1945 mOwner->writeInt32(mChunkOffsets.size()); 1946 for (List<off_t>::iterator it = mChunkOffsets.begin(); 1947 it != mChunkOffsets.end(); ++it) { 1948 if (use32BitOffset) { 1949 mOwner->writeInt32(static_cast<int32_t>(*it)); 1950 } else { 1951 mOwner->writeInt64((*it)); 1952 } 1953 } 1954 mOwner->endBox(); // stco or co64 1955 1956 mOwner->endBox(); // stbl 1957 mOwner->endBox(); // minf 1958 mOwner->endBox(); // mdia 1959 mOwner->endBox(); // trak 1960} 1961 1962} // namespace android 1963