/**********
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-2026 Live Networks, Inc.  All rights reserved.
// A filter that breaks up an MPEG 1 or 2 video elementary stream into
//   frames for: Video_Sequence_Header, GOP_Header, Picture_Header
// Implementation

#include "MPEG1or2VideoStreamFramer.hh"
#include "MPEGVideoStreamParser.hh"
#include <string.h>

////////// MPEG1or2VideoStreamParser definition //////////

// An enum representing the current state of the parser:
enum MPEGParseState {
  PARSING_VIDEO_SEQUENCE_HEADER,
  PARSING_VIDEO_SEQUENCE_HEADER_SEEN_CODE,
  PARSING_GOP_HEADER,
  PARSING_GOP_HEADER_SEEN_CODE,
  PARSING_PICTURE_HEADER,
  PARSING_SLICE
};

#define VSH_MAX_SIZE 1000

class MPEG1or2VideoStreamParser: public MPEGVideoStreamParser {
public:
  MPEG1or2VideoStreamParser(MPEG1or2VideoStreamFramer* usingSource,
			FramedSource* inputSource,
			Boolean iFramesOnly, double vshPeriod);
  virtual ~MPEG1or2VideoStreamParser();

private: // redefined virtual functions:
  virtual void flushInput();
  virtual unsigned parse();

private:
  void reset();

  MPEG1or2VideoStreamFramer* usingSource() {
    return (MPEG1or2VideoStreamFramer*)fUsingSource;
  }
  void setParseState(MPEGParseState parseState);

  unsigned parseVideoSequenceHeader(Boolean haveSeenStartCode);
  unsigned parseGOPHeader(Boolean haveSeenStartCode);
  unsigned parsePictureHeader();
  unsigned parseSlice();

private:
  MPEGParseState fCurrentParseState;
  unsigned fPicturesSinceLastGOP;
      // can be used to compute timestamp for a video_sequence_header
  unsigned short fCurPicTemporalReference;
      // used to compute slice timestamp
  unsigned char fCurrentSliceNumber; // set when parsing a slice

  // A saved copy of the most recently seen 'video_sequence_header',
  // in case we need to insert it into the stream periodically:
  unsigned char fSavedVSHBuffer[VSH_MAX_SIZE];
  unsigned fSavedVSHSize;
  double fSavedVSHTimestamp;
  double fVSHPeriod;
  Boolean fIFramesOnly, fSkippingCurrentPicture;

  void saveCurrentVSH();
  Boolean needToUseSavedVSH();
  unsigned useSavedVSH(); // returns the size of the saved VSH
};


////////// MPEG1or2VideoStreamFramer implementation //////////

MPEG1or2VideoStreamFramer::MPEG1or2VideoStreamFramer(UsageEnvironment& env,
						     FramedSource* inputSource,
						     Boolean iFramesOnly,
						     double vshPeriod,
						     Boolean createParser)
  : MPEGVideoStreamFramer(env, inputSource) {
  fParser = createParser
    ? new MPEG1or2VideoStreamParser(this, inputSource,
				    iFramesOnly, vshPeriod)
    : NULL;
}

MPEG1or2VideoStreamFramer::~MPEG1or2VideoStreamFramer() {
}

MPEG1or2VideoStreamFramer*
MPEG1or2VideoStreamFramer::createNew(UsageEnvironment& env,
				 FramedSource* inputSource,
				 Boolean iFramesOnly,
				 double vshPeriod) {
  // Need to add source type checking here???  #####
  return new MPEG1or2VideoStreamFramer(env, inputSource, iFramesOnly, vshPeriod);
}

double MPEG1or2VideoStreamFramer::getCurrentPTS() const {
  return fPresentationTime.tv_sec + fPresentationTime.tv_usec/1000000.0;
}

Boolean MPEG1or2VideoStreamFramer::isMPEG1or2VideoStreamFramer() const {
  return True;
}

////////// MPEG1or2VideoStreamParser implementation //////////

MPEG1or2VideoStreamParser
::MPEG1or2VideoStreamParser(MPEG1or2VideoStreamFramer* usingSource,
			FramedSource* inputSource,
			Boolean iFramesOnly, double vshPeriod)
  : MPEGVideoStreamParser(usingSource, inputSource),
    fCurrentParseState(PARSING_VIDEO_SEQUENCE_HEADER),
    fVSHPeriod(vshPeriod), fIFramesOnly(iFramesOnly) {
  reset();
}

MPEG1or2VideoStreamParser::~MPEG1or2VideoStreamParser() {
}

void MPEG1or2VideoStreamParser::setParseState(MPEGParseState parseState) {
  fCurrentParseState = parseState;
  MPEGVideoStreamParser::setParseState();
}

void MPEG1or2VideoStreamParser::reset() {
  fPicturesSinceLastGOP = 0;
  fCurPicTemporalReference = 0;
  fCurrentSliceNumber = 0;
  fSavedVSHSize = 0;
  fSkippingCurrentPicture = False;
}

void MPEG1or2VideoStreamParser::flushInput() {
  reset();
  StreamParser::flushInput();
  if (fCurrentParseState != PARSING_VIDEO_SEQUENCE_HEADER) {
    setParseState(PARSING_GOP_HEADER); // start from the next GOP
  }
}

unsigned MPEG1or2VideoStreamParser::parse() {
  try {
    switch (fCurrentParseState) {
    case PARSING_VIDEO_SEQUENCE_HEADER: {
      return parseVideoSequenceHeader(False);
    }
    case PARSING_VIDEO_SEQUENCE_HEADER_SEEN_CODE: {
      return parseVideoSequenceHeader(True);
    }
    case PARSING_GOP_HEADER: {
      return parseGOPHeader(False);
    }
    case PARSING_GOP_HEADER_SEEN_CODE: {
      return parseGOPHeader(True);
    }
    case PARSING_PICTURE_HEADER: {
      return parsePictureHeader();
    }
    case PARSING_SLICE: {
      return parseSlice();
    }
    default: {
      return 0; // shouldn't happen
    }
    }
  } catch (int /*e*/) {
#ifdef DEBUG
    fprintf(stderr, "MPEG1or2VideoStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n");
#endif
    return 0;  // the parsing got interrupted
  }
}

void MPEG1or2VideoStreamParser::saveCurrentVSH() {
  unsigned frameSize = curFrameSize();
  if (frameSize > sizeof fSavedVSHBuffer) return; // too big to save

  memmove(fSavedVSHBuffer, fStartOfFrame, frameSize);
  fSavedVSHSize = frameSize;
  fSavedVSHTimestamp = usingSource()->getCurrentPTS();
}

Boolean MPEG1or2VideoStreamParser::needToUseSavedVSH() {
  return usingSource()->getCurrentPTS() > fSavedVSHTimestamp+fVSHPeriod
    && fSavedVSHSize > 0;
}

unsigned MPEG1or2VideoStreamParser::useSavedVSH() {
  unsigned bytesToUse = fSavedVSHSize;
  unsigned maxBytesToUse = fLimit - fStartOfFrame;
  if (bytesToUse > maxBytesToUse) bytesToUse = maxBytesToUse;

  memmove(fStartOfFrame, fSavedVSHBuffer, bytesToUse);

  // Also reset the saved timestamp:
  fSavedVSHTimestamp = usingSource()->getCurrentPTS();

#ifdef DEBUG
  fprintf(stderr, "used saved video_sequence_header (%d bytes)\n", bytesToUse);
#endif
  return bytesToUse;
}

#define VIDEO_SEQUENCE_HEADER_START_CODE 0x000001B3
#define GROUP_START_CODE                 0x000001B8
#define PICTURE_START_CODE               0x00000100
#define SEQUENCE_END_CODE                0x000001B7

static double const frameRateFromCode[] = {
  0.0,          // forbidden
  24000/1001.0, // approx 23.976
  24.0,
  25.0,
  30000/1001.0, // approx 29.97
  30.0,
  50.0,
  60000/1001.0, // approx 59.94
  60.0,
  0.0,          // reserved
  0.0,          // reserved
  0.0,          // reserved
  0.0,          // reserved
  0.0,          // reserved
  0.0,          // reserved
  0.0           // reserved
};

unsigned MPEG1or2VideoStreamParser
::parseVideoSequenceHeader(Boolean haveSeenStartCode) {
#ifdef DEBUG
  fprintf(stderr, "parsing video sequence header\n");
#endif
  unsigned first4Bytes;
  if (!haveSeenStartCode) {
    while ((first4Bytes = test4Bytes()) != VIDEO_SEQUENCE_HEADER_START_CODE) {
#ifdef DEBUG
      fprintf(stderr, "ignoring non video sequence header: 0x%08x\n", first4Bytes);
#endif
      get1Byte(); setParseState(PARSING_VIDEO_SEQUENCE_HEADER);
          // ensures we progress over bad data
    }
    first4Bytes = get4Bytes();
  } else {
    // We've already seen the start code
    first4Bytes = VIDEO_SEQUENCE_HEADER_START_CODE;
  }
  save4Bytes(first4Bytes);

  // Next, extract the size and rate parameters from the next 8 bytes
  unsigned paramWord1 = get4Bytes();
  save4Bytes(paramWord1);
  unsigned next4Bytes = get4Bytes();
#ifdef DEBUG
  unsigned short horizontal_size_value   = (paramWord1&0xFFF00000)>>(32-12);
  unsigned short vertical_size_value     = (paramWord1&0x000FFF00)>>8;
  unsigned char aspect_ratio_information = (paramWord1&0x000000F0)>>4;
#endif
  unsigned char frame_rate_code          = (paramWord1&0x0000000F);
  usingSource()->fFrameRate = frameRateFromCode[frame_rate_code];
#ifdef DEBUG
  unsigned bit_rate_value                = (next4Bytes&0xFFFFC000)>>(32-18);
  unsigned vbv_buffer_size_value         = (next4Bytes&0x00001FF8)>>3;
  fprintf(stderr, "horizontal_size_value: %d, vertical_size_value: %d, aspect_ratio_information: %d, frame_rate_code: %d (=>%f fps), bit_rate_value: %d (=>%d bps), vbv_buffer_size_value: %d\n", horizontal_size_value, vertical_size_value, aspect_ratio_information, frame_rate_code, usingSource()->fFrameRate, bit_rate_value, bit_rate_value*400, vbv_buffer_size_value);
#endif

  // Now, copy all bytes that we see, up until we reach a GROUP_START_CODE
  // or a PICTURE_START_CODE:
  do {
    saveToNextCode(next4Bytes);
  } while (next4Bytes != GROUP_START_CODE && next4Bytes != PICTURE_START_CODE);

  setParseState((next4Bytes == GROUP_START_CODE)
		? PARSING_GOP_HEADER_SEEN_CODE : PARSING_PICTURE_HEADER);

  // Compute this frame's timestamp by noting how many pictures we've seen
  // since the last GOP header:
  usingSource()->computePresentationTime(fPicturesSinceLastGOP);

  // Save this video_sequence_header, in case we need to insert a copy
  // into the stream later:
  saveCurrentVSH();

  return curFrameSize();
}

unsigned MPEG1or2VideoStreamParser::parseGOPHeader(Boolean haveSeenStartCode) {
  // First check whether we should insert a previously-saved
  // 'video_sequence_header' here:
  if (needToUseSavedVSH()) return useSavedVSH();

#ifdef DEBUG
  fprintf(stderr, "parsing GOP header\n");
#endif
  unsigned first4Bytes;
  if (!haveSeenStartCode) {
    while ((first4Bytes = test4Bytes()) != GROUP_START_CODE) {
#ifdef DEBUG
      fprintf(stderr, "ignoring non GOP start code: 0x%08x\n", first4Bytes);
#endif
      get1Byte(); setParseState(PARSING_GOP_HEADER);
          // ensures we progress over bad data
    }
    first4Bytes = get4Bytes();
  } else {
    // We've already seen the GROUP_START_CODE
    first4Bytes = GROUP_START_CODE;
  }
  save4Bytes(first4Bytes);

  // Next, extract the (25-bit) time code from the next 4 bytes:
  unsigned next4Bytes = get4Bytes();
  unsigned time_code = (next4Bytes&0xFFFFFF80)>>(32-25);
#if defined(DEBUG) || defined(DEBUG_TIMESTAMPS)
  Boolean drop_frame_flag     = (time_code&0x01000000) != 0;
#endif
  unsigned time_code_hours    = (time_code&0x00F80000)>>19;
  unsigned time_code_minutes  = (time_code&0x0007E000)>>13;
  unsigned time_code_seconds  = (time_code&0x00000FC0)>>6;
  unsigned time_code_pictures = (time_code&0x0000003F);
#if defined(DEBUG) || defined(DEBUG_TIMESTAMPS)
  fprintf(stderr, "time_code: 0x%07x, drop_frame %d, hours %d, minutes %d, seconds %d, pictures %d\n", time_code, drop_frame_flag, time_code_hours, time_code_minutes, time_code_seconds, time_code_pictures);
#endif
#ifdef DEBUG
  Boolean closed_gop  = (next4Bytes&0x00000040) != 0;
  Boolean broken_link = (next4Bytes&0x00000020) != 0;
  fprintf(stderr, "closed_gop: %d, broken_link: %d\n", closed_gop, broken_link);
#endif

  // Now, copy all bytes that we see, up until we reach a PICTURE_START_CODE:
  do {
    saveToNextCode(next4Bytes);
  } while (next4Bytes != PICTURE_START_CODE);

  // Record the time code:
  usingSource()->setTimeCode(time_code_hours, time_code_minutes,
			     time_code_seconds, time_code_pictures,
			     fPicturesSinceLastGOP);

  fPicturesSinceLastGOP = 0;

  // Compute this frame's timestamp:
  usingSource()->computePresentationTime(0);

  setParseState(PARSING_PICTURE_HEADER);

  return curFrameSize();
}

inline Boolean isSliceStartCode(unsigned fourBytes) {
  if ((fourBytes&0xFFFFFF00) != 0x00000100) return False;

  unsigned char lastByte = fourBytes&0xFF;
  return lastByte <= 0xAF && lastByte >= 1;
}

unsigned MPEG1or2VideoStreamParser::parsePictureHeader() {
#ifdef DEBUG
  fprintf(stderr, "parsing picture header\n");
#endif
  // Note that we've already read the PICTURE_START_CODE
  // Next, extract the temporal reference from the next 4 bytes:
  unsigned next4Bytes = get4Bytes();
  unsigned short temporal_reference = (next4Bytes&0xFFC00000)>>(32-10);
  unsigned char picture_coding_type = (next4Bytes&0x00380000)>>19;
#ifdef DEBUG
  unsigned short vbv_delay          = (next4Bytes&0x0007FFF8)>>3;
  fprintf(stderr, "temporal_reference: %d, picture_coding_type: %d, vbv_delay: %d\n", temporal_reference, picture_coding_type, vbv_delay);
#endif

  fSkippingCurrentPicture = fIFramesOnly && picture_coding_type != 1;
  if (fSkippingCurrentPicture) {
    // Skip all bytes that we see, up until we reach a slice_start_code:
    do {
      skipToNextCode(next4Bytes);
    } while (!isSliceStartCode(next4Bytes));
  } else {
    // Save the PICTURE_START_CODE that we've already read:
    save4Bytes(PICTURE_START_CODE);

    // Copy all bytes that we see, up until we reach a slice_start_code:
    do {
      saveToNextCode(next4Bytes);
    } while (!isSliceStartCode(next4Bytes));
  }

  setParseState(PARSING_SLICE);

  fCurrentSliceNumber = next4Bytes&0xFF;

  // Record the temporal reference:
  fCurPicTemporalReference = temporal_reference;

  // Compute this frame's timestamp:
  usingSource()->computePresentationTime(fCurPicTemporalReference);

  if (fSkippingCurrentPicture) {
    return parse(); // try again, until we get a non-skipped frame
  } else {
    return curFrameSize();
  }
}

unsigned MPEG1or2VideoStreamParser::parseSlice() {
  // Note that we've already read the slice_start_code:
  unsigned next4Bytes = PICTURE_START_CODE|fCurrentSliceNumber;
#ifdef DEBUG_SLICE
  fprintf(stderr, "parsing slice: 0x%08x\n", next4Bytes);
#endif

  if (fSkippingCurrentPicture) {
    // Skip all bytes that we see, up until we reach a code of some sort:
    skipToNextCode(next4Bytes);
  } else {
    // Copy all bytes that we see, up until we reach a code of some sort:
    saveToNextCode(next4Bytes);
  }

  // The next thing to parse depends on the code that we just saw:
  if (isSliceStartCode(next4Bytes)) { // common case
    setParseState(PARSING_SLICE);
    fCurrentSliceNumber = next4Bytes&0xFF;
  } else {
    // Because we don't see any more slices, we are assumed to have ended
    // the current picture:
    ++fPicturesSinceLastGOP;
    ++usingSource()->fPictureCount;
    usingSource()->fPictureEndMarker = True; // HACK #####

    switch (next4Bytes) {
    case SEQUENCE_END_CODE: {
      setParseState(PARSING_VIDEO_SEQUENCE_HEADER);
      break;
    }
    case VIDEO_SEQUENCE_HEADER_START_CODE: {
      setParseState(PARSING_VIDEO_SEQUENCE_HEADER_SEEN_CODE);
      break;
    }
    case GROUP_START_CODE: {
      setParseState(PARSING_GOP_HEADER_SEEN_CODE);
      break;
    }
    case PICTURE_START_CODE: {
      setParseState(PARSING_PICTURE_HEADER);
      break;
    }
    default: {
      usingSource()->envir() << "MPEG1or2VideoStreamParser::parseSlice(): Saw unexpected code "
			    << (void*)next4Bytes << "\n";
      setParseState(PARSING_SLICE); // the safest way to recover...
      break;
    }
    }
  }

  // Compute this frame's timestamp:
  usingSource()->computePresentationTime(fCurPicTemporalReference);

  if (fSkippingCurrentPicture) {
    return parse(); // try again, until we get a non-skipped frame
  } else {
    return curFrameSize();
  }
}
