/*
 * audio-macos.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: audio-macos.c,v 1.2 2007/05/10 19:54:44 wachm Exp $
 */

/*
 * This file is based on MPlayers ao_macosx.c with the original copyright 
 * notice:
 * 
 *      Original Copyright (C) Timothy J. Wood - Aug 2000
 *
 */

#include "audio-macos.h"


#define AUDIODEB(out...) {printf("AUDIO-MACOS: ");printf(out);}


#ifndef AUDIODEB
#define AUDIODEB(out...)
#endif

/* This is large, but best (maybe it should be even larger).
 * CoreAudio supposedly has an internal latency in the order of 2ms */
#define NUM_BUFS 32

static cMacOsAudioOut *macosAO = NULL;

/**
 * \brief return number of free bytes in the buffer
 *    may only be called by mplayer's thread
 * \return minimum number of free bytes in buffer, value may change between
 *    two immediately following calls, and the real number of free bytes
 *    might actually be larger!
 */
int cMacOsAudioOut::buf_free() {
  int free = buf_read_pos - buf_write_pos - chunk_size;
  if (free < 0) free += buffer_len;
  return free;
}

/**
 * \brief return number of buffered bytes
 *    may only be called by playback thread
 * \return minimum number of buffered bytes, value may change between
 *    two immediately following calls, and the real number of buffered bytes
 *    might actually be larger!
 */
int cMacOsAudioOut::buf_used() {
  int used = buf_write_pos - buf_read_pos;
  if (used < 0) used += buffer_len;
  return used;
}

/**
 * \brief add data to ringbuffer
 */
int cMacOsAudioOut::write_buffer(unsigned char* data, int len){
  int first_len = buffer_len - buf_write_pos;
  int free = buf_free();
  if (len > free) {
          len = free;
          //AUDIODEB("write_buffer buffer full!\n");
  }
  if (first_len > len) first_len = len;
  // till end of buffer
  memcpy (&buffer[buf_write_pos], data, first_len);
  if (len > first_len) { // we have to wrap around
    // remaining part from beginning of buffer
    //AUDIODEB("write_buffer wrap around len %d first_len %d\n",len,first_len);
    memcpy (buffer, &data[first_len], len - first_len);
  }
  buf_write_pos = (buf_write_pos + len) % buffer_len;
  return len;
}

/**
 * \brief remove data from ringbuffer
 */
int cMacOsAudioOut::read_buffer(unsigned char* data,int len){
  int first_len = buffer_len - buf_read_pos;
  int buffered = buf_used();
  if (len > buffered) len = buffered;
  if (first_len > len) first_len = len;
  // till end of buffer
  memcpy (data, &buffer[buf_read_pos], first_len);
  if (len > first_len) { // we have to wrap around
    // remaining part from beginning of buffer
    //AUDIODEB("read_buffer wrap around len %d first_len %d\n",len,first_len);
    memcpy (&data[first_len], buffer, len - first_len);
  }
  buf_read_pos = (buf_read_pos + len) % buffer_len;
  return len;
}

OSStatus theRenderProc(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData) {
       return  (macosAO ? macosAO->Render(inNumFrames,ioData) : -1 );
};


OSStatus cMacOsAudioOut::Render(UInt32 inNumFrames, AudioBufferList *ioData) {
        int amt=buf_used();
        int req=(inNumFrames)*packetSize;

	if(amt>req)
 		amt=req;

	if(amt)
		read_buffer((unsigned char *)ioData->mBuffers[0].mData, amt);
	else Pause();
	ioData->mBuffers[0].mDataByteSize = amt;

 	return noErr;
}

cMacOsAudioOut::cMacOsAudioOut() : cAudioOut() {
        ComponentDescription desc; 
        Component comp; 
        OSStatus err;

        if (macosAO) {
                fprintf(stderr,"MacOsAudioOut already initialized!\n");
                exit(-1);
        };
        macosAO=this;	
        buffer = NULL;

        desc.componentType = kAudioUnitType_Output;
	desc.componentSubType = kAudioUnitSubType_DefaultOutput;
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;
				
	comp = FindNextComponent(NULL, &desc);  //Finds an component that meets the desc spec's
	if (comp == NULL) {
		fprintf(stderr, "Unable to find Output Unit component\n");
		exit(-1);
	}
		
	err = OpenAComponent(comp, &(theOutputUnit));  //gains access to the services provided by the component
	if (err) {
		fprintf(stderr,"Unable to open Output Unit component (err=%d)\n", err);
		exit(-1);
	}

	// Initialize AudioUnit 
	err = AudioUnitInitialize(theOutputUnit);
	if (err) {
		fprintf(stderr, "Unable to initialize Output Unit component (err=%d)\n", err);
		exit(-1);
	}
        scale_Factor=0x7FFF;
}


cMacOsAudioOut::~cMacOsAudioOut() {
        AudioOutputUnitStop(theOutputUnit);
        AudioUnitUninitialize(theOutputUnit);
        CloseComponent(theOutputUnit);

        free(buffer);
}

int cMacOsAudioOut::SetParams(SampleContext &context) {

        if (currContext.samplerate == context.samplerate &&
                        currContext.channels == context.channels ) {
                context=currContext;
                return 0;
        };
        AUDIODEB("alsa-macos : SetParams samplerate %d channels %d\n",context.samplerate,context.channels);
        currContext=context;

        AudioStreamBasicDescription inDesc, outDesc;
        AURenderCallbackStruct renderCallback;
        OSStatus err;
        UInt32 size, maxFrames;

	// Build Description for the input format
	inDesc.mSampleRate=currContext.samplerate;
	inDesc.mFormatID=kAudioFormatLinearPCM;
	inDesc.mChannelsPerFrame=currContext.channels;
        inDesc.mBitsPerChannel=16;

        inDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked;
        /*if((format&AF_FORMAT_POINT_MASK)==AF_FORMAT_F) {
	// float
                inDesc.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked;
        }
        else if((format&AF_FORMAT_SIGN_MASK)==AF_FORMAT_SI) {
                // signed int
                inDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked;
        }
        else {
                // unsigned int
                inDesc.mFormatFlags = kAudioFormatFlagIsPacked;
        }*/

#ifdef CPU_BIGENDIAN 
        inDesc.mFormatFlags |= kAudioFormatFlagIsBigEndian; //Intel processors???
#endif
        inDesc.mFramesPerPacket = 1;
        packetSize = inDesc.mBytesPerPacket = inDesc.mBytesPerFrame = inDesc.mFramesPerPacket*currContext.channels*(inDesc.mBitsPerChannel/8);


	size =  sizeof(AudioStreamBasicDescription);
	err = AudioUnitSetProperty(theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &inDesc, size);

	if (err) {
		printf( "Unable to set the input format (err=%d)\n", err);
		exit(-1);
	}

	size = sizeof(UInt32);
	err = AudioUnitGetProperty(theOutputUnit, kAudioDevicePropertyBufferSize, kAudioUnitScope_Input, 0, &maxFrames, &size);
	
	if (err) {
		printf( "AudioUnitGetProperty returned %d when getting kAudioDevicePropertyBufferSize\n", (int)err);
		exit(-1);
	}

	chunk_size = maxFrames;//*inDesc.mBytesPerFrame;
	printf("%5d chunk size\n", (int)chunk_size);
    
	num_chunks = NUM_BUFS;
        buffer_len = (num_chunks + 1) * chunk_size;
        buffer = buffer ? (unsigned char *)realloc(buffer,(num_chunks + 1)*chunk_size)
			: (unsigned char *)calloc(num_chunks + 1, chunk_size);

        buf_read_pos=0;
	buf_write_pos=0;
    
/*	
        ao_data.samplerate = inDesc.mSampleRate;
        ao_data.channels = inDesc.mChannelsPerFrame;
        ao_data.outburst = ao_data.buffersize = ao->chunk_size;
        ao_data.bps = ao_data.samplerate * inDesc.mBytesPerFrame;
*/
        renderCallback.inputProc = theRenderProc;
        renderCallback.inputProcRefCon = 0;
        err = AudioUnitSetProperty(theOutputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct));
	if (err) {
		fprintf(stderr, "Unable to set the render callback (err=%d)\n", err);
		exit(-1);
	}

        return 0;
}

/* stop playing, keep buffers (for pause) */
void cMacOsAudioOut::Pause() {
  OSErr status=noErr;

  /* stop callback */
  status=AudioOutputUnitStop(theOutputUnit);
  if (status)
    fprintf(stderr, "AudioOutputUnitStop returned %d\n",(int)status);
}


/* resume playing, after audio_pause() */
void cMacOsAudioOut::Play() {
  OSErr status=noErr;
  
  status=AudioOutputUnitStart(theOutputUnit);
  if (status)
    fprintf(stderr,"AudioOutputUnitStart returned %d\n",(int)status);
}

void cMacOsAudioOut::Write(uchar *Data, int Length) {
        Play();
        Scale((int16_t*)Data,Length/2,scale_Factor);
        while (Length >0) {
                int len = write_buffer(Data,Length);
                Data +=len;
                Length-=len;
                usleep(13000);
        }
};

int cMacOsAudioOut::GetDelay() {
        if ( ! currContext.samplerate*currContext.channels )
                return -1;

        int res = buffer_len - chunk_size - buf_free();
        res = res*10000 / (currContext.samplerate*currContext.channels*2) ;
        //AUDIODEB("GetDelay %d\n",res);
        return res;
};

void cMacOsAudioOut::SetVolume( int vol ) {
        scale_Factor = CalcScaleFactor(vol);
};
