| /********** |
| 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 <http://www.gnu.org/copyleft/lesser.html>.) |
| |
| 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. |
| // AMR Audio RTP Sources (RFC 4867) |
| // Implementation |
| |
| #include "AMRAudioRTPSource.hh" |
| #include "MultiFramedRTPSource.hh" |
| #include "BitVector.hh" |
| #include <string.h> |
| #include <stdlib.h> |
| |
| // This source is implemented internally by two separate sources: |
| // (i) a RTP source for the raw (and possibly interleaved) AMR frames, and |
| // (ii) a deinterleaving filter that reads from this. |
| // Define these two new classes here: |
| |
| class RawAMRRTPSource: public MultiFramedRTPSource { |
| public: |
| static RawAMRRTPSource* |
| createNew(UsageEnvironment& env, |
| Groupsock* RTPgs, unsigned char rtpPayloadFormat, |
| Boolean isWideband, Boolean isOctetAligned, |
| Boolean isInterleaved, Boolean CRCsArePresent); |
| |
| Boolean isWideband() const { return fIsWideband; } |
| unsigned char ILL() const { return fILL; } |
| unsigned char ILP() const { return fILP; } |
| unsigned TOCSize() const { return fTOCSize; } // total # of frames in the last pkt |
| unsigned char* TOC() const { return fTOC; } // FT+Q value for each TOC entry |
| unsigned& frameIndex() { return fFrameIndex; } // index of frame-block within pkt |
| Boolean& isSynchronized() { return fIsSynchronized; } |
| |
| private: |
| RawAMRRTPSource(UsageEnvironment& env, Groupsock* RTPgs, |
| unsigned char rtpPayloadFormat, |
| Boolean isWideband, Boolean isOctetAligned, |
| Boolean isInterleaved, Boolean CRCsArePresent); |
| // called only by createNew() |
| |
| virtual ~RawAMRRTPSource(); |
| |
| private: |
| // redefined virtual functions: |
| virtual Boolean hasBeenSynchronizedUsingRTCP(); |
| |
| virtual Boolean processSpecialHeader(BufferedPacket* packet, |
| unsigned& resultSpecialHeaderSize); |
| virtual char const* MIMEtype() const; |
| |
| private: |
| Boolean fIsWideband, fIsOctetAligned, fIsInterleaved, fCRCsArePresent; |
| unsigned char fILL, fILP; |
| unsigned fTOCSize; |
| unsigned char* fTOC; |
| unsigned fFrameIndex; |
| Boolean fIsSynchronized; |
| }; |
| |
| class AMRDeinterleaver: public AMRAudioSource { |
| public: |
| static AMRDeinterleaver* |
| createNew(UsageEnvironment& env, |
| Boolean isWideband, unsigned numChannels, unsigned maxInterleaveGroupSize, |
| RawAMRRTPSource* inputSource); |
| |
| private: |
| AMRDeinterleaver(UsageEnvironment& env, |
| Boolean isWideband, unsigned numChannels, |
| unsigned maxInterleaveGroupSize, RawAMRRTPSource* inputSource); |
| // called only by "createNew()" |
| |
| virtual ~AMRDeinterleaver(); |
| |
| static void afterGettingFrame(void* clientData, unsigned frameSize, |
| unsigned numTruncatedBytes, |
| struct timeval presentationTime, |
| unsigned durationInMicroseconds); |
| void afterGettingFrame1(unsigned frameSize, struct timeval presentationTime); |
| |
| private: |
| // Redefined virtual functions: |
| void doGetNextFrame(); |
| virtual void doStopGettingFrames(); |
| |
| private: |
| RawAMRRTPSource* fInputSource; |
| class AMRDeinterleavingBuffer* fDeinterleavingBuffer; |
| Boolean fNeedAFrame; |
| }; |
| |
| |
| ////////// AMRAudioRTPSource implementation ////////// |
| |
| #define MAX_NUM_CHANNELS 20 // far larger than ever expected... |
| #define MAX_INTERLEAVING_GROUP_SIZE 1000 // far larger than ever expected... |
| |
| AMRAudioSource* |
| AMRAudioRTPSource::createNew(UsageEnvironment& env, |
| Groupsock* RTPgs, |
| RTPSource*& resultRTPSource, |
| unsigned char rtpPayloadFormat, |
| Boolean isWideband, |
| unsigned numChannels, |
| Boolean isOctetAligned, |
| unsigned interleaving, |
| Boolean robustSortingOrder, |
| Boolean CRCsArePresent) { |
| // Perform sanity checks on the input parameters: |
| if (robustSortingOrder) { |
| env << "AMRAudioRTPSource::createNew(): 'Robust sorting order' was specified, but we don't yet support this!\n"; |
| return NULL; |
| } else if (numChannels > MAX_NUM_CHANNELS) { |
| env << "AMRAudioRTPSource::createNew(): The \"number of channels\" parameter (" |
| << numChannels << ") is much too large!\n"; |
| return NULL; |
| } else if (interleaving > MAX_INTERLEAVING_GROUP_SIZE) { |
| env << "AMRAudioRTPSource::createNew(): The \"interleaving\" parameter (" |
| << interleaving << ") is much too large!\n"; |
| return NULL; |
| } |
| |
| // 'Bandwidth-efficient mode' precludes some other options: |
| if (!isOctetAligned) { |
| if (interleaving > 0 || robustSortingOrder || CRCsArePresent) { |
| env << "AMRAudioRTPSource::createNew(): 'Bandwidth-efficient mode' was specified, along with interleaving, 'robust sorting order', and/or CRCs, so we assume 'octet-aligned mode' instead.\n"; |
| isOctetAligned = True; |
| } |
| } |
| |
| Boolean isInterleaved; |
| unsigned maxInterleaveGroupSize; // in frames (not frame-blocks) |
| if (interleaving > 0) { |
| isInterleaved = True; |
| maxInterleaveGroupSize = interleaving*numChannels; |
| } else { |
| isInterleaved = False; |
| maxInterleaveGroupSize = numChannels; |
| } |
| |
| RawAMRRTPSource* rawRTPSource; |
| resultRTPSource = rawRTPSource |
| = RawAMRRTPSource::createNew(env, RTPgs, rtpPayloadFormat, |
| isWideband, isOctetAligned, |
| isInterleaved, CRCsArePresent); |
| if (resultRTPSource == NULL) return NULL; |
| |
| AMRDeinterleaver* deinterleaver |
| = AMRDeinterleaver::createNew(env, isWideband, numChannels, |
| maxInterleaveGroupSize, rawRTPSource); |
| if (deinterleaver == NULL) { |
| Medium::close(resultRTPSource); |
| resultRTPSource = NULL; |
| } |
| |
| return deinterleaver; |
| } |
| |
| |
| ////////// AMRBufferedPacket and AMRBufferedPacketFactory ////////// |
| |
| // A subclass of BufferedPacket, used to separate out AMR frames. |
| |
| class AMRBufferedPacket: public BufferedPacket { |
| public: |
| AMRBufferedPacket(RawAMRRTPSource& ourSource); |
| virtual ~AMRBufferedPacket(); |
| |
| private: // redefined virtual functions |
| virtual unsigned nextEnclosedFrameSize(unsigned char*& framePtr, |
| unsigned dataSize); |
| private: |
| RawAMRRTPSource& fOurSource; |
| }; |
| |
| class AMRBufferedPacketFactory: public BufferedPacketFactory { |
| private: // redefined virtual functions |
| virtual BufferedPacket* createNewPacket(MultiFramedRTPSource* ourSource); |
| }; |
| |
| |
| ///////// RawAMRRTPSource implementation //////// |
| |
| RawAMRRTPSource* |
| RawAMRRTPSource::createNew(UsageEnvironment& env, Groupsock* RTPgs, |
| unsigned char rtpPayloadFormat, |
| Boolean isWideband, Boolean isOctetAligned, |
| Boolean isInterleaved, Boolean CRCsArePresent) { |
| return new RawAMRRTPSource(env, RTPgs, rtpPayloadFormat, |
| isWideband, isOctetAligned, |
| isInterleaved, CRCsArePresent); |
| } |
| |
| RawAMRRTPSource |
| ::RawAMRRTPSource(UsageEnvironment& env, |
| Groupsock* RTPgs, unsigned char rtpPayloadFormat, |
| Boolean isWideband, Boolean isOctetAligned, |
| Boolean isInterleaved, Boolean CRCsArePresent) |
| : MultiFramedRTPSource(env, RTPgs, rtpPayloadFormat, |
| isWideband ? 16000 : 8000, |
| new AMRBufferedPacketFactory), |
| fIsWideband(isWideband), fIsOctetAligned(isOctetAligned), |
| fIsInterleaved(isInterleaved), fCRCsArePresent(CRCsArePresent), |
| fILL(0), fILP(0), fTOCSize(0), fTOC(NULL), fFrameIndex(0), fIsSynchronized(False) { |
| } |
| |
| RawAMRRTPSource::~RawAMRRTPSource() { |
| delete[] fTOC; |
| } |
| |
| #define FT_SPEECH_LOST 14 |
| #define FT_NO_DATA 15 |
| |
| static void unpackBandwidthEfficientData(BufferedPacket* packet, |
| Boolean isWideband); // forward |
| |
| Boolean RawAMRRTPSource |
| ::processSpecialHeader(BufferedPacket* packet, |
| unsigned& resultSpecialHeaderSize) { |
| // If the data is 'bandwidth-efficient', first unpack it so that it's |
| // 'octet-aligned': |
| if (!fIsOctetAligned) unpackBandwidthEfficientData(packet, fIsWideband); |
| |
| unsigned char* headerStart = packet->data(); |
| unsigned packetSize = packet->dataSize(); |
| |
| // There's at least a 1-byte header, containing the CMR: |
| if (packetSize < 1) return False; |
| resultSpecialHeaderSize = 1; |
| |
| if (fIsInterleaved) { |
| // There's an extra byte, containing the interleave parameters: |
| if (packetSize < 2) return False; |
| |
| // Get the interleaving parameters, and check them for validity: |
| unsigned char const secondByte = headerStart[1]; |
| fILL = (secondByte&0xF0)>>4; |
| fILP = secondByte&0x0F; |
| if (fILP > fILL) return False; // invalid |
| ++resultSpecialHeaderSize; |
| } |
| #ifdef DEBUG |
| fprintf(stderr, "packetSize: %d, ILL: %d, ILP: %d\n", packetSize, fILL, fILP); |
| #endif |
| fFrameIndex = 0; // initially |
| |
| // Next, there's a "Payload Table of Contents" (one byte per entry): |
| unsigned numFramesPresent = 0, numNonEmptyFramesPresent = 0; |
| unsigned tocStartIndex = resultSpecialHeaderSize; |
| Boolean F; |
| do { |
| if (resultSpecialHeaderSize >= packetSize) return False; |
| unsigned char const tocByte = headerStart[resultSpecialHeaderSize++]; |
| F = (tocByte&0x80) != 0; |
| unsigned char const FT = (tocByte&0x78) >> 3; |
| #ifdef DEBUG |
| unsigned char Q = (tocByte&0x04)>>2; |
| fprintf(stderr, "\tTOC entry: F %d, FT %d, Q %d\n", F, FT, Q); |
| #endif |
| ++numFramesPresent; |
| if (FT != FT_SPEECH_LOST && FT != FT_NO_DATA) ++numNonEmptyFramesPresent; |
| } while (F); |
| #ifdef DEBUG |
| fprintf(stderr, "TOC contains %d entries (%d non-empty)\n", numFramesPresent, numNonEmptyFramesPresent); |
| #endif |
| |
| // Now that we know the size of the TOC, fill in our copy: |
| if (numFramesPresent > fTOCSize) { |
| delete[] fTOC; |
| fTOC = new unsigned char[numFramesPresent]; |
| } |
| fTOCSize = numFramesPresent; |
| for (unsigned i = 0; i < fTOCSize; ++i) { |
| unsigned char const tocByte = headerStart[tocStartIndex + i]; |
| fTOC[i] = tocByte&0x7C; // clear everything except the F and Q fields |
| } |
| |
| if (fCRCsArePresent) { |
| // 'numNonEmptyFramesPresent' CRC bytes will follow. |
| // Note: we currently don't check the CRCs for validity ##### |
| resultSpecialHeaderSize += numNonEmptyFramesPresent; |
| #ifdef DEBUG |
| fprintf(stderr, "Ignoring %d following CRC bytes\n", numNonEmptyFramesPresent); |
| #endif |
| if (resultSpecialHeaderSize > packetSize) return False; |
| } |
| #ifdef DEBUG |
| fprintf(stderr, "Total special header size: %d\n", resultSpecialHeaderSize); |
| #endif |
| |
| return True; |
| } |
| |
| char const* RawAMRRTPSource::MIMEtype() const { |
| return fIsWideband ? "audio/AMR-WB" : "audio/AMR"; |
| } |
| |
| Boolean RawAMRRTPSource::hasBeenSynchronizedUsingRTCP() { |
| return fIsSynchronized; |
| } |
| |
| |
| ///// AMRBufferedPacket and AMRBufferedPacketFactory implementation |
| |
| AMRBufferedPacket::AMRBufferedPacket(RawAMRRTPSource& ourSource) |
| : fOurSource(ourSource) { |
| } |
| |
| AMRBufferedPacket::~AMRBufferedPacket() { |
| } |
| |
| // The mapping from the "FT" field to frame size. |
| // Values of 65535 are invalid. |
| #define FT_INVALID 65535 |
| static unsigned short const frameBytesFromFT[16] = { |
| 12, 13, 15, 17, |
| 19, 20, 26, 31, |
| 5, FT_INVALID, FT_INVALID, FT_INVALID, |
| FT_INVALID, FT_INVALID, FT_INVALID, 0 |
| }; |
| static unsigned short const frameBytesFromFTWideband[16] = { |
| 17, 23, 32, 36, |
| 40, 46, 50, 58, |
| 60, 5, FT_INVALID, FT_INVALID, |
| FT_INVALID, FT_INVALID, 0, 0 |
| }; |
| |
| unsigned AMRBufferedPacket:: |
| nextEnclosedFrameSize(unsigned char*& framePtr, unsigned dataSize) { |
| if (dataSize == 0) return 0; // sanity check |
| |
| // The size of the AMR frame is determined by the corresponding 'FT' value |
| // in the packet's Table of Contents. |
| unsigned const tocIndex = fOurSource.frameIndex(); |
| if (tocIndex >= fOurSource.TOCSize()) return 0; // sanity check |
| |
| unsigned char const tocByte = fOurSource.TOC()[tocIndex]; |
| unsigned char const FT = (tocByte&0x78) >> 3; |
| // ASSERT: FT < 16 |
| unsigned short frameSize |
| = fOurSource.isWideband() ? frameBytesFromFTWideband[FT] : frameBytesFromFT[FT]; |
| if (frameSize == FT_INVALID) { |
| // Strange TOC entry! |
| fOurSource.envir() << "AMRBufferedPacket::nextEnclosedFrameSize(): invalid FT: " << FT << "\n"; |
| frameSize = 0; // This probably messes up the rest of this packet, but... |
| } |
| #ifdef DEBUG |
| fprintf(stderr, "AMRBufferedPacket::nextEnclosedFrameSize(): frame #: %d, FT: %d, isWideband: %d => frameSize: %d (dataSize: %d)\n", tocIndex, FT, fOurSource.isWideband(), frameSize, dataSize); |
| #endif |
| ++fOurSource.frameIndex(); |
| |
| if (dataSize < frameSize) return 0; |
| return frameSize; |
| } |
| |
| BufferedPacket* AMRBufferedPacketFactory |
| ::createNewPacket(MultiFramedRTPSource* ourSource) { |
| return new AMRBufferedPacket((RawAMRRTPSource&)(*ourSource)); |
| } |
| |
| ///////// AMRDeinterleavingBuffer ///////// |
| // (used to implement AMRDeinterleaver) |
| |
| #define AMR_MAX_FRAME_SIZE 60 |
| |
| class AMRDeinterleavingBuffer { |
| public: |
| AMRDeinterleavingBuffer(unsigned numChannels, unsigned maxInterleaveGroupSize); |
| virtual ~AMRDeinterleavingBuffer(); |
| |
| void deliverIncomingFrame(unsigned frameSize, RawAMRRTPSource* source, |
| struct timeval presentationTime); |
| Boolean retrieveFrame(unsigned char* to, unsigned maxSize, |
| unsigned& resultFrameSize, unsigned& resultNumTruncatedBytes, |
| u_int8_t& resultFrameHeader, |
| struct timeval& resultPresentationTime, |
| Boolean& resultIsSynchronized); |
| |
| unsigned char* inputBuffer() { return fInputBuffer; } |
| unsigned inputBufferSize() const { return AMR_MAX_FRAME_SIZE; } |
| |
| private: |
| unsigned char* createNewBuffer(); |
| |
| class FrameDescriptor { |
| public: |
| FrameDescriptor(); |
| virtual ~FrameDescriptor(); |
| |
| unsigned frameSize; |
| unsigned char* frameData; |
| u_int8_t frameHeader; |
| struct timeval presentationTime; |
| Boolean fIsSynchronized; |
| }; |
| |
| unsigned fNumChannels, fMaxInterleaveGroupSize; |
| FrameDescriptor* fFrames[2]; |
| unsigned char fIncomingBankId; // toggles between 0 and 1 |
| unsigned char fIncomingBinMax; // in the incoming bank |
| unsigned char fOutgoingBinMax; // in the outgoing bank |
| unsigned char fNextOutgoingBin; |
| Boolean fHaveSeenPackets; |
| u_int16_t fLastPacketSeqNumForGroup; |
| unsigned char* fInputBuffer; |
| struct timeval fLastRetrievedPresentationTime; |
| unsigned fNumSuccessiveSyncedFrames; |
| unsigned char fILL; |
| }; |
| |
| |
| ////////// AMRDeinterleaver implementation ///////// |
| |
| AMRDeinterleaver* AMRDeinterleaver |
| ::createNew(UsageEnvironment& env, |
| Boolean isWideband, unsigned numChannels, unsigned maxInterleaveGroupSize, |
| RawAMRRTPSource* inputSource) { |
| return new AMRDeinterleaver(env, isWideband, numChannels, maxInterleaveGroupSize, inputSource); |
| } |
| |
| AMRDeinterleaver::AMRDeinterleaver(UsageEnvironment& env, |
| Boolean isWideband, unsigned numChannels, |
| unsigned maxInterleaveGroupSize, |
| RawAMRRTPSource* inputSource) |
| : AMRAudioSource(env, isWideband, numChannels), |
| fInputSource(inputSource), fNeedAFrame(False) { |
| fDeinterleavingBuffer |
| = new AMRDeinterleavingBuffer(numChannels, maxInterleaveGroupSize); |
| } |
| |
| AMRDeinterleaver::~AMRDeinterleaver() { |
| delete fDeinterleavingBuffer; |
| Medium::close(fInputSource); |
| } |
| |
| static unsigned const uSecsPerFrame = 20000; // 20 ms |
| |
| void AMRDeinterleaver::doGetNextFrame() { |
| // First, try getting a frame from the deinterleaving buffer: |
| if (fDeinterleavingBuffer->retrieveFrame(fTo, fMaxSize, |
| fFrameSize, fNumTruncatedBytes, |
| fLastFrameHeader, fPresentationTime, |
| fInputSource->isSynchronized())) { |
| |
| // Success! |
| fNeedAFrame = False; |
| |
| fDurationInMicroseconds = uSecsPerFrame; |
| |
| // Call our own 'after getting' function. Because we're not a 'leaf' |
| // source, we can call this directly, without risking |
| // infinite recursion |
| afterGetting(this); |
| return; |
| } |
| |
| // No luck, so ask our source for help: |
| fNeedAFrame = True; |
| if (!fInputSource->isCurrentlyAwaitingData()) { |
| fInputSource->getNextFrame(fDeinterleavingBuffer->inputBuffer(), |
| fDeinterleavingBuffer->inputBufferSize(), |
| afterGettingFrame, this, |
| FramedSource::handleClosure, this); |
| } |
| } |
| |
| void AMRDeinterleaver::doStopGettingFrames() { |
| fNeedAFrame = False; |
| fInputSource->stopGettingFrames(); |
| } |
| |
| void AMRDeinterleaver |
| ::afterGettingFrame(void* clientData, unsigned frameSize, |
| unsigned /*numTruncatedBytes*/, |
| struct timeval presentationTime, |
| unsigned /*durationInMicroseconds*/) { |
| AMRDeinterleaver* deinterleaver = (AMRDeinterleaver*)clientData; |
| deinterleaver->afterGettingFrame1(frameSize, presentationTime); |
| } |
| |
| void AMRDeinterleaver |
| ::afterGettingFrame1(unsigned frameSize, struct timeval presentationTime) { |
| RawAMRRTPSource* source = (RawAMRRTPSource*)fInputSource; |
| |
| // First, put the frame into our deinterleaving buffer: |
| fDeinterleavingBuffer->deliverIncomingFrame(frameSize, source, presentationTime); |
| |
| // Then, try delivering a frame to the client (if he wants one): |
| if (fNeedAFrame) doGetNextFrame(); |
| } |
| |
| |
| ////////// AMRDeinterleavingBuffer implementation ///////// |
| |
| AMRDeinterleavingBuffer |
| ::AMRDeinterleavingBuffer(unsigned numChannels, unsigned maxInterleaveGroupSize) |
| : fNumChannels(numChannels), fMaxInterleaveGroupSize(maxInterleaveGroupSize), |
| fIncomingBankId(0), fIncomingBinMax(0), |
| fOutgoingBinMax(0), fNextOutgoingBin(0), |
| fHaveSeenPackets(False), fNumSuccessiveSyncedFrames(0), fILL(0) { |
| // Use two banks of descriptors - one for incoming, one for outgoing |
| fFrames[0] = new FrameDescriptor[fMaxInterleaveGroupSize]; |
| fFrames[1] = new FrameDescriptor[fMaxInterleaveGroupSize]; |
| fInputBuffer = createNewBuffer(); |
| } |
| |
| AMRDeinterleavingBuffer::~AMRDeinterleavingBuffer() { |
| delete[] fInputBuffer; |
| delete[] fFrames[0]; delete[] fFrames[1]; |
| } |
| |
| void AMRDeinterleavingBuffer |
| ::deliverIncomingFrame(unsigned frameSize, RawAMRRTPSource* source, |
| struct timeval presentationTime) { |
| fILL = source->ILL(); |
| unsigned char const ILP = source->ILP(); |
| unsigned frameIndex = source->frameIndex(); |
| unsigned short packetSeqNum = source->curPacketRTPSeqNum(); |
| |
| // First perform a sanity check on the parameters: |
| // (This is overkill, as the source should have already done this.) |
| if (ILP > fILL || frameIndex == 0) { |
| #ifdef DEBUG |
| fprintf(stderr, "AMRDeinterleavingBuffer::deliverIncomingFrame() param sanity check failed (%d,%d,%d,%d)\n", frameSize, fILL, ILP, frameIndex); |
| #endif |
| source->envir().internalError(); |
| } |
| |
| --frameIndex; // because it was incremented by the source when this frame was read |
| u_int8_t frameHeader; |
| if (frameIndex >= source->TOCSize()) { // sanity check |
| frameHeader = FT_NO_DATA<<3; |
| } else { |
| frameHeader = source->TOC()[frameIndex]; |
| } |
| |
| unsigned frameBlockIndex = frameIndex/fNumChannels; |
| unsigned frameWithinFrameBlock = frameIndex%fNumChannels; |
| |
| // The input "presentationTime" was that of the first frame-block in this |
| // packet. Update it for the current frame: |
| unsigned uSecIncrement = frameBlockIndex*(fILL+1)*uSecsPerFrame; |
| presentationTime.tv_usec += uSecIncrement; |
| presentationTime.tv_sec += presentationTime.tv_usec/1000000; |
| presentationTime.tv_usec = presentationTime.tv_usec%1000000; |
| |
| // Next, check whether this packet is part of a new interleave group |
| if (!fHaveSeenPackets |
| || seqNumLT(fLastPacketSeqNumForGroup, packetSeqNum + frameBlockIndex)) { |
| // We've moved to a new interleave group |
| #ifdef DEBUG |
| fprintf(stderr, "AMRDeinterleavingBuffer::deliverIncomingFrame(): new interleave group\n"); |
| #endif |
| fHaveSeenPackets = True; |
| fLastPacketSeqNumForGroup = packetSeqNum + fILL - ILP; |
| |
| // Switch the incoming and outgoing banks: |
| fIncomingBankId ^= 1; |
| unsigned char tmp = fIncomingBinMax; |
| fIncomingBinMax = fOutgoingBinMax; |
| fOutgoingBinMax = tmp; |
| fNextOutgoingBin = 0; |
| } |
| |
| // Now move the incoming frame into the appropriate bin: |
| unsigned const binNumber |
| = ((ILP + frameBlockIndex*(fILL+1))*fNumChannels + frameWithinFrameBlock) |
| % fMaxInterleaveGroupSize; // the % is for sanity |
| #ifdef DEBUG |
| fprintf(stderr, "AMRDeinterleavingBuffer::deliverIncomingFrame(): frameIndex %d (%d,%d) put in bank %d, bin %d (%d): size %d, header 0x%02x, presentationTime %lu.%06ld\n", frameIndex, frameBlockIndex, frameWithinFrameBlock, fIncomingBankId, binNumber, fMaxInterleaveGroupSize, frameSize, frameHeader, presentationTime.tv_sec, presentationTime.tv_usec); |
| #endif |
| FrameDescriptor& inBin = fFrames[fIncomingBankId][binNumber]; |
| unsigned char* curBuffer = inBin.frameData; |
| inBin.frameData = fInputBuffer; |
| inBin.frameSize = frameSize; |
| inBin.frameHeader = frameHeader; |
| inBin.presentationTime = presentationTime; |
| inBin.fIsSynchronized = ((RTPSource*)source)->RTPSource::hasBeenSynchronizedUsingRTCP(); |
| |
| if (curBuffer == NULL) curBuffer = createNewBuffer(); |
| fInputBuffer = curBuffer; |
| |
| if (binNumber >= fIncomingBinMax) { |
| fIncomingBinMax = binNumber + 1; |
| } |
| } |
| |
| Boolean AMRDeinterleavingBuffer |
| ::retrieveFrame(unsigned char* to, unsigned maxSize, |
| unsigned& resultFrameSize, unsigned& resultNumTruncatedBytes, |
| u_int8_t& resultFrameHeader, |
| struct timeval& resultPresentationTime, |
| Boolean& resultIsSynchronized) { |
| |
| if (fNextOutgoingBin >= fOutgoingBinMax) return False; // none left |
| |
| FrameDescriptor& outBin = fFrames[fIncomingBankId^1][fNextOutgoingBin]; |
| unsigned char* fromPtr = outBin.frameData; |
| unsigned char fromSize = outBin.frameSize; |
| outBin.frameSize = 0; // for the next time this bin is used |
| resultIsSynchronized = False; // by default; can be changed by: |
| if (outBin.fIsSynchronized) { |
| // Don't consider the outgoing frame to be synchronized until we've received at least a complete interleave cycle of |
| // synchronized frames. This ensures that the receiver will be getting all synchronized frames from now on. |
| if (++fNumSuccessiveSyncedFrames > fILL) { |
| resultIsSynchronized = True; |
| fNumSuccessiveSyncedFrames = fILL+1; // prevents overflow |
| } |
| } else { |
| fNumSuccessiveSyncedFrames = 0; |
| } |
| |
| // Check whether this frame is missing; if so, return a FT_NO_DATA frame: |
| if (fromSize == 0) { |
| resultFrameHeader = FT_NO_DATA<<3; |
| |
| // Compute this erasure frame's presentation time via extrapolation: |
| resultPresentationTime = fLastRetrievedPresentationTime; |
| resultPresentationTime.tv_usec += uSecsPerFrame; |
| if (resultPresentationTime.tv_usec >= 1000000) { |
| ++resultPresentationTime.tv_sec; |
| resultPresentationTime.tv_usec -= 1000000; |
| } |
| } else { |
| // Normal case - a frame exists: |
| resultFrameHeader = outBin.frameHeader; |
| resultPresentationTime = outBin.presentationTime; |
| } |
| |
| fLastRetrievedPresentationTime = resultPresentationTime; |
| |
| if (fromSize > maxSize) { |
| resultNumTruncatedBytes = fromSize - maxSize; |
| resultFrameSize = maxSize; |
| } else { |
| resultNumTruncatedBytes = 0; |
| resultFrameSize = fromSize; |
| } |
| memmove(to, fromPtr, resultFrameSize); |
| #ifdef DEBUG |
| fprintf(stderr, "AMRDeinterleavingBuffer::retrieveFrame(): from bank %d, bin %d: size %d, header 0x%02x, presentationTime %lu.%06ld\n", fIncomingBankId^1, fNextOutgoingBin, resultFrameSize, resultFrameHeader, resultPresentationTime.tv_sec, resultPresentationTime.tv_usec); |
| #endif |
| |
| ++fNextOutgoingBin; |
| return True; |
| } |
| |
| unsigned char* AMRDeinterleavingBuffer::createNewBuffer() { |
| return new unsigned char[inputBufferSize()]; |
| } |
| |
| AMRDeinterleavingBuffer::FrameDescriptor::FrameDescriptor() |
| : frameSize(0), frameData(NULL) { |
| } |
| |
| AMRDeinterleavingBuffer::FrameDescriptor::~FrameDescriptor() { |
| delete[] frameData; |
| } |
| |
| // Unpack bandwidth-aligned data to octet-aligned: |
| static unsigned short const frameBitsFromFT[16] = { |
| 95, 103, 118, 134, |
| 148, 159, 204, 244, |
| 39, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| static unsigned short const frameBitsFromFTWideband[16] = { |
| 132, 177, 253, 285, |
| 317, 365, 397, 461, |
| 477, 40, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| static void unpackBandwidthEfficientData(BufferedPacket* packet, |
| Boolean isWideband) { |
| #ifdef DEBUG |
| fprintf(stderr, "Unpacking 'bandwidth-efficient' payload (%d bytes):\n", packet->dataSize()); |
| for (unsigned j = 0; j < packet->dataSize(); ++j) { |
| fprintf(stderr, "%02x:", (packet->data())[j]); |
| } |
| fprintf(stderr, "\n"); |
| #endif |
| BitVector fromBV(packet->data(), 0, 8*packet->dataSize()); |
| |
| unsigned const toBufferSize = 2*packet->dataSize(); // conservatively large |
| unsigned char* toBuffer = new unsigned char[toBufferSize]; |
| unsigned toCount = 0; |
| |
| // Begin with the payload header: |
| unsigned CMR = fromBV.getBits(4); |
| toBuffer[toCount++] = CMR << 4; |
| |
| // Then, run through and unpack the TOC entries: |
| while (1) { |
| unsigned toc = fromBV.getBits(6); |
| toBuffer[toCount++] = toc << 2; |
| |
| if ((toc&0x20) == 0) break; // the F bit is 0 |
| } |
| |
| // Then, using the TOC data, unpack each frame payload: |
| unsigned const tocSize = toCount - 1; |
| for (unsigned i = 1; i <= tocSize; ++i) { |
| unsigned char tocByte = toBuffer[i]; |
| unsigned char const FT = (tocByte&0x78) >> 3; |
| unsigned short frameSizeBits |
| = isWideband ? frameBitsFromFTWideband[FT] : frameBitsFromFT[FT]; |
| unsigned short frameSizeBytes = (frameSizeBits+7)/8; |
| |
| if (frameSizeBits > fromBV.numBitsRemaining()) { |
| #ifdef DEBUG |
| fprintf(stderr, "\tWarning: Unpacking frame %d of %d: want %d bits, but only %d are available!\n", i, tocSize, frameSizeBits, fromBV.numBitsRemaining()); |
| #endif |
| break; |
| } |
| |
| shiftBits(&toBuffer[toCount], 0, // to |
| packet->data(), fromBV.curBitIndex(), // from |
| frameSizeBits // num bits |
| ); |
| fromBV.skipBits(frameSizeBits); |
| toCount += frameSizeBytes; |
| } |
| |
| #ifdef DEBUG |
| if (fromBV.numBitsRemaining() > 7) { |
| fprintf(stderr, "\tWarning: %d bits remain unused!\n", fromBV.numBitsRemaining()); |
| } |
| #endif |
| |
| // Finally, replace the current packet data with the unpacked data: |
| packet->removePadding(packet->dataSize()); // throws away current packet data |
| packet->appendData(toBuffer, toCount); |
| delete[] toBuffer; |
| } |