/******************************************************************************
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:11 $
$LastChangedBy: sto $

Project:    AAC Decoder
Title:      MP4 file format helper module for the simple I/O application
Author:     CVN

Description:
            This file contains some helper functions used for parsing MP4 files
            within the simple I/O application

References:
            None

******************************************************************************/
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "mp4ad_parsing_process.h"
#include "adi_mp4ad_parser.h"
#include "circular_buffer.h"

/*****************************************************************************
 *          #defines                                                         *
 *****************************************************************************/



/*****************************************************************************
 *          typedefs/Enumerations                                            *
 *****************************************************************************/



/*****************************************************************************
 *          Local variables                                                  *
 *****************************************************************************/



/*****************************************************************************
 *          Local function declaration                                       *
 *****************************************************************************/

/*****************************************************************************
 *          Function Definitions                                                 *
 *****************************************************************************/

/*
**
** Function:            copy_mp4ad_config
**
** Description:         Copies the audio configuration required to initialise
**                      the AAC decoder, parsed from an MP4 file
**
** Arguments:
**
**  requested_dec_config - decoder configuration structure
**  parser_config        - parser configuration structure which contains the pointers
**                          to the container information struct and the song tags struct
**                          filled by the parser
**  print_device         - file handle for printing out diagnostic info (disabled if NULL)
**
** Outputs:             requested_dec_config filled with parsed audio config
**
** Return value:        0  = success
**                      -1 = the file does not contain AAC-LC or HE-AAC audio, so decoder is not configured
**
**
*/
int copy_mp4ad_config(  adi_aacd_config_t* requested_dec_config,
                        adi_mp4ad_config_t* parser_config,
                        FILE *print_device)
{
    int return_code=0;
    int i,j;
    adi_mp4ad_container_info_t* PARSER_STATE_atoms = parser_config->container_info_ptr;
    adi_mp4ad_metadata_t* metadata = parser_config->text_fields_ptr;

    // check the file contains AAC-LC or HE-AAC audio before configuring the decoder
    if (   !(PARSER_STATE_atoms->esds.is_parsed)
        || (   (PARSER_STATE_atoms->esds.audio_object_type != ADI_MP4AD_AACLC_MPEG4_OBJECT)
            && (PARSER_STATE_atoms->esds.audio_object_type != ADI_MP4AD_SBR_MPEG4_OBJECT) )
       )
    {
        return_code = -1;
    }
    else
    {
        requested_dec_config->container_format = ADI_AACD_RAW;

        // Copy parsed variables to the requested configuration
        requested_dec_config->container_info.num_channels             = PARSER_STATE_atoms->esds.channel;
        requested_dec_config->container_info.sampling_frequency_index = PARSER_STATE_atoms->esds.sampling_frequency_index;
        requested_dec_config->container_info.sampling_frequency       = PARSER_STATE_atoms->esds.sampling_frequency;
        requested_dec_config->container_info.sbr_sampling_frequency   = PARSER_STATE_atoms->esds.sbr_sampling_frequency;
        requested_dec_config->container_info.frame_length_flag        = PARSER_STATE_atoms->esds.frame_length_flag;
        requested_dec_config->container_info.channel_configuration    = PARSER_STATE_atoms->esds.channel_configuration;

        // Copy channel config in PCE
        if ((requested_dec_config->selected_program_id = PARSER_STATE_atoms->esds.pce_tag) >= 0)
        {
            int ele_num = 0;

            requested_dec_config->container_info.num_front_channel_elements = PARSER_STATE_atoms->esds.num_front_channel_elements;
            requested_dec_config->container_info.num_side_channel_elements  = PARSER_STATE_atoms->esds.num_side_channel_elements;
            requested_dec_config->container_info.num_back_channel_elements  = PARSER_STATE_atoms->esds.num_back_channel_elements;
            requested_dec_config->container_info.num_lfe_channel_elements   = PARSER_STATE_atoms->esds.num_lfe_channel_elements;
            requested_dec_config->container_info.num_coupled_channel_elements = PARSER_STATE_atoms->esds.num_valid_cc_elements;
            
            ele_num = requested_dec_config->container_info.num_front_channel_elements
                + requested_dec_config->container_info.num_side_channel_elements
                + requested_dec_config->container_info.num_back_channel_elements
                + requested_dec_config->container_info.num_lfe_channel_elements;

            j=0;
            memset(requested_dec_config->container_info.channel_element_id,0,ADI_AACD_MAX_NUM_AUDIO_ELEMENTS);
            for (i=0; i<ele_num; i++)
            {
                if ((PARSER_STATE_atoms->esds.channel_element_id[i] & ADI_MP4AD_ELEMENT_TYPE_MASK) == ADI_MP4AD_ELEMENT_IS_FRONT)
                {
                    requested_dec_config->container_info.channel_element_id[j++] =
                        PARSER_STATE_atoms->esds.channel_element_id[i];
                }
            }
            assert ((j) == requested_dec_config->container_info.num_front_channel_elements);
        }

        if (requested_dec_config->container_info.num_channels == 1)
            requested_dec_config->output_chan_config = ADI_AACD_1_0_CHAN_CONFIG;
        else
            requested_dec_config->output_chan_config = ADI_AACD_2_0_CHAN_CONFIG;
    }

    if (print_device)
    {
        fprintf(print_device, "File contains MPEG-4 Audio with object type = %d\n",PARSER_STATE_atoms->esds.audio_object_type);

        fprintf(print_device, "    SONG TAGS\n");
        fprintf(print_device, "    --------------\n");
        fprintf(print_device, "    PARSER_METADATA_SONGTITLE   = %s\n", metadata->song_title.tag_data);
        fprintf(print_device, "    PARSER_METADATA_ARTIST      = %s\n", metadata->artist.tag_data);
        fprintf(print_device, "    PARSER_METADATA_ALBUM       = %s\n", metadata->album.tag_data);
        fprintf(print_device, "    PARSER_METADATA_YEAR        = %s\n", metadata->year.tag_data);
        fprintf(print_device, "    PARSER_METADATA_GENRE       = %s\n", metadata->genre.tag_data);
        fprintf(print_device, "    PARSER_METADATA_COMMENTS    = %s\n", metadata->comments.tag_data);
        fprintf(print_device, "    --------------\n");

        fprintf(print_device, "    SONG PROPERTIES PARSED FROM FILE\n");
        fprintf(print_device, "    --------------\n");
        fprintf(print_device, "    track max bitrate (bps) = %d\n" ,PARSER_STATE_atoms->esds.max_bitrate_bps);
        fprintf(print_device, "    track avg bitrate (bps) = %d\n" ,PARSER_STATE_atoms->esds.avg_bitrate_bps);
        fprintf(print_device, "    length of song (secs) = %.1f\n" ,(float)PARSER_STATE_atoms->mdhd.duration/PARSER_STATE_atoms->mdhd.timescale);
        fprintf(print_device, "    --------------\n");
    }

    return return_code;
}


/*
**
** Function:            perform_mp4ad_parsing_loop
**
** Description:         Performs the file parsing logic, where the input parser buffer is
**                      processed by the mp4 file parser module.
**
** Arguments:
**
**   adi_mp4ad_parser_instance  - parser instance to use for parsing this file
**   parser_buffer              - circular buffer object to use for parsing this file
**   parser_output_status       - pointer to parser output status struct so user can access the output status of the parser
**   print_device               - file handle for printing out diagnostic info (disabled if NULL)
**
**
** Outputs:             parser_output_status
**
** Return value:        >0 = remaining number of bytes size of a large atom that needs to be skipped in the file
**                      0  = more data needs to be added to the parser buffer
**                      -1 = parser initialisation failed (cannot parse the file)
**                      -2 = parser buffer too small (file only partially parsed)
**                      -3 = ADI_MP4AD_INVALID_BITSTREAM returned by parser (cannot parse the file)
**                      -4 = fatal error returned by parser (cannot parse the file)
*/
int perform_mp4ad_parsing_loop(
        adi_mp4ad_instance_t*   adi_mp4ad_parser_instance,
        cbuffer_t*              parser_buffer,
        adi_mp4ad_output_status_t* parser_output_status,
        FILE*                   print_device
)
{
    int num_bytes_added;
    adi_mp4ad_return_code_t parser_error = ADI_MP4AD_UNKNOWN_OBJECT;
    char *fourcc_ptr;

    // Parse the stream
    while (parser_error != ADI_MP4AD_NEED_MORE_INPUT)
    {
        // Parse the buffer
        adi_mp4ad_bitstream_data_t *input_bitstream = (adi_mp4ad_bitstream_data_t *)CBUFFER_PTR(*parser_buffer);
        parser_error = adi_mp4ad_parser (   adi_mp4ad_parser_instance,
                                            parser_buffer->data_length,
                                            input_bitstream,
                                            parser_output_status);

        advance_cbuffer(parser_buffer,parser_output_status->num_input_bytes_consumed);

        // move bitstream pointer to the (new) current location of unprocessed data
        input_bitstream = (adi_mp4ad_bitstream_data_t *)parser_output_status->unconsumed_input_data_ptr;

        switch (parser_error)
        {
            case ADI_MP4AD_SKIP_DATA_REQUEST:
            {
                if (print_device)
                {
                    fourcc_ptr = (char *) &(parser_output_status->next_object_id);
                    fprintf(print_device, "(%c%c%c%c) box partially skipped\n",
                        fourcc_ptr[3],fourcc_ptr[2],fourcc_ptr[1],fourcc_ptr[0]);
                }
                return parser_output_status->next_object_length_num_bytes;
            }            
            case ADI_MP4AD_NEED_MORE_INPUT:
            {
                if (print_device)
                    fprintf(print_device, "Need more data for parsing\n");

                // indicate buffer overflow if the parser needs more data but the buffer is full
                if (parser_buffer->data_length == parser_buffer->length)
                {
                    if (print_device) fprintf(print_device,
                        "Parsing buffer too small, (%d bytes required) this atom will be skipped (file only partially parsed)\n",
                        parser_output_status->next_object_length_num_bytes);

                    return -2;
                }
                // else normal filling of parser buffer by the application

                break;
            };
            case ADI_MP4AD_INVALID_BITSTREAM:
            {
                if (print_device) fprintf(print_device, "Found invalid/unsupported bitstream. Program exiting...\n");
                return -3;
            };
            case ADI_MP4AD_TERMINAL_FAILURE:
            {
                if (print_device) fprintf(print_device, "Parser cannot continue. Program exiting...\n");
                return -4;
            };
            case ADI_MP4AD_UNKNOWN_OBJECT:
            {
                if (print_device)
                {
                    fourcc_ptr = (char *) &(parser_output_status->next_object_id);
                    fprintf(print_device, "Unknown bitstream object found (%c%c%c%c)",
                        fourcc_ptr[3],fourcc_ptr[2],fourcc_ptr[1],fourcc_ptr[0]);
                    fprintf(print_device, ". Parsing skipping...\n");
                }

          
                
                // Skip this unknown object
                if (parser_output_status->next_object_length_num_bytes <= parser_buffer->data_length)
                {
                    if (print_device) fprintf(print_device, "Skipping %d bytes in the parser buffer\n", parser_output_status->next_object_length_num_bytes);
                    
                    //printf("Skipping %d bytes in the parser buffer\n", parser_output_status->next_object_length_num_bytes);
                    // The atom is fully contained in the input buffer
                    // Move the input buffer pointer to the start of the next atom
                    advance_cbuffer (parser_buffer,parser_output_status->next_object_length_num_bytes);
                    input_bitstream = (adi_mp4ad_bitstream_data_t *)CBUFFER_PTR(*parser_buffer);
                }
                else
                {
                    // The atom is not fully contained in the input buffer
                    if (print_device) fprintf(print_device, "Skipping %d extra bytes directly in the file\n", parser_output_status->next_object_length_num_bytes - parser_buffer->data_length);
                    //printf("Skipping %d extra bytes directly in the file\n", parser_output_status->next_object_length_num_bytes - parser_buffer->data_length);
                    return parser_output_status->next_object_length_num_bytes - parser_buffer->data_length; // go back to the calling function to skip this atom and get more data
                }

                break;
            }
            default:
            {
                if (print_device) fprintf(print_device, "Unknown parser error code (%d)\n",parser_error);
                return -4;
            }
        }
    } // end while NOT need more data

    if (print_device)
    {
        if (parser_output_status->index_mem_used_bytes >= 0)
            fprintf(print_device, "Number of used bytes in the indexing memory: %d\n", parser_output_status->index_mem_used_bytes);
        else
            fprintf(print_device, "Indexing memory is too small. %d additional bytes required.\n", parser_output_status->index_mem_used_bytes);
    }

    return 0;

}

/*
**
** Function:            init_mp4ad_file_access
**
** Description:         initialises the data structure used to store the
**                      state information required to access audio data chunks
**                      from a MP4 file.
**
** Arguments:
**
**   current_file_access_unit_state - data struct containing state info related to the access unit
**   adi_mp4ad_parser_instance      - parser instance to use for demultiplexing audio from this file
**   infile                         - file handle for the input mp4 audio file
**
**
** Outputs:             initialised data struct current_file_access_unit_state
**
** Return value:        none
**
**
*/
void init_mp4ad_file_access(
        mp4_file_index_t*       current_file_access_unit_state,
        adi_mp4ad_instance_t*   adi_mp4ad_parser_instance,
        FILE*                   infile
)
{
    current_file_access_unit_state->input_file = infile;
    current_file_access_unit_state->parser_handle = adi_mp4ad_parser_instance;
    current_file_access_unit_state->num_frames_to_read = 0;
    current_file_access_unit_state->curr_stream_offset = 0;

}

/*
**
** Function:            demultiplex_audio_data_from_mp4_file
**
** Description:         Fills the decoder buffer with data from the current 
**                      audio access unit in the MP4 file. If the end of the access unit
**                      is reached, then the location of the next audio access unit
**                      in the file is found.
**
** Arguments:
**
**   current_file_access_unit_state - data struct containing state info related to the access unit
**   decoder_buffer                 - circular buffer object to use for decoding this file
**   decoder_needs_more_data        - flag indicating if the decoder last returned ADI_AACD_NEED_MORE_INPUT
**   read_request                   - number of bytes requested  to be added to the decoder buffer
**   print_device                   - file handle for printing out diagnostic info (disabled if NULL)
**
**
** Outputs:             end-of-stream indicator (within current_file_access_unit_state)
**                      curr_stream_offset (within current_file_access_unit_state)
**                      number of bytes left to process in current audio access unit
**                           (within current_file_access_unit_state)
**
** Return value:        0  = success
**                      1  = left over data at end of access unit was flushed - resynch should be called 
**                      -1 = index tables do not exist - cannot read any audio data from file
**                      -2 = unknown problem preventing read of new data from next access unit
**
**
*/
int demultiplex_audio_data_from_mp4_file(
        mp4_file_index_t*       current_file_access_unit_state,
        cbuffer_t*              decoder_buffer,
        int                     decoder_needs_more_data,
        int                     read_request,
        FILE*                   print_device
)
{
    adi_mp4ad_instance_t*   adi_mp4ad_parser_instance=current_file_access_unit_state->parser_handle;
    adi_mp4ad_output_status_t parser_output_status;
    adi_mp4ad_return_code_t parser_error = ADI_MP4AD_SUCCESS;
    int num_bytes_added;
    FILE *infile = current_file_access_unit_state->input_file;
    int return_code=0;

    // Check if current data access unit (chunk) has been exhausted
    // Read the next data unit from the stream if required
    // only when current data unit is fully decoded
    // (unless decoder returns ADI_AACD_NEED_MORE_INPUT)
    if  (current_file_access_unit_state->num_frames_to_read <= 0)
    {
        // Find the next file-access chunk
        parser_error = adi_mp4ad_get_next_audio_data_unit(adi_mp4ad_parser_instance, &parser_output_status);
        if (parser_output_status.warning_info)
        {
            if (print_device) fprintf(print_device, "MP4 File parser Module has issued a warning (%#0x)\n",parser_output_status.warning_info);
        }

        switch (parser_error)
        {
            case ADI_MP4AD_SUCCESS:
            {
                current_file_access_unit_state->curr_stream_offset = parser_output_status.data_unit_stream_offset;
                // Start reading a new data unit
                fseek (infile, parser_output_status.data_unit_stream_offset, SEEK_SET);
                // Record the total number of bytes to read for this access unit
                current_file_access_unit_state->num_frames_to_read  = parser_output_status.data_unit_num_frames;
                break;
            }
            case ADI_MP4AD_STREAM_COMPLETE:
            {
                // Could not refill circular buffer, indicate End of Stream
                current_file_access_unit_state->end_of_stream_encountered_flag = 1;
                break;
            }
            case ADI_MP4AD_SEEK_TABLES_NOT_ALLOCATED:
            {
                if (print_device) fprintf(print_device, "FATAL: Index table memory was not allocated - decodingis not possible.\n");
                return -1;
            }
            default:
            {
                if (print_device) fprintf(print_device, "\nParser (error code = %d) has failed to return the audio file-access chunk\n",parser_error);
                return -2; /* not able to get the next audio access unit for unknown reason */
            }
        }
        
        if ((decoder_needs_more_data) && (decoder_buffer->data_length > 0))
        {
            return_code = 1; // signal that decoder resynch should be called
        }
        
        // flush any left over data
        //if (print_device) fprintf(print_device,"Flushing %d bytes from the decoder buffer\n", decoder_buffer->data_length);
        flush_cbuffer(decoder_buffer);
        read_request = (decoder_buffer->length) & 0xFFFFFFFC;		// USBIO bug workaround - num_bytes_to_add must be in words
    }
    
    // Circular buffering input from file
    num_bytes_added = 0;
    if (current_file_access_unit_state->num_frames_to_read > 0)
    {
        num_bytes_added = fread_cbuffer(decoder_buffer, read_request, infile);

        if (num_bytes_added == 0)
        {
            // Could not refill circular buffer, assume End of Stream
            current_file_access_unit_state->end_of_stream_encountered_flag = 1;
        }
    }

    return (return_code);
}

/*
**
** Function:            perform_mp4_file_seeking
**
** Description:         Performs the file seeking logic, where the parser module is used
**                      to update the input file pointer and the current playback time.
**
** Arguments:
**   current_file_access_unit_state - data struct containing state info related to the access unit
**   seek_time_millisecs            - desired playback timestamp to seek to
**   curr_time_millisecs_ptr        - pointer to variable storing the current playback timestamp
**   decoder_buffer                 - circular buffer object used by system for decoding this mp4 file
**   print_device                   - file handle for printing out diagnostic info (disabled if NULL)
**
**
** Outputs:             *curr_time_millisecs_ptr - time stamp for data about to be decoded
**                      curr_stream_offset (within current_file_access_unit_state)
**                      number of bytes left to process in current audio access unit
**                           (within current_file_access_unit_state)
**
** Return value:        none
*/
void perform_mp4_file_seeking(
        mp4_file_index_t*       current_file_access_unit_state,
        int                     seek_time_millisecs,
        int*                    curr_time_millisecs_ptr,
        cbuffer_t*              decoder_buffer,
        FILE*                   print_device
)
{
    FILE *infile = current_file_access_unit_state->input_file;
    adi_mp4ad_output_status_t parser_output_status;
    adi_mp4ad_return_code_t parser_error = ADI_MP4AD_SUCCESS;

    parser_error = adi_mp4ad_seek ( current_file_access_unit_state->parser_handle,
                                    seek_time_millisecs,
                                    *curr_time_millisecs_ptr,
                                    current_file_access_unit_state->curr_stream_offset,
                                    &parser_output_status);
    switch (parser_error)
    {
        case ADI_MP4AD_SUCCESS:
        {
            // Seek the input file to the desired stream offset
            current_file_access_unit_state->curr_stream_offset = parser_output_status.data_unit_stream_offset;
            fseek (infile, parser_output_status.data_unit_stream_offset, SEEK_SET);
            if (ftell(infile) != parser_output_status.data_unit_stream_offset)
            {
                if (print_device) fprintf(print_device, "Could not seek input file to offset!\n");
            }

            // Flush the decoder's input buffer
            flush_cbuffer(decoder_buffer);

            // Get ready to read a whole new audio access unit from file
            current_file_access_unit_state->num_frames_to_read = parser_output_status.data_unit_num_frames;
            // clear the end of file flag since we may have rewound the bitstream
            current_file_access_unit_state->end_of_stream_encountered_flag = 0;

            // Seek completed (output the new timestamp)
            *curr_time_millisecs_ptr = parser_output_status.data_unit_time_ms;
            if (print_device) fprintf(print_device, "MP4 Seek: %d milliseconds. file offset = %#x\n", *curr_time_millisecs_ptr, parser_output_status.data_unit_stream_offset);
            break;
        }
        case ADI_MP4AD_SEEK_IGNORED:
        {
            if (print_device) fprintf(print_device, "WARNING: Ignored seek to time %d milliseconds.\n", seek_time_millisecs);
            break;
        }
        case ADI_MP4AD_OPERATION_FAILED:
        {
            if (print_device) fprintf(print_device, "WARNING: Not able to seek to time %d milliseconds.\n", seek_time_millisecs);
            break;
        }
        case ADI_MP4AD_SEEK_TABLES_NOT_ALLOCATED:
        {
            if (print_device) fprintf(print_device, "WARNING: Index table memory was not allocated - no seeking is possible.\n");
            break;
        }
        default:
        {
            if (print_device) fprintf(print_device, "MP4A seek failed (error code = %d).\n",parser_error);
        }
    }
}
/*
**
** EOF: $HeadURL: svn://dingofiles.spd.analog.com/audio/branches/aac-decoder/3.0.x/example/file-formats/src/mp4ad_parsing_process.c $
**
*/
