/******************************************************************************
Copyright (c) 2007 Analog Devices, Inc.  All Rights Reserved.  This software is
proprietary and confidential to Analog Devices, Inc. and its licensors.
*******************************************************************************

$Revision: 1.1 $
$Date: 2008/03/27 03:02:10 $
$LastChangedBy: sto $

Project:    MP4 Audio File Parser
Title:      Main module defining all the API functions
Author:     CFJ

Description:
            This file provides the function definitions for the API of an
            example module which parses the MP4 File Format.
            See the Developer's guide for more information.

References:
            ISO/IEC 14496-12:2005 ISO Base media file format
            ISO/IEC 14496-14:2003 MP4 file format
            ADA-195 MP4 File Parser Developer's Guide

******************************************************************************/
#include <stdio.h>
#include <string.h>


#include "adi_mp4ad_parser_internal.h"

static reset_track_info(adi_mp4ad_instance_t *parser_instance);

/******************************************************************************
**          #defines
******************************************************************************/
#define ADI_MP4AD_ATOM_SIZETYPE_BITS		(64)		// size:32 type:32
#define ADI_MP4AD_ATOM_SIZE_EXTEND_BITS     (64)        // extended-size:64
#define ADI_MP4AD_ATOM_UUIDTYPE_BITS        (128)       // usertype[16]:8
#define ADI_MP4AD_ATOM_BIGGEST_HEADER_BITS	    \
(              ADI_MP4AD_ATOM_SIZETYPE_BITS     \
            + ADI_MP4AD_ATOM_SIZE_EXTEND_BITS   \
            + ADI_MP4AD_ATOM_UUIDTYPE_BITS      \
)


/******************************************************************************
**          Global const variables
******************************************************************************/
const adi_mp4ad_module_info_t adi_mp4ad_module_info = {
    0x02000000,
    "ADI MPEG-4 Audio File Parser for Blackfin, version 2.0.0"
};

// Return no more than 32 bits from the bitstream using circular buffering
FAST_CODE_SECTION
adi_mp4ad_uint64 adi_mp4ad_parser_get_bits(adi_mp4ad_instance_t *parser, int numBits)
{
	unsigned long data=0;
	int i = 0;


	// Read data from circular buffer
	adi_mp4ad_bitstream_data_t* p = &(parser->state.input_buffer.base[parser->state.input_buffer.read_index]);


	data = 0;
	for (i = 0; i < 4; i++)
	{
	    data <<= 8;
		data |= *p;
	    p = __builtin_circptr(p, 1, parser->state.input_buffer.base, parser->state.input_buffer.length);
	}
	// Extract data from the bytes
	data <<= parser->state.input_buffer.read_bit;
	data >>= 32 - numBits;

	// Move to next (byte,bit)
	parser->state.input_buffer.read_bit += numBits;
	int read_increment = ((parser->state.input_buffer.read_bit & 0xFFFFFFF8) >> 3);
	parser->state.input_buffer.read_index = __builtin_circindex	(
												parser->state.input_buffer.read_index,
												read_increment,
												parser->state.input_buffer.length);
	parser->state.input_buffer.read_bit &= 7;
	parser->state.input_buffer.is_full = 0; // always flag the buffer as being not full once we read any data
	return data;
}


// Aligns the bitstream to the start of the next byte
FAST_CODE_SECTION
void adi_mp4ad_parser_byte_align(adi_mp4ad_instance_t *parser)
{
	// Skip if already byte-aligned
	if (parser->state.input_buffer.read_bit != 0)
	{
		adi_mp4ad_parser_get_bits (parser, (8 - parser->state.input_buffer.read_bit));
	}
	return;
}


// Returns the relative position of the current bit within the buffer
// This value is not comparable across calls to adi_mp4ad_parser()
FAST_CODE_SECTION
long int adi_mp4ad_parser_get_bit_position (adi_mp4ad_instance_t *parser)
{
	long int bytes_read =  parser->state.input_buffer.read_index - parser->state.input_buffer.first_read_index;
	if (bytes_read < 0)
	{
		bytes_read += parser->state.input_buffer.length;
	}
	else if ( (bytes_read == 0) && !(parser->state.input_buffer.is_full) )
	{
	    // special case when the input buffer was full and every single byte has been read
		bytes_read = parser->state.input_buffer.length;
	}
	    
	long int bit_position = (bytes_read << 3) + (parser->state.input_buffer.read_bit + parser->state.input_buffer.first_read_bit);
	return bit_position;
};

// Rewinds the stream a given number of bits.
FAST_CODE_SECTION
adi_mp4ad_return_code_t adi_mp4ad_parser_rewind(adi_mp4ad_instance_t *parser, int numBits)
{
	// Confirm if rewind is valid
	if (adi_mp4ad_parser_get_bit_position(parser) < numBits)
	{
		return ADI_MP4AD_OPERATION_FAILED;
	}
	// Rewind bit index
	parser->state.input_buffer.read_bit -= (numBits & 7);
	if (parser->state.input_buffer.read_bit < 0)
	{
		parser->state.input_buffer.read_index--;
		parser->state.input_buffer.read_bit += 8;
	}
	// Rewind byte index
	parser->state.input_buffer.read_index -= (numBits >> 3);
	if (parser->state.input_buffer.read_index < 0)
	{
		parser->state.input_buffer.read_index += parser->state.input_buffer.length;
	}
	
	// check if we rewound to the start of the input buffer in order to reset the full flag!
	if (   (parser->state.input_buffer.read_bit == 0) 
	    && (parser->state.input_buffer.read_index == parser->state.input_buffer.first_read_index) )
	{
	    parser->state.input_buffer.is_full = 1;
	}
	    
	return ADI_MP4AD_SUCCESS;
}

// Returns number of bits remaining in the buffer
FAST_CODE_SECTION
int adi_mp4ad_parser_bits_remaining (adi_mp4ad_instance_t *parser)
{
	long int bytesRemaining = parser->state.input_buffer.write_index - parser->state.input_buffer.read_index;
	long int bitsRemaining = 0;
	if (bytesRemaining <= 0)
	{
		bytesRemaining += parser->state.input_buffer.length;
	}
	bitsRemaining = (bytesRemaining << 3) - parser->state.input_buffer.read_bit;
	// Full or empty?
	if (bitsRemaining == (parser->state.input_buffer.length<<3))
	{
		bitsRemaining *= parser->state.input_buffer.is_full;
	}
	return bitsRemaining;
}



// Returns the boxtype enum from box's type number
FAST_CODE_SECTION
adi_mp4ad_boxtype_t find_box_type(adi_mp4ad_instance_t *parser)
{
	adi_mp4ad_boxtype_t this_box;
	for (this_box = ((adi_mp4ad_boxtype_t)0); this_box < ADI_MP4AD_BOXTYPE_NUM_HANDLED; this_box++)
	{
		if (boxtype_parameters[this_box].id == parser->object.type)
		{
			return this_box;
		}
	}
	return ADI_MP4AD_BOXTYPE_ERROR;
}

// Parses a fullbox container
FAST_CODE_SECTION
adi_mp4ad_return_code_t parser_function_container_fullbox(adi_mp4ad_instance_t *parser)
{
	adi_mp4ad_return_code_t result = ADI_MP4AD_SUCCESS;

	if ((result = parse_full_box(parser)) != ADI_MP4AD_SUCCESS)
	{
		return result;
	}

	// Atom complete
	parser->state.current = ADI_MP4AD_BOXTYPE_OBJECT;
	return result;
}


// Parses a basic container
FAST_CODE_SECTION
adi_mp4ad_return_code_t parser_function_container(adi_mp4ad_instance_t *parser)
{
	adi_mp4ad_return_code_t result = ADI_MP4AD_SUCCESS;
	// Atom complete
	parser->state.current = ADI_MP4AD_BOXTYPE_OBJECT;
	return result;
}

FAST_CODE_SECTION
adi_mp4ad_return_code_t parser_function_not_implemented(adi_mp4ad_instance_t *parser)
{
	return ADI_MP4AD_INVALID_BITSTREAM;
}

// Returns 64-bits from the bitstream
FAST_CODE_SECTION
adi_mp4ad_uint64 parser_get_64_bits(adi_mp4ad_instance_t *parser)
{
	adi_mp4ad_uint64 result = adi_mp4ad_parser_get_bits(parser, 16);
	result = (result << 16) + adi_mp4ad_parser_get_bits(parser, 16);
	result = (result << 16) + adi_mp4ad_parser_get_bits(parser, 16);
	result = (result << 16) + adi_mp4ad_parser_get_bits(parser, 16);
	return result;
}

// This function skips/flushes n bits in the input buffer
FAST_CODE_SECTION
adi_mp4ad_return_code_t parser_skip_bits(adi_mp4ad_instance_t *parser, int numBits)
{
	int bits_remaining = numBits;
	while (bits_remaining >= 32)
	{
		adi_mp4ad_parser_get_bits(parser, 32);
		bits_remaining -=32;
	}
	if (bits_remaining)
	{
		adi_mp4ad_parser_get_bits(parser, bits_remaining);
	}
	return ADI_MP4AD_SUCCESS;
}


// Skips the entire box payload
FAST_CODE_SECTION
adi_mp4ad_return_code_t parser_function_skip(adi_mp4ad_instance_t *parser)
{
    int bits_remaining = adi_mp4ad_parser_bits_remaining(parser);
    int box_bits = parser->object.payloadsize << 3;
    if (bits_remaining >= box_bits)
    {
    	parser_skip_bits (parser, box_bits);
   		// Atom complete
	    parser->state.current = ADI_MP4AD_BOXTYPE_OBJECT;
	    return ADI_MP4AD_SUCCESS;
    }
    else
    {
        // The skipped box is not fully contained in the buffer   CVN 2007Aug21
        parser_skip_bits(parser, bits_remaining);      
    	parser->state.current = ADI_MP4AD_BOXTYPE_OBJECT;
    	
    	// Modify object.size to indicate the number of bytes still to skip in this box
        parser->object.size = (box_bits - bits_remaining) >> 3; 
    	return ADI_MP4AD_SKIP_DATA_REQUEST;
    }    
}

/*
	4.2 Object Structure
	aligned(8) class Box (unsigned int(32) boxtype,
	optional unsigned int(8)[16] extended_type) {
		unsigned int(32) size;
		unsigned int(32) type = boxtype;
		if (size==1) {
			unsigned int(64) largesize;
		} else if (size==0) {
			// box extends to end of file
		}
		if (boxtype==‘uuid’) {
		unsigned int(8)[16] usertype = extended_type;
		}
	}
	The semantics of these two fields are:
	size is an integer that specifies the number of bytes in this box, including all its fields and contained
	boxes; if size is 1 then the actual size is in the field largesize; if size is 0, then this box is the last
	one in the file, and its contents extend to the end of the file (normally only used for a Media Data Box)
	type identifies the box type; standard boxes use a compact type, which is normally four printable
	characters, to permit ease of identification, and is shown so in the boxes below. User extensions use
	an extended type; in this case, the type field is set to ‘uuid’.
	Boxes with an unrecognized type shall be ignored and skipped.
	Many objects also contain a version number and flags field:
*/

// Parses the box header
FAST_CODE_SECTION
adi_mp4ad_return_code_t parser_function_object(adi_mp4ad_instance_t *parser)
{
	int i = 0;
	adi_mp4ad_return_code_t result = ADI_MP4AD_SUCCESS;
	int atom_size = 0;
	long int start_bit = adi_mp4ad_parser_get_bit_position (parser);
	int is_enough_bits = 0;

	// initialise the type to something that is not /unlikely registered
	// and the length to the size of the smallest possible header required to identify the box
	parser->object.payloadsize = 0;
	parser->object.size = parser->object.headersize = ADI_MP4AD_ATOM_BIGGEST_HEADER_BITS/8; // max num bytes for box header
	parser->object.type = 0x3F3F3F3F; // ascii for "????"

	// Enough bits for standard object header?
	if (adi_mp4ad_parser_bits_remaining (parser) < ADI_MP4AD_ATOM_SIZETYPE_BITS)    // CVN 2007Jun12
	{
		return ADI_MP4AD_NEED_MORE_INPUT;
	}

	parser->object.size =	adi_mp4ad_parser_get_bits(parser, 32);
	parser->object.type =	adi_mp4ad_parser_get_bits(parser, 32);

	// Largesize?
	if (parser->object.size == 1)
	{
	    if (adi_mp4ad_parser_bits_remaining (parser) < ADI_MP4AD_ATOM_SIZE_EXTEND_BITS)    // CVN 2007Jun12
	    {
           	parser->object.headersize =	(ADI_MP4AD_ATOM_SIZETYPE_BITS + ADI_MP4AD_ATOM_SIZE_EXTEND_BITS)/8;
        	if (adi_mp4ad_parser_rewind(parser, (adi_mp4ad_parser_get_bit_position (parser) - start_bit)) == ADI_MP4AD_SUCCESS)
   	        	return ADI_MP4AD_NEED_MORE_INPUT;
   	    	else
   	        	return ADI_MP4AD_TERMINAL_FAILURE;
   	    }

		parser->object.largesize = parser_get_64_bits(parser);
	}
	else if (parser->object.size < (ADI_MP4AD_ATOM_SIZETYPE_BITS/8))
	{
	    // Otherwise Check the object length is sensible
	    return ADI_MP4AD_TERMINAL_FAILURE;
	}
	else
	{
		// Set to smaller size so only one size needs to be read by the parser functions
		parser->object.largesize = 0;
       	parser->object.headersize =	ADI_MP4AD_ATOM_SIZETYPE_BITS/8;
	}

	// Set the payload size	= atom size - header size
	atom_size = (parser->object.size == 1) ? (parser->object.largesize) : (parser->object.size);
	parser->object.headersize = ((adi_mp4ad_parser_get_bit_position (parser) - start_bit) >> 3);
	parser->object.payloadsize = 	atom_size - parser->object.headersize;

	// Translate the boxtype
	if ((parser->object.boxtype = find_box_type(parser)) == ADI_MP4AD_BOXTYPE_ERROR)
	{
		// Box type not supported.
		// Rewind to start of object
		if (adi_mp4ad_parser_rewind(parser, (adi_mp4ad_parser_get_bit_position (parser) - start_bit)) == ADI_MP4AD_SUCCESS)
		{
			return ADI_MP4AD_UNKNOWN_OBJECT;
		}
		else
		{
			return ADI_MP4AD_TERMINAL_FAILURE;
		}
	}

	// UUID?
	if (parser->object.boxtype == ADI_MP4AD_BOXTYPE_UUID)
	{
	    // adjust relative sizes of header and payload
		parser->object.headersize += (ADI_MP4AD_ATOM_UUIDTYPE_BITS)/8;
		parser->object.payloadsize -= (ADI_MP4AD_ATOM_UUIDTYPE_BITS)/8;

	    if (adi_mp4ad_parser_bits_remaining (parser) < ADI_MP4AD_ATOM_UUIDTYPE_BITS)    // CVN 2007Jun12
	    {
	        if (adi_mp4ad_parser_rewind(parser, (adi_mp4ad_parser_get_bit_position (parser) - start_bit)) == ADI_MP4AD_SUCCESS)
    	        return ADI_MP4AD_NEED_MORE_INPUT;
    	    else
    	        return ADI_MP4AD_TERMINAL_FAILURE;
    	}

		for (i=0; i<16; i++)
		{
			parser->object.usertype[i] = adi_mp4ad_parser_get_bits(parser, 8);
		}
	}
	else
	{
		for (i=0; i<16; i++)
		{
			parser->object.usertype[i] = 0;
		}
	}

	// Check if there are enough bytes to parse this atom
	// Containers are exempt from this check
	parser->object.is_container = boxtype_parameters[parser->object.boxtype].is_container;
	if (parser->object.boxtype == ADI_MP4AD_BOXTYPE_STSZ)
	   is_enough_bits = 1;	   
	else if(
		parser->object.boxtype == ADI_MP4AD_BOXTYPE_STTS  ||  parser->object.boxtype == ADI_MP4AD_BOXTYPE_STSS	||
		parser->object.boxtype == ADI_MP4AD_BOXTYPE_STSC  ||  parser->object.boxtype == ADI_MP4AD_BOXTYPE_STCO)
		is_enough_bits =	(adi_mp4ad_parser_bits_remaining (parser) >= MIN_BITS_REQ_FOR_STTS_STSS_STSC_STCO_PRELOOP );
	else
		is_enough_bits =	( parser->object.is_container )
						|| 	( adi_mp4ad_parser_bits_remaining (parser) >= (parser->object.payloadsize<<3) );
	if (!is_enough_bits)
	{
		if (adi_mp4ad_parser_rewind(parser, (adi_mp4ad_parser_get_bit_position (parser) - start_bit)) == ADI_MP4AD_SUCCESS)
		{
			return ADI_MP4AD_NEED_MORE_INPUT;
		}
		else
		{
			return ADI_MP4AD_TERMINAL_FAILURE;
		}
	}

	// Set the next state
	parser->state.current = parser->object.boxtype;

	return result;
}

/*
	aligned(8) class FullBox(unsigned int(32) boxtype, unsigned int(8) v, bit(24) f)
	extends Box(boxtype) {
		unsigned int(8) version = v;
		bit(24) flags = f;
	}

*/
// Parses the additional information in the FullBox object
FAST_CODE_SECTION
adi_mp4ad_return_code_t parse_full_box(adi_mp4ad_instance_t *parser)
{
	parser->fullbox.version =	adi_mp4ad_parser_get_bits(parser, 8);
	parser->fullbox.flags =	adi_mp4ad_parser_get_bits(parser, 24);
	return ADI_MP4AD_SUCCESS;
}


void print_atom_header (adi_mp4ad_instance_t *parser)
{
  #ifndef NDEBUG
    int i;
	char type_str[5];

	if (parser->state.current != ADI_MP4AD_BOXTYPE_OBJECT)
	{
	    // add number of spaces according to depth of current container hierarchy
    	for (i=-1; i<parser->state.container_level; i++)
    	    printf("  .");

    	// print the name of the current atom and it's size
    	type_str[0] = (parser->object.type >> 24) & 0xff;
    	type_str[1] = (parser->object.type >> 16) & 0xff;
    	type_str[2] = (parser->object.type >>  8) & 0xff;
    	type_str[3] = (parser->object.type >>  0) & 0xff;
    	type_str[4] = 0;
    	printf ("Atom %s (0x%8.8x), size %d\n", type_str, parser->object.type, parser->object.size);
    }
	return;	
  #endif
}

// Calculate the number of unused bytes in the indexing info buffer
// If the allocated buffer is too small, this function will return
// a negative number whose absolute value specifies the number of
// bytes needed to be added
static int cal_index_mem_unused_bytes(adi_mp4ad_instance_t *parser)     // CVN 2007May12
{
    int cnt = (adi_mp4ad_uint8*)(parser->state.index_mem_free_point) - (adi_mp4ad_uint8*)(parser->state.index_mem_base);
    if (parser->state.index_mem_unstored_bytes > 0)
        cnt = parser->state.index_mem_size - cnt - parser->state.index_mem_unstored_bytes;
    return cnt;
}


// Resets a hierarchy level
adi_mp4ad_return_code_t reset_hierarchy_level (	adi_mp4ad_instance_t *parser,
												int level)
{
    // check if this container is an audio "trak" that has just been parsed - STO2007SEP04
    if (   (parser->state.container_hierarchy[level].container == ADI_MP4AD_BOXTYPE_TRAK)
        && (!parser->state.audio_track_is_parsed) )
    {
        if (parser->atoms->hdlr.handler_type == 'soun')
        {
            parser->state.audio_track_is_parsed = 1;
        }
        else
        {
            // reset track related info in case we have not yet found the audio track
            reset_track_info(parser);
        }
        
    }

	parser->state.container_hierarchy[level].container = ADI_MP4AD_BOXTYPE_ERROR;
	parser->state.container_hierarchy[level].end_stream_index = -1;
	return ADI_MP4AD_SUCCESS;
}

// Update the hierarchy data structure with the current container (if any) at
//	the given stream index
FAST_CODE_SECTION
adi_mp4ad_return_code_t update_hierarchy (	adi_mp4ad_instance_t *parser,
											int stream_index)
{
	adi_mp4ad_return_code_t result = ADI_MP4AD_SUCCESS;

	// Is the container at this level finished?
	while (		(parser->state.container_hierarchy[parser->state.container_level].end_stream_index != -1)
			&&	(stream_index >= parser->state.container_hierarchy[parser->state.container_level].end_stream_index)	)
	{
		// Container finished.  Reset this level
		reset_hierarchy_level (parser, parser->state.container_level);
		// Move up a level (check to avoid buffer under-run)
		if (parser->state.container_level>0)
    		parser->state.container_level--;
	}

	// Is this the start of a new container?
	if (	boxtype_parameters[parser->state.current].is_container
		&&	(parser->state.container_hierarchy[parser->state.container_level].container != parser->state.current)	)
	{
		// Move down a level
		parser->state.container_level++;
		// check to avoid buffer overflow
		if (parser->state.container_level >= ADI_MP4AD_MAX_HIERARCHY_LEVELS)
		{
		    result = ADI_MP4AD_OPERATION_FAILED;
		}
		else
		{
    		// Container starting.  Record it in the hierarchy
    		parser->state.container_hierarchy[parser->state.container_level].container = parser->state.current;
    		parser->state.container_hierarchy[parser->state.container_level].end_stream_index =
																			stream_index + parser->object.payloadsize;
        }
	}
	return result;
}


////////////////
// API functions
////////////////

// Create the parser instance
FAST_CODE_SECTION
adi_mp4ad_instance_t* adi_mp4ad_create (
	adi_mp4ad_mem_t* mem
)
{
	adi_mp4ad_instance_t* instance = NULL;
	if (ADI_MP4AD_FASTB0_PRIO0_STATE_MEM_NUMBYTES >= sizeof(adi_mp4ad_instance_t))
	{
		instance = mem->state.fastb0.prio0;
	}
  #ifndef NDEBUG
	else
	{
	    printf("!!! Failed to create a parser instance: ");
	    printf ("require ADI_MP4AD_FASTB0_PRIO0_STATE_MEM_NUMBYTES = %d\n", sizeof(adi_mp4ad_instance_t));
    }
  #endif

	return instance;
}

// local function to reset track related info used in init, and when a trak box
// has been parsed, but the audio track has not yet been found - STO2007SEP04
static reset_track_info(adi_mp4ad_instance_t *parser_instance)
{

    if (parser_instance->state.index_mem_base)
    {
	    parser_instance->state.index_mem_free_point = (adi_mp4ad_uint8*)(parser_instance->state.index_mem_base)
	                                                + sizeof(adi_mp4ad_container_indexing_info_t);
	}
	else
    {
	    parser_instance->state.index_mem_free_point = NULL;
	}
    parser_instance->state.index_mem_unstored_bytes = 0;                            // CVN 2007May12

    parser_instance->atoms->mdhd.is_parsed = 0;
    parser_instance->atoms->esds.is_parsed = 0;
    
    if (parser_instance->atoms_indexing)
    {
	    // Initialise the index tables
		parser_instance->atoms_indexing->stts.is_parsed = 0;
		parser_instance->atoms_indexing->stts.entry_count = 0;
		parser_instance->atoms_indexing->stts.sample_count = NULL;               // CVN 2007May12
		parser_instance->atoms_indexing->stts.sample_delta = NULL;               // CVN 2007May12

		parser_instance->atoms_indexing->stss.is_parsed = 0;
		parser_instance->atoms_indexing->stss.entry_count = 0;
		parser_instance->atoms_indexing->stss.sample_number = NULL;              // CVN 2007May12

		parser_instance->atoms_indexing->stsc.is_parsed = 0;
		parser_instance->atoms_indexing->stsc.entry_count = 0;
		parser_instance->atoms_indexing->stsc.first_chunk = NULL;                // CVN 2007May12
		parser_instance->atoms_indexing->stsc.samples_per_chunk = NULL;          // CVN 2007May12
		parser_instance->atoms_indexing->stsc.sample_description_index = NULL;   // CVN 2007May12
		parser_instance->atoms_indexing->stsc.last_chunk = -1;                   // CVN 2007Aug27

		parser_instance->atoms_indexing->stco.is_parsed = 0;
		parser_instance->atoms_indexing->stco.entry_count = 0;
		parser_instance->atoms_indexing->stco.chunk_offset = NULL;               // CVN 2007May12

		parser_instance->atoms_indexing->substate = PRELOOP;						// initial indexing substate

    }

	// reset audio_track_is_parsed to FALSE
	parser_instance->state.audio_track_is_parsed = 0;

}

// Initialises the parser
FAST_CODE_SECTION
adi_mp4ad_return_code_t adi_mp4ad_init (
								adi_mp4ad_instance_t *parser_instance,
								adi_mp4ad_config_t *config_params)
{
	adi_mp4ad_return_code_t result = ADI_MP4AD_SUCCESS;

	// Reset the parser state and parsed variables
	// All variables are zero-initial unless handled elsewhere
	memset (parser_instance, 0, sizeof(adi_mp4ad_instance_t));

	// Save the buffer parameters
	parser_instance->state.input_buffer.base = config_params->circular_buffer_base;
	parser_instance->state.input_buffer.length = config_params->circular_buffer_length;
	parser_instance->state.input_buffer.is_circular = config_params->use_circular_bitstream_buffer;;
	parser_instance->state.input_buffer.read_bit = 0;

	// Initialise the container hierarchy
	parser_instance->state.container_level = 0;
	int i = 0;
	for (i=0; i < ADI_MP4AD_MAX_HIERARCHY_LEVELS; i++)
	{
		reset_hierarchy_level (	parser_instance, i);
	}

	// Initialise the pointer to the text metadata (song tags)
	parser_instance->state.text_fields = config_params->text_fields_ptr;
	// Clear output strings for parsed text metadata
	if (parser_instance->state.text_fields)
	{
    	parser_instance->state.text_fields->song_title.tag_data[0] = 0;
    	parser_instance->state.text_fields->artist.tag_data[0] = 0;
    	parser_instance->state.text_fields->album.tag_data[0] = 0;
    	parser_instance->state.text_fields->year.tag_data[0] = 0;
    	parser_instance->state.text_fields->genre.tag_data[0] = 0;
    	parser_instance->state.text_fields->comments.tag_data[0] = 0;
    	parser_instance->state.text_fields->song_title.tag_data[1] = 0;
    	parser_instance->state.text_fields->artist.tag_data[1] = 0;
    	parser_instance->state.text_fields->album.tag_data[1] = 0;
    	parser_instance->state.text_fields->year.tag_data[1] = 0;
    	parser_instance->state.text_fields->genre.tag_data[1] = 0;
    	parser_instance->state.text_fields->comments.tag_data[1] = 0;
    }

	// Initialise the pointer to the parsed atoms
	parser_instance->atoms = config_params->container_info_ptr;
	if (NULL == parser_instance->atoms)
	{
    	parser_instance->state.current = ADI_MP4AD_BOXTYPE_ERROR;
		return ADI_MP4AD_TERMINAL_FAILURE;
	}
	memset (parser_instance->atoms, 0, sizeof(adi_mp4ad_container_info_t));

	// Initialise the pointer to the parsed indexing atoms
    parser_instance->atoms_indexing = NULL;
	parser_instance->state.index_mem_size = 0;
    parser_instance->state.index_mem_base = NULL;
	if (config_params->index_mem_ptr)
	{
	    if (sizeof (adi_mp4ad_container_indexing_info_t) > config_params->index_mem_num_bytes)
    	{
    		result = ADI_MP4AD_INVALID_CONFIG_PARAMS; // do not use the supplied memory - too small.
    	}
    	else
    	{
    	    // CVN 2007May12
        	parser_instance->atoms_indexing = (adi_mp4ad_container_indexing_info_t*) config_params->index_mem_ptr;
        	parser_instance->state.index_mem_size = config_params->index_mem_num_bytes;     // CVN 2007May12
    	    parser_instance->state.index_mem_base = config_params->index_mem_ptr;
        	memset (parser_instance->state.index_mem_base, 0, parser_instance->state.index_mem_size);
        }
    }
	reset_track_info(parser_instance);

	parser_instance->state.current = ADI_MP4AD_BOXTYPE_OBJECT;
    parser_instance->state.warning_info = ADI_MP4AD_WARNING_NONE; // clear all warnings
	parser_instance->atoms->esds.pce_tag = -1;

    // reset the current chunk counter
	parser_instance->state.playback.current_chunk = -1;
	
	return result;
}


// Reconfigures parameters while parsing underway
adi_mp4ad_return_code_t adi_mp4ad_reconfigure (
    adi_mp4ad_instance_t *instance_handle,
    adi_mp4ad_config_item_t config_item,
    adi_mp4ad_config_t *config_params
)
{
	adi_mp4ad_return_code_t status= ADI_MP4AD_SUCCESS;

    // FIXME - ADI_MP4AD_ALL_RECONFIG_PARAMS not yet implemented
    switch (config_item)
    {
	    default:
	    {
	        status = ADI_MP4AD_INVALID_CONFIG_PARAMS;
		}
    }

	config_params->circular_buffer_base = instance_handle->state.input_buffer.base;
	config_params->circular_buffer_length = instance_handle->state.input_buffer.length;
	config_params->use_circular_bitstream_buffer = instance_handle->state.input_buffer.is_circular;
	return status;
}




// Parses variables from the bitstream
FAST_CODE_SECTION
adi_mp4ad_return_code_t adi_mp4ad_parser (	adi_mp4ad_instance_t *parser,
											int length,
											adi_mp4ad_bitstream_data_t *bytes,
											adi_mp4ad_output_status_t *status)
{
	adi_mp4ad_return_code_t result = ADI_MP4AD_SUCCESS;
	int num_input_bytes_consumed = 0;

    /* set default return status values here */
	status->data_unit_num_frames = 0;             // this field is not set by this function
	status->warning_info = ADI_MP4AD_WARNING_NONE;	// Reset warnings

	// Set the input buffer
	if (parser->state.input_buffer.is_circular)
	{
		// Circular buffer, start from last end point
		parser->state.input_buffer.read_index = bytes - parser->state.input_buffer.base;
	}
	else
	{
		// Linear buffer, start from buffer base address
		parser->state.input_buffer.read_index = 0;
		parser->state.input_buffer.base = bytes;
		parser->state.input_buffer.length = length;
	}
	parser->state.input_buffer.first_read_index = parser->state.input_buffer.read_index;
	parser->state.input_buffer.first_read_bit = parser->state.input_buffer.read_bit;
	parser->state.input_buffer.write_index = parser->state.input_buffer.first_read_index + length;
	if (parser->state.input_buffer.write_index >= parser->state.input_buffer.length)
	{
		parser->state.input_buffer.write_index -= parser->state.input_buffer.length;
	}
	
    parser->state.input_buffer.is_full = 0;
    // only indicate that the buffer is potentially full if data was actually input
	if (length > 0) 
    	parser->state.input_buffer.is_full = 1;
    // otherwise no data is input, so we need to bypass the while loop
    else 
    {
        result = ADI_MP4AD_NEED_MORE_INPUT;
    	parser->object.size = ADI_MP4AD_ATOM_BIGGEST_HEADER_BITS/8; // max num bytes for box header
    	parser->object.type = 0x3F3F3F3F; // ascii for "????"
    }

	// Main parsing loop
	while (result == ADI_MP4AD_SUCCESS)
	{
   		print_atom_header (parser);
		update_hierarchy(parser, parser->state.stream_index);

		// Record the read index
		int box_read_index = parser->state.input_buffer.read_index;

		// Call the parser function for the current state
		if ( (parser->state.current < 0) || (parser->state.current >= ADI_MP4AD_BOXTYPE_NUM_HANDLED) )
		{
		    result = ADI_MP4AD_TERMINAL_FAILURE;
		    break; // escape out to the user with a fatal error code if we are in an error state
		}
		result = (*boxtype_parameters[parser->state.current].parser_function)(parser);

		// Update stream_index
		num_input_bytes_consumed = parser->state.input_buffer.read_index - box_read_index;
		if (num_input_bytes_consumed < 0 && result != ADI_MP4AD_UNKNOWN_OBJECT)
		{
			num_input_bytes_consumed += parser->state.input_buffer.length;
		}
		else if (   (num_input_bytes_consumed == 0)
    	         && (result == ADI_MP4AD_NEED_MORE_INPUT)
		         && (parser->state.input_buffer.read_index == parser->state.input_buffer.first_read_index)
		         && !(parser->state.input_buffer.is_full)
		        )
		{
			// special case when all input bytes of a full buffer have been consumed in one box
            num_input_bytes_consumed = length;
		}
		parser->state.stream_index += num_input_bytes_consumed;

	}

    // compute number of bytes used so far by the indexing tables
	parser->state.index_mem_used_bytes = status->index_mem_used_bytes = cal_index_mem_unused_bytes(parser);      // CVN 2007May12

	// Return the output status
	status->unconsumed_input_data_ptr = &(parser->state.input_buffer.base[parser->state.input_buffer.read_index]);
	status->num_input_bytes_consumed = parser->state.input_buffer.read_index - parser->state.input_buffer.first_read_index;
	if (status->num_input_bytes_consumed < 0)
	{
		status->num_input_bytes_consumed += parser->state.input_buffer.length;
	}
	else if (   (status->num_input_bytes_consumed == 0)
	         && (result == ADI_MP4AD_NEED_MORE_INPUT)
		     && !(parser->state.input_buffer.is_full)
		    )
	{
	    // special case when all input bytes of a full buffer have been consumed
		status->num_input_bytes_consumed = length;
	}

	// Return length and box type of the object
	status->next_object_length_num_bytes = parser->object.size;
	status->next_object_id = parser->object.type;
	status->warning_info = parser->state.warning_info;

	// update stream index, Assuming that unknown objects are handled by the application
	if ((result == ADI_MP4AD_UNKNOWN_OBJECT) || (result == ADI_MP4AD_SKIP_DATA_REQUEST))
	{
		parser->state.stream_index += status->next_object_length_num_bytes;
	}

	return result;
}

// Return the time length between 2 given samples      CVN 2007Aug27
static long int get_time_length(adi_mp4ad_instance_t   *parser, 
                         long int               first_sample, 
                         long int               last_sample,
                         int                    last_sample_included)
{
    adi_mp4ad_container_indexing_info_t *index_tables = parser->atoms_indexing;
    long int samples_remaining = first_sample;
    long int time_length;
	int is_found = 0;
	int index;

    // check if info needed to calculate timestamp is not available
	if (!(index_tables->stts.is_parsed && (last_sample >= first_sample)))
	{
	    return (-1);
	}

  	for (index = 0; index < index_tables->stts.entry_count; index++)
	{
		if (samples_remaining > index_tables->stts.sample_count[index])
		{
			// The desired sample is after this group of stream indices
			samples_remaining -= index_tables->stts.sample_count[index];
		}
		else
		{
			// The desired sample is within this group of stream indices
			is_found = 1;
			time_length = (index_tables->stts.sample_count[index] + 1 -
			       samples_remaining) * index_tables->stts.sample_delta[index];
            // samples remaining from the next group			       
			samples_remaining = last_sample - first_sample - 
			      (index_tables->stts.sample_count[index] - samples_remaining);
			if (samples_remaining <= 0)
			{
			    // last_sample and first_sample in the same group
			    time_length = (last_sample - first_sample + 1) *
			                            index_tables->stts.sample_delta[index];
			    if (!last_sample_included)
			        time_length -= index_tables->stts.sample_delta[index];
			    return time_length;			    
			}
			else
			{
			    index++;
			    break;
			}			
		};
	}
	
	if (!is_found) return -1;
	
	is_found = 0;
	for (; index < index_tables->stts.entry_count; index++)
	{
	    if (samples_remaining > index_tables->stts.sample_count[index])
	    {
	        samples_remaining -= index_tables->stts.sample_count[index];
	        time_length += index_tables->stts.sample_count[index] * 
                                        index_tables->stts.sample_delta[index];
        }
        else
        {
            is_found = 1;
            time_length += samples_remaining *
                                        index_tables->stts.sample_delta[index];
		    if (!last_sample_included)
		        time_length -= index_tables->stts.sample_delta[index];            
            break;
        }        
    }
    
    if (!is_found)
        return -1;
    else
        return time_length;
}

// Return the number of frames between 2 given samples      CVN 2007Aug27
static long int get_num_frames(adi_mp4ad_instance_t    *parser, 
                       long int                 first_sample, 
                       long int                 last_sample)
{
    adi_mp4ad_container_info_t *atom_tables = parser->atoms;
    long int time_length = get_time_length(parser, first_sample, last_sample, 1);
    if ((time_length > 0) && atom_tables->esds.is_parsed && 
                                                  atom_tables->mdhd.is_parsed)
    {
        int frame_length = (atom_tables->esds.frame_length_flag)? 960 : 1024;
        return (long int)(0.5 + ((double)time_length * atom_tables->esds.sampling_frequency)
                               / (frame_length * atom_tables->mdhd.timescale));
    }    
    else
        return -1;
}

// return timestamp corresponding to the desired sample
int get_sample_timestamp(adi_mp4ad_instance_t *parser, long int desired_sample)
{
    long int timestamp=-1;

    // check if info needed to calculate timestamp is not available
	if (   (parser->atoms_indexing) 
	    && (parser->atoms->mdhd.is_parsed) )
	{
        // Calculate time stamp     CVN 2007Aug27
        timestamp = get_time_length(parser, 1, desired_sample, 0);
	}

    if (timestamp < 0)
        return -1;
    else
        return ((int) (0.5+(1000.0 * timestamp / parser->atoms->mdhd.timescale)));
}

// Writes the stream byte offset of the seek time to the output status, given
// the current time and stream_byte_offset
adi_mp4ad_return_code_t adi_mp4ad_seek (	adi_mp4ad_instance_t *parser,
											int seek_time_ms,
											int curr_time_ms,
											int curr_stream_byte_offset,
											adi_mp4ad_output_status_t *status)
{
	int index = 0;
	int is_found = 0;
	long int seek_sample_index = 1;  //(file format numbers samples from 1 onwards)
	long int current_time_stream = 0;

	status->index_mem_used_bytes = parser->state.index_mem_used_bytes;		// CVN 2007May23

    // Setup default return values
	status->data_unit_stream_offset = curr_stream_byte_offset;
	status->data_unit_num_frames = 0;   // CVN 2007Aug27
	status->data_unit_time_ms = curr_time_ms;
	status->warning_info = parser->state.warning_info;

    // First check if the index table memory exists
    if (parser->atoms_indexing == NULL)
	{
		return ADI_MP4AD_SEEK_TABLES_NOT_ALLOCATED;
	}

	// Confirm all required boxes have been parsed
	if (!(	parser->atoms->mdhd.is_parsed
		&&	parser->atoms_indexing->stts.is_parsed
		&&	parser->atoms_indexing->stsc.is_parsed
		&&	parser->atoms_indexing->stco.is_parsed))
	{
		return ADI_MP4AD_OPERATION_FAILED;
	}

	// Convert milliseconds to stream timescale
	long int seek_time_stream = (int) ((0.5 + (1.0 * seek_time_ms *  parser->atoms->mdhd.timescale) / 1000.0));

	// Find first sample index prior to the desired time by unpacking the stts table
	// A sample is a time-contiguous compressed section of audio
	is_found = 0;
	for (index = 0; index < parser->atoms_indexing->stts.entry_count; index++)
	{
		long int time_stream_remaining = seek_time_stream - current_time_stream;
		long int time_stream_this_entry = parser->atoms_indexing->stts.sample_count[index] * parser->atoms_indexing->stts.sample_delta[index];
		if (time_stream_remaining > time_stream_this_entry)
		{
			// The seek time is after this group of stream indices
			current_time_stream += time_stream_this_entry;
			seek_sample_index += parser->atoms_indexing->stts.sample_count[index];
			continue;
		}
		else
		{
			// The seek is is within this group of stream indices
			seek_sample_index += (time_stream_remaining / parser->atoms_indexing->stts.sample_delta[index]);
			is_found = 1;
			break;
		};
	}
	// Desired index is past the last index point?
	if (!is_found)
	{
		// Use a very large number  // CVN 2007Aug27
		seek_sample_index = 0x7FFFFFFF;
	}

	// Quantise seek_sample_index to the closest earlier sync-point from the stss table
	// this table is optional, therefore we do not treat this as an error if it
	// has not been parsed.
	if ((parser->atoms_indexing->stss.is_parsed)
	    && (parser->atoms_indexing->stss.entry_count > 0))  // CVN 2007May12
	{
		is_found = 0;
		if (	(seek_sample_index < parser->atoms_indexing->stss.sample_number[0])
			|| 	(parser->atoms_indexing->stss.entry_count == 1)	)
		{
			// Seek sample is before the first sync-point, or there is only one sync-point
			is_found = 1;
			seek_sample_index = parser->atoms_indexing->stss.sample_number[0];
		}
		else
		{
			// Find the closest earlier sync-point
			for (index = 1; index < parser->atoms_indexing->stss.entry_count; index++)
			{
				if (seek_sample_index < parser->atoms_indexing->stss.sample_number[index])
				{
					is_found = 1;
					seek_sample_index = parser->atoms_indexing->stss.sample_number[index - 1];
					break;
				}
			}
		}
		// Desired index is past the last index point?
		if (!is_found)
		{
			// Use the last available sync-point
			seek_sample_index = parser->atoms_indexing->stss.sample_number[parser->atoms_indexing->stss.entry_count - 1];
		}
	}

	// Map seek_sample_index to chunk_index using the stsc table
	long int chunk_index = 0;
	long int sample_start_of_chunk = 1;
	long int last_sample_in_chunk = 1;
	if (parser->atoms_indexing->stsc.entry_count == 1)
	{
		// There is only one group of chunks.  Find the chunk holding the seek sample
		chunk_index = (seek_sample_index - 1) / parser->atoms_indexing->stsc.samples_per_chunk[0];
		// Make sure chunk_index is not out of bound in stco table
		if (chunk_index >= parser->atoms_indexing->stco.entry_count)
    	{
    		// use the last sample in the last chunk as the desired sample to seek to
    		chunk_index = parser->atoms_indexing->stco.entry_count - 1;
    	}
    	// Make sure chunk_index is not out of bound in stsc table
    	if ((parser->atoms_indexing->stsc.last_chunk > 0) &&            // stsc is not fully stored
    	    (chunk_index >= parser->atoms_indexing->stsc.last_chunk))   // out of bound
    	{    	    
    	    chunk_index = parser->atoms_indexing->stsc.last_chunk - 1;
    	}    	
    	        
		sample_start_of_chunk = 1 + chunk_index * parser->atoms_indexing->stsc.samples_per_chunk[0];
		last_sample_in_chunk = sample_start_of_chunk + parser->atoms_indexing->stsc.samples_per_chunk[0] - 1;
	}
	else
	{
		// Expand each chunk group
		long int sample_index_stream = 1;
		is_found = 0;
		for (index = 0; index < parser->atoms_indexing->stsc.entry_count-1; index++)
		{
			// Is the seek sample within this chunk group?
			int num_chunks_prev_group = parser->atoms_indexing->stsc.first_chunk[index+1] - parser->atoms_indexing->stsc.first_chunk[index];
			long int samples_prev_group = num_chunks_prev_group * parser->atoms_indexing->stsc.samples_per_chunk[index];
			long int samples_upper_bound = sample_index_stream + samples_prev_group;
			
			if (seek_sample_index >= samples_upper_bound)
			{
 				// Sample index is after this group of chunks
				chunk_index += num_chunks_prev_group;
				
				// Make sure chunk_index is not out of bound in stco table
   			    if (chunk_index >= parser->atoms_indexing->stco.entry_count)
    			{
    			    is_found = 1;
    			    // Take the last chunk in stco table
    			    chunk_index = parser->atoms_indexing->stco.entry_count - 1;
    			}
    			// Make sure chunk_index is not out of bound in stsc table
    			if ((parser->atoms_indexing->stsc.last_chunk > 0) &&            // stsc is not fully stored
    	            (chunk_index >= parser->atoms_indexing->stsc.last_chunk))   // chunk_index out of bound    
    			{
    			    is_found = 1;
    			    chunk_index = parser->atoms_indexing->stsc.last_chunk - 1;
    			}
    			    			
    			if (is_found)
    			{    
    			    // Recalculate sample indices
    			    sample_start_of_chunk = sample_index_stream +
    			        (chunk_index + 1 - parser->atoms_indexing->stsc.first_chunk[index])
    			        * parser->atoms_indexing->stsc.samples_per_chunk[index];
    			    last_sample_in_chunk = sample_start_of_chunk + 
    			                    parser->atoms_indexing->stsc.samples_per_chunk[0] - 1;
    			    break;
    			}			                   
                else
                {
                    // Proceed to the next group of chunks
				    sample_index_stream = samples_upper_bound;
				    continue;
				}				
			}
			else
			{
			    // found the sample in this group of chunks
				break;
			}
		}
		
		if (!is_found)
		{
    		// identify the chunk index containing the sample of interest.
    		long int sample_offset = seek_sample_index - sample_index_stream;
    		long int chunk_offset = sample_offset / parser->atoms_indexing->stsc.samples_per_chunk[index];
    		
    		// Make sure chunk_index is not out of bound in stco table
    		if ((chunk_index + chunk_offset) >= parser->atoms_indexing->stco.entry_count)
    		{
    		    chunk_offset = parser->atoms_indexing->stco.entry_count - chunk_index - 1;
    		    is_found = 1;
    		}
    		// Make sure chunk_index is not out of bound in stsc table
    		if ((parser->atoms_indexing->stsc.last_chunk > 0) &&                             // stsc is not fully stored
    	        ((chunk_index + chunk_offset) >= parser->atoms_indexing->stsc.last_chunk))   // chunk_index out of bound    
    		{
    		    chunk_offset = parser->atoms_indexing->stsc.last_chunk - chunk_index - 1;
    		    is_found = 1;
    		}
    		
    		chunk_index += chunk_offset;
    		// Find the samples at the start & end of the found chunk
    		sample_start_of_chunk = sample_index_stream + chunk_offset*parser->atoms_indexing->stsc.samples_per_chunk[index];
    		last_sample_in_chunk = sample_start_of_chunk + parser->atoms_indexing->stsc.samples_per_chunk[index] - 1;
        }
	}

	// Find the file offset of the chunk using the stco table
	long int file_offset = parser->atoms_indexing->stco.chunk_offset[chunk_index];

	// Get time of the chunk's first sample using table
	status->data_unit_time_ms = get_sample_timestamp(parser, sample_start_of_chunk);
	status->data_unit_num_frames = get_num_frames(parser, sample_start_of_chunk, last_sample_in_chunk);
	if ((status->data_unit_time_ms < 0) || (status->data_unit_num_frames <= 0))
		return ADI_MP4AD_OPERATION_FAILED;

	// Update chunk
	parser->state.playback.current_chunk = chunk_index;

	// Write results
	status->data_unit_stream_offset = file_offset;

	// Return ADI_MP4AD_SEEK_IGNORED if current location best satisfies the seek location
	if (	(status->data_unit_stream_offset == curr_stream_byte_offset)
		||	(status->data_unit_time_ms == curr_time_ms)	)
	{
		return ADI_MP4AD_SEEK_IGNORED;
	}
	else
	{
#ifndef NDEBUG
        printf("Seek to chunk #%d    fileoffset: %lX      time: %ld (ms)\n", 
                                 parser->state.playback.current_chunk,
                                 status->data_unit_stream_offset,
                                 status->data_unit_time_ms);
#endif	    
		return ADI_MP4AD_SUCCESS;
	}
}

// If no next chunk is available, returns ADI_MP4AD_STREAM_COMPLETE.
// Else, writes the file offset of the next chunk to status.data_unit_stream_offset and
// the number of frames in the next audio chunk to status.data_unit_num_frames and
// returns ADI_MP4AD_SUCCESS
adi_mp4ad_return_code_t adi_mp4ad_get_next_audio_data_unit (
	adi_mp4ad_instance_t *parser,
	adi_mp4ad_output_status_t *status
)
{
	int index = 0;
	long int sample_stream_time = 0;

	status->data_unit_stream_offset = 0;    // CVN 2007Jun12
	status->data_unit_num_frames = 0;       // CVN 2007Aug27
	status->index_mem_used_bytes = parser->state.index_mem_used_bytes;		// CVN 2007May23
	status->warning_info = parser->state.warning_info;

    // first check if the index table memory exists
    if (parser->atoms_indexing == NULL)
	{
		return ADI_MP4AD_SEEK_TABLES_NOT_ALLOCATED;
	}

	// Confirm all required boxes have been parsed
	if (!(	parser->atoms_indexing->stts.is_parsed
		&&	parser->atoms_indexing->stsc.is_parsed
		&&	parser->atoms_indexing->stco.is_parsed))
	{
		return ADI_MP4AD_OPERATION_FAILED;
	}

	// Advance to the next chunk
	parser->state.playback.current_chunk++;

	// Stream is complete if there is no next chunk
	if ((parser->state.playback.current_chunk >= parser->atoms_indexing->stco.entry_count) ||
	    ((parser->atoms_indexing->stsc.last_chunk > 0) &&
	     (parser->state.playback.current_chunk >= parser->atoms_indexing->stsc.last_chunk)))
	{
		return ADI_MP4AD_STREAM_COMPLETE;
	}

	// Get the chunk file offset
	long int chunk_offset = parser->atoms_indexing->stco.chunk_offset[parser->state.playback.current_chunk];

	// Find the first and last sample in the chunk by reconstructing the sample-to-chunk table
	// and summing the lengths of each sample in the chunk
	long int first_sample_in_chunk = 0;
	long int last_sample_in_chunk = 0;
	if (parser->atoms_indexing->stsc.entry_count == 1)
	{
		// There is only one group of chunks
		first_sample_in_chunk = 1 + parser->state.playback.current_chunk * parser->atoms_indexing->stsc.samples_per_chunk[0];
		last_sample_in_chunk = first_sample_in_chunk + parser->atoms_indexing->stsc.samples_per_chunk[0] - 1;
	}
	else
	{

		// Expand each chunk group
		first_sample_in_chunk = 1;
		last_sample_in_chunk = -1;
		for (index = 1; index < parser->atoms_indexing->stsc.entry_count; index++)
		{
			// Is the chunk within this chunk group?
			int this_chunk = parser->state.playback.current_chunk + 1; 			// Chunks in table start at 1
			if (this_chunk >= parser->atoms_indexing->stsc.first_chunk[index])
			{
				// No, desired chunk is after this group of chunks
				int num_chunks_prev_group = parser->atoms_indexing->stsc.first_chunk[index] - parser->atoms_indexing->stsc.first_chunk[index-1];
				long int samples_prev_group = num_chunks_prev_group * parser->atoms_indexing->stsc.samples_per_chunk[index-1];
				first_sample_in_chunk += samples_prev_group;
				continue;
			}
			else
			{
				// Yes, desired chunk is in this group of chunks.
				int num_chunks_to_offset = this_chunk - parser->atoms_indexing->stsc.first_chunk[index-1];
				long int samples_to_offset = num_chunks_to_offset * parser->atoms_indexing->stsc.samples_per_chunk[index-1];
				first_sample_in_chunk += samples_to_offset;
				last_sample_in_chunk = first_sample_in_chunk + parser->atoms_indexing->stsc.samples_per_chunk[index-1] - 1;
				break;
			}
		}
		if (last_sample_in_chunk == -1)
		{
			//last_sample_in_chunk = first_sample_in_chunk + parser->atoms_indexing->stsc.samples_per_chunk[parser->atoms_indexing->stsc.entry_count - 1] - 1;

			// CVN 2007Jun12
			index = parser->atoms_indexing->stsc.entry_count - 1;
			first_sample_in_chunk += (parser->state.playback.current_chunk + 1 - parser->atoms_indexing->stsc.first_chunk[index]) *
			                            parser->atoms_indexing->stsc.samples_per_chunk[index];
            last_sample_in_chunk = first_sample_in_chunk + parser->atoms_indexing->stsc.samples_per_chunk[index] - 1;

		}
	}

	// Get time of the chunk's first sample using table
	status->data_unit_time_ms = get_sample_timestamp(parser, 
	                                                 first_sample_in_chunk);
	// Number of frames contained in the data unit      CVN 2007Aug27
	status->data_unit_num_frames = get_num_frames(parser, 
	                                              first_sample_in_chunk, 
	                                              last_sample_in_chunk);
	if ((status->data_unit_time_ms < 0) || (status->data_unit_num_frames <= 0))
		return ADI_MP4AD_OPERATION_FAILED;

	// Write results
	status->data_unit_stream_offset = chunk_offset;
#ifndef NDEBUG
    printf("Proceed to chunk #%d    fileoffset: %lX      time: %ld (ms)\n", 
                                 parser->state.playback.current_chunk,
                                 status->data_unit_stream_offset,
                                 status->data_unit_time_ms);
#endif

	return ADI_MP4AD_SUCCESS;
}

/*
**
** EOF: $HeadURL: svn://dingofiles.spd.analog.com/audio/trunk/mp4a-parser/src/adi_mp4ad_parser.c $
**
*/
