/*****************************************************************************
 * mpeg_adec.c: MPEG audio decoder thread
 *****************************************************************************
 * Copyright (C) 1999-2001 VideoLAN
 * $Id: mpeg_adec.c,v 1.9 2001/12/30 05:38:44 sam Exp $
 *
 * Authors: Michel Kaempf <maxx@via.ecp.fr>
 *          Michel Lespinasse <walken@via.ecp.fr>
 *          Samuel Hocevar <sam@via.ecp.fr>
 *
 * 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.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

#define MODULE_NAME mpeg_adec
#include "modules_inner.h"

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include "defs.h"

#include <stdlib.h>                                      /* malloc(), free() */
#include <string.h>

#include "common.h"                                     /* boolean_t, byte_t */
#include "intf_msg.h"
#include "threads.h"
#include "mtime.h"
#include "tests.h"

#include "audio_output.h"               /* aout_fifo_t (for audio_decoder.h) */

#include "stream_control.h"
#include "input_ext-dec.h"

#include "mpeg_adec_generic.h"
#include "mpeg_adec.h"

#include "modules.h"
#include "modules_export.h"

#define ADEC_FRAME_SIZE (2*1152)

/*****************************************************************************
 * Local Prototypes
 *****************************************************************************/
static int   decoder_Probe ( probedata_t * );
static int   decoder_Run   ( decoder_config_t * );
static void  EndThread     ( adec_thread_t * );
static void  DecodeThread  ( adec_thread_t * );


/*****************************************************************************
 * Capabilities
 *****************************************************************************/
void _M( adec_getfunctions )( function_list_t * p_function_list )
{
    p_function_list->pf_probe = decoder_Probe;
    p_function_list->functions.dec.pf_run = decoder_Run;
}

/*****************************************************************************
 * Build configuration tree.
 *****************************************************************************/
MODULE_CONFIG_START
ADD_WINDOW( "Configuration for mpeg audio decoder module" )
    ADD_COMMENT( "Nothing to configure" )
MODULE_CONFIG_STOP

MODULE_INIT_START
    p_module->i_capabilities = MODULE_CAPABILITY_DEC;
    p_module->psz_longname = "Mpeg I layer 1/2 audio decoder";
MODULE_INIT_STOP

MODULE_ACTIVATE_START
    _M( adec_getfunctions )( &p_module->p_functions->dec );
MODULE_ACTIVATE_STOP

MODULE_DEACTIVATE_START
MODULE_DEACTIVATE_STOP

/*****************************************************************************
 * decoder_Probe: probe the decoder and return score
 *****************************************************************************/
static int decoder_Probe( probedata_t *p_data )
{
    if( p_data->i_type == MPEG1_AUDIO_ES || p_data->i_type == MPEG2_AUDIO_ES )
    {
        if( !TestCPU( CPU_CAPABILITY_FPU ) )
        {
            /* This can work but we'd really prefer libmad to take over. */
            return( 1 );
        }
        if( TestMethod( ADEC_MPEG_VAR, "builtin" ) )
        {
            return( 999 );
        }
        return( 100 );
    }
    return( 0 );
}

/*****************************************************************************
 * decoder_Run: initialize, go inside main loop, detroy
 *****************************************************************************/
static int decoder_Run ( decoder_config_t * p_config )
{
    adec_thread_t   * p_adec;
    
    intf_DbgMsg("mpeg_adec debug: thread launched, initializing.");
    
    /* Allocate the memory needed to store the thread's structure */
    if ( (p_adec = (adec_thread_t *)malloc (sizeof(adec_thread_t))) == NULL ) 
    {
        intf_ErrMsg ( "adec error: not enough memory for"
                      " adec_CreateThread() to create the new thread" );
        DecoderError( p_config->p_decoder_fifo );
        return 0;
    }
    
    /*
     * Initialize the thread properties
     */
    p_adec->p_config = p_config;
    p_adec->p_fifo = p_config->p_decoder_fifo;

    /* 
     * Initilize the banks
     */
    p_adec->bank_0.actual = p_adec->bank_0.v1;
    p_adec->bank_0.pos = 0;
    p_adec->bank_1.actual = p_adec->bank_1.v1;
    p_adec->bank_1.pos = 0;
    
    /*
     * Initialize bit stream 
     */
    p_adec->p_config->pf_init_bit_stream( &p_adec->bit_stream,
        p_adec->p_config->p_decoder_fifo, NULL, NULL );

    /* Create the audio output fifo */
    p_adec->p_aout_fifo = aout_CreateFifo( AOUT_ADEC_STEREO_FIFO, 2, 0, 0,
                                           ADEC_FRAME_SIZE, NULL );
    if ( p_adec->p_aout_fifo == NULL )
    {
        intf_ErrMsg("mpeg_adec error: cannot create audio output fifo");
        return -1;
    }

    intf_DbgMsg("mpeg_adec debug: thread initialized, decoding begins.");

    p_adec->i_sync = 0;

    /* Audio decoder thread's main loop */
    while( (!p_adec->p_fifo->b_die) && (!p_adec->p_fifo->b_error) )
    {
        DecodeThread( p_adec );
    }
    
    /* If b_error is set, the audio decoder thread enters the error loop */
    if( p_adec->p_fifo->b_error ) 
    {
        DecoderError( p_adec->p_fifo );
    }

    /* End of the audio decoder thread */
    EndThread( p_adec );

    return( 0 );
}

/*
 * Following finctions are local to this module
 */

/*****************************************************************************
 * DecodeThread: decodes a mpeg frame
 *****************************************************************************/
static void DecodeThread( adec_thread_t * p_adec )
{
    s16 * buffer;
    adec_sync_info_t sync_info;

    if( ! adec_SyncFrame (p_adec, &sync_info) )
    {
        p_adec->i_sync = 1;

        p_adec->p_aout_fifo->l_rate = sync_info.sample_rate;

        buffer = ((s16 *)p_adec->p_aout_fifo->buffer)
                    + (p_adec->p_aout_fifo->l_end_frame * ADEC_FRAME_SIZE);

        if( p_adec->p_fifo->p_first->i_pts )
        {
            p_adec->p_aout_fifo->date[p_adec->p_aout_fifo->l_end_frame] =
                p_adec->p_fifo->p_first->i_pts;
            p_adec->p_fifo->p_first->i_pts = 0;
        }
        else
        {
            p_adec->p_aout_fifo->date[p_adec->p_aout_fifo->l_end_frame] =
                LAST_MDATE;
        }

        if( adec_DecodeFrame (p_adec, buffer) )
        {
            /* Ouch, failed decoding... We'll have to resync */
            p_adec->i_sync = 0;
        }
        else
        {
            vlc_mutex_lock (&p_adec->p_aout_fifo->data_lock);

            p_adec->p_aout_fifo->l_end_frame =
                (p_adec->p_aout_fifo->l_end_frame + 1) & AOUT_FIFO_SIZE;
            vlc_cond_signal (&p_adec->p_aout_fifo->data_wait);
            vlc_mutex_unlock (&p_adec->p_aout_fifo->data_lock);
        }
    }
}

/*****************************************************************************
 * EndThread : audio decoder thread destruction
 *****************************************************************************
 * This function is called when the thread ends after a sucessful
 * initialization.
 *****************************************************************************/
static void EndThread ( adec_thread_t *p_adec )
{
    intf_DbgMsg ( "adec debug: destroying audio decoder thread %p", p_adec );

    /* If the audio output fifo was created, we destroy it */
    if ( p_adec->p_aout_fifo != NULL ) 
    {
        aout_DestroyFifo ( p_adec->p_aout_fifo );

        /* Make sure the output thread leaves the NextFrame() function */
        vlc_mutex_lock (&(p_adec->p_aout_fifo->data_lock));
        vlc_cond_signal (&(p_adec->p_aout_fifo->data_wait));
        vlc_mutex_unlock (&(p_adec->p_aout_fifo->data_lock));
    }
    /* Destroy descriptor */
    free( p_adec );

    intf_DbgMsg ("adec debug: audio decoder thread %p destroyed", p_adec);
}