/*
 * receiver.c: Frontend Status Monitor plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 */

#include <unistd.h>

#include "config.h"
#include "log.h"
#include "tools.h"
#include "receiver.h"

cFemonReceiver::cFemonReceiver(const cChannel *channelP, int aTrackP, int dTrackP)
: cReceiver(channelP),
  cThread("femon receiver"),
  mutexM(),
  sleepM(),
  activeM(false),
  detectH264M(this),
  detectH265M(this),
  detectMpegM(this, this),
  detectAacM(this),
  detectLatmM(this),
  detectAc3M(this),
  videoBufferM(KILOBYTE(512), TS_SIZE, false, "Femon video"),
  videoTypeM(channelP ? channelP->Vtype(): 0),
  videoPidM(channelP ? channelP->Vpid() : 0),
  videoPacketCountM(0),
  videoBitRateM(0.0),
  videoValidM(false),
  audioBufferM(KILOBYTE(256), TS_SIZE, false, "Femon audio"),
  audioPidM(channelP ? channelP->Apid(aTrackP) : 0),
  audioPacketCountM(0),
  audioBitRateM(0.0),
  audioValidM(false),
  ac3BufferM(KILOBYTE(256), TS_SIZE, false, "Femon AC3"),
  ac3PidM(channelP ? channelP->Dpid(dTrackP) : 0),
  ac3PacketCountM(0),
  ac3BitRateM(0),
  ac3ValidM(false)
{
  debug1("%s (, %d, %d)", __PRETTY_FUNCTION__, aTrackP, dTrackP);

  SetPids(NULL);
  AddPid(videoPidM);
  AddPid(audioPidM);
  AddPid(ac3PidM);

  videoBufferM.SetTimeouts(0, 100);
  audioBufferM.SetTimeouts(0, 100);
  ac3BufferM.SetTimeouts(0, 100);

  videoInfoM.codec = VIDEO_CODEC_INVALID;
  videoInfoM.format = VIDEO_FORMAT_INVALID;
  videoInfoM.scan = VIDEO_SCAN_INVALID;
  videoInfoM.aspectRatio = VIDEO_ASPECT_RATIO_INVALID;
  videoInfoM.width = 0;
  videoInfoM.height = 0;
  videoInfoM.frameRate = 0;
  videoInfoM.bitrate = AUDIO_BITRATE_INVALID;
  audioInfoM.codec = AUDIO_CODEC_UNKNOWN;
  audioInfoM.bitrate = AUDIO_BITRATE_INVALID;
  audioInfoM.samplingFrequency = AUDIO_SAMPLING_FREQUENCY_INVALID;
  audioInfoM.channelMode = AUDIO_CHANNEL_MODE_INVALID;
  ac3InfoM.bitrate = AUDIO_BITRATE_INVALID;
  ac3InfoM.samplingFrequency = AUDIO_SAMPLING_FREQUENCY_INVALID;
  ac3InfoM.bitstreamMode = AUDIO_BITSTREAM_MODE_INVALID;
  ac3InfoM.audioCodingMode = AUDIO_CODING_MODE_INVALID;
  ac3InfoM.dolbySurroundMode = AUDIO_DOLBY_SURROUND_MODE_INVALID;
  ac3InfoM.centerMixLevel = AUDIO_CENTER_MIX_LEVEL_INVALID;
  ac3InfoM.surroundMixLevel = AUDIO_SURROUND_MIX_LEVEL_INVALID;
  ac3InfoM.dialogLevel = 0;
  ac3InfoM.lfe = false;
}

cFemonReceiver::~cFemonReceiver(void)
{
  debug1("%s", __PRETTY_FUNCTION__);
  Deactivate();
}

void cFemonReceiver::Deactivate(void)
{
  debug1("%s", __PRETTY_FUNCTION__);
  Detach();
  if (activeM) {
     activeM = false;
     sleepM.Signal();
     if (Running())
        Cancel(3);
     }
}

void cFemonReceiver::Activate(bool onP)
{
  debug1("%s (%d)", __PRETTY_FUNCTION__, onP);
  if (onP)
     Start();
  else
     Deactivate();
}

void cFemonReceiver::Receive(const uchar *dataP, int lengthP)
{
  // TS packet length: TS_SIZE
  if (Running() && (*dataP == TS_SYNC_BYTE) && (lengthP == TS_SIZE)) {
     int len, pid = TsPid(dataP);
     if (pid == videoPidM) {
        ++videoPacketCountM;
        len = videoBufferM.Put(dataP, lengthP);
        if (len != lengthP) {
           videoBufferM.ReportOverflow(lengthP - len);
           videoBufferM.Clear();
           }
        }
     else if (pid == audioPidM) {
        ++audioPacketCountM;
        len = audioBufferM.Put(dataP, lengthP);
        if (len != lengthP) {
           audioBufferM.ReportOverflow(lengthP - len);
           audioBufferM.Clear();
           }
        }
     else if (pid == ac3PidM) {
        ++ac3PacketCountM;
        len = ac3BufferM.Put(dataP, lengthP);
        if (len != lengthP) {
           ac3BufferM.ReportOverflow(lengthP - len);
           ac3BufferM.Clear();
           }
        }
     }
}

void cFemonReceiver::Action(void)
{
  debug1("%s", __PRETTY_FUNCTION__);
  cTimeMs calcPeriod(0);
  activeM = true;

  while (Running() && activeM) {
    uint8_t *Data;
    double timeout;
    int len, Length;
    bool processed = false;

    // process available video data
    while ((Data = videoBufferM.Get(Length))) {
      if (!activeM || (Length < TS_SIZE))
         break;
      Length = TS_SIZE;
      if (*Data != TS_SYNC_BYTE) {
         for (int i = 1; i < Length; ++i) {
             if (Data[i] == TS_SYNC_BYTE) {
                Length = i;
                break;
                }
             }
         videoBufferM.Del(Length);
         continue;
         }
      processed = true;
      if (TsPayloadStart(Data)) {
         while (const uint8_t *p = videoAssemblerM.GetPes(len)) {
           if (videoTypeM == 0x1B) {
              if (detectH264M.processVideo(p, len)) {
                 videoValidM = true;
                 break;
                 }
              }
           else if (videoTypeM == 0x24) {
              if (detectH265M.processVideo(p, len)) {
                 videoValidM = true;
                 break;
                 }
              }
           else {
              if (detectMpegM.processVideo(p, len)) {
                 videoValidM = true;
                 break;
                 }
              }
           }
         videoAssemblerM.Reset();
         }
      videoAssemblerM.PutTs(Data, Length);
      videoBufferM.Del(Length);
      }

    // process available audio data
    while ((Data = audioBufferM.Get(Length))) {
      if (!activeM || (Length < TS_SIZE))
         break;
      Length = TS_SIZE;
      if (*Data != TS_SYNC_BYTE) {
         for (int i = 1; i < Length; ++i) {
             if (Data[i] == TS_SYNC_BYTE) {
                Length = i;
                break;
                }
             }
         audioBufferM.Del(Length);
         continue;
         }
      processed = true;
      if (const uint8_t *p = audioAssemblerM.GetPes(len)) {
         if (detectAacM.processAudio(p, len) || detectLatmM.processAudio(p, len) || detectMpegM.processAudio(p, len))
            audioValidM = true;
         audioAssemblerM.Reset();
         }
      audioAssemblerM.PutTs(Data, Length);
      audioBufferM.Del(Length);
      }

    // process available dolby data
    while ((Data = ac3BufferM.Get(Length))) {
      if (!activeM || (Length < TS_SIZE))
         break;
      Length = TS_SIZE;
      if (*Data != TS_SYNC_BYTE) {
         for (int i = 1; i < Length; ++i) {
             if (Data[i] == TS_SYNC_BYTE) {
                Length = i;
                break;
                }
             }
         ac3BufferM.Del(Length);
         continue;
         }
      processed = true;
      if (const uint8_t *p = ac3AssemblerM.GetPes(len)) {
         if (detectAc3M.processAudio(p, len))
            ac3ValidM = true;
         ac3AssemblerM.Reset();
         }
      ac3AssemblerM.PutTs(Data, Length);
      ac3BufferM.Del(Length);
      }

    // calculate bitrates
    timeout = double(calcPeriod.Elapsed());
    if (activeM && (timeout >= (100.0 * FemonConfig.GetCalcInterval()))) {
       // TS packet 188 bytes - 4 byte header; MPEG standard defines 1Mbit = 1000000bit
       // PES headers should be compensated!
       videoBitRateM     = (1000.0 * 8.0 * 184.0 * videoPacketCountM) / timeout;
       videoPacketCountM = 0;
       audioBitRateM     = (1000.0 * 8.0 * 184.0 * audioPacketCountM) / timeout;
       audioPacketCountM = 0;
       ac3BitRateM       = (1000.0 * 8.0 * 184.0 * ac3PacketCountM)   / timeout;
       ac3PacketCountM   = 0;
       calcPeriod.Set(0);
       }

    if (!processed)
       sleepM.Wait(10); // to avoid busy loop and reduce cpu load
    }
}
