/**********
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-2025 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;
}
