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