/*  mpeg.cpp
    MPEG format helper functions

    12.09.2001: tk, initial implementation, loosely based on
                    some MPG123 functions.

    Copyright (c)2000 by Thomas Kindler, thomas.kindler@gmx.de

    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 of
    the License, or (at your option) any later version. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files -----
//
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "mpeg.h"


/**
 * Do a quick validity check on a MPEG Header.
 *
 * \note    a check for bitrate==0 was added, because we can't
 *          calculate the framelength for free form streams.
 * \param   header  pointer to header to check
 * \return  true if it could be a valid header,
 *          or false if not.
 */
bool MPEG_CheckHeader(unsigned long header)
{
  if (((header>>21) & 2047) != 2047)
    return false;
  if (((header>>19) &    3) ==    1)
    return false;
  if (((header>>17) &    3) ==    0)
    return false;
  if (((header>>12) &   15) ==    0 ||
      ((header>>12) &   15) ==   15)
    return false;
  if (((header>>10) &    3) ==    3)
    return false;
  if (((header>> 0) &    3) ==    2)
    return false;
  return true;
}


/**
 * Search for valid headers inside a buffer.
 * (Presumably by using REP_SCASB, and a call to CheckHeader().
 *  After a possible header (0xff) has been found, the frame
 *  length should be calculated, and the numHead following frames
 *  should be checked as well. If we've found e.g. 3 valid headers
 *  in a row, we can be reasonably sure that we're sync'd.
 * \TODO  implement this function!
 */
bool MPEG_FindHeader( char *buffer, unsigned buflen, 
                      int numHeads )
{
  return false;
}


/**
 * Decode a MPEG Header into a more friendy format.
 *
 * \param   header  header to parse.
 * \param   info    pointer to MpegInfo structure to fill out.
 * \return          true if successful, or
 *                  false if the header was invalid
 */
bool MPEG_ParseHeader(unsigned long header, FrameInfo *info)
{
  const int  ids[4]    = { 25, 0, 2, 1 };
  const int  layers[4] = {  0, 3, 2, 1 };
  const long mp1tab[4][16] = { {0},  // bitrates for MPEG 1
   {     0,  32000,  64000,  96000, 128000, 160000, 192000, 224000, // layer 1
    256000, 288000, 320000, 352000, 384000, 416000, 448000,     -1 },
   {     0,  32000,  48000,  56000,  64000,  80000,  96000, 112000, // layer 2
    128000, 160000, 192000, 224000, 256000, 320000, 384000,     -1 },
   {     0,  32000,  40000,  48000,  56000,  64000,  80000,  96000, // layer 3
    112000, 128000, 160000, 192000, 224000, 256000, 320000,     -1 }
  };
  const long mp2tab[4][16] = { {0},  // bitrates for MPEG2/2.5
   {     0,  32000,  48000,  56000,  64000,  80000,  96000, 112000, // layer 1
    128000, 144000, 160000, 176000, 192000, 224000, 256000,     -1 },
   {     0,   8000,  16000,  24000,  32000,  40000,  48000,  56000, // layer 2
     64000,  80000,  96000, 112000, 128000, 144000, 160000,     -1 },
   {     0,   8000,  16000,  24000,  32000,  40000,  48000,  56000, // layer 3
     64000,  80000,  96000, 112000, 128000, 144000, 160000,     -1 },
  };
  const long  freqs[4][4] = {
    { 11025,  12000,  8000, 0},   // version 2.5
    {     0,      0,     0, 0},   // reserved
    { 22050,  24000, 16000, 0},   // version 2
    { 44100,  48000, 32000, 0}    // version 1
  };

  memset(info, 0, sizeof(FrameInfo));
  if (!MPEG_CheckHeader(header))
    return false;

  info->id        =   ids[(header>>19) & 3];
  info->layer     =   layers[(header>>17) & 3];
  info->mode      =   (header>>6) & 3;
  info->extension =   (header>>4) & 3;
  info->emphasis  =   (header>>0) & 3;
  info->crc       = !((header>>16) & 1);
  info->pad       =   (header>>9) & 1;
  info->priv      =   (header>>8) & 1;
  info->copyright =   (header>>3) & 1;
  info->original  =   (header>>2) & 1;
  info->frequency =   freqs[(header>>19) & 3][(header>>10) & 3];

  if (info->id == 1)
    info->bitrate = mp1tab[info->layer][(header>>12) & 15];
  else
    info->bitrate = mp2tab[info->layer][(header>>12) & 15];

  if (info->layer == 1) {
    info->length  = 48  * info->bitrate/info->frequency;
    info->length += info->pad ? 4:0;
  } else {
    info->length  = 144 * info->bitrate/info->frequency;
    info->length += info->pad ? 1:0;
  }
  info->length += info->crc ? 2:0;
  return true;
}


char *MPEG_HeaderToString(unsigned long header)
{
  static char buf[40];
  const char *mode[] = {"Stereo", "J-Stereo", "DualChn", "Mono"};

  FrameInfo  info;
  if (!MPEG_ParseHeader(header, &info))
    return "invalid header.";

  assert(
    sprintf(buf, "MP%uL%u, %3ldkb, %2ldkHz, %s %s",
    info.id, info.layer,
    info.bitrate/1000, info.frequency/1000,
    mode[info.mode], info.crc ? "CRC" : "")
    < sizeof(buf)
  );

  return buf;
}
