📄 quicktimefilesink.cpp
字号:
/**********This library is free software; you can redistribute it and/or modify it underthe terms of the GNU Lesser General Public License as published by theFree Software Foundation; either version 2.1 of the License, or (at youroption) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)This library is distributed in the hope that it will be useful, but WITHOUTANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESSFOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License formore details.You should have received a copy of the GNU Lesser General Public Licensealong with this library; if not, write to the Free Software Foundation, Inc.,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA**********/// "liveMedia"// Copyright (c) 1996-2010 Live Networks, Inc. All rights reserved.// A sink that generates a QuickTime file from a composite media session// Implementation#include "QuickTimeFileSink.hh"#include "QuickTimeGenericRTPSource.hh"#include "GroupsockHelper.hh"#include "InputFile.hh"#include "OutputFile.hh"#include "H263plusVideoRTPSource.hh" // for the special header#include "MPEG4GenericRTPSource.hh" //for "samplingFrequencyFromAudioSpecificConfig()"#include "MPEG4LATMAudioRTPSource.hh" // for "parseGeneralConfigStr()"#include "Base64.hh"#include <ctype.h>#define fourChar(x,y,z,w) ( ((x)<<24)|((y)<<16)|((z)<<8)|(w) )#define H264_IDR_FRAME 0x65 //bit 8 == 0, bits 7-6 (ref) == 3, bits 5-0 (type) == 5////////// SubsessionIOState, ChunkDescriptor ///////////// A structure used to represent the I/O state of each input 'subsession':class ChunkDescriptor {public: ChunkDescriptor(int64_t offsetInFile, unsigned size, unsigned frameSize, unsigned frameDuration, struct timeval presentationTime); virtual ~ChunkDescriptor(); ChunkDescriptor* extendChunk(int64_t newOffsetInFile, unsigned newSize, unsigned newFrameSize, unsigned newFrameDuration, struct timeval newPresentationTime); // this may end up allocating a new chunk insteadpublic: ChunkDescriptor* fNextChunk; int64_t fOffsetInFile; unsigned fNumFrames; unsigned fFrameSize; unsigned fFrameDuration; struct timeval fPresentationTime; // of the start of the data};class SubsessionBuffer {public: SubsessionBuffer(unsigned bufferSize) : fBufferSize(bufferSize) { reset(); fData = new unsigned char[bufferSize]; } virtual ~SubsessionBuffer() { delete[] fData; } void reset() { fBytesInUse = 0; } void addBytes(unsigned numBytes) { fBytesInUse += numBytes; } unsigned char* dataStart() { return &fData[0]; } unsigned char* dataEnd() { return &fData[fBytesInUse]; } unsigned bytesInUse() const { return fBytesInUse; } unsigned bytesAvailable() const { return fBufferSize - fBytesInUse; } void setPresentationTime(struct timeval const& presentationTime) { fPresentationTime = presentationTime; } struct timeval const& presentationTime() const {return fPresentationTime;}private: unsigned fBufferSize; struct timeval fPresentationTime; unsigned char* fData; unsigned fBytesInUse;};class SyncFrame {public: SyncFrame(unsigned frameNum); virtual ~SyncFrame();public: class SyncFrame *nextSyncFrame; unsigned sfFrameNum; };// A 64-bit counter, used below:class Count64 {public: Count64() : hi(0), lo(0) { } void operator+=(unsigned arg); u_int32_t hi, lo;};class SubsessionIOState {public: SubsessionIOState(QuickTimeFileSink& sink, MediaSubsession& subsession); virtual ~SubsessionIOState(); Boolean setQTstate(); void setFinalQTstate(); void afterGettingFrame(unsigned packetDataSize, struct timeval presentationTime); void onSourceClosure(); Boolean syncOK(struct timeval presentationTime); // returns true iff data is usable despite a sync check static void setHintTrack(SubsessionIOState* hintedTrack, SubsessionIOState* hintTrack); Boolean isHintTrack() const { return fTrackHintedByUs != NULL; } Boolean hasHintTrack() const { return fHintTrackForUs != NULL; } UsageEnvironment& envir() const { return fOurSink.envir(); }public: static unsigned fCurrentTrackNumber; unsigned fTrackID; SubsessionIOState* fHintTrackForUs; SubsessionIOState* fTrackHintedByUs; SubsessionBuffer *fBuffer, *fPrevBuffer; QuickTimeFileSink& fOurSink; MediaSubsession& fOurSubsession; unsigned short fLastPacketRTPSeqNum; Boolean fOurSourceIsActive; Boolean fHaveBeenSynced; // used in synchronizing with other streams struct timeval fSyncTime; Boolean fQTEnableTrack; unsigned fQTcomponentSubtype; char const* fQTcomponentName; typedef unsigned (QuickTimeFileSink::*atomCreationFunc)(); atomCreationFunc fQTMediaInformationAtomCreator; atomCreationFunc fQTMediaDataAtomCreator; char const* fQTAudioDataType; unsigned short fQTSoundSampleVersion; unsigned fQTTimeScale; unsigned fQTTimeUnitsPerSample; unsigned fQTBytesPerFrame; unsigned fQTSamplesPerFrame; // These next fields are derived from the ones above, // plus the information from each chunk: unsigned fQTTotNumSamples; unsigned fQTDurationM; // in media time units unsigned fQTDurationT; // in track time units int64_t fTKHD_durationPosn; // position of the duration in the output 'tkhd' atom unsigned fQTInitialOffsetDuration; // if there's a pause at the beginning ChunkDescriptor *fHeadChunk, *fTailChunk; unsigned fNumChunks; SyncFrame *fHeadSyncFrame, *fTailSyncFrame; // Counters to be used in the hint track's 'udta'/'hinf' atom; struct hinf { Count64 trpy; Count64 nump; Count64 tpyl; // Is 'maxr' needed? Computing this would be a PITA. ##### Count64 dmed; Count64 dimm; // 'drep' is always 0 // 'tmin' and 'tmax' are always 0 unsigned pmax; unsigned dmax; } fHINF;private: void useFrame(SubsessionBuffer& buffer); void useFrameForHinting(unsigned frameSize, struct timeval presentationTime, unsigned startSampleNumber); // used by the above two routines: unsigned useFrame1(unsigned sourceDataSize, struct timeval presentationTime, unsigned frameDuration, int64_t destFileOffset); // returns the number of samples in this dataprivate: // A structure used for temporarily storing frame state: struct { unsigned frameSize; struct timeval presentationTime; int64_t destFileOffset; // used for non-hint tracks only // The remaining fields are used for hint tracks only: unsigned startSampleNumber; unsigned short seqNum; unsigned rtpHeader; unsigned char numSpecialHeaders; // used when our RTP source has special headers unsigned specialHeaderBytesLength; // ditto unsigned char specialHeaderBytes[SPECIAL_HEADER_BUFFER_SIZE]; // ditto unsigned packetSizes[256]; } fPrevFrameState;};////////// QuickTimeFileSink implementation //////////QuickTimeFileSink::QuickTimeFileSink(UsageEnvironment& env, MediaSession& inputSession, char const* outputFileName, unsigned bufferSize, unsigned short movieWidth, unsigned short movieHeight, unsigned movieFPS, Boolean packetLossCompensate, Boolean syncStreams, Boolean generateHintTracks, Boolean generateMP4Format) : Medium(env), fInputSession(inputSession), fBufferSize(bufferSize), fPacketLossCompensate(packetLossCompensate), fSyncStreams(syncStreams), fGenerateMP4Format(generateMP4Format), fAreCurrentlyBeingPlayed(False), fLargestRTPtimestampFrequency(0), fNumSubsessions(0), fNumSyncedSubsessions(0), fHaveCompletedOutputFile(False), fMovieWidth(movieWidth), fMovieHeight(movieHeight), fMovieFPS(movieFPS), fMaxTrackDurationM(0) { fOutFid = OpenOutputFile(env, outputFileName); if (fOutFid == NULL) return; fNewestSyncTime.tv_sec = fNewestSyncTime.tv_usec = 0; fFirstDataTime.tv_sec = fFirstDataTime.tv_usec = (unsigned)(~0); // Set up I/O state for each input subsession: MediaSubsessionIterator iter(fInputSession); MediaSubsession* subsession; while ((subsession = iter.next()) != NULL) { // Ignore subsessions without a data source: FramedSource* subsessionSource = subsession->readSource(); if (subsessionSource == NULL) continue; // If "subsession's" SDP description specified screen dimension // or frame rate parameters, then use these. (Note that this must // be done before the call to "setQTState()" below.) if (subsession->videoWidth() != 0) { fMovieWidth = subsession->videoWidth(); } if (subsession->videoHeight() != 0) { fMovieHeight = subsession->videoHeight(); } if (subsession->videoFPS() != 0) { fMovieFPS = subsession->videoFPS(); } SubsessionIOState* ioState = new SubsessionIOState(*this, *subsession); if (ioState == NULL || !ioState->setQTstate()) { // We're not able to output a QuickTime track for this subsession delete ioState; ioState = NULL; continue; } subsession->miscPtr = (void*)ioState; if (generateHintTracks) { // Also create a hint track for this track: SubsessionIOState* hintTrack = new SubsessionIOState(*this, *subsession); SubsessionIOState::setHintTrack(ioState, hintTrack); if (!hintTrack->setQTstate()) { delete hintTrack; SubsessionIOState::setHintTrack(ioState, NULL); } } // Also set a 'BYE' handler for this subsession's RTCP instance: if (subsession->rtcpInstance() != NULL) { subsession->rtcpInstance()->setByeHandler(onRTCPBye, ioState); } unsigned rtpTimestampFrequency = subsession->rtpTimestampFrequency(); if (rtpTimestampFrequency > fLargestRTPtimestampFrequency) { fLargestRTPtimestampFrequency = rtpTimestampFrequency; } ++fNumSubsessions; } // Use the current time as the file's creation and modification // time. Use Apple's time format: seconds since January 1, 1904 gettimeofday(&fStartTime, NULL); fAppleCreationTime = fStartTime.tv_sec - 0x83dac000; // Begin by writing a "mdat" atom at the start of the file. // (Later, when we've finished copying data to the file, we'll come // back and fill in its size.) fMDATposition = TellFile64(fOutFid); addAtomHeader64("mdat"); // add 64Bit offset fMDATposition += 8;}QuickTimeFileSink::~QuickTimeFileSink() { completeOutputFile(); // Then, delete each active "SubsessionIOState": MediaSubsessionIterator iter(fInputSession); MediaSubsession* subsession; while ((subsession = iter.next()) != NULL) { SubsessionIOState* ioState = (SubsessionIOState*)(subsession->miscPtr); if (ioState == NULL) continue; delete ioState->fHintTrackForUs; // if any delete ioState; } // Finally, close our output file: CloseOutputFile(fOutFid);}QuickTimeFileSink*QuickTimeFileSink::createNew(UsageEnvironment& env, MediaSession& inputSession, char const* outputFileName, unsigned bufferSize, unsigned short movieWidth, unsigned short movieHeight, unsigned movieFPS, Boolean packetLossCompensate, Boolean syncStreams, Boolean generateHintTracks, Boolean generateMP4Format) { QuickTimeFileSink* newSink = new QuickTimeFileSink(env, inputSession, outputFileName, bufferSize, movieWidth, movieHeight, movieFPS, packetLossCompensate, syncStreams, generateHintTracks, generateMP4Format); if (newSink == NULL || newSink->fOutFid == NULL) { Medium::close(newSink); return NULL; } return newSink;}Boolean QuickTimeFileSink::startPlaying(afterPlayingFunc* afterFunc, void* afterClientData) { // Make sure we're not already being played: if (fAreCurrentlyBeingPlayed) { envir().setResultMsg("This sink has already been played"); return False; } fAreCurrentlyBeingPlayed = True; fAfterFunc = afterFunc; fAfterClientData = afterClientData;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -