<commit_before>/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: <NAME>
 *			Copyright (c) Telecom ParisTech 2017-2021
 *					All rights reserved
 *
 *  This file is part of GPAC / ffmpeg decode filter
 *
 *  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 <gpac/setup.h>

#ifdef GPAC_HAS_FFMPEG

#include "ff_common.h"

#include <libswscale/swscale.h>

#define FF_CHECK_PROP(_name, _ffname, _type)	if (ctx->_name != ctx->decoder->_ffname) { \
		gf_filter_pid_set_property(ctx->out_pid, _type, &PROP_UINT( (u32) ctx->decoder->_ffname ) );	\
		ctx->_name = (u32) ctx->decoder->_ffname;	\
	} \

#define FF_CHECK_PROPL(_name, _ffname, _type)	if (ctx->_name != ctx->decoder->_ffname) { \
	gf_filter_pid_set_property(ctx->out_pid, _type, &PROP_LONGUINT( (u32) ctx->decoder->_ffname ) );	\
	ctx->_name = (u32) ctx->decoder->_ffname;	\
} \

#define FF_CHECK_PROP_VAL(_name, _val, _type)	if (ctx->_name != _val) { \
		gf_filter_pid_set_property(ctx->out_pid, _type, &PROP_UINT( _val ) );	\
		ctx->_name = _val;	\
	} \

static GF_Err ffdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove);


typedef struct _gf_ffdec_ctx
{
	//internal data
	Bool initialized;

	Bool owns_context;
	AVCodecContext *decoder;
	//decode options
	AVDictionary *options;

	Bool reconfig_pending;

	GF_FilterPid *in_pid, *out_pid;
	//media type
	u32 type;
	//CRC32 of extra_data
	u32 extra_data_crc;

	GF_Err (*process)(GF_Filter *filter, struct _gf_ffdec_ctx *ctx);

	u32 flush_done;

	//for now we don't share the data
	AVFrame *frame;
	//audio state
	u32 channels, sample_rate, sample_fmt, bytes_per_sample;
	u64 channel_layout;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
	u32 frame_start;
#endif
	u32 nb_samples_already_in_frame;

	//video state
	u32 width, height, pixel_fmt, stride, stride_uv;
	GF_Fraction sar;
	struct SwsContext *sws_ctx;

	GF_List *src_packets;

	u32 o_ff_pfmt;
	Bool force_full_range;
	Bool drop_non_refs;

	s64 first_cts_plus_one;
	s64 delay;
	u32 ts_offset;

#if (LIBAVCODEC_VERSION_MAJOR < 59)
	AVPacket pkt;
#else
	AVPacket *pkt;
#endif

} GF_FFDecodeCtx;

static GF_Err ffdec_initialize(GF_Filter *filter)
{
	GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter);
	ctx->initialized = GF_TRUE;
	ctx->src_packets = gf_list_new();

#if (LIBAVCODEC_VERSION_MAJOR >= 59)
	ctx->pkt = av_packet_alloc();
#endif

	ffmpeg_setup_logs(GF_LOG_CODEC);
	return GF_OK;
}

static void ffdec_finalize(GF_Filter *filter)
{
	GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter);

	if (ctx->options) av_dict_free(&ctx->options);
	if (ctx->frame) av_frame_free(&ctx->frame);
	if (ctx->sws_ctx) sws_freeContext(ctx->sws_ctx);

	while (gf_list_count(ctx->src_packets)) {
		GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets);
		gf_filter_pck_unref(pck);
	}
	gf_list_del(ctx->src_packets);

#if (LIBAVCODEC_VERSION_MAJOR >= 59)
	av_packet_free(&ctx->pkt);
#endif

	if (ctx->owns_context && ctx->decoder) {
		avcodec_free_context(&ctx->decoder);
	}
	return;
}

static void ffdec_check_pix_fmt_change(struct _gf_ffdec_ctx *ctx, u32 pix_fmt)
{
	if (ctx->pixel_fmt != pix_fmt) {
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_PIXFMT, &PROP_UINT(pix_fmt));
		ctx->pixel_fmt = pix_fmt;
		ctx->force_full_range = GF_FALSE;
		if (ctx->decoder->color_range==AVCOL_RANGE_JPEG)
			ctx->force_full_range = GF_TRUE;
		else if (ffmpeg_pixfmt_is_fullrange(ctx->decoder->pix_fmt))
			ctx->force_full_range = GF_TRUE;
		else
			ctx->force_full_range = GF_FALSE;

		if (ctx->force_full_range)
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_RANGE, &PROP_BOOL(GF_TRUE) );
		else
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_RANGE, NULL );

		if (ctx->decoder->color_primaries!=AVCOL_PRI_UNSPECIFIED)
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_PRIMARIES, &PROP_UINT(ctx->decoder->color_primaries) );
		else
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_PRIMARIES, NULL );

		if (ctx->decoder->colorspace!=AVCOL_SPC_UNSPECIFIED)
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_MX, &PROP_UINT(ctx->decoder->colorspace) );
		else
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_MX, NULL );

		if (ctx->decoder->color_trc!=AVCOL_TRC_UNSPECIFIED)
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_TRANSFER, &PROP_UINT(ctx->decoder->color_trc) );
		else
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_TRANSFER, NULL );
	}
}

static GF_Err ffdec_process_video(GF_Filter *filter, struct _gf_ffdec_ctx *ctx)
{
	AVPacket *pkt;
	AVFrame *frame;
	u32 dst_stride[5];
	u8 *dst_planes[5];
	u32 pix_out;
	Bool is_eos=GF_FALSE;
	s32 res;
	s32 gotpic;
	u64 out_cts;
	const char *data = NULL;
	Bool seek_flag = GF_FALSE;
	u32 i, count, ff_pfmt;
	u32 size=0, outsize, stride, stride_uv, uv_height, nb_planes;
	u8 *out_buffer;
	GF_FilterPacket *pck_src;
	GF_FilterPacket *dst_pck;
	GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->in_pid);

	if (ctx->reconfig_pending) {
		pck = NULL;
	} else if (!pck) {
		is_eos = gf_filter_pid_is_eos(ctx->in_pid);
		if (!is_eos) return GF_OK;
	}

	if (pck && ctx->drop_non_refs && !gf_filter_pck_get_sap(pck)) {
		gf_filter_pid_drop_packet(ctx->in_pid);
		return GF_OK;
	}
    //we don't own the codec and we're in end of stream, don't try to decode (the context might have been closed)
    if (!pck && !ctx->owns_context) {
        gf_filter_pid_set_eos(ctx->out_pid);
        return GF_EOS;
    }

	frame = ctx->frame;

	FF_INIT_PCK(ctx, pkt)

	if (pck) {
		data = gf_filter_pck_get_data(pck, &size);

		if (!size) {
			gf_filter_pid_drop_packet(ctx->in_pid);
			FF_RELEASE_PCK(pkt)
			return GF_OK;
		}

		pck_src = pck;
		gf_filter_pck_ref_props(&pck_src);
		if (pck_src) gf_list_add(ctx->src_packets, pck_src);

		//seems ffmpeg is not properly handling the decoding after a flush, we close and reopen the codec
		if (ctx->flush_done) {
#if 0
			AVDictionary *options = NULL;
			const AVCodec *codec = ctx->decoder->codec;
			avcodec_free_context(&ctx->decoder);

			av_dict_copy(&options, ctx->options, 0);
			avcodec_open2(ctx->decoder, codec, &options );
			if (options) av_dict_free(&options);
#else
			avcodec_flush_buffers(ctx->decoder);
#endif
			ctx->flush_done = GF_FALSE;
		}

		pkt->dts = gf_filter_pck_get_dts(pck);
		pkt->pts = gf_filter_pck_get_cts(pck);
		pkt->duration = gf_filter_pck_get_duration(pck);
		if (gf_filter_pck_get_sap(pck)>0)
			pkt->flags = AV_PKT_FLAG_KEY;
	}
	pkt->data = (uint8_t*)data;
	pkt->size = size;

	/*TOCHECK: for AVC bitstreams after ISMA decryption, in case (as we do) the decryption DRM tool
	doesn't put back nalu size, we have to do it ourselves, but we can't modify input data...*/

	gotpic=0;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
	res = avcodec_decode_video2(ctx->decoder, frame, &gotpic, pkt);
#else
	res = avcodec_send_packet(ctx->decoder, pkt);
	switch (res) {
	case AVERROR(EAGAIN):
		if (pck_src) {
			gf_list_del_item(ctx->src_packets, pck_src);
			gf_filter_pck_unref(pck_src);
		}
		FF_RELEASE_PCK(pkt);
		//input full but output available, go on but don't discard input
		pck = NULL;
		break;
	case 0:
	case AVERROR_EOF:
		break;
	default:
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to send frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(res) ));
		break;
	}
	gotpic = 0;
	res = avcodec_receive_frame(ctx->decoder, frame);
	switch (res) {
	case AVERROR(EAGAIN):
		res = 0;
		break;
	case 0:
		gotpic = 1;
		break;
	case AVERROR_EOF:
		res = 0;
		break;
	}
#endif


	if (pck) gf_filter_pid_drop_packet(ctx->in_pid);


	if (!gotpic) {
		FF_RELEASE_PCK(pkt);
		if (is_eos) {
			ctx->flush_done = GF_TRUE;
			gf_filter_pid_set_eos(ctx->out_pid);
			return GF_EOS;
		}
		if (ctx->reconfig_pending) {
			GF_Err e;
			avcodec_free_context(&ctx->decoder);
			ctx->decoder = NULL;
			ctx->reconfig_pending = GF_FALSE;
			//these properties are checked after decode, when we reconfigure we copy props from input to output
			//so we need to make sure we retrigger pid config even if these did not change
			ctx->pixel_fmt = 0;
			ctx->width = 0;
			ctx->height = 0;
			ctx->stride = 0;
			ctx->stride_uv = 0;
			ctx->sar.num = ctx->sar.den = 0;
			while (gf_list_count(ctx->src_packets)) {
				GF_FilterPacket *ref_pck = gf_list_pop_back(ctx->src_packets);
				gf_filter_pck_unref(ref_pck);
			}
			GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFDec] PID %s reconfigure pending and all frames flushed, reconfiguring\n", gf_filter_pid_get_name(ctx->in_pid) ));
			e = ffdec_configure_pid(filter, ctx->in_pid, GF_FALSE);
			if (e==GF_NOT_SUPPORTED)
				return GF_PROFILE_NOT_SUPPORTED;
			return e;
		}
	}

	if (res < 0) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(res) ));
		FF_RELEASE_PCK(pkt);
		return GF_NON_COMPLIANT_BITSTREAM;
	}
	FF_RELEASE_PCK(pkt);
	if (!gotpic) return GF_OK;

	if (ctx->decoder->pix_fmt != ctx->o_ff_pfmt) {
		u32 pix_fmt = ffmpeg_pixfmt_to_gpac(ctx->decoder->pix_fmt);
		ctx->o_ff_pfmt = ctx->decoder->pix_fmt;
		if (!pix_fmt) {
			pix_fmt = GF_PIXEL_RGB;
			ctx->o_ff_pfmt = AV_PIX_FMT_RGB24;
		}
		ffdec_check_pix_fmt_change(ctx, pix_fmt);
	}
	//update all props
	FF_CHECK_PROP(width, width, GF_PROP_PID_WIDTH)
	FF_CHECK_PROP(height, height, GF_PROP_PID_HEIGHT)

	stride = stride_uv = uv_height = nb_planes = 0;
	if (! gf_pixel_get_size_info(ctx->pixel_fmt, ctx->width, ctx->height, &outsize, &stride, &stride_uv, &nb_planes, &uv_height) ) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to query pixelformat size infon", gf_filter_pid_get_name(ctx->in_pid) ));
		return GF_NOT_SUPPORTED;
	}

	FF_CHECK_PROP_VAL(stride, stride, GF_PROP_PID_STRIDE)
	FF_CHECK_PROP_VAL(stride_uv, stride_uv, GF_PROP_PID_STRIDE_UV)
	if (ctx->sar.num * ctx->decoder->sample_aspect_ratio.den != ctx->sar.den * ctx->decoder->sample_aspect_ratio.num) {
		ctx->sar.num = ctx->decoder->sample_aspect_ratio.num;
		ctx->sar.den = ctx->decoder->sample_aspect_ratio.den;

		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_SAR, &PROP_FRAC( ctx->sar ) );
	}

	pck_src = NULL;
	count = gf_list_count(ctx->src_packets);
	for (i=0; i<count; i++) {
		u64 cts;
		pck_src = gf_list_get(ctx->src_packets, i);
		cts = gf_filter_pck_get_cts(pck_src);
		if (cts == frame->pts)
			break;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
		if (cts == frame->pkt_pts)
			break;
#endif
		pck_src = NULL;
	}

	seek_flag = GF_FALSE;
	if (pck_src) {
		seek_flag = gf_filter_pck_get_seek_flag(pck_src);
		out_cts = gf_filter_pck_get_cts(pck_src);
	} else {
		out_cts = frame->pts;
	}
	//this was a seek frame, do not dispatch
	if (seek_flag) {
		if (pck_src) {
			gf_list_del_item(ctx->src_packets, pck_src);
			gf_filter_pck_unref(pck_src);
		}
		return GF_OK;
	}

	dst_pck = gf_filter_pck_new_alloc(ctx->out_pid, outsize, &out_buffer);
	if (!dst_pck) return GF_OUT_OF_MEM;

	if (pck_src) {
		gf_filter_pck_merge_properties(pck_src, dst_pck);
		gf_filter_pck_set_dependency_flags(dst_pck, 0);
		gf_list_del_item(ctx->src_packets, pck_src);
		gf_filter_pck_unref(pck_src);
	} else {
		gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1);
	}

    //rewrite dts and pts to PTS value
    gf_filter_pck_set_dts(dst_pck, out_cts);
    gf_filter_pck_set_cts(dst_pck, out_cts);

	ff_pfmt = ctx->decoder->pix_fmt;
	if (ff_pfmt==AV_PIX_FMT_YUVJ420P) {
		ff_pfmt = AV_PIX_FMT_YUV420P;
		if (!ctx->force_full_range) {
			ctx->force_full_range = GF_TRUE;
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_RANGE, &PROP_BOOL(GF_TRUE));
		}
	}

	memset(&dst_planes, 0, sizeof(u8 *)*5);
	memset(&dst_stride, 0, sizeof(u32)*5);

	//TODO: cleanup, we should not convert pixel format in the decoder but through filters !
	switch (ctx->pixel_fmt) {
	case GF_PIXEL_RGB:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_stride[0] = 3*ctx->width;
		pix_out = AV_PIX_FMT_RGB24;
		break;
	case GF_PIXEL_RGBA:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_stride[0] = 4*ctx->width;
		pix_out = AV_PIX_FMT_RGBA;
		break;
	case GF_PIXEL_YUV:
	case GF_PIXEL_YUV_10:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_planes[1] =  (uint8_t *)out_buffer + ctx->stride * ctx->height;
		dst_planes[2] =  (uint8_t *)dst_planes[1] + ctx->stride_uv * uv_height;
		dst_stride[0] = ctx->stride;
		dst_stride[1] = dst_stride[2] = ctx->stride_uv;
		if (ctx->pixel_fmt == GF_PIXEL_YUV_10)
			pix_out = AV_PIX_FMT_YUV420P10LE;
		else
			pix_out = AV_PIX_FMT_YUV420P;
		break;

	case GF_PIXEL_YUV422:
	case GF_PIXEL_YUV422_10:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_planes[1] =  (uint8_t *)out_buffer + ctx->stride * ctx->height;
		dst_planes[2] =  (uint8_t *)dst_planes[1] + ctx->stride_uv * ctx->height;
		dst_stride[0] = ctx->stride;
		dst_stride[1] = dst_stride[2] = ctx->stride_uv;
		if (ctx->pixel_fmt == GF_PIXEL_YUV422_10)
			pix_out = AV_PIX_FMT_YUV422P10LE;
		else
			pix_out = AV_PIX_FMT_YUV422P;
		break;

	case GF_PIXEL_YUV444:
	case GF_PIXEL_YUV444_10:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_planes[1] =  (uint8_t *)out_buffer + ctx->stride * ctx->height;
		dst_planes[2] =  (uint8_t *)out_buffer + 2*ctx->stride * ctx->height;
		dst_stride[0] = dst_stride[1] = dst_stride[2] = ctx->stride;
		if (ctx->pixel_fmt == GF_PIXEL_YUV444_10)
			pix_out = AV_PIX_FMT_YUV444P10LE;
		else
			pix_out = AV_PIX_FMT_YUV444P;
		break;

	default:
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Unsupported pixel format %s, patch welcome\n", av_get_pix_fmt_name(ctx->decoder->pix_fmt) ));

		gf_filter_pck_discard(dst_pck);

		return GF_NOT_SUPPORTED;
	}

	ctx->sws_ctx = sws_getCachedContext(ctx->sws_ctx,
	                                   ctx->decoder->width, ctx->decoder->height, ff_pfmt,
	                                   ctx->width, ctx->height, pix_out, SWS_BICUBIC, NULL, NULL, NULL);
	if (ctx->sws_ctx) {
		sws_scale(ctx->sws_ctx, (const uint8_t * const*)frame->data, frame->linesize, 0, ctx->height, dst_planes, dst_stride);
	}


	gf_filter_pck_set_seek_flag(dst_pck, GF_FALSE);

	if (frame->interlaced_frame)
		gf_filter_pck_set_interlaced(dst_pck, frame->top_field_first ? 2 : 1);

	gf_filter_pck_send(dst_pck);
	return GF_OK;
}


static GF_Err ffdec_process_audio(GF_Filter *filter, struct _gf_ffdec_ctx *ctx)
{
	s32 gotpic;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
	s32 len;
#endif
	AVPacket *pkt;
	s32 res, in_size, i;
	u32 samples_to_trash;
	u32 output_size, prev_afmt;
	Bool is_eos=GF_FALSE;
	u8 *data;
	AVFrame *frame;
	GF_FilterPacket *dst_pck, *src_pck;
	GF_FilterPacket *pck;

decode_next:
	pck = gf_filter_pid_get_packet(ctx->in_pid);
	in_size = 0;
	
	if (ctx->reconfig_pending) {
		pck = NULL;
	} else if (!pck) {
		is_eos = gf_filter_pid_is_eos(ctx->in_pid);
		if (!is_eos) return GF_OK;
	}

	FF_INIT_PCK(ctx, pkt)

	if (pck) pkt->data = (uint8_t *) gf_filter_pck_get_data(pck, &in_size);

	if (pck) {
		src_pck = pck;
		gf_filter_pck_ref_props(&src_pck);
		if (src_pck) gf_list_add(ctx->src_packets, src_pck);

		if (!pkt->data) {
			gf_filter_pid_drop_packet(ctx->in_pid);
			return GF_OK;
		}

		pkt->pts = gf_filter_pck_get_cts(pck);
		pkt->dts = gf_filter_pck_get_dts(pck);

		pkt->size = in_size;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
		if ((s32) ctx->frame_start > pkt->size) ctx->frame_start = 0;
		//seek to last byte consumed by the previous decode4()
		else if (ctx->frame_start) {
			pkt->data += ctx->frame_start;
			pkt->size -= ctx->frame_start;
		}
#endif

		pkt->duration = gf_filter_pck_get_duration(pck);
		if (gf_filter_pck_get_sap(pck)>0)
			pkt->flags = AV_PKT_FLAG_KEY;

		if (!ctx->first_cts_plus_one)
			ctx->first_cts_plus_one = pkt->pts + 1;

	} else {
		pkt->size = 0;
	}

	prev_afmt = ctx->decoder->sample_fmt;
	frame = ctx->frame;

#if (LIBAVCODEC_VERSION_MAJOR < 59)
	len = res = avcodec_decode_audio4(ctx->decoder, frame, &gotpic, pkt);
#else
	res = avcodec_send_packet(ctx->decoder, pkt);
	switch (res) {
	case AVERROR(EAGAIN):
		if (src_pck) {
			gf_list_del_item(ctx->src_packets, src_pck);
			gf_filter_pck_unref(src_pck);
		}
		av_packet_unref(pkt);
		return GF_OK;
	case 0:
	case AVERROR_EOF:
		break;
	default:
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(res) ));
		break;
	}

	gotpic = 0;
	res = avcodec_receive_frame(ctx->decoder, frame);
	switch (res) {
	case AVERROR(EAGAIN):
		res = 0;
		break;
	case 0:
		gotpic = 1;
		break;
	case AVERROR_EOF:
		res = 0;
		break;
	default:
		break;
	}
dispatch_next:
#endif

	samples_to_trash = 0;
	if ( gotpic && (ctx->delay<0)) {
		if (pkt->pts + 1 - ctx->first_cts_plus_one + ctx->delay < 0) {
			if (pkt->duration + ctx->delay < 0) {
				frame->nb_samples = 0;
				samples_to_trash = 0;
				ctx->delay += pkt->duration;
			} else {
				samples_to_trash = (u32) -ctx->delay;
			}
			
			if (!samples_to_trash || (samples_to_trash > (u32) frame->nb_samples) ) {
				frame->nb_samples = 0;
				samples_to_trash = 0;
			}
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DELAY, NULL);
		} else {
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DELAY, NULL);
			ctx->delay = 0;
		}
	}

	//this will handle eos as well
	if ((res<0) || !gotpic) {
#if (LIBAVCODEC_VERSION_MAJOR < 59)
		ctx->frame_start = 0;
#endif
		if (pck) gf_filter_pid_drop_packet(ctx->in_pid);
		if (pkt->size && (res<0)) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(res) ));
			FF_RELEASE_PCK(pkt);
			return GF_NON_COMPLIANT_BITSTREAM;
		}
		FF_RELEASE_PCK(pkt);
		if (is_eos) {
			gf_filter_pid_set_eos(ctx->out_pid);
			return GF_EOS;
		}
		if (ctx->reconfig_pending) {
			GF_Err e;
			avcodec_free_context(&ctx->decoder);
			ctx->decoder = NULL;
			ctx->reconfig_pending = GF_FALSE;
			//these properties are checked after decode, when we reconfigure we copy props from input to output
			//so we need to make sure we retrigger pid config even if these did not change
			ctx->sample_fmt = 0;
			ctx->sample_rate = 0;
			ctx->channels = 0;
			ctx->channel_layout = 0;
			GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFDec] PID %s reconfigure pending and all frames flushed, reconfiguring\n", gf_filter_pid_get_name(ctx->in_pid) ));
			e = ffdec_configure_pid(filter, ctx->in_pid, GF_FALSE);
			if (e==GF_NOT_SUPPORTED)
				return GF_PROFILE_NOT_SUPPORTED;
			return e;
		}
		return GF_OK;
	}
	FF_RELEASE_PCK(pkt);


	FF_CHECK_PROP(channels, channels, GF_PROP_PID_NUM_CHANNELS)
	FF_CHECK_PROPL(channel_layout, channel_layout, GF_PROP_PID_CHANNEL_LAYOUT)
	FF_CHECK_PROP(sample_rate, sample_rate, GF_PROP_PID_SAMPLE_RATE)

	if (prev_afmt != ctx->decoder->sample_fmt) {
		ctx->sample_fmt = ffmpeg_audio_fmt_to_gpac(ctx->decoder->sample_fmt);
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(ctx->sample_fmt) );
		ctx->bytes_per_sample = gf_audio_fmt_bit_depth(ctx->sample_fmt) / 8;
	}

	if (pck) {
		const GF_PropertyValue *er = gf_filter_pck_get_property(pck, GF_PROP_PCK_END_RANGE);
		if (er && er->value.boolean) {
			u32 timescale = gf_filter_pck_get_timescale(pck);
			u64 odur = gf_filter_pck_get_duration(pck);
			if (timescale != ctx->sample_rate) {
				odur = gf_timestamp_rescale(odur, timescale, ctx->sample_rate);
			}
			if (odur < frame->nb_samples) {
				frame->nb_samples = (int) odur;
			}
		}
	}

	output_size = (frame->nb_samples - samples_to_trash) * ctx->channels * ctx->bytes_per_sample;
	dst_pck = gf_filter_pck_new_alloc(ctx->out_pid, output_size, &data);
	if (!dst_pck) return GF_OUT_OF_MEM;

	switch (frame->format) {
	case AV_SAMPLE_FMT_U8P:
	case AV_SAMPLE_FMT_S16P:
	case AV_SAMPLE_FMT_S32P:
	case AV_SAMPLE_FMT_FLTP:
	case AV_SAMPLE_FMT_DBLP:
		for (i=0; (u32) i< ctx->channels; i++) {
			char *inputChannel = frame->extended_data[i] + samples_to_trash * ctx->bytes_per_sample;
			memcpy(data, inputChannel, ctx->bytes_per_sample * (frame->nb_samples-samples_to_trash) );
			data += ctx->bytes_per_sample * (frame->nb_samples-samples_to_trash);
		}
		break;
	default:
		memcpy(data, ctx->frame->data[0] + samples_to_trash * ctx->bytes_per_sample, ctx->bytes_per_sample * (frame->nb_samples - samples_to_trash) * ctx->channels);
		break;
	}

	//we don't follow the same approach as in video, we assume the codec works with one in one out
	//and use the first entry in src packets to match in order to copy the properties
	//a nicer approach would be to count delay frames (number of frames used to initialize)
	//and backmerge properties from the last packet in to the last-nb_init_frames
	src_pck = gf_list_get(ctx->src_packets, 0);

	if (src_pck) {
		gf_filter_pck_merge_properties(src_pck, dst_pck);
		gf_filter_pck_set_dependency_flags(dst_pck, 0);
		gf_list_rem(ctx->src_packets, 0);
		gf_filter_pck_unref(src_pck);
	}

	if (output_size) {
		if (frame->pts != AV_NOPTS_VALUE) {
			u64 pts = frame->pts;
			u32 timescale = gf_filter_pck_get_timescale(pck);
			if (ctx->nb_samples_already_in_frame) {
				if (ctx->sample_rate == timescale) {
					pts += ctx->nb_samples_already_in_frame;
				}
			}
			if (pts >= ctx->ts_offset) pts -= ctx->ts_offset;
			else pts = 0;
			gf_filter_pck_set_cts(dst_pck, pts);
			gf_filter_pck_set_dts(dst_pck, pts);
		}
		gf_filter_pck_send(dst_pck);
	} else {
		gf_filter_pck_discard(dst_pck);
	}

#if (LIBAVCODEC_VERSION_MAJOR < 59)
	ctx->frame_start += len;
	//done with this input packet
	if (in_size <= (s32) ctx->frame_start) {
		frame->nb_samples = 0;
		ctx->frame_start = 0;
		ctx->nb_samples_already_in_frame = 0;
		gf_filter_pid_drop_packet(ctx->in_pid);

		if (gf_filter_pid_would_block(ctx->out_pid))
			return GF_OK;

		//if space available in ouput, decode right away - needed for audio formats with very short frames
		//avoid recursion
		goto decode_next;
	}
	//still some data to decode in packet, don't drop it
	//todo: check if frame->pkt_pts or frame->pts is updated by ffmpeg, otherwise do it ourselves !
	GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Code not yet tested  - frame PTS was "LLU" - nb samples dec %d\n", frame->pkt_pts, frame->nb_samples));
	ctx->nb_samples_already_in_frame += frame->nb_samples;
	frame->nb_samples = 0;
#else
	//if multiple frames in packet, send them
	gotpic = 0;
	res = avcodec_receive_frame(ctx->decoder, frame);
	if (res==0) {
		gotpic = 1;
		goto dispatch_next;
	}
	if (pck) gf_filter_pid_drop_packet(ctx->in_pid);
#endif

	//avoid recursion
	goto decode_next;

//	return ffdec_process_audio(filter, ctx);
}

#ifdef FF_SUB_SUPPORT
static GF_Err ffdec_process_subtitle(GF_Filter *filter, struct _gf_ffdec_ctx *ctx)
{
	AVPacket *pkt;
	AVSubtitle subs;
	s32 gotpic;
	s32 len, in_size;
	Bool is_eos=GF_FALSE;

	GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->in_pid);

	if (!pck) {
		is_eos = gf_filter_pid_is_eos(ctx->in_pid);
		if (!is_eos) return GF_OK;
	}
	FF_INIT_PCK(ctx, pkt);

	if (pck) pkt->data = (uint8_t *) gf_filter_pck_get_data(pck, &in_size);

	if (!is_eos) {
		u64 dts;
		pkt->pts = gf_filter_pck_get_cts(pck);

		//copy over SAP and duration in dts
		dts = gf_filter_pck_get_sap(pck);
		dts <<= 32;
		dts |= gf_filter_pck_get_duration(pck);
		pkt->dts = dts;

		pkt->size = in_size;
	} else {
		pkt->size = 0;
	}

	memset(&subs, 0, sizeof(AVSubtitle));
	len = avcodec_decode_subtitle2(ctx->decoder, &subs, &gotpic, pkt);

	//this will handle eos as well
	if ((len<0) || !gotpic) {
		FF_RELEASE_PCK(pkt)
		if (pck) gf_filter_pid_drop_packet(ctx->in_pid);
		if (len<0) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(len) ));
			return GF_NON_COMPLIANT_BITSTREAM;
		}
		if (is_eos) {
			gf_filter_pid_set_eos(ctx->out_pid);
			return GF_EOS;
		}
		return GF_OK;
	}
	//TODO - do we want to remap to TX3G/other and handle the rendering some place else, or do we do the rendering here ?


	avsubtitle_free(&subs);
	if (pck) gf_filter_pid_drop_packet(ctx->in_pid);
	FF_RELEASE_PCK(pkt)
	return GF_OK;
}
#endif

static GF_Err ffdec_process(GF_Filter *filter)
{
	GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter);
	if (gf_filter_pid_would_block(ctx->out_pid))
		return GF_OK;

	return ctx->process(filter, ctx);
}

static const GF_FilterCapability FFDecodeAnnexBCaps[] =
{
	CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
	CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_UNFRAMED, GF_TRUE),
	CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_VVC),
	CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_AVC),
	CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_HEVC),
	CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW),
};


static GF_Err ffdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
{
	s32 res;
	u32 type=0, gpac_codecid=0;
	AVDictionary *options = NULL;
	const GF_PropertyValue *prop;
	const AVCodec *codec=NULL;
	GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter);

	//disconnect of src pid (not yet supported)
	if (is_remove) {
		//one in one out, this is simple
		if (ctx->out_pid) {
			gf_filter_pid_remove(ctx->out_pid);
			ctx->out_pid = NULL;
		}
		return GF_OK;
	}

	//check our PID: streamtype and codecid
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
	if (!prop) return GF_NOT_SUPPORTED;

	type = prop->value.uint;
	switch (type) {
	case GF_STREAM_AUDIO:
	case GF_STREAM_VISUAL:
#ifdef FF_SUB_SUPPORT
	case GF_STREAM_TEXT:
#endif
		break;
	default:
		return GF_NOT_SUPPORTED;
	}
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
	if (!prop) return GF_NOT_SUPPORTED;
	gpac_codecid = prop->value.uint;
	if (gpac_codecid==GF_CODECID_RAW)
		return GF_NOT_SUPPORTED;


	//initial config or update
	if (!ctx->in_pid || (ctx->in_pid==pid)) {
		ctx->in_pid = pid;
		if (!ctx->type) ctx->type = type;
		else if (ctx->type != type) {
			return GF_NOT_SUPPORTED;
		}
	} else {
		//only one input pid in ctx
		if (ctx->in_pid) return GF_REQUIRES_NEW_INSTANCE;
	}

	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
	ctx->width = prop ? prop->value.uint : 0;
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
	ctx->height = prop ? prop->value.uint : 0;
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE);
	ctx->sample_rate = prop ? prop->value.uint : 0;
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS);
	ctx->channels = prop ? prop->value.uint : 0;

	if (gpac_codecid==GF_CODECID_VVC) {
		u32 codec_id = ffmpeg_codecid_from_gpac(gpac_codecid, NULL);
		codec = codec_id ? avcodec_find_decoder(codec_id) : NULL;
		//libvvdec only supports annexB, request ufnalu adaptation filter
		if (codec && codec->name && strstr(codec->name, "vvdec")) {
			prop = gf_filter_pid_get_property(pid, GF_PROP_PID_UNFRAMED);
			if (!prop || !prop->value.boolean) {
				gf_filter_override_caps(filter, FFDecodeAnnexBCaps, GF_ARRAY_LENGTH(FFDecodeAnnexBCaps));
				gf_filter_pid_negociate_property(ctx->in_pid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) );
				return GF_OK;
			}
		}
	}


	if (gpac_codecid == GF_CODECID_FFMPEG) {
		prop = gf_filter_pid_get_property(pid, GF_FFMPEG_DECODER_CONFIG);
		if (prop && (prop->type==GF_PROP_POINTER)) {
			ctx->decoder = prop->value.ptr;
			if (!ctx->decoder) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s codec context not exposed by demuxer !\n", gf_filter_pid_get_name(pid) ));
				return GF_SERVICE_ERROR;
			}
			codec = avcodec_find_decoder(ctx->decoder->codec_id);
			ctx->owns_context = GF_FALSE;
		}
		else if (prop && (prop->type==GF_PROP_UINT)) {
			ctx->decoder = avcodec_alloc_context3(NULL);
			if (! ctx->decoder) return GF_OUT_OF_MEM;
			codec = avcodec_find_decoder(prop->value.uint);
		}
		else {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s codec context not exposed by demuxer !\n", gf_filter_pid_get_name(pid) ));
			return GF_SERVICE_ERROR;
		}

		if (!codec) {
			gf_filter_set_name(filter, "ffdec");
			return GF_NOT_SUPPORTED;
		}
	}
	//we reconfigure the stream
	else {
		u32 codec_id, ff_codectag=0;
        
        if (!ctx->owns_context) {
            ctx->decoder = NULL;
        }
		if (ctx->decoder) {
			codec_id = ffmpeg_codecid_from_gpac(gpac_codecid, NULL);
			//same codec, same config, don't reinit
			if (ctx->decoder->codec->id == codec_id) {
				u32 cfg_crc=0;
				prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG);
				if (prop && prop->value.data.ptr && prop->value.data.size) {
					cfg_crc = gf_crc_32(prop->value.data.ptr, prop->value.data.size);
				}
				if (cfg_crc == ctx->extra_data_crc) {
					goto reuse_codec_context;
				}
			}

            
			//we could further optimize by detecting we have the same codecid and injecting the extradata
			//but this is not 100% reliable, and will require parsing AVC/HEVC config
			//since this seems to work properly with decoder close/open, we keep it as is
			ctx->reconfig_pending = GF_TRUE;
			GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFDec] PID %s reconfigure detected, flushing frame\n", gf_filter_pid_get_name(pid) ));
			return GF_OK;
		}

		codec_id = ffmpeg_codecid_from_gpac(gpac_codecid, &ff_codectag);
		//specific remaps
		if (!codec_id) {
			switch (gpac_codecid) {
			case GF_CODECID_MVC: codec_id = AV_CODEC_ID_H264; break;
			}

		}
		if (codec_id) codec = avcodec_find_decoder(codec_id);
		if (!codec) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] No decoder found for codec %s\n", gf_codecid_name(gpac_codecid) ));
			gf_filter_set_name(filter, "ffdec");
			return GF_NOT_SUPPORTED;
		}

		ctx->decoder = avcodec_alloc_context3(NULL);
		if (! ctx->decoder) return GF_OUT_OF_MEM;
		ctx->owns_context = GF_TRUE;
		if (ff_codectag)
			ctx->decoder->codec_tag = ff_codectag;

		ffmpeg_set_enc_dec_flags(ctx->options, ctx->decoder);

		//for some raw codecs
		if (ctx->width && ctx->height) {
			ctx->decoder->width = ctx->width;
			ctx->decoder->height = ctx->height;
		}
		if (ctx->sample_rate && ctx->channels) {
			ctx->decoder->sample_rate = ctx->sample_rate;
			ctx->decoder->channels = ctx->channels;
		}

		//we may have a dsi here!
		prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG);
		if (prop && prop->value.data.ptr && prop->value.data.size) {
			//looks like ffmpeg wants the fLaC keyword
			if (gpac_codecid==GF_CODECID_FLAC) {
				ctx->decoder->extradata_size = prop->value.data.size+4;
				ctx->decoder->extradata = av_malloc(sizeof(char) * prop->value.data.size+4);
				memcpy(ctx->decoder->extradata, "fLaC", 4);
				memcpy(ctx->decoder->extradata+4, prop->value.data.ptr, prop->value.data.size);
			} else {
				ctx->decoder->extradata_size = prop->value.data.size;
				ctx->decoder->extradata = av_malloc(sizeof(char) * prop->value.data.size);
				memcpy(ctx->decoder->extradata, prop->value.data.ptr, prop->value.data.size);
			}
			ctx->extra_data_crc = gf_crc_32(prop->value.data.ptr, prop->value.data.size);
		}
	}

	//by default let libavcodec decide - if single thread is required, let the user define -threads option
	if (codec->capabilities & AV_CODEC_CAP_AUTO_THREADS)
		ctx->decoder->thread_count = 0;

	//clone options (in case we need to destroy/recreate the codec) and open codec
	av_dict_copy(&options, ctx->options, 0);
	res = avcodec_open2(ctx->decoder, codec, &options);
	if (res < 0) {
		if (options) av_dict_free(&options);
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to open codec context: %s\n", gf_filter_pid_get_name(pid), av_err2str(res) ));
		return GF_NON_COMPLIANT_BITSTREAM;
	}

	ffmpeg_report_options(filter, options, ctx->options);

	//we're good to go, declare our output pid
	ctx->in_pid = pid;
	if (!ctx->out_pid) {
		char szCodecName[1000];
		ctx->out_pid = gf_filter_pid_new(filter);

		//to change once we implement on-the-fly codec change
		sprintf(szCodecName, "ffdec:%s", ctx->decoder->codec->name ? ctx->decoder->codec->name : "unknown");
		gf_filter_set_name(filter, szCodecName);
		gf_filter_pid_set_framing_mode(ctx->in_pid, GF_TRUE);
	}

reuse_codec_context:
	//copy props it at init config or at reconfig
	if (ctx->out_pid) {
		gf_filter_pid_copy_properties(ctx->out_pid, ctx->in_pid);
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) );
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DECODER_CONFIG, NULL );
	}

	if (type==GF_STREAM_VISUAL) {
		u32 pix_fmt;
		ctx->force_full_range = GF_FALSE;
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_RANGE, NULL );
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_MX, NULL );
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_TRANSFER, NULL );
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_PRIMARIES, NULL );

		ctx->process = ffdec_process_video;
		//for some streams, we don't have w/h/pixfmt after opening the decoder
		//to make sure we are not confusing potential filters expecting them, init to default values
		if (ctx->decoder->pix_fmt>=0) {
			ctx->o_ff_pfmt = ctx->decoder->pix_fmt;
			pix_fmt = ffmpeg_pixfmt_to_gpac(ctx->decoder->pix_fmt);
			if (!pix_fmt) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FFDec] Unsupported pixel format %d, defaulting to RGB\n", ctx->decoder->pix_fmt));
				pix_fmt = GF_PIXEL_RGB;
				ctx->o_ff_pfmt = AV_PIX_FMT_RGB24;
			}
		} else {
			pix_fmt = GF_PIXEL_YUV;
			ctx->o_ff_pfmt = AV_PIX_FMT_YUV420P;
		}
		ffdec_check_pix_fmt_change(ctx, pix_fmt);

		if (ctx->decoder->width) {
			FF_CHECK_PROP(width, width, GF_PROP_PID_WIDTH)
		} else {
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_WIDTH, &PROP_UINT( ctx->width) );
		}
		if (ctx->decoder->height) {
			FF_CHECK_PROP(height, height, GF_PROP_PID_HEIGHT)
		} else {
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_HEIGHT, &PROP_UINT( ctx->height) );
		}
		if (ctx->decoder->sample_aspect_ratio.num && ctx->decoder->sample_aspect_ratio.den) {
			ctx->sar.num = ctx->decoder->sample_aspect_ratio.num;
			ctx->sar.den = ctx->decoder->sample_aspect_ratio.den;
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_SAR, &PROP_FRAC( ctx->sar) );
		}
		if (!ctx->frame)
			ctx->frame = av_frame_alloc();
<commit_msg>avoid repeated warning of unsupported pixel format<commit_after>/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: <NAME>
 *			Copyright (c) Telecom ParisTech 2017-2021
 *					All rights reserved
 *
 *  This file is part of GPAC / ffmpeg decode filter
 *
 *  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 <gpac/setup.h>

#ifdef GPAC_HAS_FFMPEG

#include "ff_common.h"

#include <libswscale/swscale.h>

#define FF_CHECK_PROP(_name, _ffname, _type)	if (ctx->_name != ctx->decoder->_ffname) { \
		gf_filter_pid_set_property(ctx->out_pid, _type, &PROP_UINT( (u32) ctx->decoder->_ffname ) );	\
		ctx->_name = (u32) ctx->decoder->_ffname;	\
	} \

#define FF_CHECK_PROPL(_name, _ffname, _type)	if (ctx->_name != ctx->decoder->_ffname) { \
	gf_filter_pid_set_property(ctx->out_pid, _type, &PROP_LONGUINT( (u32) ctx->decoder->_ffname ) );	\
	ctx->_name = (u32) ctx->decoder->_ffname;	\
} \

#define FF_CHECK_PROP_VAL(_name, _val, _type)	if (ctx->_name != _val) { \
		gf_filter_pid_set_property(ctx->out_pid, _type, &PROP_UINT( _val ) );	\
		ctx->_name = _val;	\
	} \

static GF_Err ffdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove);


typedef struct _gf_ffdec_ctx
{
	//internal data
	Bool initialized;

	Bool owns_context;
	AVCodecContext *decoder;
	//decode options
	AVDictionary *options;

	Bool reconfig_pending;

	GF_FilterPid *in_pid, *out_pid;
	//media type
	u32 type;
	//CRC32 of extra_data
	u32 extra_data_crc;

	GF_Err (*process)(GF_Filter *filter, struct _gf_ffdec_ctx *ctx);

	u32 flush_done;

	//for now we don't share the data
	AVFrame *frame;
	//audio state
	u32 channels, sample_rate, sample_fmt, bytes_per_sample;
	u64 channel_layout;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
	u32 frame_start;
#endif
	u32 nb_samples_already_in_frame;

	//video state
	u32 width, height, pixel_fmt, stride, stride_uv;
	GF_Fraction sar;
	struct SwsContext *sws_ctx;

	GF_List *src_packets;

	//only used to check decoder output change
	u32 o_ff_pfmt;
	Bool force_full_range;
	Bool drop_non_refs;

	s64 first_cts_plus_one;
	s64 delay;
	u32 ts_offset;

#if (LIBAVCODEC_VERSION_MAJOR < 59)
	AVPacket pkt;
#else
	AVPacket *pkt;
#endif

} GF_FFDecodeCtx;

static GF_Err ffdec_initialize(GF_Filter *filter)
{
	GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter);
	ctx->initialized = GF_TRUE;
	ctx->src_packets = gf_list_new();

#if (LIBAVCODEC_VERSION_MAJOR >= 59)
	ctx->pkt = av_packet_alloc();
#endif

	ffmpeg_setup_logs(GF_LOG_CODEC);
	return GF_OK;
}

static void ffdec_finalize(GF_Filter *filter)
{
	GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter);

	if (ctx->options) av_dict_free(&ctx->options);
	if (ctx->frame) av_frame_free(&ctx->frame);
	if (ctx->sws_ctx) sws_freeContext(ctx->sws_ctx);

	while (gf_list_count(ctx->src_packets)) {
		GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets);
		gf_filter_pck_unref(pck);
	}
	gf_list_del(ctx->src_packets);

#if (LIBAVCODEC_VERSION_MAJOR >= 59)
	av_packet_free(&ctx->pkt);
#endif

	if (ctx->owns_context && ctx->decoder) {
		avcodec_free_context(&ctx->decoder);
	}
	return;
}

static void ffdec_check_pix_fmt_change(struct _gf_ffdec_ctx *ctx, u32 pix_fmt)
{
	if (ctx->pixel_fmt != pix_fmt) {
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_PIXFMT, &PROP_UINT(pix_fmt));
		ctx->pixel_fmt = pix_fmt;
		ctx->force_full_range = GF_FALSE;
		if (ctx->decoder->color_range==AVCOL_RANGE_JPEG)
			ctx->force_full_range = GF_TRUE;
		else if (ffmpeg_pixfmt_is_fullrange(ctx->decoder->pix_fmt))
			ctx->force_full_range = GF_TRUE;
		else
			ctx->force_full_range = GF_FALSE;

		if (ctx->force_full_range)
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_RANGE, &PROP_BOOL(GF_TRUE) );
		else
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_RANGE, NULL );

		if (ctx->decoder->color_primaries!=AVCOL_PRI_UNSPECIFIED)
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_PRIMARIES, &PROP_UINT(ctx->decoder->color_primaries) );
		else
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_PRIMARIES, NULL );

		if (ctx->decoder->colorspace!=AVCOL_SPC_UNSPECIFIED)
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_MX, &PROP_UINT(ctx->decoder->colorspace) );
		else
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_MX, NULL );

		if (ctx->decoder->color_trc!=AVCOL_TRC_UNSPECIFIED)
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_TRANSFER, &PROP_UINT(ctx->decoder->color_trc) );
		else
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_TRANSFER, NULL );
	}
}

static GF_Err ffdec_process_video(GF_Filter *filter, struct _gf_ffdec_ctx *ctx)
{
	AVPacket *pkt;
	AVFrame *frame;
	u32 dst_stride[5];
	u8 *dst_planes[5];
	u32 pix_out;
	Bool is_eos=GF_FALSE;
	s32 res;
	s32 gotpic;
	u64 out_cts;
	const char *data = NULL;
	Bool seek_flag = GF_FALSE;
	u32 i, count, ff_pfmt;
	u32 size=0, outsize, stride, stride_uv, uv_height, nb_planes;
	u8 *out_buffer;
	GF_FilterPacket *pck_src;
	GF_FilterPacket *dst_pck;
	GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->in_pid);

	if (ctx->reconfig_pending) {
		pck = NULL;
	} else if (!pck) {
		is_eos = gf_filter_pid_is_eos(ctx->in_pid);
		if (!is_eos) return GF_OK;
	}

	if (pck && ctx->drop_non_refs && !gf_filter_pck_get_sap(pck)) {
		gf_filter_pid_drop_packet(ctx->in_pid);
		return GF_OK;
	}
    //we don't own the codec and we're in end of stream, don't try to decode (the context might have been closed)
    if (!pck && !ctx->owns_context) {
        gf_filter_pid_set_eos(ctx->out_pid);
        return GF_EOS;
    }

	frame = ctx->frame;

	FF_INIT_PCK(ctx, pkt)

	if (pck) {
		data = gf_filter_pck_get_data(pck, &size);

		if (!size) {
			gf_filter_pid_drop_packet(ctx->in_pid);
			FF_RELEASE_PCK(pkt)
			return GF_OK;
		}

		pck_src = pck;
		gf_filter_pck_ref_props(&pck_src);
		if (pck_src) gf_list_add(ctx->src_packets, pck_src);

		//seems ffmpeg is not properly handling the decoding after a flush, we close and reopen the codec
		if (ctx->flush_done) {
#if 0
			AVDictionary *options = NULL;
			const AVCodec *codec = ctx->decoder->codec;
			avcodec_free_context(&ctx->decoder);

			av_dict_copy(&options, ctx->options, 0);
			avcodec_open2(ctx->decoder, codec, &options );
			if (options) av_dict_free(&options);
#else
			avcodec_flush_buffers(ctx->decoder);
#endif
			ctx->flush_done = GF_FALSE;
		}

		pkt->dts = gf_filter_pck_get_dts(pck);
		pkt->pts = gf_filter_pck_get_cts(pck);
		pkt->duration = gf_filter_pck_get_duration(pck);
		if (gf_filter_pck_get_sap(pck)>0)
			pkt->flags = AV_PKT_FLAG_KEY;
	}
	pkt->data = (uint8_t*)data;
	pkt->size = size;

	/*TOCHECK: for AVC bitstreams after ISMA decryption, in case (as we do) the decryption DRM tool
	doesn't put back nalu size, we have to do it ourselves, but we can't modify input data...*/

	gotpic=0;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
	res = avcodec_decode_video2(ctx->decoder, frame, &gotpic, pkt);
#else
	res = avcodec_send_packet(ctx->decoder, pkt);
	switch (res) {
	case AVERROR(EAGAIN):
		if (pck_src) {
			gf_list_del_item(ctx->src_packets, pck_src);
			gf_filter_pck_unref(pck_src);
		}
		FF_RELEASE_PCK(pkt);
		//input full but output available, go on but don't discard input
		pck = NULL;
		break;
	case 0:
	case AVERROR_EOF:
		break;
	default:
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to send frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(res) ));
		break;
	}
	gotpic = 0;
	res = avcodec_receive_frame(ctx->decoder, frame);
	switch (res) {
	case AVERROR(EAGAIN):
		res = 0;
		break;
	case 0:
		gotpic = 1;
		break;
	case AVERROR_EOF:
		res = 0;
		break;
	}
#endif


	if (pck) gf_filter_pid_drop_packet(ctx->in_pid);


	if (!gotpic) {
		FF_RELEASE_PCK(pkt);
		if (is_eos) {
			ctx->flush_done = GF_TRUE;
			gf_filter_pid_set_eos(ctx->out_pid);
			return GF_EOS;
		}
		if (ctx->reconfig_pending) {
			GF_Err e;
			avcodec_free_context(&ctx->decoder);
			ctx->decoder = NULL;
			ctx->reconfig_pending = GF_FALSE;
			//these properties are checked after decode, when we reconfigure we copy props from input to output
			//so we need to make sure we retrigger pid config even if these did not change
			ctx->pixel_fmt = 0;
			ctx->width = 0;
			ctx->height = 0;
			ctx->stride = 0;
			ctx->stride_uv = 0;
			ctx->sar.num = ctx->sar.den = 0;
			while (gf_list_count(ctx->src_packets)) {
				GF_FilterPacket *ref_pck = gf_list_pop_back(ctx->src_packets);
				gf_filter_pck_unref(ref_pck);
			}
			GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFDec] PID %s reconfigure pending and all frames flushed, reconfiguring\n", gf_filter_pid_get_name(ctx->in_pid) ));
			e = ffdec_configure_pid(filter, ctx->in_pid, GF_FALSE);
			if (e==GF_NOT_SUPPORTED)
				return GF_PROFILE_NOT_SUPPORTED;
			return e;
		}
	}

	if (res < 0) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(res) ));
		FF_RELEASE_PCK(pkt);
		return GF_NON_COMPLIANT_BITSTREAM;
	}
	FF_RELEASE_PCK(pkt);
	if (!gotpic) return GF_OK;

	if (ctx->decoder->pix_fmt != ctx->o_ff_pfmt) {
		u32 pix_fmt = ffmpeg_pixfmt_to_gpac(ctx->decoder->pix_fmt);
		ctx->o_ff_pfmt = ctx->decoder->pix_fmt;
		if (!pix_fmt) {
			pix_fmt = GF_PIXEL_RGB;
		}
		ffdec_check_pix_fmt_change(ctx, pix_fmt);
	}
	//update all props
	FF_CHECK_PROP(width, width, GF_PROP_PID_WIDTH)
	FF_CHECK_PROP(height, height, GF_PROP_PID_HEIGHT)

	stride = stride_uv = uv_height = nb_planes = 0;
	if (! gf_pixel_get_size_info(ctx->pixel_fmt, ctx->width, ctx->height, &outsize, &stride, &stride_uv, &nb_planes, &uv_height) ) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to query pixelformat size infon", gf_filter_pid_get_name(ctx->in_pid) ));
		return GF_NOT_SUPPORTED;
	}

	FF_CHECK_PROP_VAL(stride, stride, GF_PROP_PID_STRIDE)
	FF_CHECK_PROP_VAL(stride_uv, stride_uv, GF_PROP_PID_STRIDE_UV)
	if (ctx->sar.num * ctx->decoder->sample_aspect_ratio.den != ctx->sar.den * ctx->decoder->sample_aspect_ratio.num) {
		ctx->sar.num = ctx->decoder->sample_aspect_ratio.num;
		ctx->sar.den = ctx->decoder->sample_aspect_ratio.den;

		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_SAR, &PROP_FRAC( ctx->sar ) );
	}

	pck_src = NULL;
	count = gf_list_count(ctx->src_packets);
	for (i=0; i<count; i++) {
		u64 cts;
		pck_src = gf_list_get(ctx->src_packets, i);
		cts = gf_filter_pck_get_cts(pck_src);
		if (cts == frame->pts)
			break;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
		if (cts == frame->pkt_pts)
			break;
#endif
		pck_src = NULL;
	}

	seek_flag = GF_FALSE;
	if (pck_src) {
		seek_flag = gf_filter_pck_get_seek_flag(pck_src);
		out_cts = gf_filter_pck_get_cts(pck_src);
	} else {
		out_cts = frame->pts;
	}
	//this was a seek frame, do not dispatch
	if (seek_flag) {
		if (pck_src) {
			gf_list_del_item(ctx->src_packets, pck_src);
			gf_filter_pck_unref(pck_src);
		}
		return GF_OK;
	}

	dst_pck = gf_filter_pck_new_alloc(ctx->out_pid, outsize, &out_buffer);
	if (!dst_pck) return GF_OUT_OF_MEM;

	if (pck_src) {
		gf_filter_pck_merge_properties(pck_src, dst_pck);
		gf_filter_pck_set_dependency_flags(dst_pck, 0);
		gf_list_del_item(ctx->src_packets, pck_src);
		gf_filter_pck_unref(pck_src);
	} else {
		gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1);
	}

    //rewrite dts and pts to PTS value
    gf_filter_pck_set_dts(dst_pck, out_cts);
    gf_filter_pck_set_cts(dst_pck, out_cts);

	ff_pfmt = ctx->decoder->pix_fmt;
	if (ff_pfmt==AV_PIX_FMT_YUVJ420P) {
		ff_pfmt = AV_PIX_FMT_YUV420P;
		if (!ctx->force_full_range) {
			ctx->force_full_range = GF_TRUE;
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_RANGE, &PROP_BOOL(GF_TRUE));
		}
	}

	memset(&dst_planes, 0, sizeof(u8 *)*5);
	memset(&dst_stride, 0, sizeof(u32)*5);

	//TODO: cleanup, we should not convert pixel format in the decoder but through filters !
	switch (ctx->pixel_fmt) {
	case GF_PIXEL_RGB:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_stride[0] = 3*ctx->width;
		pix_out = AV_PIX_FMT_RGB24;
		break;
	case GF_PIXEL_RGBA:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_stride[0] = 4*ctx->width;
		pix_out = AV_PIX_FMT_RGBA;
		break;
	case GF_PIXEL_YUV:
	case GF_PIXEL_YUV_10:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_planes[1] =  (uint8_t *)out_buffer + ctx->stride * ctx->height;
		dst_planes[2] =  (uint8_t *)dst_planes[1] + ctx->stride_uv * uv_height;
		dst_stride[0] = ctx->stride;
		dst_stride[1] = dst_stride[2] = ctx->stride_uv;
		if (ctx->pixel_fmt == GF_PIXEL_YUV_10)
			pix_out = AV_PIX_FMT_YUV420P10LE;
		else
			pix_out = AV_PIX_FMT_YUV420P;
		break;

	case GF_PIXEL_YUV422:
	case GF_PIXEL_YUV422_10:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_planes[1] =  (uint8_t *)out_buffer + ctx->stride * ctx->height;
		dst_planes[2] =  (uint8_t *)dst_planes[1] + ctx->stride_uv * ctx->height;
		dst_stride[0] = ctx->stride;
		dst_stride[1] = dst_stride[2] = ctx->stride_uv;
		if (ctx->pixel_fmt == GF_PIXEL_YUV422_10)
			pix_out = AV_PIX_FMT_YUV422P10LE;
		else
			pix_out = AV_PIX_FMT_YUV422P;
		break;

	case GF_PIXEL_YUV444:
	case GF_PIXEL_YUV444_10:
		dst_planes[0] =  (uint8_t *)out_buffer;
		dst_planes[1] =  (uint8_t *)out_buffer + ctx->stride * ctx->height;
		dst_planes[2] =  (uint8_t *)out_buffer + 2*ctx->stride * ctx->height;
		dst_stride[0] = dst_stride[1] = dst_stride[2] = ctx->stride;
		if (ctx->pixel_fmt == GF_PIXEL_YUV444_10)
			pix_out = AV_PIX_FMT_YUV444P10LE;
		else
			pix_out = AV_PIX_FMT_YUV444P;
		break;

	default:
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Unsupported pixel format %s, patch welcome\n", av_get_pix_fmt_name(ctx->decoder->pix_fmt) ));

		gf_filter_pck_discard(dst_pck);

		return GF_NOT_SUPPORTED;
	}

	ctx->sws_ctx = sws_getCachedContext(ctx->sws_ctx,
	                                   ctx->decoder->width, ctx->decoder->height, ff_pfmt,
	                                   ctx->width, ctx->height, pix_out, SWS_BICUBIC, NULL, NULL, NULL);
	if (ctx->sws_ctx) {
		sws_scale(ctx->sws_ctx, (const uint8_t * const*)frame->data, frame->linesize, 0, ctx->height, dst_planes, dst_stride);
	}


	gf_filter_pck_set_seek_flag(dst_pck, GF_FALSE);

	if (frame->interlaced_frame)
		gf_filter_pck_set_interlaced(dst_pck, frame->top_field_first ? 2 : 1);

	gf_filter_pck_send(dst_pck);
	return GF_OK;
}


static GF_Err ffdec_process_audio(GF_Filter *filter, struct _gf_ffdec_ctx *ctx)
{
	s32 gotpic;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
	s32 len;
#endif
	AVPacket *pkt;
	s32 res, in_size, i;
	u32 samples_to_trash;
	u32 output_size, prev_afmt;
	Bool is_eos=GF_FALSE;
	u8 *data;
	AVFrame *frame;
	GF_FilterPacket *dst_pck, *src_pck;
	GF_FilterPacket *pck;

decode_next:
	pck = gf_filter_pid_get_packet(ctx->in_pid);
	in_size = 0;
	
	if (ctx->reconfig_pending) {
		pck = NULL;
	} else if (!pck) {
		is_eos = gf_filter_pid_is_eos(ctx->in_pid);
		if (!is_eos) return GF_OK;
	}

	FF_INIT_PCK(ctx, pkt)

	if (pck) pkt->data = (uint8_t *) gf_filter_pck_get_data(pck, &in_size);

	if (pck) {
		src_pck = pck;
		gf_filter_pck_ref_props(&src_pck);
		if (src_pck) gf_list_add(ctx->src_packets, src_pck);

		if (!pkt->data) {
			gf_filter_pid_drop_packet(ctx->in_pid);
			return GF_OK;
		}

		pkt->pts = gf_filter_pck_get_cts(pck);
		pkt->dts = gf_filter_pck_get_dts(pck);

		pkt->size = in_size;
#if (LIBAVCODEC_VERSION_MAJOR < 59)
		if ((s32) ctx->frame_start > pkt->size) ctx->frame_start = 0;
		//seek to last byte consumed by the previous decode4()
		else if (ctx->frame_start) {
			pkt->data += ctx->frame_start;
			pkt->size -= ctx->frame_start;
		}
#endif

		pkt->duration = gf_filter_pck_get_duration(pck);
		if (gf_filter_pck_get_sap(pck)>0)
			pkt->flags = AV_PKT_FLAG_KEY;

		if (!ctx->first_cts_plus_one)
			ctx->first_cts_plus_one = pkt->pts + 1;

	} else {
		pkt->size = 0;
	}

	prev_afmt = ctx->decoder->sample_fmt;
	frame = ctx->frame;

#if (LIBAVCODEC_VERSION_MAJOR < 59)
	len = res = avcodec_decode_audio4(ctx->decoder, frame, &gotpic, pkt);
#else
	res = avcodec_send_packet(ctx->decoder, pkt);
	switch (res) {
	case AVERROR(EAGAIN):
		if (src_pck) {
			gf_list_del_item(ctx->src_packets, src_pck);
			gf_filter_pck_unref(src_pck);
		}
		av_packet_unref(pkt);
		return GF_OK;
	case 0:
	case AVERROR_EOF:
		break;
	default:
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(res) ));
		break;
	}

	gotpic = 0;
	res = avcodec_receive_frame(ctx->decoder, frame);
	switch (res) {
	case AVERROR(EAGAIN):
		res = 0;
		break;
	case 0:
		gotpic = 1;
		break;
	case AVERROR_EOF:
		res = 0;
		break;
	default:
		break;
	}
dispatch_next:
#endif

	samples_to_trash = 0;
	if ( gotpic && (ctx->delay<0)) {
		if (pkt->pts + 1 - ctx->first_cts_plus_one + ctx->delay < 0) {
			if (pkt->duration + ctx->delay < 0) {
				frame->nb_samples = 0;
				samples_to_trash = 0;
				ctx->delay += pkt->duration;
			} else {
				samples_to_trash = (u32) -ctx->delay;
			}
			
			if (!samples_to_trash || (samples_to_trash > (u32) frame->nb_samples) ) {
				frame->nb_samples = 0;
				samples_to_trash = 0;
			}
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DELAY, NULL);
		} else {
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DELAY, NULL);
			ctx->delay = 0;
		}
	}

	//this will handle eos as well
	if ((res<0) || !gotpic) {
#if (LIBAVCODEC_VERSION_MAJOR < 59)
		ctx->frame_start = 0;
#endif
		if (pck) gf_filter_pid_drop_packet(ctx->in_pid);
		if (pkt->size && (res<0)) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(res) ));
			FF_RELEASE_PCK(pkt);
			return GF_NON_COMPLIANT_BITSTREAM;
		}
		FF_RELEASE_PCK(pkt);
		if (is_eos) {
			gf_filter_pid_set_eos(ctx->out_pid);
			return GF_EOS;
		}
		if (ctx->reconfig_pending) {
			GF_Err e;
			avcodec_free_context(&ctx->decoder);
			ctx->decoder = NULL;
			ctx->reconfig_pending = GF_FALSE;
			//these properties are checked after decode, when we reconfigure we copy props from input to output
			//so we need to make sure we retrigger pid config even if these did not change
			ctx->sample_fmt = 0;
			ctx->sample_rate = 0;
			ctx->channels = 0;
			ctx->channel_layout = 0;
			GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFDec] PID %s reconfigure pending and all frames flushed, reconfiguring\n", gf_filter_pid_get_name(ctx->in_pid) ));
			e = ffdec_configure_pid(filter, ctx->in_pid, GF_FALSE);
			if (e==GF_NOT_SUPPORTED)
				return GF_PROFILE_NOT_SUPPORTED;
			return e;
		}
		return GF_OK;
	}
	FF_RELEASE_PCK(pkt);


	FF_CHECK_PROP(channels, channels, GF_PROP_PID_NUM_CHANNELS)
	FF_CHECK_PROPL(channel_layout, channel_layout, GF_PROP_PID_CHANNEL_LAYOUT)
	FF_CHECK_PROP(sample_rate, sample_rate, GF_PROP_PID_SAMPLE_RATE)

	if (prev_afmt != ctx->decoder->sample_fmt) {
		ctx->sample_fmt = ffmpeg_audio_fmt_to_gpac(ctx->decoder->sample_fmt);
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(ctx->sample_fmt) );
		ctx->bytes_per_sample = gf_audio_fmt_bit_depth(ctx->sample_fmt) / 8;
	}

	if (pck) {
		const GF_PropertyValue *er = gf_filter_pck_get_property(pck, GF_PROP_PCK_END_RANGE);
		if (er && er->value.boolean) {
			u32 timescale = gf_filter_pck_get_timescale(pck);
			u64 odur = gf_filter_pck_get_duration(pck);
			if (timescale != ctx->sample_rate) {
				odur = gf_timestamp_rescale(odur, timescale, ctx->sample_rate);
			}
			if (odur < frame->nb_samples) {
				frame->nb_samples = (int) odur;
			}
		}
	}

	output_size = (frame->nb_samples - samples_to_trash) * ctx->channels * ctx->bytes_per_sample;
	dst_pck = gf_filter_pck_new_alloc(ctx->out_pid, output_size, &data);
	if (!dst_pck) return GF_OUT_OF_MEM;

	switch (frame->format) {
	case AV_SAMPLE_FMT_U8P:
	case AV_SAMPLE_FMT_S16P:
	case AV_SAMPLE_FMT_S32P:
	case AV_SAMPLE_FMT_FLTP:
	case AV_SAMPLE_FMT_DBLP:
		for (i=0; (u32) i< ctx->channels; i++) {
			char *inputChannel = frame->extended_data[i] + samples_to_trash * ctx->bytes_per_sample;
			memcpy(data, inputChannel, ctx->bytes_per_sample * (frame->nb_samples-samples_to_trash) );
			data += ctx->bytes_per_sample * (frame->nb_samples-samples_to_trash);
		}
		break;
	default:
		memcpy(data, ctx->frame->data[0] + samples_to_trash * ctx->bytes_per_sample, ctx->bytes_per_sample * (frame->nb_samples - samples_to_trash) * ctx->channels);
		break;
	}

	//we don't follow the same approach as in video, we assume the codec works with one in one out
	//and use the first entry in src packets to match in order to copy the properties
	//a nicer approach would be to count delay frames (number of frames used to initialize)
	//and backmerge properties from the last packet in to the last-nb_init_frames
	src_pck = gf_list_get(ctx->src_packets, 0);

	if (src_pck) {
		gf_filter_pck_merge_properties(src_pck, dst_pck);
		gf_filter_pck_set_dependency_flags(dst_pck, 0);
		gf_list_rem(ctx->src_packets, 0);
		gf_filter_pck_unref(src_pck);
	}

	if (output_size) {
		if (frame->pts != AV_NOPTS_VALUE) {
			u64 pts = frame->pts;
			u32 timescale = gf_filter_pck_get_timescale(pck);
			if (ctx->nb_samples_already_in_frame) {
				if (ctx->sample_rate == timescale) {
					pts += ctx->nb_samples_already_in_frame;
				}
			}
			if (pts >= ctx->ts_offset) pts -= ctx->ts_offset;
			else pts = 0;
			gf_filter_pck_set_cts(dst_pck, pts);
			gf_filter_pck_set_dts(dst_pck, pts);
		}
		gf_filter_pck_send(dst_pck);
	} else {
		gf_filter_pck_discard(dst_pck);
	}

#if (LIBAVCODEC_VERSION_MAJOR < 59)
	ctx->frame_start += len;
	//done with this input packet
	if (in_size <= (s32) ctx->frame_start) {
		frame->nb_samples = 0;
		ctx->frame_start = 0;
		ctx->nb_samples_already_in_frame = 0;
		gf_filter_pid_drop_packet(ctx->in_pid);

		if (gf_filter_pid_would_block(ctx->out_pid))
			return GF_OK;

		//if space available in ouput, decode right away - needed for audio formats with very short frames
		//avoid recursion
		goto decode_next;
	}
	//still some data to decode in packet, don't drop it
	//todo: check if frame->pkt_pts or frame->pts is updated by ffmpeg, otherwise do it ourselves !
	GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Code not yet tested  - frame PTS was "LLU" - nb samples dec %d\n", frame->pkt_pts, frame->nb_samples));
	ctx->nb_samples_already_in_frame += frame->nb_samples;
	frame->nb_samples = 0;
#else
	//if multiple frames in packet, send them
	gotpic = 0;
	res = avcodec_receive_frame(ctx->decoder, frame);
	if (res==0) {
		gotpic = 1;
		goto dispatch_next;
	}
	if (pck) gf_filter_pid_drop_packet(ctx->in_pid);
#endif

	//avoid recursion
	goto decode_next;

//	return ffdec_process_audio(filter, ctx);
}

#ifdef FF_SUB_SUPPORT
static GF_Err ffdec_process_subtitle(GF_Filter *filter, struct _gf_ffdec_ctx *ctx)
{
	AVPacket *pkt;
	AVSubtitle subs;
	s32 gotpic;
	s32 len, in_size;
	Bool is_eos=GF_FALSE;

	GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->in_pid);

	if (!pck) {
		is_eos = gf_filter_pid_is_eos(ctx->in_pid);
		if (!is_eos) return GF_OK;
	}
	FF_INIT_PCK(ctx, pkt);

	if (pck) pkt->data = (uint8_t *) gf_filter_pck_get_data(pck, &in_size);

	if (!is_eos) {
		u64 dts;
		pkt->pts = gf_filter_pck_get_cts(pck);

		//copy over SAP and duration in dts
		dts = gf_filter_pck_get_sap(pck);
		dts <<= 32;
		dts |= gf_filter_pck_get_duration(pck);
		pkt->dts = dts;

		pkt->size = in_size;
	} else {
		pkt->size = 0;
	}

	memset(&subs, 0, sizeof(AVSubtitle));
	len = avcodec_decode_subtitle2(ctx->decoder, &subs, &gotpic, pkt);

	//this will handle eos as well
	if ((len<0) || !gotpic) {
		FF_RELEASE_PCK(pkt)
		if (pck) gf_filter_pid_drop_packet(ctx->in_pid);
		if (len<0) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt->pts, av_err2str(len) ));
			return GF_NON_COMPLIANT_BITSTREAM;
		}
		if (is_eos) {
			gf_filter_pid_set_eos(ctx->out_pid);
			return GF_EOS;
		}
		return GF_OK;
	}
	//TODO - do we want to remap to TX3G/other and handle the rendering some place else, or do we do the rendering here ?


	avsubtitle_free(&subs);
	if (pck) gf_filter_pid_drop_packet(ctx->in_pid);
	FF_RELEASE_PCK(pkt)
	return GF_OK;
}
#endif

static GF_Err ffdec_process(GF_Filter *filter)
{
	GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter);
	if (gf_filter_pid_would_block(ctx->out_pid))
		return GF_OK;

	return ctx->process(filter, ctx);
}

static const GF_FilterCapability FFDecodeAnnexBCaps[] =
{
	CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
	CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_UNFRAMED, GF_TRUE),
	CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_VVC),
	CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_AVC),
	CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_HEVC),
	CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW),
};


static GF_Err ffdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
{
	s32 res;
	u32 type=0, gpac_codecid=0;
	AVDictionary *options = NULL;
	const GF_PropertyValue *prop;
	const AVCodec *codec=NULL;
	GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter);

	//disconnect of src pid (not yet supported)
	if (is_remove) {
		//one in one out, this is simple
		if (ctx->out_pid) {
			gf_filter_pid_remove(ctx->out_pid);
			ctx->out_pid = NULL;
		}
		return GF_OK;
	}

	//check our PID: streamtype and codecid
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
	if (!prop) return GF_NOT_SUPPORTED;

	type = prop->value.uint;
	switch (type) {
	case GF_STREAM_AUDIO:
	case GF_STREAM_VISUAL:
#ifdef FF_SUB_SUPPORT
	case GF_STREAM_TEXT:
#endif
		break;
	default:
		return GF_NOT_SUPPORTED;
	}
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
	if (!prop) return GF_NOT_SUPPORTED;
	gpac_codecid = prop->value.uint;
	if (gpac_codecid==GF_CODECID_RAW)
		return GF_NOT_SUPPORTED;


	//initial config or update
	if (!ctx->in_pid || (ctx->in_pid==pid)) {
		ctx->in_pid = pid;
		if (!ctx->type) ctx->type = type;
		else if (ctx->type != type) {
			return GF_NOT_SUPPORTED;
		}
	} else {
		//only one input pid in ctx
		if (ctx->in_pid) return GF_REQUIRES_NEW_INSTANCE;
	}

	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
	ctx->width = prop ? prop->value.uint : 0;
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
	ctx->height = prop ? prop->value.uint : 0;
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE);
	ctx->sample_rate = prop ? prop->value.uint : 0;
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS);
	ctx->channels = prop ? prop->value.uint : 0;

	if (gpac_codecid==GF_CODECID_VVC) {
		u32 codec_id = ffmpeg_codecid_from_gpac(gpac_codecid, NULL);
		codec = codec_id ? avcodec_find_decoder(codec_id) : NULL;
		//libvvdec only supports annexB, request ufnalu adaptation filter
		if (codec && codec->name && strstr(codec->name, "vvdec")) {
			prop = gf_filter_pid_get_property(pid, GF_PROP_PID_UNFRAMED);
			if (!prop || !prop->value.boolean) {
				gf_filter_override_caps(filter, FFDecodeAnnexBCaps, GF_ARRAY_LENGTH(FFDecodeAnnexBCaps));
				gf_filter_pid_negociate_property(ctx->in_pid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) );
				return GF_OK;
			}
		}
	}


	if (gpac_codecid == GF_CODECID_FFMPEG) {
		prop = gf_filter_pid_get_property(pid, GF_FFMPEG_DECODER_CONFIG);
		if (prop && (prop->type==GF_PROP_POINTER)) {
			ctx->decoder = prop->value.ptr;
			if (!ctx->decoder) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s codec context not exposed by demuxer !\n", gf_filter_pid_get_name(pid) ));
				return GF_SERVICE_ERROR;
			}
			codec = avcodec_find_decoder(ctx->decoder->codec_id);
			ctx->owns_context = GF_FALSE;
		}
		else if (prop && (prop->type==GF_PROP_UINT)) {
			ctx->decoder = avcodec_alloc_context3(NULL);
			if (! ctx->decoder) return GF_OUT_OF_MEM;
			codec = avcodec_find_decoder(prop->value.uint);
		}
		else {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s codec context not exposed by demuxer !\n", gf_filter_pid_get_name(pid) ));
			return GF_SERVICE_ERROR;
		}

		if (!codec) {
			gf_filter_set_name(filter, "ffdec");
			return GF_NOT_SUPPORTED;
		}
	}
	//we reconfigure the stream
	else {
		u32 codec_id, ff_codectag=0;
        
        if (!ctx->owns_context) {
            ctx->decoder = NULL;
        }
		if (ctx->decoder) {
			codec_id = ffmpeg_codecid_from_gpac(gpac_codecid, NULL);
			//same codec, same config, don't reinit
			if (ctx->decoder->codec->id == codec_id) {
				u32 cfg_crc=0;
				prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG);
				if (prop && prop->value.data.ptr && prop->value.data.size) {
					cfg_crc = gf_crc_32(prop->value.data.ptr, prop->value.data.size);
				}
				if (cfg_crc == ctx->extra_data_crc) {
					goto reuse_codec_context;
				}
			}

            
			//we could further optimize by detecting we have the same codecid and injecting the extradata
			//but this is not 100% reliable, and will require parsing AVC/HEVC config
			//since this seems to work properly with decoder close/open, we keep it as is
			ctx->reconfig_pending = GF_TRUE;
			GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFDec] PID %s reconfigure detected, flushing frame\n", gf_filter_pid_get_name(pid) ));
			return GF_OK;
		}

		codec_id = ffmpeg_codecid_from_gpac(gpac_codecid, &ff_codectag);
		//specific remaps
		if (!codec_id) {
			switch (gpac_codecid) {
			case GF_CODECID_MVC: codec_id = AV_CODEC_ID_H264; break;
			}

		}
		if (codec_id) codec = avcodec_find_decoder(codec_id);
		if (!codec) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] No decoder found for codec %s\n", gf_codecid_name(gpac_codecid) ));
			gf_filter_set_name(filter, "ffdec");
			return GF_NOT_SUPPORTED;
		}

		ctx->decoder = avcodec_alloc_context3(NULL);
		if (! ctx->decoder) return GF_OUT_OF_MEM;
		ctx->owns_context = GF_TRUE;
		if (ff_codectag)
			ctx->decoder->codec_tag = ff_codectag;

		ffmpeg_set_enc_dec_flags(ctx->options, ctx->decoder);

		//for some raw codecs
		if (ctx->width && ctx->height) {
			ctx->decoder->width = ctx->width;
			ctx->decoder->height = ctx->height;
		}
		if (ctx->sample_rate && ctx->channels) {
			ctx->decoder->sample_rate = ctx->sample_rate;
			ctx->decoder->channels = ctx->channels;
		}

		//we may have a dsi here!
		prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG);
		if (prop && prop->value.data.ptr && prop->value.data.size) {
			//looks like ffmpeg wants the fLaC keyword
			if (gpac_codecid==GF_CODECID_FLAC) {
				ctx->decoder->extradata_size = prop->value.data.size+4;
				ctx->decoder->extradata = av_malloc(sizeof(char) * prop->value.data.size+4);
				memcpy(ctx->decoder->extradata, "fLaC", 4);
				memcpy(ctx->decoder->extradata+4, prop->value.data.ptr, prop->value.data.size);
			} else {
				ctx->decoder->extradata_size = prop->value.data.size;
				ctx->decoder->extradata = av_malloc(sizeof(char) * prop->value.data.size);
				memcpy(ctx->decoder->extradata, prop->value.data.ptr, prop->value.data.size);
			}
			ctx->extra_data_crc = gf_crc_32(prop->value.data.ptr, prop->value.data.size);
		}
	}

	//by default let libavcodec decide - if single thread is required, let the user define -threads option
	if (codec->capabilities & AV_CODEC_CAP_AUTO_THREADS)
		ctx->decoder->thread_count = 0;

	//clone options (in case we need to destroy/recreate the codec) and open codec
	av_dict_copy(&options, ctx->options, 0);
	res = avcodec_open2(ctx->decoder, codec, &options);
	if (res < 0) {
		if (options) av_dict_free(&options);
		GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to open codec context: %s\n", gf_filter_pid_get_name(pid), av_err2str(res) ));
		return GF_NON_COMPLIANT_BITSTREAM;
	}

	ffmpeg_report_options(filter, options, ctx->options);

	//we're good to go, declare our output pid
	ctx->in_pid = pid;
	if (!ctx->out_pid) {
		char szCodecName[1000];
		ctx->out_pid = gf_filter_pid_new(filter);

		//to change once we implement on-the-fly codec change
		sprintf(szCodecName, "ffdec:%s", ctx->decoder->codec->name ? ctx->decoder->codec->name : "unknown");
		gf_filter_set_name(filter, szCodecName);
		gf_filter_pid_set_framing_mode(ctx->in_pid, GF_TRUE);
	}

reuse_codec_context:
	//copy props it at init config or at reconfig
	if (ctx->out_pid) {
		gf_filter_pid_copy_properties(ctx->out_pid, ctx->in_pid);
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) );
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DECODER_CONFIG, NULL );
	}

	if (type==GF_STREAM_VISUAL) {
		u32 pix_fmt;
		ctx->force_full_range = GF_FALSE;
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_RANGE, NULL );
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_MX, NULL );
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_TRANSFER, NULL );
		gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_COLR_PRIMARIES, NULL );

		ctx->process = ffdec_process_video;
		//for some streams, we don't have w/h/pixfmt after opening the decoder
		//to make sure we are not confusing potential filters expecting them, init to default values
		if (ctx->decoder->pix_fmt>=0) {
			ctx->o_ff_pfmt = ctx->decoder->pix_fmt;
			pix_fmt = ffmpeg_pixfmt_to_gpac(ctx->decoder->pix_fmt);
			if (!pix_fmt) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FFDec] Unsupported pixel format %d, defaulting to RGB\n", ctx->decoder->pix_fmt));
				pix_fmt = GF_PIXEL_RGB;
			}
		} else {
			pix_fmt = GF_PIXEL_YUV;
			ctx->o_ff_pfmt = AV_PIX_FMT_YUV420P;
		}
		ffdec_check_pix_fmt_change(ctx, pix_fmt);

		if (ctx->decoder->width) {
			FF_CHECK_PROP(width, width, GF_PROP_PID_WIDTH)
		} else {
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_WIDTH, &PROP_UINT( ctx->width) );
		}
		if (ctx->decoder->height) {
			FF_CHECK_PROP(height, height, GF_PROP_PID_HEIGHT)
		} else {
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_HEIGHT, &PROP_UINT( ctx->height) );
		}
		if (ctx->decoder->sample_aspect_ratio.num && ctx->decoder->sample_aspect_ratio.den) {
			ctx->sar.num = ctx->decoder->sample_aspect_ratio.num;
			ctx->sar.den = ctx->decoder->sample_aspect_ratio.den;
			gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_SAR, &PROP_FRAC( ctx->sar) );
		}
		if (!ctx->frame)
			ctx->frame = av_frame_alloc();

{'fraction_non_alphanumeric': 0.12615667567101557, 'fraction_numerical': 0.01100637967699293, 'mean_word_length': 4.601819059884095, 'pattern_counts': {'":': 10, '<': 57, '<?xml version=': 0, '>': 1005, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7279859', 'n_tokens_mistral': 31501, 'n_tokens_neox': 28688, 'n_words': 5507}