/*
 *      Copyright (C) 2010-2013 Team XBMC
 *      http://xbmc.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with XBMC; see the file COPYING.  If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

#include "AEStreamInfo.h"
#include "utils/log.h"
#include <algorithm>
#include <string.h>

#define IEC61937_PREAMBLE1 0xF872
#define IEC61937_PREAMBLE2 0x4E1F
#define DTS_PREAMBLE_14BE  0x1FFFE800
#define DTS_PREAMBLE_14LE  0xFF1F00E8
#define DTS_PREAMBLE_16BE  0x7FFE8001
#define DTS_PREAMBLE_16LE  0xFE7F0180
#define DTS_PREAMBLE_HD    0x64582025
#define DTS_SFREQ_COUNT    16
#define MAX_EAC3_BLOCKS    6

static enum AEChannel OutputMaps[2][9] = {
  {AE_CH_RAW, AE_CH_RAW, AE_CH_NULL},
  {AE_CH_RAW, AE_CH_RAW, AE_CH_RAW, AE_CH_RAW, AE_CH_RAW, AE_CH_RAW, AE_CH_RAW, AE_CH_RAW, AE_CH_NULL}
};

static const uint16_t AC3Bitrates   [] = {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640};
static const uint16_t AC3FSCod      [] = {48000, 44100, 32000, 0};
static const uint8_t  AC3BlkCod     [] = {1, 2, 3, 6};
static const uint8_t  AC3Channels   [] = {2, 1, 2, 3, 3, 4, 4, 5};
static const uint8_t  DTSChannels   [] = {1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8};
static const uint8_t  THDChanMap    [] = {2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1};

static const uint32_t DTSSampleRates[DTS_SFREQ_COUNT] =
{
  0     ,
  8000  ,
  16000 ,
  32000 ,
  64000 ,
  128000,
  11025 ,
  22050 ,
  44100 ,
  88200 ,
  176400,
  12000 ,
  24000 ,
  48000 ,
  96000 ,
  192000
};

CAEStreamInfo::CAEStreamInfo() :
  m_bufferSize    (0),
  m_skipBytes     (0),
  m_coreOnly      (false),
  m_needBytes     (0),
  m_syncFunc      (&CAEStreamInfo::DetectType),
  m_hasSync       (false),
  m_sampleRate    (0),
  m_outputRate    (0),
  m_outputChannels(0),
  m_channelMap    (CAEChannelInfo()),
  m_channels      (0),
  m_coreSize      (0),
  m_dtsBlocks     (0),
  m_dtsPeriod     (0),
  m_fsize         (0),
  m_fsizeMain     (0),
  m_repeat        (0),
  m_substreams    (0),
  m_dataType      (STREAM_TYPE_NULL),
  m_dataIsLE      (false),
  m_packFunc      (NULL)
{
  av_crc_init(m_crcTrueHD, 0, 16, 0x2D, sizeof(m_crcTrueHD));
}

CAEStreamInfo::~CAEStreamInfo()
{
}

int CAEStreamInfo::AddData(uint8_t *data, unsigned int size, uint8_t **buffer/* = NULL */, unsigned int *bufferSize/* = 0 */)
{
  if (size == 0)
  {
    if (bufferSize)
      *bufferSize = 0;
    return 0;
  }

  if (m_skipBytes)
  {
    unsigned int canSkip = std::min(size, m_skipBytes);
    unsigned int room    = sizeof(m_buffer) - m_bufferSize;
    unsigned int copy    = std::min(room, canSkip);

    memcpy(m_buffer + m_bufferSize, data, copy);
    m_bufferSize += copy;
    m_skipBytes  -= copy;

    if (m_skipBytes)
    {
      if (bufferSize)
        *bufferSize = 0;
      return copy;
    }

    GetPacket(buffer, bufferSize);
    return copy;
  }
  else
  {
    unsigned int consumed = 0;
    unsigned int offset = 0;
    unsigned int room = sizeof(m_buffer) - m_bufferSize;
    while(1)
    {
      if (!size)
      {
        if (bufferSize)
          *bufferSize = 0;
        return consumed;
      }

      unsigned int copy = std::min(room, size);
      memcpy(m_buffer + m_bufferSize, data, copy);
      m_bufferSize += copy;
      consumed     += copy;
      data         += copy;
      size         -= copy;
      room         -= copy;

      if (m_needBytes > m_bufferSize)
        continue;

      m_needBytes = 0;
      offset      = (this->*m_syncFunc)(m_buffer, m_bufferSize);

      if (m_hasSync || m_needBytes)
        break;
      else
      {
        /* lost sync */
        m_syncFunc = &CAEStreamInfo::DetectType;
        m_dataType = STREAM_TYPE_NULL;
        m_packFunc = NULL;
        m_repeat   = 1;

        /* if the buffer is full, or the offset < the buffer size */
        if (m_bufferSize == sizeof(m_buffer) || offset < m_bufferSize)
        {
          m_bufferSize -= offset;
          room         += offset;
          memmove(m_buffer, m_buffer + offset, m_bufferSize);
        }
      }
    }

    /* if we got here, we acquired sync on the buffer */

    /* align the buffer */
    if (offset)
    {
      m_bufferSize -= offset;
      memmove(m_buffer, m_buffer + offset, m_bufferSize);
    }

    /* bytes to skip until the next packet */
    m_skipBytes = std::max(0, (int)m_fsize - (int)m_bufferSize);
    if (m_skipBytes)
    {
      if (bufferSize)
        *bufferSize = 0;
      return consumed;
    }

    if (!m_needBytes)
      GetPacket(buffer, bufferSize);

    return consumed;
  }
}

void CAEStreamInfo::GetPacket(uint8_t **buffer, unsigned int *bufferSize)
{
  /* if the caller wants the packet */
  if (buffer)
  {
    /* if it is dtsHD and we only want the core, just fetch that */
    unsigned int size = m_fsize;
    if (m_dataType == STREAM_TYPE_DTSHD_CORE)
      size = m_coreSize;

    /* make sure the buffer is allocated and big enough */
    if (!*buffer || !bufferSize || *bufferSize < size)
    {
      delete[] *buffer;
      *buffer = new uint8_t[size];
    }

    /* copy the data into the buffer and update the size */
    memcpy(*buffer, m_buffer, size);
    if (bufferSize)
      *bufferSize = size;
  }

  /* remove the parsed data from the buffer */
  m_bufferSize -= m_fsize;
  memmove(m_buffer, m_buffer + m_fsize, m_bufferSize);
  m_fsize = 0;
  m_coreSize = 0;
}

/* SYNC FUNCTIONS */

/*
  This function looks for sync words across the types in paralell, and only does an exhaustive
  test if it finds a syncword. Once sync has been established, the relevent sync function sets
  m_syncFunc to itself. This function will only be called again if total sync is lost, which
  allows is to switch stream types on the fly much like a real reicever does.
*/
unsigned int CAEStreamInfo::DetectType(uint8_t *data, unsigned int size)
{
  unsigned int skipped  = 0;
  unsigned int possible = 0;

  while (size > 8)
  {
    /* if it could be DTS */
    unsigned int header = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
    if (header == DTS_PREAMBLE_14LE ||
        header == DTS_PREAMBLE_14BE ||
        header == DTS_PREAMBLE_16LE ||
        header == DTS_PREAMBLE_16BE)
    {
      unsigned int skip = SyncDTS(data, size);
      if (m_hasSync || m_needBytes)
        return skipped + skip;
      else
        possible = skipped;
    }

    /* if it could be AC3 */
    if (data[0] == 0x0b && data[1] == 0x77)
    {
      unsigned int skip = SyncAC3(data, size);
      if (m_hasSync)
        return skipped + skip;
      else
        possible = skipped;
    }

    /* if it could be TrueHD */
    if (data[4] == 0xf8 && data[5] == 0x72 && data[6] == 0x6f && data[7] == 0xba)
    {
      unsigned int skip = SyncTrueHD(data, size);
      if (m_hasSync)
        return skipped + skip;
      else
        possible = skipped;
    }

    /* move along one byte */
    --size;
    ++skipped;
    ++data;
  }

  return possible ? possible : skipped;
}

unsigned int CAEStreamInfo::SyncAC3(uint8_t *data, unsigned int size)
{
  unsigned int skip = 0;

  // handle substreams
  if (m_fsizeMain)
  {
    data += m_fsizeMain;
  }

  for (; size - skip > 7; ++skip, ++data)
  {
    /* search for an ac3 sync word */
    if (data[0] != 0x0b || data[1] != 0x77)
      continue;

    uint8_t bsid  = data[5] >> 3;
    uint8_t acmod = data[6] >> 5;
    uint8_t lfeon;

    int8_t pos = 4;
    if ((acmod & 0x1) && (acmod != 0x1))
      pos -= 2;
    if (acmod & 0x4 )
      pos -= 2;
    if (acmod == 0x2)
      pos -= 2;
    if (pos < 0)
      lfeon = (data[7] & 0x64) ? 1 : 0;
    else
      lfeon = ((data[6] >> pos) & 0x1) ? 1 : 0;

    if (bsid > 0x11 || acmod > 7)
      continue;

    if (bsid <= 10)
    {
      /* Normal AC-3 */

      uint8_t fscod      = data[4] >> 6;
      uint8_t frmsizecod = data[4] & 0x3F;
      if (fscod == 3 || frmsizecod > 37)
        continue;

      /* get the details we need to check crc1 and framesize */
      unsigned int bitRate = AC3Bitrates[frmsizecod >> 1];
      unsigned int framesize = 0;
      switch (fscod)
      {
        case 0: framesize = bitRate * 2; break;
        case 1: framesize = (320 * bitRate / 147 + (frmsizecod & 1 ? 1 : 0)); break;
        case 2: framesize = bitRate * 4; break;
      }

      m_fsize = framesize << 1;
      m_sampleRate = AC3FSCod[fscod];

      /* dont do extensive testing if we have not lost sync */
      /* this may be the main stream of EAC3 */
      if (m_dataType == STREAM_TYPE_EAC3 && skip == 0)
      {
        m_fsizeMain = m_fsize;
        m_fsize = 0;
        return 0;
      }
      else if (m_dataType == STREAM_TYPE_AC3 && skip == 0)
        return 0;

      unsigned int crc_size;
      /* if we have enough data, validate the entire packet, else try to validate crc2 (5/8 of the packet) */
      if (framesize <= size - skip)
        crc_size = framesize - 1;
      else 
        crc_size = (framesize >> 1) + (framesize >> 3) - 1;

      if (crc_size <= size - skip)
        if (av_crc(av_crc_get_table(AV_CRC_16_ANSI), 0, &data[2], crc_size * 2))
          continue;

      /* if we get here, we can sync */
      m_hasSync        = true;
      m_outputRate     = m_sampleRate;
      m_outputChannels = 2;
      m_channelMap     = CAEChannelInfo(OutputMaps[0]);
      m_channels       = AC3Channels[acmod] + lfeon;
      m_syncFunc       = &CAEStreamInfo::SyncAC3;
      m_dataType       = STREAM_TYPE_AC3;
      m_packFunc       = &CAEPackIEC61937::PackAC3;
      m_repeat         = 1;

      CLog::Log(LOGINFO, "CAEStreamInfo::SyncAC3 - AC3 stream detected (%d channels, %dHz)", m_channels, m_sampleRate);
      return skip;
    }
    else
    {
      /* Enhanced AC-3 */
      uint8_t strmtyp = data[2] >> 6;
      if (strmtyp == 3)
        continue;

      unsigned int framesize = (((data[2] & 0x7) << 8) | data[3]) + 1;
      uint8_t      fscod     = (data[4] >> 6) & 0x3;
      uint8_t      cod       = (data[4] >> 4) & 0x3;
      uint8_t      blocks;

      if (fscod == 0x3)
      {
        if (cod == 0x3)
          continue;

        blocks       = 6;
        m_sampleRate = AC3FSCod[cod] >> 1;
      }
      else
      {
        blocks       = AC3BlkCod[cod  ];
        m_sampleRate = AC3FSCod [fscod];
      }

      m_fsize        = framesize << 1;

      // concatenate substream to independent stream
      if (strmtyp == 1 && m_fsizeMain)
      {
        m_fsize += m_fsizeMain;
      }
      m_fsizeMain = 0;

      m_repeat       = MAX_EAC3_BLOCKS / blocks;

      // E-AC-3 rate is 4 times bitstream sample rate as per IEC 61937.
      m_outputRate = 4 * m_sampleRate;

      if (m_dataType == STREAM_TYPE_EAC3 && m_hasSync && skip == 0)
        return 0;

      /* if we get here, we can sync */
      m_hasSync        = true;
      m_outputChannels = 2;
      m_channelMap     = CAEChannelInfo(OutputMaps[0]);
      m_channels       = 8; /* FIXME: this should be read out of the stream */
      m_syncFunc       = &CAEStreamInfo::SyncAC3;
      m_dataType       = STREAM_TYPE_EAC3;
      m_packFunc       = &CAEPackIEC61937::PackEAC3;
      m_fsizeMain      = 0;

      CLog::Log(LOGINFO, "CAEStreamInfo::SyncAC3 - E-AC3 stream detected (%d channels, %dHz)", m_channels, m_sampleRate);
      return skip;
    }
  }

  /* if we get here, the entire packet is invalid and we have lost sync */
  CLog::Log(LOGINFO, "CAEStreamInfo::SyncAC3 - AC3 sync lost");
  m_hasSync = false;
  m_fsizeMain = 0;
  return skip;
}

unsigned int CAEStreamInfo::SyncDTS(uint8_t *data, unsigned int size)
{
  if (size < 13)
  {
    if (m_needBytes < 13)
      m_needBytes = 14;
    return 0;
  }

  unsigned int skip = 0;
  for (; size - skip > 13; ++skip, ++data)
  {
    unsigned int header = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
    unsigned int hd_sync = 0;
    bool match = true;
    unsigned int dtsBlocks;
    unsigned int amode;
    unsigned int sfreq;
    unsigned int lfe;
    int bits;

    switch (header)
    {
      /* 14bit BE */
      case DTS_PREAMBLE_14BE:
        if (data[4] != 0x07 || (data[5] & 0xf0) != 0xf0)
        {
          match = false;
          break;
        }
        dtsBlocks = (((data[5] & 0x7) << 4) | ((data[6] & 0x3C) >> 2)) + 1;
        m_fsize     = (((((data[6] & 0x3) << 8) | data[7]) << 4) | ((data[8] & 0x3C) >> 2)) + 1;
        amode       = ((data[8] & 0x3) << 4) | ((data[9] & 0xF0) >> 4);
        sfreq       = data[9] & 0xF;
        lfe         = (data[12] & 0x18) >> 3;
        m_dataIsLE  = false;
        bits        = 14;
        break;

      /* 14bit LE */
      case DTS_PREAMBLE_14LE:
        if (data[5] != 0x07 || (data[4] & 0xf0) != 0xf0)
        {
          match = false;
          break;
        }
        dtsBlocks = (((data[4] & 0x7) << 4) | ((data[7] & 0x3C) >> 2)) + 1;
        m_fsize     = (((((data[7] & 0x3) << 8) | data[6]) << 4) | ((data[9] & 0x3C) >> 2)) + 1;
        amode       = ((data[9] & 0x3) << 4) | ((data[8] & 0xF0) >> 4);
        sfreq       = data[8] & 0xF;
        lfe         = (data[13] & 0x18) >> 3;
        m_dataIsLE  = true;
        bits        = 14;
        break;

      /* 16bit BE */
      case DTS_PREAMBLE_16BE:
        dtsBlocks = (((data[4] & 0x1) << 7) | ((data[5] & 0xFC) >> 2)) + 1;
        m_fsize     = (((((data[5] & 0x3) << 8) | data[6]) << 4) | ((data[7] & 0xF0) >> 4)) + 1;
        amode       = ((data[7] & 0x0F) << 2) | ((data[8] & 0xC0) >> 6);
        sfreq       = (data[8] & 0x3C) >> 2;
        lfe         = (data[10] >> 1) & 0x3;
        m_dataIsLE  = false;
        bits        = 16;
        break;

      /* 16bit LE */
      case DTS_PREAMBLE_16LE:
        dtsBlocks = (((data[5] & 0x1) << 7) | ((data[4] & 0xFC) >> 2)) + 1;
        m_fsize     = (((((data[4] & 0x3) << 8) | data[7]) << 4) | ((data[6] & 0xF0) >> 4)) + 1;
        amode       = ((data[6] & 0x0F) << 2) | ((data[9] & 0xC0) >> 6);
        sfreq       = (data[9] & 0x3C) >> 2;
        lfe         = (data[11] >> 1) & 0x3;
        m_dataIsLE  = true;
        bits        = 16;
        break;

      default:
        match = false;
        break;
    }

    if (!match || sfreq == 0 || sfreq >= DTS_SFREQ_COUNT)
      continue;

    /* make sure the framesize is sane */
    if (m_fsize < 96 || m_fsize > 16384)
      continue;

    bool invalid = false;
    DataType dataType;
    switch (dtsBlocks << 5)
    {
      case 512 : dataType = STREAM_TYPE_DTS_512 ; break;
      case 1024: dataType = STREAM_TYPE_DTS_1024; break;
      case 2048: dataType = STREAM_TYPE_DTS_2048; break;
      default:
        invalid = true;
        break;
    }

    if (invalid)
      continue;

    /* adjust the fsize for 14 bit streams */
    if (bits == 14)
      m_fsize = m_fsize / 14 * 16;

    /* we need enough data to check for DTS-HD */
    if (size - skip < m_fsize + 10)
    {
      /* we can assume DTS sync at this point */
      m_syncFunc  = &CAEStreamInfo::SyncDTS;
      m_needBytes = m_fsize + 10;
      m_fsize     = 0;

      return skip;
    }

    /* look for DTS-HD */
    hd_sync = (data[m_fsize] << 24) | (data[m_fsize + 1] << 16) | (data[m_fsize + 2] << 8) | data[m_fsize + 3];
    if (hd_sync == DTS_PREAMBLE_HD)
    {
      int hd_size;
      bool blownup = (data[m_fsize + 5] & 0x20) != 0;
      if (blownup)
        hd_size = (((data[m_fsize + 6] & 0x01) << 19) | (data[m_fsize + 7] << 11) | (data[m_fsize + 8] << 3) | ((data[m_fsize + 9] & 0xe0) >> 5)) + 1;
      else
        hd_size = (((data[m_fsize + 6] & 0x1f) << 11) | (data[m_fsize + 7] << 3) | ((data[m_fsize + 8] & 0xe0) >> 5)) + 1;

      /* set the type according to core or not */
      if (m_coreOnly)
        dataType = STREAM_TYPE_DTSHD_CORE;
      else
        dataType = STREAM_TYPE_DTSHD;

      m_coreSize  = m_fsize;
      m_fsize    += hd_size;
    }

    unsigned int sampleRate = DTSSampleRates[sfreq];
    if (!m_hasSync || skip || dataType != m_dataType || sampleRate != m_sampleRate || dtsBlocks != m_dtsBlocks)
    {
      m_hasSync        = true;
      m_dataType       = dataType;
      m_sampleRate     = sampleRate;
      m_dtsBlocks      = dtsBlocks;
      m_channels       = DTSChannels[amode] + (lfe ? 1 : 0);
      m_syncFunc       = &CAEStreamInfo::SyncDTS;
      m_repeat         = 1;

      if (dataType == STREAM_TYPE_DTSHD)
      {
        m_outputRate     = 192000;
        m_outputChannels = 8;
        m_channelMap     = CAEChannelInfo(OutputMaps[1]);
        m_channels      += 2; /* FIXME: this needs to be read out, not sure how to do that yet */
      }
      else
      {
        m_outputRate     = m_sampleRate;
        m_outputChannels = 2;
        m_channelMap     = CAEChannelInfo(OutputMaps[0]);
      }

      std::string type;
      switch (dataType)
      {
        case STREAM_TYPE_DTSHD     : type = "dtsHD"; break;
        case STREAM_TYPE_DTSHD_CORE: type = "dtsHD (core)"; break;
        default                    : type = "dts"; break;
      }

      /* calculate the period size for dtsHD */
      m_dtsPeriod = (m_outputRate * (m_outputChannels >> 1)) * (m_dtsBlocks << 5) / m_sampleRate;

      CLog::Log(LOGINFO, "CAEStreamInfo::SyncDTS - %s stream detected (%d channels, %dHz, %dbit %s, period: %u)",
                type.c_str(), m_channels, m_sampleRate,
                bits, m_dataIsLE ? "LE" : "BE",
                m_dtsPeriod);
    }

    return skip;
  }

  /* lost sync */
  CLog::Log(LOGINFO, "CAEStreamInfo::SyncDTS - DTS sync lost");
  m_hasSync = false;
  return skip;
}

inline unsigned int CAEStreamInfo::GetTrueHDChannels(const uint16_t chanmap)
{
  int channels = 0;
  for (int i = 0; i < 13; ++i)
    channels += THDChanMap[i] * ((chanmap >> i) & 1);
  return channels;
}

unsigned int CAEStreamInfo::SyncTrueHD(uint8_t *data, unsigned int size)
{
  unsigned int left = size;
  unsigned int skip = 0;

  /* if MLP */
  for (; left; ++skip, ++data, --left)
  {
    /* if we dont have sync and there is less the 8 bytes, then break out */
    if (!m_hasSync && left < 8)
      return size;

    /* if its a major audio unit */
    uint16_t length   = ((data[0] & 0x0F) << 8 | data[1]) << 1;
    uint32_t syncword = ((((data[4] << 8 | data[5]) << 8) | data[6]) << 8) | data[7];
    if (syncword == 0xf8726fba)
    {
      /* we need 32 bytes to sync on a master audio unit */
      if (left < 32)
        return skip;

      /* get the rate and ensure its valid */
      int rate = (data[8] & 0xf0) >> 4;
      if (rate == 0xF)
        continue;

      unsigned int major_sync_size = 28;
      if (data[29] & 1)
      {
        /* extension(s) present, look up count */
        int extension_count = data[30] >> 4;
        major_sync_size += 2 + extension_count * 2;
      }

      if (left < 4 + major_sync_size)
        return skip;

      /* verify the crc of the audio unit */
      uint16_t crc = av_crc(m_crcTrueHD, 0, data + 4, major_sync_size - 4);
      crc ^= (data[4 + major_sync_size - 3] << 8) | data[4 + major_sync_size - 4];
      if (((data[4 + major_sync_size - 1] << 8) | data[4 + major_sync_size - 2]) != crc)
        continue;

      /* get the sample rate and substreams, we have a valid master audio unit */
      m_sampleRate = (rate & 0x8 ? 44100 : 48000) << (rate & 0x7);
      m_substreams = (data[20] & 0xF0) >> 4;

      /* get the number of encoded channels */
      uint16_t channel_map = ((data[10] & 0x1F) << 8) | data[11];
      if (!channel_map)
        channel_map = (data[9] << 1) | (data[10] >> 7);
      m_channels = CAEStreamInfo::GetTrueHDChannels(channel_map);

      if (m_sampleRate == 48000 || m_sampleRate == 96000 || m_sampleRate == 192000)
        m_outputRate = 192000;
      else
        m_outputRate = 176400;

      if (!m_hasSync)
        CLog::Log(LOGINFO, "CAEStreamInfo::SyncTrueHD - TrueHD stream detected (%d channels, %dHz)", m_channels, m_sampleRate);

      m_hasSync        = true;
      m_fsize          = length;
      m_dataType       = STREAM_TYPE_TRUEHD;
      m_outputChannels = 8;
      m_channelMap     = CAEChannelInfo(OutputMaps[1]);
      m_syncFunc       = &CAEStreamInfo::SyncTrueHD;
      m_packFunc       = &CAEPackIEC61937::PackTrueHD;
      m_repeat         = 1;
      return skip;
    }
    else
    {
      /* we cant sink to a subframe until we have the information from a master audio unit */
      if (!m_hasSync)
        continue;

      /* if there is not enough data left to verify the packet, just return the skip amount */
      if (left < (unsigned int)m_substreams * 4)
        return skip;

      /* verify the parity */
      int     p     = 0;
      uint8_t check = 0;
      for (int i = -1; i < m_substreams; ++i)
      {
        check ^= data[p++];
        check ^= data[p++];
        if (i == -1 || data[p - 2] & 0x80)
        {
          check ^= data[p++];
          check ^= data[p++];
        }
      }

      /* if the parity nibble does not match */
      if ((((check >> 4) ^ check) & 0xF) != 0xF)
      {
        /* lost sync */
        m_hasSync  = false;
        CLog::Log(LOGINFO, "CAEStreamInfo::SyncTrueHD - Sync Lost");
        continue;
      }
      else
      {
        m_fsize = length;
        return skip;
      }
    }
  }

  /* lost sync */
  m_hasSync  = false;
  return skip;
}

