/**********
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-2024 Live Networks, Inc.  All rights reserved.
// 'ADU' MP3 streams (for improved loss-tolerance)
// Implementation

#include "MP3ADU.hh"
#include "MP3ADUdescriptor.hh"
#include "MP3Internals.hh"
#include <string.h>

#ifdef TEST_LOSS
#include "GroupsockHelper.hh"
#endif

// Segment data structures, used in the implementation below:

#define SegmentBufSize 2000	/* conservatively high */

class Segment {
public:
  unsigned char buf[SegmentBufSize];
  unsigned char* dataStart() { return &buf[descriptorSize]; }
  unsigned frameSize; // if it's a non-ADU frame
  unsigned dataHere(); // if it's a non-ADU frame

  unsigned descriptorSize;
  static unsigned const headerSize;
  unsigned sideInfoSize, aduSize;
  unsigned backpointer;

  struct timeval presentationTime;
  unsigned durationInMicroseconds;
};

unsigned const Segment::headerSize = 4;

#define SegmentQueueSize 20

class SegmentQueue {
public:
  SegmentQueue(Boolean directionIsToADU, Boolean includeADUdescriptors)
    : fDirectionIsToADU(directionIsToADU),
      fIncludeADUdescriptors(includeADUdescriptors) {
    reset();
  }

  Segment s[SegmentQueueSize];

  unsigned headIndex() {return fHeadIndex;}
  Segment& headSegment() {return s[fHeadIndex];}

  unsigned nextFreeIndex() {return fNextFreeIndex;}
  Segment& nextFreeSegment() {return s[fNextFreeIndex];}
  Boolean isEmpty() {return isEmptyOrFull() && totalDataSize() == 0;}
  Boolean isFull() {return isEmptyOrFull() && totalDataSize() > 0;}

  static unsigned nextIndex(unsigned ix) {return (ix+1)%SegmentQueueSize;}
  static unsigned prevIndex(unsigned ix) {return (ix+SegmentQueueSize-1)%SegmentQueueSize;}

  unsigned totalDataSize() {return fTotalDataSize;}

  void enqueueNewSegment(FramedSource* inputSource, FramedSource* usingSource);

  Boolean dequeue();

  Boolean insertDummyBeforeTail(unsigned backpointer);

  void reset() { fHeadIndex = fNextFreeIndex = fTotalDataSize = 0; }

private:
  static void sqAfterGettingSegment(void* clientData,
				    unsigned numBytesRead,
				    unsigned numTruncatedBytes,
				    struct timeval presentationTime,
				    unsigned durationInMicroseconds);

  Boolean sqAfterGettingCommon(Segment& seg, unsigned numBytesRead);
  Boolean isEmptyOrFull() {return headIndex() == nextFreeIndex();}

  unsigned fHeadIndex, fNextFreeIndex, fTotalDataSize;

  // The following is used for asynchronous reads:
  FramedSource* fUsingSource;

  // This tells us whether the direction in which we're being used
  // is MP3->ADU, or vice-versa.  (This flag is used for debugging output.)
  Boolean fDirectionIsToADU;

  // The following is true iff we're used to enqueue incoming
  // ADU frames, and these have an ADU descriptor in front
  Boolean fIncludeADUdescriptors;
};

////////// ADUFromMP3Source //////////

ADUFromMP3Source::ADUFromMP3Source(UsageEnvironment& env,
				   FramedSource* inputSource,
				   Boolean includeADUdescriptors)
  : FramedFilter(env, inputSource),
    fAreEnqueueingMP3Frame(False),
    fSegments(new SegmentQueue(True /* because we're MP3->ADU */,
			       False /*no descriptors in incoming frames*/)),
    fIncludeADUdescriptors(includeADUdescriptors),
    fTotalDataSizeBeforePreviousRead(0), fScale(1), fFrameCounter(0) {
}

ADUFromMP3Source::~ADUFromMP3Source() {
  delete fSegments;
}


char const* ADUFromMP3Source::MIMEtype() const {
  return "audio/MPA-ROBUST";
}

ADUFromMP3Source* ADUFromMP3Source::createNew(UsageEnvironment& env,
                                              FramedSource* inputSource,
                                              Boolean includeADUdescriptors) {
  // The source must be a MPEG audio source:
  if (strcmp(inputSource->MIMEtype(), "audio/MPEG") != 0) {
    env.setResultMsg(inputSource->name(), " is not an MPEG audio source");
    return NULL;
  }

  return new ADUFromMP3Source(env, inputSource, includeADUdescriptors);
}

void ADUFromMP3Source::resetInput() {
  fSegments->reset();
}

Boolean ADUFromMP3Source::setScaleFactor(int scale) {
  if (scale < 1) return False;
  fScale = scale;
  return True;
}

void ADUFromMP3Source::doGetNextFrame() {
  if (!fAreEnqueueingMP3Frame) {
    // Arrange to enqueue a new MP3 frame:
    fTotalDataSizeBeforePreviousRead = fSegments->totalDataSize();
    fAreEnqueueingMP3Frame = True;
    fSegments->enqueueNewSegment(fInputSource, this);
  } else {
    // Deliver an ADU from a previously-read MP3 frame:
    fAreEnqueueingMP3Frame = False;

    if (!doGetNextFrame1()) {
      // An internal error occurred; act as if our source went away:
      handleClosure();
    }
  }
}

Boolean ADUFromMP3Source::doGetNextFrame1() {
  // First, check whether we have enough previously-read data to output an
  // ADU for the last-read MP3 frame:
  unsigned tailIndex;
  Segment* tailSeg;
  Boolean needMoreData;

  if (fSegments->isEmpty()) {
    needMoreData = True;
    tailSeg = NULL; tailIndex = 0; // unneeded, but stops compiler warnings
  } else {
    tailIndex = SegmentQueue::prevIndex(fSegments->nextFreeIndex());
    tailSeg = &(fSegments->s[tailIndex]);

    needMoreData
	  = fTotalDataSizeBeforePreviousRead < tailSeg->backpointer // bp points back too far
      || tailSeg->backpointer + tailSeg->dataHere() < tailSeg->aduSize; // not enough data
  }

  if (needMoreData) {
    // We don't have enough data to output an ADU from the last-read MP3
    // frame, so need to read another one and try again:
    doGetNextFrame();
    return True;
  }

  // Output an ADU from the tail segment:
  fFrameSize = tailSeg->headerSize+tailSeg->sideInfoSize+tailSeg->aduSize;
  fPresentationTime = tailSeg->presentationTime;
  fDurationInMicroseconds = tailSeg->durationInMicroseconds;
  unsigned descriptorSize
    = fIncludeADUdescriptors ? ADUdescriptor::computeSize(fFrameSize) : 0;
#ifdef DEBUG
  fprintf(stderr, "m->a:outputting ADU %d<-%d, nbr:%d, sis:%d, dh:%d, (descriptor size: %d)\n", tailSeg->aduSize, tailSeg->backpointer, fFrameSize, tailSeg->sideInfoSize, tailSeg->dataHere(), descriptorSize);
#endif
  if (descriptorSize + fFrameSize > fMaxSize) {
    envir() << "ADUFromMP3Source::doGetNextFrame1(): not enough room ("
	    << descriptorSize + fFrameSize << ">"
	    << fMaxSize << ")\n";
    fFrameSize = 0;
    return False;
  }

  unsigned char* toPtr = fTo;
  // output the ADU descriptor:
  if (fIncludeADUdescriptors) {
    fFrameSize += ADUdescriptor::generateDescriptor(toPtr, fFrameSize);
  }

  // output header and side info:
  memmove(toPtr, tailSeg->dataStart(),
	  tailSeg->headerSize + tailSeg->sideInfoSize);
  toPtr += tailSeg->headerSize + tailSeg->sideInfoSize;

  // go back to the frame that contains the start of our data:
  unsigned offset = 0;
  unsigned i = tailIndex;
  unsigned prevBytes = tailSeg->backpointer;
  while (prevBytes > 0) {
    i = SegmentQueue::prevIndex(i);
    unsigned dataHere = fSegments->s[i].dataHere();
    if (dataHere < prevBytes) {
      prevBytes -= dataHere;
    } else {
      offset = dataHere - prevBytes;
      break;
    }
  }

  // dequeue any segments that we no longer need:
  while (fSegments->headIndex() != i) {
    fSegments->dequeue(); // we're done with it
  }

  unsigned bytesToUse = tailSeg->aduSize;
  while (bytesToUse > 0) {
    Segment& seg = fSegments->s[i];
    unsigned char* fromPtr
      = &seg.dataStart()[seg.headerSize + seg.sideInfoSize + offset];
    unsigned dataHere = seg.dataHere() - offset;
    unsigned bytesUsedHere = dataHere < bytesToUse ? dataHere : bytesToUse;
    memmove(toPtr, fromPtr, bytesUsedHere);
    bytesToUse -= bytesUsedHere;
    toPtr += bytesUsedHere;
    offset = 0;
    i = SegmentQueue::nextIndex(i);
  }


  if (fFrameCounter++%fScale == 0) {
    // Call our own 'after getting' function.  Because we're not a 'leaf'
    // source, we can call this directly, without risking infinite recursion.
    afterGetting(this);
  } else {
    // Don't use this frame; get another one:
    doGetNextFrame();
  }

  return True;
}


////////// MP3FromADUSource //////////

MP3FromADUSource::MP3FromADUSource(UsageEnvironment& env,
				   FramedSource* inputSource,
				   Boolean includeADUdescriptors)
  : FramedFilter(env, inputSource),
    fAreEnqueueingADU(False),
    fSegments(new SegmentQueue(False /* because we're ADU->MP3 */,
			       includeADUdescriptors)) {
}

MP3FromADUSource::~MP3FromADUSource() {
  delete fSegments;
}

char const* MP3FromADUSource::MIMEtype() const {
  return "audio/MPEG";
}

MP3FromADUSource* MP3FromADUSource::createNew(UsageEnvironment& env,
					      FramedSource* inputSource,
					      Boolean includeADUdescriptors) {
  // The source must be an MP3 ADU source:
  if (strcmp(inputSource->MIMEtype(), "audio/MPA-ROBUST") != 0) {
    env.setResultMsg(inputSource->name(), " is not an MP3 ADU source");
    return NULL;
  }

  return new MP3FromADUSource(env, inputSource, includeADUdescriptors);
}


void MP3FromADUSource::doGetNextFrame() {
  if (fAreEnqueueingADU) insertDummyADUsIfNecessary();
  fAreEnqueueingADU = False;

  if (needToGetAnADU()) {
    // Before returning a frame, we must enqueue at least one ADU:
#ifdef TEST_LOSS
  NOTE: This code no longer works, because it uses synchronous reads,
  which are no longer supported.
    static unsigned const framesPerPacket = 10;
    static unsigned const frameCount = 0;
    static Boolean packetIsLost;
    while (1) {
      if ((frameCount++)%framesPerPacket == 0) {
	packetIsLost = (our_random()%10 == 0); // simulate 10% packet loss #####
      }

      if (packetIsLost) {
	// Read and discard the next input frame (that would be part of
	// a lost packet):
	Segment dummySegment;
	unsigned numBytesRead;
	struct timeval presentationTime;
	// (this works only if the source can be read synchronously)
	fInputSource->syncGetNextFrame(dummySegment.buf,
				       sizeof dummySegment.buf, numBytesRead,
				       presentationTime);
      } else {
	break; // from while (1)
      }
    }
#endif

    fAreEnqueueingADU = True;
    fSegments->enqueueNewSegment(fInputSource, this);
  } else {
    // Return a frame now:
    generateFrameFromHeadADU();
        // sets fFrameSize, fPresentationTime, and fDurationInMicroseconds

    // Call our own 'after getting' function.  Because we're not a 'leaf'
    // source, we can call this directly, without risking infinite recursion.
    afterGetting(this);
  }
}

Boolean MP3FromADUSource::needToGetAnADU() {
  // Check whether we need to first enqueue a new ADU before we
  // can generate a frame for our head ADU.
  Boolean needToEnqueue = True;

  if (!fSegments->isEmpty()) {
    unsigned index = fSegments->headIndex();
    Segment* seg = &(fSegments->headSegment());
    int const endOfHeadFrame = (int) seg->dataHere();
    unsigned frameOffset = 0;

    while (1) {
      int endOfData = frameOffset - seg->backpointer + seg->aduSize;
      if (endOfData >= endOfHeadFrame) {
	// We already have enough data to generate a frame
	needToEnqueue = False;
	break;
      }

      frameOffset += seg->dataHere();
      index = SegmentQueue::nextIndex(index);
      if (index == fSegments->nextFreeIndex()) break;
      seg = &(fSegments->s[index]);
    }
  }

  return needToEnqueue;
}

void MP3FromADUSource::insertDummyADUsIfNecessary() {
  if (fSegments->isEmpty()) return; // shouldn't happen

  // The tail segment (ADU) is assumed to have been recently
  // enqueued.  If its backpointer would overlap the data
  // of the previous ADU, then we need to insert one or more
  // empty, 'dummy' ADUs ahead of it.  (This situation should occur
  // only if an intermediate ADU was lost.)

  unsigned tailIndex
    = SegmentQueue::prevIndex(fSegments->nextFreeIndex());
  Segment* tailSeg = &(fSegments->s[tailIndex]);

  while (1) {
    unsigned prevADUend; // relative to the start of the new ADU
    if (fSegments->headIndex() != tailIndex) {
      // there is a previous segment
      unsigned prevIndex = SegmentQueue::prevIndex(tailIndex);
      Segment& prevSegment = fSegments->s[prevIndex];
      prevADUend = prevSegment.dataHere() + prevSegment.backpointer;
      if (prevSegment.aduSize > prevADUend) {
	// shouldn't happen if the previous ADU was well-formed
	prevADUend = 0;
      } else {
	prevADUend -= prevSegment.aduSize;
      }
    } else {
      prevADUend = 0;
    }

    if (tailSeg->backpointer > prevADUend) {
      // We need to insert a dummy ADU in front of the tail
#ifdef DEBUG
      fprintf(stderr, "a->m:need to insert a dummy ADU (%d, %d, %d) [%d, %d]\n", tailSeg->backpointer, prevADUend, tailSeg->dataHere(), fSegments->headIndex(), fSegments->nextFreeIndex());
#endif
      tailIndex = fSegments->nextFreeIndex();
      if (!fSegments->insertDummyBeforeTail(prevADUend)) return;
      tailSeg = &(fSegments->s[tailIndex]);
    } else {
      break; // no more dummy ADUs need to be inserted
    }
  }
}

Boolean MP3FromADUSource::generateFrameFromHeadADU() {
    // Output a frame for the head ADU:
    if (fSegments->isEmpty()) return False;
    unsigned index = fSegments->headIndex();
    Segment* seg = &(fSegments->headSegment());
#ifdef DEBUG
    fprintf(stderr, "a->m:outputting frame for %d<-%d (fs %d, dh %d), (descriptorSize: %d)\n", seg->aduSize, seg->backpointer, seg->frameSize, seg->dataHere(), seg->descriptorSize);
#endif
    unsigned char* toPtr = fTo;

    // output header and side info:
    fFrameSize = seg->frameSize;
    fPresentationTime = seg->presentationTime;
    fDurationInMicroseconds = seg->durationInMicroseconds;
    memmove(toPtr, seg->dataStart(), seg->headerSize + seg->sideInfoSize);
    toPtr += seg->headerSize + seg->sideInfoSize;

    // zero out the rest of the frame, in case ADU data doesn't fill it all in
    unsigned bytesToZero = seg->dataHere();
    for (unsigned i = 0; i < bytesToZero; ++i) {
      toPtr[i] = '\0';
    }

    // Fill in the frame with appropriate ADU data from this and
    // subsequent ADUs:
    unsigned frameOffset = 0;
    unsigned toOffset = 0;
    unsigned const endOfHeadFrame = seg->dataHere();

    while (toOffset < endOfHeadFrame) {
      int startOfData = frameOffset - seg->backpointer;
      if (startOfData > (int)endOfHeadFrame) break; // no more ADUs needed

      int endOfData = startOfData + seg->aduSize;
      if (endOfData > (int)endOfHeadFrame) {
	endOfData = endOfHeadFrame;
      }

      unsigned fromOffset;
      if (startOfData <= (int)toOffset) {
	fromOffset = toOffset - startOfData;
	startOfData = toOffset;
	if (endOfData < startOfData) endOfData = startOfData;
      } else {
	fromOffset = 0;

	// we may need some padding bytes beforehand
	unsigned bytesToZero = startOfData - toOffset;
#ifdef DEBUG
	if (bytesToZero > 0) fprintf(stderr, "a->m:outputting %d zero bytes (%d, %d, %d, %d)\n", bytesToZero, startOfData, toOffset, frameOffset, seg->backpointer);
#endif
	toOffset += bytesToZero;
      }

      unsigned char* fromPtr
	= &seg->dataStart()[seg->headerSize + seg->sideInfoSize + fromOffset];
      unsigned bytesUsedHere = endOfData - startOfData;
#ifdef DEBUG
      if (bytesUsedHere > 0) fprintf(stderr, "a->m:outputting %d bytes from %d<-%d\n", bytesUsedHere, seg->aduSize, seg->backpointer);
#endif
      memmove(toPtr + toOffset, fromPtr, bytesUsedHere);
      toOffset += bytesUsedHere;

      frameOffset += seg->dataHere();
      index = SegmentQueue::nextIndex(index);
      if (index == fSegments->nextFreeIndex()) break;
      seg = &(fSegments->s[index]);
    }

    fSegments->dequeue();

    return True;
}


////////// Segment //////////

unsigned Segment::dataHere() {
  int result = frameSize - (headerSize + sideInfoSize);
  if (result < 0) {
    return 0;
  }

  return (unsigned)result;
}

////////// SegmentQueue //////////

void SegmentQueue::enqueueNewSegment(FramedSource* inputSource,
				     FramedSource* usingSource) {
  if (isFull()) {
    usingSource->envir() << "SegmentQueue::enqueueNewSegment() overflow\n";
    usingSource->handleClosure();
    return;
  }

  fUsingSource = usingSource;

  Segment& seg = nextFreeSegment();
  inputSource->getNextFrame(seg.buf, sizeof seg.buf,
			    sqAfterGettingSegment, this,
			    FramedSource::handleClosure, usingSource);
}

void SegmentQueue::sqAfterGettingSegment(void* clientData,
					 unsigned numBytesRead,
					 unsigned /*numTruncatedBytes*/,
					 struct timeval presentationTime,
					 unsigned durationInMicroseconds) {
  SegmentQueue* segQueue = (SegmentQueue*)clientData;
  Segment& seg = segQueue->nextFreeSegment();

  seg.presentationTime = presentationTime;
  seg.durationInMicroseconds = durationInMicroseconds;

  if (segQueue->sqAfterGettingCommon(seg, numBytesRead)) {
#ifdef DEBUG
    char const* direction = segQueue->fDirectionIsToADU ? "m->a" : "a->m";
    fprintf(stderr, "%s:read frame %d<-%d, fs:%d, sis:%d, dh:%d, (descriptor size: %d)\n", direction, seg.aduSize, seg.backpointer, seg.frameSize, seg.sideInfoSize, seg.dataHere(), seg.descriptorSize);
#endif
  }

  // Continue our original calling source where it left off:
  segQueue->fUsingSource->doGetNextFrame();
}

// Common code called after a new segment is enqueued
Boolean SegmentQueue::sqAfterGettingCommon(Segment& seg,
					   unsigned numBytesRead) {
  unsigned char* fromPtr = seg.buf;

  if (fIncludeADUdescriptors) {
    // The newly-read data is assumed to be an ADU with a descriptor
    // in front
    (void)ADUdescriptor::getRemainingFrameSize(fromPtr);
    seg.descriptorSize = (unsigned)(fromPtr-seg.buf);
  } else {
    seg.descriptorSize = 0;
  }

  // parse the MP3-specific info in the frame to get the ADU params
  unsigned hdr;
  MP3SideInfo sideInfo;
  if (!GetADUInfoFromMP3Frame(fromPtr, numBytesRead,
			      hdr, seg.frameSize,
			      sideInfo, seg.sideInfoSize,
			      seg.backpointer, seg.aduSize)) {
    return False;
  }

  // If we've just read an ADU (rather than a regular MP3 frame), then use the
  // entire "numBytesRead" data for the 'aduSize', so that we include any
  // 'ancillary data' that may be present at the end of the ADU:
  if (!fDirectionIsToADU) {
    unsigned newADUSize
      = numBytesRead - seg.descriptorSize - 4/*header size*/ - seg.sideInfoSize;
    if (newADUSize > seg.aduSize) seg.aduSize = newADUSize;
  }
  fTotalDataSize += seg.dataHere();
  fNextFreeIndex = nextIndex(fNextFreeIndex);

  return True;
}

Boolean SegmentQueue::dequeue() {
  if (isEmpty()) {
    fUsingSource->envir() << "SegmentQueue::dequeue(): underflow!\n";
    return False;
  }

  Segment& seg = s[headIndex()];
  fTotalDataSize -= seg.dataHere();
  fHeadIndex = nextIndex(fHeadIndex);
  return True;
}

Boolean SegmentQueue::insertDummyBeforeTail(unsigned backpointer) {
  if (isEmptyOrFull()) return False;

  // Copy the current tail segment to its new position, then modify the
  // old tail segment to be a 'dummy' ADU

  unsigned newTailIndex = nextFreeIndex();
  Segment& newTailSeg = s[newTailIndex];

  unsigned oldTailIndex = prevIndex(newTailIndex);
  Segment& oldTailSeg = s[oldTailIndex];

  newTailSeg = oldTailSeg; // structure copy

  // Begin by setting (replacing) the ADU descriptor of the dummy ADU:
  unsigned char* ptr = oldTailSeg.buf;
  if (fIncludeADUdescriptors) {
    unsigned remainingFrameSize
      = oldTailSeg.headerSize + oldTailSeg.sideInfoSize + 0 /* 0-size ADU */;
    unsigned currentDescriptorSize = oldTailSeg.descriptorSize;

    if (currentDescriptorSize == 2) {
      ADUdescriptor::generateTwoByteDescriptor(ptr, remainingFrameSize);
    } else {
      (void)ADUdescriptor::generateDescriptor(ptr, remainingFrameSize);
    }
  }

  // Then zero out the side info of the dummy frame:
  if (!ZeroOutMP3SideInfo(ptr, oldTailSeg.frameSize,
			  backpointer)) return False;

  unsigned dummyNumBytesRead
    = oldTailSeg.descriptorSize + 4/*header size*/ + oldTailSeg.sideInfoSize;
  return sqAfterGettingCommon(oldTailSeg, dummyNumBytesRead);
}
