//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2016 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  This program is distributed in the hope that it will be useful,       *
//*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *
//*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: FFMPEG library parser

#include "ffmpgdec.h"

#ifdef MPXPLAY_LINK_INFILE_FF_MPEG

#define INFFMPG_ADITEXTSIZE_LONGNAME 128

extern AVInputFormat ff_aac_demuxer;
extern AVInputFormat ff_ape_demuxer;
extern AVInputFormat ff_avi_demuxer;
extern AVInputFormat ff_ac3_demuxer;
extern AVInputFormat ff_dts_demuxer;
extern AVInputFormat ff_flac_demuxer;
extern AVInputFormat ff_flv_demuxer;
extern AVInputFormat ff_matroska_demuxer;
extern AVInputFormat ff_mov_demuxer;
extern AVInputFormat ff_mp3_demuxer;
extern AVInputFormat ff_ogg_demuxer;
extern AVInputFormat ff_mpegps_demuxer;
extern AVInputFormat ff_mpegts_demuxer;

extern int mpxplay_control_startup_programid_select;
#ifdef MPXPLAY_GUI_CONSOLE
#include <display/display.h>
extern unsigned int refdisp;
#endif

static unsigned int inffmpeg_channel_layout_order[MPXPLAY_PCMOUTCHAN_MAX] =
{
 MPXPLAY_PCMOUTCHAN_FRONT_LEFT,
 MPXPLAY_PCMOUTCHAN_FRONT_RIGHT,
 MPXPLAY_PCMOUTCHAN_FRONT_CENTER,
 MPXPLAY_PCMOUTCHAN_LFE,
 MPXPLAY_PCMOUTCHAN_REAR_LEFT,
 MPXPLAY_PCMOUTCHAN_REAR_RIGHT,
 MPXPLAY_PCMOUTCHAN_FCENTER_LEFT,
 MPXPLAY_PCMOUTCHAN_FCENTER_RIGHT,
 MPXPLAY_PCMOUTCHAN_REAR_CENTER,
 MPXPLAY_PCMOUTCHAN_SIDE_LEFT,
 MPXPLAY_PCMOUTCHAN_SIDE_RIGHT
};

static void in_ffmpeg_avinfo_strings_assemble(struct ffmpg_demuxer_data_s *ffmpi,struct mpxplay_infile_info_s *miis, char *filename);
static void infmpeg_preanalyze_and_init_video(struct ffmpg_demuxer_data_s *ffmpi, struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds);

mpxp_bool_t infmpeg_is_fileformat_real_stream(struct ffmpg_demuxer_data_s *ffmpi) // contains no any header (like TS or MPG)
{
	if((ffmpi->fctx->iformat == &ff_mpegts_demuxer) || (ffmpi->fctx->iformat == &ff_mpegps_demuxer))  // TODO: for other formats
		return TRUE;
	return FALSE;
}

static mpxp_bool_t infmpeg_is_fileformat_native_audio_stream(struct ffmpg_demuxer_data_s *ffmpi) // contains no any header (like AAC or MP3)
{
	if( (ffmpi->fctx->iformat == &ff_aac_demuxer) || (ffmpi->fctx->iformat == &ff_ac3_demuxer) || (ffmpi->fctx->iformat == &ff_dts_demuxer)
	 || (ffmpi->fctx->iformat == &ff_mp3_demuxer) // not used in FFmpeg
	){  // TODO: for other formats
		return TRUE;
	}
	return FALSE;
}

int in_fmpeg_audio_codecid_to_waveid(struct ffmpg_demuxer_data_s *ffmpi,struct mpxplay_infile_info_s *miis, AVFrame *audio_frame)
{
	struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
	struct mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
	unsigned int new_audio_waveid, new_audio_bits, new_audio_freq, new_audio_filechannels;
	AVCodecParameters *codecpar;
	AVCodecContext *codec;
	uint64_t channel_layout;
#ifdef MPXPLAY_GUI_CONSOLE
	enum AVCodecID av_codec_id;
#endif
	int sample_fmt, i, ch;
	AVStream *st;

	// set default values (if no valid audio stream / info)
	spi->streamtype = MPXPLAY_SPI_STREAMTYPE_AUDIO;
	new_audio_waveid = MPXPLAY_WAVEID_PCM_FLOAT;
	new_audio_bits = 1;
	new_audio_freq = 48000;
	new_audio_filechannels = 2;
	funcbit_disable(adi->infobits, ADI_FLAG_FLOATOUT);
	funcbit_disable(ffmpi->flags, INFFMPG_FLAG_PLANARAUDIO);

	// load values from codec or codecpar // TODO: check
	codec = ffmpi->codec_ctx[MPXPLAY_STREAMTYPEINDEX_AUDIO];
	st = ffmpi->selected_avstream[MPXPLAY_STREAMTYPEINDEX_AUDIO];
	codecpar = (st)? st->codecpar : NULL;
#ifdef MPXPLAY_GUI_CONSOLE
	av_codec_id = (codec && (codec->codec_id > AV_CODEC_ID_NONE))? codec->codec_id : ((codecpar)? codecpar->codec_id : AV_CODEC_ID_NONE);
#endif
	sample_fmt = (audio_frame && (audio_frame->format >= 0))? audio_frame->format : ((codec && (codec->sample_fmt >= 0))? codec->sample_fmt : ((codecpar)? codecpar->format : AV_SAMPLE_FMT_FLT));

	switch(sample_fmt){
		case AV_SAMPLE_FMT_U8: new_audio_waveid=MPXPLAY_WAVEID_PCM_ULE; new_audio_bits=8; break;
		case AV_SAMPLE_FMT_S16:new_audio_waveid=MPXPLAY_WAVEID_PCM_SLE; new_audio_bits=16; break;
		case AV_SAMPLE_FMT_S32:new_audio_waveid=MPXPLAY_WAVEID_PCM_SLE; new_audio_bits=32; break;
		case AV_SAMPLE_FMT_FLT:new_audio_waveid=MPXPLAY_WAVEID_PCM_FLOAT; new_audio_bits=1; break;
		case AV_SAMPLE_FMT_DBL:new_audio_waveid=MPXPLAY_WAVEID_PCM_F64LE; new_audio_bits=1; break;
		case AV_SAMPLE_FMT_U8P: new_audio_waveid=MPXPLAY_WAVEID_PCM_ULE; new_audio_bits=8; funcbit_enable(ffmpi->flags, INFFMPG_FLAG_PLANARAUDIO); break;
		case AV_SAMPLE_FMT_S16P:new_audio_waveid=MPXPLAY_WAVEID_PCM_SLE; new_audio_bits=16; funcbit_enable(ffmpi->flags, INFFMPG_FLAG_PLANARAUDIO); break;
		case AV_SAMPLE_FMT_S32P:new_audio_waveid=MPXPLAY_WAVEID_PCM_SLE; new_audio_bits=32; funcbit_enable(ffmpi->flags, INFFMPG_FLAG_PLANARAUDIO); break;
		case AV_SAMPLE_FMT_FLTP:new_audio_waveid=MPXPLAY_WAVEID_PCM_FLOAT; new_audio_bits=1; funcbit_enable(ffmpi->flags, INFFMPG_FLAG_PLANARAUDIO); break;
		case AV_SAMPLE_FMT_DBLP:new_audio_waveid=MPXPLAY_WAVEID_PCM_F64LE; new_audio_bits=1; funcbit_enable(ffmpi->flags, INFFMPG_FLAG_PLANARAUDIO); break;
		default:mpxplay_debugf(MPXPLAY_DEBUG_WARNING,"Invalid codec->sample_fmt: %d", codec->sample_fmt);return -1;
	}
	if(audio_frame && audio_frame->sample_rate > 0)
		new_audio_freq = audio_frame->sample_rate;
	else if(codec && (codec->sample_rate > 0))
		new_audio_freq = codec->sample_rate;
	else if(codecpar && (codecpar->sample_rate > 0))
		new_audio_freq = codecpar->sample_rate;

	if(audio_frame && audio_frame->channels > 0)
		new_audio_filechannels = audio_frame->channels;
	else if(codec && (codec->channels > 0))
		new_audio_filechannels = codec->channels;
	else if(codecpar && (codecpar->channels > 0))
		new_audio_filechannels = codecpar->channels;

	if(new_audio_filechannels > MPXPLAY_PCMOUTCHAN_MAX)
		return -1;
	if((ffmpi->flags & INFFMPG_FLAG_PLANARAUDIO) && (new_audio_filechannels > AV_NUM_DATA_POINTERS))
		return -1;

	if(audio_frame && audio_frame->channel_layout)
		channel_layout = audio_frame->channel_layout;
	else if(codec && codec->channel_layout)
		channel_layout = codec->channel_layout;
	else if(codecpar)
		channel_layout = codecpar->channel_layout;
	else
		channel_layout = 0ULL;
	if(!channel_layout)
		channel_layout = av_get_default_channel_layout(new_audio_filechannels);

	if(channel_layout != ffmpi->audioframe_channel_layout)
	{
		if(adi->chanmatrix)
		{
			pds_free(adi->chanmatrix);
			adi->chanmatrix = NULL;
		}
		if(new_audio_filechannels >= 2) // TODO: add missing chanmatrix to output drivers for mono and stereo
		{
			adi->chanmatrix = pds_calloc(new_audio_filechannels, sizeof(*adi->chanmatrix));
			for(i = 0, ch = 0; (i < MPXPLAY_PCMOUTCHAN_MAX) && (ch < new_audio_filechannels); i++)
			{
				if(channel_layout & (1 << i))
					adi->chanmatrix[ch++] = inffmpeg_channel_layout_order[i];
			}
		}
	}

	if(codec && (codec->codec_id >= AV_CODEC_ID_AMR_NB) && (ffmpi->fctx->nb_streams <= 1))
	{
		mpxp_int64_t file_bitrate = miis->filesize * 1000 / (ffmpi->fctx->duration / 8000);
		if(codec->bit_rate <= 0)
			codec->bit_rate = file_bitrate;
		if(codecpar && (codecpar->bit_rate <= 0))
			codecpar->bit_rate = file_bitrate;
	}
	if(codec && (codec->bit_rate > 0) && (codec->bit_rate < 9999000) && (codec->codec_id >= AV_CODEC_ID_AMR_NB))
		adi->bitrate = codec->bit_rate/1000;
	else if(codecpar && (codecpar->bit_rate > 0) && (codecpar->bit_rate < 9999000) && (codecpar->codec_id >= AV_CODEC_ID_AMR_NB))
		adi->bitrate = codecpar->bit_rate / 1000;

	if((miis->timemsec <= MPXPLAY_TIME_BASE) && adi->bitrate && (ffmpi->fctx->nb_streams <= 1))
		miis->timemsec = miis->filesize * 8 / adi->bitrate;

	ffmpi->outsample_size = (new_audio_waveid==MPXPLAY_WAVEID_PCM_FLOAT)? sizeof(float) : ((new_audio_waveid==MPXPLAY_WAVEID_PCM_F64LE)? sizeof(double) : (new_audio_bits / 8));
	spi->block_align = ffmpi->outsample_size * new_audio_filechannels;
	spi->bs_framesize = INFFMPG_MAX_OUTPUT_SAMPLENUM * spi->block_align;
	funcbit_enable(spi->flags,(MPXPLAY_SPI_FLAG_NEED_DECODER|MPXPLAY_SPI_FLAG_CONTAINER));

	if( (spi->wave_id != new_audio_waveid) || (adi->bits != new_audio_bits) || (adi->freq != new_audio_freq) || (adi->filechannels != new_audio_filechannels)
	 || (ffmpi->audioframe_channel_layout != channel_layout)
#ifdef MPXPLAY_GUI_CONSOLE
	 || (ffmpi->audioframe_codec_id != av_codec_id)
#endif
	){
		spi->wave_id = new_audio_waveid;
		adi->bits = new_audio_bits;
		adi->freq = new_audio_freq;
		adi->filechannels = new_audio_filechannels;
		adi->outchannels = new_audio_filechannels;
		ffmpi->audioframe_channel_layout = channel_layout;
#ifdef MPXPLAY_GUI_CONSOLE
		ffmpi->audioframe_codec_id = av_codec_id;
		in_ffmpeg_avinfo_strings_assemble(ffmpi, miis, NULL);
		funcbit_enable(refdisp, RDT_HEADER);
#endif
		return 1;
	}

	return 0;
}

static void in_ffmpeg_avinfo_strings_assemble(struct ffmpg_demuxer_data_s *ffmpi,struct mpxplay_infile_info_s *miis, char *filename)
{
	struct mpxplay_streampacket_info_s *spi = miis->audio_stream;
	struct mpxplay_audio_decoder_info_s *adi = miis->audio_decoder_infos;

	if(miis->longname)
		pds_free(miis->longname);
	if(adi->bitratetext)
		pds_free(adi->bitratetext);

	miis->longname = pds_malloc(INFFMPG_ADITEXTSIZE_LONGNAME + 8);
	adi->bitratetext = pds_malloc(INFFMPG_ADITEXTSIZE_LONGNAME + 8);
	if(miis->longname && adi->bitratetext)
	{
		char conatiner_name[32]="", *s;
		char codec_name[MPXPLAY_STREAMTYPEINDEX_NUM][32];
		char bit_rate_text[MPXPLAY_STREAMTYPEINDEX_NUM][80];
		int i;
		pds_strncpy(conatiner_name, (ffmpi->fctx->iformat->long_name)? (char *)ffmpi->fctx->iformat->long_name :
				((ffmpi->fctx->iformat->name)? (char *)ffmpi->fctx->iformat->name :
				pds_filename_get_extension_from_shortname(filename)), sizeof(conatiner_name));
		s = pds_strchr(conatiner_name, ',');
		if(s)
			*s = 0;
		pds_str_uppercase(conatiner_name);
		for(i = 0; i <= MPXPLAY_STREAMTYPEINDEX_VIDEO; i++)
		{
			int ff_stream_index = ffmpi->selected_ff_stream_index[i];
			codec_name[i][0] = 0;
			bit_rate_text[i][0] = 0;
			if((ff_stream_index >= 0) && (ff_stream_index < ffmpi->fctx->nb_streams))
			{
				AVStream *st = ffmpi->fctx->streams[ff_stream_index];
				char resolution_text[64] = "", fps_text[64] = "", bitrate_text[64] = "";
				int bit_rate;
				if(!st || !st->codecpar)
					continue;
				pds_strncpy(codec_name[i],(char *)avcodec_get_name(st->codecpar->codec_id), sizeof(codec_name[i]));
				pds_str_uppercase(codec_name[i]);
				if(i == MPXPLAY_STREAMTYPEINDEX_VIDEO)
				{
					snprintf(resolution_text, sizeof(resolution_text), "%dx%d", st->codecpar->width, st->codecpar->height);
					resolution_text[sizeof(resolution_text) - 1] = 0;
					if(st->avg_frame_rate.num && st->avg_frame_rate.den)
					{
						snprintf(fps_text, sizeof(fps_text), ", %2.2f fps", (float)((float)st->avg_frame_rate.num / (float)st->avg_frame_rate.den));
						fps_text[sizeof(fps_text) - 1] = 0;
					}
				}
				bit_rate = ((st->codecpar->bit_rate)? st->codecpar->bit_rate : st->codec->bit_rate) / 1000;
				if(bit_rate)
				{
					if(bit_rate > 999)
					{
						snprintf(bitrate_text, sizeof(bitrate_text), "%.1f mb/s", (float)bit_rate / 1000.0);
					}
					else
					{
						snprintf(bitrate_text, sizeof(bitrate_text), "%d kb/s", bit_rate);
					}
					bitrate_text[sizeof(bitrate_text) - 1] = 0;
				}
				else if(i == MPXPLAY_STREAMTYPEINDEX_AUDIO)
				{
					snprintf(bitrate_text, sizeof(bitrate_text), " %d bits", ((adi->bits >= 8)? adi->bits : 32));
				}
				if(resolution_text[0] || bitrate_text[0])
				{
					snprintf(bit_rate_text[i], sizeof(bit_rate_text[i]), "%s%s%s%s", resolution_text, fps_text,
						((resolution_text[0] && bitrate_text[0])? ", " : ""), bitrate_text);
					bit_rate_text[i][sizeof(bit_rate_text[i]) - 1] = 0;
				}
			}
		}
#ifdef MPXPLAY_GUI_QT
		snprintf(miis->longname, INFFMPG_ADITEXTSIZE_LONGNAME, "%s -> %s%s%s", conatiner_name,
				(codec_name[MPXPLAY_STREAMTYPEINDEX_VIDEO][0])? codec_name[MPXPLAY_STREAMTYPEINDEX_VIDEO] : "",
				(codec_name[MPXPLAY_STREAMTYPEINDEX_AUDIO][0] && codec_name[MPXPLAY_STREAMTYPEINDEX_VIDEO][0])? " / " : "",
				(codec_name[MPXPLAY_STREAMTYPEINDEX_AUDIO][0])? codec_name[MPXPLAY_STREAMTYPEINDEX_AUDIO] : "");
		snprintf(adi->bitratetext, INFFMPG_ADITEXTSIZE_LONGNAME, "%s%s%s%s",
				(bit_rate_text[MPXPLAY_STREAMTYPEINDEX_AUDIO][0] && bit_rate_text[MPXPLAY_STREAMTYPEINDEX_VIDEO][0])? "A: " : "",
				(bit_rate_text[MPXPLAY_STREAMTYPEINDEX_AUDIO][0])? bit_rate_text[MPXPLAY_STREAMTYPEINDEX_AUDIO] : "",
				(bit_rate_text[MPXPLAY_STREAMTYPEINDEX_AUDIO][0] && bit_rate_text[MPXPLAY_STREAMTYPEINDEX_VIDEO][0])? "   V: " : (bit_rate_text[MPXPLAY_STREAMTYPEINDEX_VIDEO][0]? "V: " : ""),
				(bit_rate_text[MPXPLAY_STREAMTYPEINDEX_VIDEO][0])? bit_rate_text[MPXPLAY_STREAMTYPEINDEX_VIDEO] : "");
		miis->longname[INFFMPG_ADITEXTSIZE_LONGNAME] = adi->bitratetext[INFFMPG_ADITEXTSIZE_LONGNAME] = 0;
#else // MPXPLAY_GUI_CONSOLE
		snprintf(miis->longname, MPXPLAY_ADITEXTSIZE_LONGNAME, "%.4s/%-5s", conatiner_name, (codec_name[MPXPLAY_STREAMTYPEINDEX_AUDIO][0])? codec_name[MPXPLAY_STREAMTYPEINDEX_AUDIO] : "");
		snprintf(adi->bitratetext, MPXPLAY_ADITEXTSIZE_BITRATE, "%8s", (bit_rate_text[MPXPLAY_STREAMTYPEINDEX_AUDIO][0])? bit_rate_text[MPXPLAY_STREAMTYPEINDEX_AUDIO] : "");
		miis->longname[MPXPLAY_ADITEXTSIZE_LONGNAME] = adi->bitratetext[MPXPLAY_ADITEXTSIZE_LONGNAME] = 0;
#endif
	}
}

static int inffmpg_assign_values_audiovideo(struct ffmpg_demuxer_data_s *ffmpi, struct mpxplay_infile_info_s *miis, char *filename, mpxp_uint32_t openmode)
{
	struct mpxplay_audio_decoder_info_s *adi = miis->audio_decoder_infos;
	AVFormatContext *ic = ffmpi->fctx;
	int result = 0;

	miis->timemsec = ic->duration / (AV_TIME_BASE/MPXPLAY_TIME_BASE);
	if(!miis->timemsec)
	{
		mpxplay_debugf(MPXPLAY_DEBUG_HEADER,"inffmpg_assign_values_audiovideo dur:%lld", ic->duration);
		return result;
	}

	if(openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER)
	{
		if(in_fmpeg_audio_codecid_to_waveid(ffmpi, miis, NULL) >= 0)
		{
			struct mpxplay_streampacket_info_s *spi_audio = miis->audio_stream;
			mpxplay_debugf(MPXPLAY_DEBUG_HEADER,"ns:%d freq:%d chans:%d bits:%d duration:%d br:%d wid:%8.8X fmt:%d",
					ic->nb_streams,adi->freq,adi->outchannels,adi->bits,(long)miis->timemsec,adi->bitrate,spi_audio->wave_id,
					(ffmpi->codec_ctx[MPXPLAY_STREAMTYPEINDEX_AUDIO])? ffmpi->codec_ctx[MPXPLAY_STREAMTYPEINDEX_AUDIO]->sample_fmt : 0);
			in_ffmpeg_avinfo_strings_assemble(ffmpi, miis, filename);
			result = 1;
		}
		if(ffmpi->codec_ctx[MPXPLAY_STREAMTYPEINDEX_VIDEO])
		{
			struct mpxplay_streampacket_info_s *spi_video = miis->video_stream;
			spi_video->streamtype = MPXPLAY_SPI_STREAMTYPE_VIDEO;
			result = 1;
		}
	}
	else
	{
		result = 1;
	}

	return result;
}

static int inffmp_parse_stream_header(struct ffmpg_demuxer_data_s *ffmpi, struct mpxplay_filehand_buffered_func_s *fbfs, void *fbds, unsigned int stream_count_min)
{
	AVFormatContext *s = ffmpi->fctx;
	AVPacket *pkt = ffmpi->pkt;
	int j, av_stream_count=-1;
	int prev_stnum=0;
	int retry_frame = (ffmpi->flags & INFFMPG_FLAG_AUTODETECT)? 200 : 1000;
	int retry_empty = 2, retry_bad = (s->flags & INFFMPG_FLAG_AUTODETECT)? 1 : 2;
	int retry_stream = 150;
	unsigned int stream_counters[INFFMPG_MAX_STREAMS];

	memset(stream_counters, 0, sizeof(stream_counters));

	avio_seek(s->pb, 0, SEEK_SET);

	do{
		int retcode;
		av_packet_unref(pkt);
		retcode = av_read_frame(ffmpi->fctx, pkt); // this requires to parse streams (instead of iformat->read_packet)
		if(retcode==AVERROR(EIO)) // !!! means empty data here
		{
			mpxplay_debugf(MPXPLAY_DEBUG_HEADER,"AVERROR(EIO)");
			if(avio_feof(s->pb))
				break;
			if(!(--retry_empty))
				break;
		}
		else if(retcode < 0)
		{
			mpxplay_debugf(MPXPLAY_DEBUG_HEADER,"retry_bad: %d retcode:%d", retry_bad, retcode);
			if(!(--retry_bad))
				break;
			prev_stnum = 0;
			retry_stream = 150;
		}
		else if((pkt->size > 0) && (pkt->stream_index < s->nb_streams))
		{
			mpxplay_debugf(MPXPLAY_DEBUG_HEADER,"r:%5d nbs:%d si:%d mt:%d cid:%X rs:%d",retry_frame,
					s->nb_streams, pkt->stream_index, s->streams[pkt->stream_index]->codec->codec_type,
					s->streams[pkt->stream_index]->codec->codec_id, retry_stream);
			stream_counters[pkt->stream_index]++;
			av_stream_count = -1;
			for(j = 0; (j < s->nb_streams) && (j < INFFMPG_MAX_STREAMS); j++){
				AVStream *st = s->streams[j];
				if((st->codec->codec_type == AVMEDIA_TYPE_AUDIO) || (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)){
					av_stream_count++;
					//mpxplay_debugf(MPXPLAY_DEBUG_HEADER,"n:%d cid:%X sid:%d rs:%d",j,st->codec->codec_id,st->id,retry_stream);
				}
			}
			if(av_stream_count >= 0)
			{
//				if(ffmpi->flags & INFFMPG_FLAG_LOADHEADONLY) // FIXME: doesn't work on program streams
//					break;
				for(j = 0; (j < s->nb_streams) && (j < INFFMPG_MAX_STREAMS); j++)
					if(stream_counters[j] < stream_count_min) // ??? we have to read/find all streams 5 times to be sure that there are no more streams
						break;
				if((j == s->nb_streams) || (j == INFFMPG_MAX_STREAMS))
					break;
				if(!(--retry_stream))
					break;
			}
			retry_empty = 2;
			retry_bad = 2;
		}
	}while(--retry_frame);
	avio_seek(s->pb,0,SEEK_SET);
	av_packet_unref(pkt);
	mpxplay_debugf(MPXPLAY_DEBUG_HEADER,"asc:%d r:%d",av_stream_count, retry_frame);
	return av_stream_count;
}

static long inffmpg_get_pts_from_filepos(struct ffmpg_demuxer_data_s *ffmpi, struct mpxplay_filehand_buffered_func_s *fbfs, void *fbds, int64_t filepos,int64_t *pts_vals,unsigned int index,unsigned int lastindex)
{
 AVFormatContext *ic = ffmpi->fctx;
 AVPacket *pkt = ffmpi->pkt;
 unsigned int selected_streamtype_index;
 int selected_ff_stindex;
 long res,retry,retryrp;
#ifdef MPXPLAY_USE_DEBUGF
 long counter=0;
#endif

 if(ffmpi->selected_ff_stream_index[MPXPLAY_STREAMTYPEINDEX_AUDIO] >= 0){
	 selected_streamtype_index = MPXPLAY_STREAMTYPEINDEX_AUDIO;
	 selected_ff_stindex = ffmpi->selected_ff_stream_index[MPXPLAY_STREAMTYPEINDEX_AUDIO];
 }else{
	 selected_streamtype_index = MPXPLAY_STREAMTYPEINDEX_VIDEO;
	 selected_ff_stindex = ffmpi->selected_ff_stream_index[MPXPLAY_STREAMTYPEINDEX_VIDEO];
 }

 avio_seek(ic->pb, filepos, SEEK_SET);
 if(index==lastindex)
  retry=10000;
 else
  retry=1000;
 retryrp=2;
 do{
  pkt->stream_index=100;
  pkt->size=0;
  av_packet_unref(pkt);
  //if((res=av_read_frame(ic,pkt))<0)
  if((res=ic->iformat->read_packet(ic,pkt))<0)
   if((res==AVERROR(EIO)) || !(--retryrp))
    return res;
  mpxplay_debugf(MPXPLAY_DEBUG_STDUR,"%4d read_packet si:%d ssi:%d fp:%llu",retry,pkt->stream_index, selected_ff_stindex, (mpxp_int64_t)filepos);

  if((pkt->pts>=0) && ((ffmpi->fctx->nb_programs <= 1) || (pkt->stream_index==selected_ff_stindex)) && (pkt->size > 0)){ // not exact, but faster if we use the pts of other stream (and some TS files fail without this)
   int64_t timestamp = in_ffmpgdec_get_avpacket_ts_timestamp(ffmpi, selected_streamtype_index, pkt->pts) - INFFMPG_TIMESTAMP_SHIFT;

   mpxplay_debugf(MPXPLAY_DEBUG_STDUR,"%5.5d i:%d si:%d fsi:%d pts:%llu dts:%llu fp:%llu (-%llu)",
     counter++,index,pkt->stream_index,selected_ff_stindex,pkt->pts,pkt->dts,(mpxp_int64_t)filepos,(mpxp_int64_t)(fbfs->filelength(fbds) - filepos));

   if(index==lastindex){
    if((timestamp>=0) && (timestamp>pts_vals[index]))
     pts_vals[index]=timestamp;
   }else{
    if(timestamp>=0){
     pts_vals[index]=timestamp;
     break;
    }
   }
  }
 }while(--retry);
 av_packet_unref(pkt);
 if(!retry)
  return -1;
 return 0;
}

//it reads from all streams, not only a specified audio stream! (this is faster, but not exact)
static int64_t inffmpg_get_stream_duration_pts(struct ffmpg_demuxer_data_s *ffmpi,struct mpxplay_filehand_buffered_func_s *fbfs, void *fbds,struct mpxplay_infile_info_s *miis,unsigned int checknum)
{
 AVFormatContext *ic=ffmpi->fctx;
 long i;
 mpxp_filesize_t oldfilepos=-1,filepos,lastblocksize=0;
 float diff1,diff2;
 int64_t duration=0,pts_vals[INFFMPG_PTS_CHECK_NUM_MAX];

 oldfilepos=avio_tell(ic->pb);

 do{
  pds_memset(&pts_vals[0],0,sizeof(pts_vals));
  filepos=0;
  for(i=0;i<checknum;i++){
   if(inffmpg_get_pts_from_filepos(ffmpi,fbfs,fbds,filepos,&pts_vals[0],i,(checknum-1))<0)
    break;
   if(!i){ // calculate last block checksize from the first blocksize
    lastblocksize=max(INFFMPG_INITIAL_PACKETBUF_SIZE,fbfs->ftell(fbds)*2);
    if(lastblocksize>(miis->filesize/2))
     lastblocksize=miis->filesize/2;
   }
   if(i==(checknum-2)) // last block follows
    filepos=miis->filesize-lastblocksize;
   else
    filepos+=miis->filesize/(checknum-1);
   mpxplay_debugf(MPXPLAY_DEBUG_STDUR,"c:%d i:%d pts:%llu fp:%d",checknum,i,pts_vals[i],(long)filepos);
  }

  if(checknum<3){ // TS files
   if(pts_vals[1]>pts_vals[0]){ // usually have to be
    duration=pts_vals[1]-pts_vals[0];
    break;
   } // invalid pts vals, search like MPG
   checknum=3;
  }else{ // MPG/VOB files (search for two sequential block with similar length)
   diff1=0;
   for(i=0;i<(checknum-1);i++){
    if(pts_vals[i+1]<pts_vals[i])
     diff1=0;
    else if(diff1==0)
     diff1=(float)pts_vals[i+1]-(float)pts_vals[i];
    else{
     diff2=pts_vals[i+1]-pts_vals[i];
     if(((diff1*1.4)>diff2) && ((diff1*0.6)<diff2)){ // max 40% difference between the length of block1 and block2
      if(checknum==3){
       duration=pts_vals[2]-pts_vals[0];
      }else{
       if(diff2>diff1)
        diff1=diff2;
       duration=(diff1*(float)(checknum-1));
      }
      goto err_out_durpts;
     }else
      diff1=diff2;
    }
   }
  }

  if(checknum>(INFFMPG_PTS_CHECK_NUM_MAX/2)){ // last pass, block-search failed
   if(pts_vals[1]>pts_vals[0]){
    for(i=1;i<(checknum-1);i++){
     if(pts_vals[i+1]<pts_vals[i]){ // pts[i] is the last valid pts, we calculate the duration from this value
      duration=(((float)pts_vals[i]-(float)pts_vals[0])*(float)(checknum-1)/(float)i);
      goto err_out_durpts;
     }
    }
    if(i==(checknum-1))
     duration=pts_vals[checknum-1]-pts_vals[0];
   }
  }
  checknum<<=1; // 3,6,12
 }while(checknum<=INFFMPG_PTS_CHECK_NUM_MAX);

err_out_durpts:

 avio_seek(ic->pb, oldfilepos, SEEK_SET);

 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"sp0:%"PRIi64" sp1:%"PRIi64" ep:%"PRIi64" d:%"PRIi64"",pts_vals[0],pts_vals[1],pts_vals[checknum-1],duration);
 return duration;
}

//------------------------------------------------------------------------------------------------------------------
static int inffmpg_infile_check(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,AVInputFormat *demuxer,mpxp_uint32_t openmode)
{
	struct ffmpg_demuxer_data_s *ffmpi;
	int i;

	mpxplay_debugf(MPXPLAY_DEBUG_HEADER, "-----------------------------------------------------------------------------------------------");
	mpxplay_debugf(MPXPLAY_DEBUG_HEADER, "inffmpg_infile_check BEGIN play:%d (om:%8.8X) d:%8.8X %s", (openmode & MPXPLAY_INFILE_OPENMODE_LOAD_PLAY)? 1 : 0, openmode, (int)fbds, filename);

#ifdef INFFMPG_USE_MPXPLAY_IOCTX
	funcbit_disable(openmode,(O_WRONLY|O_RDWR)); // ???
	if(!fbfs->fopen(fbds, filename, openmode|O_BINARY, INFFMPG_IOCTX_SIZE_MAX)){
		mpxplay_debugf(MPXPLAY_DEBUG_HEADER, "inffmpg_infile_check fopen failed");
		return MPXPLAY_ERROR_INFILE_FILEOPEN;
	}

	miis->filesize = fbfs->filelength(fbds);
	if(miis->filesize <= 44)
		goto err_out_chk;
#else
	miis->filesize = 1000000;
#endif

	ffmpi = pds_calloc(1, sizeof(struct ffmpg_demuxer_data_s));
	if(!ffmpi)
		goto err_out_chk;
	miis->private_data = ffmpi;
	ffmpi->fbds = fbds;
	ffmpi->miis = miis;

	if(miis->control_cb(fbds, MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_ISSEEKABLE, NULL, NULL))
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_IS_SEEKABLE);
	if(miis->control_cb(fbds, MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_ISSTREAM, NULL, NULL))
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_IS_LIVESTREAM);
	if(!demuxer)
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_AUTODETECT);
	if(!funcbit_test(openmode, MPXPLAY_INFILE_OPENMODE_LOAD_PLAY)){
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_LOADHEADONLY);
		ffmpi->streamtype_limit = MPXPLAY_STREAMTYPEINDEX_VIDEO;
	}else
#ifdef MPXPLAY_GUI_QT
	if(mpxplay_dispqt_ffmpegvideo_configcbk_check(MPXPLAY_INFILE_CBKCFG_SRVFFMV_OPEN_CALLBACK) == 0)
		ffmpi->streamtype_limit = MPXPLAY_STREAMTYPEINDEX_SUBTITLE;
	else
#endif
		ffmpi->streamtype_limit = MPXPLAY_STREAMTYPEINDEX_AUDIO;

	mpxplay_debugf(MPXPLAY_DEBUG_HEADER, "inffmpg_infile_check seekable: %d demux:%s", (ffmpi->flags & INFFMPG_FLAG_IS_SEEKABLE), (demuxer? demuxer->name : ""));

	ffmpi->fctx = avformat_alloc_context();
	if(!ffmpi->fctx)
		goto err_out_chk;

#ifdef INFFMPG_USE_MPXPLAY_IOCTX
	i = miis->control_cb(fbds, MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_RBLOCKSIZE, NULL, NULL);
	if(i < INFFMPG_IOCTX_SIZE_MIN)
		i = INFFMPG_IOCTX_SIZE_MIN;
	else if(i > INFFMPG_IOCTX_SIZE_MAX)
		i = INFFMPG_IOCTX_SIZE_MAX;
	ffmpi->iobuffer = av_malloc(i + 1024);
	ffmpi->ioctx = avio_alloc_context(ffmpi->iobuffer, i, 0, fbds, (ffmpeg_read_packet_t *)fbfs->fread, NULL, (ffmpi->flags & INFFMPG_FLAG_IS_SEEKABLE)? fbfs->fseek : NULL);
	if(!ffmpi->ioctx)
		goto err_out_chk;
	ffmpi->fctx->pb = ffmpi->ioctx;

	if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_IS_LIVESTREAM))
	{
		miis->timemsec = 0x7eeeeeee;
		ffmpi->fctx->format_probesize = 16384;
		funcbit_disable(openmode, MPXPLAY_INFILE_OPENMODE_INFO_LENGTH);
	}
	if(!(openmode & MPXPLAY_INFILE_OPENMODE_LOAD_SEEKTAB))
		funcbit_enable(ffmpi->fctx->flags, (AVFMT_FLAG_IGNIDX | AVFMT_FLAG_NOPARSE | AVFMT_FLAG_NOFILLIN));
	else if(!funcbit_test(ffmpi->flags, INFFMPG_FLAG_IS_SEEKABLE))
		funcbit_enable(ffmpi->fctx->flags, AVFMT_FLAG_IGNIDX);
	funcbit_enable(ffmpi->fctx->flags, (AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_NOBUFFER));

	if(avformat_open_input(&ffmpi->fctx, (openmode & MPXPLAY_INFILE_OPENMODE_LOAD_PLAY)? NULL : filename, (openmode & MPXPLAY_INFILE_OPENMODE_AUTODETECT)? NULL : demuxer, NULL)!=0)
#else
	if(avformat_open_input(&ffmpi->fctx, filename, (openmode & MPXPLAY_INFILE_OPENMODE_AUTODETECT)? NULL : demuxer, NULL)!=0)
#endif
		goto err_out_chk;

	mpxplay_debugf(MPXPLAY_DEBUG_HEADER, "inffmpg_infile_check avformat_open_input OK format:%s dur:%lld", ffmpi->fctx->iformat->name, ffmpi->fctx->duration);
	if(ffmpi->fctx->duration <= 0)
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_IS_STREAM); // TODO: check

	if( (ffmpi->flags & INFFMPG_FLAG_IS_STREAM) && (ffmpi->flags & INFFMPG_FLAG_IS_SEEKABLE)
	 && (infmpeg_is_fileformat_real_stream(ffmpi) || (ffmpi->fctx->iformat == &ff_ogg_demuxer)) // TODO: for other formats
	){
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_STREAM_PARSING);
	}

	if(!infmpeg_is_fileformat_real_stream(ffmpi) && !infmpeg_is_fileformat_native_audio_stream(ffmpi))
	{
		funcbit_enable(((struct mpxpframe_s *)fbds)->flags, MPXPLAY_MPXPFRAME_FLAG_FILEFORMAT_CONTAINER); // TODO: config API
	}

	if((openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER) || (ffmpi->fctx->duration <= 0) || (ffmpi->flags & INFFMPG_FLAG_STREAM_PARSING)) // FIXME: hack for ff_ogg_demuxer to get ID3 infos
	{
		funcbit_disable(ffmpi->fctx->flags, (AVFMT_FLAG_NOPARSE | AVFMT_FLAG_NOFILLIN));
		if(!(openmode & MPXPLAY_INFILE_OPENMODE_INFO_LENGTH))
			ffmpi->fctx->duration = (int64_t)miis->timemsec * (AV_TIME_BASE / MPXPLAY_TIME_BASE);
		ffmpi->pkt = av_packet_alloc();
		if(!ffmpi->pkt)
			goto err_out_chk;
		if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_IS_LIVESTREAM) && infmpeg_is_fileformat_real_stream(ffmpi))
			fbfs->fseek(fbds, 0, SEEK_SET);
//		if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_IS_LIVESTREAM)){ // TODO: live stream parsing
//			inffmp_parse_stream_header(ffmpi, fbfs, fbds, 5);
//		}
//		else
		{
			if((openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER) || !(ffmpi->flags & INFFMPG_FLAG_STREAM_PARSING)){
				if(avformat_find_stream_info(ffmpi->fctx, NULL) < 0)
					goto err_out_chk;
				mpxplay_debugf(MPXPLAY_DEBUG_HEADER, "inffmpg_infile_check avformat_find_stream_info END format:%s dur:%lld ns:%d",
					ffmpi->fctx->iformat->name, ffmpi->fctx->duration, ffmpi->fctx->nb_streams);
			}
			else if(inffmp_parse_stream_header(ffmpi, fbfs, fbds, 5) < 0)
				goto err_out_chk;
		}
		if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_IS_LIVESTREAM) && infmpeg_is_fileformat_real_stream(ffmpi))
			fbfs->fseek(fbds, 0, SEEK_SET);
		if(ffmpi->fctx->duration < AV_TIME_BASE)
			ffmpi->fctx->duration = AV_TIME_BASE;
	}

	if((openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER) || (ffmpi->flags & INFFMPG_FLAG_STREAM_PARSING))
	{
		in_ffmpgdec_packet_queues_init(&ffmpi->codecctx_queues[0]);
//		ffmpi->program_number_current = (ffmpi->fctx->programs)? ffmpi->fctx->programs[0]->id : INFFMPG_INVALID_STREAM_INDEX; // FIXME: bullshit
		ffmpi->program_number_current = INFFMPG_INVALID_STREAM_INDEX;
		if(openmode & MPXPLAY_INFILE_OPENMODE_LOAD_PLAY)
		{
			if(in_ffmpgdec_content_has_programs(ffmpi))
			{
				ffmpi->program_number_current = mpxplay_control_startup_programid_select;
				mpxplay_debugf(MPXPLAY_DEBUG_TEMP, "program_number_current %d", ffmpi->program_number_current);
			}
		}

		ffmpi->select_new_program_number = INFFMPG_INVALID_STREAM_INDEX;
		in_ffmpgdec_stream_select_all_default(ffmpi);
		in_ffmpgdec_streams_initialize_all(ffmpi, TRUE);
	}

	if((ffmpi->flags & INFFMPG_FLAG_STREAM_PARSING) && (openmode & MPXPLAY_INFILE_OPENMODE_INFO_LENGTH))
	{
		ffmpi->fctx->duration = inffmpg_get_stream_duration_pts(ffmpi, fbfs, fbds, miis, (ffmpi->fctx->iformat == &ff_mpegps_demuxer)? 3 : 2);
//		if(ffmpi->fctx->duration < AV_TIME_BASE)
//			if(avformat_find_stream_info(ffmpi->fctx, NULL) < 0) // FIXME: doesn't work here
//				goto err_out_chk;
	}

	if(!inffmpg_assign_values_audiovideo(ffmpi,miis,filename,openmode))
		goto err_out_chk;

	if(openmode & MPXPLAY_INFILE_OPENMODE_LOAD_PLAY)
	{
		ffmpi->audio_frame = av_frame_alloc();
		if(!ffmpi->audio_frame)
			goto err_out_chk;
#ifdef INFFMP_USE_DEMUXER_MUTEX
		pds_threads_mutex_new(&ffmpi->mutexhnd_demuxer);
#endif
		pds_threads_mutex_new(&ffmpi->mutexhnd_ffmpeg);
		in_ffmpgdec_packet_queues_init(&ffmpi->packet_queues[0]);
		funcbit_enable(ffmpi->fctx->flags, AVFMT_FLAG_FAST_SEEK);
#ifdef INFFMP_USE_DEMUXER_SEEK
		ffmpi->seek_request_pos = INFFMPG_SEEKREQUEST_INVALID_POS;
#endif
		ffmpi->avsync_timestamp = INFFMPG_INVALID_PTS;
		for(i = 0; i < MPXPLAY_STREAMTYPEINDEX_NUM; i++)
			ffmpi->stream_last_pts[i] = INFFMPG_INVALID_PTS;
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_INITIAL_SEEK);
		if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_IS_LIVESTREAM))
		{
			infmpeg_preanalyze_and_init_video(ffmpi, fbfs, fbds);
		}
		else
		{
			avio_flush(ffmpi->fctx->pb);
			avformat_flush(ffmpi->fctx);
		}
		funcbit_enable(((struct mpxpframe_s *)fbds)->flags, MPXPLAY_MPXPFRAME_FLAG_OWN_SRVFFMV_CALLBACK); // TODO: config API
		mpxplay_debugf(MPXPLAY_DEBUG_OPENCLOSE, "ffmpg infile PLAY %8.8X fbds:%8.8X", (int)ffmpi, (int)fbds);
	}

	mpxplay_debugf(MPXPLAY_DEBUG_HEADER, "inffmpg_infile_check END OK %8.8X", (int)ffmpi);

	return MPXPLAY_ERROR_INFILE_OK;

err_out_chk:
	mpxplay_debugf(MPXPLAY_DEBUG_HEADER, "inffmpg_infile_check FAILED");
	return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static void FFMPG_preinit(void)
{
	av_register_all();
#ifdef MPXPLAY_GUI_QT
	avfilter_register_all();
#endif
	av_log_set_level(AV_LOG_PANIC);
	//av_log_set_level(AV_LOG_ERROR);
	//av_log_set_level(AV_LOG_WARNING);
	//av_log_set_level(AV_LOG_VERBOSE);
	//av_log_set_level(AV_LOG_INFO);
}

static void FFMPG_infile_close(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
	struct ffmpg_demuxer_data_s *ffmpi=miis->private_data;
	struct mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
	if(ffmpi){
		int i;
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_DEMUX_TERMINATE);
#ifdef INFFMP_USE_DEMUXER_SEEK
		ffmpi->seek_request_pos = INFFMPG_SEEKREQUEST_INVALID_POS;
#endif
		if(ffmpi->mutexhnd_ffmpeg){
			mpxplay_debugf(MPXPLAY_DEBUG_OPENCLOSE, "ffmpg infile CLOSE BEGIN %8.8X nba:%d nbv:%d pn:%d", (long)ffmpi,
				ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_AUDIO].nb_packets, ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets,
				mpxplay_control_startup_programid_select);
		}
#ifdef MPXPLAY_GUI_QT
		if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_CALLBACKVIDEOCFG))
			mpxplay_dispqt_ffmpegvideo_config_callback(MPXPLAY_INFILE_CBKCFG_SRVFFMV_CLOSE_CALLBACK, in_ffmpgdec_infile_callback, (void *)ffmpi);
#endif
		if(ffmpi->mutexhnd_ffmpeg){
		    mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "ffmpg infile CLOSE pds_threads_mutex_lock BEGIN");
			PDS_THREADS_MUTEX_LOCK(&ffmpi->mutexhnd_ffmpeg, INFFMPG_MUTEX_TIMEOUT);
			if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_DEMUX_THREAD)){
				mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "ffmpg infile CLOSE mpxplay_timer_deletefunc BEGIN");
				mpxplay_timer_deletefunc(&in_ffmpgdec_demux_read_packets, ffmpi);
			}
			//mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "ffmpg infile CLOSE avio_flush BEGIN");
			if(ffmpi->fctx){
				if(ffmpi->fctx->pb)
					avio_flush(ffmpi->fctx->pb);
				avformat_flush(ffmpi->fctx);
			}
			mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "ffmpg infile CLOSE in_ffmpgdec_packet_queues_close BEGIN");
			in_ffmpgdec_packet_queues_close(ffmpi, &ffmpi->packet_queues[0]);
			mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "ffmpg infile CLOSE in_ffmpgdec_packet_queues_close END");
		}
		if(ffmpi->pkt)
			av_packet_free(&ffmpi->pkt);
		if(ffmpi->audio_frame)
			av_frame_free(&ffmpi->audio_frame);
		in_ffmpgdec_packet_queues_close(ffmpi, &ffmpi->codecctx_queues[0]);
		if(ffmpi->fctx)
			avformat_close_input(&ffmpi->fctx);
#ifdef INFFMPG_USE_MPXPLAY_IOCTX
		if(ffmpi->mutexhnd_ffmpeg)
		{
			mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "ffmpg infile CLOSE fbfs->fclose BEGIN");
		}
		fbfs->fclose(fbds);
#endif
		if(ffmpi->mutexhnd_ffmpeg){
			void *mutexhnd = ffmpi->mutexhnd_ffmpeg;
			ffmpi->mutexhnd_ffmpeg = NULL;
			mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "ffmpg infile CLOSE pds_threads_mutex_unlock BEGIN");
			PDS_THREADS_MUTEX_UNLOCK(&mutexhnd);
			pds_threads_mutex_del(&mutexhnd);
#ifdef INFFMP_USE_DEMUXER_MUTEX
			pds_threads_mutex_del(&ffmpi->mutexhnd_demuxer);
#endif
			mpxplay_debugf(MPXPLAY_DEBUG_OPENCLOSE, "ffmpg infile CLOSE END %8.8X", (long)ffmpi);
		}
		if(miis->longname){
			pds_free(miis->longname);
			miis->longname = NULL;
		}
		if(adi->bitratetext){
			pds_free(adi->bitratetext);
			adi->bitratetext = NULL;
		}
		if(adi->chanmatrix)
		{
			pds_free(adi->chanmatrix);
			adi->chanmatrix = NULL;
		}
		pds_memset(ffmpi, 0, sizeof(*ffmpi));
		pds_free(ffmpi);
	}
}

static int FFMPG_infile_demux(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
	struct ffmpg_demuxer_data_s *ffmpi = miis->private_data;
	struct mpxplay_streampacket_info_s *spi = miis->audio_stream;
	int retcode_demux = MPXPLAY_ERROR_INFILE_SYNC_IN, i, retcode_audio = 0;

	if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_DEMUX_TERMINATE))
	{
		return MPXPLAY_ERROR_INFILE_EOF;
	}

	if(PDS_THREADS_MUTEX_LOCK(&ffmpi->mutexhnd_ffmpeg, 10) != MPXPLAY_ERROR_OK)
	{
		mpxplay_debugf(MPXPLAY_DEBUG_ERROR,"FFMPG_infile_demux LOCK FAILED %8.8X", (int)ffmpi);
		goto err_out_demux;
	}

	if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_DEMUX_TERMINATE))
	{
		PDS_THREADS_MUTEX_UNLOCK(&ffmpi->mutexhnd_ffmpeg);
		retcode_demux = MPXPLAY_ERROR_INFILE_EOF;
		goto err_out_demux;
	}

	//mpxplay_debugf(MPXPLAY_DEBUG_DEMUX,"FFMPG_infile_demux START rd:%d", ffmpi->retcode_demux);

	if(!funcbit_test(ffmpi->flags, INFFMPG_FLAG_DEMUX_THREAD) && !funcbit_test(ffmpi->flags, INFFMPG_FLAG_STILLPICTURE))
	{
		ffmpi->fbfs = fbfs;
		ffmpi->fbds = fbds;
		ffmpi->miis = miis;
		in_ffmpgdec_demux_read_packets(ffmpi);
	}

	retcode_demux = ffmpi->retcode_demux;

	retcode_audio = in_ffmpgdec_decode_audio(ffmpi, miis);
	if((retcode_audio != 0) && (ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets || funcbit_test(ffmpi->flags, INFFMPG_FLAG_STILLPICTURE) || (ffmpi->select_new_stream_number[MPXPLAY_STREAMTYPEINDEX_AUDIO] == INFFMPG_STREAM_INDEX_LOST)))
	{
		spi->bs_leftbytes = PCM_OUTSAMPLES * spi->block_align;
		pds_memset(spi->bitstreambuf, 0, spi->bs_leftbytes);
		funcbit_enable(spi->flags,MPXPLAY_SPI_FLAG_SILENTBLOCK);
		mpxplay_debugf(MPXPLAY_DEBUG_ADECODE, "FFMPG_infile_demux FILL empty audio ff:%8.8X sp:%8.8X", (unsigned int)ffmpi,spi->flags);
	}
	else
	{
		funcbit_disable(spi->flags,MPXPLAY_SPI_FLAG_SILENTBLOCK);
	}

	if(retcode_demux < 0){
		mpxplay_debugf(MPXPLAY_DEBUG_ADECODE,"FFMPG_infile_demux %8.8X rd:%d ra:%d lb:%d nba:%d nbv:%d fl:%8.8X", (unsigned int)ffmpi, retcode_demux, retcode_audio, spi->bs_leftbytes,
				ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_AUDIO].nb_packets, ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets, ffmpi->flags);
		if(spi->bs_leftbytes || ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_AUDIO].nb_packets || ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets)
			retcode_demux = MPXPLAY_ERROR_INFILE_OK;
		else if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_IS_LIVESTREAM) || (retcode_demux==AVERROR(ESPIPE)) || (retcode_demux==AVERROR(EAGAIN)))
			retcode_demux = MPXPLAY_ERROR_INFILE_SYNC_IN;
		else
			retcode_demux = MPXPLAY_ERROR_INFILE_EOF;
	}
	else if(!spi->bs_leftbytes && !ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_AUDIO].nb_packets && !ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets){
		if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_IS_LIVESTREAM))
			retcode_demux = MPXPLAY_ERROR_INFILE_SYNC_IN;
		else
			retcode_demux = MPXPLAY_ERROR_INFILE_NODATA;
//		mpxplay_debugf(MPXPLAY_DEBUG_DEMUX,"retcode_demux: NODATA %8.8X", (int)ffmpi);
	}else
		retcode_demux = MPXPLAY_ERROR_INFILE_OK;

//	if((retcode_demux != MPXPLAY_ERROR_INFILE_OK) || !spi->bs_leftbytes){
//		mpxplay_debugf(MPXPLAY_DEBUG_DEMUX,"FFMPG_infile_demux END %8.8X frd:%d rd:%d ra:%d lb:%d ea:%d an:%d vn:%d fpos:%d%% fl:%8.8X", (int)ffmpi, ffmpi->retcode_demux,
//				retcode_demux, retcode_audio,
//				spi->bs_leftbytes, AVERROR(EAGAIN), ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_AUDIO].nb_packets,
//				ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets, (int)(100 * fbfs->ftell(fbds) / fbfs->filelength(fbds)), ffmpi->flags);
//	}

	if(retcode_demux != MPXPLAY_ERROR_INFILE_EOF)
	{
		ffmpi->miis = miis;
		ffmpi->fbfs = fbfs;
		ffmpi->fbds = fbds;
	}
	//mpxplay_debugf(MPXPLAY_DEBUG_DEMUX,"FFMPG_infile_demux END fbds:%8.8X ra:%d rd:%d", (unsigned int)ffmpi->fbds, retcode_audio, retcode_demux);

	PDS_THREADS_MUTEX_UNLOCK(&ffmpi->mutexhnd_ffmpeg);

err_out_demux:
	return retcode_demux;
}

static void INFFMPG_clearbuffs_all(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,unsigned int seektype)
{
}

static void infmpeg_preanalyze_and_init_video(struct ffmpg_demuxer_data_s *ffmpi, struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
	int i;

	mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "infmpeg_preanalyze_and_init_video BEGIN %8.8X", (unsigned int)ffmpi);

#ifdef INFFMPG_USE_MPXPLAY_IOCTX
	ffmpi->fbfs = fbfs;
	ffmpi->fctx->pb->opaque = fbds;
	ffmpi->fctx->pb->read_packet = (ffmpeg_read_packet_t *)fbfs->fread;
#endif

	funcbit_enable(ffmpi->flags, INFFMPG_FLAG_DEMUX_THREAD);
	i = ffmpi->fctx->nb_streams * 2 + 100;
	do{
		ffmpi->fbfs = fbfs;
		ffmpi->fbds = fbds;
		in_ffmpgdec_demux_read_packets(ffmpi);
	}while((--i) && (ffmpi->retcode_demux == 0) && (ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_AUDIO].nb_packets < INFFMPG_QUEUE_PACKETNUM_FILL_MAX)
												&& (ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets < INFFMPG_QUEUE_PACKETNUM_FILL_MAX));
	funcbit_disable(ffmpi->flags, INFFMPG_FLAG_DEMUX_THREAD);

#ifdef MPXPLAY_GUI_QT
	if(!ffmpi->codec_ctx[MPXPLAY_STREAMTYPEINDEX_AUDIO] && (ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets > 0) && (ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets <= INFFMPG_STILL_PICTURE_FRAME_MAX)) // FIXME: assumed still picture hack
	{
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_STILLPICTURE);
	}

	if(mpxplay_dispqt_ffmpegvideo_config_callback(MPXPLAY_INFILE_CBKCFG_SRVFFMV_OPEN_CALLBACK, in_ffmpgdec_infile_callback, (void *)ffmpi) == 0)
	{
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_CALLBACKVIDEOCFG);
	}
#endif
	if(!funcbit_test(ffmpi->flags, INFFMPG_FLAG_STILLPICTURE))
	{
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_DEMUX_THREAD); // !!! we use it for audio too if video out is enabled
#ifdef INFFMP_USE_DEMUXER_THREAD
#ifdef INFFMP_USE_DEMUXER_BUFILL
		funcbit_enable(((struct mpxpframe_s *)fbds)->buffertype, (PREBUFTYPE_BUFFFILL_NOCHK | PREBUFTYPE_BUFFFILL_NOSKHLP)); // FIXME: API hack
#endif
		mpxplay_timer_addfunc(&in_ffmpgdec_demux_read_packets, ffmpi, MPXPLAY_TIMERTYPE_THREAD | MPXPLAY_TIMERTYPE_REPEAT | MPXPLAY_TIMERFLAG_MULTIPLY, 1);
#else
		mpxplay_timer_addfunc(&in_ffmpgdec_demux_read_packets, ffmpi, MPXPLAY_TIMERTYPE_REPEAT | MPXPLAY_TIMERFLAG_MULTIPLY, 1);
#endif
		mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "infmpeg_preanalyze_and_init_video mpxplay_timer_addfunc in_ffmpgdec_demux_read_packets %8.8X", (unsigned int)ffmpi);
	}

	if(!funcbit_test(ffmpi->flags, INFFMPG_FLAG_CALLBACKVIDEOCFG))
	{
		funcbit_disable(ffmpi->flags, INFFMPG_FLAG_STILLPICTURE);
		ffmpi->streamtype_limit = MPXPLAY_STREAMTYPEINDEX_AUDIO;
	}

	funcbit_disable(ffmpi->flags, INFFMPG_FLAG_INITIAL_SEEK);

	mpxplay_debugf(MPXPLAY_DEBUG_OPENCLOSE,"infmpeg_preanalyze_and_init_video END %8.8X nba:%d nbv:%d sp:%d i:%d rd:%d", (unsigned int)ffmpi,
	  ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_AUDIO].nb_packets, ffmpi->packet_queues[MPXPLAY_STREAMTYPEINDEX_VIDEO].nb_packets,
	  ((ffmpi->flags & INFFMPG_FLAG_STILLPICTURE)? 1 : 0), i, ffmpi->retcode_demux);
}

void infmpeg_infile_seek_process(struct ffmpg_demuxer_data_s *ffmpi)
{
	mpxp_int64_t seek_pos = ffmpi->seek_request_pos;
	unsigned int seek_flags = ffmpi->seek_request_flags;
#ifdef INFFMP_USE_DEMUXER_SEEK
	struct mpxpframe_s *frp = (struct mpxpframe_s *)ffmpi->fbds;
	unsigned int buffertype_save;
#endif
	int retcode;

#ifdef INFFMP_USE_DEMUXER_SEEK
	if(frp)
	{
		buffertype_save = frp->buffertype;
		funcbit_disable(frp->buffertype, PREBUFTYPE_INT);
	}
	ffmpi->seek_request_pos = INFFMPG_SEEKREQUEST_INVALID_POS;
#endif
	ffmpi->seek_request_retcode = MPXPLAY_ERROR_OK;

	mpxplay_debugf(MPXPLAY_DEBUG_SEEK,"infmpeg_infile_seek_process BEGIN ffmpi:%8.8X fl:%8.8X pos:%lld dur:%lld fs:%d",
			(unsigned int)ffmpi, seek_flags, seek_pos, ffmpi->fctx->duration, (long)ffmpi->fbfs->filelength(ffmpi->fbds));

	retcode = av_seek_frame(ffmpi->fctx, -1, seek_pos, seek_flags);
	if(retcode < 0)
	{
		funcbit_disable(seek_flags, AVSEEK_FLAG_BACKWARD);
		retcode = av_seek_frame(ffmpi->fctx, -1, seek_pos, seek_flags);
		mpxplay_debugf(MPXPLAY_DEBUG_WARNING,"av_seek_frame FAILED %d", retcode);
		if(retcode < 0)
		{
			if(ffmpi->fbfs && ffmpi->fbfs->filelength && ffmpi->fbfs->fseek && ffmpi->fbds)
			{
				if(!(seek_flags & AVSEEK_FLAG_BYTE))
					seek_pos = (mpxp_int64_t)((float)ffmpi->fbfs->filelength(ffmpi->fbds) * (float)seek_pos / (float)ffmpi->fctx->duration);
				if(ffmpi->fbfs->fseek(ffmpi->fbds, seek_pos, SEEK_SET) != seek_pos)
					ffmpi->seek_request_retcode = MPXPLAY_ERROR_INFILE_EOF;
				mpxplay_debugf(MPXPLAY_DEBUG_WARNING,"av_seek_frame LOWSEEK pos:%lld ret:%d", seek_flags, ffmpi->seek_request_retcode);
			}
			else
			{
				ffmpi->seek_request_retcode = MPXPLAY_ERROR_INFILE_EOF;
			}
		}
	}
#ifdef INFFMP_USE_DEMUXER_SEEK
	if(frp)
	{
		funcbit_smp_copy(frp->buffertype, buffertype_save, PREBUFTYPE_INT);
	}
#endif
	if(!funcbit_test(ffmpi->flags, INFFMPG_FLAG_INITIAL_SEEK) && (ffmpi->seek_request_retcode == MPXPLAY_ERROR_OK))
	{
		funcbit_enable(ffmpi->flags, INFFMPG_FLAG_SEEK_CLEAR);
		if(funcbit_test(seek_flags, AVSEEK_FLAG_PAUSE))
		{
			in_ffmpgdec_packet_queues_clear(ffmpi, ffmpi->packet_queues);  // FIXME: this is not the proper solution
		}
	}
	mpxplay_debugf(MPXPLAY_DEBUG_SEEK,"infmpeg_infile_seek_process END ff:%8.8X fl:%8.8X pos:%lld ret:%d", (unsigned int)ffmpi, seek_flags, seek_pos, ffmpi->seek_request_retcode);
}

static long FFMPG_infile_fseek_av(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,long newmpxframenum)
{
	struct ffmpg_demuxer_data_s *ffmpi=miis->private_data;
	mpxp_filesize_t newfilepos;
	mpxp_int64_t newtimepos;
	//AVStream *stream;
	int i, seek_flags = 0, retcode;
#ifdef INFFMP_USE_DEMUXER_MUTEX
	int lock_result = -1;
#endif

#ifdef INFFMP_USE_DEMUXER_SEEK
	ffmpi->seek_request_pos = INFFMPG_SEEKREQUEST_INVALID_POS;
#endif

	mpxplay_debugf(MPXPLAY_DEBUG_OPENCLSDTL, "FFMPG_infile_fseek_av BEGIN frp:%8.8X ffmpi:%8.8X nf:%d", (unsigned int)fbds, (unsigned int)ffmpi, newmpxframenum);

	if((newmpxframenum > miis->allframes) || ((ffmpi->flags & INFFMPG_FLAG_STILLPICTURE) && (miis->seektype & MPX_SEEKTYPE_RELATIVE)))
	{
		return MPXPLAY_ERROR_INFILE_EOF;
	}
#ifdef INFFMP_USE_DEMUXER_SEEK
	if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_SEEK_CLEAR) && newmpxframenum && !(miis->seektype & MPX_SEEKTYPE_BOF))
	{
		return newmpxframenum;
	}
#endif
	if(!funcbit_test(ffmpi->flags, INFFMPG_FLAG_IS_SEEKABLE))
	{
		if(infmpeg_is_fileformat_real_stream(ffmpi)) // hack for DVB for faster start  TODO:???
			if(!newmpxframenum || (miis->seektype & MPX_SEEKTYPE_BOF))
				fbfs->fseek(fbds, newmpxframenum, SEEK_SET);  // we rewind only in the buffer
		return newmpxframenum;
	}

#ifdef INFFMP_USE_DEMUXER_MUTEX
	if(!funcbit_test(ffmpi->flags, INFFMPG_FLAG_INITIAL_SEEK))
	{
		mpxplay_debugf(MPXPLAY_DEBUG_SEEK,"FFMPG_infile_fseek_av LOCK begin");
		lock_result = PDS_THREADS_MUTEX_LOCK(&ffmpi->mutexhnd_demuxer, INFFMPG_MUTEX_TIMEOUT);
		mpxplay_debugf(MPXPLAY_DEBUG_SEEK,"FFMPG_infile_fseek_av LOCK end res:%d", lock_result);
	}
#endif
	ffmpi->retcode_demux = MPXPLAY_ERROR_OK;

#ifdef INFFMPG_USE_MPXPLAY_IOCTX
	ffmpi->fbfs = fbfs;
	ffmpi->fctx->pb->opaque = fbds;
	ffmpi->fctx->pb->read_packet = (ffmpeg_read_packet_t *)fbfs->fread;
	ffmpi->fctx->pb->seek = (miis->control_cb(fbds, MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_ISSEEKABLE, NULL, NULL))? fbfs->fseek : NULL;
#endif

	if(miis->seektype&MPX_SEEKTYPE_BACKWARD)
		funcbit_enable(seek_flags,AVSEEK_FLAG_BACKWARD);
	if(ffmpi->flags & INFFMPG_FLAG_STREAM_PARSING)
	{
		newtimepos = (int64_t)((float)miis->filesize * (float)newmpxframenum / (float)miis->allframes);
		funcbit_enable(seek_flags, AVSEEK_FLAG_BYTE);
	}
	else
	{
		if((miis->seektype&MPX_SEEKTYPE_PRECISE) || !ffmpi->nb_streams[MPXPLAY_STREAMTYPEINDEX_VIDEO])
			funcbit_enable(seek_flags,AVSEEK_FLAG_ANY);
		//stream = ffmpi->selected_avstream[(ffmpi->nb_streams[MPXPLAY_STREAMTYPEINDEX_VIDEO])? MPXPLAY_STREAMTYPEINDEX_VIDEO : MPXPLAY_STREAMTYPEINDEX_AUDIO];
		newtimepos = (miis->allframes)? (int64_t)((float)miis->timemsec * (float)newmpxframenum / (float)miis->allframes) * (AV_TIME_BASE/MPXPLAY_TIME_BASE) : 0;
		//newtimepos = newtimepos * stream->time_base.num / stream->time_base.den;
	}
	if(funcbit_test(miis->seektype, MPX_SEEKTYPE_PAUSE))
	{
		funcbit_enable(seek_flags, AVSEEK_FLAG_PAUSE);
	}

	ffmpi->seek_request_flags = seek_flags;
	ffmpi->seek_request_pos = newtimepos;
#ifndef INFFMP_USE_DEMUXER_SEEK
	infmpeg_infile_seek_process(ffmpi);
	if(ffmpi->seek_request_retcode != MPXPLAY_ERROR_OK)
	{
		newmpxframenum = ffmpi->seek_request_retcode;
		goto err_out_seek;
	}
#endif

	if(funcbit_test(ffmpi->flags, INFFMPG_FLAG_INITIAL_SEEK))
	{
#ifdef INFFMP_USE_DEMUXER_SEEK
		ffmpi->fbds = fbds;
		infmpeg_infile_seek_process(ffmpi);
#endif
		infmpeg_preanalyze_and_init_video(ffmpi, fbfs, fbds);
	}
	else if(funcbit_test(miis->seektype, MPX_SEEKTYPE_PAUSE))
	{
		ffmpi->fbfs = fbfs;
		ffmpi->fbds = fbds;
#ifndef INFFMP_USE_DEMUXER_THREAD
		in_ffmpgdec_demux_read_packets(ffmpi);
#endif
	}

err_out_seek:
#ifdef INFFMP_USE_DEMUXER_MUTEX
	if(lock_result == MPXPLAY_ERROR_OK)
	{
		PDS_THREADS_MUTEX_UNLOCK(&ffmpi->mutexhnd_demuxer);
	}
#endif

	mpxplay_debugf(MPXPLAY_DEBUG_SEEK,"FFMPG_infile_fseek END np:%lld", ffmpi->seek_request_pos);

	return newmpxframenum;
}

//-------------------------------------------------------------------
static void inffmpg_tag_get_str(struct mpxplay_infile_info_s *miis, void *fbds, AVDictionaryEntry *t, unsigned int cmd, int i3i_index)
{
	int len;
	if(t && miis){
		char *str = t->value;
		if(str && str[0]){
			len = -1;
			miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PACKFUNCTTI3I(cmd,MPXPLAY_TEXTCONV_TYPE_UTF8,i3i_index),str,&len);
		}
	}
}

void inffmpg_tags_update(struct mpxplay_infile_info_s *miis, void *fbds, AVDictionary *metadata, unsigned int cmd)
{
	AVDictionaryEntry *t;

	t = av_dict_get(metadata, "title", NULL, 0);
	inffmpg_tag_get_str(miis, fbds, t, cmd, I3I_TITLE);
	t = av_dict_get(metadata, "artist", NULL, 0);
	inffmpg_tag_get_str(miis, fbds, t, cmd, I3I_ARTIST);
	t = av_dict_get(metadata, "comment", NULL, 0);
	inffmpg_tag_get_str(miis, fbds, t, cmd, I3I_COMMENT);
	t = av_dict_get(metadata, "album", NULL, 0);
	inffmpg_tag_get_str(miis, fbds, t, cmd, I3I_ALBUM);
	t = av_dict_get(metadata, "genre", NULL, 0);
	inffmpg_tag_get_str(miis, fbds, t, cmd, I3I_GENRE);
	t = av_dict_get(metadata, "date", NULL, 0);
	inffmpg_tag_get_str(miis, fbds, t, cmd, I3I_YEAR);
	t = av_dict_get(metadata, "track", NULL, 0);
	inffmpg_tag_get_str(miis, fbds, t, cmd, I3I_TRACKNUM);
}

static int INFFMPG_tag_get(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
	struct ffmpg_demuxer_data_s *ffmpi = (struct ffmpg_demuxer_data_s *)miis->private_data;

	if(!ffmpi || !ffmpi->fctx)
		return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;

	if(ffmpi->fctx->metadata)
		inffmpg_tags_update(miis, fbds, ffmpi->fctx->metadata, MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PUT);

	return MPXPLAY_ERROR_INFILE_OK;
}

static int INFFMPEG_autodetect_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,NULL,openmode);
}

static int INAAC_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_aac_demuxer,openmode);
}

static int INAPE_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_ape_demuxer,openmode);
}

static int INASF_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,NULL,openmode); // NULL because multiply parsers in FFMPEG
}

static int INAVI_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_avi_demuxer,openmode);
}

static int INDTS_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_dts_demuxer,openmode);
}

static int INAC3_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_ac3_demuxer,openmode);
}

static int INFLAC_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_flac_demuxer,openmode);
}

static int INFLV_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_flv_demuxer,openmode);
}

static int INMKV_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_matroska_demuxer,openmode);
}

static int INMOV_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_mov_demuxer,openmode);
}

static int INMP3_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_mp3_demuxer,openmode);
}

static int INMPG_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,NULL,openmode); // NULL because multiply parsers in FFMPEG
}

static int INOGG_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_ogg_demuxer,openmode);
}

static int INTS_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 return inffmpg_infile_check(fbfs,fbds,filename,miis,&ff_mpegts_demuxer,openmode);
}

// scripts: AVS, concat
// subtitles: AQT, ASS, (DC)STR, jacosub, LRC, microDVD, MPL2, SUB
// "SMI","SAMI",               // samidec.c (subtitle)

struct mpxplay_infile_func_s IN_FFMPEG_AUTODETECT={
 MPXPLAY_INFILEFUNC_FLAG_VIDEO,
 &FFMPG_preinit, NULL, &INFFMPEG_autodetect_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all,
 &INFFMPG_tag_get,
 NULL, NULL,
 {"FFM",					// this is used for a general FFM (FFMPEG) name (FFM->CODEC)
 "4XM",						// 4xm.c
 "ACM",						// acm.c
 "ACT",						// act.c
 "ADP","DTK"				// adp.c
 "ADS","SS2",				// ads.c
 "ADX",						// adxdec.c
 "AEA",						// aea.c
 "AFC",						// afc/c
 "AIF","AIFF","AFC","AIFC",	// aiffdec.c
 "AIX",						// aixdec.c
 "AMR",						// amr.c
 "ANM",						// anm.c
 "APC",						// apc.c
 "APNG",					// apngdec.c
 "AST",						// astdec.c
 "AU",						// au.c
 "AVR",						// avr.c
 "AVS",						// avs.c
 "VID",						// bethsoftvid.c
 "BFI",						// bfi.c
 "BIK",						// bink.c
 "IDF","ADF",				// bintext.c
 "BIT",						// bit.c
 "BMV",						// bmv.c
 "BOA",						// boadec.c
 "BRSTM","BFSTM","BCSTM",	// brstm.c
 "C93",						// c93.c
 "CAF",						// caf.c
 "CDG",						// cdg.c
 "CDXL","XL",				// cdxl.c
 "CINE",					// cinedec.c
 "302","DAUD",				// dauddec.c
 "DFA",						// dfa.c
 "DSF",						// dsfdec.c
 "DSI",						// dsicin.c
 "DSS",						// dss.c
 "DTSHD",					// dtshddec.c
 "DV","DIF",				// dv.c
 "DXA",						// dxa.c
 "CDATA",					// eacdata.c
 "WVE","UV2",				// electronicarts.c
 "PAF","FAP",				// epafdec.c
 "FFM",						// ffmdec.c
 "FLM",						// filmstripdec.c
 "FLI","FLC","FLX",			// flic.c
 "FRM",						// frmdec.c
 "FSB",						// fsb.c
 "G722","722",				// g722.c
 "TCO","RCO","G723_1",		// g723_1.c
 "G729",					// g729.c
 "GENH",					// genh.c
 "GIF",						// gifdec.c
 "GSM",						// gsmdec.c
 "GXF",						// gxf.c
#ifdef MPXPLAY_GUI_QT
 "H261",					// h261dec.c
 "H263",					// h263dec.c
 "H26L","H264","264","AVC",	// h264dec.c
 "HEVC","H265","265",		// hevc.c
#endif
 "HNM",						// hnm.c
 "ICO",						// icodec.c  // FIXME: it seems it doesn't work (no video packet is demuxed)
 "ROQ",						// idroqdec.c
 "IFF",						// iff.c
 "LBC",						// ilbc.c
#ifdef MPXPLAY_GUI_QT
 "BMP","DDS","DPX","EXR","JPG","JPEG","PAM","PBM","PCX","PGM","PNG","SGI","TIF","TIFF","WEBP", // img2dec.c
 "PIX",                     // img2_alias_pix.c
#endif
 "CGI",						// ingenientdec.c (can be wrong)
 "MVE",						// ipmovie.c
 "SF","IRCAM",				// ircamdec.c
 "ISS",						// icc.c
 "IV8",						// iv8.c
 "IVF",						// ivfdec.c
 "JV",						// jvdec.c
 "LFV",						// lvfdec.c
 "LXF",						// lxfdec.c
 "M4V",						// m4vdec.c
 "MGSTS",					// mgsts.c
 "J2K",                     // mj2kdec.c
 "MLP","THD",				// mlpdec.c
 "MM",						// mm.c
 "MMF",						// mmf.c
 "MPC",						// mpc, mpc8.c
 "MJPG",					// mpjpeg.c, mpjpegdec.c, rawdec.c, smjpegdec.c
 "MSF",						// msf.c
 "MTAF",					// mtaf.c
 "MTV",						// mtc.c
 "MUSX",					// musx.c
 "MV",						// mvdec.c
 "MVI",						// mvi.c
 "MXF",						// mxfdec.c
 "MXG",						// mxg.c
 "V",						// ncdec.c
 "NIST","SPH",				// nistspheredec.c
 "NSV",						// nsvdec.c
 "NUT",						// nutdec.c
 "NUV",						// nuv.c
 "OMA","OMG","AA3",			// omadec.c
 "PAF",						// paf.c
 "UW","UB","AL","UL","SLN",	// pcmdec.c
 "PJS",						// pjsdec.c
 "PVA",						// pva.c
 "PVF",						// pvfdec.c
 "QCP",						// qcp.c
 "R3D",						// r3d.c
 "MJPEG","MPO",				// rawdec.c
 "YUV","CIF","QCIF","RGB",	// rawvideodec.c
 "RSD",						// redspark.c
 "RL2",						// rl2.c
 "RM","RDT","IVR",			// rmdec.c
 "RPL",						// rpl.c
 "RSD",						// rsd.c
 "RSO",						// rsodec.c
 "SBG",						// sbgdec.c
 "SDR2",					// sdr2.c
 "SDS",                     // sdsdec.c
 "SDX",                     // sdxdec.c
 "SHN",						// shortendec.c
 "VMD",						// sierravmd.c
 "VB","SON",				// siff.c
 "SMK",						// smacker.c
 "SOL",						// sol.c
 "SOX",						// soxdec.c
 "SVAG",					// svag.c
 "SWF",						// swfdec.c
 "TAK",						// takdec.c
 "THP",						// thp.c
 "TMV",						// tmv.c
 "TTA",						// tta.c
 "V210","YUV10",			// v210.c
 "VAG",						// vag.c
 "VC1",						// vc1dec.c
 "VIV",						// vivo.c
 "VOC",						// vocdec.c
 "VPK",						// vpk.c
 "VQF","VQL","VQE",			// vqf.c
 "WAV","W64", "CDW",		// wavdec.c
 "WC3",						// wc3movie.c
 "AUD",						// westwood_aud.c
 "VQA",						// westwood_vqa.c
 "WSD",						// wsddec.c
 "WTV",						// wtvdec.c
 "WV",						// wvdec.c
 "WVE",						// wvedec.c
 "XA",						// xa.c
 "XMV",						// xmv.c
 "XVAG",					// xvag.c
 "XWMA",					// xwma.c
 "YOP",						// yop.c
 "Y4M",						// yuv4mpegdec.c
 NULL
 }
};

struct mpxplay_infile_func_s IN_FFMPEG_AAC_funcs={
 ( MPXPLAY_INFILEFUNC_FLAG_NOADETECT
 | MPXPLAY_TAGTYPE_PUT_SUPPORT(MPXPLAY_TAGTYPE_ID3V1|MPXPLAY_TAGTYPE_ID3V2|MPXPLAY_TAGTYPE_APETAG)
 | MPXPLAY_TAGTYPE_PUT_PRIMARY(MPXPLAY_TAGTYPE_APETAG)),
 NULL, NULL, &INAAC_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all, NULL, NULL, NULL,
 {"AAC",NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_APE_funcs={
 ( MPXPLAY_INFILEFUNC_FLAG_NOADETECT
 | MPXPLAY_TAGTYPE_PUT_SUPPORT(MPXPLAY_TAGTYPE_ID3V1|MPXPLAY_TAGTYPE_ID3V2|MPXPLAY_TAGTYPE_APETAG)
 | MPXPLAY_TAGTYPE_PUT_PRIMARY(MPXPLAY_TAGTYPE_APETAG)),
 NULL, NULL, &INAPE_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all, NULL, NULL, NULL,
 {"APE","APL","MAC",NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_ASF_funcs={
 MPXPLAY_INFILEFUNC_FLAG_NOADETECT | MPXPLAY_INFILEFUNC_FLAG_VIDEO | MPXPLAY_INFILEFUNC_FLAG_ONLYVIDEO,
 NULL, NULL, &INASF_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all,
 &INFFMPG_tag_get,
 &mpxplay_ASF_infile_tag_put,
 NULL,
 {"ASF","WMV","WMA",NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_AVI_funcs={
 MPXPLAY_INFILEFUNC_FLAG_NOADETECT | MPXPLAY_INFILEFUNC_FLAG_VIDEO | MPXPLAY_INFILEFUNC_FLAG_ONLYVIDEO,
 NULL, NULL, &INAVI_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all,
 &INFFMPG_tag_get, NULL, NULL,
 {"AVI",NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_AC3_funcs={
 ( MPXPLAY_INFILEFUNC_FLAG_NOADETECT
 | MPXPLAY_TAGTYPE_PUT_SUPPORT(MPXPLAY_TAGTYPE_ID3V1|MPXPLAY_TAGTYPE_APETAG)
 | MPXPLAY_TAGTYPE_PUT_PRIMARY(MPXPLAY_TAGTYPE_APETAG)),
 NULL, NULL, &INAC3_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all, NULL, NULL, NULL,
 {"AC3",NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_DTS_funcs={
  ( MPXPLAY_INFILEFUNC_FLAG_NOADETECT
  | MPXPLAY_TAGTYPE_PUT_SUPPORT(MPXPLAY_TAGTYPE_ID3V1|MPXPLAY_TAGTYPE_APETAG)
  | MPXPLAY_TAGTYPE_PUT_PRIMARY(MPXPLAY_TAGTYPE_APETAG)),
 NULL, NULL, &INDTS_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all, NULL, NULL, NULL,
 {"DTS",NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_FLAC_funcs={
 MPXPLAY_INFILEFUNC_FLAG_NOADETECT,
 NULL, NULL, &INFLAC_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all,
 &INFFMPG_tag_get, &mpxplay_INFLAC_tag_put, NULL,
 {"FLAC","FLA",NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_FLV_funcs={
 MPXPLAY_INFILEFUNC_FLAG_NOADETECT|MPXPLAY_INFILEFUNC_FLAG_VIDEO|MPXPLAY_INFILEFUNC_FLAG_ONLYVIDEO,
 NULL, NULL, &INFLV_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all,
 &INFFMPG_tag_get, NULL, NULL,
 {"FLV",NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_MKV_funcs={
 MPXPLAY_INFILEFUNC_FLAG_NOADETECT|MPXPLAY_INFILEFUNC_FLAG_VIDEO|MPXPLAY_INFILEFUNC_FLAG_ONLYVIDEO,
 NULL, NULL, &INMKV_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all,
 &INFFMPG_tag_get, NULL, NULL,
 {"MKV","MK3D","MKA","MKS","WEBM",
#ifdef __DOS__
 "WEB",
#endif
 NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_MOV_funcs={
 MPXPLAY_INFILEFUNC_FLAG_NOADETECT|MPXPLAY_INFILEFUNC_FLAG_VIDEO,
 NULL, NULL, &INMOV_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all,
 &INFFMPG_tag_get, &mpxplay_INMP4_tag_put, NULL,
 {"MP4","MOV","M4A","M4B","3GP","3G2","MJ2", NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_MP3_funcs={
( MPXPLAY_INFILEFUNC_FLAG_NOADETECT
| MPXPLAY_TAGTYPE_PUT_SUPPORT(MPXPLAY_TAGTYPE_ID3V1|MPXPLAY_TAGTYPE_APETAG)
| MPXPLAY_TAGTYPE_PUT_PRIMARY(MPXPLAY_TAGTYPE_APETAG)),
 NULL, NULL, &INMP3_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all, NULL, NULL, NULL,
 {"MP3","MP2","MPA","M2A", NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_MPG_funcs={
 MPXPLAY_INFILEFUNC_FLAG_NOADETECT|MPXPLAY_INFILEFUNC_FLAG_NOCHECK|MPXPLAY_INFILEFUNC_FLAG_VIDEO|MPXPLAY_INFILEFUNC_FLAG_ONLYVIDEO,
 NULL, NULL, &INMPG_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all, NULL, NULL, NULL,
 {"MPG","MPEG","VOB","M2V",
#ifdef __DOS__
 "MPE",
#endif
 NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_OGG_funcs={
 MPXPLAY_INFILEFUNC_FLAG_NOADETECT|MPXPLAY_INFILEFUNC_FLAG_VIDEO,
 NULL, NULL, &INOGG_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all,
 &INFFMPG_tag_get, &mpxplay_INOGG_tag_put, NULL,
 {"OGG","OGA","SPX","OPUS","OGM","OGV", // FIXME: SPX doesn't work in FFMPEG?
#ifdef __DOS__
 "OPU",
#endif
 NULL}
};

struct mpxplay_infile_func_s IN_FFMPEG_TS_funcs={
 MPXPLAY_INFILEFUNC_FLAG_NOADETECT|MPXPLAY_INFILEFUNC_FLAG_NOCHECK|MPXPLAY_INFILEFUNC_FLAG_VIDEO|MPXPLAY_INFILEFUNC_FLAG_ONLYVIDEO,
 NULL, NULL, &INTS_infile_open, &FFMPG_infile_close, &FFMPG_infile_demux, &FFMPG_infile_fseek_av, &INFFMPG_clearbuffs_all, NULL, NULL, NULL,
 {"TS","MTS","M2TS",
#ifdef __DOS__
 "M2T",
#endif
 NULL}
};

#endif //MPXPLAY_LINK_INFILE_FF_MPEG
