MPEG4Writer.cpp revision 30ab66297501757d745b9ae10da61adcd891f497
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#include <arpa/inet.h> 18 19#include <ctype.h> 20#include <pthread.h> 21 22#include <media/stagefright/MPEG4Writer.h> 23#include <media/stagefright/MediaBuffer.h> 24#include <media/stagefright/MetaData.h> 25#include <media/stagefright/MediaDebug.h> 26#include <media/stagefright/MediaDefs.h> 27#include <media/stagefright/MediaSource.h> 28#include <media/stagefright/Utils.h> 29 30namespace android { 31 32class MPEG4Writer::Track { 33public: 34 Track(MPEG4Writer *owner, const sp<MediaSource> &source); 35 ~Track(); 36 37 status_t start(); 38 void stop(); 39 bool reachedEOS(); 40 41 int64_t getDuration() const; 42 void writeTrackHeader(int32_t trackID); 43 44private: 45 MPEG4Writer *mOwner; 46 sp<MetaData> mMeta; 47 sp<MediaSource> mSource; 48 volatile bool mDone; 49 50 pthread_t mThread; 51 52 struct SampleInfo { 53 size_t size; 54 off_t offset; 55 int64_t timestamp; 56 }; 57 List<SampleInfo> mSampleInfos; 58 59 void *mCodecSpecificData; 60 size_t mCodecSpecificDataSize; 61 62 bool mReachedEOS; 63 64 static void *ThreadWrapper(void *me); 65 void threadEntry(); 66 67 Track(const Track &); 68 Track &operator=(const Track &); 69}; 70 71MPEG4Writer::MPEG4Writer(const char *filename) 72 : mFile(fopen(filename, "wb")), 73 mOffset(0), 74 mMdatOffset(0) { 75 CHECK(mFile != NULL); 76} 77 78MPEG4Writer::MPEG4Writer(int fd) 79 : mFile(fdopen(fd, "wb")), 80 mOffset(0), 81 mMdatOffset(0) { 82 CHECK(mFile != NULL); 83} 84 85MPEG4Writer::~MPEG4Writer() { 86 stop(); 87 88 for (List<Track *>::iterator it = mTracks.begin(); 89 it != mTracks.end(); ++it) { 90 delete *it; 91 } 92 mTracks.clear(); 93} 94 95void MPEG4Writer::addSource(const sp<MediaSource> &source) { 96 Track *track = new Track(this, source); 97 mTracks.push_back(track); 98} 99 100status_t MPEG4Writer::start() { 101 if (mFile == NULL) { 102 return UNKNOWN_ERROR; 103 } 104 105 beginBox("ftyp"); 106 writeFourcc("isom"); 107 writeInt32(0); 108 writeFourcc("isom"); 109 endBox(); 110 111 mMdatOffset = mOffset; 112 write("\x00\x00\x00\x01mdat????????", 16); 113 114 for (List<Track *>::iterator it = mTracks.begin(); 115 it != mTracks.end(); ++it) { 116 status_t err = (*it)->start(); 117 118 if (err != OK) { 119 for (List<Track *>::iterator it2 = mTracks.begin(); 120 it2 != it; ++it2) { 121 (*it2)->stop(); 122 } 123 124 return err; 125 } 126 } 127 128 return OK; 129} 130 131void MPEG4Writer::stop() { 132 if (mFile == NULL) { 133 return; 134 } 135 136 int64_t max_duration = 0; 137 for (List<Track *>::iterator it = mTracks.begin(); 138 it != mTracks.end(); ++it) { 139 (*it)->stop(); 140 141 int64_t duration = (*it)->getDuration(); 142 if (duration > max_duration) { 143 max_duration = duration; 144 } 145 } 146 147 // Fix up the size of the 'mdat' chunk. 148 fseek(mFile, mMdatOffset + 8, SEEK_SET); 149 int64_t size = mOffset - mMdatOffset; 150 size = hton64(size); 151 fwrite(&size, 1, 8, mFile); 152 fseek(mFile, mOffset, SEEK_SET); 153 154 time_t now = time(NULL); 155 156 beginBox("moov"); 157 158 beginBox("mvhd"); 159 writeInt32(0); // version=0, flags=0 160 writeInt32(now); // creation time 161 writeInt32(now); // modification time 162 writeInt32(1000); // timescale 163 writeInt32(max_duration); 164 writeInt32(0x10000); // rate 165 writeInt16(0x100); // volume 166 writeInt16(0); // reserved 167 writeInt32(0); // reserved 168 writeInt32(0); // reserved 169 writeInt32(0x10000); // matrix 170 writeInt32(0); 171 writeInt32(0); 172 writeInt32(0); 173 writeInt32(0x10000); 174 writeInt32(0); 175 writeInt32(0); 176 writeInt32(0); 177 writeInt32(0x40000000); 178 writeInt32(0); // predefined 179 writeInt32(0); // predefined 180 writeInt32(0); // predefined 181 writeInt32(0); // predefined 182 writeInt32(0); // predefined 183 writeInt32(0); // predefined 184 writeInt32(mTracks.size() + 1); // nextTrackID 185 endBox(); // mvhd 186 187 int32_t id = 1; 188 for (List<Track *>::iterator it = mTracks.begin(); 189 it != mTracks.end(); ++it, ++id) { 190 (*it)->writeTrackHeader(id); 191 } 192 endBox(); // moov 193 194 CHECK(mBoxes.empty()); 195 196 fclose(mFile); 197 mFile = NULL; 198} 199 200off_t MPEG4Writer::addSample(MediaBuffer *buffer) { 201 Mutex::Autolock autoLock(mLock); 202 203 off_t old_offset = mOffset; 204 205 fwrite((const uint8_t *)buffer->data() + buffer->range_offset(), 206 1, buffer->range_length(), mFile); 207 208 mOffset += buffer->range_length(); 209 210 return old_offset; 211} 212 213off_t MPEG4Writer::addLengthPrefixedSample(MediaBuffer *buffer) { 214 Mutex::Autolock autoLock(mLock); 215 216 off_t old_offset = mOffset; 217 218 size_t length = buffer->range_length(); 219 CHECK(length < 65536); 220 221 uint8_t x = length >> 8; 222 fwrite(&x, 1, 1, mFile); 223 x = length & 0xff; 224 fwrite(&x, 1, 1, mFile); 225 226 fwrite((const uint8_t *)buffer->data() + buffer->range_offset(), 227 1, length, mFile); 228 229 mOffset += length + 2; 230 231 return old_offset; 232} 233 234void MPEG4Writer::beginBox(const char *fourcc) { 235 CHECK_EQ(strlen(fourcc), 4); 236 237 mBoxes.push_back(mOffset); 238 239 writeInt32(0); 240 writeFourcc(fourcc); 241} 242 243void MPEG4Writer::endBox() { 244 CHECK(!mBoxes.empty()); 245 246 off_t offset = *--mBoxes.end(); 247 mBoxes.erase(--mBoxes.end()); 248 249 fseek(mFile, offset, SEEK_SET); 250 writeInt32(mOffset - offset); 251 mOffset -= 4; 252 fseek(mFile, mOffset, SEEK_SET); 253} 254 255void MPEG4Writer::writeInt8(int8_t x) { 256 fwrite(&x, 1, 1, mFile); 257 ++mOffset; 258} 259 260void MPEG4Writer::writeInt16(int16_t x) { 261 x = htons(x); 262 fwrite(&x, 1, 2, mFile); 263 mOffset += 2; 264} 265 266void MPEG4Writer::writeInt32(int32_t x) { 267 x = htonl(x); 268 fwrite(&x, 1, 4, mFile); 269 mOffset += 4; 270} 271 272void MPEG4Writer::writeInt64(int64_t x) { 273 x = hton64(x); 274 fwrite(&x, 1, 8, mFile); 275 mOffset += 8; 276} 277 278void MPEG4Writer::writeCString(const char *s) { 279 size_t n = strlen(s); 280 281 fwrite(s, 1, n + 1, mFile); 282 mOffset += n + 1; 283} 284 285void MPEG4Writer::writeFourcc(const char *s) { 286 CHECK_EQ(strlen(s), 4); 287 fwrite(s, 1, 4, mFile); 288 mOffset += 4; 289} 290 291void MPEG4Writer::write(const void *data, size_t size) { 292 fwrite(data, 1, size, mFile); 293 mOffset += size; 294} 295 296bool MPEG4Writer::reachedEOS() { 297 bool allDone = true; 298 for (List<Track *>::iterator it = mTracks.begin(); 299 it != mTracks.end(); ++it) { 300 if (!(*it)->reachedEOS()) { 301 allDone = false; 302 break; 303 } 304 } 305 306 return allDone; 307} 308 309//////////////////////////////////////////////////////////////////////////////// 310 311MPEG4Writer::Track::Track( 312 MPEG4Writer *owner, const sp<MediaSource> &source) 313 : mOwner(owner), 314 mMeta(source->getFormat()), 315 mSource(source), 316 mDone(false), 317 mCodecSpecificData(NULL), 318 mCodecSpecificDataSize(0), 319 mReachedEOS(false) { 320} 321 322MPEG4Writer::Track::~Track() { 323 stop(); 324 325 if (mCodecSpecificData != NULL) { 326 free(mCodecSpecificData); 327 mCodecSpecificData = NULL; 328 } 329} 330 331status_t MPEG4Writer::Track::start() { 332 status_t err = mSource->start(); 333 334 if (err != OK) { 335 mDone = mReachedEOS = true; 336 return err; 337 } 338 339 pthread_attr_t attr; 340 pthread_attr_init(&attr); 341 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 342 343 mDone = false; 344 mReachedEOS = false; 345 346 pthread_create(&mThread, &attr, ThreadWrapper, this); 347 pthread_attr_destroy(&attr); 348 349 return OK; 350} 351 352void MPEG4Writer::Track::stop() { 353 if (mDone) { 354 return; 355 } 356 357 mDone = true; 358 359 void *dummy; 360 pthread_join(mThread, &dummy); 361 362 mSource->stop(); 363} 364 365bool MPEG4Writer::Track::reachedEOS() { 366 return mReachedEOS; 367} 368 369// static 370void *MPEG4Writer::Track::ThreadWrapper(void *me) { 371 Track *track = static_cast<Track *>(me); 372 373 track->threadEntry(); 374 375 return NULL; 376} 377 378void MPEG4Writer::Track::threadEntry() { 379 sp<MetaData> meta = mSource->getFormat(); 380 const char *mime; 381 meta->findCString(kKeyMIMEType, &mime); 382 bool is_mpeg4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4); 383 bool is_avc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC); 384 int32_t count = 0; 385 386 MediaBuffer *buffer; 387 while (!mDone && mSource->read(&buffer) == OK) { 388 if (buffer->range_length() == 0) { 389 buffer->release(); 390 buffer = NULL; 391 392 continue; 393 } 394 395 ++count; 396 397 if (is_avc && count < 3) { 398 size_t size = buffer->range_length(); 399 400 switch (count) { 401 case 1: 402 { 403 CHECK_EQ(mCodecSpecificData, NULL); 404 mCodecSpecificData = malloc(size + 8); 405 uint8_t *header = (uint8_t *)mCodecSpecificData; 406 header[0] = 1; 407 header[1] = 0x42; // profile 408 header[2] = 0x80; 409 header[3] = 0x1e; // level 410 header[4] = 0xfc | 3; 411 header[5] = 0xe0 | 1; 412 header[6] = size >> 8; 413 header[7] = size & 0xff; 414 memcpy(&header[8], 415 (const uint8_t *)buffer->data() + buffer->range_offset(), 416 size); 417 418 mCodecSpecificDataSize = size + 8; 419 break; 420 } 421 422 case 2: 423 { 424 size_t offset = mCodecSpecificDataSize; 425 mCodecSpecificDataSize += size + 3; 426 mCodecSpecificData = realloc(mCodecSpecificData, mCodecSpecificDataSize); 427 uint8_t *header = (uint8_t *)mCodecSpecificData; 428 header[offset] = 1; 429 header[offset + 1] = size >> 8; 430 header[offset + 2] = size & 0xff; 431 memcpy(&header[offset + 3], 432 (const uint8_t *)buffer->data() + buffer->range_offset(), 433 size); 434 break; 435 } 436 } 437 438 buffer->release(); 439 buffer = NULL; 440 441 continue; 442 } 443 444 if (mCodecSpecificData == NULL && is_mpeg4) { 445 const uint8_t *data = 446 (const uint8_t *)buffer->data() + buffer->range_offset(); 447 448 const size_t size = buffer->range_length(); 449 450 size_t offset = 0; 451 while (offset + 3 < size) { 452 if (data[offset] == 0x00 && data[offset + 1] == 0x00 453 && data[offset + 2] == 0x01 && data[offset + 3] == 0xb6) { 454 break; 455 } 456 457 ++offset; 458 } 459 460 // CHECK(offset + 3 < size); 461 if (offset + 3 >= size) { 462 // XXX assume the entire first chunk of data is the codec specific 463 // data. 464 offset = size; 465 } 466 467 mCodecSpecificDataSize = offset; 468 mCodecSpecificData = malloc(offset); 469 memcpy(mCodecSpecificData, data, offset); 470 471 buffer->set_range(buffer->range_offset() + offset, size - offset); 472 } 473 474 off_t offset = is_avc ? mOwner->addLengthPrefixedSample(buffer) 475 : mOwner->addSample(buffer); 476 477 SampleInfo info; 478 info.size = is_avc ? buffer->range_length() + 2 : buffer->range_length(); 479 info.offset = offset; 480 481 int64_t timestampUs; 482 CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs)); 483 484 // Our timestamp is in ms. 485 info.timestamp = (timestampUs + 500) / 1000; 486 487 mSampleInfos.push_back(info); 488 489 buffer->release(); 490 buffer = NULL; 491 } 492 493 mReachedEOS = true; 494} 495 496int64_t MPEG4Writer::Track::getDuration() const { 497 return 10000; // XXX 498} 499 500void MPEG4Writer::Track::writeTrackHeader(int32_t trackID) { 501 const char *mime; 502 bool success = mMeta->findCString(kKeyMIMEType, &mime); 503 CHECK(success); 504 505 bool is_audio = !strncasecmp(mime, "audio/", 6); 506 507 time_t now = time(NULL); 508 509 mOwner->beginBox("trak"); 510 511 mOwner->beginBox("tkhd"); 512 mOwner->writeInt32(0); // version=0, flags=0 513 mOwner->writeInt32(now); // creation time 514 mOwner->writeInt32(now); // modification time 515 mOwner->writeInt32(trackID); 516 mOwner->writeInt32(0); // reserved 517 mOwner->writeInt32(getDuration()); 518 mOwner->writeInt32(0); // reserved 519 mOwner->writeInt32(0); // reserved 520 mOwner->writeInt16(0); // layer 521 mOwner->writeInt16(0); // alternate group 522 mOwner->writeInt16(is_audio ? 0x100 : 0); // volume 523 mOwner->writeInt16(0); // reserved 524 525 mOwner->writeInt32(0x10000); // matrix 526 mOwner->writeInt32(0); 527 mOwner->writeInt32(0); 528 mOwner->writeInt32(0); 529 mOwner->writeInt32(0x10000); 530 mOwner->writeInt32(0); 531 mOwner->writeInt32(0); 532 mOwner->writeInt32(0); 533 mOwner->writeInt32(0x40000000); 534 535 if (is_audio) { 536 mOwner->writeInt32(0); 537 mOwner->writeInt32(0); 538 } else { 539 int32_t width, height; 540 bool success = mMeta->findInt32(kKeyWidth, &width); 541 success = success && mMeta->findInt32(kKeyHeight, &height); 542 CHECK(success); 543 544 mOwner->writeInt32(width); 545 mOwner->writeInt32(height); 546 } 547 mOwner->endBox(); // tkhd 548 549 mOwner->beginBox("mdia"); 550 551 mOwner->beginBox("mdhd"); 552 mOwner->writeInt32(0); // version=0, flags=0 553 mOwner->writeInt32(now); // creation time 554 mOwner->writeInt32(now); // modification time 555 mOwner->writeInt32(1000); // timescale 556 mOwner->writeInt32(getDuration()); 557 mOwner->writeInt16(0); // language code XXX 558 mOwner->writeInt16(0); // predefined 559 mOwner->endBox(); 560 561 mOwner->beginBox("hdlr"); 562 mOwner->writeInt32(0); // version=0, flags=0 563 mOwner->writeInt32(0); // predefined 564 mOwner->writeFourcc(is_audio ? "soun" : "vide"); 565 mOwner->writeInt32(0); // reserved 566 mOwner->writeInt32(0); // reserved 567 mOwner->writeInt32(0); // reserved 568 mOwner->writeCString(""); // name 569 mOwner->endBox(); 570 571 mOwner->beginBox("minf"); 572 573 mOwner->beginBox("dinf"); 574 mOwner->beginBox("dref"); 575 mOwner->writeInt32(0); // version=0, flags=0 576 mOwner->writeInt32(1); 577 mOwner->beginBox("url "); 578 mOwner->writeInt32(1); // version=0, flags=1 579 mOwner->endBox(); // url 580 mOwner->endBox(); // dref 581 mOwner->endBox(); // dinf 582 583 if (is_audio) { 584 mOwner->beginBox("smhd"); 585 mOwner->writeInt32(0); // version=0, flags=0 586 mOwner->writeInt16(0); // balance 587 mOwner->writeInt16(0); // reserved 588 mOwner->endBox(); 589 } else { 590 mOwner->beginBox("vmhd"); 591 mOwner->writeInt32(0x00000001); // version=0, flags=1 592 mOwner->writeInt16(0); // graphics mode 593 mOwner->writeInt16(0); // opcolor 594 mOwner->writeInt16(0); 595 mOwner->writeInt16(0); 596 mOwner->endBox(); 597 } 598 mOwner->endBox(); // minf 599 600 mOwner->beginBox("stbl"); 601 602 mOwner->beginBox("stsd"); 603 mOwner->writeInt32(0); // version=0, flags=0 604 mOwner->writeInt32(1); // entry count 605 if (is_audio) { 606 const char *fourcc = NULL; 607 if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, mime)) { 608 fourcc = "samr"; 609 } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_WB, mime)) { 610 fourcc = "sawb"; 611 } else { 612 LOGE("Unknown mime type '%s'.", mime); 613 CHECK(!"should not be here, unknown mime type."); 614 } 615 616 mOwner->beginBox(fourcc); // audio format 617 mOwner->writeInt32(0); // reserved 618 mOwner->writeInt16(0); // reserved 619 mOwner->writeInt16(0); // data ref index 620 mOwner->writeInt32(0); // reserved 621 mOwner->writeInt32(0); // reserved 622 mOwner->writeInt16(2); // channel count 623 mOwner->writeInt16(16); // sample size 624 mOwner->writeInt16(0); // predefined 625 mOwner->writeInt16(0); // reserved 626 627 int32_t samplerate; 628 bool success = mMeta->findInt32(kKeySampleRate, &samplerate); 629 CHECK(success); 630 631 mOwner->writeInt32(samplerate << 16); 632 mOwner->endBox(); 633 } else { 634 if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) { 635 mOwner->beginBox("mp4v"); 636 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) { 637 mOwner->beginBox("s263"); 638 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { 639 mOwner->beginBox("avc1"); 640 } else { 641 LOGE("Unknown mime type '%s'.", mime); 642 CHECK(!"should not be here, unknown mime type."); 643 } 644 645 mOwner->writeInt32(0); // reserved 646 mOwner->writeInt16(0); // reserved 647 mOwner->writeInt16(0); // data ref index 648 mOwner->writeInt16(0); // predefined 649 mOwner->writeInt16(0); // reserved 650 mOwner->writeInt32(0); // predefined 651 mOwner->writeInt32(0); // predefined 652 mOwner->writeInt32(0); // predefined 653 654 int32_t width, height; 655 bool success = mMeta->findInt32(kKeyWidth, &width); 656 success = success && mMeta->findInt32(kKeyHeight, &height); 657 CHECK(success); 658 659 mOwner->writeInt16(width); 660 mOwner->writeInt16(height); 661 mOwner->writeInt32(0x480000); // horiz resolution 662 mOwner->writeInt32(0x480000); // vert resolution 663 mOwner->writeInt32(0); // reserved 664 mOwner->writeInt16(1); // frame count 665 mOwner->write(" ", 32); 666 mOwner->writeInt16(0x18); // depth 667 mOwner->writeInt16(-1); // predefined 668 669 CHECK(23 + mCodecSpecificDataSize < 128); 670 671 if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) { 672 mOwner->beginBox("esds"); 673 674 mOwner->writeInt32(0); // version=0, flags=0 675 676 mOwner->writeInt8(0x03); // ES_DescrTag 677 mOwner->writeInt8(23 + mCodecSpecificDataSize); 678 mOwner->writeInt16(0x0000); // ES_ID 679 mOwner->writeInt8(0x1f); 680 681 mOwner->writeInt8(0x04); // DecoderConfigDescrTag 682 mOwner->writeInt8(15 + mCodecSpecificDataSize); 683 mOwner->writeInt8(0x20); // objectTypeIndication ISO/IEC 14492-2 684 mOwner->writeInt8(0x11); // streamType VisualStream 685 686 static const uint8_t kData[] = { 687 0x01, 0x77, 0x00, 688 0x00, 0x03, 0xe8, 0x00, 689 0x00, 0x03, 0xe8, 0x00 690 }; 691 mOwner->write(kData, sizeof(kData)); 692 693 mOwner->writeInt8(0x05); // DecoderSpecificInfoTag 694 695 mOwner->writeInt8(mCodecSpecificDataSize); 696 mOwner->write(mCodecSpecificData, mCodecSpecificDataSize); 697 698 static const uint8_t kData2[] = { 699 0x06, // SLConfigDescriptorTag 700 0x01, 701 0x02 702 }; 703 mOwner->write(kData2, sizeof(kData2)); 704 705 mOwner->endBox(); // esds 706 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) { 707 mOwner->beginBox("d263"); 708 709 mOwner->writeInt32(0); // vendor 710 mOwner->writeInt8(0); // decoder version 711 mOwner->writeInt8(10); // level: 10 712 mOwner->writeInt8(0); // profile: 0 713 714 mOwner->endBox(); // d263 715 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { 716 mOwner->beginBox("avcC"); 717 mOwner->write(mCodecSpecificData, mCodecSpecificDataSize); 718 mOwner->endBox(); // avcC 719 } 720 721 mOwner->endBox(); // mp4v, s263 or avc1 722 } 723 mOwner->endBox(); // stsd 724 725 mOwner->beginBox("stts"); 726 mOwner->writeInt32(0); // version=0, flags=0 727 mOwner->writeInt32(mSampleInfos.size() - 1); 728 729 List<SampleInfo>::iterator it = mSampleInfos.begin(); 730 int64_t last = (*it).timestamp; 731 ++it; 732 while (it != mSampleInfos.end()) { 733 mOwner->writeInt32(1); 734 mOwner->writeInt32((*it).timestamp - last); 735 736 last = (*it).timestamp; 737 738 ++it; 739 } 740 mOwner->endBox(); // stts 741 742 mOwner->beginBox("stsz"); 743 mOwner->writeInt32(0); // version=0, flags=0 744 mOwner->writeInt32(0); // default sample size 745 mOwner->writeInt32(mSampleInfos.size()); 746 for (List<SampleInfo>::iterator it = mSampleInfos.begin(); 747 it != mSampleInfos.end(); ++it) { 748 mOwner->writeInt32((*it).size); 749 } 750 mOwner->endBox(); // stsz 751 752 mOwner->beginBox("stsc"); 753 mOwner->writeInt32(0); // version=0, flags=0 754 mOwner->writeInt32(mSampleInfos.size()); 755 int32_t n = 1; 756 for (List<SampleInfo>::iterator it = mSampleInfos.begin(); 757 it != mSampleInfos.end(); ++it, ++n) { 758 mOwner->writeInt32(n); 759 mOwner->writeInt32(1); 760 mOwner->writeInt32(1); 761 } 762 mOwner->endBox(); // stsc 763 764 mOwner->beginBox("co64"); 765 mOwner->writeInt32(0); // version=0, flags=0 766 mOwner->writeInt32(mSampleInfos.size()); 767 for (List<SampleInfo>::iterator it = mSampleInfos.begin(); 768 it != mSampleInfos.end(); ++it, ++n) { 769 mOwner->writeInt64((*it).offset); 770 } 771 mOwner->endBox(); // co64 772 773 mOwner->endBox(); // stbl 774 mOwner->endBox(); // mdia 775 mOwner->endBox(); // trak 776} 777 778} // namespace android 779