/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / FFMPEG module
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "ffmpeg_in.h"
#include <gpac/avparse.h>

#ifndef FFMPEG_OLD_HEADERS

#if (LIBAVCODEC_VERSION_MAJOR <= 52) && (LIBAVCODEC_VERSION_MINOR <= 20)
#undef USE_AVCODEC2
#else
#define USE_AVCODEC2	1
#endif

#else
#undef USE_AVCODEC2
#endif



/**
 * Allocates data for FFMPEG decoding
 * \param oldBuffer The oldBuffer (freed if not NULL)
 * \param size Size to allocate (will use extra padding for real size)
 * \return The newly allocated buffer
 */
static char * ffmpeg_realloc_buffer(char * oldBuffer, u32 size){
	char * buffer;
	/* Size of buffer must be larger, see avcodec_decode_video2 documentation */
	u32 allocatedSz = sizeof( char ) * (FF_INPUT_BUFFER_PADDING_SIZE + size);
	if (oldBuffer)
		gf_free(oldBuffer);
	buffer = gf_malloc( allocatedSz );
	if (buffer)
		memset( buffer, 0, allocatedSz);
	return buffer;
}


static AVCodec *ffmpeg_get_codec(u32 codec_4cc)
{
	char name[5];
	AVCodec *codec;
	strcpy(name, gf_4cc_to_str(codec_4cc));

	codec = avcodec_find_decoder_by_name(name);
	if (!codec) {
		strupr(name);
		codec = avcodec_find_decoder_by_name(name);
		if (!codec) {
			strlwr(name);
			codec = avcodec_find_decoder_by_name(name);
		}
	}
	/*custom mapings*/
	if (!codec) {
		if (!stricmp(name, "s263")) codec = avcodec_find_decoder_by_name("h263");
		else if (!stricmp(name, "samr") || !stricmp(name, "amr ")) codec = avcodec_find_decoder_by_name("amr_nb");
		else if (!stricmp(name, "sawb")) codec = avcodec_find_decoder_by_name("amr_wb");
	}
	return codec;
}


static void FFDEC_LoadDSI(FFDec *ffd, GF_BitStream *bs, AVCodec *codec, AVCodecContext *ctx, Bool from_ff_demux)
{
	u32 dsi_size;
	if (!codec) return;

	dsi_size = (u32) gf_bs_available(bs);
	if (!dsi_size) return;

	/*demuxer is ffmpeg, extra data can be copied directly*/
	if (from_ff_demux) {
		if (ctx->extradata)
			gf_free(ctx->extradata);
		ctx->extradata_size = dsi_size;
		ctx->extradata = ffmpeg_realloc_buffer(ctx->extradata, ctx->extradata_size);
		gf_bs_read_data(bs, ctx->extradata, ctx->extradata_size);
		return;
	}

	switch (codec->id) {
	case CODEC_ID_SVQ3:
	{
		u32 at_type, size;
		size = gf_bs_read_u32(bs);
		/*there should be an 'SMI' entry*/
		at_type = gf_bs_read_u32(bs);
		if (at_type == GF_4CC('S', 'M', 'I', ' ')) {
			if (ctx->extradata)
				gf_free(ctx->extradata);
			ctx->extradata_size = 0x5a + size;
			ctx->extradata = ffmpeg_realloc_buffer(ctx->extradata, ctx->extradata_size);
			strcpy(ctx->extradata, "SVQ3");
			gf_bs_read_data(bs, (unsigned char *)ctx->extradata + 0x5a, size);
		}
	}
		break;
	default:
		if (ctx->extradata)
			gf_free(ctx->extradata);
		ctx->extradata_size = dsi_size;
		ctx->extradata = ffmpeg_realloc_buffer(ctx->extradata, ctx->extradata_size);
		gf_bs_read_data(bs, ctx->extradata, ctx->extradata_size);
		break;
	}
}

static GF_Err FFDEC_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd)
{
	u32 codec_id = 0;
	int gotpic;
	GF_BitStream *bs;
	AVCodecContext **ctx;
	AVCodec **codec;
	AVFrame **frame;

#ifndef GPAC_DISABLE_AV_PARSERS
	GF_M4VDecSpecInfo dsi;
	GF_Err e;
#endif
	FFDec *ffd = (FFDec *)plug->privateStack;
	if (esd->decoderConfig->upstream) return GF_NOT_SUPPORTED;
	if (!ffd->oti) return GF_NOT_SUPPORTED;



	/*locate any auxiliary video data descriptor on this stream*/
	if (esd->dependsOnESID) {
		u32 i = 0;
		GF_Descriptor *d = NULL;
		if (esd->dependsOnESID != ffd->base_ES_ID) return GF_NOT_SUPPORTED;
		while ((d = gf_list_enum(esd->extensionDescriptors, &i))) {
			if (d->tag == GF_ODF_AUX_VIDEO_DATA) break;
		}
		if (!d) return GF_NOT_SUPPORTED;

		ffd->depth_ES_ID = esd->ESID;
		ctx = &ffd->depth_ctx;
		codec = &ffd->depth_codec;
		frame = &ffd->depth_frame;
	} else {
		if (ffd->base_ES_ID) return GF_NOT_SUPPORTED;
		ffd->base_ES_ID = esd->ESID;
		ctx = &ffd->base_ctx;
		codec = &ffd->base_codec;
		frame = &ffd->base_frame;
	}
	if (!(*ctx)){
	  *ctx = avcodec_alloc_context();
	}

	/*private FFMPEG DSI*/
	if (ffd->oti == GPAC_OTI_MEDIA_FFMPEG) {
		bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ);
		codec_id = gf_bs_read_u32(bs);
		if (ffd->st==GF_STREAM_AUDIO) {
			(*ctx)->codec_type = AVMEDIA_TYPE_AUDIO;
			(*ctx)->sample_rate = gf_bs_read_u32(bs);
			(*ctx)->channels = gf_bs_read_u16(bs);
			(*ctx)->frame_size = gf_bs_read_u16(bs);
			/*bits_per_sample */gf_bs_read_u8(bs);
			/*num_frames_per_au*/ gf_bs_read_u8(bs);

			/*ffmpeg specific*/
			(*ctx)->block_align = gf_bs_read_u16(bs);
		} else if (ffd->st==GF_STREAM_VISUAL) {
			(*ctx)->codec_type = AVMEDIA_TYPE_VIDEO;
			(*ctx)->width = gf_bs_read_u16(bs);
			(*ctx)->height = gf_bs_read_u16(bs);
		}
		(*ctx)->bit_rate = gf_bs_read_u32(bs);
		(*ctx)->codec_tag = gf_bs_read_u32(bs);
		ffd->raw_pix_fmt = gf_bs_read_u32(bs);

		*codec = avcodec_find_decoder(codec_id);
		FFDEC_LoadDSI(ffd, bs, *codec, *ctx, 1);
		gf_bs_del(bs);
	}
	/*private QT DSI*/
	else if (ffd->oti == GPAC_OTI_MEDIA_GENERIC) {
		bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ);
		codec_id = gf_bs_read_u32(bs);
		if (ffd->st==GF_STREAM_AUDIO) {
			(*ctx)->codec_type = AVMEDIA_TYPE_AUDIO;
			(*ctx)->sample_rate = gf_bs_read_u32(bs);
			(*ctx)->channels = gf_bs_read_u16(bs);
			(*ctx)->frame_size = gf_bs_read_u16(bs);
			/*bits_per_sample */ gf_bs_read_u8(bs);
			/*num_frames_per_au*/ gf_bs_read_u8(bs);
			/*just in case...*/
			if (codec_id == GF_4CC('a', 'm', 'r', ' ')) {
			  (*ctx)->sample_rate = 8000;
			  (*ctx)->channels = 1;
			  (*ctx)->frame_size = 160;
			}
		} else if (ffd->st==GF_STREAM_VISUAL) {
			(*ctx)->codec_type = AVMEDIA_TYPE_VIDEO;
			(*ctx)->width = gf_bs_read_u16(bs);
			(*ctx)->height = gf_bs_read_u16(bs);
		}
		(*codec) = ffmpeg_get_codec(codec_id);
		FFDEC_LoadDSI(ffd, bs, *codec, *ctx, 0);
		gf_bs_del(bs);
	}
	/*use std MPEG-4 st/oti*/
	else {
		u32 codec_id = 0;
		if (ffd->st==GF_STREAM_VISUAL) {
			(*ctx)->codec_type = AVMEDIA_TYPE_VIDEO;
			switch (ffd->oti) {
			case GPAC_OTI_VIDEO_MPEG4_PART2:
				codec_id = CODEC_ID_MPEG4;
				break;
			case GPAC_OTI_VIDEO_AVC:
				codec_id = CODEC_ID_H264;
				/*ffmpeg H264/AVC needs that*/
				//(*ctx)->codec_tag = 0x31637661;
				break;
			case GPAC_OTI_VIDEO_MPEG1:
			case GPAC_OTI_VIDEO_MPEG2_SIMPLE:
			case GPAC_OTI_VIDEO_MPEG2_MAIN:
			case GPAC_OTI_VIDEO_MPEG2_SNR:
			case GPAC_OTI_VIDEO_MPEG2_SPATIAL:
			case GPAC_OTI_VIDEO_MPEG2_HIGH:
			case GPAC_OTI_VIDEO_MPEG2_422:
				codec_id = CODEC_ID_MPEG2VIDEO;
				break;
			case GPAC_OTI_IMAGE_JPEG:
				codec_id = CODEC_ID_MJPEG;
				ffd->is_image=1;
				break;
			case 0xFF:
				codec_id = CODEC_ID_SVQ3;
				break;
			}
		} else if (ffd->st==GF_STREAM_AUDIO) {
			(*ctx)->codec_type = AVMEDIA_TYPE_AUDIO;
			switch (ffd->oti) {
			case GPAC_OTI_AUDIO_MPEG2_PART3:
			case GPAC_OTI_AUDIO_MPEG1:
				(*ctx)->frame_size = 1152;
				codec_id = CODEC_ID_MP2;
				break;
			}
		}
		else if ((ffd->st==GF_STREAM_ND_SUBPIC) && (ffd->oti==0xe0)) {
			codec_id = CODEC_ID_DVD_SUBTITLE;
		}
		*codec = avcodec_find_decoder(codec_id);
	}
	/*should never happen*/
	if (! (*codec)) return GF_OUT_OF_MEM;

	/*setup MPEG-4 video streams*/
	if ((ffd->st==GF_STREAM_VISUAL)) {
		/*for all MPEG-4 variants get size*/
		if ((ffd->oti==GPAC_OTI_VIDEO_MPEG4_PART2) || (ffd->oti == GPAC_OTI_VIDEO_AVC)) {
			/*if not set this may be a remap of non-mpeg4 transport (eg, transport on MPEG-TS) where
			the DSI is carried in-band*/
			if (esd->decoderConfig->decoderSpecificInfo->data) {

				/*for regular MPEG-4, try to decode and if this fails try H263 decoder at first frame*/
				if (ffd->oti==GPAC_OTI_VIDEO_MPEG4_PART2) {
#ifndef GPAC_DISABLE_AV_PARSERS
					e = gf_m4v_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
					if (e) return e;
					if (dsi.width%2) dsi.width++;
					if (dsi.height%2) dsi.height++;
					(*ctx)->width = dsi.width;
					(*ctx)->height = dsi.height;
					if (!dsi.width && !dsi.height) ffd->check_short_header = 1;
					ffd->previous_par = (dsi.par_num<<16) | dsi.par_den;
					ffd->no_par_update = 1;
#endif
				} else if (ffd->oti==GPAC_OTI_VIDEO_AVC) {
					ffd->check_h264_isma = 1;
				}

				/*setup dsi for FFMPEG context BEFORE attaching decoder (otherwise not proper init)*/
				(*ctx)->extradata = ffmpeg_realloc_buffer((*ctx)->extradata, esd->decoderConfig->decoderSpecificInfo->dataLength);
				if ((*ctx)->extradata){
					memcpy((*ctx)->extradata, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength);
					(*ctx)->extradata_size = esd->decoderConfig->decoderSpecificInfo->dataLength;
				} else {
					/* out of mem ? */
					(*ctx)->extradata_size = 0;
				}
			}
		}
		*frame = avcodec_alloc_frame();
	}
	if (codec_id == CODEC_ID_RAWVIDEO) {
		(*ctx)->codec_id = CODEC_ID_RAWVIDEO;
		(*ctx)->pix_fmt = ffd->raw_pix_fmt;
		if ((*ctx)->extradata && strstr((*ctx)->extradata, "BottomUp")) ffd->flipped = 1;
	} else {
		if (avcodec_open((*ctx), (*codec) )<0) return GF_NON_COMPLIANT_BITSTREAM;
	}

	/*setup audio streams*/
	if (ffd->st==GF_STREAM_AUDIO) {
		if ((*codec)->id == CODEC_ID_MP2) {
			(*ctx)->frame_size = ((*ctx)->sample_rate > 24000) ? 1152 : 576;
		}
		/*may be 0 (cfg not known yet)*/
		ffd->out_size = (*ctx)->channels * (*ctx)->frame_size * 2 /*16 / 8*/;
		if (!(*ctx)->sample_rate) (*ctx)->sample_rate = 44100;
		if (!(*ctx)->channels) (*ctx)->channels = 2;
	} else {
		switch ((*codec)->id) {
		case CODEC_ID_MJPEG:
		case CODEC_ID_MJPEGB:
		case CODEC_ID_LJPEG:
#if (LIBAVCODEC_VERSION_INT > AV_VERSION_INT(51, 20, 0))
		case CODEC_ID_GIF:
#endif
		case CODEC_ID_RAWVIDEO:
			ffd->pix_fmt = GF_PIXEL_RGB_24;
			break;

		case CODEC_ID_DVD_SUBTITLE:
			*frame = avcodec_alloc_frame();
#ifdef USE_AVCODEC2
			{
			  AVPacket pkt;
			  av_init_packet(&pkt);
			  pkt.data = esd->decoderConfig->decoderSpecificInfo->data;
			  pkt.size = esd->decoderConfig->decoderSpecificInfo->dataLength;
			  avcodec_decode_video2((*ctx), *frame, &gotpic, &pkt);
			}
#else
			avcodec_decode_video((*ctx), *frame, &gotpic, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength);
#endif
			ffd->pix_fmt = GF_PIXEL_YV12;
			break;
		default:
			ffd->pix_fmt = GF_PIXEL_YV12;
			break;
		}

		ffd->out_pix_fmt = ffd->pix_fmt;

		if (ffd->out_pix_fmt == GF_PIXEL_YV12) {
			if (ffd->depth_codec) {
				ffd->out_size = (*ctx)->width * (*ctx)->height * 5 / 2;
				ffd->out_pix_fmt = GF_PIXEL_YUVD;
				ffd->yuv_size = (*ctx)->width * (*ctx)->height * 3 / 2;
			} else {
				ffd->out_size = (*ctx)->width * (*ctx)->height * 3 / 2;
			}
		} else {
			ffd->out_size = (*ctx)->width * (*ctx)->height * 3;
		}
	}
	return GF_OK;
}
static GF_Err FFDEC_DetachStream(GF_BaseDecoder *plug, u16 ES_ID)
{
	AVCodecContext **ctx;
	AVCodec **codec;
#ifdef FFMPEG_SWSCALE
	struct SwsContext **sws;
#endif
	FFDec *ffd = (FFDec *)plug->privateStack;

	if (ffd->base_ES_ID==ES_ID) {
		ffd->base_ES_ID = 0;
		codec = &ffd->base_codec;
		ctx = &ffd->base_ctx;
#ifdef FFMPEG_SWSCALE
		sws = &ffd->base_sws;
#endif
	} else if (ffd->depth_ES_ID==ES_ID) {
		ffd->depth_ES_ID = 0;
		codec = &ffd->depth_codec;
		ctx = &ffd->depth_ctx;
#ifdef FFMPEG_SWSCALE
		sws = &ffd->depth_sws;
#endif
	} else {
		return GF_OK;
	}

	if (*ctx) {
		if ((*ctx)->extradata) gf_free((*ctx)->extradata);
		(*ctx)->extradata = NULL;
		if ((*ctx)->codec) avcodec_close((*ctx));
		*ctx = NULL;
	}
	*codec = NULL;

#ifdef FFMPEG_SWSCALE
	if (*sws) {
		sws_freeContext(*sws);
		*sws = NULL;
	}
#endif
	return GF_OK;
}

static GF_Err FFDEC_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *capability)
{
	FFDec *ffd = (FFDec *)plug->privateStack;

	/*base caps*/
	switch (capability->CapCode) {
	/*ffmpeg seems quite reliable*/
	case GF_CODEC_RESILIENT:
		capability->cap.valueInt = 1;
		return GF_OK;
	case GF_CODEC_PADDING_BYTES:
		capability->cap.valueInt = FF_INPUT_BUFFER_PADDING_SIZE;
		return GF_OK;
	case GF_CODEC_REORDER:
		capability->cap.valueInt = 1;
		return GF_OK;
	}

	if (!ffd->base_ctx) {
		capability->cap.valueInt = 0;
		return GF_OK;
	}

	/*caps valid only if stream attached*/
	switch (capability->CapCode) {
	case GF_CODEC_OUTPUT_SIZE:
		capability->cap.valueInt = ffd->out_size;
		break;
	case GF_CODEC_SAMPLERATE:
		capability->cap.valueInt = ffd->base_ctx->sample_rate;
		break;
	case GF_CODEC_NB_CHAN:
		capability->cap.valueInt = ffd->base_ctx->channels;
		break;
	case GF_CODEC_BITS_PER_SAMPLE:
		capability->cap.valueInt = 16;
		break;
	case GF_CODEC_BUFFER_MIN:
		capability->cap.valueInt = (ffd->st==GF_STREAM_AUDIO) ? 4 : 1;
		break;
	case GF_CODEC_BUFFER_MAX:
	  /*for audio let the systems engine decide since we may have very large block size (1 sec with some QT movies)*/
		capability->cap.valueInt = (ffd->st==GF_STREAM_AUDIO) ? 0 : (ffd->is_image ? 1 : 4);
		break;
	/*by default AAC access unit lasts num_samples (timescale being sampleRate)*/
	case GF_CODEC_CU_DURATION:
		capability->cap.valueInt =  (ffd->st==GF_STREAM_AUDIO) ? ffd->base_ctx->frame_size : 0;
		break;
	case GF_CODEC_WIDTH:
		capability->cap.valueInt = ffd->base_ctx->width;
		break;
	case GF_CODEC_HEIGHT:
		capability->cap.valueInt = ffd->base_ctx->height;
		break;
	case GF_CODEC_STRIDE:
		capability->cap.valueInt = ffd->base_ctx->width;
		if (ffd->out_pix_fmt==GF_PIXEL_RGB_24) capability->cap.valueInt *= 3;
		break;
	case GF_CODEC_FPS:
		capability->cap.valueFloat = 30.0f;
		break;
	case GF_CODEC_PAR:
		capability->cap.valueInt = ffd->previous_par;
		break;
	case GF_CODEC_PIXEL_FORMAT:
		if (ffd->base_ctx->width) capability->cap.valueInt = ffd->out_pix_fmt;
		break;
	/*ffmpeg performs frame reordering internally*/
	case GF_CODEC_REORDER:
		capability->cap.valueInt = 1;
		break;
	case GF_CODEC_WAIT_RAP:
		//ffd->ctx->hurry_up = 5;
		break;
	case GF_CODEC_CHANNEL_CONFIG:
		/*currently unused in ffmpeg*/
		if (ffd->base_ctx->channels==1) {
			capability->cap.valueInt = GF_AUDIO_CH_FRONT_CENTER;
		} else {
			capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT;
		}
		break;

	case GF_CODEC_PADDING_BYTES:
		capability->cap.valueInt = FF_INPUT_BUFFER_PADDING_SIZE;
		break;
	default:
		capability->cap.valueInt = 0;
		break;
	}
	return GF_OK;
}

static GF_Err FFDEC_SetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability capability)
{
	FFDec *ffd = (FFDec *)plug->privateStack;
	assert(plug);
	assert( ffd );
	switch (capability.CapCode) {
	case GF_CODEC_WAIT_RAP:
		ffd->frame_start = 0;
		if (ffd->st==GF_STREAM_VISUAL) {
			if (ffd->base_ctx && ffd->base_ctx->codec) avcodec_flush_buffers(ffd->base_ctx);
			if (ffd->depth_ctx && ffd->depth_ctx->codec) avcodec_flush_buffers(ffd->depth_ctx);
		}
		return GF_OK;
	default:
		/*return unsupported to avoid confusion by the player (like color space changing ...) */
		return GF_NOT_SUPPORTED;
	}
}

static GF_Err FFDEC_ProcessData(GF_MediaDecoder *plug,
		char *inBuffer, u32 inBufferLength,
		u16 ES_ID,
		char *outBuffer, u32 *outBufferLength,
		u8 PaddingBits, u32 mmlevel)
{
#ifdef USE_AVCODEC2
	AVPacket pkt;
#endif
	AVPicture pict;
	u32 pix_out;
	s32 w, h, gotpic;
	u32 outsize;
	AVCodecContext *ctx;
	AVCodec **codec;
	AVFrame *frame;
#ifdef FFMPEG_SWSCALE
	struct SwsContext **cached_sws;
#endif
	FFDec *ffd = plug->privateStack;
#ifdef FFMPEG_SWSCALE
	cached_sws = &(ffd->base_sws);
#endif

	if (!ES_ID || (ffd->base_ES_ID==ES_ID)) {
		ctx = ffd->base_ctx;
		codec = &ffd->base_codec;
		frame = ffd->base_frame;
	} else if (ffd->depth_ES_ID==ES_ID) {
		ctx = ffd->depth_ctx;
		codec = &ffd->depth_codec;
		frame = ffd->depth_frame;
	} else {
		return GF_BAD_PARAM;
	}


	/*WARNING: this breaks H264 (and maybe others) decoding, disabled for now*/
#if 0
	if (!ctx->hurry_up) {
		switch (mmlevel) {
		case GF_CODEC_LEVEL_SEEK:
		case GF_CODEC_LEVEL_DROP:
			/*skip as much as possible*/
			ctx->hurry_up = 5;
			break;
		case GF_CODEC_LEVEL_VERY_LATE:
		case GF_CODEC_LEVEL_LATE:
			/*skip B-frames*/
			ctx->hurry_up = 1;
			break;
		default:
			ctx->hurry_up = 0;
			break;
		}
	}
#endif

#ifdef USE_AVCODEC2
	av_init_packet(&pkt);
	pkt.data = inBuffer;
	pkt.size = inBufferLength;
#endif
	/*audio stream*/
	if (ffd->st==GF_STREAM_AUDIO) {
		s32 len;
		u32 buf_size = (*outBufferLength);
		(*outBufferLength) = 0;

		/*seeking don't decode*/
		if (!inBuffer || (mmlevel == GF_CODEC_LEVEL_SEEK)) {
			*outBufferLength = 0;
			ffd->frame_start = 0;
			return GF_OK;
		}
		if (ffd->frame_start>inBufferLength) ffd->frame_start = 0;

redecode:
		gotpic = AVCODEC_MAX_AUDIO_FRAME_SIZE;
#ifdef USE_AVCODEC2
		len = avcodec_decode_audio3(ctx, (short *)ffd->audio_buf, &gotpic, &pkt);
#else
		len = avcodec_decode_audio2(ctx, (short *)ffd->audio_buf, &gotpic, inBuffer + ffd->frame_start, inBufferLength - ffd->frame_start);
#endif
		if (len<0) { ffd->frame_start = 0; return GF_NON_COMPLIANT_BITSTREAM; }
		if (gotpic<0) { ffd->frame_start = 0; return GF_OK; }

		/*first config*/
		if (!ffd->out_size) {
			if (ctx->channels * ctx->frame_size* 2 < gotpic) ctx->frame_size = gotpic / 2 * ctx->channels;
			ffd->out_size = ctx->channels * ctx->frame_size * 2 /* 16 / 8 */;
		}
		if (ffd->out_size < (u32) gotpic) {
			/*looks like relying on frame_size is not a good idea for all codecs, so we use gotpic*/
			(*outBufferLength) = ffd->out_size = gotpic;
			return GF_BUFFER_TOO_SMALL;
		}
		if (ffd->out_size > buf_size) {
			/*don't use too small output chunks otherwise we'll never have enough when mixing - we could
			also request more slots in the composition memory but let's not waste mem*/
			if (ffd->out_size < (u32) 576*ctx->channels) ffd->out_size=ctx->channels*576;
			(*outBufferLength) = ffd->out_size;
			return GF_BUFFER_TOO_SMALL;
		}

		/*we're sure to have at least gotpic bytes available in output*/
		memcpy(outBuffer, ffd->audio_buf, sizeof(char) * gotpic);
		(*outBufferLength) += gotpic;
		outBuffer += gotpic;

		ffd->frame_start += len;
		if (inBufferLength <= ffd->frame_start) {
			ffd->frame_start = 0;
			return GF_OK;
		}
		/*still space go on*/
		if ((*outBufferLength)+ctx->frame_size<ffd->out_size) goto redecode;

		/*more frames in the current sample*/
		return GF_PACKED_FRAMES;
	}


	if ( ctx->codec_id == CODEC_ID_RAWVIDEO) {
		if (*outBufferLength != ffd->out_size) {
			*outBufferLength = ffd->out_size;
			return GF_BUFFER_TOO_SMALL;
		}
		if (inBufferLength) {
			*outBufferLength = ffd->out_size;
			assert(inBufferLength==ffd->out_size);

			if (ffd->raw_pix_fmt==PIX_FMT_BGR24) {
				s32 i, j;
				for (j=0; j<ctx->height; j++) {
					u8 *src = inBuffer + j*3*ctx->width;
					u8 *dst = outBuffer + j*3*ctx->width;
					if (ffd->flipped) {
						dst = outBuffer + (ctx->height-j-1) * 3*ctx->width;
					}
					for (i=0; i<ctx->width; i++) {
						dst[0] = src[2];
						dst[1] = src[1];
						dst[2] = src[2];
						src += 3;
						dst += 3;
					}
				}
			} else {
				memcpy(outBuffer, inBuffer, ffd->out_size);
			}
		} else {
			*outBufferLength = 0;
		}
		return GF_OK;
	}

	*outBufferLength = 0;
	/*visual stream*/
	w = ctx->width;
	h = ctx->height;

	if (ffd->check_h264_isma) {
		/*for AVC bitstreams after ISMA decryption, in case (as we do) the decryption DRM tool
		doesn't put back nalu size, do it ourselves...*/
		if (inBuffer && !inBuffer[0] && !inBuffer[1] && !inBuffer[2] && (inBuffer[3]==0x01)) {
			u32 nalu_size;
			char *start, *end, *bufferEnd;

			start = inBuffer;
			end = inBuffer + 4;
			bufferEnd = inBuffer + inBufferLength;
			/* FIXME : SOUCHAY : not sure of exact behaviour, but old one was reading non-allocated memory */
			while ((end+3) < bufferEnd) {
				if (!end[0] && !end[1] && !end[2] && (end[3]==0x01)) {
					nalu_size = end - start - 4;
					start[0] = (nalu_size>>24)&0xFF;
					start[1] = (nalu_size>>16)&0xFF;
					start[2] = (nalu_size>>8)&0xFF;
					start[3] = (nalu_size)&0xFF;
					start = end;
					end = start+4;
					continue;
				}
				end++;
			}
			nalu_size = (inBuffer+inBufferLength) - start - 4;
			start[0] = (nalu_size>>24)&0xFF;
			start[1] = (nalu_size>>16)&0xFF;
			start[2] = (nalu_size>>8)&0xFF;
			start[3] = (nalu_size)&0xFF;
			ffd->check_h264_isma = 2;
		}
		/*if we had ISMA E&A and lost it this is likely due to a pck loss - do NOT switch back to regular*/
		else if (ffd->check_h264_isma == 1) {
			ffd->check_h264_isma = 0;
		}
	}

#ifdef USE_AVCODEC2
	if (avcodec_decode_video2(ctx, frame, &gotpic, &pkt) < 0) {
#else
	if (avcodec_decode_video(ctx, frame, &gotpic, inBuffer, inBufferLength) < 0) {
#endif
		if (!ffd->check_short_header) {
			return GF_NON_COMPLIANT_BITSTREAM;
		}

		/*switch to H263 (ffmpeg MPEG-4 codec doesn't understand short headers)*/
		{
			u32 old_codec = (*codec)->id;
			ffd->check_short_header = 0;
			/*OK we loose the DSI stored in the codec context, but H263 doesn't need any, and if we're
			here this means the DSI was broken, so no big deal*/
			avcodec_close(ctx);
			*codec = avcodec_find_decoder(CODEC_ID_H263);
			if (! (*codec) || (avcodec_open(ctx, *codec)<0)) return GF_NON_COMPLIANT_BITSTREAM;
#if USE_AVCODEC2
			if (avcodec_decode_video2(ctx, frame, &gotpic, &pkt) < 0) {
#else
			if (avcodec_decode_video(ctx, frame, &gotpic, inBuffer, inBufferLength) < 0) {
#endif
				/*nope, stay in MPEG-4*/
				avcodec_close(ctx);
				*codec = avcodec_find_decoder(old_codec);
				assert(*codec);
				avcodec_open(ctx, *codec);
				return GF_NON_COMPLIANT_BITSTREAM;
			}
		}
	}

	if (!gotpic && (!ctx->width || !ctx->height) ) {
		ctx->width = w;
		ctx->height = h;
		return GF_OK;
	}

	/*some streams use odd width/height frame values*/
	if (ffd->out_pix_fmt == GF_PIXEL_YV12) {
		if (ctx->width%2) ctx->width++;
		if (ctx->height%2) ctx->height++;
	}

	/*recompute outsize in case on-the-fly change*/
	if ((w != ctx->width) || (h != ctx->height)) {
		outsize = ctx->width * ctx->height * 3;
		if (ffd->out_pix_fmt != GF_PIXEL_RGB_24) outsize /= 2;
		if (ffd->depth_codec) {
			outsize = 5 * ctx->width * ctx->height / 2;
			ffd->yuv_size = 3 * ctx->width * ctx->height / 2;
		}
		ffd->out_size = outsize;
		*outBufferLength = ffd->out_size;
		if (ffd->check_h264_isma) {
			inBuffer[0] = inBuffer[1] = inBuffer[2] = 0;
			inBuffer[3] = 1;
		}
#ifdef FFMPEG_SWSCALE
		if (*cached_sws) {
			sws_freeContext(*cached_sws);
			*cached_sws = NULL;
		}
#endif
		if (!ffd->no_par_update && ctx->sample_aspect_ratio.num && ctx->sample_aspect_ratio.den) {
			ffd->previous_par = (ctx->sample_aspect_ratio.num<<16) | ctx->sample_aspect_ratio.den;
		}
		return GF_BUFFER_TOO_SMALL;
	}
	/*check PAR in case on-the-fly change*/
	if (!ffd->no_par_update && ctx->sample_aspect_ratio.num && ctx->sample_aspect_ratio.den) {
		u32 new_par = (ctx->sample_aspect_ratio.num<<16) | ctx->sample_aspect_ratio.den;
		if (new_par != ffd->previous_par) {
			ffd->previous_par = new_par;
			*outBufferLength = ffd->out_size;
			return GF_BUFFER_TOO_SMALL;
		}
	}

	if (mmlevel	== GF_CODEC_LEVEL_SEEK) return GF_OK;

	if (!gotpic) return GF_OK;

	if (ES_ID == ffd->depth_ES_ID) {
		s32 i;
		u8 *pYO, *pYD;

		pYO = frame->data[0];
		pYD = outBuffer+ffd->yuv_size;
		for (i=0; i<ctx->height; i++) {
			memcpy(pYD, pYO, sizeof(char) * ctx->width);
			pYD += ctx->width;
			pYO += frame->linesize[0];
		}
		*outBufferLength = ffd->out_size;
		return GF_OK;
	}

#if defined(_WIN32_WCE) || defined(__SYMBIAN32__)
	if (ffd->pix_fmt==GF_PIXEL_RGB_24) {
		memcpy(outBuffer, frame->data[0], sizeof(char)*3*ctx->width);
	} else {
		s32 i;
		char *pYO, *pUO, *pVO;
		unsigned char *pYD, *pUD, *pVD;
		pYO = frame->data[0];
		pUO = frame->data[1];
		pVO = frame->data[2];
		pYD = outBuffer;
		pUD = outBuffer + ctx->width * ctx->height;
		pVD = outBuffer + 5 * ctx->width * ctx->height / 4;


		for (i=0; i<ctx->height; i++) {
			memcpy(pYD, pYO, sizeof(char) * ctx->width);
			pYD += ctx->width;
			pYO += frame->linesize[0];
			if (i%2) continue;

			memcpy(pUD, pUO, sizeof(char) * ctx->width/2);
			memcpy(pVD, pVO, sizeof(char) * ctx->width/2);
			pUD += ctx->width/2;
			pVD += ctx->width/2;
			pUO += frame->linesize[1];
			pVO += frame->linesize[2];
		}
		*outBufferLength = ffd->out_size;
	}
#else
	memset(&pict, 0, sizeof(pict));
	if (ffd->out_pix_fmt==GF_PIXEL_RGB_24) {
		pict.data[0] = outBuffer;
		pict.linesize[0] = 3*ctx->width;
		pix_out = PIX_FMT_RGB24;
	} else {
		pict.data[0] = outBuffer;
		pict.data[1] = outBuffer + ctx->width * ctx->height;
		pict.data[2] = outBuffer + 5 * ctx->width * ctx->height / 4;
		pict.linesize[0] = ctx->width;
		pict.linesize[1] = pict.linesize[2] = ctx->width/2;
		pix_out = PIX_FMT_YUV420P;
		if (!mmlevel && frame->interlaced_frame) {
			avpicture_deinterlace((AVPicture *) frame, (AVPicture *) frame, ctx->pix_fmt, ctx->width, ctx->height);
		}
	}
	pict.data[3] = 0;
	pict.linesize[3] = 0;
#ifndef FFMPEG_SWSCALE
	img_convert(&pict, pix_out, (AVPicture *) frame, ctx->pix_fmt, ctx->width, ctx->height);
#else
	*cached_sws = sws_getCachedContext(*cached_sws,
					   ctx->width, ctx->height, ctx->pix_fmt,
					   ctx->width, ctx->height, pix_out,
					   SWS_BICUBIC, NULL, NULL, NULL);
	if ((*cached_sws)) {
#if LIBSWSCALE_VERSION_INT < AV_VERSION_INT(0, 9, 0)
		int sz = sws_scale((*cached_sws), frame->data, frame->linesize, 0, ctx->height, pict.data, pict.linesize);
#else
		int sz = sws_scale((*cached_sws), (const uint8_t * const*)frame->data, frame->linesize, 0, ctx->height, pict.data, pict.linesize);
#endif
		assert( sz > 0 );
	}
#endif

	*outBufferLength = ffd->out_size;
#endif

	return GF_OK;
}

static u32 FFDEC_CanHandleStream(GF_BaseDecoder *plug, u32 StreamType, GF_ESD *esd, u8 PL)
{
	GF_BitStream *bs;
	u32 codec_id;
	Bool check_4cc;
	FFDec *ffd = plug->privateStack;

	/*media type query*/
	if (!esd) {
		if ((StreamType==GF_STREAM_VISUAL) || (StreamType==GF_STREAM_AUDIO)) return GF_CODEC_STREAM_TYPE_SUPPORTED;
		return GF_CODEC_NOT_SUPPORTED;
	}

	/*store types*/
	ffd->oti = esd->decoderConfig->objectTypeIndication;
	ffd->st = StreamType;

	codec_id = 0;
	check_4cc = 0;

	/*private from FFMPEG input*/
	if (ffd->oti == GPAC_OTI_MEDIA_FFMPEG) {
		bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ);
		codec_id = gf_bs_read_u32(bs);
		gf_bs_del(bs);
	}
	/*private from IsoMedia input*/
	else if (ffd->oti == GPAC_OTI_MEDIA_GENERIC) {
		bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ);
		codec_id = gf_bs_read_u32(bs);
		check_4cc = 1;
		gf_bs_del(bs);
	}
	else if (StreamType==GF_STREAM_AUDIO) {
		/*std MPEG-2 audio*/
		if ((ffd->oti==GPAC_OTI_AUDIO_MPEG2_PART3) || (ffd->oti==GPAC_OTI_AUDIO_MPEG1)) codec_id = CODEC_ID_MP2;
		/*std AC3 audio*/
		//if (ffd->oti==0xA5) codec_id = CODEC_ID_AC3;
	}

	/*std MPEG-4 visual*/
	else if (StreamType==GF_STREAM_VISUAL) {

		/*fixme - we should use some priority rather than declare ffmpeg can't handle svc*/
		if (esd->decoderConfig->objectTypeIndication == GPAC_OTI_VIDEO_AVC) {
			if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) {
				Bool is_svc = 0;
				u32 i, count;
				GF_AVCConfig *cfg = gf_odf_avc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength);
				if (!cfg) return GF_CODEC_SUPPORTED;

				/*decode all NALUs*/
				count = gf_list_count(cfg->sequenceParameterSets);
				for (i=0; i<count; i++) {
					GF_AVCConfigSlot *slc = gf_list_get(cfg->sequenceParameterSets, i);
					u8 nal_type = slc->data[0] & 0x1F;

					if (nal_type==GF_AVC_NALU_SVC_SUBSEQ_PARAM) {
						is_svc = 1;
						break;
					}
				}
				gf_odf_avc_cfg_del(cfg);
				return (is_svc || esd->decoderConfig->rvc_config || esd->decoderConfig->predefined_rvc_config) ? GF_CODEC_MAYBE_SUPPORTED : GF_CODEC_SUPPORTED;
			}
			if (esd->decoderConfig->rvc_config || esd->decoderConfig->predefined_rvc_config) return GF_CODEC_MAYBE_SUPPORTED;
			return GF_CODEC_SUPPORTED;
		}

		switch (ffd->oti) {
		/*MPEG-4 v1 simple profile*/
		case GPAC_OTI_VIDEO_MPEG4_PART2: codec_id = CODEC_ID_MPEG4; break;
		/*H264 (not std OTI, just the way we use it internally)*/
		case GPAC_OTI_VIDEO_AVC: codec_id = CODEC_ID_H264; break;
		/*MPEG1 video*/
		case GPAC_OTI_VIDEO_MPEG1:
		/*MPEG2 video*/
		case GPAC_OTI_VIDEO_MPEG2_SIMPLE:
		case GPAC_OTI_VIDEO_MPEG2_MAIN:
		case GPAC_OTI_VIDEO_MPEG2_SNR:
		case GPAC_OTI_VIDEO_MPEG2_SPATIAL:
		case GPAC_OTI_VIDEO_MPEG2_HIGH:
		case GPAC_OTI_VIDEO_MPEG2_422:
			codec_id = CODEC_ID_MPEG2VIDEO; break;
		/*JPEG*/
		case GPAC_OTI_IMAGE_JPEG:
			codec_id = CODEC_ID_MJPEG;
			/*return maybe supported as FFMPEG JPEG decoder has some issues with many files, so let's use it only if no
			other dec is available*/
			if (avcodec_find_decoder(codec_id) != NULL) 
				return GF_CODEC_MAYBE_SUPPORTED;
			
			return GF_CODEC_NOT_SUPPORTED;
		default:
			return GF_CODEC_NOT_SUPPORTED;
		}
	}
	/*NeroDigital DVD subtitles*/
	else if ((StreamType==GF_STREAM_ND_SUBPIC) && (ffd->oti==0xe0))
		return GF_CODEC_SUPPORTED;

	if (!codec_id) return GF_CODEC_NOT_SUPPORTED;

	if (check_4cc && (ffmpeg_get_codec(codec_id) != NULL)) {
		if (esd->decoderConfig->rvc_config || esd->decoderConfig->predefined_rvc_config) return GF_CODEC_MAYBE_SUPPORTED;
		return GF_CODEC_SUPPORTED;
	}

	if (avcodec_find_decoder(codec_id) != NULL) {
		if (esd->decoderConfig->rvc_config || esd->decoderConfig->predefined_rvc_config) return GF_CODEC_MAYBE_SUPPORTED;
		return GF_CODEC_SUPPORTED;
	}
	return GF_CODEC_NOT_SUPPORTED;
}

static const char *FFDEC_GetCodecName(GF_BaseDecoder *dec)
{
	FFDec *ffd;
	if (!dec)
		return NULL;
	ffd = dec->privateStack;
	if (ffd && ffd->base_codec) {
		sprintf(ffd->szCodec, "FFMPEG %s", ffd->base_codec->name ? ffd->base_codec->name : "unknown");
		return ffd->szCodec;
	}
	return NULL;
}


void *FFDEC_Load()
{
	GF_MediaDecoder *ptr;
	FFDec *priv;

	/* Note for valgrind : those two functions cause a leak in valgrind */
	GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[FFMPEG Decoder] Registering all ffmpeg codecs...\n") );
#ifdef FF_API_AVCODE_INIT /*commit ffmpeg 3211932c513338566b31d990d06957e15a644d13*/
	avcodec_init();
#endif
	avcodec_register_all();
	GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[FFMPEG Decoder] Done registering all ffmpeg codecs.\n") );


	GF_SAFEALLOC(ptr , GF_MediaDecoder);
	GF_SAFEALLOC(priv , FFDec);
	ptr->privateStack = priv;

	ptr->AttachStream = FFDEC_AttachStream;
	ptr->DetachStream = FFDEC_DetachStream;
	ptr->GetCapabilities = FFDEC_GetCapabilities;
	ptr->SetCapabilities = FFDEC_SetCapabilities;
	ptr->CanHandleStream = FFDEC_CanHandleStream;
	ptr->GetName = FFDEC_GetCodecName;
	ptr->ProcessData = FFDEC_ProcessData;

	GF_REGISTER_MODULE_INTERFACE(ptr, GF_MEDIA_DECODER_INTERFACE, "FFMPEG decoder", "gpac distribution");
	return (GF_BaseInterface *) ptr;
}

void FFDEC_Delete(void *ifce)
{
	GF_BaseDecoder *dec = ifce;
	FFDec *ffd;
	if (!ifce)
		return;
	ffd = dec->privateStack;
	dec->privateStack = NULL;
	if (ffd){
		if (ffd->base_ctx && ffd->base_ctx->codec) avcodec_close(ffd->base_ctx);
		ffd->base_ctx = NULL;
		if (ffd->depth_ctx && ffd->depth_ctx->codec) avcodec_close(ffd->depth_ctx);
		ffd->depth_ctx = NULL;
#ifdef FFMPEG_SWSCALE
		if (ffd->base_sws) sws_freeContext(ffd->base_sws);
		ffd->base_sws = NULL;
		if (ffd->depth_sws) sws_freeContext(ffd->base_sws);
		ffd->depth_sws = NULL;
#endif
		gf_free(ffd);
	}
	gf_free(dec);
}
