/********** This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. (See .) This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along 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-2020 Live Networks, Inc. All rights reserved. // RTP Sources containing generic QuickTime stream data, as defined in // // Implementation #include "QuickTimeGenericRTPSource.hh" ///// QTGenericBufferedPacket and QTGenericBufferedPacketFactory ///// // A subclass of BufferedPacket, used to separate out // individual frames (when PCK == 2) class QTGenericBufferedPacket: public BufferedPacket { public: QTGenericBufferedPacket(QuickTimeGenericRTPSource& ourSource); virtual ~QTGenericBufferedPacket(); private: // redefined virtual functions virtual unsigned nextEnclosedFrameSize(unsigned char*& framePtr, unsigned dataSize); private: QuickTimeGenericRTPSource& fOurSource; }; class QTGenericBufferedPacketFactory: public BufferedPacketFactory { private: // redefined virtual functions virtual BufferedPacket* createNewPacket(MultiFramedRTPSource* ourSource); }; ////////// QuickTimeGenericRTPSource ////////// QuickTimeGenericRTPSource* QuickTimeGenericRTPSource::createNew(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat, unsigned rtpTimestampFrequency, char const* mimeTypeString) { return new QuickTimeGenericRTPSource(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency, mimeTypeString); } QuickTimeGenericRTPSource ::QuickTimeGenericRTPSource(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat, unsigned rtpTimestampFrequency, char const* mimeTypeString) : MultiFramedRTPSource(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency, new QTGenericBufferedPacketFactory), fMIMEtypeString(strDup(mimeTypeString)) { qtState.PCK = 0; qtState.timescale = 0; qtState.sdAtom = NULL; qtState.sdAtomSize = qtState.width = qtState.height = 0; } QuickTimeGenericRTPSource::~QuickTimeGenericRTPSource() { delete[] qtState.sdAtom; delete[] (char*)fMIMEtypeString; } Boolean QuickTimeGenericRTPSource ::processSpecialHeader(BufferedPacket* packet, unsigned& resultSpecialHeaderSize) { unsigned char* headerStart = packet->data(); unsigned packetSize = packet->dataSize(); // The "QuickTime Header" must be at least 4 bytes in size: // Extract the known fields from the first 4 bytes: unsigned expectedHeaderSize = 4; if (packetSize < expectedHeaderSize) return False; unsigned char VER = (headerStart[0]&0xF0)>>4; if (VER > 1) return False; // unknown header version qtState.PCK = (headerStart[0]&0x0C)>>2; #ifdef DEBUG Boolean S = (headerStart[0]&0x02) != 0; #endif Boolean Q = (headerStart[0]&0x01) != 0; Boolean L = (headerStart[1]&0x80) != 0; #ifdef DEBUG Boolean D = (headerStart[2]&0x80) != 0; unsigned short payloadId = ((headerStart[2]&0x7F)<<8)|headerStart[3]; #endif headerStart += 4; #ifdef DEBUG fprintf(stderr, "PCK: %d, S: %d, Q: %d, L: %d, D: %d, payloadId: %d\n", qtState.PCK, S, Q, L, D, payloadId); #endif if (Q) { // A "QuickTime Payload Description" follows expectedHeaderSize += 4; if (packetSize < expectedHeaderSize) return False; #ifdef DEBUG Boolean K = (headerStart[0]&0x80) != 0; Boolean F = (headerStart[0]&0x40) != 0; Boolean A = (headerStart[0]&0x20) != 0; Boolean Z = (headerStart[0]&0x10) != 0; #endif unsigned payloadDescriptionLength = (headerStart[2]<<8)|headerStart[3]; headerStart += 4; #ifdef DEBUG fprintf(stderr, "\tK: %d, F: %d, A: %d, Z: %d, payloadDescriptionLength: %d\n", K, F, A, Z, payloadDescriptionLength); #endif // Make sure "payloadDescriptionLength" is valid if (payloadDescriptionLength < 12) return False; expectedHeaderSize += (payloadDescriptionLength - 4); unsigned nonPaddedSize = expectedHeaderSize; expectedHeaderSize += 3; expectedHeaderSize -= expectedHeaderSize%4; // adds padding if (packetSize < expectedHeaderSize) return False; unsigned char padding = expectedHeaderSize - nonPaddedSize; #ifdef DEBUG unsigned mediaType = (headerStart[0]<<24)|(headerStart[1]<<16) |(headerStart[2]<<8)|headerStart[3]; #endif qtState.timescale = (headerStart[4]<<24)|(headerStart[5]<<16) |(headerStart[6]<<8)|headerStart[7]; headerStart += 8; payloadDescriptionLength -= 12; #ifdef DEBUG fprintf(stderr, "\tmediaType: '%c%c%c%c', timescale: %d, %d bytes of TLVs left\n", mediaType>>24, (mediaType&0xFF0000)>>16, (mediaType&0xFF00)>>8, mediaType&0xFF, qtState.timescale, payloadDescriptionLength); #endif while (payloadDescriptionLength > 3) { unsigned short tlvLength = (headerStart[0]<<8)|headerStart[1]; unsigned short tlvType = (headerStart[2]<<8)|headerStart[3]; payloadDescriptionLength -= 4; if (tlvLength > payloadDescriptionLength) return False; // bad TLV headerStart += 4; #ifdef DEBUG fprintf(stderr, "\t\tTLV '%c%c', length %d, leaving %d remaining bytes\n", tlvType>>8, tlvType&0xFF, tlvLength, payloadDescriptionLength - tlvLength); for (int i = 0; i < tlvLength; ++i) fprintf(stderr, "%02x:", headerStart[i]); fprintf(stderr, "\n"); #endif // Check for 'TLV's that we can use for our 'qtState' switch (tlvType) { case ('s'<<8|'d'): { // session description atom // Sanity check: the first 4 bytes of this must equal "tlvLength": unsigned atomLength = (headerStart[0]<<24)|(headerStart[1]<<16) |(headerStart[2]<<8)|(headerStart[3]); if (atomLength != (unsigned)tlvLength) break; delete[] qtState.sdAtom; qtState.sdAtom = new char[tlvLength]; memmove(qtState.sdAtom, headerStart, tlvLength); qtState.sdAtomSize = tlvLength; break; } case ('t'<<8|'w'): { // track width qtState.width = (headerStart[0]<<8)|headerStart[1]; break; } case ('t'<<8|'h'): { // track height qtState.height = (headerStart[0]<<8)|headerStart[1]; break; } } payloadDescriptionLength -= tlvLength; headerStart += tlvLength; } if (payloadDescriptionLength > 0) return False; // malformed TLV data headerStart += padding; } if (L) { // Sample-Specific info follows expectedHeaderSize += 4; if (packetSize < expectedHeaderSize) return False; unsigned ssInfoLength = (headerStart[2]<<8)|headerStart[3]; headerStart += 4; #ifdef DEBUG fprintf(stderr, "\tssInfoLength: %d\n", ssInfoLength); #endif // Make sure "ssInfoLength" is valid if (ssInfoLength < 4) return False; expectedHeaderSize += (ssInfoLength - 4); unsigned nonPaddedSize = expectedHeaderSize; expectedHeaderSize += 3; expectedHeaderSize -= expectedHeaderSize%4; // adds padding if (packetSize < expectedHeaderSize) return False; unsigned char padding = expectedHeaderSize - nonPaddedSize; ssInfoLength -= 4; while (ssInfoLength > 3) { unsigned short tlvLength = (headerStart[0]<<8)|headerStart[1]; #ifdef DEBUG unsigned short tlvType = (headerStart[2]<<8)|headerStart[3]; #endif ssInfoLength -= 4; if (tlvLength > ssInfoLength) return False; // bad TLV #ifdef DEBUG fprintf(stderr, "\t\tTLV '%c%c', length %d, leaving %d remaining bytes\n", tlvType>>8, tlvType&0xFF, tlvLength, ssInfoLength - tlvLength); for (int i = 0; i < tlvLength; ++i) fprintf(stderr, "%02x:", headerStart[4+i]); fprintf(stderr, "\n"); #endif ssInfoLength -= tlvLength; headerStart += 4 + tlvLength; } if (ssInfoLength > 0) return False; // malformed TLV data headerStart += padding; } fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame; // whether the *previous* packet ended a frame fCurrentPacketCompletesFrame = packet->rtpMarkerBit(); resultSpecialHeaderSize = expectedHeaderSize; #ifdef DEBUG fprintf(stderr, "Result special header size: %d\n", resultSpecialHeaderSize); #endif return True; } char const* QuickTimeGenericRTPSource::MIMEtype() const { if (fMIMEtypeString == NULL) return MultiFramedRTPSource::MIMEtype(); return fMIMEtypeString; } ////////// QTGenericBufferedPacket and QTGenericBufferedPacketFactory impl QTGenericBufferedPacket ::QTGenericBufferedPacket(QuickTimeGenericRTPSource& ourSource) : fOurSource(ourSource) { } QTGenericBufferedPacket::~QTGenericBufferedPacket() { } unsigned QTGenericBufferedPacket:: nextEnclosedFrameSize(unsigned char*& framePtr, unsigned dataSize) { // We use the entire packet for a frame, unless "PCK" == 2 if (fOurSource.qtState.PCK != 2) return dataSize; if (dataSize < 8) return 0; // sanity check unsigned short sampleLength = (framePtr[2]<<8)|framePtr[3]; // later, extract and use the "timestamp" field ##### framePtr += 8; dataSize -= 8; return sampleLength < dataSize ? sampleLength : dataSize; } BufferedPacket* QTGenericBufferedPacketFactory ::createNewPacket(MultiFramedRTPSource* ourSource) { return new QTGenericBufferedPacket((QuickTimeGenericRTPSource&)(*ourSource)); }