/**
 * $Id: hddaudio.c 17043 2008-10-12 12:00:26Z schlaile $
 *
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * 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-1307, USA.
 *
 * Contributor(s): Peter Schlaile <peter [at] schlaile [dot] de> 2005
 *
 * ***** END GPL LICENSE BLOCK *****
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <fcntl.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef WITH_FFMPEG
#include <ffmpeg/avformat.h>
#include <ffmpeg/avcodec.h>
#include <ffmpeg/rational.h>
#if LIBAVFORMAT_VERSION_INT < (49 << 16)
#define FFMPEG_OLD_FRAME_RATE 1
#else
#define FFMPEG_CODEC_IS_POINTER 1
#endif
#endif

#include "MEM_guardedalloc.h"

#include "BIF_editsound.h"

#include "blendef.h"

#ifdef WITH_FFMPEG
extern void do_init_ffmpeg();
#endif

struct hdaudio {
	int sample_rate;
	int channels;
	int audioStream;

#ifdef WITH_FFMPEG
	char * filename;
	AVCodec *pCodec;
	AVFormatContext *pFormatCtx;
	AVCodecContext *pCodecCtx;
	int frame_position;
	int frame_duration;
	int frame_alloc_duration;
	int decode_pos;
	int frame_size;
	short * decode_cache;
	short * decode_cache_zero;
	short * resample_cache;
	int decode_cache_size;
	int target_channels;
	int target_rate;
	int resample_samples_written;
	int resample_samples_in;
	ReSampleContext *resampler;
#else
	

#endif
};

#ifdef WITH_FFMPEG
#ifdef FFMPEG_CODEC_IS_POINTER
static AVCodecContext* get_codec_from_stream(AVStream* stream)
{
	return stream->codec;
}
#else
static AVCodecContext* get_codec_from_stream(AVStream* stream)
{
	return &stream->codec;
}
#endif
#endif

struct hdaudio * sound_open_hdaudio(char * filename)
{
#ifdef WITH_FFMPEG
	struct hdaudio * rval;
	int            i, audioStream;

	AVCodec *pCodec;
	AVFormatContext *pFormatCtx;
	AVCodecContext *pCodecCtx;

	do_init_ffmpeg();

	if(av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)!=0) {
		return 0;
	}

	if(av_find_stream_info(pFormatCtx)<0) {
		av_close_input_file(pFormatCtx);
		return 0;
	}

	dump_format(pFormatCtx, 0, filename, 0);


        /* Find the first audio stream */
	audioStream=-1;
	for(i=0; i<pFormatCtx->nb_streams; i++)
		if(get_codec_from_stream(pFormatCtx->streams[i])
		   ->codec_type == CODEC_TYPE_AUDIO)
		{
			audioStream=i;
			break;
		}

	if(audioStream == -1) {
		av_close_input_file(pFormatCtx);
		return 0;
	}

	pCodecCtx = get_codec_from_stream(pFormatCtx->streams[audioStream]);

        /* Find the decoder for the audio stream */
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
	if(pCodec == NULL) {
		av_close_input_file(pFormatCtx);
		return 0;
	}

	if(avcodec_open(pCodecCtx, pCodec)<0) {
		av_close_input_file(pFormatCtx);
		return 0;
	}

	if (pCodecCtx->channels > 2) {
		fprintf(stderr, "Sorry, audio file has too many channels."
			" Will fix in future, but resampler doesn't support "
			"this.\n");
		avcodec_close(pCodecCtx);
		av_close_input_file(pFormatCtx);
		return 0;
	}

	rval = (struct hdaudio *)MEM_mallocN(sizeof(struct hdaudio), 
					     "hdaudio struct");

	rval->filename = strdup(filename);
	rval->sample_rate = pCodecCtx->sample_rate;
	rval->channels = pCodecCtx->channels;

	rval->pFormatCtx = pFormatCtx;
	rval->pCodecCtx = pCodecCtx;
	rval->pCodec = pCodec;
	rval->audioStream = audioStream;
	rval->frame_position = -10;

	rval->frame_duration = AV_TIME_BASE / 10;
	rval->frame_alloc_duration = AV_TIME_BASE;
	rval->decode_cache_size = 
		(long long) rval->sample_rate * rval->channels
		* rval->frame_alloc_duration / AV_TIME_BASE
		* 2;

	rval->decode_cache = (short*) MEM_mallocN(
		rval->decode_cache_size * sizeof(short)
		+ AVCODEC_MAX_AUDIO_FRAME_SIZE, 
		"hdaudio decode cache");
	rval->decode_cache_zero = rval->decode_cache;
	rval->decode_pos = 0;
	rval->target_channels = -1;
	rval->target_rate = -1;
	rval->resampler = 0;
	rval->resample_cache = 0;
	rval->resample_samples_written = 0;
	rval->resample_samples_in = 0;
	return rval;
#else
	return 0;
#endif
}

struct hdaudio * sound_copy_hdaudio(struct hdaudio * c)
{
#ifdef WITH_FFMPEG
	return sound_open_hdaudio(c->filename);
#else
	return 0;
#endif
}

long sound_hdaudio_get_duration(struct hdaudio * hdaudio, double frame_rate)
{
#ifdef WITH_FFMPEG
	return hdaudio->pFormatCtx->duration * frame_rate / AV_TIME_BASE;
#else
	return 0;
#endif
}

#ifdef WITH_FFMPEG
static void sound_hdaudio_extract_small_block(
	struct hdaudio * hdaudio, 
	short * target_buffer,
	int sample_position /* units of target_rate */,
	int target_rate,
	int target_channels,
	int nb_samples /* in target */)
{
	AVPacket packet;
	int frame_position;
	int frame_size = (long long) target_rate 
		* hdaudio->frame_duration / AV_TIME_BASE;
	int in_frame_size = (long long) hdaudio->sample_rate
		* hdaudio->frame_duration / AV_TIME_BASE;
	int rate_conversion = 
		(target_rate != hdaudio->sample_rate) 
		|| (target_channels != hdaudio->channels);
	int sample_ofs = target_channels * (sample_position % frame_size);

	frame_position = sample_position / frame_size; 

	if (hdaudio == 0) return;

	if (rate_conversion) {
		if (hdaudio->resampler && 
		    (hdaudio->target_rate != target_rate
		     || hdaudio->target_channels != target_channels)) {
			audio_resample_close(hdaudio->resampler);
			hdaudio->resampler = 0;
		}
		if (!hdaudio->resampler) {
			hdaudio->resampler = audio_resample_init(
				target_channels, hdaudio->channels,
				target_rate, hdaudio->sample_rate);
			hdaudio->target_rate = target_rate;
			hdaudio->target_channels = target_channels;
			if (hdaudio->resample_cache) {
				MEM_freeN(hdaudio->resample_cache);
			}
			

			hdaudio->resample_cache = (short*) MEM_mallocN(
				(long long) 
				hdaudio->target_channels 
				* frame_size * 2
				* sizeof(short), 
				"hdaudio resample cache");
			if (frame_position == hdaudio->frame_position) {
				hdaudio->resample_samples_in = 
					in_frame_size * 7 / 4;
				hdaudio->resample_samples_written
					= audio_resample(
						hdaudio->resampler,
						hdaudio->resample_cache,
						hdaudio->decode_cache_zero,
						in_frame_size * 7 / 4);
			}
		}
	}

	if (frame_position == hdaudio->frame_position + 1
	    && in_frame_size * hdaudio->channels <= hdaudio->decode_pos) {
		int bl_size = in_frame_size * hdaudio->channels;
		int decode_pos = hdaudio->decode_pos;
	       
		hdaudio->frame_position = frame_position;

		memmove(hdaudio->decode_cache,
			hdaudio->decode_cache + bl_size,
			(decode_pos - bl_size) * sizeof(short));
		
		decode_pos -= bl_size;

		if (decode_pos < hdaudio->decode_cache_size) {
			memset(hdaudio->decode_cache + decode_pos, 0,
			       (hdaudio->decode_cache_size - decode_pos) 
			       * sizeof(short));

			while(av_read_frame(
				      hdaudio->pFormatCtx, &packet) >= 0) {
				int data_size;
				int len;
				uint8_t *audio_pkt_data;
				int audio_pkt_size;
				
				if(packet.stream_index 
				   != hdaudio->audioStream) {
					av_free_packet(&packet);
					continue;
				}

				audio_pkt_data = packet.data;
				audio_pkt_size = packet.size;

				while (audio_pkt_size > 0) {
					len = avcodec_decode_audio(
						hdaudio->pCodecCtx, 
						hdaudio->decode_cache 
						+ decode_pos, 
						&data_size, 
						audio_pkt_data, 
						audio_pkt_size);
					if (len <= 0) {
						audio_pkt_size = 0;
						break;
					}
					
					audio_pkt_size -= len;
					audio_pkt_data += len;
					
					if (data_size <= 0) {
						continue;
					}
					
					decode_pos += data_size / sizeof(short);
					if (decode_pos + data_size
					    / sizeof(short)
					    > hdaudio->decode_cache_size) {
						break;
					}
				}
				av_free_packet(&packet);
				
				if (decode_pos + data_size / sizeof(short)
				    > hdaudio->decode_cache_size) {
					break;
				}
			}
		}

		if (rate_conversion) {
			int written = hdaudio->resample_samples_written
				* target_channels;
			int ofs = target_channels * frame_size;
			int recycle = written - ofs;
			int next_in = in_frame_size 
				+ (3.0/4.0 
				   - (double) recycle / (double) 
				   (frame_size * target_channels)
					) * in_frame_size;

			memmove(hdaudio->resample_cache,
				hdaudio->resample_cache + ofs,
			        recycle * sizeof(short));

			hdaudio->resample_samples_written
			       = audio_resample(
				       hdaudio->resampler,
				       hdaudio->resample_cache + recycle,
				       hdaudio->decode_cache_zero
				       + hdaudio->resample_samples_in
				       * hdaudio->channels
				       - bl_size,
				       next_in)
				+ recycle / target_channels;

			hdaudio->resample_samples_in = next_in;
		}

		hdaudio->decode_pos = decode_pos;
	}

	if (frame_position != hdaudio->frame_position) { 
		long decode_pos = 0;
		long long st_time = hdaudio->pFormatCtx
			->streams[hdaudio->audioStream]->start_time;
		double time_base = 
			av_q2d(hdaudio->pFormatCtx
			       ->streams[hdaudio->audioStream]->time_base);
		long long pos = (long long) frame_position * AV_TIME_BASE
			* hdaudio->frame_duration / AV_TIME_BASE;

		long long seek_pos;

		hdaudio->frame_position = frame_position;

		if (st_time == AV_NOPTS_VALUE) {
			st_time = 0;
		}

		pos += st_time * AV_TIME_BASE * time_base;

		/* seek a little bit before the target position,
		   (ffmpeg seek algorithm doesn't seem to work always as
		   specified...)
		*/

		seek_pos = pos - (AV_TIME_BASE
				 * hdaudio->frame_duration 
				  / AV_TIME_BASE / 10);
		if (seek_pos < 0) {
			seek_pos = pos;
		}

		av_seek_frame(hdaudio->pFormatCtx, -1, 
			      seek_pos, 
			      AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD);
		avcodec_flush_buffers(hdaudio->pCodecCtx);

		memset(hdaudio->decode_cache, 0,
		       hdaudio->decode_cache_size * sizeof(short));

		hdaudio->decode_cache_zero = hdaudio->decode_cache;

		while(av_read_frame(hdaudio->pFormatCtx, &packet) >= 0) {
			int data_size;
			int len;
			uint8_t *audio_pkt_data;
			int audio_pkt_size;

			if(packet.stream_index != hdaudio->audioStream) {
				av_free_packet(&packet);
				continue;
			}

			audio_pkt_data = packet.data;
			audio_pkt_size = packet.size;

			if (!hdaudio->decode_cache_zero 
			    && audio_pkt_size > 0) { 
				long long diff;

				if (packet.pts == AV_NOPTS_VALUE) {
					fprintf(stderr,
						"hdaudio: audio "
						"pts=NULL audio "
						"distortion!\n");
					diff = 0;
				} else {
					long long pts = packet.pts;
					long long spts = (long long) (
						pos / time_base / AV_TIME_BASE 
						+ 0.5);
					diff = spts - pts;
					if (diff < 0) {
						fprintf(stderr,
							"hdaudio: "
							"negative seek: "
							"%lld < %lld "
							"(pos=%lld) "
							"audio distortion!!\n",
							spts, pts, pos);
						diff = 0;
					}
				}


				diff *= hdaudio->sample_rate * time_base;
				diff *= hdaudio->channels;

				if (diff > hdaudio->decode_cache_size / 2) {
					fprintf(stderr,
						"hdaudio: audio "
						"diff too large!!\n");
					diff = 0;
				}

				hdaudio->decode_cache_zero
					= hdaudio->decode_cache + diff;
			}

			while (audio_pkt_size > 0) {
				len = avcodec_decode_audio(
					hdaudio->pCodecCtx, 
					hdaudio->decode_cache 
					+ decode_pos, 
					&data_size, 
					audio_pkt_data, 
					audio_pkt_size);
				if (len <= 0) {
					audio_pkt_size = 0;
					break;
				}
				
				audio_pkt_size -= len;
				audio_pkt_data += len;

				if (data_size <= 0) {
					continue;
				}
				
				decode_pos += data_size / sizeof(short);
				if (decode_pos + data_size
				    / sizeof(short)
				    > hdaudio->decode_cache_size) {
					break;
				}
			}
	
			av_free_packet(&packet);

			if (decode_pos + data_size / sizeof(short)
			    > hdaudio->decode_cache_size) {
				break;
			}
		}
		if (rate_conversion) {
			hdaudio->resample_samples_written
				= audio_resample(hdaudio->resampler,
						 hdaudio->resample_cache,
						 hdaudio->decode_cache_zero,
						 in_frame_size * 7 / 4);
			hdaudio->resample_samples_in = 
				in_frame_size * 7 / 4;
		}
		hdaudio->decode_pos = decode_pos;
	}

	memcpy(target_buffer, (rate_conversion 
			       ? hdaudio->resample_cache 
			       : hdaudio->decode_cache_zero) + sample_ofs, 
	       nb_samples * target_channels * sizeof(short));
}
#endif


void sound_hdaudio_extract(struct hdaudio * hdaudio, 
			   short * target_buffer,
			   int sample_position /* units of target_rate */,
			   int target_rate,
			   int target_channels,
			   int nb_samples /* in target */)
{
#ifdef WITH_FFMPEG
	long long max_samples = (long long) target_rate 
		* hdaudio->frame_duration / AV_TIME_BASE / 4;

	while (nb_samples > max_samples) {
		sound_hdaudio_extract_small_block(hdaudio, target_buffer,
						  sample_position,
						  target_rate,
						  target_channels,
						  max_samples);
		target_buffer += max_samples * target_channels;
		sample_position += max_samples;
		nb_samples -= max_samples;
	}
	if (nb_samples > 0) {
		sound_hdaudio_extract_small_block(hdaudio, target_buffer,
						  sample_position,
						  target_rate,
						  target_channels,
						  nb_samples);
	}
#else

#endif	
}
			   
void sound_close_hdaudio(struct hdaudio * hdaudio)
{
#ifdef WITH_FFMPEG

	if (hdaudio) {
		avcodec_close(hdaudio->pCodecCtx);
		av_close_input_file(hdaudio->pFormatCtx);
		MEM_freeN (hdaudio->decode_cache);
		if (hdaudio->resample_cache) {
			MEM_freeN(hdaudio->resample_cache);
		}
		free(hdaudio->filename);
		MEM_freeN (hdaudio);
	}
#else

#endif
}

