/*
#  $Id$
# -----------------------------------------------------------------------------
#  The part of 'VideoNEXT MediaClient SDK'
# -----------------------------------------------------------------------------
#  Author: Petrov Maxim, 03/04/2009
#  Edited by:
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
*/

#include <sys/time.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include "perf.h"

#if (defined(__WIN32__) || defined(_WIN32)) && !defined(IMN_PIM) && !defined(__MINGW32CE__) && !defined(__MINGW32__)
// For Windoze, we need to implement our own gettimeofday()
extern int gettimeofday(struct timeval*, int*);
#endif

#include "AudioVideoPlaybackController.h"
#include "VideoFrameConsumer.h"
#include "RTSPMediaStreamProducerImpl.h"
#include "osdep.h"
#include "FileRecorder.h"

namespace videonext { namespace media {

Mutex VideoFrameConsumer::fCodecMutex;

VideoFrameConsumer* VideoFrameConsumer::createNew(AdaptiveUsageEnvironment& env, 
                                                  AudioVideoPlaybackController *controller,                                
                                                  unsigned bufferSize,
                                                  unsigned decoderType,
                                                  PIXEL_FORMAT preferredPixelFormat,
                                                  StreamInfo *streamInfo,
                                                  bool isVideoTranscoded)
{
    VideoFrameConsumer* newSink = NULL;

    newSink = new VideoFrameConsumer(env, controller, bufferSize, 
                                     decoderType, preferredPixelFormat, 
                                     streamInfo, isVideoTranscoded);

    if (newSink == NULL) return NULL;

    return newSink;        
}

VideoFrameConsumer::VideoFrameConsumer(AdaptiveUsageEnvironment& env, 
                                       AudioVideoPlaybackController *controller,                 
                                       unsigned bufferSize,
                                       unsigned decoderType,
                                       PIXEL_FORMAT preferredPixelFormat,
                                       StreamInfo *streamInfo,
                                       bool isVideoTranscoded)
   : FrameConsumer(env, controller, bufferSize, decoderType, streamInfo),      
     fCodecCtx(0), fCodec(0), fPicture(0), fPictureDecoded(0), fSwsCtx(0),
     fPreferredPixelFormat(preferredPixelFormat), fH264Header(0), fH264HeaderSize(0),
     fH264SEI(0), fH264SEISize(0), fIsVideoTranscoded(isVideoTranscoded),
     fLastDecodedFrameNum(~0),
     pictWidth(0), pictHeight(0), pixFmt(AV_PIX_FMT_NONE), fExternalBuffer_(0),
     fExternalBuffersNumber_(SINGLE_BUF), fExternalBuffersPosition_(0),
     fExtradata(0), fExtradataSize(0), fRecorderExtradata(0),
     fRecorderExtradataSize(0), fDisplayAspectRatio(0.0f), fStreamStats(0)
{}

/*virtual*/ VideoFrameConsumer::~VideoFrameConsumer() 
{
    delete fStreamStats;
    
    if (fDecoderType)
    {
       destroyVideoDecoder();
    }

    if (fH264SEI)    
        delete[] fH264SEI;

    if (fH264Header) 
        delete[] fH264Header;

    delete[] fExtradata;
	delete[] fRecorderExtradata;
}

/*virtual*/ bool VideoFrameConsumer::init()
{
   if (!initVideoDecoder()) return false;

   fPlaybackController->mediaStreamHandler()->newStream(*fStreamInfo);

   return true;
}

bool VideoFrameConsumer::initVideoDecoder()
{
    MutexGuard mutexGuard(fCodecMutex);
    
    /* find the correct video decoder */
    if (fStreamInfo->RTPCodecStr == "MP4V-ES")
    {
        fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_MPEG4);
    }
    else if (fStreamInfo->RTPCodecStr == "JPEG")
    {
        fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_MJPEG);
    }
    else if (fStreamInfo->RTPCodecStr == "H264")
    {
	fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_H264);
    }
    else if (fStreamInfo->RTPCodecStr == "HEVC")
    {
	fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_HEVC);
    }
    else if (fStreamInfo->RTPCodecStr == "MPV")
    {
        fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO);
    }


    if (!fCodec)
    {
        char errmsg[512];
        snprintf(errmsg, sizeof(errmsg), "Cannot initialize decoding facility (unsupported codec: %s)", fStreamInfo->RTPCodecStr.c_str());
        envir().setResultMsg(errmsg);
        envir().setResultErr(EINTERNALERROR);
        return false;
    }

    fCodecCtx = avcodec_alloc_context3(NULL);

    /* Disable MT behaviour */
    fCodecCtx->thread_count = 1;
    fCodecCtx->thread_type  = 0;

    fPicture = new AVFrame*[TRIPLE_BUF];
    fPictureDecoded = new AVFrame*[TRIPLE_BUF];

    for(int i=0; i<TRIPLE_BUF; i++) {
        if(i > 0) {
                fPicture[i] = fPictureDecoded[i] = NULL;
        }

        fPicture[i]        = av_frame_alloc();
        fPictureDecoded[i] = av_frame_alloc();
        if (fPictureDecoded[i] == NULL || fPicture[i] == NULL)
        {
            envir().setResultErrMsg("Memory allocation failed: ");
            return false;
        }
    }

    if (fExtradata && (fCodec->id == AV_CODEC_ID_H264 || fCodec->id == AV_CODEC_ID_HEVC))
    {
        uint8_t hdr[] = {0, 0, 0, 1};
        fCodecCtx->extradata_size = fExtradataSize + 4;
        fCodecCtx->extradata = (uint8_t*)av_mallocz(fCodecCtx->extradata_size + 32);
        memcpy(fCodecCtx->extradata, hdr, 4);
        memcpy(fCodecCtx->extradata+4, fExtradata, fExtradataSize);

        fCodecCtx->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
    }

    /* open it */
    if (avcodec_open2(fCodecCtx, fCodec, NULL) < 0)
    {
        char errmsg[512];
        snprintf(errmsg, sizeof(errmsg), "Cannot initialize decoding facility (failed to open decoder: %s)", fStreamInfo->RTPCodecStr.c_str());
        envir().setResultMsg(errmsg);
        envir().setResultErr(EINTERNALERROR);
        avcodec_close(fCodecCtx);
        av_free(fCodecCtx);    
        av_free(*fPicture);
        av_free(*fPictureDecoded);
        fCodecCtx = 0;
        *fPicture = *fPictureDecoded = 0;
        return false;
    }

    av_init_packet(&fVideoPacket);
    fVideoPacket.flags = AV_PKT_FLAG_KEY;

    return true;
}

void VideoFrameConsumer::destroyVideoDecoder()
{
    MutexGuard mutexGuard(fCodecMutex);

    if (fCodecCtx) {
        if (fCodecCtx->extradata)
            av_freep(&fCodecCtx->extradata);
        avcodec_close(fCodecCtx);
        av_free(fCodecCtx);
    }
    
    if (fSwsCtx)
       sws_freeContext(fSwsCtx);

    for(int i=0; i<TRIPLE_BUF; i++) {
        if (fPicture && fPicture[i])
            av_frame_free(&fPicture[i]);

        if (fPictureDecoded && fPictureDecoded[i]) {
            if (!fExternalBuffer_ && fPictureDecoded[i]->data[0])
                av_freep(&fPictureDecoded[i]->data[0]);

            av_frame_free(&fPictureDecoded[i]);
        }
    }

    delete[] fPicture;
    delete[] fPictureDecoded;
}

void VideoFrameConsumer::reloadVideoDecoder()
{
    MutexGuard mutexGuard(fCodecMutex);

    if (fCodecCtx) {
        if (fCodecCtx->extradata)
            av_freep(&fCodecCtx->extradata);
        avcodec_close(fCodecCtx);
        av_free(fCodecCtx);
    }

    /* find the correct video decoder */
    if (fStreamInfo->RTPCodecStr == "MP4V-ES")
    {
        fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_MPEG4);
    }
    else if (fStreamInfo->RTPCodecStr == "JPEG")
    {
        fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_MJPEG);
    }
    else if (fStreamInfo->RTPCodecStr == "H264")
    {
	fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_H264);
    }
    else if (fStreamInfo->RTPCodecStr == "HEVC")
    {
	fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_HEVC);
    }
    else if (fStreamInfo->RTPCodecStr == "MPV")
    {
        fCodec = (AVCodec*)avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO);
    }

    if (!fCodec)
    {
        char errmsg[512];
        snprintf(errmsg, sizeof(errmsg), "Cannot initialize decoding facility (unsupported codec: %s)", fStreamInfo->RTPCodecStr.c_str());
        envir().setResultMsg(errmsg);
        envir().setResultErr(EINTERNALERROR);
        return;
    }

    fCodecCtx = avcodec_alloc_context3(NULL);

    /* Disable MT behaviour */
    fCodecCtx->thread_count = 1;
    fCodecCtx->thread_type  = 0;

    if (fExtradata && (fCodec->id == AV_CODEC_ID_H264 || fCodec->id == AV_CODEC_ID_HEVC))
    {
        uint8_t hdr[] = {0, 0, 0, 1};
        fCodecCtx->extradata_size = fExtradataSize + 4;
        fCodecCtx->extradata = (uint8_t*)av_mallocz(fCodecCtx->extradata_size + 32);
        memcpy(fCodecCtx->extradata, hdr, 4);
        memcpy(fCodecCtx->extradata+4, fExtradata, fExtradataSize);

        fCodecCtx->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
    }

    /* open it */
    if (avcodec_open2(fCodecCtx, fCodec, NULL) < 0)
    {
        char errmsg[512];
        snprintf(errmsg, sizeof(errmsg), "Cannot initialize decoding facility (failed to open decoder: %s)", fStreamInfo->RTPCodecStr.c_str());
        envir().setResultMsg(errmsg);
        envir().setResultErr(EINTERNALERROR);
        avcodec_close(fCodecCtx);
        av_free(fCodecCtx);    
        av_free(*fPicture);
        av_free(*fPictureDecoded);
        fCodecCtx = 0;
        *fPicture = *fPictureDecoded = 0;
        return;
    }
}

uint8_t* VideoFrameConsumer::findHeader(uint8_t* data, unsigned dataSize)
{
    uint8_t *ptr = NULL; 
    
    if (fCodec->id == AV_CODEC_ID_MJPEG || (fCodec->id == AV_CODEC_ID_H264 && !fIsVideoTranscoded))
    {
        ptr = data + dataSize - 7;

        uint8_t* limit = data;
        while (ptr != limit)
        {
            if (*ptr == 'V' && *(ptr+1) == 'N' && *(ptr+2) == 'T'
                && *(ptr+3) == 'i' && *(ptr+4) == 'm'
                && *(ptr+5) == 'e' && *(ptr+6) == ':'
                && *(ptr+7) == ' ')
            {
               break; 
            }

            ptr--;
        }
        
        if (ptr == limit)
        { 
            ptr = NULL;
        }        
    }
    else if (fCodec->id == AV_CODEC_ID_MPEG4 || fCodec->id == AV_CODEC_ID_H264 || fCodec->id == AV_CODEC_ID_HEVC || fCodec->id == AV_CODEC_ID_MPEG2VIDEO) // getting ptr from USER_DATA or from SEI
    {
        // searchnig for "VNTime"
        int max_search_length = dataSize - 8;
        int n = 0;
        do
        {
            ptr = (uint8_t *)memchr(&data[n], 'V', max_search_length-n);

            if (ptr == NULL)
            {                
                return NULL;
            }

            if (*(ptr+1) == 'N' && *(ptr+2) == 'T'
                && *(ptr+3) == 'i' && *(ptr+4) == 'm'
                && *(ptr+5) == 'e' && *(ptr+6) == ':'
                && *(ptr+7) == ' '
               )
            {
                break;
            }

            n = (ptr - data) + 1;

            ptr = NULL;

        } while (n < max_search_length);
    }
    
    return ptr;
}

#define MAX_H264_PARAMS_SIZE 1024
#define MAX_H264_SEI_SIZE 4096

/*virtual*/ void VideoFrameConsumer::addExtraData(unsigned char* data, unsigned dataSize)
{
    delete [] fExtradata;
    fExtradata = new unsigned char[dataSize];
    memcpy(fExtradata, data, dataSize);
    fExtradataSize = dataSize;

    if(dataSize + 4 > MAX_H264_PARAMS_SIZE)
    {
        envir() <<  "No more space for h264 params\n";
        return;
    }

    delete [] fH264Header;
    fH264Header = new unsigned char[MAX_H264_PARAMS_SIZE];
    uint8_t hdr[] = {0, 0, 0, 1};
    memcpy(fH264Header, hdr, 4);
    memcpy(fH264Header+4, data, dataSize);
    fH264HeaderSize = dataSize+4;

    //fprintf(stderr, "addExtraData: data=%p, dataSize=%d, hdr=%p, hdrSize=%d\n", data, dataSize, fH264Header, fH264HeaderSize);
#if 0
   for (unsigned i= 0; i < (dataSize>103?103:dataSize); i++)
       fprintf(stderr, "%02X ", (uint8_t)((unsigned char*)data)[i]);
   fprintf(stderr, "\n");
#endif
}

/*virtual*/void VideoFrameConsumer::addData(unsigned char* data, unsigned dataSize,
                                            const struct timeval &presentationTime,
                                            bool parseOurHeader, const std::string* pObjectsData)
{
//    fprintf(stderr, "addData: %p, %d, parseOurHeader: %d\n", data, dataSize, parseOurHeader);
#if 0
   for (unsigned i= 0; i < (dataSize>180?180:dataSize); i++)
       fprintf(stderr, "%02X ", (uint8_t)((unsigned char*)data)[i]);
   fprintf(stderr, "\n");
#endif

   if (data == 0 || dataSize == 0)
       return;

   if (!fStreamStats && fRtpSource)
   {
       fStreamStats = new StreamStats(envir(), *fRtpSource,
                                      *fStreamInfo, *fPlaybackController->mediaStreamHandler());
       // starts updates automatically
   }

   if (fPlaybackController->isServerPaused())
   {
//      fprintf(stderr, "Got frame (size:%d) but server paused\n", dataSize);
       fEnv.log("Got frame but server paused. Size: %d, pts: %ld.%06d\n", dataSize, presentationTime.tv_sec, (int)presentationTime.tv_usec);
   }
   else
   {
//      fprintf(stderr, "Got video frame (%d)\n", dataSize);
//      envir() << "Got frame: " << dataSize << "\n";
   }

   if (dataSize < 4)
   {
       fEnv.log("Frame is too small.Size: %d, pts: %ld.%06d\n", dataSize, presentationTime.tv_sec, (int)presentationTime.tv_usec);
       return;
   }

   struct timeval tv = {.tv_sec = 0, .tv_usec = 0};
   unsigned realDataSize = 0;
   unsigned objectsDataSize = 0;
   unsigned hdrSize = 0;
 

   if (fCodec->id == AV_CODEC_ID_H264)
   {
      unsigned char start_code[4] = {0x00, 0x00, 0x00, 0x01}; 

      // check what unit we have
      uint8_t firstByte = data[0];

      if ((firstByte&0x1F) == 7 || (firstByte&0x1F) == 8) // param_set
      {
          if (parseOurHeader)
          {
              if (!fH264Header)
                  fH264Header = new uint8_t[MAX_H264_PARAMS_SIZE];

              if (dataSize + fH264HeaderSize + 4 > MAX_H264_PARAMS_SIZE)
              {
                  envir() <<  "No more space for h264 params\n";
                  return;
              }

              //fprintf(stderr, "param_sets: 0x%02x 0x%02x 0x%02x 0x%02x\n", data[0], data[1], data[2], data[3]);
              //fprintf(stderr, "addData: data=%p, dataSize=%d, hdr=%p, hdrSize=%d\n", data, dataSize, fH264Header, fH264HeaderSize);

              memcpy(fH264Header + fH264HeaderSize, start_code, 4); fH264HeaderSize+=4;
              memcpy(fH264Header + fH264HeaderSize, data, dataSize); fH264HeaderSize+=dataSize;
          }

         return;
      }
      else if ((firstByte&0x1F) == 6) // sei
      {         
//         fprintf(stderr, "sei: 0x%02x 0x%02x 0x%02x 0x%02x\n", data[0], data[1], data[2], data[3]);
          if (parseOurHeader)
          {
              if (data[1] == 0x20 && data[3] == 'V')
              {
                  // our sei
                  if (!fH264SEI) fH264SEI = new uint8_t[MAX_H264_SEI_SIZE];
                  
                  fH264SEISize = dataSize>MAX_H264_PARAMS_SIZE ? MAX_H264_PARAMS_SIZE : dataSize;
                  memcpy(fH264SEI, data, fH264SEISize);
              }
              return;
          }
          else
          {
              uint8_t *ptr;
              for (ptr = data; ptr != data + dataSize - 5; ptr++)
              {
                  if (ptr[0] == 0x00 && ptr[1] == 0x00 && ptr[2] == 0x00 && ptr[3] == 0x01 
                      && ((ptr[4]&0x1F) == 5 || (ptr[4]&0x1F) == 1))
                  {
                      dataSize = dataSize - (ptr-data) - 4;
                      data = ptr+4;                      
                  }
              }
          }
      }
      else if ((firstByte&0x1F) == 5) // idr slice.
      {
//         fprintf(stderr, "idr slice: 0x%02x 0x%02x 0x%02x 0x%02x\n", data[0], data[1], data[2], data[3]);
      }
      else if ((firstByte&0x1F) == 1) // non idr slice.
      {
//         fprintf(stderr, "non idr slice: 0x%02x 0x%02x 0x%02x 0x%02x\n", data[0], data[1], data[2], data[3]);
      }
      else 
      {
//         fprintf(stderr, "Unknown NALU type: %d\n", firstByte&0x1F);
      }
   }
   else if (fCodec->id == AV_CODEC_ID_HEVC)
   {
      unsigned char start_code[4] = {0x00, 0x00, 0x00, 0x01}; 

      // check what unit we have
      uint8_t firstByte = data[0];
      uint8_t type = (firstByte >> 1) & 0x3f;
      
      if (type == 32) // param_set
      {
          //fprintf(stderr, "param_sets: 0x%02x 0x%02x 0x%02x 0x%02x, fH264HeaderSize=%d\n", data[0], data[1], data[2], data[3], fH264HeaderSize);
          if (parseOurHeader)
          {
              if (!fH264Header)
                  fH264Header = new uint8_t[MAX_H264_PARAMS_SIZE];

              if (dataSize + fH264HeaderSize + 4 > MAX_H264_PARAMS_SIZE)
              {
                  envir() <<  "No more space for h264 params\n";
                  return;
              }

              //fprintf(stderr, "param_sets: 0x%02x 0x%02x 0x%02x 0x%02x\n", data[0], data[1], data[2], data[3]);
              //fprintf(stderr, "addData: data=%p, dataSize=%d, hdr=%p, hdrSize=%d\n", data, dataSize, fH264Header, fH264HeaderSize);

              memcpy(fH264Header + fH264HeaderSize, start_code, 4); fH264HeaderSize+=4;
              memcpy(fH264Header + fH264HeaderSize, data, dataSize); fH264HeaderSize+=dataSize;
          }
      }
      else if (type == 39) // sei
      {
          //fprintf(stderr, "sei: 0x%02x 0x%02x 0x%02x 0x%02x\n", data[0], data[1], data[2], data[3]);
          if (parseOurHeader)
          {
              if (data[1] == 0x05 && data[3] == 'V')
              {
                  // our sei
                  if (!fH264SEI) fH264SEI = new uint8_t[MAX_H264_SEI_SIZE];
                  
                  fH264SEISize = dataSize>MAX_H264_PARAMS_SIZE ? MAX_H264_PARAMS_SIZE : dataSize;
                  memcpy(fH264SEI, data, fH264SEISize);
              }
              return;
          }
          else
          {
              uint8_t *ptr;
              for (ptr = data; ptr != data + dataSize - 5; ptr++)
              {
                  uint8_t type = (ptr[4] >> 1) & 0x3f;
                  if (ptr[0] == 0x00 && ptr[1] == 0x00 && ptr[2] == 0x00 && ptr[3] == 0x01
                      && ((type >= 0 && type <= 9) || type == 19 || type == 20 || type == 32) )
                  {
                      dataSize = dataSize - (ptr-data) - 4;
                      data = ptr+4;
                      break;
                  }
              }
          }
      }
   }

   uint8_t *ptr = 0;

   if (parseOurHeader)
   {
       if ((fCodec->id == AV_CODEC_ID_H264 || fCodec->id == AV_CODEC_ID_HEVC) && fH264SEISize)
           ptr = findHeader(fH264SEI, fH264SEISize);
       else ptr = findHeader(data, dataSize);

       if (ptr != NULL)
       {
		   int result = sscanf((char*)ptr, "VNTime: %ld.%d;Size: %d;ObjsSize: %d",
                                       &tv.tv_sec, (int*)&tv.tv_usec, &realDataSize, &objectsDataSize);
		   bool haveSizes = false;
		   switch (result)
		   {
				case 2:
                                    break; // OK, no sizes, just time
				case 4:
					haveSizes = true; // We have sizes
					break;
				default:
					// seems frame is buggy.. skip it.
					envir() << "Unexpected extra headers ("<< (char*)ptr <<") Skiping..\n";
					return;
		   }
    
           hdrSize = strlen((char*)ptr) + 1/*zero byte*/;
           if (haveSizes && fCodec->id == AV_CODEC_ID_MJPEG)
           {
               // sanity check for the frame size        
               if (realDataSize != dataSize - (hdrSize+4/*comment marker*/) - objectsDataSize)
               {
                   // seems frame is buggy.. skip it.
                   envir() << "frame's size (" << dataSize - hdrSize - objectsDataSize
                           << ") is not corresponding to declared size (" 
                           << realDataSize << ".. skip it\n";
                   return;
               }
           }
       }
       else if (fPlaybackController->mediaProducer()->serverVersion() == 0) // stream is going from unknown server
       {
           envir() << "stream is going from unknown server\n";
           tv = presentationTime;
       }
       else if (fPlaybackController->mediaProducer()->serverVersion() == 0xFFFFFFFF)  // FileMediaStreamProducer version
       {
           tv = presentationTime;
       }
       else
       {
           // seems frame is buggy.. or was sent not from our server.
           // Anyway skip it.
           envir() << "Frame does not have extra headers. Skiping..\n";   
           return;
       }
   }
   else
   {
       tv = presentationTime;
   }
   
   MediaFrame *pMediaFrame;            
   try
   {
      pMediaFrame = new MediaFrame(NULL, 0, fPlaybackController->memCounter());
   }
   catch (std::bad_alloc &)
   {
      envir().setResultErrMsg("Request memory for pMediaFrame fail: ");
      envir() <<  envir().getResultMsg();
      return;
   }
    
   // copying objects data (if presents)
   if(pObjectsData)
       pMediaFrame->objectsData.append(*pObjectsData);
   else if(objectsDataSize > 0)
      pMediaFrame->objectsData.append((char*)(ptr+hdrSize), objectsDataSize);

   if (ptr && fCodec->id == AV_CODEC_ID_MJPEG)
   {
      ptr-=4; *ptr++=0xFF;*ptr++ = 0xD9;
      dataSize = ptr - data;
   }
       
   pMediaFrame->size = dataSize;
    
   /* Don't forget that ffmpeg requires a little more bytes
    * that the real frame size 
    */
   if ( (pMediaFrame->data = (unsigned char*)malloc(dataSize + fH264HeaderSize + AV_INPUT_BUFFER_PADDING_SIZE*4 + 4/*, sizeof(unsigned char)*/)) == NULL)
   {
      envir().setResultErrMsg("Request memory for pMediaFrame->data fail: ");
      envir() <<  envir().getResultMsg();
      
      delete pMediaFrame;    
      return;
   }
   
   pMediaFrame->tv = tv;

   if (fCodec->id == AV_CODEC_ID_H264 || fCodec->id == AV_CODEC_ID_HEVC)
   {
      unsigned char start_code[4] = {0x00, 0x00, 0x00, 0x01};
      if (fH264HeaderSize)
      {
#if 0
         fprintf(stderr, "Header size: %d\n", fH264HeaderSize);
         for (unsigned i= 0; i < (fH264HeaderSize>180?180:fH264HeaderSize); i++)
             fprintf(stderr, "%02X ", (uint8_t)((unsigned char*)fH264Header)[i]);
         fprintf(stderr, "\n");
#endif
         memcpy(pMediaFrame->data, fH264Header, fH264HeaderSize);

		 delete[] fRecorderExtradata;
		 fRecorderExtradataSize = fH264HeaderSize + 4;
		 fRecorderExtradata = new unsigned char[fRecorderExtradataSize];
         memcpy(fRecorderExtradata, start_code, 4);
         memcpy(fRecorderExtradata + 4, fH264Header, fH264HeaderSize);
      }
      memcpy(pMediaFrame->data + fH264HeaderSize, start_code, 4);
      memcpy(pMediaFrame->data + fH264HeaderSize + 4, data, dataSize);
      pMediaFrame->size = pMediaFrame->size + fH264HeaderSize + 4;
      fH264HeaderSize = 0;
   }
   else 
   {
      memcpy(pMediaFrame->data, data, dataSize);
   }

   CPMediaFrame cpMediaFrame(pMediaFrame);

   // fprintf(stderr, "ADD data\n");
   // for (int i= 0; i < (pMediaFrame->size>200?200:pMediaFrame->size); i++)
   // {
   //     fprintf(stderr, "%02X ", (uint8_t)((unsigned char*)pMediaFrame->data)[i]);
   // }
   // fprintf(stderr, "\n");

   FrameConsumer::addData(cpMediaFrame);
}

/*virtual*/ void VideoFrameConsumer::clear()
{
}


void VideoFrameConsumer::onNewFrame(CPMediaFrame &cpMediaFrame, bool show)
{
   CacheBoundaries cacheBoundaries;
   fPlaybackController->cache()->boundaries(&cacheBoundaries.begin, &cacheBoundaries.end);

   if (!show)
       return;
   
   Frame frame;
   frame.streamInfo   = fStreamInfo;
   frame.capturedTime = &cpMediaFrame->tv;
   frame.objectsData  = cpMediaFrame->objectsData;
   frame.dar          = fDisplayAspectRatio;

   if (!fDecoderType)
   {
       frame.streamInfo   = fStreamInfo;
       frame.data[0]      = cpMediaFrame->data;
       frame.data[1]      = 0;
       frame.data[2]      = 0;
       frame.data[3]      = 0;
       frame.dataSize[0]  = cpMediaFrame->size;
       frame.dataSize[1]  = 0;
       frame.dataSize[2]  = 0;
       frame.dataSize[3]  = 0;
       frame.width        = 0;
       frame.height       = 0;            
   }
   else
   {
       AVFrame* src = fPicture[fExternalBuffersPosition_];
       AVFrame& pic_dec = *fPictureDecoded[fExternalBuffersPosition_];

       if (fExternalBuffer_)
           fExternalBuffersPosition_ = fPlaybackController->mediaStreamHandler()->lockImageBuffer(fExternalBuffersPosition_);

       if (fSwsCtx)
           sws_scale(fSwsCtx, src->data, src->linesize,
                     0, fCodecCtx->height, pic_dec.data, pic_dec.linesize);

       if (fExternalBuffer_)
           fPlaybackController->mediaStreamHandler()->unlockImageBuffer(fExternalBuffersPosition_);

       if (fSwsCtx)
       {
           frame.data[0]      = pic_dec.data[0];
           frame.data[1]      = pic_dec.data[1];
           frame.data[2]      = pic_dec.data[2];
           frame.data[3]      = 0;

           frame.dataSize[0]  = pic_dec.linesize[0];
           frame.dataSize[1]  = pic_dec.linesize[1];
           frame.dataSize[2]  = pic_dec.linesize[2];
           frame.dataSize[3]  = 0;
       }
       else
       {
           frame.data[0]      = src->data[0];
           frame.data[1]      = src->data[1];
           frame.data[2]      = src->data[2];
           frame.data[3]      = 0;

           frame.dataSize[0]  = src->linesize[0];
           frame.dataSize[1]  = src->linesize[1];
           frame.dataSize[2]  = src->linesize[2];
           frame.dataSize[3]  = 0;

           if (src->format == AV_PIX_FMT_YUVJ420P)
               src->format = AV_PIX_FMT_YUV420P;
           else if (src->format == AV_PIX_FMT_YUVJ422P)
               src->format = AV_PIX_FMT_YUV422P;
           else if (src->format == AV_PIX_FMT_YUVJ444P)
               src->format = AV_PIX_FMT_YUV444P;
       }

       frame.width  = fCodecCtx->width;
       frame.height = fCodecCtx->height;
   }

   fPlaybackController->playMutex().unlock();
   fPlaybackController->mediaStreamHandler()->newFrame(frame, cacheBoundaries, fExternalBuffersPosition_);
   fPlaybackController->playMutex().lock();

#if 0
do {
   static int fnum = 0;
   char fname[256];
   sprintf(fname, "frame_%04d.ppm", fnum++);
   FILE *fh = fopen(fname, "w");
   if (!fh)
       break;
   /* Write PPM Header */
   fprintf(fh, "P6 %d %d %d\n", frame.width, frame.height, 255); /* width, height, maxval */
   fwrite(frame.data[0], frame.width * frame.height * 3, 1, fh);
   printf("%s\n", fname);
   fclose(fh);
} while (0);
#endif

   if(++fExternalBuffersPosition_ >= fExternalBuffersNumber_)
       fExternalBuffersPosition_ = 0;
}

void VideoFrameConsumer::onNewBrokenFrame(CPMediaFrame &cpMediaFrame, bool show)
{
   CacheBoundaries cacheBoundaries;
   fPlaybackController->cache()->boundaries(&cacheBoundaries.begin, &cacheBoundaries.end);

   if (!show)
       return;
   
   Frame frame;
   frame.streamInfo   = fStreamInfo;
   frame.capturedTime = &cpMediaFrame->tv;
   frame.objectsData  = cpMediaFrame->objectsData;
   frame.dar          = fDisplayAspectRatio;

   frame.streamInfo   = fStreamInfo;
   frame.data[0]      = 0;
   frame.data[1]      = 0;
   frame.data[2]      = 0;
   frame.dataSize[0]  = 0;
   frame.dataSize[1]  = 0;
   frame.dataSize[2]  = 0;
   frame.width        = fCodecCtx->width;
   frame.height       = fCodecCtx->height;


   fPlaybackController->playMutex().unlock();
   fPlaybackController->mediaStreamHandler()->newFrame(frame, cacheBoundaries, fExternalBuffersPosition_);
   fPlaybackController->playMutex().lock();

   if(++fExternalBuffersPosition_ >= fExternalBuffersNumber_)
       fExternalBuffersPosition_ = 0;
}

/*virtual*/ void VideoFrameConsumer::playFrame(CPMediaFrame &cpMediaFrame, bool show)
{
/*
    {
        static timeval prev_frame = {0,0};
        static timeval prev_time = {0,0};
        struct timeval now; gettimeofday(&now, 0);

        videonext::media::Timeval frame_diff = videonext::media::Timeval(cpMediaFrame->tv) - videonext::media::Timeval(prev_frame);
        videonext::media::Timeval time_diff  = videonext::media::Timeval(now) - videonext::media::Timeval(prev_time);



        fprintf(stderr, " -- frame: %d.%d(%d) %d.%d(%d)\n",
                cpMediaFrame->tv.tv_sec, cpMediaFrame->tv.tv_usec, frame_diff.seconds() * 1000000 + frame_diff.useconds(),
                now.tv_sec, now.tv_usec, time_diff.seconds() * 1000000 + time_diff.useconds());


        prev_frame = cpMediaFrame->tv;
        prev_time  = now;

    }
*/

#if 0 // DEBUG
    struct tm ptm;
    char tmp_buff[80];
    time_t ts = cpMediaFrame->tv.tv_sec;
    ACE_OS::gmtime_r(&ts, &ptm);
    strftime(tmp_buff, sizeof(tmp_buff), "%y/%m/%d-%H:%M:%S", &ptm);
    printf("Frame type: %d, decoder_type: %d, tv: %ld.%06d (%s), show: %d, %dx%d\n",
           cpMediaFrame->type, fDecoderType, cpMediaFrame->tv.tv_sec, cpMediaFrame->tv.tv_usec, tmp_buff, show, fCodecCtx->width, fCodecCtx->height);
#endif

    MutexGuard guard(fPlaybackController->playMutex());

    if (!fDecoderType)
    {
       onNewFrame(cpMediaFrame, show);
       return;
    }

    if (fLastDecodedFrameNum == cpMediaFrame->num)
    {
       // do not decode same frame twice
       fprintf(stderr, "Skip frame (num:%ld, size:%ld). Reason: was decoded early\n", 
               cpMediaFrame->num, cpMediaFrame->size);

       if (fPlaybackController->isStepMode()) {
           onNewFrame(cpMediaFrame, show);
       }

       return;
    }

    fLastDecodedFrameNum = cpMediaFrame->num;

    bool received(false);
  again:
    AVPacket pkt = {};
    av_init_packet(&pkt);
    pkt.data = cpMediaFrame->data;
    pkt.size = cpMediaFrame->size;
    int ret = avcodec_send_packet(fCodecCtx, &pkt);
    if (ret < 0)
    {
        char errbuf[256];
        fEnv.log("Error sending video frame to decoding (size: %d, pts: %ld.%06d): %s\n", pkt.size, cpMediaFrame->tv.tv_sec, (int)cpMediaFrame->tv.tv_usec, av_make_error_string(errbuf, sizeof(errbuf), ret));
        av_packet_unref(&pkt);
        return;
    }

    ret = avcodec_receive_frame(fCodecCtx, fPicture[fExternalBuffersPosition_]);

    if (ret < 0)
    {
        char errbuf[256];
        std::string frame_str;
        for (int i = 0; i < (pkt.size < 100 ? pkt.size : 100); i++)
        {
            char str[16] = {};
            sprintf(str, "%02x ", (unsigned char)pkt.data[i]);
            frame_str += str;
        }
        frame_str += ".......";
        fEnv.log("Error decoding video frame (size: %d, type: %d, pts: %ld.%06d): %s. Frame was:\n'%s'\n", pkt.size, cpMediaFrame->type, cpMediaFrame->tv.tv_sec, (int)cpMediaFrame->tv.tv_usec, av_make_error_string(errbuf, sizeof(errbuf), ret), frame_str.c_str());

        av_packet_unref(&pkt);
        if( !cpMediaFrame->objectsData.empty() && ( fPlaybackController->isStepMode() || !fPlaybackController->isPaused() ) ) {
            onNewBrokenFrame( cpMediaFrame, show );
        }

        if (cpMediaFrame->type == 0)
        {
            fEnv.log("Reloading decoder...\n");
            reloadVideoDecoder();
            if (!received)
            {
                received = true;
                av_packet_unref(&pkt);
                goto again;
            }
        }

        return;
    }

    av_packet_unref(&pkt);

    if (pictHeight && (pictHeight != fCodecCtx->height || pictWidth != fCodecCtx->width || pixFmt != fCodecCtx->pix_fmt))
    {
        if (fSwsCtx)
        {
            sws_freeContext(fSwsCtx);
            for(int i=0; i<fExternalBuffersNumber_; i++) {
                if (!fExternalBuffer_)
                    av_freep(&fPictureDecoded[i]->data[0]);
                fPictureDecoded[i]->data[0] = NULL;
            }
        }
    }
    if (NULL == fPictureDecoded[0]->data[0])
    {
        if (fCodecCtx->sample_aspect_ratio.num) 
        {
            AVRational dar;
            av_reduce(&dar.num, &dar.den,
                      fCodecCtx->width * fCodecCtx->sample_aspect_ratio.num,
                      fCodecCtx->height * fCodecCtx->sample_aspect_ratio.den,
                      1024 * 1024);

            fDisplayAspectRatio = dar.num / (float)dar.den;
        }
        else
        {
            fDisplayAspectRatio = 0.0f;
        }

        AVPixelFormat dstPixelFormat;
        switch (fPreferredPixelFormat)
        {
            case PIX_YUV420P:
            {
                if (fCodecCtx->pix_fmt == AV_PIX_FMT_YUVJ420P)
                {
                    fprintf(stderr, "Fix AV_PIX_FMT_YUV420P => AV_PIX_FMT_YUVJ420P\n");
                    dstPixelFormat = AV_PIX_FMT_YUVJ420P;
                }
                else
                    dstPixelFormat = AV_PIX_FMT_YUV420P;
                break;
            }
            case PIX_RGB24:
                dstPixelFormat = AV_PIX_FMT_RGB24;
                break;
            case PIX_RGB32:
                dstPixelFormat = AV_PIX_FMT_RGB32;
                break;
            case PIX_BGR32:
                dstPixelFormat = AV_PIX_FMT_BGR32;
                break;
            case PIX_NONE:
                dstPixelFormat = AV_PIX_FMT_NONE;
                break;
            default:
                dstPixelFormat = fCodecCtx->pix_fmt;
                envir() << "Unknown pixel format";
        }

        guard.release();

        fExternalBuffer_ = (unsigned char*)fPlaybackController->mediaStreamHandler()->createImageBuffer(fCodecCtx->width,
                                                                                                        fCodecCtx->height,
                                                                                                        fDisplayAspectRatio,
                                                                                                        fExternalBuffersNumber_);
        guard.acquire();

        if (fExternalBuffer_)
        {
            av_image_fill_linesizes(fPictureDecoded[0]->linesize, dstPixelFormat, fCodecCtx->width);

            int buf_size = av_image_fill_pointers(fPictureDecoded[0]->data, dstPixelFormat, fCodecCtx->height,
                                                  fExternalBuffer_, fPictureDecoded[0]->linesize);

            fprintf(stderr, "Single buffer size for %dx%d/%s: %d\n", fCodecCtx->width, fCodecCtx->height, av_get_pix_fmt_name(dstPixelFormat), buf_size);
            fPictureDecoded[0]->format = dstPixelFormat;

            for(int i=1; i<fExternalBuffersNumber_; i++) {
                fPicture[i]        = av_frame_alloc();
                fPictureDecoded[i] = av_frame_alloc();
                if (fPictureDecoded[i] == NULL || fPicture[i] == NULL)
                {
                    if (fPicture[i])
                        av_frame_free(&fPicture[i]);

                    if (fPictureDecoded[i])
                        av_frame_free(&fPictureDecoded[i]);

                    fPicture[i] = fPictureDecoded[i] = NULL;

                    fExternalBuffersNumber_ = (FRAME_BUFFERS_NUM)i;
                    break;
                }

                av_image_fill_linesizes(fPictureDecoded[i]->linesize, dstPixelFormat, fCodecCtx->width);

                av_image_fill_pointers(fPictureDecoded[i]->data, dstPixelFormat, fCodecCtx->height,
                                       fExternalBuffer_ + i*buf_size, fPictureDecoded[i]->linesize);

                fPictureDecoded[i]->format = dstPixelFormat;
            }
        }
        else if (dstPixelFormat != AV_PIX_FMT_NONE)
        {
            for(int i=0; i<fExternalBuffersNumber_; i++)
                av_image_alloc(fPictureDecoded[i]->data, fPictureDecoded[i]->linesize, fCodecCtx->width, fCodecCtx->height, dstPixelFormat, 16);
        }

        if (dstPixelFormat != AV_PIX_FMT_NONE && (dstPixelFormat != fCodecCtx->pix_fmt || fExternalBuffer_) )
        {
            fSwsCtx = sws_getContext(fCodecCtx->width, fCodecCtx->height, fCodecCtx->pix_fmt, 
                                     fCodecCtx->width, fCodecCtx->height,
                                     dstPixelFormat, SWS_FAST_BILINEAR, NULL, NULL, NULL);
            assert(fSwsCtx);
        }
        pictWidth = fCodecCtx->width;
        pictHeight = fCodecCtx->height;
        pixFmt = fCodecCtx->pix_fmt;

    }

    if (fPlaybackController->isStepMode()
        || !fPlaybackController->isPaused())
    {
        onNewFrame(cpMediaFrame, show);
    }
    // fprintf(stderr, "Decode size: %d, data: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n",
    //         cpMediaFrame->size, cpMediaFrame->data[4], cpMediaFrame->data[5], cpMediaFrame->data[6], cpMediaFrame->data[7],
    //                             cpMediaFrame->data[8], cpMediaFrame->data[9], cpMediaFrame->data[10], cpMediaFrame->data[11]);

    if (fPlaybackController->isPaused())
        return;

    {
        MutexGuard mg(fPlaybackController->recordingMutex());
        FileRecorder* fileRecorder = fPlaybackController->fileRecorder();

        if (fileRecorder && fCodecCtx->width > 0 && fCodecCtx->height > 0)
        {
            FileRecorder::StreamInfo streamInfo(fCodec, fCodecCtx->width, fCodecCtx->height, fRecorderExtradata, fRecorderExtradataSize);
            fileRecorder->pushFrame(streamInfo, cpMediaFrame);
        }
    }
}

/*static*/int VideoFrameConsumer::getFrameType(uint8_t *data, unsigned size, AVCodecID codec)
{
    if (codec == AV_CODEC_ID_MPEG4)
    {
        // Loking for known start code
        unsigned char *ptr = data;
        unsigned char *end = data + size - 4;
        for (; ptr < end; ptr++)
        {
           if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 1 
               && (ptr[3] == 0xb0 || ptr[3] == 0xb3 || ptr[3] == 0xb5 || ptr[3] == 0xb6))
              break;
        }

        unsigned first4Bytes = (ptr[0]<<24)|(ptr[1]<<16)|(ptr[2]<<8)|ptr[3];                
        if (first4Bytes == 0x000001b0 || first4Bytes == 0x000001b5 || first4Bytes == 0x000001b3) 
        {
            return 0; // because next going I_VOP
        }
        else if (first4Bytes == 0x000001b6) // VOP
        {   
           uint8_t nextByte = ptr[4];
           uint8_t vop_coding_type = nextByte>>6;
           return  vop_coding_type;
        }
        else 
        {
            return 1;
        }
    }
    else if (codec == AV_CODEC_ID_MJPEG) // in mjpeg case - all frames are I-frames.
    {
        return 0;
    }
    else if (codec == AV_CODEC_ID_H264)
    {
        // Looking for non SEI NALU
        unsigned char *ptr = data;
        unsigned char *end = data + size - 5;
        for (; ptr < end; ptr++)
        {
           if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == 1
               && ((ptr[4]&0x1F) != 6 /*NOT SEI*/ && (ptr[4]&0x1F) != 9))
              break;
        }

        if ((ptr[4]&0x1F) == 5 || (ptr[4]&0x1F) == 7) // IDR slice or SPS.
        {
            return 0;  
        }
        else
        { 
            return 1;
        }
    
//       fprintf(stderr, "setFrameType: %d (0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x)", cpMediaFrame->type, 
//              cpMediaFrame->data[0], cpMediaFrame->data[1], cpMediaFrame->data[2], cpMediaFrame->data[3],  cpMediaFrame->data[4]);
    }
    else if (codec == AV_CODEC_ID_HEVC)
    {
        // Looking for non SEI NALU
        unsigned char *ptr = data;
        unsigned char *end = data + size - 5;
        for (; ptr < end; ptr++)
        {
            uint8_t type = (ptr[4] >> 1) & 0x3f;
            //printf("frame type: %d\n", type);
            if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == 1
                && (type != 39 /*NOT SEI*/))
                break;
        }

        uint8_t type = (ptr[4] >> 1) & 0x3f;
        if (type == 32 || type == 19 || type == 20) // IDR slice or VPS.
        {
            return 0;  
        }
        else
        { 
            return 1;
        }
    
//       fprintf(stderr, "setFrameType: %d (0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x)", cpMediaFrame->type, 
//              cpMediaFrame->data[0], cpMediaFrame->data[1], cpMediaFrame->data[2], cpMediaFrame->data[3],  cpMediaFrame->data[4]);
    }
    else if (codec == AV_CODEC_ID_MPEG2VIDEO)
    {
        // Loking for known start code
        unsigned char *ptr = data;
        unsigned char *end = data + size - 5;
        for (; ptr < end; ptr++)
        {
            if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 1
                && (ptr[3] == 0xb3 || ptr[3] == 0xb8 || ptr[3] == 0x00))
                break;
        }

        unsigned first4Bytes = (ptr[0]<<24)|(ptr[1]<<16)|(ptr[2]<<8)|ptr[3];    

        if (first4Bytes == 0x000001b3 || first4Bytes == 0x000001b8) 
        {
           return 0; // because next going I_VOP
        }

        if (first4Bytes == 0x00000100) // VOP
        {
            ptr+=4;
            unsigned next4Bytes = (ptr[0]<<24)|(ptr[1]<<16)|(ptr[2]<<8)|ptr[3];
            unsigned char picture_coding_type = (next4Bytes&0x00380000)>>19;
            if (picture_coding_type == 1) // Intra-coded
            {            
                return 0;  
            }
            else 
                return  1;
        }

        return 1;
    }



    return 0;
}

/*virtual*/ void VideoFrameConsumer::setFrameType(CPMediaFrame &cpMediaFrame)
{
    cpMediaFrame->mediaType = MediaFrame::VIDEO;
    cpMediaFrame->type      = getFrameType(cpMediaFrame->data, cpMediaFrame->size, fCodec->id);

//    fprintf(stderr, "setFrameType: %d (0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x)", cpMediaFrame->type, 
//              cpMediaFrame->data[0], cpMediaFrame->data[1], cpMediaFrame->data[2], cpMediaFrame->data[3],  cpMediaFrame->data[4]);

}

}}
