/*****************************************************************************/
/* Copyright (c) 2009 NXP B.V.                                               */
/*                                                                           */
/* 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, using               */
/* version 2.1 of the License.                                               */
/*                                                                           */
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA   */
/*                                                                           */
/*****************************************************************************/
/*

Rev Date        Author      Comments
--------------------------------------------------------------------------------
1   20090205    laird       First Version
2   20090313    laird       Stopping wasteful memcpys
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/
/*
 * gsttiquicktime_h264.c
 *
 * This file defines functions used for creating temporary headers needed for
 * decoding H.264 streams demuxed via qtdemuxer.
 *
 * Original Author:
 *     Brijesh Singh, Texas Instruments, Inc.
 *
 * Copyright (C) $year Texas Instruments Incorporated - http://www.ti.com/
 *
 * This program 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 version 2.1 of the License.
 * whether express or implied; without even the implied warranty of *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

/**************************************************************
* INCLUDE FILES                                               *
***************************************************************/
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gst/gst.h>
#include "gststb22xframedvideo_h264.h"

/**************************************************************
* LOCAL MACROS                                                *
***************************************************************/

/**************************************************************
* LOCAL TYPEDEFS                                              *
***************************************************************/

/** NAL start code length (in byte) */
#define ES_START_CODE_LENGTH 3

/**************************************************************
* STATIC FUNCTION PROTOTYPES                                  *
***************************************************************/
static int gst_stb22x_framed_video_sps_pps_calBufSize(GstBuffer *codec_data);

/**************************************************************
* STATIC DATA                                                 *
***************************************************************/
/* NAL start code */
static unsigned int ES_START_CODE=0x10000;

/**
 * Declare a debug category for this component
 * Enabled by doing export GST_DEBUG=stb22xframedvideo:x where x=1-5.
 */
GST_DEBUG_CATEGORY_EXTERN (gst_stb22x_framed_video_debug);
#define GST_CAT_DEFAULT gst_stb22x_framed_video_debug

/**************************************************************
* FUNCTION IMPLEMENTATION                                     *
***************************************************************/
/**
 * gst_stb22x_framed_video_get_sps_pps_data - This function returns SPS and PPS NAL unit
 * syntax by parsing the codec_data field. This is used to construct
 * byte-stream NAL unit syntax.
 * Each byte-stream NAL unit syntax structure contains one start
 * code prefix of size four bytes and value 0x0000001, followed by one NAL
 * unit syntax.
 */
GstBuffer* gst_stb22x_framed_video_get_sps_pps_data (GstBuffer *codec_data)
{
    int i, sps_pps_pos = 0, len;
    guint8 numSps, numPps;
    guint8 byte_pos;
    gint sps_pps_size = 0;
    GstBuffer *g_sps_pps_data = NULL;
    guint8 *sps_pps_data = NULL;

    GST_LOG(" ->> Entering");

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

    /* Get the total sps and pps length after adding NAL code prefix. */
    sps_pps_size = gst_stb22x_framed_video_sps_pps_calBufSize(codec_data);

    /* Allocate sps_pps_data buffer */
    g_sps_pps_data =  gst_buffer_new_and_alloc(sps_pps_size);
    if (g_sps_pps_data == NULL)
    {
        GST_ERROR("Failed to allocate sps_pps_data buffer\n");
        return (NULL);
    }
    sps_pps_data = GST_BUFFER_DATA(g_sps_pps_data);

    /* Set byte_pos to 5. Indicates sps byte location in avcC header. */
    byte_pos = 5;

    /* Copy sps unit dump. */
    numSps = AVCC_ATOM_GET_NUM_SPS(codec_data, byte_pos);
    byte_pos++;
    for (i=0; i < numSps; i++)
    {
        memcpy(sps_pps_data+sps_pps_pos, (unsigned char*) &ES_START_CODE,
               ES_START_CODE_LENGTH);
        sps_pps_pos += ES_START_CODE_LENGTH;
        len = AVCC_ATOM_GET_SPS_NAL_LENGTH(codec_data,byte_pos);
        GST_LOG("  - sps[%d]=%d\n", i, len);
        byte_pos +=2;

        memcpy(sps_pps_data+sps_pps_pos,
               GST_BUFFER_DATA(codec_data)+byte_pos, len);
        sps_pps_pos += len;
        byte_pos += len;
    }

    /* Copy pps unit dump. */
    numPps = AVCC_ATOM_GET_NUM_PPS(codec_data,byte_pos);
    byte_pos++;
    for (i=0; i < numPps; i++)
    {
        memcpy(sps_pps_data+sps_pps_pos, (unsigned char*) &ES_START_CODE,
               ES_START_CODE_LENGTH);
        sps_pps_pos += ES_START_CODE_LENGTH;
        len = AVCC_ATOM_GET_PPS_NAL_LENGTH(codec_data,byte_pos);
        GST_LOG("  - pps[%d]=%d\n", i, len);
        byte_pos +=2;
        memcpy(sps_pps_data+sps_pps_pos,
               GST_BUFFER_DATA(codec_data)+byte_pos, len);
        sps_pps_pos += len;
        byte_pos += len;
    }

    GST_LOG(" <<- Leaving");
    return (g_sps_pps_data);
}

/**
 * Return the pre-defined ES stream prefix code.
 */
GstBuffer* gst_stb22x_framed_video_get_es_prefix_code (void)
{
    GstBuffer *es_code_prefix;

    GST_LOG(" ->> Entering");

    es_code_prefix = gst_buffer_new_and_alloc(ES_START_CODE_LENGTH);
    if (es_code_prefix == NULL)
    {
        GST_ERROR("Failed to allocate memory\n");
        return (NULL);
    }

    memcpy(GST_BUFFER_DATA(es_code_prefix),
           (unsigned char*) &ES_START_CODE,
           ES_START_CODE_LENGTH);

    GST_LOG(" <<- Leaving");
    return (es_code_prefix);
}

/**
 * This takes in a NAL unit of data, it then makes sure that the ES header is
 * inserted before each ES packet held within the NAL unit.
 * This is required for the MSVD to playback H264 data.
 *
 * H264 in quicktime is what we call in gstreamer 'packtized' h264.
 * A codec_data is exchanged in the caps that contains, among other things,
 * the nal_length_size field and SPS, PPS.

 * The data consists of a nal_length_size header containing the length of
 * the NAL unit that immediatly follows the size header.
 */
gboolean gst_stb22x_framed_video_parse_and_create_buffer (GstBuffer *buf,
                                                          GstBuffer *es_code_prefix,
                                                          guint8 nal_length,
                                                          struct iovec** pIov)
{
    int i, nal_size = 0;
    int offset = 0;
    GstBuffer *subBuf;
    int avail = GST_BUFFER_SIZE(buf);
    guint8 *inBuf = GST_BUFFER_DATA(buf);

    GST_LOG(" ->> Entering");

    do
    {
        nal_size = 0;
        for (i=0; i < nal_length; i++)
        {
            nal_size = (nal_size << 8) | inBuf[i];
        }
        inBuf += nal_length;
        offset += nal_length;

        /* Write ES header */
        (*pIov)->iov_base = GST_BUFFER_DATA(es_code_prefix);
        (*pIov)->iov_len = GST_BUFFER_SIZE(es_code_prefix);
        (*pIov)++;

        /* Get a child buffer starting with offset and end at nal_size */
        subBuf = gst_buffer_create_sub(buf, offset, nal_size);
        if (subBuf == NULL)
        {
            GST_ERROR("Failed to get nal_size=%d buffer from adapter\n", nal_size);
            return (FALSE);
        }
        (*pIov)->iov_base = GST_BUFFER_DATA(subBuf);
        (*pIov)->iov_len = GST_BUFFER_SIZE(subBuf);
        (*pIov)++;
        gst_buffer_unref(subBuf);

        offset += nal_size;
        inBuf += nal_size;
        avail -= (nal_size + nal_length);
    } while (avail > 0);

    GST_LOG(" <<- Leaving");
    return (TRUE);
}

/**
 * Return the number of ES buffers inside a NALU
 */
gint gst_stb22x_framed_video_get_num_es_buffers (GstBuffer *buf,
                                                 guint8 nal_length)
{
    gint i = 0;
    gint num_buffers = 0;
    gint nal_size = 0;
    gint avail = GST_BUFFER_SIZE(buf);
    guint8 *inBuf = GST_BUFFER_DATA(buf);

    GST_LOG(" ->> Entering");

    do
    {
        nal_size = 0;
        for (i=0; i < nal_length; i++)
        {
            nal_size = (nal_size << 8) | inBuf[i];
        }
        inBuf += nal_length;

        inBuf += nal_size;
        avail -= (nal_size + nal_length);

        /* Increment the number of buffers.*/
        num_buffers++;
    } while (avail > 0);

    GST_LOG(" <<- Leaving");
    return num_buffers;
}

/**
 * Return the NAL length in avcC header.
 */
guint8 gst_stb22x_framed_video_get_nal_length (GstBuffer *codec_data)
{
    guint8 nal_length;

    GST_LOG(" ->> Entering");

    /* Get nal length from avcC header */
    nal_length = AVCC_ATOM_GET_NAL_LENGTH(codec_data, 4);

    GST_LOG("NAL length=%d ",nal_length);

    GST_LOG(" <<- Leaving");
    return (nal_length);
}

/**
 * gst_stb22x_framed_video_valid_avcc_header - This function checks if codec_data has a
 * valid avcC header in h264 stream.
 * To do this, it reads codec_data field passed via demuxer and if the
 * codec_data buffer size is greater than 7, then we have a valid quicktime
 * avcC atom header.
 *
 *      -: avcC atom header :-
 *  -----------------------------------
 *  1 byte  - version
 *  1 byte  - h.264 stream profile
 *  1 byte  - h.264 compatible profiles
 *  1 byte  - h.264 stream level
 *  6 bits  - reserved set to 63
 *  2 bits  - NAL length
 *            ( 0 - 1 byte; 1 - 2 bytes; 3 - 4 bytes)
 *  1 byte  - number of SPS
 *  for (i=0; i < number of SPS; i++) {
 *      2 bytes - SPS length
 *      SPS length bytes - SPS NAL unit
 *  }
 *  1 byte  - number of PPS
 *  for (i=0; i < number of PPS; i++) {
 *      2 bytes - PPS length
 *      PPS length bytes - PPS NAL unit
 *  }
 * ------------------------------------------
 */
gboolean gst_stb22x_framed_video_valid_quicktime_header (GstBuffer *codec_data)
{
    GST_LOG(" ->> Entering");

    if (codec_data == NULL)
    {
        GST_LOG("demuxer does not have codec_data field\n");
        return (FALSE);
    }

    /* Check the buffer size. */
    if (GST_BUFFER_SIZE(codec_data) < 7)
    {
        GST_LOG("codec_data field does not have a valid quicktime header\n");
        return (FALSE);
    }

    /* print some debugging */
    GST_LOG("avcC version=%d, profile=%#x , level=%#x ",
            AVCC_ATOM_GET_VERSION(codec_data,0),
            AVCC_ATOM_GET_STREAM_PROFILE(codec_data,1),
            AVCC_ATOM_GET_STREAM_LEVEL(codec_data, 3));

    GST_LOG(" <<- Leaving");
    return (TRUE);
}

/**
 * gst_stb22x_framed_video_sps_pps_calBuffer_Size  - Function to calculate total buffer size
 * needed for copying SPS(Sequence parameter set) and PPS
 * (Picture parameter set) data.
 */
static int gst_stb22x_framed_video_sps_pps_calBufSize (GstBuffer *codec_data)
{
    int i, byte_pos, sps_nal_length=0, pps_nal_length=0;
    int numSps=0, numPps=0, sps_pps_size=0;

    GST_LOG(" ->> Entering");

    /* Set byte_pos to 5. Indicates sps byte location in avcC header. */
    byte_pos = 5;

    /* Get number of SPS in avcC header */
    numSps = AVCC_ATOM_GET_NUM_SPS(codec_data,byte_pos);
    GST_LOG("sps=%d ", numSps);

    /* number of SPS is 1-byte long, increment byte_pos counter by 1 byte */
    byte_pos++;

    /* If number of SPS is non-zero, then copy NAL unit dump in buffer */
    if (numSps)
    {

        for (i=0; i < numSps; i++)
        {
            sps_nal_length = AVCC_ATOM_GET_SPS_NAL_LENGTH(codec_data,
                                                          byte_pos);
            /* NAL length is 2-byte long, increment the byte_pos by NAL length
             * plus 2.
             */
            byte_pos = byte_pos + sps_nal_length + 2;

            /* add NAL start code prefix length in total sps_pps_size, this is
             * because we need to add code prefix on every NAL unit. */
            sps_pps_size += sps_nal_length + ES_START_CODE_LENGTH;
        }
    }

    /* Get the number of PPS in avcC header */
    numPps = AVCC_ATOM_GET_NUM_PPS(codec_data, byte_pos);
    GST_LOG("pps=%d \n", numPps);

    /* number of PPS is 1-byte long, increment byte_pos counter  by 1 byte */
    byte_pos++;


    /* If number of PPS is non-zero, then copy NAL unit dump in buffer */
    if (numPps)
    {

        for (i=0; i < numPps; i++)
        {
            pps_nal_length = AVCC_ATOM_GET_PPS_NAL_LENGTH(codec_data,
                                                          byte_pos);
            /* NAL length is 2-byte long, increment the byte_pos by NAL length
             * plus 2.
             */
            byte_pos = byte_pos + pps_nal_length + 2;

            /* add NAL start code prefix length in total sps_pps_size, this is
             * because we need to add code prefix on every NAL unit. */
            sps_pps_size += pps_nal_length + ES_START_CODE_LENGTH;
        }
    }

    GST_LOG(" <<- Leaving");
    return (sps_pps_size);
}

