/* Network-Integrated Multimedia Middleware (NMM)
 * http://www.motama.com/nmm.html
 *  
 * Copyright (C) 2006-2007
 * Motama GmbH, Saarbruecken, Germany
 * http://www.motama.com
 *  
 * This file is part of NMM. 
 * 
 * NMM 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; only version 2 of the License.      
 * 
 * NMM 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 NMM; if not, write to the Free Software Foundation,    
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */


#ifndef _NMM_PLAYBACKNODE_HPP_
#define _NMM_PLAYBACKNODE_HPP_

#include "nmm/nmm_prefix.hpp"

#ifndef AFMT_AC3
#define AFMT_AC3      0x00000400      /* Dolby Digital AC3 */
#endif

#include "nmm/multimedia/TDevicePlugin.hpp"
#include "nmm/multimedia/sync/GenericBSyncSinkNode.hpp"

#include "nmm/utils/time/Clock.hpp"

#include "nmm/plugins/audio/interfaces/IAudioDeviceImpl.hpp"
#include "nmm/multimedia/interfaces/ITrackImpl.hpp"
#include "nmm/multimedia/interfaces/IMuteImpl.hpp"
#include "nmm/plugins/audio/oss/IFragmentSizeImpl.hpp"

namespace NMM {

  //! This class plays an audio stream to a given output device.
  /*!
   *  The PlaybackNode uses the OSS audio driver.
   *  The Node itself supports the following formats:
   *
   *  - Logarithmic mu-law encoding
   *  - Logarithmic A-law encoding
   *  - Linear encoding:
   *    -  8-bit, (un)signed
   *    - 16-bit, (un)signed, [big|little] endian
   *  - AC3
   *
   *  Possible sample rates: 8000, 11025, 22050, 44100, 48000, 96000 Hz
   *
   *  Possible channel numbers: 1, 2, 4, 5, 6
   *
   *  The actually supported formats may be only a subset of these depending on your
   *  sound hardware.
   *
   *  In this new version, much functionality is implemented in
   *  GenericSinkNode.
   */
  class PlaybackNode :
    public GenericBSyncSinkNode,
    protected IAudioDeviceImpl,
    protected ITrackImpl,
    protected IMuteImpl,
    public IFragmentSizeImpl
  {
  private:
    /* structures for direct ac3 output */
    struct syncframe {
      struct syncinfo {
        unsigned char syncword[2];
        unsigned char crc1[2];
        unsigned char code;
      } syncinfo;

      struct bsi {
        unsigned char bsidmod;
        unsigned char acmod;
      } bsi;
    };

    struct frmsize_s {
        unsigned short bit_rate;
        unsigned short frm_size[3];
    };

    struct ac3info {
      int bitrate, framesize, samplerate, bsmod;
    };

    unsigned char  iecbuf[6144];
    unsigned char* temp_ac3;
    unsigned int   temp_offset;
    unsigned int   temp_size;

    /* flag, if we should decode ac3 audio data in present buffer */
    bool decode_ac3;

    /* ac3 sync word */
    char sync[4];

    /* table that holds ac3 frame sizes */
    struct frmsize_s frmsizecod_tbl[64];

    //! Formats to check in the doInit method.
    static const int test_formats[];

    //! Channels to check in the doInit method.
    static const int test_channels[];

    //! Sample rates to check in the doInit method.
    static const int test_sample_rates[];

    //! Converts an OSS_Format into an NMM::Format.
    Format& getNMMFormat(const int oss_format, Format& nmm_format);

    //! Converts an NMM::Format into an OSS_Format.
    int getOSSFormat(const Format& nmm_format);

    //! The synchronization mode.
    /*!
     *  This mode describes how the number of unplayed bytes in the internal buffer is determined.
     *  This number describes the output delay, that is the time between writing data to 
     *  the audio device and hearing the corresponding sound.
     *  The modes GETODELAY and GETOSPACE are preferred. The OSS Documentation recommends
     *  the usage of GETODELAY. But this is only possible if the audio device is able to
     *  give an accurate buffer size. 
     *  Otherwise the system clock is used to compute an approximation for the unplayed 
     *  bytes in the internal buffer. This approximation can be very inaccurate: The soundcard's
     *  clock and the system clock usually don't have the same velocity.
     */
    enum SyncMode {
      GETODELAY,     //!< Use SNDCTL_DSP_GETODELAY to query the bytes in the internal buffer.
      GETOSPACE,     //!< Use SNDCTL_DSP_GETOSPACE to query the bytes in the internal buffer.
      SYSTEM_CLOCK   //!< Compute the bytes in the internal buffer with the system clock.
    };

    SyncMode sync_mode;  //!< The actual synchroniztion mode of the PlaybackNode.

    int sample_rate;                //!< The sample_rate (depends on input_format)
    int channels;                   //!< The number of channels (depends on input_format)
    int bit_per_sample;             //!< The bits per sample (depends on input format)
    int byte_per_sample;            //!< The bytes per sample (depends on input format)
    int byte_per_second;            //!< The bytes per second (depends on input format)
    double written_bytes;

    int fragment_argument;          
    int internal_buffer_size;
    Time internal_buffer_time;

    Clock clock;

    void setCLOEXEC(int fd);

  protected:
    string dev;              //!< The name of the sound device.
    string __mixer_device;
    mutable int audio_fd;    //!< File descriptor number pf the sound device.
    int mixer_fd;            //!filedescriptor for mixer device
    
    Result doSetFormat(const Format& format);
    
    /**
     * Initialize framesize table for direkt ac3 playback
     */
    void InitFramesizeTable();

    /**
     * extract some info from ac3 packet
     * \param buf pointer to input buffer
     * \param size size of input buffer
     * \param ai pointer to info structure where information is saved
     * \param skipped returns number of bytes that were skipped before ac3 sync word appeared
     * \returns 0, if information could be extracted, -1 otherwise
     */
    int ac3_iec958_parse_syncinfo( unsigned char* buf, int size, struct ac3info* ai, int* skipped );

    /**
     * convert ac3 frame to standard spdif output format
     * \param length length of the ac3 frame
     * \param data_type always 1 if AC3
     * \param big_endian 1, if data are encoded in big endian format
     * \param data pointer to ac3 buffer
     * \param out pointer to converted output buffer
     */
    void ac3_iec958_build_burst( int length, int data_type, int big_endian, unsigned char* data, unsigned char* out );



    //! The doFlush method from Node.
    /*!
     *  Flushes the audio device and checks the supported formats.
     *  \return - SUCCESS If the device could be flushed
     *          - FAILURE otherwise
     */
    virtual Result doFlush();
    
    //! The doInit method from Node.
    /*!
     *  Simply calls doInitInput
     */
    virtual Result doInit();

    //! The doInitOutput method from Node.
    /*!
     * In this method, the sound device is configured to the values determined in the formats.
     * \return SUCCESS if the device has been configured correctly, FAILURE otherwise.
     */
    virtual Result doInitOutput();
    
    //! The doDeinit method from Node.
    /*!
     *  Simply calls doDeinitInput
     */
    virtual Result doDeinit();
    
    //! Sets the output device.
    /** \warning obsolete method
      */
    Result oldSetDevice(string& );

    //! Gets the output device.
    /** \warning obsolete method
      */
    Result oldGetDevice(string& );
    
    //! The getOutputDelay - method from GenericSyncSinkNode.
    Interval getOutputDelay();

    //! The doStart method from Node.
    virtual Result doStart();
    
    //! The doStop method from Node.
    /*! 
     *  In this method the data in the internal buffer of the sound device will be played.
     *  The method returns when the internal buffer is empty. Depending on the fragment size,
     *  that means this method can take up to one second.
     */
    virtual Result doStop();
    
    //! The presentBuffer method from GenericSyncSinkNode.
    virtual Message* presentBuffer(Buffer *in_buffer);
    
    //! Event handler for debug 
    Result EventDebug(int&);
    
    //! startTrack from ITrack interface
    virtual Result startTrack(const string&);
    
    //! endTrack from ITrack interface
    virtual Result endTrack();

    //! started from ITrack interface
    virtual Result started(const Interval&);
    
    //! finished from ITrack interface
    virtual Result finished();
    
    //! new seek method
    Result eventSeek(Interval&, Time&, nmm_int32&);
    
    //! speed up or delay playback
    Result eventSpeedUp(int& speedup);
    
    /**
     * Set audio format.
     * This method configures the audio device to a given audio format.
     * It is called on activation and on format changes. It has been renamed to keep interface
     * and implementation seperate.
     * @param format NMM audio format
     * @return SUCCESS if the device has been configured correctly, FAILURE otherwise.
     */
    Result setFormatInternal(Format& input_format);
    
  public:
    //! The main constructor.
    /*!
     *  Can take a BufferManager to use instead of creating its own Allocator.
     *  \param dev the device to open.
     *  \param manager the buffer manager to use.
     */
    PlaybackNode(const char* name = "PlaybackNode",
		 const StreamQueue::Mode queue_mode = StreamQueue::MODE_SUSPEND,
		 const int size = StreamQueue::DEFAULT_SIZE);

    //! The main destructor.
    /*!
     *  Closes the audio device if it is still open.
     */
    ~PlaybackNode();

    void setLineInVolume( int vol );
    int getLineInVolume();
    
    //! Sets the output device.
    void setDevice(const string& ) throw (const DeviceNotFoundException&);
    
    //! Sets the mixer device
    void setMixerDevice(const string&) throw (const DeviceNotFoundException&);

    //! Sets the mixer volume element
    void setMixerVolumeElement(const string& value);
        
    //! Sets the output device.
    string getDevice() const;
    
    //! Gets the output device.
    string getMixerDevice() const;

    /**
     * IFragmentSize interface.
     */
    virtual nmm_int32 getFragmentSize();
    virtual void setFragmentSize(const nmm_int32& value); 

  protected:

    virtual void increaseVolume();

    virtual void decreaseVolume();

    virtual void setVolume(const nmm_int32& volume);

    virtual nmm_int32 getVolume();

    /**
     * Mutes an audio source or sink
     */
    virtual void mute();

    /**
     * Un-mutes an audio source or sink
     */
    virtual void unmute();
  };

  class PlaybackNodePlugin
    : public TDevicePlugin<PlaybackNode>
  {
  public:
    PlaybackNodePlugin(const string& node_name, int max_instance = -1);
    
  protected:
    virtual void checkForStandardAuxDevice(IDevice& device) throw();
    virtual void checkForAuxDevice(IDevice& device, unsigned device_num) throw();
  };
}

#include "nmm/nmm_suffix.hpp"

#endif // _NMM_PLAYBACKNODE_HPP_


