/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2012
 *					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>
#include <libavcodec/avcodec.h>


#if ((LIBAVCODEC_VERSION_MAJOR == 55) && (LIBAVCODEC_VERSION_MINOR >= 38)) || (LIBAVCODEC_VERSION_MAJOR > 55)
#define HAS_HEVC

#include <libavutil/opt.h>
#endif

#if defined(GPAC_ANDROID) || defined(GPAC_IPHONE)
#define NO_10bit
#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 uint8_t * ffmpeg_realloc_buffer(uint8_t * oldBuffer, u32 size) {
	uint8_t * 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 = (uint8_t*)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, (char *) 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((char *) ctx->extradata, "SVQ3");
			gf_bs_read_data(bs, (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, (char *)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;
	const char *sOpt;

#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_Descriptor*)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)) {

#ifdef USE_AVCTX3
		*ctx = avcodec_alloc_context3(NULL);
#else
		*ctx = avcodec_alloc_context();
#endif
	}

	/*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);
			(*ctx)->bit_rate = gf_bs_read_u32(bs);
			(*ctx)->codec_tag = gf_bs_read_u32(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, GF_TRUE);
		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, GF_FALSE);
		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;
				break;
#ifdef HAS_HEVC
			case GPAC_OTI_VIDEO_HEVC:
				codec_id = AV_CODEC_ID_HEVC;
				break;
#endif
			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 = GF_TRUE;
				break;
			case GPAC_OTI_IMAGE_PNG:
				codec_id = CODEC_ID_PNG;
				ffd->is_image = GF_TRUE;
				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;
			case GPAC_OTI_AUDIO_AC3:
				codec_id = CODEC_ID_AC3;
				break;
			case GPAC_OTI_AUDIO_EAC3:
				codec_id = CODEC_ID_EAC3;
				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) || (ffd->oti == GPAC_OTI_VIDEO_HEVC)) {
			/*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 && 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 = GF_TRUE;
					ffd->previous_par = (dsi.par_num<<16) | dsi.par_den;
					ffd->no_par_update = GF_TRUE;
#endif
				} else if (ffd->oti==GPAC_OTI_VIDEO_AVC) {
					ffd->check_h264_isma = GF_TRUE;
				}

				/*setup dsi for FFMPEG context BEFORE attaching decoder (otherwise not proper init)*/
				(*ctx)->extradata = ffmpeg_realloc_buffer((*ctx)->extradata, esd->decoderConfig->decoderSpecificInfo->dataLength + 8);
				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;
				}
			}
		}
#if !defined(FF_API_AVFRAME_LAVC)
		*frame = avcodec_alloc_frame();
#else
		*frame = av_frame_alloc();
#endif

	}
#ifdef HAS_HEVC
	if (ffd->oti == GPAC_OTI_VIDEO_HEVC) {
		GF_SystemRTInfo rti;
		u32 nb_threads, detected_nb_threads = 1;
		sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "OpenHEVC", "ThreadingType");
		if (sOpt && !strcmp(sOpt, "wpp")) av_opt_set(*ctx, "thread_type", "slice", 0);
		else if (sOpt && !strcmp(sOpt, "frame+wpp")) av_opt_set(*ctx, "thread_type", "frameslice", 0);
		else {
			av_opt_set(*ctx, "thread_type", "frame", 0);
			if (!sOpt) gf_modules_set_option((GF_BaseInterface *)plug, "OpenHEVC", "ThreadingType", "frame");
		}
		if (gf_sys_get_rti(0, &rti, 0) ) {
			detected_nb_threads = (rti.nb_cores>1) ? rti.nb_cores-1 : 1;
		}
		sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "OpenHEVC", "NumThreads");
		if (!sOpt) {
			char szO[100];
			sprintf(szO, "%d", detected_nb_threads);
			gf_modules_set_option((GF_BaseInterface *)plug, "OpenHEVC", "NumThreads", szO);
			nb_threads = detected_nb_threads;
		} else {
			nb_threads = atoi(sOpt);
		}
		if (nb_threads > detected_nb_threads) {
			GF_LOG(GF_LOG_CODEC, GF_LOG_WARNING, ("[HEVC@ffmpeg] Initializing with %d threads but only %d available cores detected on the system\n", nb_threads, rti.nb_cores));
		} else {
			GF_LOG(GF_LOG_CODEC, GF_LOG_INFO, ("[HEVC@ffmpeg] Initializing with %d threads\n", nb_threads));
		}
		fprintf(stderr, "[HEVC@ffmpeg] Initializing with %d threads\n", nb_threads);
		av_opt_set_int(*ctx, "threads", nb_threads, 0);

		/* Set the decoder id */
		//av_opt_set_int(openHevcContext->c->priv_data, "decoder-id", i, 0);

		sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "OpenHEVC", "CBUnits");
		if (!sOpt) gf_modules_set_option((GF_BaseInterface *)plug, "OpenHEVC", "CBUnits", "4");
		if (sOpt) ffd->output_cb_size = atoi(sOpt);
	}
#endif //HAS_HEVC
	if (!ffd->output_cb_size) ffd->output_cb_size = 4;

	if (codec_id == CODEC_ID_RAWVIDEO) {
		(*ctx)->codec_id = CODEC_ID_RAWVIDEO;
		(*ctx)->pix_fmt = ffd->raw_pix_fmt;
		if ((*ctx)->extradata && strstr((char *) (*ctx)->extradata, "BottomUp")) ffd->flipped = GF_TRUE;
	} else {
#ifdef USE_AVCTX3
		if (avcodec_open2((*ctx), (*codec), NULL )<0) return GF_NON_COMPLIANT_BITSTREAM;
#else
		if (avcodec_open((*ctx), (*codec) )<0) return GF_NON_COMPLIANT_BITSTREAM;
#endif
	}
	/*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;

#if defined(USE_AVCTX3)

#if !defined(FF_API_AVFRAME_LAVC)
		ffd->audio_frame = avcodec_alloc_frame();
#else
		ffd->audio_frame = av_frame_alloc();
#endif

#endif

	} 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_PNG:
		case CODEC_ID_RAWVIDEO:
			if ((*ctx)->pix_fmt==PIX_FMT_YUV420P) {
				ffd->pix_fmt = GF_PIXEL_YV12;
			} else {
				ffd->pix_fmt = GF_PIXEL_RGB_24;
			}
			break;

		case CODEC_ID_DVD_SUBTITLE:
#if !defined(FF_API_AVFRAME_LAVC)
			*frame = avcodec_alloc_frame();
#else
			*frame = av_frame_alloc();
#endif

#ifdef USE_AVCODEC2
			{
				AVPacket pkt;
				av_init_packet(&pkt);
				pkt.data = (uint8_t *) 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) {
			ffd->stride = (*ctx)->width;
			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;
		}
	}


	sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Systems", "Output8bit");
	if (!sOpt) gf_modules_set_option((GF_BaseInterface *)plug, "Systems", "Output8bit", (ffd->display_bpp>8) ? "no" : "yes");
	if (sOpt && !strcmp(sOpt, "yes")) ffd->output_as_8bit = GF_TRUE;

	if (ffd->output_as_8bit && (ffd->stride > (u32) (*ctx)->width)) {
		ffd->stride /=2;
		ffd->out_size /= 2;
		ffd->conv_to_8bit = GF_TRUE;
	}

	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;

#if defined(USE_AVCTX3)
	if (ffd->audio_frame) {
		av_free(ffd->audio_frame);
	}
#endif

#ifdef FFMPEG_SWSCALE
	if (*sws) {
		sws_freeContext(*sws);
		*sws = NULL;
	}
#endif
	if (ffd->conv_buffer) {
		gf_free(ffd->conv_buffer);
		ffd->conv_buffer = NULL;
	}
	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;
	case GF_CODEC_DIRECT_OUTPUT:
		capability->cap.valueBool = GF_TRUE;
		return GF_OK;
	case GF_CODEC_WANTS_THREAD:
		capability->cap.valueBool= GF_TRUE;
		break;
	}

	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 : ffd->output_cb_size);
		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:
		if (ffd->out_pix_fmt==GF_PIXEL_RGB_24)
			capability->cap.valueInt = ffd->stride*3;
		else if (ffd->out_pix_fmt==GF_PIXEL_RGBA)
			capability->cap.valueInt = ffd->stride*4;
		else if (ffd->conv_buffer)
			capability->cap.valueInt = ffd->base_ctx->width;
		else
			capability->cap.valueInt = ffd->stride;
		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;
		if (ffd->conv_buffer) capability->cap.valueInt = GF_PIXEL_YV12;
		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_DISPLAY_BPP:
		ffd->display_bpp = capability.cap.valueInt;
		return GF_OK;
	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;
	case GF_CODEC_DIRECT_OUTPUT:
		ffd->direct_output = GF_TRUE;
		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, u32 *CTS,
                                char *outBuffer, u32 *outBufferLength,
                                u8 PaddingBits, u32 mmlevel)
{
#ifdef USE_AVCODEC2
	AVPacket pkt;
#endif
	AVPicture pict;
	u32 pix_out;
	s32 w, h, gotpic, stride;
	u32 outsize;
	AVCodecContext *ctx;
	AVCodec **codec;
	AVFrame *frame;
#ifdef FFMPEG_SWSCALE
	struct SwsContext **cached_sws;
#endif
	FFDec *ffd = (FFDec*)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 = (uint8_t *)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;
		//seek to last byte consumed by the previous decode4()
		else if (ffd->frame_start) {
			pkt.data += ffd->frame_start;
			pkt.size -= ffd->frame_start;
		}
redecode:

#if defined(USE_AVCTX3)
		len = avcodec_decode_audio4(ctx, ffd->audio_frame, &gotpic, &pkt);
		if (gotpic) {
			//int inputDataSize = av_samples_get_buffer_size(NULL, ctx->channels, ffd->audio_frame->nb_samples, ctx->sample_fmt, 1);
			gotpic = ffd->audio_frame->nb_samples * 2 * ctx->channels;

		}
#elif defined(USE_AVCODEC2)
		gotpic = 192000;
		len = avcodec_decode_audio3(ctx, (short *)ffd->audio_buf, &gotpic, &pkt);
#else
		gotpic = AVCODEC_MAX_AUDIO_FRAME_SIZE;
		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) {
			u32 bpp = 2;

			if (ctx->channels * ctx->frame_size * bpp < gotpic) ctx->frame_size = gotpic / (bpp * ctx->channels);

			ffd->out_size = ctx->channels * ctx->frame_size * bpp;
		}
		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;
		}

#if defined(USE_AVCTX3)
		if (ffd->audio_frame->format==AV_SAMPLE_FMT_FLTP) {
			s32 i, j;
			s16 *output = (s16 *) outBuffer;
			for (j=0; j<ctx->channels; j++) {
				Float* inputChannel = (Float*)ffd->audio_frame->extended_data[j];
				for (i=0 ; i<ffd->audio_frame->nb_samples ; i++) {
					Float sample = inputChannel[i];
					if (sample<-1.0f) sample=-1.0f;
					else if (sample>1.0f) sample=1.0f;

					output[i*ctx->channels + j] = (int16_t) (sample * GF_SHORT_MAX );
				}
			}
		} else if (ffd->audio_frame->format==AV_SAMPLE_FMT_U8) {
			u32 i, size = ffd->audio_frame->nb_samples * ctx->channels;
			s16 *output = (s16 *) outBuffer;
			s8 *input = (s8 *) ffd->audio_frame->data;
			for (i=0; i<size; i++) {
				output [i] = input[i] * 128;
			}
		} else if (ffd->audio_frame->format==AV_SAMPLE_FMT_S32) {
			u32 i, shift, size = ffd->audio_frame->nb_samples * ctx->channels;
			s16 *output = (s16 *) outBuffer;
			s32 *input = (s32*) ffd->audio_frame->data;
			shift = 1<<31;
			for (i=0; i<size; i++) {
				output [i] = input[i] * shift;
			}
		} else if (ffd->audio_frame->format==AV_SAMPLE_FMT_S16) {
			memcpy(outBuffer, ffd->audio_frame->data, sizeof(char) * ffd->audio_frame->nb_samples * ctx->channels*2);
		} else {
			GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FFMPEG Decoder] Raw Audio format %d not supoorted\n", ffd->audio_frame->format ));
		}
#else
		/*we're sure to have at least gotpic bytes available in output*/
		memcpy(outBuffer, ffd->audio_buf, sizeof(char) * gotpic);
#endif

		(*outBufferLength) += gotpic;
		outBuffer += gotpic;

#if defined(USE_AVCTX3)
		ffd->audio_frame->nb_samples = 0;
#endif

		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 = (u8 *) inBuffer + j*3*ctx->width;
					u8 *dst = (u8 *)outBuffer + j*3*ctx->width;
					if (ffd->flipped) {
						dst = (u8 *)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;

	/*we have a valid frame not yet dispatched*/
	if (ffd->had_pic) {
		ffd->had_pic = GF_FALSE;
		gotpic = 1;
	} else {
		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 = (u32) (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 = (u32) ((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 = GF_FALSE;
			}
		}

#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 = GF_FALSE;
				/*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);

#ifdef USE_AVCTX3
				if (! (*codec) || (avcodec_open2(ctx, *codec, NULL)<0)) return GF_NON_COMPLIANT_BITSTREAM;
#else
				if (! (*codec) || (avcodec_open(ctx, *codec)<0)) return GF_NON_COMPLIANT_BITSTREAM;
#endif

#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);
#ifdef USE_AVCTX3
					avcodec_open2(ctx, *codec, NULL);
#else
					avcodec_open(ctx, *codec);
#endif
					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++;
		}
	}

	/*we have a picture and need resize, do it*/
	if (gotpic && ffd->needs_output_resize) {
		ffd->needs_output_resize = GF_FALSE;
		ffd->had_pic = GF_TRUE;
		*outBufferLength = ffd->out_size;
		return GF_BUFFER_TOO_SMALL;
	}

	stride = frame->linesize[0];
#ifndef NO_10bit
	if ((ctx->pix_fmt == PIX_FMT_YUV420P10LE) && ffd->output_as_8bit && (frame->linesize[0] >= 2*w) )  {
		ffd->conv_to_8bit = GF_TRUE;
		stride=w;
	}
#endif

	/*recompute outsize in case on-the-fly change*/
	if ((w != ctx->width) || (h != ctx->height)
	        || (ffd->direct_output && (stride != ffd->stride))
	        || ((ffd->out_pix_fmt==GF_PIXEL_YV12) && (ctx->pix_fmt != PIX_FMT_YUV420P) && !ffd->output_as_8bit )
	        //need to realloc the conversion buffer
	        || (ffd->conv_to_8bit && !ffd->conv_buffer && ffd->direct_output)
	   ) {

		ffd->stride = (!ffd->conv_to_8bit && ffd->direct_output) ? frame->linesize[0] : ctx->width;
		if (ctx->pix_fmt  == PIX_FMT_RGBA) {
			ffd->out_pix_fmt = ffd->pix_fmt = GF_PIXEL_RGBA;
			outsize = ctx->width * ctx->height * 4;
		}
		else if (ffd->out_pix_fmt == GF_PIXEL_RGB_24) {
			outsize = ctx->width * ctx->height * 3;
		}
#ifndef NO_10bit
		//this YUV format is handled natively in GPAC
		else if ((ctx->pix_fmt == PIX_FMT_YUV420P10LE) && !ffd->output_as_8bit) {
			ffd->stride = ffd->direct_output ? frame->linesize[0] : ctx->width*2;
			outsize = ffd->stride * ctx->height * 3 / 2;
			ffd->out_pix_fmt = GF_PIXEL_YV12_10;
		}
#endif
		//the rest will be YUV
		else {
			outsize = ffd->stride * ctx->height * 3 / 2;
			ffd->out_pix_fmt = GF_PIXEL_YV12;
		}

		if (ffd->depth_codec) {
			outsize = 5 * ctx->width * ctx->height / 2;
			ffd->yuv_size = 3 * ctx->width * ctx->height / 2;
		}
		ffd->out_size = outsize;

		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;
		}

		/*we didn't get any picture: wait for a picture before resizing output buffer, otherwise we will have no
		video in the output buffer*/
		if (!gotpic) {
			ffd->needs_output_resize = GF_TRUE;
			return GF_OK;
		}
		*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
		ffd->had_pic = GF_TRUE;

		if (ffd->conv_to_8bit && ffd->direct_output) {
			ffd->conv_buffer = (char*)gf_realloc(ffd->conv_buffer, sizeof(char)*ffd->out_size);
		}

		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 (ffd->previous_par && (new_par != ffd->previous_par)) {
			ffd->previous_par = new_par;

			if (!gotpic) {
				ffd->needs_output_resize = GF_TRUE;
				return GF_OK;
			}
			*outBufferLength = ffd->out_size;
			ffd->had_pic = GF_TRUE;
			return GF_BUFFER_TOO_SMALL;
		}
	}

//	if (mmlevel	== GF_CODEC_LEVEL_SEEK) return GF_OK;

	if (!gotpic) return GF_OK;

#if (LIBAVCODEC_VERSION_MAJOR>52)
	//fixme - investigate this, happens in some dash cases
	if ((frame->width!=ctx->width) || (frame->height!=ctx->height))  {
		*outBufferLength = 0;
		return GF_OK;
	}
#endif

	if (ffd->direct_output && !ffd->conv_to_8bit) {
		*outBufferLength = ffd->out_size;
		return GF_OK;
	}

	if (ffd->conv_to_8bit) {
		GF_VideoSurface dst;
		memset(&dst, 0, sizeof(GF_VideoSurface));
		dst.width = ctx->width;
		dst.height = ctx->height;
		dst.pitch_y = ctx->width;
		dst.video_buffer = ffd->direct_output ? ffd->conv_buffer : outBuffer;
		dst.pixel_format = GF_PIXEL_YV12;

		gf_color_write_yv12_10_to_yuv(&dst, (u8 *) frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], ctx->width, ctx->height, NULL, GF_FALSE);
		*outBufferLength = ffd->out_size;
		return GF_OK;
	}


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

		pYO = frame->data[0];
		pYD = (u8 *) 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 if (ffd->pix_fmt==GF_PIXEL_RGBA) {
		memcpy(outBuffer, frame->data[0], sizeof(char)*4*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] =  (uint8_t *)outBuffer;
		pict.linesize[0] = 3*ctx->width;
		pix_out = PIX_FMT_RGB24;
	} else if (ffd->out_pix_fmt==GF_PIXEL_RGBA) {
		pict.data[0] =  (uint8_t *)outBuffer;
		pict.linesize[0] = 4*ctx->width;
		pix_out = PIX_FMT_RGBA;
	} else {
		pict.data[0] =  (uint8_t *)outBuffer;
		pict.data[1] =  (uint8_t *)outBuffer + ffd->stride * ctx->height;
		pict.data[2] =  (uint8_t *)outBuffer + 5 * ffd->stride * ctx->height / 4;
		pict.linesize[0] = ffd->stride;
		pict.linesize[1] = pict.linesize[2] = ffd->stride/2;
		pix_out = PIX_FMT_YUV420P;
#ifndef NO_10bit
		//this YUV format is handled natively in GPAC
		if (ctx->pix_fmt==PIX_FMT_YUV420P10LE) {
			pix_out = PIX_FMT_YUV420P10LE;
		}
#endif

#if (LIBAVCODEC_VERSION_MAJOR<56)
		if (!mmlevel && frame->interlaced_frame) {
			avpicture_deinterlace((AVPicture *) frame, (AVPicture *) frame, ctx->pix_fmt, ctx->width, ctx->height);
		}
#endif

	}
	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)
		sws_scale((*cached_sws), frame->data, frame->linesize, 0, ctx->height, pict.data, pict.linesize);
#else
		sws_scale((*cached_sws), (const uint8_t * const*)frame->data, frame->linesize, 0, ctx->height, pict.data, pict.linesize);
#endif

	}
#endif

	*outBufferLength = ffd->out_size;
#endif

	return GF_OK;
}

static GF_Err FFDEC_GetOutputBuffer(GF_MediaDecoder *ifcg, u16 ES_ID, u8 **pY_or_RGB, u8 **pU, u8 **pV)
{
	FFDec *ffd = (FFDec*)ifcg->privateStack;
	AVFrame *frame;


	if (ffd->conv_buffer) {
		*pY_or_RGB = (u8 *) ffd->conv_buffer;
		*pU = (u8 *) ffd->conv_buffer + ffd->stride * ffd->base_ctx->height;
		*pV = (u8 *) ffd->conv_buffer + 5*ffd->stride * ffd->base_ctx->height/4;
		return GF_OK;
	}

	if (ES_ID && (ffd->depth_ES_ID==ES_ID)) {
		frame = ffd->depth_frame;
		*pY_or_RGB = frame->data[0];
	} else {
		frame = ffd->base_frame;
		*pY_or_RGB = frame->data[0];
		*pU = frame->data[1];
		*pV = frame->data[2];
	}
	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 = (FFDec*)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 = GF_FALSE;

	/*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 = GF_TRUE;
		gf_bs_del(bs);
	}
	else if (StreamType==GF_STREAM_AUDIO) {
		/*std MPEG-2 audio*/
		switch (ffd->oti) {
		case GPAC_OTI_AUDIO_MPEG2_PART3:
		case GPAC_OTI_AUDIO_MPEG1:
			codec_id = CODEC_ID_MP2;
			break;
		case GPAC_OTI_AUDIO_AC3:
			codec_id = CODEC_ID_AC3;
			break;
		case GPAC_OTI_AUDIO_EAC3:
			codec_id = CODEC_ID_EAC3;
			break;
		}
	}

	/*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 = GF_FALSE;
				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;

				if (esd->has_ref_base)
					is_svc = GF_TRUE;

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

					if (nal_type==GF_AVC_NALU_SVC_SUBSEQ_PARAM) {
						is_svc = GF_TRUE;
						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 || esd->has_ref_base) 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;
#ifdef HAS_HEVC
		case GPAC_OTI_VIDEO_HEVC:
			codec_id = AV_CODEC_ID_HEVC;
			break;
#endif
		/*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;
		case GPAC_OTI_IMAGE_PNG:
			if (avcodec_find_decoder(CODEC_ID_PNG) != 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;

		//for HEVC return MAYBE supported to fallback to openHEVC if present (more optimized for now)
#ifdef HAS_HEVC
		if (codec_id == AV_CODEC_ID_HEVC)
			return GF_CODEC_MAYBE_SUPPORTED;
#endif

		return GF_CODEC_SUPPORTED;
	}

	return GF_CODEC_NOT_SUPPORTED;
}

static const char *FFDEC_GetCodecName(GF_BaseDecoder *dec)
{
	FFDec *ffd;
	if (!dec)
		return NULL;
	ffd = (FFDec*)dec->privateStack;
	if (ffd && ffd->base_codec) {
		sprintf(ffd->szCodec, "FFMPEG %s - version %s", ffd->base_codec->name ? ffd->base_codec->name : "unknown", LIBAVCODEC_IDENT);
		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;
	ptr->GetOutputBuffer = FFDEC_GetOutputBuffer;

	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 = (GF_BaseDecoder*)ifce;
	FFDec *ffd;
	if (!ifce)
		return;
	ffd = (FFDec*)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);
}
