00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "QuickTimeFileSink.hh"
00022 #include "QuickTimeGenericRTPSource.hh"
00023 #include "GroupsockHelper.hh"
00024 #include "InputFile.hh"
00025 #include "OutputFile.hh"
00026 #include "H263plusVideoRTPSource.hh"
00027 #include "MPEG4GenericRTPSource.hh"
00028 #include "MPEG4LATMAudioRTPSource.hh"
00029 #include "Base64.hh"
00030
00031 #include <ctype.h>
00032
00033 #define fourChar(x,y,z,w) ( ((x)<<24)|((y)<<16)|((z)<<8)|(w) )
00034
00035 #define H264_IDR_FRAME 0x65 //bit 8 == 0, bits 7-6 (ref) == 3, bits 5-0 (type) == 5
00036
00038
00039
00040 class ChunkDescriptor {
00041 public:
00042 ChunkDescriptor(int64_t offsetInFile, unsigned size,
00043 unsigned frameSize, unsigned frameDuration,
00044 struct timeval presentationTime);
00045 virtual ~ChunkDescriptor();
00046
00047 ChunkDescriptor* extendChunk(int64_t newOffsetInFile, unsigned newSize,
00048 unsigned newFrameSize,
00049 unsigned newFrameDuration,
00050 struct timeval newPresentationTime);
00051
00052 public:
00053 ChunkDescriptor* fNextChunk;
00054 int64_t fOffsetInFile;
00055 unsigned fNumFrames;
00056 unsigned fFrameSize;
00057 unsigned fFrameDuration;
00058 struct timeval fPresentationTime;
00059 };
00060
00061 class SubsessionBuffer {
00062 public:
00063 SubsessionBuffer(unsigned bufferSize)
00064 : fBufferSize(bufferSize) {
00065 reset();
00066 fData = new unsigned char[bufferSize];
00067 }
00068 virtual ~SubsessionBuffer() { delete[] fData; }
00069 void reset() { fBytesInUse = 0; }
00070 void addBytes(unsigned numBytes) { fBytesInUse += numBytes; }
00071
00072 unsigned char* dataStart() { return &fData[0]; }
00073 unsigned char* dataEnd() { return &fData[fBytesInUse]; }
00074 unsigned bytesInUse() const { return fBytesInUse; }
00075 unsigned bytesAvailable() const { return fBufferSize - fBytesInUse; }
00076
00077 void setPresentationTime(struct timeval const& presentationTime) {
00078 fPresentationTime = presentationTime;
00079 }
00080 struct timeval const& presentationTime() const {return fPresentationTime;}
00081
00082 private:
00083 unsigned fBufferSize;
00084 struct timeval fPresentationTime;
00085 unsigned char* fData;
00086 unsigned fBytesInUse;
00087 };
00088
00089 class SyncFrame {
00090 public:
00091 SyncFrame(unsigned frameNum);
00092 virtual ~SyncFrame();
00093
00094 public:
00095 class SyncFrame *nextSyncFrame;
00096 unsigned sfFrameNum;
00097 };
00098
00099
00100 class Count64 {
00101 public:
00102 Count64()
00103 : hi(0), lo(0) {
00104 }
00105
00106 void operator+=(unsigned arg);
00107
00108 u_int32_t hi, lo;
00109 };
00110
00111 class SubsessionIOState {
00112 public:
00113 SubsessionIOState(QuickTimeFileSink& sink, MediaSubsession& subsession);
00114 virtual ~SubsessionIOState();
00115
00116 Boolean setQTstate();
00117 void setFinalQTstate();
00118
00119 void afterGettingFrame(unsigned packetDataSize,
00120 struct timeval presentationTime);
00121 void onSourceClosure();
00122
00123 Boolean syncOK(struct timeval presentationTime);
00124
00125
00126 static void setHintTrack(SubsessionIOState* hintedTrack,
00127 SubsessionIOState* hintTrack);
00128 Boolean isHintTrack() const { return fTrackHintedByUs != NULL; }
00129 Boolean hasHintTrack() const { return fHintTrackForUs != NULL; }
00130
00131 UsageEnvironment& envir() const { return fOurSink.envir(); }
00132
00133 public:
00134 static unsigned fCurrentTrackNumber;
00135 unsigned fTrackID;
00136 SubsessionIOState* fHintTrackForUs; SubsessionIOState* fTrackHintedByUs;
00137
00138 SubsessionBuffer *fBuffer, *fPrevBuffer;
00139 QuickTimeFileSink& fOurSink;
00140 MediaSubsession& fOurSubsession;
00141
00142 unsigned short fLastPacketRTPSeqNum;
00143 Boolean fOurSourceIsActive;
00144
00145 Boolean fHaveBeenSynced;
00146 struct timeval fSyncTime;
00147
00148 Boolean fQTEnableTrack;
00149 unsigned fQTcomponentSubtype;
00150 char const* fQTcomponentName;
00151 typedef unsigned (QuickTimeFileSink::*atomCreationFunc)();
00152 atomCreationFunc fQTMediaInformationAtomCreator;
00153 atomCreationFunc fQTMediaDataAtomCreator;
00154 char const* fQTAudioDataType;
00155 unsigned short fQTSoundSampleVersion;
00156 unsigned fQTTimeScale;
00157 unsigned fQTTimeUnitsPerSample;
00158 unsigned fQTBytesPerFrame;
00159 unsigned fQTSamplesPerFrame;
00160
00161
00162 unsigned fQTTotNumSamples;
00163 unsigned fQTDurationM;
00164 unsigned fQTDurationT;
00165 int64_t fTKHD_durationPosn;
00166
00167 unsigned fQTInitialOffsetDuration;
00168
00169
00170 ChunkDescriptor *fHeadChunk, *fTailChunk;
00171 unsigned fNumChunks;
00172 SyncFrame *fHeadSyncFrame, *fTailSyncFrame;
00173
00174
00175 struct hinf {
00176 Count64 trpy;
00177 Count64 nump;
00178 Count64 tpyl;
00179
00180 Count64 dmed;
00181 Count64 dimm;
00182
00183
00184 unsigned pmax;
00185 unsigned dmax;
00186 } fHINF;
00187
00188 private:
00189 void useFrame(SubsessionBuffer& buffer);
00190 void useFrameForHinting(unsigned frameSize,
00191 struct timeval presentationTime,
00192 unsigned startSampleNumber);
00193
00194
00195 unsigned useFrame1(unsigned sourceDataSize,
00196 struct timeval presentationTime,
00197 unsigned frameDuration, int64_t destFileOffset);
00198
00199
00200 private:
00201
00202 struct {
00203 unsigned frameSize;
00204 struct timeval presentationTime;
00205 int64_t destFileOffset;
00206
00207
00208 unsigned startSampleNumber;
00209 unsigned short seqNum;
00210 unsigned rtpHeader;
00211 unsigned char numSpecialHeaders;
00212 unsigned specialHeaderBytesLength;
00213 unsigned char specialHeaderBytes[SPECIAL_HEADER_BUFFER_SIZE];
00214 unsigned packetSizes[256];
00215 } fPrevFrameState;
00216 };
00217
00218
00220
00221 QuickTimeFileSink::QuickTimeFileSink(UsageEnvironment& env,
00222 MediaSession& inputSession,
00223 char const* outputFileName,
00224 unsigned bufferSize,
00225 unsigned short movieWidth,
00226 unsigned short movieHeight,
00227 unsigned movieFPS,
00228 Boolean packetLossCompensate,
00229 Boolean syncStreams,
00230 Boolean generateHintTracks,
00231 Boolean generateMP4Format)
00232 : Medium(env), fInputSession(inputSession),
00233 fBufferSize(bufferSize), fPacketLossCompensate(packetLossCompensate),
00234 fSyncStreams(syncStreams), fGenerateMP4Format(generateMP4Format),
00235 fAreCurrentlyBeingPlayed(False),
00236 fLargestRTPtimestampFrequency(0),
00237 fNumSubsessions(0), fNumSyncedSubsessions(0),
00238 fHaveCompletedOutputFile(False),
00239 fMovieWidth(movieWidth), fMovieHeight(movieHeight),
00240 fMovieFPS(movieFPS), fMaxTrackDurationM(0) {
00241 fOutFid = OpenOutputFile(env, outputFileName);
00242 if (fOutFid == NULL) return;
00243
00244 fNewestSyncTime.tv_sec = fNewestSyncTime.tv_usec = 0;
00245 fFirstDataTime.tv_sec = fFirstDataTime.tv_usec = (unsigned)(~0);
00246
00247
00248 MediaSubsessionIterator iter(fInputSession);
00249 MediaSubsession* subsession;
00250 while ((subsession = iter.next()) != NULL) {
00251
00252 FramedSource* subsessionSource = subsession->readSource();
00253 if (subsessionSource == NULL) continue;
00254
00255
00256
00257
00258 if (subsession->videoWidth() != 0) {
00259 fMovieWidth = subsession->videoWidth();
00260 }
00261 if (subsession->videoHeight() != 0) {
00262 fMovieHeight = subsession->videoHeight();
00263 }
00264 if (subsession->videoFPS() != 0) {
00265 fMovieFPS = subsession->videoFPS();
00266 }
00267
00268 SubsessionIOState* ioState
00269 = new SubsessionIOState(*this, *subsession);
00270 if (ioState == NULL || !ioState->setQTstate()) {
00271
00272 delete ioState; ioState = NULL;
00273 continue;
00274 }
00275 subsession->miscPtr = (void*)ioState;
00276
00277 if (generateHintTracks) {
00278
00279 SubsessionIOState* hintTrack
00280 = new SubsessionIOState(*this, *subsession);
00281 SubsessionIOState::setHintTrack(ioState, hintTrack);
00282 if (!hintTrack->setQTstate()) {
00283 delete hintTrack;
00284 SubsessionIOState::setHintTrack(ioState, NULL);
00285 }
00286 }
00287
00288
00289 if (subsession->rtcpInstance() != NULL) {
00290 subsession->rtcpInstance()->setByeHandler(onRTCPBye, ioState);
00291 }
00292
00293 unsigned rtpTimestampFrequency = subsession->rtpTimestampFrequency();
00294 if (rtpTimestampFrequency > fLargestRTPtimestampFrequency) {
00295 fLargestRTPtimestampFrequency = rtpTimestampFrequency;
00296 }
00297
00298 ++fNumSubsessions;
00299 }
00300
00301
00302
00303
00304 gettimeofday(&fStartTime, NULL);
00305 fAppleCreationTime = fStartTime.tv_sec - 0x83dac000;
00306
00307
00308
00309
00310 fMDATposition = TellFile64(fOutFid);
00311 addAtomHeader64("mdat");
00312
00313 fMDATposition += 8;
00314 }
00315
00316 QuickTimeFileSink::~QuickTimeFileSink() {
00317 completeOutputFile();
00318
00319
00320 MediaSubsessionIterator iter(fInputSession);
00321 MediaSubsession* subsession;
00322 while ((subsession = iter.next()) != NULL) {
00323 SubsessionIOState* ioState
00324 = (SubsessionIOState*)(subsession->miscPtr);
00325 if (ioState == NULL) continue;
00326
00327 delete ioState->fHintTrackForUs;
00328 delete ioState;
00329 }
00330
00331
00332 CloseOutputFile(fOutFid);
00333 }
00334
00335 QuickTimeFileSink*
00336 QuickTimeFileSink::createNew(UsageEnvironment& env,
00337 MediaSession& inputSession,
00338 char const* outputFileName,
00339 unsigned bufferSize,
00340 unsigned short movieWidth,
00341 unsigned short movieHeight,
00342 unsigned movieFPS,
00343 Boolean packetLossCompensate,
00344 Boolean syncStreams,
00345 Boolean generateHintTracks,
00346 Boolean generateMP4Format) {
00347 QuickTimeFileSink* newSink =
00348 new QuickTimeFileSink(env, inputSession, outputFileName, bufferSize, movieWidth, movieHeight, movieFPS,
00349 packetLossCompensate, syncStreams, generateHintTracks, generateMP4Format);
00350 if (newSink == NULL || newSink->fOutFid == NULL) {
00351 Medium::close(newSink);
00352 return NULL;
00353 }
00354
00355 return newSink;
00356 }
00357
00358 Boolean QuickTimeFileSink::startPlaying(afterPlayingFunc* afterFunc,
00359 void* afterClientData) {
00360
00361 if (fAreCurrentlyBeingPlayed) {
00362 envir().setResultMsg("This sink has already been played");
00363 return False;
00364 }
00365
00366 fAreCurrentlyBeingPlayed = True;
00367 fAfterFunc = afterFunc;
00368 fAfterClientData = afterClientData;
00369
00370 return continuePlaying();
00371 }
00372
00373 Boolean QuickTimeFileSink::continuePlaying() {
00374
00375
00376 Boolean haveActiveSubsessions = False;
00377 MediaSubsessionIterator iter(fInputSession);
00378 MediaSubsession* subsession;
00379 while ((subsession = iter.next()) != NULL) {
00380 FramedSource* subsessionSource = subsession->readSource();
00381 if (subsessionSource == NULL) continue;
00382
00383 if (subsessionSource->isCurrentlyAwaitingData()) continue;
00384
00385 SubsessionIOState* ioState
00386 = (SubsessionIOState*)(subsession->miscPtr);
00387 if (ioState == NULL) continue;
00388
00389 haveActiveSubsessions = True;
00390 unsigned char* toPtr = ioState->fBuffer->dataEnd();
00391 unsigned toSize = ioState->fBuffer->bytesAvailable();
00392 subsessionSource->getNextFrame(toPtr, toSize,
00393 afterGettingFrame, ioState,
00394 onSourceClosure, ioState);
00395 }
00396 if (!haveActiveSubsessions) {
00397 envir().setResultMsg("No subsessions are currently active");
00398 return False;
00399 }
00400
00401 return True;
00402 }
00403
00404 void QuickTimeFileSink
00405 ::afterGettingFrame(void* clientData, unsigned packetDataSize,
00406 unsigned ,
00407 struct timeval presentationTime,
00408 unsigned ) {
00409 SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00410 if (!ioState->syncOK(presentationTime)) {
00411
00412 ioState->fOurSink.continuePlaying();
00413 return;
00414 }
00415 ioState->afterGettingFrame(packetDataSize, presentationTime);
00416 }
00417
00418 void QuickTimeFileSink::onSourceClosure(void* clientData) {
00419 SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00420 ioState->onSourceClosure();
00421 }
00422
00423 void QuickTimeFileSink::onSourceClosure1() {
00424
00425
00426 MediaSubsessionIterator iter(fInputSession);
00427 MediaSubsession* subsession;
00428 while ((subsession = iter.next()) != NULL) {
00429 SubsessionIOState* ioState
00430 = (SubsessionIOState*)(subsession->miscPtr);
00431 if (ioState == NULL) continue;
00432
00433 if (ioState->fOurSourceIsActive) return;
00434 }
00435
00436 completeOutputFile();
00437
00438
00439 if (fAfterFunc != NULL) {
00440 (*fAfterFunc)(fAfterClientData);
00441 }
00442 }
00443
00444 void QuickTimeFileSink::onRTCPBye(void* clientData) {
00445 SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00446
00447 struct timeval timeNow;
00448 gettimeofday(&timeNow, NULL);
00449 unsigned secsDiff
00450 = timeNow.tv_sec - ioState->fOurSink.fStartTime.tv_sec;
00451
00452 MediaSubsession& subsession = ioState->fOurSubsession;
00453 ioState->envir() << "Received RTCP \"BYE\" on \""
00454 << subsession.mediumName()
00455 << "/" << subsession.codecName()
00456 << "\" subsession (after "
00457 << secsDiff << " seconds)\n";
00458
00459
00460 ioState->onSourceClosure();
00461 }
00462
00463 static Boolean timevalGE(struct timeval const& tv1,
00464 struct timeval const& tv2) {
00465 return (unsigned)tv1.tv_sec > (unsigned)tv2.tv_sec
00466 || (tv1.tv_sec == tv2.tv_sec
00467 && (unsigned)tv1.tv_usec >= (unsigned)tv2.tv_usec);
00468 }
00469
00470 void QuickTimeFileSink::completeOutputFile() {
00471 if (fHaveCompletedOutputFile || fOutFid == NULL) return;
00472
00473
00474
00475 int64_t curFileSize = TellFile64(fOutFid);
00476 setWord64(fMDATposition, (u_int64_t)curFileSize);
00477
00478
00479 MediaSubsessionIterator iter(fInputSession);
00480 MediaSubsession* subsession;
00481 while ((subsession = iter.next()) != NULL) {
00482 SubsessionIOState* ioState
00483 = (SubsessionIOState*)(subsession->miscPtr);
00484 if (ioState == NULL) continue;
00485
00486 ChunkDescriptor* const headChunk = ioState->fHeadChunk;
00487 if (headChunk != NULL
00488 && timevalGE(fFirstDataTime, headChunk->fPresentationTime)) {
00489 fFirstDataTime = headChunk->fPresentationTime;
00490 }
00491 }
00492
00493
00494 iter.reset();
00495 while ((subsession = iter.next()) != NULL) {
00496 SubsessionIOState* ioState
00497 = (SubsessionIOState*)(subsession->miscPtr);
00498 if (ioState == NULL) continue;
00499
00500 ioState->setFinalQTstate();
00501
00502 if (ioState->hasHintTrack()) {
00503 ioState->fHintTrackForUs->setFinalQTstate();
00504 }
00505 }
00506
00507 if (fGenerateMP4Format) {
00508
00509 addAtom_ftyp();
00510 }
00511
00512
00513 addAtom_moov();
00514
00515
00516 fHaveCompletedOutputFile = True;
00517 }
00518
00519
00521
00522 unsigned SubsessionIOState::fCurrentTrackNumber = 0;
00523
00524 SubsessionIOState::SubsessionIOState(QuickTimeFileSink& sink,
00525 MediaSubsession& subsession)
00526 : fHintTrackForUs(NULL), fTrackHintedByUs(NULL),
00527 fOurSink(sink), fOurSubsession(subsession),
00528 fLastPacketRTPSeqNum(0), fHaveBeenSynced(False), fQTTotNumSamples(0),
00529 fHeadChunk(NULL), fTailChunk(NULL), fNumChunks(0),
00530 fHeadSyncFrame(NULL), fTailSyncFrame(NULL) {
00531 fTrackID = ++fCurrentTrackNumber;
00532
00533 fBuffer = new SubsessionBuffer(fOurSink.fBufferSize);
00534 fPrevBuffer = sink.fPacketLossCompensate
00535 ? new SubsessionBuffer(fOurSink.fBufferSize) : NULL;
00536
00537 FramedSource* subsessionSource = subsession.readSource();
00538 fOurSourceIsActive = subsessionSource != NULL;
00539
00540 fPrevFrameState.presentationTime.tv_sec = 0;
00541 fPrevFrameState.presentationTime.tv_usec = 0;
00542 fPrevFrameState.seqNum = 0;
00543 }
00544
00545 SubsessionIOState::~SubsessionIOState() {
00546 delete fBuffer; delete fPrevBuffer;
00547 delete fHeadChunk; delete fHeadSyncFrame;
00548 }
00549
00550 Boolean SubsessionIOState::setQTstate() {
00551 char const* noCodecWarning1 = "Warning: We don't implement a QuickTime ";
00552 char const* noCodecWarning2 = " Media Data Type for the \"";
00553 char const* noCodecWarning3 = "\" track, so we'll insert a dummy \"????\" Media Data Atom instead. A separate, codec-specific editing pass will be needed before this track can be played.\n";
00554
00555 do {
00556 fQTEnableTrack = True;
00557 fQTTimeScale = fOurSubsession.rtpTimestampFrequency();
00558 fQTTimeUnitsPerSample = 1;
00559 fQTBytesPerFrame = 0;
00560
00561 fQTSamplesPerFrame = 1;
00562
00563
00564
00565 if (isHintTrack()) {
00566
00567 fQTEnableTrack = False;
00568 fQTcomponentSubtype = fourChar('h','i','n','t');
00569 fQTcomponentName = "hint media handler";
00570 fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_gmhd;
00571 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_rtp;
00572 } else if (strcmp(fOurSubsession.mediumName(), "audio") == 0) {
00573 fQTcomponentSubtype = fourChar('s','o','u','n');
00574 fQTcomponentName = "Apple Sound Media Handler";
00575 fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_smhd;
00576 fQTMediaDataAtomCreator
00577 = &QuickTimeFileSink::addAtom_soundMediaGeneral;
00578 fQTSoundSampleVersion = 0;
00579
00580
00581 if (strcmp(fOurSubsession.codecName(), "X-QT") == 0 ||
00582 strcmp(fOurSubsession.codecName(), "X-QUICKTIME") == 0) {
00583 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_genericMedia;
00584 } else if (strcmp(fOurSubsession.codecName(), "PCMU") == 0) {
00585 fQTAudioDataType = "ulaw";
00586 fQTBytesPerFrame = 1;
00587 } else if (strcmp(fOurSubsession.codecName(), "GSM") == 0) {
00588 fQTAudioDataType = "agsm";
00589 fQTBytesPerFrame = 33;
00590 fQTSamplesPerFrame = 160;
00591 } else if (strcmp(fOurSubsession.codecName(), "PCMA") == 0) {
00592 fQTAudioDataType = "alaw";
00593 fQTBytesPerFrame = 1;
00594 } else if (strcmp(fOurSubsession.codecName(), "QCELP") == 0) {
00595 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_Qclp;
00596 fQTSamplesPerFrame = 160;
00597 } else if (strcmp(fOurSubsession.codecName(), "MPEG4-GENERIC") == 0 ||
00598 strcmp(fOurSubsession.codecName(), "MP4A-LATM") == 0) {
00599 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_mp4a;
00600 fQTTimeUnitsPerSample = 1024;
00601
00602
00603 unsigned frequencyFromConfig
00604 = samplingFrequencyFromAudioSpecificConfig(fOurSubsession.fmtp_config());
00605 if (frequencyFromConfig != 0) fQTTimeScale = frequencyFromConfig;
00606 } else {
00607 envir() << noCodecWarning1 << "Audio" << noCodecWarning2
00608 << fOurSubsession.codecName() << noCodecWarning3;
00609 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_dummy;
00610 fQTEnableTrack = False;
00611 }
00612 } else if (strcmp(fOurSubsession.mediumName(), "video") == 0) {
00613 fQTcomponentSubtype = fourChar('v','i','d','e');
00614 fQTcomponentName = "Apple Video Media Handler";
00615 fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_vmhd;
00616
00617
00618 if (strcmp(fOurSubsession.codecName(), "X-QT") == 0 ||
00619 strcmp(fOurSubsession.codecName(), "X-QUICKTIME") == 0) {
00620 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_genericMedia;
00621 } else if (strcmp(fOurSubsession.codecName(), "H263-1998") == 0 ||
00622 strcmp(fOurSubsession.codecName(), "H263-2000") == 0) {
00623 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_h263;
00624 fQTTimeScale = 600;
00625 fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00626 } else if (strcmp(fOurSubsession.codecName(), "H264") == 0) {
00627 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_avc1;
00628 fQTTimeScale = 600;
00629 fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00630 } else if (strcmp(fOurSubsession.codecName(), "MP4V-ES") == 0) {
00631 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_mp4v;
00632 fQTTimeScale = 600;
00633 fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00634 } else {
00635 envir() << noCodecWarning1 << "Video" << noCodecWarning2
00636 << fOurSubsession.codecName() << noCodecWarning3;
00637 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_dummy;
00638 fQTEnableTrack = False;
00639 }
00640 } else {
00641 envir() << "Warning: We don't implement a QuickTime Media Handler for media type \""
00642 << fOurSubsession.mediumName() << "\"";
00643 break;
00644 }
00645
00646 #ifdef QT_SUPPORT_PARTIALLY_ONLY
00647 envir() << "Warning: We don't have sufficient codec-specific information (e.g., sample sizes) to fully generate the \""
00648 << fOurSubsession.mediumName() << "/" << fOurSubsession.codecName()
00649 << "\" track, so we'll disable this track in the movie. A separate, codec-specific editing pass will be needed before this track can be played\n";
00650 fQTEnableTrack = False;
00651 #endif
00652
00653 return True;
00654 } while (0);
00655
00656 envir() << ", so a track for the \"" << fOurSubsession.mediumName()
00657 << "/" << fOurSubsession.codecName()
00658 << "\" subsession will not be included in the output QuickTime file\n";
00659 return False;
00660 }
00661
00662 void SubsessionIOState::setFinalQTstate() {
00663
00664 fQTDurationT = 0;
00665
00666 ChunkDescriptor* chunk = fHeadChunk;
00667 while (chunk != NULL) {
00668 unsigned const numFrames = chunk->fNumFrames;
00669 unsigned const dur = numFrames*chunk->fFrameDuration;
00670 fQTDurationT += dur;
00671
00672 chunk = chunk->fNextChunk;
00673 }
00674
00675
00676 double scaleFactor = fOurSink.movieTimeScale()/(double)fQTTimeScale;
00677 fQTDurationM = (unsigned)(fQTDurationT*scaleFactor);
00678
00679 if (fQTDurationM > fOurSink.fMaxTrackDurationM) {
00680 fOurSink.fMaxTrackDurationM = fQTDurationM;
00681 }
00682 }
00683
00684 void SubsessionIOState::afterGettingFrame(unsigned packetDataSize,
00685 struct timeval presentationTime) {
00686
00687
00688 unsigned short rtpSeqNum
00689 = fOurSubsession.rtpSource()->curPacketRTPSeqNum();
00690 if (fOurSink.fPacketLossCompensate && fPrevBuffer->bytesInUse() > 0) {
00691 short seqNumGap = rtpSeqNum - fLastPacketRTPSeqNum;
00692 for (short i = 1; i < seqNumGap; ++i) {
00693
00694 useFrame(*fPrevBuffer);
00695 }
00696 }
00697 fLastPacketRTPSeqNum = rtpSeqNum;
00698
00699
00700 if (fBuffer->bytesInUse() == 0) {
00701 fBuffer->setPresentationTime(presentationTime);
00702 }
00703 fBuffer->addBytes(packetDataSize);
00704
00705
00706
00707 if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_genericMedia){
00708 QuickTimeGenericRTPSource* rtpSource
00709 = (QuickTimeGenericRTPSource*)fOurSubsession.rtpSource();
00710 QuickTimeGenericRTPSource::QTState& qtState = rtpSource->qtState;
00711 fQTTimeScale = qtState.timescale;
00712 if (qtState.width != 0) {
00713 fOurSink.fMovieWidth = qtState.width;
00714 }
00715 if (qtState.height != 0) {
00716 fOurSink.fMovieHeight = qtState.height;
00717 }
00718
00719
00720
00721 if (qtState.sdAtomSize >= 8) {
00722 char const* atom = qtState.sdAtom;
00723 unsigned mediaType = fourChar(atom[4],atom[5],atom[6],atom[7]);
00724 switch (mediaType) {
00725 case fourChar('a','g','s','m'): {
00726 fQTBytesPerFrame = 33;
00727 fQTSamplesPerFrame = 160;
00728 break;
00729 }
00730 case fourChar('Q','c','l','p'): {
00731 fQTBytesPerFrame = 35;
00732 fQTSamplesPerFrame = 160;
00733 break;
00734 }
00735 case fourChar('H','c','l','p'): {
00736 fQTBytesPerFrame = 17;
00737 fQTSamplesPerFrame = 160;
00738 break;
00739 }
00740 case fourChar('h','2','6','3'): {
00741 fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00742 break;
00743 }
00744 }
00745 }
00746 } else if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_Qclp) {
00747
00748
00749
00750
00751 fQTBytesPerFrame = packetDataSize;
00752 }
00753
00754 useFrame(*fBuffer);
00755 if (fOurSink.fPacketLossCompensate) {
00756
00757 SubsessionBuffer* tmp = fPrevBuffer;
00758 fPrevBuffer = fBuffer;
00759 fBuffer = tmp;
00760 }
00761 fBuffer->reset();
00762
00763
00764 fOurSink.continuePlaying();
00765 }
00766
00767 void SubsessionIOState::useFrame(SubsessionBuffer& buffer) {
00768 unsigned char* const frameSource = buffer.dataStart();
00769 unsigned const frameSize = buffer.bytesInUse();
00770 struct timeval const& presentationTime = buffer.presentationTime();
00771 int64_t const destFileOffset = TellFile64(fOurSink.fOutFid);
00772 unsigned sampleNumberOfFrameStart = fQTTotNumSamples + 1;
00773 Boolean avcHack = fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_avc1;
00774
00775
00776
00777 if (!fOurSink.fSyncStreams
00778 || fQTcomponentSubtype != fourChar('v','i','d','e')) {
00779 unsigned const frameDuration = fQTTimeUnitsPerSample*fQTSamplesPerFrame;
00780 unsigned frameSizeToUse = frameSize;
00781 if (avcHack) frameSizeToUse += 4;
00782
00783 fQTTotNumSamples += useFrame1(frameSizeToUse, presentationTime, frameDuration, destFileOffset);
00784 } else {
00785
00786
00787
00788 struct timeval const& ppt = fPrevFrameState.presentationTime;
00789 if (ppt.tv_sec != 0 || ppt.tv_usec != 0) {
00790
00791 double duration = (presentationTime.tv_sec - ppt.tv_sec)
00792 + (presentationTime.tv_usec - ppt.tv_usec)/1000000.0;
00793 if (duration < 0.0) duration = 0.0;
00794 unsigned frameDuration
00795 = (unsigned)((2*duration*fQTTimeScale+1)/2);
00796 unsigned frameSizeToUse = fPrevFrameState.frameSize;
00797 if (avcHack) frameSizeToUse += 4;
00798
00799 unsigned numSamples
00800 = useFrame1(frameSizeToUse, ppt, frameDuration, fPrevFrameState.destFileOffset);
00801 fQTTotNumSamples += numSamples;
00802 sampleNumberOfFrameStart = fQTTotNumSamples + 1;
00803 }
00804
00805 if (avcHack && (*frameSource == H264_IDR_FRAME)) {
00806 SyncFrame* newSyncFrame = new SyncFrame(fQTTotNumSamples + 1);
00807 if (fTailSyncFrame == NULL) {
00808 fHeadSyncFrame = newSyncFrame;
00809 } else {
00810 fTailSyncFrame->nextSyncFrame = newSyncFrame;
00811 }
00812 fTailSyncFrame = newSyncFrame;
00813 }
00814
00815
00816 fPrevFrameState.frameSize = frameSize;
00817 fPrevFrameState.presentationTime = presentationTime;
00818 fPrevFrameState.destFileOffset = destFileOffset;
00819 }
00820
00821 if (avcHack) fOurSink.addWord(frameSize);
00822
00823
00824 fwrite(frameSource, 1, frameSize, fOurSink.fOutFid);
00825
00826
00827 if (hasHintTrack()) {
00828
00829
00830 if (!fHaveBeenSynced) {
00831 fHaveBeenSynced
00832 = fOurSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP();
00833 }
00834 if (fHaveBeenSynced) {
00835 fHintTrackForUs->useFrameForHinting(frameSize, presentationTime,
00836 sampleNumberOfFrameStart);
00837 }
00838 }
00839 }
00840
00841 void SubsessionIOState::useFrameForHinting(unsigned frameSize,
00842 struct timeval presentationTime,
00843 unsigned startSampleNumber) {
00844
00845
00846
00847
00848 Boolean hack263 = strcmp(fOurSubsession.codecName(), "H263-1998") == 0;
00849 Boolean hackm4a_generic = strcmp(fOurSubsession.mediumName(), "audio") == 0
00850 && strcmp(fOurSubsession.codecName(), "MPEG4-GENERIC") == 0;
00851 Boolean hackm4a_latm = strcmp(fOurSubsession.mediumName(), "audio") == 0
00852 && strcmp(fOurSubsession.codecName(), "MP4A-LATM") == 0;
00853 Boolean hackm4a = hackm4a_generic || hackm4a_latm;
00854 Boolean haveSpecialHeaders = (hack263 || hackm4a_generic);
00855
00856
00857
00858
00859 RTPSource* const rs = fOurSubsession.rtpSource();
00860 struct timeval const& ppt = fPrevFrameState.presentationTime;
00861 if (ppt.tv_sec != 0 || ppt.tv_usec != 0) {
00862 double duration = (presentationTime.tv_sec - ppt.tv_sec)
00863 + (presentationTime.tv_usec - ppt.tv_usec)/1000000.0;
00864 if (duration < 0.0) duration = 0.0;
00865 unsigned msDuration = (unsigned)(duration*1000);
00866 if (msDuration > fHINF.dmax) fHINF.dmax = msDuration;
00867 unsigned hintSampleDuration
00868 = (unsigned)((2*duration*fQTTimeScale+1)/2);
00869 if (hackm4a) {
00870
00871
00872
00873 hintSampleDuration = fTrackHintedByUs->fQTTimeUnitsPerSample;
00874
00875
00876
00877
00878 if (fTrackHintedByUs->fQTTimeScale != fOurSubsession.rtpTimestampFrequency()) {
00879 unsigned const scalingFactor
00880 = fOurSubsession.rtpTimestampFrequency()/fTrackHintedByUs->fQTTimeScale ;
00881 hintSampleDuration *= scalingFactor;
00882 }
00883 }
00884
00885 int64_t const hintSampleDestFileOffset = TellFile64(fOurSink.fOutFid);
00886
00887 unsigned const maxPacketSize = 1450;
00888 unsigned short numPTEntries
00889 = (fPrevFrameState.frameSize + (maxPacketSize-1))/maxPacketSize;
00890 unsigned char* immediateDataPtr = NULL;
00891 unsigned immediateDataBytesRemaining = 0;
00892 if (haveSpecialHeaders) {
00893 numPTEntries = fPrevFrameState.numSpecialHeaders;
00894 immediateDataPtr = fPrevFrameState.specialHeaderBytes;
00895 immediateDataBytesRemaining
00896 = fPrevFrameState.specialHeaderBytesLength;
00897 }
00898 unsigned hintSampleSize
00899 = fOurSink.addHalfWord(numPTEntries);
00900 hintSampleSize += fOurSink.addHalfWord(0x0000);
00901
00902 unsigned offsetWithinSample = 0;
00903 for (unsigned i = 0; i < numPTEntries; ++i) {
00904
00905 unsigned short numDTEntries = 1;
00906 unsigned short seqNum = fPrevFrameState.seqNum++;
00907
00908 unsigned rtpHeader = fPrevFrameState.rtpHeader;
00909 if (i+1 < numPTEntries) {
00910
00911 rtpHeader &=~ (1<<23);
00912 }
00913 unsigned dataFrameSize = (i+1 < numPTEntries)
00914 ? maxPacketSize : fPrevFrameState.frameSize - i*maxPacketSize;
00915 unsigned sampleNumber = fPrevFrameState.startSampleNumber;
00916
00917 unsigned char immediateDataLen = 0;
00918 if (haveSpecialHeaders) {
00919 ++numDTEntries;
00920 if (immediateDataBytesRemaining > 0) {
00921 if (hack263) {
00922 immediateDataLen = *immediateDataPtr++;
00923 --immediateDataBytesRemaining;
00924 if (immediateDataLen > immediateDataBytesRemaining) {
00925
00926 immediateDataLen = immediateDataBytesRemaining;
00927 }
00928 } else {
00929 immediateDataLen = fPrevFrameState.specialHeaderBytesLength;
00930 }
00931 }
00932 dataFrameSize = fPrevFrameState.packetSizes[i] - immediateDataLen;
00933
00934 if (hack263) {
00935 Boolean PbitSet
00936 = immediateDataLen >= 1 && (immediateDataPtr[0]&0x4) != 0;
00937 if (PbitSet) {
00938 offsetWithinSample += 2;
00939 }
00940 }
00941 }
00942
00943
00944 hintSampleSize += fOurSink.addWord(0);
00945 hintSampleSize += fOurSink.addWord(rtpHeader|seqNum);
00946
00947 hintSampleSize += fOurSink.addHalfWord(0x0000);
00948 hintSampleSize += fOurSink.addHalfWord(numDTEntries);
00949 unsigned totalPacketSize = 0;
00950
00951
00952 if (haveSpecialHeaders) {
00953
00954 hintSampleSize += fOurSink.addByte(1);
00955 unsigned char len = immediateDataLen > 14 ? 14 : immediateDataLen;
00956 hintSampleSize += fOurSink.addByte(len);
00957 totalPacketSize += len; fHINF.dimm += len;
00958 unsigned char j;
00959 for (j = 0; j < len; ++j) {
00960 hintSampleSize += fOurSink.addByte(immediateDataPtr[j]);
00961 }
00962 for (j = len; j < 14; ++j) {
00963 hintSampleSize += fOurSink.addByte(0);
00964 }
00965
00966 immediateDataPtr += immediateDataLen;
00967 immediateDataBytesRemaining -= immediateDataLen;
00968 }
00969
00970 hintSampleSize += fOurSink.addByte(2);
00971 hintSampleSize += fOurSink.addByte(0);
00972 hintSampleSize += fOurSink.addHalfWord(dataFrameSize);
00973 totalPacketSize += dataFrameSize; fHINF.dmed += dataFrameSize;
00974 hintSampleSize += fOurSink.addWord(sampleNumber);
00975 hintSampleSize += fOurSink.addWord(offsetWithinSample);
00976
00977 unsigned short const bytesPerCompressionBlock
00978 = fTrackHintedByUs->fQTBytesPerFrame;
00979 unsigned short const samplesPerCompressionBlock
00980 = fTrackHintedByUs->fQTSamplesPerFrame;
00981 hintSampleSize += fOurSink.addHalfWord(bytesPerCompressionBlock);
00982 hintSampleSize += fOurSink.addHalfWord(samplesPerCompressionBlock);
00983
00984 offsetWithinSample += dataFrameSize;
00985
00986
00987 fHINF.nump += 1;
00988 fHINF.tpyl += totalPacketSize;
00989 totalPacketSize += 12;
00990 fHINF.trpy += totalPacketSize;
00991 if (totalPacketSize > fHINF.pmax) fHINF.pmax = totalPacketSize;
00992 }
00993
00994
00995 fQTTotNumSamples += useFrame1(hintSampleSize, ppt, hintSampleDuration,
00996 hintSampleDestFileOffset);
00997 }
00998
00999
01000 fPrevFrameState.frameSize = frameSize;
01001 fPrevFrameState.presentationTime = presentationTime;
01002 fPrevFrameState.startSampleNumber = startSampleNumber;
01003 fPrevFrameState.rtpHeader
01004 = rs->curPacketMarkerBit()<<23
01005 | (rs->rtpPayloadFormat()&0x7F)<<16;
01006 if (hack263) {
01007 H263plusVideoRTPSource* rs_263 = (H263plusVideoRTPSource*)rs;
01008 fPrevFrameState.numSpecialHeaders = rs_263->fNumSpecialHeaders;
01009 fPrevFrameState.specialHeaderBytesLength = rs_263->fSpecialHeaderBytesLength;
01010 unsigned i;
01011 for (i = 0; i < rs_263->fSpecialHeaderBytesLength; ++i) {
01012 fPrevFrameState.specialHeaderBytes[i] = rs_263->fSpecialHeaderBytes[i];
01013 }
01014 for (i = 0; i < rs_263->fNumSpecialHeaders; ++i) {
01015 fPrevFrameState.packetSizes[i] = rs_263->fPacketSizes[i];
01016 }
01017 } else if (hackm4a_generic) {
01018
01019 unsigned const sizeLength = fOurSubsession.fmtp_sizelength();
01020 unsigned const indexLength = fOurSubsession.fmtp_indexlength();
01021 if (sizeLength + indexLength != 16) {
01022 envir() << "Warning: unexpected 'sizeLength' " << sizeLength
01023 << " and 'indexLength' " << indexLength
01024 << "seen when creating hint track\n";
01025 }
01026 fPrevFrameState.numSpecialHeaders = 1;
01027 fPrevFrameState.specialHeaderBytesLength = 4;
01028 fPrevFrameState.specialHeaderBytes[0] = 0;
01029 fPrevFrameState.specialHeaderBytes[1] = 16;
01030 fPrevFrameState.specialHeaderBytes[2] = ((frameSize<<indexLength)&0xFF00)>>8;
01031 fPrevFrameState.specialHeaderBytes[3] = (frameSize<<indexLength);
01032 fPrevFrameState.packetSizes[0]
01033 = fPrevFrameState.specialHeaderBytesLength + frameSize;
01034 }
01035 }
01036
01037 unsigned SubsessionIOState::useFrame1(unsigned sourceDataSize,
01038 struct timeval presentationTime,
01039 unsigned frameDuration,
01040 int64_t destFileOffset) {
01041
01042 unsigned frameSize = fQTBytesPerFrame;
01043 if (frameSize == 0) {
01044
01045 frameSize = sourceDataSize;
01046 }
01047 unsigned const numFrames = sourceDataSize/frameSize;
01048 unsigned const numSamples = numFrames*fQTSamplesPerFrame;
01049
01050
01051 ChunkDescriptor* newTailChunk;
01052 if (fTailChunk == NULL) {
01053 newTailChunk = fHeadChunk
01054 = new ChunkDescriptor(destFileOffset, sourceDataSize,
01055 frameSize, frameDuration, presentationTime);
01056 } else {
01057 newTailChunk = fTailChunk->extendChunk(destFileOffset, sourceDataSize,
01058 frameSize, frameDuration,
01059 presentationTime);
01060 }
01061 if (newTailChunk != fTailChunk) {
01062
01063 ++fNumChunks;
01064 fTailChunk = newTailChunk;
01065 }
01066
01067 return numSamples;
01068 }
01069
01070 void SubsessionIOState::onSourceClosure() {
01071 fOurSourceIsActive = False;
01072 fOurSink.onSourceClosure1();
01073 }
01074
01075 Boolean SubsessionIOState::syncOK(struct timeval presentationTime) {
01076 QuickTimeFileSink& s = fOurSink;
01077 if (!s.fSyncStreams) return True;
01078
01079 if (s.fNumSyncedSubsessions < s.fNumSubsessions) {
01080
01081
01082 if (!fHaveBeenSynced) {
01083
01084 if (fOurSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP()) {
01085
01086 if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_avc1) {
01087
01088 if ((s.fNumSubsessions == 2) && (s.fNumSyncedSubsessions < (s.fNumSubsessions - 1))) return False;
01089
01090
01091 unsigned char* const frameSource = fBuffer->dataStart();
01092 if (*frameSource != H264_IDR_FRAME) return False;
01093 }
01094
01095 fHaveBeenSynced = True;
01096 fSyncTime = presentationTime;
01097 ++s.fNumSyncedSubsessions;
01098
01099 if (timevalGE(fSyncTime, s.fNewestSyncTime)) {
01100 s.fNewestSyncTime = fSyncTime;
01101 }
01102 }
01103 }
01104 }
01105
01106
01107 if (s.fNumSyncedSubsessions < s.fNumSubsessions) return False;
01108
01109
01110 return timevalGE(presentationTime, s.fNewestSyncTime);
01111 }
01112
01113 void SubsessionIOState::setHintTrack(SubsessionIOState* hintedTrack,
01114 SubsessionIOState* hintTrack) {
01115 if (hintedTrack != NULL) hintedTrack->fHintTrackForUs = hintTrack;
01116 if (hintTrack != NULL) hintTrack->fTrackHintedByUs = hintedTrack;
01117 }
01118
01119 SyncFrame::SyncFrame(unsigned frameNum)
01120 : nextSyncFrame(NULL), sfFrameNum(frameNum) {
01121 }
01122
01123 SyncFrame::~SyncFrame() {
01124 delete nextSyncFrame;
01125 }
01126
01127 void Count64::operator+=(unsigned arg) {
01128 unsigned newLo = lo + arg;
01129 if (newLo < lo) {
01130 ++hi;
01131 }
01132 lo = newLo;
01133 }
01134
01135 ChunkDescriptor
01136 ::ChunkDescriptor(int64_t offsetInFile, unsigned size,
01137 unsigned frameSize, unsigned frameDuration,
01138 struct timeval presentationTime)
01139 : fNextChunk(NULL), fOffsetInFile(offsetInFile),
01140 fNumFrames(size/frameSize),
01141 fFrameSize(frameSize), fFrameDuration(frameDuration),
01142 fPresentationTime(presentationTime) {
01143 }
01144
01145 ChunkDescriptor::~ChunkDescriptor() {
01146 delete fNextChunk;
01147 }
01148
01149 ChunkDescriptor* ChunkDescriptor
01150 ::extendChunk(int64_t newOffsetInFile, unsigned newSize,
01151 unsigned newFrameSize, unsigned newFrameDuration,
01152 struct timeval newPresentationTime) {
01153
01154
01155 if (newOffsetInFile == fOffsetInFile + fNumFrames*fFrameSize) {
01156
01157
01158 if (newFrameSize == fFrameSize && newFrameDuration == fFrameDuration) {
01159 fNumFrames += newSize/fFrameSize;
01160 return this;
01161 }
01162 }
01163
01164
01165 ChunkDescriptor* newDescriptor
01166 = new ChunkDescriptor(newOffsetInFile, newSize,
01167 newFrameSize, newFrameDuration,
01168 newPresentationTime);
01169
01170 fNextChunk = newDescriptor;
01171
01172 return newDescriptor;
01173 }
01174
01175
01177
01178 unsigned QuickTimeFileSink::addWord64(u_int64_t word) {
01179 addByte((unsigned char)(word>>56)); addByte((unsigned char)(word>>48));
01180 addByte((unsigned char)(word>>40)); addByte((unsigned char)(word>>32));
01181 addByte((unsigned char)(word>>24)); addByte((unsigned char)(word>>16));
01182 addByte((unsigned char)(word>>8)); addByte((unsigned char)(word));
01183
01184 return 8;
01185 }
01186
01187 unsigned QuickTimeFileSink::addWord(unsigned word) {
01188 addByte(word>>24); addByte(word>>16);
01189 addByte(word>>8); addByte(word);
01190
01191 return 4;
01192 }
01193
01194 unsigned QuickTimeFileSink::addHalfWord(unsigned short halfWord) {
01195 addByte((unsigned char)(halfWord>>8)); addByte((unsigned char)halfWord);
01196
01197 return 2;
01198 }
01199
01200 unsigned QuickTimeFileSink::addZeroWords(unsigned numWords) {
01201 for (unsigned i = 0; i < numWords; ++i) {
01202 addWord(0);
01203 }
01204
01205 return numWords*4;
01206 }
01207
01208 unsigned QuickTimeFileSink::add4ByteString(char const* str) {
01209 addByte(str[0]); addByte(str[1]); addByte(str[2]); addByte(str[3]);
01210
01211 return 4;
01212 }
01213
01214 unsigned QuickTimeFileSink::addArbitraryString(char const* str,
01215 Boolean oneByteLength) {
01216 unsigned size = 0;
01217 if (oneByteLength) {
01218
01219 unsigned strLength = strlen(str);
01220 if (strLength >= 256) {
01221 envir() << "QuickTimeFileSink::addArbitraryString(\""
01222 << str << "\") saw string longer than we know how to handle ("
01223 << strLength << "\n";
01224 }
01225 size += addByte((unsigned char)strLength);
01226 }
01227
01228 while (*str != '\0') {
01229 size += addByte(*str++);
01230 }
01231
01232 return size;
01233 }
01234
01235 unsigned QuickTimeFileSink::addAtomHeader(char const* atomName) {
01236
01237 addWord(0);
01238
01239
01240 add4ByteString(atomName);
01241
01242 return 8;
01243 }
01244
01245 unsigned QuickTimeFileSink::addAtomHeader64(char const* atomName) {
01246
01247 addWord(1);
01248
01249
01250 add4ByteString(atomName);
01251
01252 addWord64(0);
01253
01254 return 16;
01255 }
01256
01257 void QuickTimeFileSink::setWord(int64_t filePosn, unsigned size) {
01258 do {
01259 if (SeekFile64(fOutFid, filePosn, SEEK_SET) < 0) break;
01260 addWord(size);
01261 if (SeekFile64(fOutFid, 0, SEEK_END) < 0) break;
01262
01263 return;
01264 } while (0);
01265
01266
01267 envir() << "QuickTimeFileSink::setWord(): SeekFile64 failed (err "
01268 << envir().getErrno() << ")\n";
01269 }
01270
01271 void QuickTimeFileSink::setWord64(int64_t filePosn, u_int64_t size) {
01272 do {
01273 if (SeekFile64(fOutFid, filePosn, SEEK_SET) < 0) break;
01274 addWord64(size);
01275 if (SeekFile64(fOutFid, 0, SEEK_END) < 0) break;
01276
01277 return;
01278 } while (0);
01279
01280
01281 envir() << "QuickTimeFileSink::setWord(): SeekFile64 failed (err "
01282 << envir().getErrno() << ")\n";
01283 }
01284
01285
01286
01287 #define addAtom(name) \
01288 unsigned QuickTimeFileSink::addAtom_##name() { \
01289 int64_t initFilePosn = TellFile64(fOutFid); \
01290 unsigned size = addAtomHeader("" #name "")
01291
01292 #define addAtomEnd \
01293 setWord(initFilePosn, size); \
01294 return size; \
01295 }
01296
01297 addAtom(ftyp);
01298 size += add4ByteString("mp42");
01299 size += addWord(0x00000000);
01300 size += add4ByteString("mp42");
01301 size += add4ByteString("isom");
01302 addAtomEnd;
01303
01304 addAtom(moov);
01305 size += addAtom_mvhd();
01306
01307 if (fGenerateMP4Format) {
01308 size += addAtom_iods();
01309 }
01310
01311
01312
01313
01314
01315 MediaSubsessionIterator iter(fInputSession);
01316 MediaSubsession* subsession;
01317 while ((subsession = iter.next()) != NULL) {
01318 fCurrentIOState = (SubsessionIOState*)(subsession->miscPtr);
01319 if (fCurrentIOState == NULL) continue;
01320 if (strcmp(subsession->mediumName(), "audio") != 0) continue;
01321
01322 size += addAtom_trak();
01323
01324 if (fCurrentIOState->hasHintTrack()) {
01325
01326 fCurrentIOState = fCurrentIOState->fHintTrackForUs;
01327 size += addAtom_trak();
01328 }
01329 }
01330 iter.reset();
01331 while ((subsession = iter.next()) != NULL) {
01332 fCurrentIOState = (SubsessionIOState*)(subsession->miscPtr);
01333 if (fCurrentIOState == NULL) continue;
01334 if (strcmp(subsession->mediumName(), "audio") == 0) continue;
01335
01336 size += addAtom_trak();
01337
01338 if (fCurrentIOState->hasHintTrack()) {
01339
01340 fCurrentIOState = fCurrentIOState->fHintTrackForUs;
01341 size += addAtom_trak();
01342 }
01343 }
01344 addAtomEnd;
01345
01346 addAtom(mvhd);
01347 size += addWord(0x00000000);
01348 size += addWord(fAppleCreationTime);
01349 size += addWord(fAppleCreationTime);
01350
01351
01352
01353 size += addWord(movieTimeScale());
01354
01355 unsigned const duration = fMaxTrackDurationM;
01356 fMVHD_durationPosn = TellFile64(fOutFid);
01357 size += addWord(duration);
01358
01359 size += addWord(0x00010000);
01360 size += addWord(0x01000000);
01361 size += addZeroWords(2);
01362 size += addWord(0x00010000);
01363 size += addZeroWords(3);
01364 size += addWord(0x00010000);
01365 size += addZeroWords(3);
01366 size += addWord(0x40000000);
01367 size += addZeroWords(6);
01368 size += addWord(SubsessionIOState::fCurrentTrackNumber+1);
01369 addAtomEnd;
01370
01371 addAtom(iods);
01372 size += addWord(0x00000000);
01373 size += addWord(0x10808080);
01374 size += addWord(0x07004FFF);
01375 size += addWord(0xFF0FFFFF);
01376 addAtomEnd;
01377
01378 addAtom(trak);
01379 size += addAtom_tkhd();
01380
01381
01382
01383 if (fCurrentIOState->fHeadChunk != NULL
01384 && (fSyncStreams || fCurrentIOState->isHintTrack())) {
01385 size += addAtom_edts();
01386 }
01387
01388
01389 if (fCurrentIOState->isHintTrack()) size += addAtom_tref();
01390
01391 size += addAtom_mdia();
01392
01393
01394 if (fCurrentIOState->isHintTrack()) size += addAtom_udta();
01395 addAtomEnd;
01396
01397 addAtom(tkhd);
01398 if (fCurrentIOState->fQTEnableTrack) {
01399 size += addWord(0x0000000F);
01400 } else {
01401
01402 size += addWord(0x00000000);
01403 }
01404 size += addWord(fAppleCreationTime);
01405 size += addWord(fAppleCreationTime);
01406 size += addWord(fCurrentIOState->fTrackID);
01407 size += addWord(0x00000000);
01408
01409 unsigned const duration = fCurrentIOState->fQTDurationM;
01410 fCurrentIOState->fTKHD_durationPosn = TellFile64(fOutFid);
01411 size += addWord(duration);
01412 size += addZeroWords(3);
01413 size += addWord(0x01000000);
01414 size += addWord(0x00010000);
01415 size += addZeroWords(3);
01416 size += addWord(0x00010000);
01417 size += addZeroWords(3);
01418 size += addWord(0x40000000);
01419 if (strcmp(fCurrentIOState->fOurSubsession.mediumName(), "video") == 0) {
01420 size += addWord(fMovieWidth<<16);
01421 size += addWord(fMovieHeight<<16);
01422 } else {
01423 size += addZeroWords(2);
01424 }
01425 addAtomEnd;
01426
01427 addAtom(edts);
01428 size += addAtom_elst();
01429 addAtomEnd;
01430
01431 #define addEdit1(duration,trackPosition) do { \
01432 unsigned trackDuration \
01433 = (unsigned) ((2*(duration)*movieTimeScale()+1)/2); \
01434 \
01435 size += addWord(trackDuration); \
01436 totalDurationOfEdits += trackDuration; \
01437 size += addWord(trackPosition); \
01438 size += addWord(0x00010000); \
01439 ++numEdits; \
01440 } while (0)
01441 #define addEdit(duration) addEdit1((duration),editTrackPosition)
01442 #define addEmptyEdit(duration) addEdit1((duration),(~0))
01443
01444 addAtom(elst);
01445 size += addWord(0x00000000);
01446
01447
01448
01449 int64_t numEntriesPosition = TellFile64(fOutFid);
01450 size += addWord(0);
01451 unsigned numEdits = 0;
01452 unsigned totalDurationOfEdits = 0;
01453
01454
01455
01456
01457
01458 double const syncThreshold = 0.1;
01459
01460
01461 struct timeval editStartTime = fFirstDataTime;
01462 unsigned editTrackPosition = 0;
01463 unsigned currentTrackPosition = 0;
01464 double trackDurationOfEdit = 0.0;
01465 unsigned chunkDuration = 0;
01466
01467 ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
01468 while (chunk != NULL) {
01469 struct timeval const& chunkStartTime = chunk->fPresentationTime;
01470 double movieDurationOfEdit
01471 = (chunkStartTime.tv_sec - editStartTime.tv_sec)
01472 + (chunkStartTime.tv_usec - editStartTime.tv_usec)/1000000.0;
01473 trackDurationOfEdit = (currentTrackPosition-editTrackPosition)
01474 / (double)(fCurrentIOState->fQTTimeScale);
01475
01476 double outOfSync = movieDurationOfEdit - trackDurationOfEdit;
01477
01478 if (outOfSync > syncThreshold) {
01479
01480
01481
01482 if (trackDurationOfEdit > 0.0) addEdit(trackDurationOfEdit);
01483 addEmptyEdit(outOfSync);
01484
01485 editStartTime = chunkStartTime;
01486 editTrackPosition = currentTrackPosition;
01487 } else if (outOfSync < -syncThreshold) {
01488
01489
01490 if (movieDurationOfEdit > 0.0) addEdit(movieDurationOfEdit);
01491
01492 editStartTime = chunkStartTime;
01493 editTrackPosition = currentTrackPosition;
01494 }
01495
01496
01497 unsigned numChannels = fCurrentIOState->fOurSubsession.numChannels();
01498 chunkDuration = chunk->fNumFrames*chunk->fFrameDuration/numChannels;
01499 currentTrackPosition += chunkDuration;
01500
01501 chunk = chunk->fNextChunk;
01502 }
01503
01504
01505 trackDurationOfEdit
01506 += (double)chunkDuration/fCurrentIOState->fQTTimeScale;
01507 if (trackDurationOfEdit > 0.0) addEdit(trackDurationOfEdit);
01508
01509
01510 setWord(numEntriesPosition, numEdits);
01511
01512
01513
01514
01515 if (totalDurationOfEdits > fCurrentIOState->fQTDurationM) {
01516 fCurrentIOState->fQTDurationM = totalDurationOfEdits;
01517 setWord(fCurrentIOState->fTKHD_durationPosn, totalDurationOfEdits);
01518
01519
01520 if (totalDurationOfEdits > fMaxTrackDurationM) {
01521 fMaxTrackDurationM = totalDurationOfEdits;
01522 setWord(fMVHD_durationPosn, totalDurationOfEdits);
01523 }
01524
01525
01526 double scaleFactor
01527 = fCurrentIOState->fQTTimeScale/(double)movieTimeScale();
01528 fCurrentIOState->fQTDurationT
01529 = (unsigned)(totalDurationOfEdits*scaleFactor);
01530 }
01531 addAtomEnd;
01532
01533 addAtom(tref);
01534 size += addAtom_hint();
01535 addAtomEnd;
01536
01537 addAtom(hint);
01538 SubsessionIOState* hintedTrack = fCurrentIOState->fTrackHintedByUs;
01539
01540 size += addWord(hintedTrack->fTrackID);
01541 addAtomEnd;
01542
01543 addAtom(mdia);
01544 size += addAtom_mdhd();
01545 size += addAtom_hdlr();
01546 size += addAtom_minf();
01547 addAtomEnd;
01548
01549 addAtom(mdhd);
01550 size += addWord(0x00000000);
01551 size += addWord(fAppleCreationTime);
01552 size += addWord(fAppleCreationTime);
01553
01554 unsigned const timeScale = fCurrentIOState->fQTTimeScale;
01555 size += addWord(timeScale);
01556
01557 unsigned const duration = fCurrentIOState->fQTDurationT;
01558 size += addWord(duration);
01559
01560 size += addWord(0x00000000);
01561 addAtomEnd;
01562
01563 addAtom(hdlr);
01564 size += addWord(0x00000000);
01565 size += add4ByteString("mhlr");
01566 size += addWord(fCurrentIOState->fQTcomponentSubtype);
01567
01568 size += add4ByteString("appl");
01569 size += addWord(0x00000000);
01570 size += addWord(0x00000000);
01571 size += addArbitraryString(fCurrentIOState->fQTcomponentName);
01572
01573 addAtomEnd;
01574
01575 addAtom(minf);
01576 SubsessionIOState::atomCreationFunc mediaInformationAtomCreator
01577 = fCurrentIOState->fQTMediaInformationAtomCreator;
01578 size += (this->*mediaInformationAtomCreator)();
01579 size += addAtom_hdlr2();
01580 size += addAtom_dinf();
01581 size += addAtom_stbl();
01582 addAtomEnd;
01583
01584 addAtom(smhd);
01585 size += addZeroWords(2);
01586 addAtomEnd;
01587
01588 addAtom(vmhd);
01589 size += addWord(0x00000001);
01590 size += addWord(0x00408000);
01591 size += addWord(0x80008000);
01592 addAtomEnd;
01593
01594 addAtom(gmhd);
01595 size += addAtom_gmin();
01596 addAtomEnd;
01597
01598 addAtom(gmin);
01599 size += addWord(0x00000000);
01600
01601
01602 size += addWord(0x00408000);
01603 size += addWord(0x80008000);
01604 size += addWord(0x00000000);
01605 addAtomEnd;
01606
01607 unsigned QuickTimeFileSink::addAtom_hdlr2() {
01608 int64_t initFilePosn = TellFile64(fOutFid);
01609 unsigned size = addAtomHeader("hdlr");
01610 size += addWord(0x00000000);
01611 size += add4ByteString("dhlr");
01612 size += add4ByteString("alis");
01613 size += add4ByteString("appl");
01614 size += addZeroWords(2);
01615 size += addArbitraryString("Apple Alias Data Handler");
01616 addAtomEnd;
01617
01618 addAtom(dinf);
01619 size += addAtom_dref();
01620 addAtomEnd;
01621
01622 addAtom(dref);
01623 size += addWord(0x00000000);
01624 size += addWord(0x00000001);
01625 size += addAtom_alis();
01626 addAtomEnd;
01627
01628 addAtom(alis);
01629 size += addWord(0x00000001);
01630 addAtomEnd;
01631
01632 addAtom(stbl);
01633 size += addAtom_stsd();
01634 size += addAtom_stts();
01635 if (fCurrentIOState->fQTcomponentSubtype == fourChar('v','i','d','e')) {
01636 size += addAtom_stss();
01637 }
01638 size += addAtom_stsc();
01639 size += addAtom_stsz();
01640 size += addAtom_co64();
01641 addAtomEnd;
01642
01643 addAtom(stsd);
01644 size += addWord(0x00000000);
01645 size += addWord(0x00000001);
01646 SubsessionIOState::atomCreationFunc mediaDataAtomCreator
01647 = fCurrentIOState->fQTMediaDataAtomCreator;
01648 size += (this->*mediaDataAtomCreator)();
01649 addAtomEnd;
01650
01651 unsigned QuickTimeFileSink::addAtom_genericMedia() {
01652 int64_t initFilePosn = TellFile64(fOutFid);
01653
01654
01655
01656 QuickTimeGenericRTPSource* rtpSource = (QuickTimeGenericRTPSource*)
01657 fCurrentIOState->fOurSubsession.rtpSource();
01658 QuickTimeGenericRTPSource::QTState& qtState = rtpSource->qtState;
01659 char const* from = qtState.sdAtom;
01660 unsigned size = qtState.sdAtomSize;
01661 for (unsigned i = 0; i < size; ++i) addByte(from[i]);
01662 addAtomEnd;
01663
01664 unsigned QuickTimeFileSink::addAtom_soundMediaGeneral() {
01665 int64_t initFilePosn = TellFile64(fOutFid);
01666 unsigned size = addAtomHeader(fCurrentIOState->fQTAudioDataType);
01667
01668
01669 size += addWord(0x00000000);
01670 size += addWord(0x00000001);
01671
01672 unsigned short const version = fCurrentIOState->fQTSoundSampleVersion;
01673 size += addWord(version<<16);
01674 size += addWord(0x00000000);
01675 unsigned short numChannels
01676 = (unsigned short)(fCurrentIOState->fOurSubsession.numChannels());
01677 size += addHalfWord(numChannels);
01678 size += addHalfWord(0x0010);
01679
01680 size += addWord(0xfffe0000);
01681
01682 unsigned const sampleRateFixedPoint = fCurrentIOState->fQTTimeScale << 16;
01683 size += addWord(sampleRateFixedPoint);
01684 addAtomEnd;
01685
01686 unsigned QuickTimeFileSink::addAtom_Qclp() {
01687
01688
01689 int64_t initFilePosn = TellFile64(fOutFid);
01690 fCurrentIOState->fQTAudioDataType = "Qclp";
01691 fCurrentIOState->fQTSoundSampleVersion = 1;
01692 unsigned size = addAtom_soundMediaGeneral();
01693
01694
01695
01696 size += addWord(0x000000a0);
01697 size += addWord(0x00000000);
01698 size += addWord(0x00000000);
01699 size += addWord(0x00000002);
01700
01701
01702 size += addAtom_wave();
01703 addAtomEnd;
01704
01705 addAtom(wave);
01706 size += addAtom_frma();
01707 if (strcmp(fCurrentIOState->fQTAudioDataType, "Qclp") == 0) {
01708 size += addWord(0x00000014);
01709 size += add4ByteString("Qclp");
01710 if (fCurrentIOState->fQTBytesPerFrame == 35) {
01711 size += addAtom_Fclp();
01712 } else {
01713 size += addAtom_Hclp();
01714 }
01715 size += addWord(0x00000008);
01716 size += addWord(0x00000000);
01717 size += addWord(0x00000000);
01718 size += addWord(0x00000008);
01719 } else if (strcmp(fCurrentIOState->fQTAudioDataType, "mp4a") == 0) {
01720 size += addWord(0x0000000c);
01721 size += add4ByteString("mp4a");
01722 size += addWord(0x00000000);
01723 size += addAtom_esds();
01724 size += addWord(0x00000008);
01725 size += addWord(0x00000000);
01726 }
01727 addAtomEnd;
01728
01729 addAtom(frma);
01730 size += add4ByteString(fCurrentIOState->fQTAudioDataType);
01731 addAtomEnd;
01732
01733 addAtom(Fclp);
01734 size += addWord(0x00000000);
01735 addAtomEnd;
01736
01737 addAtom(Hclp);
01738 size += addWord(0x00000000);
01739 addAtomEnd;
01740
01741 unsigned QuickTimeFileSink::addAtom_mp4a() {
01742 unsigned size = 0;
01743
01744
01745 int64_t initFilePosn = TellFile64(fOutFid);
01746 fCurrentIOState->fQTAudioDataType = "mp4a";
01747
01748 if (fGenerateMP4Format) {
01749 fCurrentIOState->fQTSoundSampleVersion = 0;
01750 size = addAtom_soundMediaGeneral();
01751 size += addAtom_esds();
01752 } else {
01753 fCurrentIOState->fQTSoundSampleVersion = 1;
01754 size = addAtom_soundMediaGeneral();
01755
01756
01757
01758 size += addWord(fCurrentIOState->fQTTimeUnitsPerSample);
01759 size += addWord(0x00000001);
01760 size += addWord(0x00000001);
01761 size += addWord(0x00000002);
01762
01763
01764 size += addAtom_wave();
01765 }
01766 addAtomEnd;
01767
01768 addAtom(esds);
01769
01770 MediaSubsession& subsession = fCurrentIOState->fOurSubsession;
01771 if (strcmp(subsession.mediumName(), "audio") == 0) {
01772
01773 size += addWord(0x00000000);
01774 size += addWord(0x03808080);
01775 size += addWord(0x2a000000);
01776 size += addWord(0x04808080);
01777 size += addWord(0x1c401500);
01778 size += addWord(0x18000000);
01779 size += addWord(0x6d600000);
01780 size += addWord(0x6d600580);
01781 size += addByte(0x80); size += addByte(0x80);
01782 } else if (strcmp(subsession.mediumName(), "video") == 0) {
01783
01784 size += addWord(0x00000000);
01785 size += addWord(0x03330000);
01786 size += addWord(0x1f042b20);
01787 size += addWord(0x1104fd46);
01788 size += addWord(0x000d4e10);
01789 size += addWord(0x000d4e10);
01790 size += addByte(0x05);
01791 }
01792
01793
01794 unsigned configSize;
01795 unsigned char* config
01796 = parseGeneralConfigStr(subsession.fmtp_config(), configSize);
01797 size += addByte(configSize);
01798 for (unsigned i = 0; i < configSize; ++i) {
01799 size += addByte(config[i]);
01800 }
01801 delete[] config;
01802
01803 if (strcmp(subsession.mediumName(), "audio") == 0) {
01804
01805 size += addWord(0x06808080);
01806 size += addHalfWord(0x0102);
01807 } else {
01808
01809 size += addHalfWord(0x0601);
01810 size += addByte(0x02);
01811 }
01812
01813 addAtomEnd;
01814
01815 addAtom(srcq);
01816
01817 size += addWord(0x00000040);
01818
01819 addAtomEnd;
01820
01821 addAtom(h263);
01822
01823 size += addWord(0x00000000);
01824 size += addWord(0x00000001);
01825
01826 size += addWord(0x00020001);
01827 size += add4ByteString("appl");
01828 size += addWord(0x00000000);
01829 size += addWord(0x000002fc);
01830 unsigned const widthAndHeight = (fMovieWidth<<16)|fMovieHeight;
01831 size += addWord(widthAndHeight);
01832 size += addWord(0x00480000);
01833 size += addWord(0x00480000);
01834 size += addWord(0x00000000);
01835 size += addWord(0x00010548);
01836
01837 size += addWord(0x2e323633);
01838 size += addZeroWords(6);
01839 size += addWord(0x00000018);
01840 size += addHalfWord(0xffff);
01841 addAtomEnd;
01842
01843 addAtom(avc1);
01844
01845 size += addWord(0x00000000);
01846 size += addWord(0x00000001);
01847
01848 size += addWord(0x00000000);
01849 size += add4ByteString("appl");
01850 size += addWord(0x00000000);
01851 size += addWord(0x00000000);
01852 unsigned const widthAndHeight = (fMovieWidth<<16)|fMovieHeight;
01853 size += addWord(widthAndHeight);
01854 size += addWord(0x00480000);
01855 size += addWord(0x00480000);
01856 size += addWord(0x00000000);
01857 size += addWord(0x00010548);
01858
01859 size += addWord(0x2e323634);
01860 size += addZeroWords(6);
01861 size += addWord(0x00000018);
01862 size += addHalfWord(0xffff);
01863 size += addAtom_avcC();
01864 addAtomEnd;
01865
01866 addAtom(avcC);
01867
01868 char* psets = strDup(fCurrentIOState->fOurSubsession.fmtp_spropparametersets());
01869 if (psets == NULL) return 0;
01870
01871 size_t comma_pos = strcspn(psets, ",");
01872 psets[comma_pos] = '\0';
01873 char* sps_b64 = psets;
01874 char* pps_b64 = &psets[comma_pos+1];
01875 unsigned sps_count;
01876 unsigned char* sps_data = base64Decode(sps_b64, sps_count, false);
01877 unsigned pps_count;
01878 unsigned char* pps_data = base64Decode(pps_b64, pps_count, false);
01879
01880
01881 size += addByte(0x01);
01882 size += addByte(sps_data[1]);
01883 size += addByte(sps_data[2]);
01884 size += addByte(sps_data[3]);
01885 size += addByte(0xff);
01886 size += addByte(0xe0 | (sps_count > 0 ? 1 : 0) );
01887 if (sps_count > 0) {
01888 size += addHalfWord(sps_count);
01889 for (unsigned i = 0; i < sps_count; i++) {
01890 size += addByte(sps_data[i]);
01891 }
01892 }
01893 size += addByte(pps_count > 0 ? 1 : 0);
01894 if (pps_count > 0) {
01895 size += addHalfWord(pps_count);
01896 for (unsigned i = 0; i < pps_count; i++) {
01897 size += addByte(pps_data[i]);
01898 }
01899 }
01900
01901
01902 delete[] pps_data; delete[] sps_data;
01903 delete[] psets;
01904 addAtomEnd;
01905
01906 addAtom(mp4v);
01907
01908 size += addWord(0x00000000);
01909 size += addWord(0x00000001);
01910
01911 size += addWord(0x00020001);
01912 size += add4ByteString("appl");
01913 size += addWord(0x00000200);
01914 size += addWord(0x00000400);
01915 unsigned const widthAndHeight = (fMovieWidth<<16)|fMovieHeight;
01916 size += addWord(widthAndHeight);
01917 size += addWord(0x00480000);
01918 size += addWord(0x00480000);
01919 size += addWord(0x00000000);
01920 size += addWord(0x00010c4d);
01921
01922 size += addWord(0x5045472d);
01923 size += addWord(0x34205669);
01924 size += addWord(0x64656f00);
01925 size += addZeroWords(4);
01926 size += addWord(0x00000018);
01927 size += addHalfWord(0xffff);
01928 size += addAtom_esds();
01929 size += addWord(0x00000000);
01930 addAtomEnd;
01931
01932 unsigned QuickTimeFileSink::addAtom_rtp() {
01933 int64_t initFilePosn = TellFile64(fOutFid);
01934 unsigned size = addAtomHeader("rtp ");
01935
01936 size += addWord(0x00000000);
01937 size += addWord(0x00000001);
01938 size += addWord(0x00010001);
01939 size += addWord(1450);
01940
01941 size += addAtom_tims();
01942 addAtomEnd;
01943
01944 addAtom(tims);
01945 size += addWord(fCurrentIOState->fOurSubsession.rtpTimestampFrequency());
01946 addAtomEnd;
01947
01948 addAtom(stts);
01949 size += addWord(0x00000000);
01950
01951
01952
01953 int64_t numEntriesPosition = TellFile64(fOutFid);
01954 size += addWord(0);
01955
01956
01957
01958 unsigned numEntries = 0, numSamplesSoFar = 0;
01959 unsigned prevSampleDuration = 0;
01960 unsigned const samplesPerFrame = fCurrentIOState->fQTSamplesPerFrame;
01961 ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
01962 while (chunk != NULL) {
01963 unsigned const sampleDuration = chunk->fFrameDuration/samplesPerFrame;
01964 if (sampleDuration != prevSampleDuration) {
01965
01966
01967 if (chunk != fCurrentIOState->fHeadChunk) {
01968 ++numEntries;
01969 size += addWord(numSamplesSoFar);
01970 size += addWord(prevSampleDuration);
01971 numSamplesSoFar = 0;
01972 }
01973 }
01974
01975 unsigned const numSamples = chunk->fNumFrames*samplesPerFrame;
01976 numSamplesSoFar += numSamples;
01977 prevSampleDuration = sampleDuration;
01978 chunk = chunk->fNextChunk;
01979 }
01980
01981
01982 ++numEntries;
01983 size += addWord(numSamplesSoFar);
01984 size += addWord(prevSampleDuration);
01985
01986
01987 setWord(numEntriesPosition, numEntries);
01988 addAtomEnd;
01989
01990 addAtom(stss);
01991 size += addWord(0x00000000);
01992
01993
01994
01995 int64_t numEntriesPosition = TellFile64(fOutFid);
01996 size += addWord(0);
01997
01998 unsigned numEntries = 0, numSamplesSoFar = 0;
01999 if (fCurrentIOState->fHeadSyncFrame != NULL) {
02000 SyncFrame* currentSyncFrame = fCurrentIOState->fHeadSyncFrame;
02001 while(currentSyncFrame != NULL) {
02002 ++numEntries;
02003 size += addWord(currentSyncFrame->sfFrameNum);
02004 currentSyncFrame = currentSyncFrame->nextSyncFrame;
02005 }
02006 } else {
02007
02008 unsigned const samplesPerFrame = fCurrentIOState->fQTSamplesPerFrame;
02009 ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02010 while (chunk != NULL) {
02011 unsigned const numSamples = chunk->fNumFrames*samplesPerFrame;
02012 numSamplesSoFar += numSamples;
02013 chunk = chunk->fNextChunk;
02014 }
02015
02016
02017 unsigned i;
02018 for (i = 0; i < numSamplesSoFar; i += 12) {
02019
02020
02021 size += addWord(i+1);
02022 ++numEntries;
02023 }
02024
02025
02026 if (i != (numSamplesSoFar - 1)) {
02027 size += addWord(numSamplesSoFar);
02028 ++numEntries;
02029 }
02030 }
02031
02032
02033 setWord(numEntriesPosition, numEntries);
02034 addAtomEnd;
02035
02036 addAtom(stsc);
02037 size += addWord(0x00000000);
02038
02039
02040
02041 int64_t numEntriesPosition = TellFile64(fOutFid);
02042 size += addWord(0);
02043
02044
02045
02046 unsigned numEntries = 0, chunkNumber = 0;
02047 unsigned prevSamplesPerChunk = ~0;
02048 unsigned const samplesPerFrame = fCurrentIOState->fQTSamplesPerFrame;
02049 ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02050 while (chunk != NULL) {
02051 ++chunkNumber;
02052 unsigned const samplesPerChunk = chunk->fNumFrames*samplesPerFrame;
02053 if (samplesPerChunk != prevSamplesPerChunk) {
02054
02055 ++numEntries;
02056 size += addWord(chunkNumber);
02057 size += addWord(samplesPerChunk);
02058 size += addWord(0x00000001);
02059
02060 prevSamplesPerChunk = samplesPerChunk;
02061 }
02062 chunk = chunk->fNextChunk;
02063 }
02064
02065
02066 setWord(numEntriesPosition, numEntries);
02067 addAtomEnd;
02068
02069 addAtom(stsz);
02070 size += addWord(0x00000000);
02071
02072
02073
02074
02075 Boolean haveSingleEntryTable = True;
02076 double firstBPS = 0.0;
02077 ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02078 while (chunk != NULL) {
02079 double bps
02080 = (double)(chunk->fFrameSize)/(fCurrentIOState->fQTSamplesPerFrame);
02081 if (bps < 1.0) {
02082
02083
02084 break;
02085 }
02086
02087 if (firstBPS == 0.0) {
02088 firstBPS = bps;
02089 } else if (bps != firstBPS) {
02090 haveSingleEntryTable = False;
02091 break;
02092 }
02093
02094 chunk = chunk->fNextChunk;
02095 }
02096
02097 unsigned sampleSize;
02098 if (haveSingleEntryTable) {
02099 if (fCurrentIOState->isHintTrack()
02100 && fCurrentIOState->fHeadChunk != NULL) {
02101 sampleSize = fCurrentIOState->fHeadChunk->fFrameSize
02102 / fCurrentIOState->fQTSamplesPerFrame;
02103 } else {
02104
02105 sampleSize = fCurrentIOState->fQTTimeUnitsPerSample;
02106 }
02107 } else {
02108 sampleSize = 0;
02109 }
02110 size += addWord(sampleSize);
02111 unsigned const totNumSamples = fCurrentIOState->fQTTotNumSamples;
02112 size += addWord(totNumSamples);
02113
02114 if (!haveSingleEntryTable) {
02115
02116
02117 ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02118 while (chunk != NULL) {
02119 unsigned numSamples
02120 = chunk->fNumFrames*(fCurrentIOState->fQTSamplesPerFrame);
02121 unsigned sampleSize
02122 = chunk->fFrameSize/(fCurrentIOState->fQTSamplesPerFrame);
02123 for (unsigned i = 0; i < numSamples; ++i) {
02124 size += addWord(sampleSize);
02125 }
02126
02127 chunk = chunk->fNextChunk;
02128 }
02129 }
02130 addAtomEnd;
02131
02132 addAtom(co64);
02133 size += addWord(0x00000000);
02134 size += addWord(fCurrentIOState->fNumChunks);
02135
02136
02137 ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02138 while (chunk != NULL) {
02139 size += addWord64(chunk->fOffsetInFile);
02140
02141 chunk = chunk->fNextChunk;
02142 }
02143 addAtomEnd;
02144
02145 addAtom(udta);
02146 size += addAtom_name();
02147 size += addAtom_hnti();
02148 size += addAtom_hinf();
02149 addAtomEnd;
02150
02151 addAtom(name);
02152 char description[100];
02153 sprintf(description, "Hinted %s track",
02154 fCurrentIOState->fOurSubsession.mediumName());
02155 size += addArbitraryString(description, False);
02156 addAtomEnd;
02157
02158 addAtom(hnti);
02159 size += addAtom_sdp();
02160 addAtomEnd;
02161
02162 unsigned QuickTimeFileSink::addAtom_sdp() {
02163 int64_t initFilePosn = TellFile64(fOutFid);
02164 unsigned size = addAtomHeader("sdp ");
02165
02166
02167 char const* sdpLines = fCurrentIOState->fOurSubsession.savedSDPLines();
02168
02169
02170 char* newSDPLines = new char[strlen(sdpLines)+100];
02171 char const* searchStr = "a=control:trackid=";
02172 Boolean foundSearchString = False;
02173 char const *p1, *p2, *p3;
02174 for (p1 = sdpLines; *p1 != '\0'; ++p1) {
02175 for (p2 = p1,p3 = searchStr; tolower(*p2) == *p3; ++p2,++p3) {}
02176 if (*p3 == '\0') {
02177
02178 int beforeTrackNumPosn = p2-sdpLines;
02179
02180 int trackNumLength;
02181 if (sscanf(p2, " %*d%n", &trackNumLength) < 0) break;
02182 int afterTrackNumPosn = beforeTrackNumPosn + trackNumLength;
02183
02184
02185 int i;
02186 for (i = 0; i < beforeTrackNumPosn; ++i) newSDPLines[i] = sdpLines[i];
02187 sprintf(&newSDPLines[i], "%d", fCurrentIOState->fTrackID);
02188 i = afterTrackNumPosn;
02189 int j = i + strlen(&newSDPLines[i]);
02190 while (1) {
02191 if ((newSDPLines[j] = sdpLines[i]) == '\0') break;
02192 ++i; ++j;
02193 }
02194
02195 foundSearchString = True;
02196 break;
02197 }
02198 }
02199
02200 if (!foundSearchString) {
02201
02202
02203 sprintf(newSDPLines, "%s%s%d\r\n",
02204 sdpLines, searchStr, fCurrentIOState->fTrackID);
02205 }
02206
02207 size += addArbitraryString(newSDPLines, False);
02208 delete[] newSDPLines;
02209 addAtomEnd;
02210
02211 addAtom(hinf);
02212 size += addAtom_totl();
02213 size += addAtom_npck();
02214 size += addAtom_tpay();
02215 size += addAtom_trpy();
02216 size += addAtom_nump();
02217 size += addAtom_tpyl();
02218
02219 size += addAtom_dmed();
02220 size += addAtom_dimm();
02221 size += addAtom_drep();
02222 size += addAtom_tmin();
02223 size += addAtom_tmax();
02224 size += addAtom_pmax();
02225 size += addAtom_dmax();
02226 size += addAtom_payt();
02227 addAtomEnd;
02228
02229 addAtom(totl);
02230 size += addWord(fCurrentIOState->fHINF.trpy.lo);
02231 addAtomEnd;
02232
02233 addAtom(npck);
02234 size += addWord(fCurrentIOState->fHINF.nump.lo);
02235 addAtomEnd;
02236
02237 addAtom(tpay);
02238 size += addWord(fCurrentIOState->fHINF.tpyl.lo);
02239 addAtomEnd;
02240
02241 addAtom(trpy);
02242 size += addWord(fCurrentIOState->fHINF.trpy.hi);
02243 size += addWord(fCurrentIOState->fHINF.trpy.lo);
02244 addAtomEnd;
02245
02246 addAtom(nump);
02247 size += addWord(fCurrentIOState->fHINF.nump.hi);
02248 size += addWord(fCurrentIOState->fHINF.nump.lo);
02249 addAtomEnd;
02250
02251 addAtom(tpyl);
02252 size += addWord(fCurrentIOState->fHINF.tpyl.hi);
02253 size += addWord(fCurrentIOState->fHINF.tpyl.lo);
02254 addAtomEnd;
02255
02256 addAtom(dmed);
02257 size += addWord(fCurrentIOState->fHINF.dmed.hi);
02258 size += addWord(fCurrentIOState->fHINF.dmed.lo);
02259 addAtomEnd;
02260
02261 addAtom(dimm);
02262 size += addWord(fCurrentIOState->fHINF.dimm.hi);
02263 size += addWord(fCurrentIOState->fHINF.dimm.lo);
02264 addAtomEnd;
02265
02266 addAtom(drep);
02267 size += addWord(0);
02268 size += addWord(0);
02269 addAtomEnd;
02270
02271 addAtom(tmin);
02272 size += addWord(0);
02273 addAtomEnd;
02274
02275 addAtom(tmax);
02276 size += addWord(0);
02277 addAtomEnd;
02278
02279 addAtom(pmax);
02280 size += addWord(fCurrentIOState->fHINF.pmax);
02281 addAtomEnd;
02282
02283 addAtom(dmax);
02284 size += addWord(fCurrentIOState->fHINF.dmax);
02285 addAtomEnd;
02286
02287 addAtom(payt);
02288 MediaSubsession& ourSubsession = fCurrentIOState->fOurSubsession;
02289 RTPSource* rtpSource = ourSubsession.rtpSource();
02290 size += addWord(rtpSource->rtpPayloadFormat());
02291
02292
02293 unsigned rtpmapStringLength = strlen(ourSubsession.codecName()) + 20;
02294 char* rtpmapString = new char[rtpmapStringLength];
02295 sprintf(rtpmapString, "%s/%d",
02296 ourSubsession.codecName(), rtpSource->timestampFrequency());
02297 size += addArbitraryString(rtpmapString);
02298 delete[] rtpmapString;
02299 addAtomEnd;
02300
02301
02302 unsigned QuickTimeFileSink::addAtom_dummy() {
02303 int64_t initFilePosn = TellFile64(fOutFid);
02304 unsigned size = addAtomHeader("????");
02305 addAtomEnd;