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