/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2017-2025
 *					All rights reserved
 *
 *  This file is part of GPAC / inspection 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/filters.h>
#include <gpac/constants.h>
#include <gpac/list.h>
#include <gpac/xml.h>
#include <gpac/internal/media_dev.h>
#include <gpac/internal/isomedia_dev.h>

#ifndef GPAC_DISABLE_INSPECT

static u32 inspect_log_tool = GF_LOG_APP;

typedef struct
{
	GF_FilterPid *src_pid;
	FILE *tmp;
	u64 pck_num;
	u32 idx;
	u8 dump_pid; //0: no, 1: configure/reconfig, 2: info update
	u8 init_pid_config_done;
	u64 pck_for_config;
	u64 prev_dts, prev_cts, init_ts;
	u32 codec_id, service_id;
	u32 stream_type;

#ifndef GPAC_DISABLE_AV_PARSERS
	HEVCState *hevc_state;
	AVCState *avc_state;
	VVCState *vvc_state;
	AV1State *av1_state;
	GF_M4VParser *mv124_state;
	GF_M4VDecSpecInfo dsi;
#endif
	GF_VPConfig *vpcc;

	//for analyzing
	GF_BitStream *bs;

	Bool has_svcc;
	u32 nalu_size_length;
	Bool is_adobe_protected;
	Bool is_cenc_protected;
	Bool aborted;
	Bool is_fake;

	GF_Fraction tmcd_rate;
	u32 tmcd_flags;
	u32 tmcd_fpt;

	u32 opus_channel_count;

	u32 csize;
	Bool buffer_done, no_analysis;
	u32 buf_start_time;
	u64 last_pcr;
	GF_FilterClockType last_clock_type;

	char *stat_codecs;
	GF_FilterSAPType start_with_sap;
	u32 timescale;
	u64 total_dur;
	u64 last_sap_cts;
	u32 nb_sap1, nb_sap2, nb_sap3, nb_sap4;
	u32 min_sap_diff, max_sap_diff, avg_sap_diff;
	u64 nb_bytes;
	s64 max_ctso, min_ctso;
	Bool has_disc, has_corr;
	u8 has_crypted;
	u32 nb_recfg;
	u32 max_sap_size, avg_sap_size, avg_nosap_size;
	u32 nb_nosaps;
	u32 constant_dur, max_dur;
	u64 first_dts, first_cts;
	u32 bytes_in_wnd, max_rate;
} PidCtx;

GF_OPT_ENUM (GF_InspectDumpMode,
	INSPECT_MODE_PCK=0,
	INSPECT_MODE_BLOCK,
	INSPECT_MODE_REFRAME,
	INSPECT_MODE_RAW,
);

GF_OPT_ENUM (GF_InspectSkipPropsMode,
	INSPECT_TEST_NO=0,
	INSPECT_TEST_NOPROP,
	INSPECT_TEST_NETWORK,
	INSPECT_TEST_NETX,
	INSPECT_TEST_ENCODE,
	INSPECT_TEST_ENCX,
	INSPECT_TEST_NOCRC,
	INSPECT_TEST_NOBR,
);

GF_OPT_ENUM (GF_InspectSampleAnalyzeMode,
	INSPECT_ANALYZE_OFF=0,
	INSPECT_ANALYZE_ON,
	INSPECT_ANALYZE_BS,
	INSPECT_ANALYZE_BS_BITS,
);

typedef struct
{
	u32 mode, timeout;
	Bool interleave;
	Bool dump_data;
	Bool deep;
	Bool stats;
	char *log;
	char *fmt;
	GF_InspectSampleAnalyzeMode analyze;
	Bool props, hdr, allp, info, pcr, xml, full;
	Double speed, start;
	GF_InspectSkipPropsMode test;
	GF_Fraction dur;
	Bool crc, dtype;
	Bool fftmcd;
	u32 buffer, mbuffer, rbuffer;

	FILE *dump;
	Bool dump_log;

	GF_List *src_pids;

	Bool is_prober, probe_done, hdr_done, dump_pck;
	Bool args_updated;
	Bool has_seen_eos;

	u32 last_config_time;

} GF_InspectCtx;

static void format_duration(s64 dur, u64 timescale, FILE *dump, Bool skip_name);

GF_Err gf_bs_set_logger(GF_BitStream *bs, void (*on_bs_log)(void *udta, const char *field_name, u32 nb_bits, u64 field_val, s32 idx1, s32 idx2, s32 idx3), void *udta);

static void inspect_printf(FILE *dump, const char *fmt, ...)
{
	va_list list;
	if (dump == NULL) {
#ifndef GPAC_DISABLE_LOG
		if (gf_log_tool_level_on(inspect_log_tool, GF_LOG_INFO) ) {
			gf_log_lt(GF_LOG_INFO, inspect_log_tool);
			va_start(list, fmt);
			gf_log_va_list(GF_LOG_INFO, inspect_log_tool, fmt, list);
			va_end(list);
		}
#endif
	} else {
		va_start(list, fmt);
		gf_vfprintf(dump, fmt, list);
		va_end(list);
	}
}

#define DUMP_ATT_STR(_name, _val) if (ctx->xml) { \
		inspect_printf(dump, " %s=\"%s\"", _name, _val); \
	} else { \
		inspect_printf(dump, " %s %s", _name, _val); \
	}

#define DUMP_ATT_LLU(_name, _val) if (ctx->xml) { \
		inspect_printf(dump, " %s=\""LLU"\"", _name, _val);\
	} else {\
		inspect_printf(dump, " %s "LLU, _name, _val);\
	}

#define DUMP_ATT_U(_name, _val) if (ctx->xml) { \
		inspect_printf(dump, " %s=\"%u\"", _name, _val);\
	} else {\
		inspect_printf(dump, " %s %u", _name, _val);\
	}

#define DUMP_ATT_D(_name, _val) if (ctx->xml) { \
		inspect_printf(dump, " %s=\"%d\"", _name, _val);\
	} else {\
		inspect_printf(dump, " %s %d", _name, _val);\
	}

#define DUMP_ATT_X(_name, _val)  if (ctx->xml) { \
		inspect_printf(dump, " %s=\"0x%08X\"", _name, _val);\
	} else {\
		inspect_printf(dump, " %s 0x%08X", _name, _val);\
	}

#define DUMP_ATT_BOOL(_name, _val)  if (_val == 0) { \
		DUMP_ATT_STR(_name, "False")\
	} else {\
		DUMP_ATT_STR(_name, "True")\
	}

#define DUMP_ATT_FRAC(_name, _val)  if (ctx->xml) { \
		inspect_printf(dump, " %s=\"%d/%u\"", _name, _val.num, _val.den);\
	} else {\
		inspect_printf(dump, " %s %d/%u", _name, _val.num, _val.den);\
	}



#ifndef GPAC_DISABLE_AV_PARSERS
static u32 inspect_get_nal_size(char *ptr, u32 nalh_size)
{
	u32 nal_size=0;
	u32 v = nalh_size;
	while (v) {
		nal_size |= (u8) *ptr;
		ptr++;
		v-=1;
		if (v) nal_size <<= 8;
	}
	return nal_size;
}

typedef struct
{
	u32 code;
	const char *name;
} tag_to_name;

static const tag_to_name SEINames[] =
{
	{0, "buffering_period"},
	{1, "pic_timing"},
	{2, "pan_scan_rect"},
	{3, "filler_payload"},
	{4, "itu_t_t35"},
	{5, "user_data_unregistered"},
	{6, "recovery_point"},
	{7, "dec_ref_pic_marking_repetition"},
	{8, "spare_pic"},
	{9, "scene_info"},
	{10, "sub_seq_info"},
	{11, "sub_seq_layer_characteristics"},
	{12, "sub_seq_characteristics"},
	{13, "full_frame_freeze"},
	{14, "full_frame_freeze_release"},
	{15, "picture_snapshot"},
	{16, "progressive_refinement_segment_start"},
	{17, "progressive_refinement_segment_end"},
	{18, "motion_constrained_slice_group_set"},
	{19, "film_grain_characteristics"},
	{20, "deblocking_filter_display_preference"},
	{21, "stereo_video_info"},
	{22, "post_filter_hint"},
	{23, "tone_mapping_info"},
	{24, "scalability_info"},
	{25, "sub_pic_scalable_layer"},
	{26, "non_required_layer_rep"},
	{27, "priority_layer_info"},
	{28, "layers_not_present"},
	{29, "layer_dependency_change"},
	{30, "scalable_nesting"},
	{31, "base_layer_temporal_hrd"},
	{32, "quality_layer_integrity_check"},
	{33, "redundant_pic_property"},
	{34, "tl0_dep_rep_index"},
	{35, "tl_switching_point"},
	{36, "parallel_decoding_info"},
	{37, "mvc_scalable_nesting"},
	{38, "view_scalability_info"},
	{39, "multiview_scene_info"},
	{40, "multiview_acquisition_info"},
	{41, "non_required_view_component"},
	{42, "view_dependency_change"},
	{43, "operation_points_not_present"},
	{44, "base_view_temporal_hrd"},
	{45, "frame_packing_arrangement"},
	{47, "display_orientation"},
	{56, "green_metadata"},
	{128, "structure_of_pictures_info"},
	{129, "active_parameter_sets"},
	{130, "decoding_unit_info"},
	{131, "temporal_sub_layer_zero_index"},
	{132, "decoded_picture_hash"},
	{133, "scalable_nesting"},
	{134, "region_refresh_info"},
	{135, "no_display"},
	{136, "time_code"},
	{137, "mastering_display_colour_volume"},
	{138, "segmented_rect_frame_packing_arrangement"},
	{140, "temporal_motion_constrained_tile_sets"},
	{141, "knee_function_info"},
	{142, "colour_remapping_info"},
	{143, "deinterlaced_field_identification"},
	{144, "content_light_level_info"},
	{145, "dependent_rap_indication"},
	{146, "coded_region_completion"},
	{147, "alternative_transfer_characteristics"},
	{148, "ambient_viewing_environment"},
	{160, "layers_not_present"},
	{161, "inter_layer_constrained_tile_sets"},
	{162, "bsp_nesting"},
	{163, "bsp_initial_arrival_time"},
	{164, "sub_bitstream_property"},
	{165, "alpha_channel_info"},
	{166, "overlay_info"},
	{167, "temporal_mv_prediction_constraints"},
	{168, "frame_field_info"},
	{176, "three_dimensional_reference_displays_info"},
	{177, "depth_representation_info"},
	{178, "multiview_scene_info"},
	{179, "multiview_acquisition_info"},
	{180, "multiview_view_position"},
	{181, "alternative_depth_info"}
};

static const char *get_sei_name(u32 sei_type)
{
	u32 i, count = sizeof(SEINames) / sizeof(tag_to_name);
	for (i=0; i<count; i++) {
		if (SEINames[i].code == sei_type) return SEINames[i].name;
	}
	return "Unknown";
}

static const tag_to_name HEVCNalNames[] =
{
	{GF_HEVC_NALU_SLICE_TRAIL_N, "TRAIL_N slice segment"},
	{GF_HEVC_NALU_SLICE_TRAIL_R, "TRAIL_R slice segment"},
	{GF_HEVC_NALU_SLICE_TSA_N, "TSA_N slice segment"},
	{GF_HEVC_NALU_SLICE_TSA_R, "TSA_R slice segment"},
	{GF_HEVC_NALU_SLICE_STSA_N, "STSA_N slice segment"},
	{GF_HEVC_NALU_SLICE_STSA_R, "STSA_R slice segment"},
	{GF_HEVC_NALU_SLICE_RADL_N, "RADL_N slice segment"},
	{GF_HEVC_NALU_SLICE_RADL_R, "RADL_R slice segment"},
	{GF_HEVC_NALU_SLICE_RASL_N, "RASL_N slice segment"},
	{GF_HEVC_NALU_SLICE_RASL_R, "RASL_R slice segment"},
	{GF_HEVC_NALU_SLICE_BLA_W_LP, "Broken link access slice (W LP)"},
	{GF_HEVC_NALU_SLICE_BLA_W_DLP, "Broken link access slice (W DLP)"},
	{GF_HEVC_NALU_SLICE_BLA_N_LP, "Broken link access slice (N LP)"},
	{GF_HEVC_NALU_SLICE_IDR_W_DLP, "IDR slice (W DLP)"},
	{GF_HEVC_NALU_SLICE_IDR_N_LP, "IDR slice (N LP)"},
	{GF_HEVC_NALU_SLICE_CRA, "CRA slice"},
	{GF_HEVC_NALU_VID_PARAM, "Video Parameter Set"},
	{GF_HEVC_NALU_SEQ_PARAM, "Sequence Parameter Set"},
	{GF_HEVC_NALU_PIC_PARAM, "Picture Parameter Set"},
	{GF_HEVC_NALU_ACCESS_UNIT, "AU Delimiter"},
	{GF_HEVC_NALU_END_OF_SEQ, "End of Sequence"},
	{GF_HEVC_NALU_END_OF_STREAM, "End of Stream"},
	{GF_HEVC_NALU_FILLER_DATA, "Filler Data"},
	{GF_HEVC_NALU_SEI_PREFIX, "SEI Prefix"},
	{GF_HEVC_NALU_SEI_SUFFIX, "SEI Suffix"},
	{GF_HEVC_NALU_FF_AGGREGATOR, "HEVCAggregator"},
	{GF_HEVC_NALU_FF_EXTRACTOR, "HEVCExtractor"},
	{GF_HEVC_NALU_DV_RPU, "UNSPEC_DolbyVision_RPU"},
	{GF_HEVC_NALU_DV_EL, "UNSPEC_DolbyVision_EL"}
};

static const char *get_hevc_nal_name(u32 nal_type)
{
	u32 i, count = sizeof(HEVCNalNames) / sizeof(tag_to_name);
	for (i=0; i<count; i++) {
		if (HEVCNalNames[i].code == nal_type) return HEVCNalNames[i].name;
	}
	return NULL;
}

static const tag_to_name VVCNalNames[] =
{
	{GF_VVC_NALU_SLICE_TRAIL, "Slice_TRAIL"},
	{GF_VVC_NALU_SLICE_STSA, "Slice_STSA"},
	{GF_VVC_NALU_SLICE_RADL, "Slice_RADL"},
	{GF_VVC_NALU_SLICE_RASL, "Slice_RASL"},
	{GF_VVC_NALU_SLICE_IDR_W_RADL, "IDR_RADL"},
	{GF_VVC_NALU_SLICE_IDR_N_LP, "IDR"},
	{GF_VVC_NALU_SLICE_CRA, "CRA"},
	{GF_VVC_NALU_SLICE_GDR, "GDR"},
	{GF_VVC_NALU_OPI, "OperationPointInfo"},
	{GF_VVC_NALU_DEC_PARAM, "DecoderParameterSet"},
	{GF_VVC_NALU_VID_PARAM, "VideoParameterSet"},
	{GF_VVC_NALU_SEQ_PARAM, "SequenceParameterSet"},
	{GF_VVC_NALU_PIC_PARAM, "PictureParameterSet"},
	{GF_VVC_NALU_APS_PREFIX, "AdaptationParameterSet_Prefix"},
	{GF_VVC_NALU_APS_SUFFIX, "AdaptationParameterSet_Suffix"},
	{GF_VVC_NALU_ACCESS_UNIT, "AUDelimiter"},
	{GF_VVC_NALU_END_OF_SEQ, "EOS"},
	{GF_VVC_NALU_END_OF_STREAM, "EOB"},
	{GF_VVC_NALU_FILLER_DATA, "FillerData"},
	{GF_VVC_NALU_SEI_PREFIX, "SEI_Prefix"},
	{GF_VVC_NALU_SEI_SUFFIX, "SEI_Suffix"},
	{GF_VVC_NALU_PIC_HEADER, "PictureHeader"}
};

static const char *get_vvc_nal_name(u32 nal_type)
{
	u32 i, count = sizeof(VVCNalNames) / sizeof(tag_to_name);
	for (i=0; i<count; i++) {
		if (VVCNalNames[i].code == nal_type) return VVCNalNames[i].name;
	}
	return NULL;
}

static const tag_to_name AVCNalNames[] =
{
	{GF_AVC_NALU_NON_IDR_SLICE, "Non IDR slice"},
	{GF_AVC_NALU_DP_A_SLICE, "DP Type A slice"},
	{GF_AVC_NALU_DP_B_SLICE, "DP Type B slice"},
	{GF_AVC_NALU_DP_C_SLICE, "DP Type C slice"},
	{GF_AVC_NALU_IDR_SLICE, "IDR slice"},
	{GF_AVC_NALU_SEI, "SEI Message"},
	{GF_AVC_NALU_SEQ_PARAM, "SequenceParameterSet"},
	{GF_AVC_NALU_PIC_PARAM, "PictureParameterSet"},
	{GF_AVC_NALU_ACCESS_UNIT, "AccessUnit delimiter"},
	{GF_AVC_NALU_END_OF_SEQ, "EndOfSequence"},
	{GF_AVC_NALU_END_OF_STREAM, "EndOfStream"},
	{GF_AVC_NALU_FILLER_DATA, "Filler data"},
	{GF_AVC_NALU_SEQ_PARAM_EXT, "SequenceParameterSetExtension"},
	{GF_AVC_NALU_SVC_PREFIX_NALU, "SVCPrefix"},
	{GF_AVC_NALU_SVC_SUBSEQ_PARAM, "SVCSubsequenceParameterSet"},
	{GF_AVC_NALU_SLICE_AUX, "Auxiliary Slice"},
	{GF_AVC_NALU_SVC_SLICE, "SVCSlice"},
	{GF_AVC_NALU_DV_RPU, "DV_RPU"},
	{GF_AVC_NALU_DV_EL, "DV_EL"},
	{GF_AVC_NALU_FF_AGGREGATOR, "SVCAggregator"},
	{GF_AVC_NALU_FF_EXTRACTOR, "SVCExtractor"}
};

static const char *get_avc_nal_name(u32 nal_type)
{
	u32 i, count = sizeof(AVCNalNames) / sizeof(tag_to_name);
	for (i=0; i<count; i++) {
		if (AVCNalNames[i].code == nal_type) return AVCNalNames[i].name;
	}
	return NULL;
}

typedef struct
{
	FILE *dump;
	Bool dump_bits;
} InspectLogCbk;

static void inspect_log_bs(Bool is_nalu, void *udta, const char *field_name, u32 nb_bits, u64 field_val, s32 idx1, s32 idx2, s32 idx3)
{
	InspectLogCbk *cbk = (InspectLogCbk*)udta;

	if (!nb_bits)
		return;

	if (is_nalu)
		inspect_printf(cbk->dump, "\"");

	inspect_printf(cbk->dump, " %s", field_name);
	if (idx1>=0) {
		inspect_printf(cbk->dump, "_%d", idx1);
		if (idx2>=0) {
			inspect_printf(cbk->dump, "_%d", idx2);
			if (idx3>=0) {
				inspect_printf(cbk->dump, "_%d", idx3);
			}
		}
	}
	inspect_printf(cbk->dump, "=\""LLD, field_val);
	if (cbk->dump_bits && ((s32) nb_bits > 1) )
		inspect_printf(cbk->dump, "(%u)", nb_bits);

	if (!is_nalu)
		inspect_printf(cbk->dump, "\"");
}

static void shifted_bs_log(void *udta, const char *field_name, u32 nb_bits, u64 field_val, s32 idx1, s32 idx2, s32 idx3)
{
	inspect_log_bs(GF_TRUE, udta, field_name, nb_bits, field_val, idx1, idx2, idx3);
}
static void regular_bs_log(void *udta, const char *field_name, u32 nb_bits, u64 field_val, s32 idx1, s32 idx2, s32 idx3)
{
	inspect_log_bs(GF_FALSE, udta, field_name, nb_bits, field_val, idx1, idx2, idx3);
}

static void dump_clli(FILE *dump, GF_BitStream *bs)
{
	u16 max_content_light_level = gf_bs_read_int(bs, 16);
	u16 max_pic_average_light_level = gf_bs_read_int(bs, 16);
	inspect_printf(dump, " max_content_light_level=\"%u\" max_pic_average_light_level=\"%u\"", max_content_light_level, max_pic_average_light_level);
}

static void dump_mdcv(FILE *dump, GF_BitStream *bs, Bool isMPEG)
{
	u8 c;
	u16 display_primaries_x[3];
	u16 display_primaries_y[3];
	u16 white_point_x;
	u16 white_point_y;
	u32 max_display_mastering_luminance;
	u32 min_display_mastering_luminance;
	for(c=0;c<3;c++) {
		display_primaries_x[c] = gf_bs_read_int(bs, 16);
		display_primaries_y[c] = gf_bs_read_int(bs, 16);
	}
	white_point_x = gf_bs_read_int(bs, 16);
	white_point_y = gf_bs_read_int(bs, 16);
	max_display_mastering_luminance = gf_bs_read_int(bs, 32);
	min_display_mastering_luminance = gf_bs_read_int(bs, 32);
	inspect_printf(dump, " display_primaries_x=\"%.04f %.04f %.04f\" display_primaries_y=\"%.04f %.04f %.04f\" white_point_x=\"%.04f\" white_point_y=\"%.04f\" max_display_mastering_luminance=\"%.04f\" min_display_mastering_luminance=\"%.04f\"",
			   display_primaries_x[0]*1.0/(isMPEG?50000:65536),
			   display_primaries_x[1]*1.0/(isMPEG?50000:65536),
			   display_primaries_x[2]*1.0/(isMPEG?50000:65536),
			   display_primaries_y[0]*1.0/(isMPEG?50000:65536),
			   display_primaries_y[1]*1.0/(isMPEG?50000:65536),
			   display_primaries_y[2]*1.0/(isMPEG?50000:65536),
			   white_point_x*1.0/(isMPEG?50000:65536),
			   white_point_y*1.0/(isMPEG?50000:65536),
			   max_display_mastering_luminance*1.0/(isMPEG?10000:256),
			   min_display_mastering_luminance*1.0/(isMPEG?10000:(1<<14)));
}

static void dump_t35(FILE *dump, GF_BitStream *bs, u32 sei_size)
{
	u32 country_code = gf_bs_read_u8(bs);
	inspect_printf(dump, " country_code=\"0x%x\"", country_code);
	if (country_code == 0xFF) {
		u32 country_code_extension = gf_bs_read_u8(bs);
		inspect_printf(dump, " country_code_extension=\"0x%x\"", country_code_extension);
	}
	u32 terminal_provider_code = gf_bs_read_u16(bs);
	inspect_printf(dump, " terminal_provider_code=\"0x%x\"", terminal_provider_code);

	if ((terminal_provider_code==49) || (terminal_provider_code==47)) {
		u32 code = gf_bs_read_u32(bs);
		u32 udta_code = gf_bs_read_u8(bs);
		inspect_printf(dump, " user_id=\"%s\"", gf_4cc_to_str(code) );
		inspect_printf(dump, " user_data_type=\"%d\"", udta_code);
		if (terminal_provider_code==47) {
			inspect_printf(dump, " directv_user_data_length %d\n", gf_bs_read_u8(bs) );
		}
		if (udta_code==3) {
			u32 i;
			inspect_printf(dump, " em_data_flag=\"%d\"\n", gf_bs_read_int(bs, 1) );
			inspect_printf(dump, " cc_data_flag=\"%d\"\n", gf_bs_read_int(bs, 1) );
			inspect_printf(dump, " extra_data_flag=\"%d\"\n", gf_bs_read_int(bs, 1) );
			u32 cc_count = gf_bs_read_int(bs, 5);
			inspect_printf(dump, " cc_count=\"%d\"\n", cc_count);
			inspect_printf(dump, " em_data=\"%x\"\n", gf_bs_read_u8(bs));

			inspect_printf(dump, " cc_data=\"[");
			for (i=0; i< cc_count; ++i) {
				/*u8 marker = */gf_bs_read_int(bs, 5);
				u8 valid = gf_bs_read_int(bs, 1);
				u8 type = gf_bs_read_int(bs, 2);
				u16 data = gf_bs_read_u16(bs);
				if (i) inspect_printf(dump, ", ");
				if (valid)
					inspect_printf(dump, "%d 0x%x", type, data);
				else
					inspect_printf(dump, "skip");
			}
			inspect_printf(dump, "]\"");
		}
	} else {
		u32 terminal_provider_oriented_code = gf_bs_read_u16(bs);
		u32 application_identifier = gf_bs_read_u8(bs);
		u32 application_mode = gf_bs_read_u8(bs);
		inspect_printf(dump, " terminal_provider_oriented_code=\"0x%x\" application_identifier=\"%u\" application_mode=\"%u\"",
				   terminal_provider_oriented_code,
				   application_identifier, application_mode);
	}
}

static void dump_unregistered_sei(FILE *dump, GF_BitStream *bs, u32 sei_size)
{
	/* original timecode unregistered SEI UUID */
	bin128 tmcd_uuid = {
		0x42, 0x45, 0x3a, 0x42, 0x3b, 0xaa, 0xa6, 0xf1,
		0xbb, 0x3b, 0x13, 0x42, 0x0e, 0x08, 0xc7, 0x62
	};

	//uuid
	u32 i;
	bin128 uuid = {0};
	inspect_printf(dump, " uuid=\"0x");
	for (i=0; i<16; i++) {
		uuid[i] = gf_bs_read_u8(bs);
		inspect_printf(dump, "%02x", uuid[i]);
	}
	inspect_printf(dump, "\"");

	//payload
	u32 nb_read = 0;
	u8 *payload = gf_malloc(sei_size - 16);
	if (!payload) return;
	inspect_printf(dump, " payload=\"0x");
	for (i=0; i<sei_size - 16 && gf_bs_available(bs); i++) {
		payload[i] = gf_bs_read_u8(bs);
		inspect_printf(dump, "%02x", payload[i]);
		nb_read++;
	}
	inspect_printf(dump, "\"");

	if (nb_read < sei_size - 16) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Not enough data in unregistered SEI\n"));
		goto finish;
	}

	if (!memcmp(uuid, tmcd_uuid, 16)) {
		if (nb_read < 4) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Not enough data in timecode unregistered SEI\n"));
			goto finish;
		}
		inspect_printf(dump, " timecode=\"");
		inspect_printf(dump, "%02u:%02u:%02u:%02u", payload[3], payload[2], payload[1], payload[0]);
		inspect_printf(dump, "\"");
	}

finish:
	gf_free(payload);
}

static u32 dump_udta_m2v(FILE *dump, u8 *data, u32 sei_size)
{

	u32 udta_id = 0;
	if (sei_size < 4)
		return 1;

	udta_id = GF_4CC(data[0], data[1], data[2], data[3]);
	u32 udta_code = 0;

	inspect_printf(dump, " udta_id=\"%s\"", gf_4cc_to_str(udta_id));

	if (udta_id==GF_4CC('G','A','9','4') && sei_size >= 5)
		udta_code = data[4];
	else if (udta_id==GF_4CC('D','T','G','1') && sei_size >= 5)
		udta_code = data[4];

	if (udta_code)
		inspect_printf(dump, " udta_code=\"0x%X\"", udta_code);

	if (udta_code == 3 && sei_size >= 7) {
		u32 i;
		data+=5;
		inspect_printf(dump, " em_data_flag=\"%d\"\n", (data[0] & 0x80) ? 1 : 0);
		inspect_printf(dump, " cc_data_flag=\"%d\"\n", (data[0] & 0x40) ? 1 : 0 );
		inspect_printf(dump, " extra_data_flag=\"%d\"\n", (data[0] & 0x20) ? 1 : 0 );
		u32 cc_count = data[0] & 0x1F;
		inspect_printf(dump, " cc_count=\"%d\"\n", cc_count);
		inspect_printf(dump, " em_data=\"%x\"\n", data[1]);
		data+=2;
		sei_size-=7;
		inspect_printf(dump, " cc_data=\"[");
		for (i=0; i< cc_count; ++i) {
			if (sei_size < 3)
				break;
			u8 valid = (data[0]>>2) & 0x1;
			u8 type = data[0] & 0x3;
			u16 ccdata = (data[1]<<8) | data[2];
			if (i) inspect_printf(dump, ", ");
				if (valid)
					inspect_printf(dump, "%d 0x%x", type, ccdata);
				else
					inspect_printf(dump, "skip");
			data+=3;
			sei_size-=3;
		}
		inspect_printf(dump, "]\"");
	}
	return 0;
}

static void dump_time_code(FILE *dump, GF_BitStream *bs, int i)
{
	Bool full_timestamp_flag, discontinuity_flag, cnt_dropped_flag;
	u8 counting_type;
	u32 n_frames;
	u32 time_offset_length, time_offset_value;
	u32 seconds = 0, minutes = 0, hours = 0;

	counting_type = gf_bs_read_int(bs, 5);
	inspect_printf(dump, " counting_type_%d=\"%d\"", i, counting_type);
	full_timestamp_flag = gf_bs_read_int(bs, 1);
	inspect_printf(dump, " full_timestamp_flag_%d=\"%d\"", i, full_timestamp_flag);
	discontinuity_flag = gf_bs_read_int(bs, 1);
	inspect_printf(dump, " discontinuity_flag_%d=\"%d\"", i, discontinuity_flag);
	cnt_dropped_flag = gf_bs_read_int(bs, 1);
	inspect_printf(dump, " cnt_dropped_flag_%d=\"%d\"", i, cnt_dropped_flag);
	n_frames = gf_bs_read_int(bs, 9);
	if (full_timestamp_flag) {
		seconds = gf_bs_read_int(bs, 6);
		minutes = gf_bs_read_int(bs, 6);
		hours = gf_bs_read_int(bs, 5);
	} else {
		Bool seconds_flag = gf_bs_read_int(bs, 1);
		if (seconds_flag) {
			seconds = gf_bs_read_int(bs, 6);
			Bool minutes_flag = gf_bs_read_int(bs, 1);
			if (minutes_flag) {
				minutes = gf_bs_read_int(bs, 6);
				Bool hours_flag = gf_bs_read_int(bs, 1);
				if (hours_flag) {
					hours = gf_bs_read_int(bs, 5);
				}
			}
		}
	}
	inspect_printf(dump, " time_code_%d=\"%02d:%02d:%02d:%02d\"", i, hours, minutes, seconds, n_frames);
	time_offset_length = gf_bs_read_int(bs, 5);
	inspect_printf(dump, " time_offset_length_%d=\"%d\"", i, time_offset_length);
	time_offset_value = 0;
	if (time_offset_length > 0) {
		time_offset_value = gf_bs_read_int(bs, time_offset_length);
	}
	inspect_printf(dump, " time_offset_value_%d=\"%d\"", i, time_offset_value);
}

static void dump_time_code_hevc(FILE *dump, GF_BitStream *bs)
{
	u8 num_clock_ts = gf_bs_read_int(bs, 2);
	inspect_printf(dump, " num_clock_ts=\"%d\"", num_clock_ts);
	for (int i = 0; i < num_clock_ts; i++) {
		Bool clock_timestamp_flag = gf_bs_read_int(bs, 1);
		if (clock_timestamp_flag) {
			Bool units_field_based_flag = gf_bs_read_int(bs, 1);
			inspect_printf(dump, " units_field_based_flag_%d=\"%d\"", i, units_field_based_flag);
			dump_time_code(dump, bs, i);
		}
	}
}

static u32 gf_bs_inspect_int(GF_BitStream *bs, FILE *dump, char *name, int idx, int nbits)
{
	u32 val = gf_bs_read_int(bs, nbits);
	inspect_printf(dump, " %s_%d=\"%d\"", name, idx, val);
	return val;
}

static void dump_avc_pic_timing(FILE *dump, GF_BitStream *bs, AVCState *avc)
{
	int sps_id = avc->sps_active_idx;
	const char NumClockTS[] = { 1, 1, 1, 2, 2, 3, 3, 2, 3 };
	AVCSeiPicTiming *pt = &avc->sei.pic_timing;

	if (sps_id < 0) {
		/*sps_active_idx equals -1 when no sps has been detected. In this case SEI should not be decoded.*/
		gf_assert(0);
		return;
	}

	if (avc->sps[sps_id].vui.nal_hrd_parameters_present_flag || avc->sps[sps_id].vui.vcl_hrd_parameters_present_flag) { /*CpbDpbDelaysPresentFlag, see 14496-10(2003) E.11*/
		gf_bs_inspect_int(bs, dump, "cpb_removal_delay_minus1", sps_id, 1 + avc->sps[sps_id].vui.hrd.cpb_removal_delay_length_minus1);
		gf_bs_inspect_int(bs, dump, "dpb_output_delay_minus1", sps_id, 1 + avc->sps[sps_id].vui.hrd.dpb_output_delay_length_minus1);
	}

	/*ISO 14496-10 (2003), D.8.2: we need to get pic_struct in order to know if we display top field first or bottom field first*/
	if (avc->sps[sps_id].vui.pic_struct_present_flag) {
		int i;
		pt->pic_struct = gf_bs_inspect_int(bs, dump, "pic_struct", sps_id, 4);
		if (pt->pic_struct > 8) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Inspect] invalid avc pic_struct value %d\n", pt->pic_struct));
			return;
		}

		inspect_printf(dump, " num_clock_ts=\"%d\"", NumClockTS[pt->pic_struct]);
		for (i = 0; i < NumClockTS[pt->pic_struct]; i++) {
			if (gf_bs_inspect_int(bs, dump, "clock_timestamp_flag", i, 1)) {
				Bool full_timestamp_flag;
				/*ct_type*/gf_bs_read_int(bs, 2);
				gf_bs_inspect_int(bs, dump, "nuit_field_based_flag", i, 1);
				gf_bs_inspect_int(bs, dump, "counting_type", i, 5);
				full_timestamp_flag = gf_bs_inspect_int(bs, dump, "full_timestamp_flag", i, 1);
				gf_bs_inspect_int(bs, dump, "discontinuity_flag", i, 1);
				gf_bs_inspect_int(bs, dump, "cnt_dropped_flag", i, 1);
				u32 n_frames = gf_bs_read_int(bs, 8);

				u32 hours=0, minutes=0, seconds=0;
				if (full_timestamp_flag) {
					seconds = gf_bs_read_int(bs, 6);
					minutes = gf_bs_read_int(bs, 6);
					hours = gf_bs_read_int(bs, 5);
				} else {
					if (gf_bs_read_int(bs, 1)) {
						seconds = gf_bs_read_int(bs, 6);
						if (gf_bs_read_int(bs, 1)) {
							minutes = gf_bs_read_int(bs, 6);
							if (gf_bs_read_int(bs, 1)) {
								hours = gf_bs_read_int(bs, 5);
							}
						}
					}
					if (avc->sps[sps_id].vui.hrd.time_offset_length > 0) {
						inspect_printf(dump, " time_offset_length_%d=\"%d\"", i, avc->sps[sps_id].vui.hrd.time_offset_length);
						gf_bs_inspect_int(bs, dump, "time_offset_value", i, avc->sps[sps_id].vui.hrd.time_offset_length);
					}
				}

				inspect_printf(dump, " time_code_%d=\"%02d:%02d:%02d:%02d\"", i, hours, minutes, seconds, n_frames);
			}
		}
	}
}

static void dump_sei(FILE *dump, GF_BitStream *bs, AVCState *avc, HEVCState *hevc, VVCState *vvc)
{
	gf_bs_enable_emulation_byte_removal(bs, GF_TRUE);

	//skip nal header
	gf_bs_read_int(bs, avc ? 8 : 16);

	while (gf_bs_available(bs) ) {
		u32 sei_type = 0;
		u32 sei_size = 0;
		u32 sei_pos;
		while (gf_bs_peek_bits(bs, 8, 0) == 0xFF) {
			sei_type += 255;
			gf_bs_read_int(bs, 8);
		}
		sei_type += gf_bs_read_int(bs, 8);
		while (gf_bs_peek_bits(bs, 8, 0) == 0xFF) {
			sei_size += 255;
			gf_bs_read_int(bs, 8);
		}
		sei_size += gf_bs_read_int(bs, 8);
		sei_pos = (u32) gf_bs_get_position(bs);

		inspect_printf(dump, "    <SEIMessage ptype=\"%u\" psize=\"%u\" type=\"%s\"", sei_type, sei_size, get_sei_name(sei_type) );
		if (sei_type == 144) {
			dump_clli(dump, bs);
		} else if (sei_type == 137) {
			dump_mdcv(dump, bs, GF_TRUE);
		} else if (sei_type == 1 && avc) {
			dump_avc_pic_timing(dump, bs, avc);
		} else if (sei_type == 136) {
			dump_time_code_hevc(dump, bs);
		} else if (sei_type == 4) {
			dump_t35(dump, bs, sei_size);
		} else if (sei_type == 5) {
			dump_unregistered_sei(dump, bs, sei_size);
		} else if (avc && (sei_type==6)) {
			u32 frame_cnt = gf_bs_read_ue(bs);
			inspect_printf(dump, " frame_count=\"%u\"", frame_cnt);
		}
		inspect_printf(dump, "/>\n");

		//don't trust sei parsers, force jumping to next - use byte-per-byte read for EPB removal
		gf_bs_seek(bs, sei_pos);
		while (sei_size && gf_bs_available(bs)) {
			gf_bs_read_u8(bs);
			sei_size--;
		}

		if (gf_bs_peek_bits(bs, 8, 0) == 0x80) {
			break;
		}
	}
}

static void inspect_dump_crypt(FILE *dump, u32 nal_size, u8 *sai_buffer, u32 sai_buffer_size, u32 nalu_offset)
{
	GF_BitStream *bs = gf_bs_new(sai_buffer, sai_buffer_size, GF_BITSTREAM_READ);
	u8 iv_size = 0;
	Bool multi_key=GF_FALSE;
	u32 nb_iv_init=0;
	u32 i, nb_subs;

restart:
	if (iv_size && !multi_key) gf_bs_skip_bytes(bs, iv_size);
	nb_subs = gf_bs_read_u16(bs);
	if (!multi_key && (gf_bs_available(bs) == nb_subs*6)) {

	} else if (!multi_key && (iv_size<16)) {
		gf_bs_seek(bs, 0);
		iv_size += 8;
		goto restart;
	} else {
		//multikey
		if (!multi_key) {
			iv_size=0;
			multi_key = GF_TRUE;
			gf_bs_seek(bs, 0);
			nb_subs = gf_bs_read_u16(bs);
		}
		nb_iv_init = nb_subs;
		gf_bs_skip_bytes(bs, nb_iv_init*(2+iv_size));
		nb_subs = gf_bs_read_u32(bs);
		if (gf_bs_available(bs) == nb_subs*8) {
		} else if (iv_size<16) {
			gf_bs_seek(bs, 0);
			iv_size += 8;
			goto restart;
		} else {
			inspect_printf(dump, "encrypted=\"unsupported multikey\" ");
			gf_bs_del(bs);
			return;
		}
	}

	u32 offset=0;
	for (i=0; i<nb_subs; i++) {
		if (multi_key) gf_bs_read_u16(bs);
		u32 clear = gf_bs_read_u16(bs);
		u32 crypt = gf_bs_read_u32(bs);
		if ((nalu_offset>=offset) && (nalu_offset+nal_size <= offset+clear)) {
			inspect_printf(dump, "encrypted=\"no\" ");
			gf_bs_del(bs);
			return;
		}
		if ((nalu_offset>=offset) && (nalu_offset+nal_size <= offset+clear+crypt)) {
			inspect_printf(dump, "encrypted=\"yes\" ");
			gf_bs_del(bs);
			return;
		}
		offset += clear+crypt;
		if (nalu_offset+nal_size<offset) break;
	}

	inspect_printf(dump, "encrypted=\"no\" ");
	gf_bs_del(bs);
}



static void gf_inspect_dump_nalu_internal(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, VVCState *vvc, u32 nalh_size, Bool dump_crc, Bool is_encrypted, GF_InspectSampleAnalyzeMode full_bs_dump, PidCtx *pctx, u8 *sai_buffer, u32 sai_buffer_size, u32 nalu_offset)
{
	s32 res = 0;
	u8 type, nal_ref_idc;
	u8 dependency_id, quality_id, temporal_id;
	u8 track_ref_index;
	s8 sample_offset;
	u32 data_offset, data_size;
	Bool full_parse = GF_FALSE;
	s32 idx;
	InspectLogCbk lcbk;
	GF_BitStream *bs = NULL;
	const char *nal_name;

	if (full_bs_dump<INSPECT_ANALYZE_BS) {
		if (!gf_sys_is_test_mode() && full_bs_dump)
			full_parse = GF_TRUE;
		full_bs_dump = 0;
	} else {
		lcbk.dump = dump;
		lcbk.dump_bits = full_bs_dump==INSPECT_ANALYZE_BS_BITS ? GF_TRUE : GF_FALSE;
	}

	if (!ptr_size) {
		inspect_printf(dump, "error=\"invalid nal size 0\"/>\n");
		return;
	}

	if (dump_crc) inspect_printf(dump, "crc=\"%u\" ", gf_crc_32(ptr, ptr_size) );

	if (sai_buffer && sai_buffer_size) {
		inspect_dump_crypt(dump, ptr_size+nalh_size, sai_buffer, sai_buffer_size, nalu_offset);
	}

	if (hevc) {
		if (ptr_size<=1) {
			inspect_printf(dump, "error=\"invalid nal size 1\"/>\n");
			return;
		}

		if (full_bs_dump) {
			if (pctx) {
				if (!pctx->bs)
					pctx->bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
				else
					gf_bs_reassign_buffer(pctx->bs, ptr, ptr_size);
				bs = pctx->bs;
			} else {
				bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
			}
			gf_bs_set_logger(bs, regular_bs_log, &lcbk);
			hevc->full_slice_header_parse = GF_TRUE;
			res = gf_hevc_parse_nalu_bs(bs, hevc, &type, &temporal_id, &quality_id);
		} else {
			bs = NULL;
			if (full_parse)
				hevc->full_slice_header_parse = GF_TRUE;
			res = gf_hevc_parse_nalu(ptr, ptr_size, hevc, &type, &temporal_id, &quality_id);
			inspect_printf(dump, "code=\"%d\"", type);
		}

		if (res==-1) {
			inspect_printf(dump, " status=\"error parsing\"", type);
		}
		inspect_printf(dump, " type=\"", type);
		nal_name = get_hevc_nal_name(type);
		if (nal_name)
			gf_fputs(nal_name, dump);
		else
			inspect_printf(dump, "UNKNOWN (parsing return %d)", res);

		//specific dump
		switch (type) {
		case GF_HEVC_NALU_VID_PARAM:
			if (full_bs_dump) break;
			idx = gf_hevc_read_vps(ptr, ptr_size, hevc);
			if (idx<0) inspect_printf(dump, "\" vps_id=\"PARSING FAILURE");
			else inspect_printf(dump, "\" vps_id=\"%d", idx);
			break;
		case GF_HEVC_NALU_SEQ_PARAM:
			if (full_bs_dump) break;
			idx = gf_hevc_read_sps(ptr, ptr_size, hevc);
			if (idx<0) {
				inspect_printf(dump, "\" sps_id=\"PARSING FAILURE");
				break;
			}
			{
			HEVC_SPS *sps= &hevc->sps[idx];
			inspect_printf(dump, "\" sps_id=\"%d", idx);

			inspect_printf(dump, "\" aspect_ratio_info_present_flag=\"%d", sps->aspect_ratio_info_present_flag);
			inspect_printf(dump, "\" bit_depth_chroma=\"%d", sps->bit_depth_chroma);
			inspect_printf(dump, "\" bit_depth_luma=\"%d", sps->bit_depth_luma);
			inspect_printf(dump, "\" chroma_format_idc=\"%d", sps->chroma_format_idc);
			inspect_printf(dump, "\" colour_description_present_flag=\"%d", sps->colour_description_present_flag);
			inspect_printf(dump, "\" colour_primaries=\"%d", sps->colour_primaries);
			inspect_printf(dump, "\" cw_flag=\"%d", sps->cw_flag);
			if (sps->cw_flag) {
				inspect_printf(dump, "\" cw_bottom=\"%d", sps->cw_bottom);
				inspect_printf(dump, "\" cw_top=\"%d", sps->cw_top);
				inspect_printf(dump, "\" cw_left=\"%d", sps->cw_left);
				inspect_printf(dump, "\" cw_right=\"%d", sps->cw_right);
			}
			inspect_printf(dump, "\" height=\"%d", sps->height);
			inspect_printf(dump, "\" width=\"%d", sps->width);
			inspect_printf(dump, "\" log2_max_pic_order_cnt_lsb=\"%d", sps->log2_max_pic_order_cnt_lsb);
			inspect_printf(dump, "\" long_term_ref_pics_present_flag=\"%d", sps->long_term_ref_pics_present_flag);
			inspect_printf(dump, "\" matrix_coeffs=\"%d", sps->matrix_coeffs);
			inspect_printf(dump, "\" max_CU_depth=\"%d", sps->max_CU_depth);
			inspect_printf(dump, "\" max_CU_width=\"%d", sps->max_CU_width);
			inspect_printf(dump, "\" max_CU_height=\"%d", sps->max_CU_height);
			inspect_printf(dump, "\" num_long_term_ref_pic_sps=\"%d", sps->num_long_term_ref_pic_sps);
			inspect_printf(dump, "\" num_short_term_ref_pic_sets=\"%d", sps->num_short_term_ref_pic_sets);
			inspect_printf(dump, "\" has_timing_info=\"%d", sps->has_timing_info);
			if (sps->has_timing_info) {
				inspect_printf(dump, "\" time_scale=\"%d", sps->time_scale);
				inspect_printf(dump, "\" num_ticks_poc_diff_one_minus1=\"%d", sps->num_ticks_poc_diff_one_minus1);
				inspect_printf(dump, "\" num_units_in_tick=\"%d", sps->num_units_in_tick);
				inspect_printf(dump, "\" poc_proportional_to_timing_flag=\"%d", sps->poc_proportional_to_timing_flag);
			}
			inspect_printf(dump, "\" rep_format_idx=\"%d", sps->rep_format_idx);
			inspect_printf(dump, "\" sample_adaptive_offset_enabled_flag=\"%d", sps->sample_adaptive_offset_enabled_flag);
			inspect_printf(dump, "\" sar_idc=\"%d", sps->sar_idc);
			inspect_printf(dump, "\" separate_colour_plane_flag=\"%d", sps->separate_colour_plane_flag);
			inspect_printf(dump, "\" temporal_mvp_enable_flag=\"%d", sps->temporal_mvp_enable_flag);
			inspect_printf(dump, "\" transfer_characteristic=\"%d", sps->transfer_characteristic);
			inspect_printf(dump, "\" video_full_range_flag=\"%d", sps->video_full_range_flag);
			inspect_printf(dump, "\" sps_ext_or_max_sub_layers_minus1=\"%d", sps->sps_ext_or_max_sub_layers_minus1);
			inspect_printf(dump, "\" max_sub_layers_minus1=\"%d", sps->max_sub_layers_minus1);
			inspect_printf(dump, "\" update_rep_format_flag=\"%d", sps->update_rep_format_flag);
			inspect_printf(dump, "\" sub_layer_ordering_info_present_flag=\"%d", sps->sub_layer_ordering_info_present_flag);
			inspect_printf(dump, "\" scaling_list_enable_flag=\"%d", sps->scaling_list_enable_flag);
			inspect_printf(dump, "\" infer_scaling_list_flag=\"%d", sps->infer_scaling_list_flag);
			inspect_printf(dump, "\" scaling_list_ref_layer_id=\"%d", sps->scaling_list_ref_layer_id);
			inspect_printf(dump, "\" scaling_list_data_present_flag=\"%d", sps->scaling_list_data_present_flag);
			inspect_printf(dump, "\" asymmetric_motion_partitions_enabled_flag=\"%d", sps->asymmetric_motion_partitions_enabled_flag);
			inspect_printf(dump, "\" pcm_enabled_flag=\"%d", sps->pcm_enabled_flag);
			inspect_printf(dump, "\" strong_intra_smoothing_enable_flag=\"%d", sps->strong_intra_smoothing_enable_flag);
			inspect_printf(dump, "\" vui_parameters_present_flag=\"%d", sps->vui_parameters_present_flag);
			inspect_printf(dump, "\" log2_diff_max_min_luma_coding_block_size=\"%d", sps->log2_diff_max_min_luma_coding_block_size);
			inspect_printf(dump, "\" log2_min_transform_block_size=\"%d", sps->log2_min_transform_block_size);
			inspect_printf(dump, "\" log2_min_luma_coding_block_size=\"%d", sps->log2_min_luma_coding_block_size);
			inspect_printf(dump, "\" log2_max_transform_block_size=\"%d", sps->log2_max_transform_block_size);
			inspect_printf(dump, "\" max_transform_hierarchy_depth_inter=\"%d", sps->max_transform_hierarchy_depth_inter);
			inspect_printf(dump, "\" max_transform_hierarchy_depth_intra=\"%d", sps->max_transform_hierarchy_depth_intra);
			inspect_printf(dump, "\" pcm_sample_bit_depth_luma_minus1=\"%d", sps->pcm_sample_bit_depth_luma_minus1);
			inspect_printf(dump, "\" pcm_sample_bit_depth_chroma_minus1=\"%d", sps->pcm_sample_bit_depth_chroma_minus1);
			inspect_printf(dump, "\" pcm_loop_filter_disable_flag=\"%d", sps->pcm_loop_filter_disable_flag);
			inspect_printf(dump, "\" log2_min_pcm_luma_coding_block_size_minus3=\"%d", sps->log2_min_pcm_luma_coding_block_size_minus3);
			inspect_printf(dump, "\" log2_diff_max_min_pcm_luma_coding_block_size=\"%d", sps->log2_diff_max_min_pcm_luma_coding_block_size);
			inspect_printf(dump, "\" overscan_info_present=\"%d", sps->overscan_info_present);
			inspect_printf(dump, "\" overscan_appropriate=\"%d", sps->overscan_appropriate);
			inspect_printf(dump, "\" video_signal_type_present_flag=\"%d", sps->video_signal_type_present_flag);
			inspect_printf(dump, "\" video_format=\"%d", sps->video_format);
			inspect_printf(dump, "\" chroma_loc_info_present_flag=\"%d", sps->chroma_loc_info_present_flag);
			inspect_printf(dump, "\" chroma_sample_loc_type_top_field=\"%d", sps->chroma_sample_loc_type_top_field);
			inspect_printf(dump, "\" chroma_sample_loc_type_bottom_field=\"%d", sps->chroma_sample_loc_type_bottom_field);
			inspect_printf(dump, "\" neutral_chroma_indication_flag=\"%d", sps->neutral_chroma_indication_flag);
			inspect_printf(dump, "\" field_seq_flag=\"%d", sps->field_seq_flag);
			inspect_printf(dump, "\" frame_field_info_present_flag=\"%d", sps->frame_field_info_present_flag);
			inspect_printf(dump, "\" default_display_window_flag=\"%d", sps->default_display_window_flag);
			inspect_printf(dump, "\" left_offset=\"%d", sps->left_offset);
			inspect_printf(dump, "\" right_offset=\"%d", sps->right_offset);
			inspect_printf(dump, "\" top_offset=\"%d", sps->top_offset);
			inspect_printf(dump, "\" bottom_offset=\"%d", sps->bottom_offset);
			inspect_printf(dump, "\" hrd_parameters_present_flag=\"%d", sps->hrd_parameters_present_flag);
			}
			break;
		case GF_HEVC_NALU_PIC_PARAM:
			if (full_bs_dump) break;
			idx = gf_hevc_read_pps(ptr, ptr_size, hevc);
			if (idx<0) {
				inspect_printf(dump, "\" pps_id=\"PARSING FAILURE");
				break;
			}
			{
			HEVC_PPS *pps= &hevc->pps[idx];
			inspect_printf(dump, "\" pps_id=\"%d", idx);

			inspect_printf(dump, "\" cabac_init_present_flag=\"%d", pps->cabac_init_present_flag);
			inspect_printf(dump, "\" dependent_slice_segments_enabled_flag=\"%d", pps->dependent_slice_segments_enabled_flag);
			inspect_printf(dump, "\" entropy_coding_sync_enabled_flag=\"%d", pps->entropy_coding_sync_enabled_flag);
			inspect_printf(dump, "\" lists_modification_present_flag=\"%d", pps->lists_modification_present_flag);
			inspect_printf(dump, "\" loop_filter_across_slices_enabled_flag=\"%d", pps->loop_filter_across_slices_enabled_flag);
			inspect_printf(dump, "\" loop_filter_across_tiles_enabled_flag=\"%d", pps->loop_filter_across_tiles_enabled_flag);
			inspect_printf(dump, "\" num_extra_slice_header_bits=\"%d", pps->num_extra_slice_header_bits);
			inspect_printf(dump, "\" num_ref_idx_l0_default_active=\"%d", pps->num_ref_idx_l0_default_active);
			inspect_printf(dump, "\" num_ref_idx_l1_default_active=\"%d", pps->num_ref_idx_l1_default_active);
			inspect_printf(dump, "\" tiles_enabled_flag=\"%d", pps->tiles_enabled_flag);
			if (pps->tiles_enabled_flag) {
				inspect_printf(dump, "\" uniform_spacing_flag=\"%d", pps->uniform_spacing_flag);
				if (!pps->uniform_spacing_flag) {
					u32 k;
					inspect_printf(dump, "\" num_tile_columns=\"%d", pps->num_tile_columns);
					inspect_printf(dump, "\" num_tile_rows=\"%d", pps->num_tile_rows);
					inspect_printf(dump, "\" colomns_width=\"");
					for (k=0; k<pps->num_tile_columns-1; k++)
						inspect_printf(dump, "%d ", pps->column_width[k]);
					inspect_printf(dump, "\" rows_height=\"");
					for (k=0; k<pps->num_tile_rows-1; k++)
						inspect_printf(dump, "%d ", pps->row_height[k]);
				}
			}
			inspect_printf(dump, "\" output_flag_present_flag=\"%d", pps->output_flag_present_flag);
			inspect_printf(dump, "\" pic_init_qp_minus26=\"%d", pps->pic_init_qp_minus26);
			inspect_printf(dump, "\" slice_chroma_qp_offsets_present_flag=\"%d", pps->slice_chroma_qp_offsets_present_flag);
			inspect_printf(dump, "\" slice_segment_header_extension_present_flag=\"%d", pps->slice_segment_header_extension_present_flag);
			inspect_printf(dump, "\" weighted_pred_flag=\"%d", pps->weighted_pred_flag);
			inspect_printf(dump, "\" weighted_bipred_flag=\"%d", pps->weighted_bipred_flag);

			inspect_printf(dump, "\" sign_data_hiding_flag=\"%d", pps->sign_data_hiding_flag);
			inspect_printf(dump, "\" constrained_intra_pred_flag=\"%d", pps->constrained_intra_pred_flag);
			inspect_printf(dump, "\" transform_skip_enabled_flag=\"%d", pps->transform_skip_enabled_flag);
			inspect_printf(dump, "\" cu_qp_delta_enabled_flag=\"%d", pps->cu_qp_delta_enabled_flag);
			if (pps->cu_qp_delta_enabled_flag)
				inspect_printf(dump, "\" diff_cu_qp_delta_depth=\"%d", pps->diff_cu_qp_delta_depth);
			inspect_printf(dump, "\" transquant_bypass_enable_flag=\"%d", pps->transquant_bypass_enable_flag);
			inspect_printf(dump, "\" pic_cb_qp_offset=\"%d", pps->pic_cb_qp_offset);
			inspect_printf(dump, "\" pic_cr_qp_offset=\"%d", pps->pic_cr_qp_offset);

			inspect_printf(dump, "\" deblocking_filter_control_present_flag=\"%d", pps->deblocking_filter_control_present_flag);
			if (pps->deblocking_filter_control_present_flag) {
				inspect_printf(dump, "\" deblocking_filter_override_enabled_flag=\"%d", pps->deblocking_filter_override_enabled_flag);
				inspect_printf(dump, "\" pic_disable_deblocking_filter_flag=\"%d", pps->pic_disable_deblocking_filter_flag);
				inspect_printf(dump, "\" beta_offset_div2=\"%d", pps->beta_offset_div2);
				inspect_printf(dump, "\" tc_offset_div2=\"%d", pps->tc_offset_div2);
			}
			inspect_printf(dump, "\" pic_scaling_list_data_present_flag=\"%d", pps->pic_scaling_list_data_present_flag);
			inspect_printf(dump, "\" log2_parallel_merge_level_minus2=\"%d", pps->log2_parallel_merge_level_minus2);
			}
			break;
		case GF_HEVC_NALU_ACCESS_UNIT:
			if (ptr_size<3) {
				inspect_printf(dump, "\" status=\"CORRUPTED NAL");
				break;
			}
			inspect_printf(dump, "\" primary_pic_type=\"%d", ptr[2] >> 5);
			break;
		//extractor
		case GF_HEVC_NALU_FF_EXTRACTOR:
		{
			u32 remain = ptr_size-2;
			char *s = ptr+2;

			gf_fputs(" ", dump);

			while (remain) {
				u32 mode = s[0];
				remain -= 1;
				s += 1;
				if (!remain) {
					inspect_printf(dump, "error=\"invalid inband data extractor size, no bytes remaining\"/>\n");
					return;
				} else if (mode) {
					u32 len = s[0];
					if (len+1>remain) {
						inspect_printf(dump, "error=\"invalid inband data extractor size: %d vs %d remaining\"/>\n", len, remain);
						return;
					}
					remain -= len+1;
					s += len+1;
					inspect_printf(dump, "\" inband_size=\"%d", len);
				} else {
					if (remain < 2 + 2*nalh_size) {
						inspect_printf(dump, "error=\"invalid ref data extractor size: %d vs %d remaining\"/>\n", 2 + 2*nalh_size, remain);
						return;
					}
					track_ref_index = (u8) s[0];
					sample_offset = (s8) s[1];
					data_offset = inspect_get_nal_size(&s[2], nalh_size);
					data_size = inspect_get_nal_size(&s[2+nalh_size], nalh_size);
					inspect_printf(dump, "\" track_ref_index=\"%d\" sample_offset=\"%d\" data_offset=\"%d\" data_size=\"%d", track_ref_index, sample_offset, data_offset, data_size);

					remain -= 2 + 2*nalh_size;
					s += 2 + 2*nalh_size;
				}
			}
		}
			break;
		default:
			break;
		}
		gf_fputs("\"", dump);

		if (!full_bs_dump && (type < GF_HEVC_NALU_VID_PARAM)) {
			inspect_printf(dump, " slice=\"%s\" poc=\"%d\"", (hevc->s_info.slice_type==GF_HEVC_SLICE_TYPE_I) ? "I" : (hevc->s_info.slice_type==GF_HEVC_SLICE_TYPE_P) ? "P" : (hevc->s_info.slice_type==GF_HEVC_SLICE_TYPE_B) ? "B" : "Unknown", hevc->s_info.poc);
			inspect_printf(dump, " first_slice_in_pic=\"%d\"", hevc->s_info.first_slice_segment_in_pic_flag);
			inspect_printf(dump, " dependent_slice_segment=\"%d\"", hevc->s_info.dependent_slice_segment_flag);

			if (!gf_sys_is_test_mode()) {
				inspect_printf(dump, " redundant_pic_cnt=\"%d\"", hevc->s_info.redundant_pic_cnt);
				inspect_printf(dump, " slice_qp_delta=\"%d\"", hevc->s_info.slice_qp_delta);
				inspect_printf(dump, " slice_segment_address=\"%d\"", hevc->s_info.slice_segment_address);
				inspect_printf(dump, " slice_type=\"%d\"", hevc->s_info.slice_type);
			}
		}
		if (!gf_sys_is_test_mode() && (type < GF_HEVC_NALU_VID_PARAM) && hevc->s_info.nb_reference_pocs) {
			u32 i;
			inspect_printf(dump, " POC=\"%d\" referencePOCs=\"", hevc->s_info.poc);
			for (i=0; i<hevc->s_info.nb_reference_pocs; i++) {
				if (i) inspect_printf(dump, " ");
				inspect_printf(dump, "%d", hevc->s_info.reference_pocs[i]);
			}
			inspect_printf(dump, "\"");
		}
		if (!full_bs_dump)
			inspect_printf(dump, " layer_id=\"%d\" temporal_id=\"%d\"", quality_id, temporal_id);

		if (bs) {
			if (!pctx)
				gf_bs_del(bs);
			else
				gf_bs_set_logger(bs, NULL, NULL);
		}

		if ((type == GF_HEVC_NALU_SEI_PREFIX) || (type == GF_HEVC_NALU_SEI_SUFFIX)) {
			inspect_printf(dump, ">\n");
			if (pctx) {
				if (!pctx->bs)
					pctx->bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
				else
					gf_bs_reassign_buffer(pctx->bs, ptr, ptr_size);
				bs = pctx->bs;
			} else {
				bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
			}
			dump_sei(dump, bs, avc, hevc, vvc);
			if (!pctx) gf_bs_del(bs);
			inspect_printf(dump, "   </NALU>\n");
		} else {
			inspect_printf(dump, "/>\n");
		}
		return;
	}

	if (vvc) {
		u8 lid, tid;

		if (ptr_size<=1) {
			inspect_printf(dump, "error=\"invalid nal size 1\"/>\n");
			return;
		}

		if (full_bs_dump) {
			vvc->parse_mode = 2;
			if (pctx) {
				if (!pctx->bs)
					pctx->bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
				else
					gf_bs_reassign_buffer(pctx->bs, ptr, ptr_size);
				bs = pctx->bs;
			} else {
				bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
			}
			gf_bs_set_logger(bs, regular_bs_log, &lcbk);
			res = gf_vvc_parse_nalu_bs(bs, vvc, &type, &lid, &tid);
		} else {
			vvc->parse_mode = 0;
			bs = NULL;
			u32 forb_zero = (ptr[0] & 0x80) ? 1 : 0;
			u32 res_zero = (ptr[0] & 0x40) ? 1 : 0;
			lid = (ptr[0] & 0x3F);
			tid = (ptr[1] & 0x7);
			if (forb_zero || res_zero || !tid) {
				inspect_printf(dump, "error=\"invalid header (forb %d res_zero %d tid %d)\"/>\n", forb_zero, res_zero, tid);
				return;
			}
			tid -= 1;
			type = ptr[1]>>3;

			res = gf_vvc_parse_nalu(ptr, ptr_size, vvc, &type, &lid, &tid);
			inspect_printf(dump, "code=\"%d\" temporalid=\"%d\" layerid=\"%d\"", type, tid, lid);
		}
		if (res==-1) {
			inspect_printf(dump, " status=\"error parsing\"", type);
		}

		inspect_printf(dump, " type=\"");
		nal_name = get_vvc_nal_name(type);
		if (nal_name)
			inspect_printf(dump, nal_name);
		else {
			inspect_printf(dump, "unknown");
			res = -2;
		}
		//specific dump
		switch (type) {
		case GF_VVC_NALU_VID_PARAM:
			if ((res>=0) && !full_bs_dump) {
				u32 j;
				VVC_VPS *vps = &vvc->vps[vvc->last_parsed_vps_id];
				inspect_printf(dump, "\" id=\"%d\" num_ptl=\"%d\" max_layers=\"%d\" max_sublayers=\"%d", vps->id, vps->num_ptl, vps->max_layers, vps->max_sub_layers);
				if (vps->max_layers>1) {
					inspect_printf(dump, "\" max_layer_id=\"%d\" all_layers_independent=\"%d\" each_layer_is_ols=\"%d", vps->max_layer_id, vps->all_layers_independent, vps->each_layer_is_ols);
				}
				for (j=0; j<vps->num_ptl; j++) {
					VVC_ProfileTierLevel *ptl = &vps->ptl[j];
					inspect_printf(dump, "\" general_level_idc=\"%d\" frame_only_constraint=\"%d\" multilayer_enabled=\"%d\" max_tid=\"%d", ptl->general_level_idc, ptl->frame_only_constraint, ptl->multilayer_enabled, ptl->ptl_max_tid);

					if (ptl->pt_present) {
						inspect_printf(dump, "\" general_profile_idc=\"%d\" general_tier_flag=\"%d\" gci_present=\"%d", ptl->general_profile_idc, ptl->general_tier_flag, ptl->gci_present);
					}
				}
			}
			res = -2;
			break;
		case GF_VVC_NALU_SEQ_PARAM:
			if ((res>=0) && !full_bs_dump) {
				VVC_SPS *sps = &vvc->sps[vvc->last_parsed_sps_id];

				inspect_printf(dump, "\" id=\"%d\" vps_id=\"%d\" max_sublayers=\"%d\" chroma_idc=\"%d\" bit_depth=\"%d\" CTBsizeY=\"%d\" gdr_enabled=\"%d\" ref_pic_sampling=\"%d\" subpic_info_present=\"%d\" poc_msb_cycle_flag=\"%d", sps->id, sps->vps_id, sps->max_sublayers, sps->chroma_format_idc, sps->bitdepth, 1<<sps->log2_ctu_size, sps->gdr_enabled, sps->ref_pic_resampling, sps->subpic_info_present, sps->poc_msb_cycle_flag);
				if (sps->ref_pic_resampling) {
					inspect_printf(dump, "\" res_change_in_clvs=\"%d", sps->res_change_in_clvs);
				}
				inspect_printf(dump, "\" width=\"%d\" height=\"%d", sps->width, sps->height);
				if (!sps->vps_id) {
					VVC_ProfileTierLevel *ptl = &vvc->vps[0].ptl[0];
					inspect_printf(dump, "\" general_level_idc=\"%d\" frame_only_constraint=\"%d\" multilayer_enabled=\"%d\" max_tid=\"%d", ptl->general_level_idc, ptl->frame_only_constraint, ptl->multilayer_enabled, ptl->ptl_max_tid);

					if (ptl->pt_present) {
						inspect_printf(dump, "\" general_profile_idc=\"%d\" general_tier_flag=\"%d\" gci_present=\"%d", ptl->general_profile_idc, ptl->general_tier_flag, ptl->gci_present);
					}
				}
				inspect_printf(dump, "\" conf_window=\"%d", sps->conf_window);
				if (sps->conf_window) {
					inspect_printf(dump, "\" cw_left=\"%d\" cw_right=\"%d\" cw_top=\"%d\" cw_bottom=\"%d", sps->cw_left, sps->cw_right, sps->cw_top, sps->cw_bottom);
				}
			}
			res=-2;
			break;
		case GF_VVC_NALU_PIC_PARAM:
			if ((res>=0) && !full_bs_dump) {
				VVC_PPS *pps = &vvc->pps[vvc->last_parsed_pps_id];
				inspect_printf(dump, "\" id=\"%d\" sps_id=\"%d\" width=\"%d\" height=\"%d\" mixed_nal_types=\"%d\" conf_window=\"%d", pps->id, pps->sps_id, pps->width, pps->height, pps->mixed_nal_types, pps->conf_window);

				if (pps->conf_window) {
					inspect_printf(dump, "\" cw_left=\"%d\" cw_right=\"%d\" cw_top=\"%d\" cw_bottom=\"%d", pps->cw_left, pps->cw_right, pps->cw_top, pps->cw_bottom);
				}
				inspect_printf(dump, "\" output_flag_present_flag=\"%d\" no_pic_partition_flag=\"%d\" subpic_id_mapping_present_flag=\"%d", pps->output_flag_present_flag, pps->no_pic_partition_flag, pps->subpic_id_mapping_present_flag);
			}
			res=-2;
			break;
		default:
			break;
		}
		inspect_printf(dump, "\"");

		//picture header or slice
		if ((type!=GF_VVC_NALU_PIC_HEADER) && (type>GF_VVC_NALU_SLICE_GDR))
			res = -2;
		if ((res>=0) && !full_bs_dump) {
			if (type!=GF_VVC_NALU_PIC_HEADER)
				inspect_printf(dump, " picture_header_in_slice_header_flag=\"%d\"", vvc->s_info.picture_header_in_slice_header_flag);

			if ((type==GF_VVC_NALU_PIC_HEADER) || vvc->s_info.picture_header_in_slice_header_flag) {
				inspect_printf(dump, " pps_id=\"%d\" poc=\"%d\" irap_or_gdr_pic=\"%d\" non_ref_pic=\"%d\" inter_slice_allowed_flag=\"%d\" poc_lsb=\"%d\"", vvc->s_info.pps->id, vvc->s_info.poc, vvc->s_info.irap_or_gdr_pic, vvc->s_info.non_ref_pic, vvc->s_info.inter_slice_allowed_flag, vvc->s_info.poc_lsb);
				if (vvc->s_info.irap_or_gdr_pic)
					inspect_printf(dump, " gdr_pic=\"%d\" gdr_recovery_count=\"%d\"", vvc->s_info.gdr_pic, vvc->s_info.gdr_recovery_count);
				if (vvc->s_info.inter_slice_allowed_flag)
					inspect_printf(dump, " intra_slice_allowed_flag=\"%d\"", vvc->s_info.intra_slice_allowed_flag);
				if (vvc->s_info.sps->poc_msb_cycle_flag && vvc->s_info.poc_msb_cycle_present_flag)
					inspect_printf(dump, " poc_msb_cycle=\"%d\"", vvc->s_info.poc_msb_cycle);
			}
			if (type!=GF_VVC_NALU_PIC_HEADER)
				inspect_printf(dump, " slice_type=\"%d\"", vvc->s_info.slice_type);
		}

		if (bs) {
			if (!pctx)
				gf_bs_del(bs);
			else
				gf_bs_set_logger(bs, NULL, NULL);
		}

		if (!gf_sys_is_test_mode() && (type <= GF_VVC_NALU_SLICE_GDR) && vvc->s_info.nb_reference_pocs) {
			u32 i;
			inspect_printf(dump, " POC=\"%d\" referencePOCs=\"", vvc->s_info.poc);
			for (i=0; i<vvc->s_info.nb_reference_pocs; i++) {
				if (i) inspect_printf(dump, " ");
				inspect_printf(dump, "%d", vvc->s_info.reference_pocs[i]);
			}
			inspect_printf(dump, "\"");
		}

		if ((type == GF_VVC_NALU_SEI_PREFIX) || (type == GF_VVC_NALU_SEI_SUFFIX)) {
			inspect_printf(dump, ">\n");
			if (pctx) {
				if (!pctx->bs)
					pctx->bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
				else
					gf_bs_reassign_buffer(pctx->bs, ptr, ptr_size);
				bs = pctx->bs;
			} else {
				bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
			}
			dump_sei(dump, bs, avc, hevc, vvc);
			if (!pctx) gf_bs_del(bs);
			inspect_printf(dump, "   </NALU>\n");
		} else {
			inspect_printf(dump, "/>\n");
		}
		return;
	}

	//avc
	if (!ptr_size) {
		inspect_printf(dump, "error=\"invalid nal size 1\"/>\n");
		return;
	}
	type = ptr[0] & 0x1F;
	nal_ref_idc = ptr[0] & 0x60;
	nal_ref_idc>>=5;
	if (! full_bs_dump)
		inspect_printf(dump, "code=\"%d\" ", type);

	inspect_printf(dump, "type=\"");
	res = -2;
	nal_name = get_avc_nal_name(type);
	if (type == GF_AVC_NALU_SVC_SLICE) nal_name = is_svc ? "SVCSlice" : "CodedSliceExtension";
	if (nal_name) {
		gf_fputs(nal_name, dump);
	} else {
		gf_fputs("unknown", dump);
	}

	if (pctx) {
		if (!pctx->bs)
			pctx->bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
		else
			gf_bs_reassign_buffer(pctx->bs, ptr, ptr_size);
		bs = pctx->bs;
	} else {
		bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ);
	}

	if (full_bs_dump)
		gf_bs_set_logger(bs, shifted_bs_log, &lcbk);

	//specific dump
	switch (type) {
	case GF_AVC_NALU_NON_IDR_SLICE:
	case GF_AVC_NALU_DP_A_SLICE:
	case GF_AVC_NALU_DP_B_SLICE:
	case GF_AVC_NALU_DP_C_SLICE:
	case GF_AVC_NALU_IDR_SLICE:
		if (is_encrypted) break;
		res = gf_avc_parse_nalu(bs, avc);
		break;
	case GF_AVC_NALU_SEQ_PARAM:
		if (is_encrypted) break;
		idx = gf_avc_read_sps_bs(bs, avc, 0, NULL);
		if (idx<0) {
			inspect_printf(dump, "\" sps_id=\"PARSING FAILURE");
			break;
		}
		if (full_bs_dump) break;
		inspect_printf(dump, "\" sps_id=\"%d", idx);
		inspect_printf(dump, "\" frame_mbs_only_flag=\"%d", avc->sps->frame_mbs_only_flag);
		inspect_printf(dump, "\" mb_adaptive_frame_field_flag=\"%d", avc->sps->mb_adaptive_frame_field_flag);
		inspect_printf(dump, "\" vui_parameters_present_flag=\"%d", avc->sps->vui_parameters_present_flag);
		inspect_printf(dump, "\" max_num_ref_frames=\"%d", avc->sps->max_num_ref_frames);
		inspect_printf(dump, "\" gaps_in_frame_num_value_allowed_flag=\"%d", avc->sps->gaps_in_frame_num_value_allowed_flag);
		inspect_printf(dump, "\" chroma_format_idc=\"%d", avc->sps->chroma_format);
		inspect_printf(dump, "\" bit_depth_luma_minus8=\"%d", avc->sps->luma_bit_depth_m8);
		inspect_printf(dump, "\" bit_depth_chroma_minus8=\"%d", avc->sps->chroma_bit_depth_m8);
		inspect_printf(dump, "\" width=\"%d", avc->sps->width);
		inspect_printf(dump, "\" height=\"%d", avc->sps->height);
		inspect_printf(dump, "\" crop_top=\"%d", avc->sps->crop.top);
		inspect_printf(dump, "\" crop_left=\"%d", avc->sps->crop.left);
		inspect_printf(dump, "\" crop_bottom=\"%d", avc->sps->crop.bottom);
		inspect_printf(dump, "\" crop_right=\"%d", avc->sps->crop.right);
		if (avc->sps->vui_parameters_present_flag) {
			inspect_printf(dump, "\" vui_video_full_range_flag=\"%d", avc->sps->vui.video_full_range_flag);
			inspect_printf(dump, "\" vui_video_signal_type_present_flag=\"%d", avc->sps->vui.video_signal_type_present_flag);
			inspect_printf(dump, "\" vui_aspect_ratio_info_present_flag=\"%d", avc->sps->vui.aspect_ratio_info_present_flag);
			inspect_printf(dump, "\" vui_aspect_ratio_num=\"%d", avc->sps->vui.par_num);
			inspect_printf(dump, "\" vui_aspect_ratio_den=\"%d", avc->sps->vui.par_den);
			inspect_printf(dump, "\" vui_overscan_info_present_flag=\"%d", avc->sps->vui.overscan_info_present_flag);
			inspect_printf(dump, "\" vui_colour_description_present_flag=\"%d", avc->sps->vui.colour_description_present_flag);
			inspect_printf(dump, "\" vui_colour_primaries=\"%d", avc->sps->vui.colour_primaries);
			inspect_printf(dump, "\" vui_transfer_characteristics=\"%d", avc->sps->vui.transfer_characteristics);
			inspect_printf(dump, "\" vui_matrix_coefficients=\"%d", avc->sps->vui.matrix_coefficients);
			inspect_printf(dump, "\" vui_low_delay_hrd_flag=\"%d", avc->sps->vui.low_delay_hrd_flag);
		}
		inspect_printf(dump, "\" log2_max_poc_lsb=\"%d", avc->sps->log2_max_poc_lsb);
		inspect_printf(dump, "\" log2_max_frame_num=\"%d", avc->sps->log2_max_frame_num);
		inspect_printf(dump, "\" delta_pic_order_always_zero_flag=\"%d", avc->sps->delta_pic_order_always_zero_flag);
		inspect_printf(dump, "\" offset_for_non_ref_pic=\"%d", avc->sps->offset_for_non_ref_pic);

		break;
	case GF_AVC_NALU_PIC_PARAM:
		if (is_encrypted) break;
		idx = gf_avc_read_pps_bs(bs, avc);
		if (idx<0) {
			inspect_printf(dump, "\" pps_id=\"PARSING FAILURE");
			break;
		}
		if (full_bs_dump) break;
		inspect_printf(dump, "\" pps_id=\"%d\" sps_id=\"%d", idx, avc->pps[idx].sps_id);
		inspect_printf(dump, "\" entropy_coding_mode_flag=\"%d", avc->pps[idx].entropy_coding_mode_flag);
		inspect_printf(dump, "\" deblocking_filter_control_present_flag=\"%d", avc->pps[idx].deblocking_filter_control_present_flag);
		inspect_printf(dump, "\" mb_slice_group_map_type=\"%d", avc->pps[idx].mb_slice_group_map_type);
		inspect_printf(dump, "\" num_ref_idx_l0_default_active_minus1=\"%d", avc->pps[idx].num_ref_idx_l0_default_active_minus1);
		inspect_printf(dump, "\" num_ref_idx_l1_default_active_minus1=\"%d", avc->pps[idx].num_ref_idx_l1_default_active_minus1);
		inspect_printf(dump, "\" pic_order_present=\"%d", avc->pps[idx].pic_order_present);
		inspect_printf(dump, "\" pic_size_in_map_units_minus1=\"%d", avc->pps[idx].pic_size_in_map_units_minus1);
		inspect_printf(dump, "\" redundant_pic_cnt_present=\"%d", avc->pps[idx].redundant_pic_cnt_present);
		inspect_printf(dump, "\" slice_group_change_rate_minus1=\"%d", avc->pps[idx].slice_group_change_rate_minus1);
		inspect_printf(dump, "\" slice_group_count=\"%d", avc->pps[idx].slice_group_count);
		inspect_printf(dump, "\" weighted_pred_flag=\"%d", avc->pps[idx].weighted_pred_flag);
		inspect_printf(dump, "\" weighted_bipred_idc=\"%d", avc->pps[idx].weighted_bipred_idc);
		break;
	case GF_AVC_NALU_ACCESS_UNIT:
		if (is_encrypted) break;
		if (full_bs_dump) break;
		inspect_printf(dump, "\" primary_pic_type=\"%d", gf_bs_read_u8(bs) >> 5);
		break;
	case GF_AVC_NALU_SVC_SUBSEQ_PARAM:
		if (is_encrypted) break;
		idx = gf_avc_read_sps_bs(bs, avc, 1, NULL);
		if (idx<0) {
			inspect_printf(dump, "\" status=\"CORRUPTED NAL");
			break;
		}
		if (full_bs_dump) break;
		inspect_printf(dump, "\" sps_id=\"%d", idx - GF_SVC_SSPS_ID_SHIFT);
		break;
	case GF_AVC_NALU_SVC_SLICE:
		if (is_encrypted) break;
		gf_avc_parse_nalu(bs, avc);
		if (full_bs_dump) break;
		if (ptr_size<4) {
			inspect_printf(dump, "\" status=\"CORRUPTED NAL");
			break;
		}
		dependency_id = (ptr[2] & 0x70) >> 4;
		quality_id = (ptr[2] & 0x0F);
		temporal_id = (ptr[3] & 0xE0) >> 5;
		inspect_printf(dump, "\" dependency_id=\"%d\" quality_id=\"%d\" temporal_id=\"%d", dependency_id, quality_id, temporal_id);
		inspect_printf(dump, "\" poc=\"%d", avc->s_info.poc);
		break;
	case GF_AVC_NALU_SVC_PREFIX_NALU:
		if (is_encrypted) break;
		if (full_bs_dump) break;
		if (ptr_size<4) {
			inspect_printf(dump, "\" status=\"CORRUPTED NAL");
			break;
		}
		dependency_id = (ptr[2] & 0x70) >> 4;
		quality_id = (ptr[2] & 0x0F);
		temporal_id = (ptr[3] & 0xE0) >> 5;
		inspect_printf(dump, "\" dependency_id=\"%d\" quality_id=\"%d\" temporal_id=\"%d", dependency_id, quality_id, temporal_id);
		break;
	//extractor
	case GF_AVC_NALU_FF_EXTRACTOR:
		if (is_encrypted) break;
		if (ptr_size<7+nalh_size+nalh_size) {
			inspect_printf(dump, "\" status=\"CORRUPTED NAL");
			break;
		}
		track_ref_index = (u8) ptr[4];
		sample_offset = (s8) ptr[5];
		data_offset = inspect_get_nal_size(&ptr[6], nalh_size);
		data_size = inspect_get_nal_size(&ptr[6+nalh_size], nalh_size);
		inspect_printf(dump, "\" track_ref_index=\"%d\" sample_offset=\"%d\" data_offset=\"%d\" data_size=\"%d\"", track_ref_index, sample_offset, data_offset, data_size);
		break;
	default:
		break;
	}
	gf_fputs("\"", dump);

	if (!full_bs_dump) {
		if (nal_ref_idc) {
			inspect_printf(dump, " nal_ref_idc=\"%d\"", nal_ref_idc);
		}
		if (res>=0) {
			inspect_printf(dump, " poc=\"%d\" pps_id=\"%d\" field_pic_flag=\"%d\"", avc->s_info.poc, avc->s_info.pps->id, (int)avc->s_info.field_pic_flag);
		}
	}

	if (res == -1)
		inspect_printf(dump, " status=\"error decoding slice\"");

	if (!is_encrypted && (type == GF_AVC_NALU_SEI)) {
		inspect_printf(dump, ">\n");
		gf_bs_set_logger(bs, NULL, NULL);
		dump_sei(dump, bs, avc, hevc, vvc);
		inspect_printf(dump, "   </NALU>\n");
	} else {
		inspect_printf(dump, "/>\n");
	}

	if (bs) {
		if (!pctx)
			gf_bs_del(bs);
		else
			gf_bs_set_logger(bs, NULL, NULL);
	}
}

static u32 inspect_get_analyze_mode()
{
	u32 i, nb_args = gf_sys_get_argc();
	for (i=1;i<nb_args;i++) {
		const char *arg = gf_sys_get_arg(i);
		if (!strncmp(arg, "--analyze=", 10)) {
			if (!strcmp(arg+10, "on")) return INSPECT_ANALYZE_ON;
			else if (!strcmp(arg+10, "bs")) return INSPECT_ANALYZE_BS;
			else if (!strcmp(arg+10, "full")) return INSPECT_ANALYZE_BS_BITS;
			return INSPECT_ANALYZE_OFF;
		}
	}
	return INSPECT_ANALYZE_OFF;
}

GF_EXPORT
void gf_inspect_dump_nalu(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, VVCState *vvc, u32 nalh_size, Bool dump_crc, Bool is_encrypted, u8 *sai_buffer, u32 sai_buffer_size, u32 sample_offset)
{
	if (!dump) return;
	gf_inspect_dump_nalu_internal(dump, ptr, ptr_size, is_svc, hevc, avc, vvc, nalh_size, dump_crc, is_encrypted, inspect_get_analyze_mode(), NULL, sai_buffer, sai_buffer_size, sample_offset);
}

static void av1_dump_tile(FILE *dump, u32 idx, AV1Tile *tile)
{
	inspect_printf(dump, "     <Tile number=\"%d\" start=\"%d\" size=\"%d\"/>\n", idx, tile->obu_start_offset, tile->size);
}

static u64 gf_inspect_dump_obu_internal(FILE *dump, AV1State *av1, u8 *obu_ptr, u64 obu_ptr_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc, PidCtx *pctx, GF_InspectSampleAnalyzeMode full_dump, u8 *sai_buffer, u32 sai_buffer_size, u32 offset_in_sample)
{
	//when the pid context is not set, obu_size (which includes the header size in gpac) must be set
	if (!pctx && (obu_size <= 1))
		return obu_size;

	if (pctx) {
		InspectLogCbk lcbk;

		if (full_dump>=INSPECT_ANALYZE_BS) {
			lcbk.dump = dump;
			lcbk.dump_bits = (full_dump==INSPECT_ANALYZE_BS_BITS) ? GF_TRUE : GF_FALSE;
			gf_bs_set_logger(pctx->bs, regular_bs_log, &lcbk);

			inspect_printf(dump, "   <OBU");
		}
		gf_av1_parse_obu(pctx->bs, &obu_type, &obu_size, &hdr_size, pctx->av1_state);

		if (full_dump>=INSPECT_ANALYZE_BS) {
			gf_bs_set_logger(pctx->bs, NULL, NULL);
		} else {
			full_dump = 0;
		}
	}

	if (!full_dump) {
		inspect_printf(dump, "   <OBU");
	}


#define DUMP_OBU_INT(_v) inspect_printf(dump, #_v"=\"%d\" ", av1->_v);
#define DUMP_OBU_INT2(_n, _v) inspect_printf(dump, _n"=\"%d\" ", _v);

	inspect_printf(dump, " size=\""LLU"\" type=\"%s\" header_size=\"%d\" ", obu_size, gf_av1_get_obu_name(obu_type), hdr_size);

	if (sai_buffer && sai_buffer_size) {
		inspect_dump_crypt(dump, obu_size, sai_buffer, sai_buffer_size, offset_in_sample);
	}

	if (!full_dump) {
		inspect_printf(dump, "has_size_field=\"%d\" has_ext=\"%d\" temporalID=\"%d\" spatialID=\"%d\" ", av1->obu_has_size_field, av1->obu_extension_flag, av1->temporal_id , av1->spatial_id);
	}

	if (dump_crc && (obu_size<0xFFFFFFFFUL))
		inspect_printf(dump, "crc=\"%u\" ", gf_crc_32(obu_ptr, (u32) obu_size) );
	switch (obu_type) {
	case OBU_SEQUENCE_HEADER:
		if (full_dump) break;
		DUMP_OBU_INT(sequence_width)
		DUMP_OBU_INT(sequence_height)
		DUMP_OBU_INT(still_picture)
		DUMP_OBU_INT(OperatingPointIdc)
		DUMP_OBU_INT2("profile", av1->config->seq_profile)
		DUMP_OBU_INT2("level", av1->config->seq_level_idx_0)
		DUMP_OBU_INT(bit_depth)
		DUMP_OBU_INT2("monochrome", av1->config->monochrome)
		DUMP_OBU_INT(color_description_present_flag)
		DUMP_OBU_INT(color_primaries)
		DUMP_OBU_INT(transfer_characteristics)
		DUMP_OBU_INT(matrix_coefficients)
		DUMP_OBU_INT(color_range)
		DUMP_OBU_INT2("chroma_subsampling_x", av1->config->chroma_subsampling_x)
		DUMP_OBU_INT2("chroma_subsampling_y", av1->config->chroma_subsampling_y)
		DUMP_OBU_INT2("chroma_sample_position", av1->config->chroma_sample_position)
		DUMP_OBU_INT(film_grain_params_present)
		break;
	case OBU_FRAME_HEADER:
	case OBU_FRAME:
		if (!full_dump) {
			if (av1->frame_id_numbers_present_flag) {
				DUMP_OBU_INT2("delta_frame_id_length_minus_2", av1->delta_frame_id_length_minus_2)
			}
			if (av1->reduced_still_picture_header) {
				DUMP_OBU_INT(reduced_still_picture_header)
			}
			DUMP_OBU_INT2("uncompressed_header_bytes", av1->frame_state.uncompressed_header_bytes);
			if (av1->frame_state.uncompressed_header_bytes) {
				if (av1->frame_state.frame_type==AV1_KEY_FRAME) inspect_printf(dump, "frame_type=\"key\" ");
				else if (av1->frame_state.frame_type==AV1_INTER_FRAME) inspect_printf(dump, "frame_type=\"inter\" ");
				else if (av1->frame_state.frame_type==AV1_INTRA_ONLY_FRAME) inspect_printf(dump, "frame_type=\"intra_only\" ");
				else if (av1->frame_state.frame_type==AV1_SWITCH_FRAME) inspect_printf(dump, "frame_type=\"switch\" ");
				inspect_printf(dump, "refresh_frame_flags=\"%d\" ", av1->frame_state.refresh_frame_flags);

				if (!av1->frame_state.show_existing_frame) {
					DUMP_OBU_INT2("show_frame", av1->frame_state.show_frame);
				} else {
					DUMP_OBU_INT2("show_existing_frame", av1->frame_state.show_existing_frame);
					DUMP_OBU_INT2("frame_to_show_map_idx", av1->frame_state.frame_to_show_map_idx);
				}
				DUMP_OBU_INT(width);
				DUMP_OBU_INT(height);
			}
		}
		if (obu_type==OBU_FRAME_HEADER)
			break;

	case OBU_TILE_GROUP:
		if (av1->frame_state.nb_tiles_in_obu) {
			u32 i;
			DUMP_OBU_INT2("nb_tiles", av1->frame_state.nb_tiles_in_obu)
			fprintf(dump, ">\n");
			for (i = 0; i < av1->frame_state.nb_tiles_in_obu; i++) {
				av1_dump_tile(dump, i, &av1->frame_state.tiles[i]);
			}
		} else {
			inspect_printf(dump, "nb_tiles=\"unknown\">\n");
		}
		inspect_printf(dump, "   </OBU>\n");
		break;
	case OBU_METADATA:
		if (obu_ptr_length>hdr_size) {
			GF_BitStream *bs = gf_bs_new(obu_ptr+hdr_size, obu_ptr_length-hdr_size, GF_BITSTREAM_READ);
			ObuMetadataType metadata_type = (ObuMetadataType)gf_av1_leb128_read(bs, NULL);
			DUMP_OBU_INT2("metadata_type", metadata_type);
			switch (metadata_type) {
				case OBU_METADATA_TYPE_TIMECODE:
					dump_time_code(dump, bs, 0);
					break;
				case OBU_METADATA_TYPE_ITUT_T35:
					dump_t35(dump, bs, 0);
					break;
				case OBU_METADATA_TYPE_HDR_CLL:
					dump_clli(dump, bs);
					break;
				case OBU_METADATA_TYPE_HDR_MDCV:
					dump_mdcv(dump, bs, GF_FALSE);
					break;
				default:
					break;
			}
			gf_bs_del(bs);
		}
		break;
	default:
		break;

	}
	if ((obu_type != OBU_TILE_GROUP) && (obu_type != OBU_FRAME) )
		inspect_printf(dump, "/>\n");

	return obu_size;
}

GF_EXPORT
void gf_inspect_dump_obu(FILE *dump, AV1State *av1, u8 *obu_ptr, u64 obu_ptr_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc, u8 *sai_buffer, u32 sai_buffer_size, u32 offset_in_sample)
{
	if (!dump) return;
	gf_inspect_dump_obu_internal(dump, av1, obu_ptr, obu_ptr_length, obu_type, obu_size, hdr_size, dump_crc, NULL, inspect_get_analyze_mode(), sai_buffer, sai_buffer_size, offset_in_sample);
}

static void gf_inspect_dump_prores_internal(FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc, PidCtx *pctx)
{
	GF_ProResFrameInfo prores_frame;
	GF_Err e;
	GF_BitStream *bs;
	if (pctx) {
		gf_bs_reassign_buffer(pctx->bs, ptr, frame_size);
		bs = pctx->bs;
	} else {
		bs = gf_bs_new(ptr, frame_size, GF_BITSTREAM_READ);
	}
	e = gf_media_prores_parse_bs(bs, &prores_frame);
	if (!pctx) gf_bs_del(bs);

	if (e) {
		inspect_printf(dump, "   <!-- Error reading frame %s -->\n", gf_error_to_string(e) );
		return;
	}
	inspect_printf(dump, "   <ProResFrame framesize=\"%d\" frameID=\"%s\" version=\"%d\""
		, prores_frame.frame_size
		, gf_4cc_to_str(prores_frame.frame_identifier)
		, prores_frame.version
	);
	inspect_printf(dump, " encoderID=\"%s\" width=\"%d\" height=\"%d\""
		, gf_4cc_to_str(prores_frame.encoder_id)
		, prores_frame.width
		, prores_frame.height
	);
	switch (prores_frame.chroma_format) {
	case 0: inspect_printf(dump, " chromaFormat=\"reserved(0)\""); break;
	case 1: inspect_printf(dump, " chromaFormat=\"reserved(1)\""); break;
	case 2: inspect_printf(dump, " chromaFormat=\"4:2:2\""); break;
	case 3: inspect_printf(dump, " chromaFormat=\"4:4:4\""); break;
	}
	switch (prores_frame.interlaced_mode) {
	case 0: inspect_printf(dump, " interlacedMode=\"progressive\""); break;
	case 1: inspect_printf(dump, " interlacedMode=\"interlaced_top_first\""); break;
	case 2: inspect_printf(dump, " interlacedMode=\"interlaced_bottom_first\""); break;
	case 3: inspect_printf(dump, " interlacedMode=\"reserved\""); break;
	}
	switch (prores_frame.aspect_ratio_information) {
	case 0: inspect_printf(dump, " aspectRatio=\"unknown\""); break;
	case 1: inspect_printf(dump, " aspectRatio=\"1:1\""); break;
	case 2: inspect_printf(dump, " aspectRatio=\"4:3\""); break;
	case 3: inspect_printf(dump, " aspectRatio=\"16:9\""); break;
	default: inspect_printf(dump, " aspectRatio=\"reserved(%d)\"", prores_frame.aspect_ratio_information); break;
	}
	switch (prores_frame.framerate_code) {
	case 0: inspect_printf(dump, " framerate=\"unknown\""); break;
	case 1: inspect_printf(dump, " framerate=\"23.976\""); break;
	case 2: inspect_printf(dump, " framerate=\"24\""); break;
	case 3: inspect_printf(dump, " framerate=\"25\""); break;
	case 4: inspect_printf(dump, " framerate=\"29.97\""); break;
	case 5: inspect_printf(dump, " framerate=\"30\""); break;
	case 6: inspect_printf(dump, " framerate=\"50\""); break;
	case 7: inspect_printf(dump, " framerate=\"59.94\""); break;
	case 8: inspect_printf(dump, " framerate=\"60\""); break;
	case 9: inspect_printf(dump, " framerate=\"100\""); break;
	case 10: inspect_printf(dump, " framerate=\"119.88\""); break;
	case 11: inspect_printf(dump, " framerate=\"120\""); break;
	default: inspect_printf(dump, " framerate=\"reserved(%d)\"", prores_frame.framerate_code); break;
	}
	switch (prores_frame.color_primaries) {
	case 0: case 2: inspect_printf(dump, " colorPrimaries=\"unknown\""); break;
	case 1: inspect_printf(dump, " colorPrimaries=\"BT.709\""); break;
	case 5: inspect_printf(dump, " colorPrimaries=\"BT.601-625\""); break;
	case 6: inspect_printf(dump, " colorPrimaries=\"BT.601-525\""); break;
	case 9: inspect_printf(dump, " colorPrimaries=\"BT.2020\""); break;
	case 11: inspect_printf(dump, " colorPrimaries=\"P3\""); break;
	case 12: inspect_printf(dump, " colorPrimaries=\"P3-D65\""); break;
	default: inspect_printf(dump, " colorPrimaries=\"reserved(%d)\"", prores_frame.color_primaries); break;
	}
	switch (prores_frame.matrix_coefficients) {
	case 0: case 2: inspect_printf(dump, " matrixCoefficients=\"unknown\""); break;
	case 1: inspect_printf(dump, " matrixCoefficients=\"BT.709\""); break;
	case 6: inspect_printf(dump, " matrixCoefficients=\"BT.601\""); break;
	case 9: inspect_printf(dump, " matrixCoefficients=\"BT.2020\""); break;
	default: inspect_printf(dump, " matrixCoefficients=\"reserved(%d)\"", prores_frame.matrix_coefficients); break;
	}
	switch (prores_frame.transfer_characteristics) {
	case 0: inspect_printf(dump, " transferCharacteristics=\"unknown\""); break;
	case 1: inspect_printf(dump, " transferCharacteristics=\"BT-709\""); break;
	case 16: inspect_printf(dump, " transferCharacteristics=\"ST-2084\""); break;
	case 18: inspect_printf(dump, " transferCharacteristics=\"STD-B67\""); break;
	case 2:
	default:
		inspect_printf(dump, " transferCharacteristics=\"unspecified\""); break;
	}
	switch (prores_frame.alpha_channel_type) {
	case 0: inspect_printf(dump, " alphaChannel=\"none\""); break;
	case 1: inspect_printf(dump, " alphaChannel=\"8bits\""); break;
	case 2: inspect_printf(dump, " alphaChannel=\"16bits\""); break;
	default: inspect_printf(dump, " alphaChannel=\"reserved(%d)\"", prores_frame.alpha_channel_type); break;
	}
	inspect_printf(dump, " numPictures=\"%d\"" , prores_frame.transfer_characteristics, prores_frame.nb_pic);

	if (dump_crc) {
		inspect_printf(dump, " crc=\"%d\"" , gf_crc_32(ptr, (u32) frame_size) );
	}
	if (!prores_frame.load_luma_quant_matrix && !prores_frame.load_chroma_quant_matrix) {
		inspect_printf(dump, "/>\n");
	} else {
		u32 j, k;
		inspect_printf(dump, ">\n");
		if (prores_frame.load_luma_quant_matrix) {
			inspect_printf(dump, "    <LumaQuantMatrix coefs=\"");
			for (j=0; j<8; j++) {
				for (k=0; k<8; k++) {
					inspect_printf(dump, " %02X", prores_frame.luma_quant_matrix[j][k]);
				}
			}
			inspect_printf(dump, "\">\n");
		}
		if (prores_frame.load_chroma_quant_matrix) {
			inspect_printf(dump, "    <ChromaQuantMatrix coefs=\"");
			for (j=0; j<8; j++) {
				for (k=0; k<8; k++) {
					inspect_printf(dump, " %02X", prores_frame.chroma_quant_matrix[j][k]);
				}
			}
			inspect_printf(dump, "\">\n");
		}
		inspect_printf(dump, "   </ProResFrame>\n");
	}
}

GF_EXPORT
void gf_inspect_dump_prores(FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc)
{
	if (!dump) return;
	gf_inspect_dump_prores_internal(dump, ptr, frame_size, dump_crc, NULL);
}

static void gf_inspect_dump_opus_internal(FILE *dump, u8 *ptr, u32 size, u32 channel_count, Bool dump_crc, PidCtx *pctx)
{
	u32 pck_offset=0;
	u32 k;

	if (pctx) channel_count = pctx->opus_channel_count;

	for (k=0; k<channel_count; k++) {
		u8 self_delimited = (k != channel_count-1);
		GF_OpusPacketHeader pckh;
		u8 headerres;

		headerres = gf_opus_parse_packet_header(ptr+pck_offset, size-pck_offset, self_delimited, &pckh);
		if (!headerres) break;

		inspect_printf(dump, "    <OpusPacket offset=\"%d\" self_delimited=\"%d\"", pck_offset, pckh.self_delimited);
		inspect_printf(dump, " header_size=\"%d\" config=\"%d\" stereo=\"%d\" code=\"%d\"", pckh.size, pckh.TOC_config, pckh.TOC_stereo, pckh.TOC_code);
		if (pckh.TOC_code == 0) {
			inspect_printf(dump, " nb_frames=\"%d\" frame_lengths=\"%d\"/>\n", pckh.nb_frames, pckh.frame_lengths[0]);
		} else if (pckh.TOC_code == 1) {
			inspect_printf(dump, " nb_frames=\"%d\" frame_lengths=\"%d %d\"/>\n", pckh.nb_frames, pckh.frame_lengths[0], pckh.frame_lengths[1]);
		} else if (pckh.TOC_code == 2) {
			inspect_printf(dump, " nb_frames=\"%d\" frame_lengths=\"%d %d\"/>\n", pckh.nb_frames, pckh.frame_lengths[0], pckh.frame_lengths[1]);
		} else if (pckh.TOC_code == 3) {
			u32 j;
			inspect_printf(dump, " vbr=\"%d\" padding=\"%d\" padding_length=\"%d\" nb_frames=\"%d\"", pckh.code3_vbr, pckh.code3_padding, pckh.code3_padding_length, pckh.nb_frames);
			inspect_printf(dump, " frame_lengths=\"");
			for(j=0;j<pckh.nb_frames;j++) {
				if (j!=0) fprintf(dump, " ");
				inspect_printf(dump, "%d", pckh.frame_lengths[j]);
			}
			inspect_printf(dump, "\"");
			if (dump_crc) {
				inspect_printf(dump, " crc=\"%d\"" , gf_crc_32(ptr, (u32) size) );
			}
			inspect_printf(dump, "/>\n");
		}

		if (self_delimited) {
			if (pck_offset+pckh.packet_size >= size) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Opus] Not enough data to parse next self-delimited packet!\n"));
				break;
			}
			pck_offset += pckh.packet_size;
		}
	}
}

GF_EXPORT
void gf_inspect_dump_opus(FILE *dump, u8 *ptr, u64 size, u32 channel_count, Bool dump_crc)
{
	if (!dump) return;
	gf_inspect_dump_opus_internal(dump, ptr, (u32) size, channel_count, dump_crc, NULL);
}

enum {
	MHAS_FILLER = 0,
	MHAS_CONFIG,
	MHAS_FRAME,
	MHAS_SCENE_INFO,
	MHAS_RES4,
	MHAS_RES5,
	MHAS_SYNC,
	MHAS_SYNC_GAP,
	MHAS_MARKER,
	MHAS_CRC16,
	MHAS_CRC32,
	MHAS_DESCRIPTOR,
	MHAS_INTERACTION,
	MHAS_LOUDNESS_CRC,
	MHAS_BUFFER_INFO,
	MHAS_GLOBAL_CRC16,
	MHAS_GLOBAL_CRC32,
	MHAS_AUDIO_TRUNCATION,
	MHAS_GEN_DATA,
};
static struct {
	u32 type;
	const char *name;
} mhas_pack_types[] =
{
	{MHAS_FILLER, "filler"},
	{MHAS_CONFIG, "config"},
	{MHAS_FRAME, "frame"},
	{MHAS_SCENE_INFO, "scene_info"},
	{MHAS_SYNC, "sync"},
	{MHAS_SYNC_GAP, "sync_gap"},
	{MHAS_MARKER, "marker"},
	{MHAS_CRC16, "crc16"},
	{MHAS_CRC32, "crc32"},
	{MHAS_DESCRIPTOR, "descriptor"},
	{MHAS_INTERACTION, "interaction"},
	{MHAS_LOUDNESS_CRC, "loudness_drc"},
	{MHAS_BUFFER_INFO, "buffer_info"},
	{MHAS_GLOBAL_CRC16, "global_crc16"},
	{MHAS_GLOBAL_CRC32, "global_crc32"},
	{MHAS_AUDIO_TRUNCATION, "audio_truncation"},
	{MHAS_GEN_DATA, "gen_data"},
};

static void dump_mha_config(FILE *dump, GF_BitStream *bs, const char *indent)
{
	u32 val;
	inspect_printf(dump, "%s<MPEGHConfig", indent);
	val = gf_bs_read_int(bs, 8);
	inspect_printf(dump, " ProfileLevelIndication=\"%d\"", val);
	val = gf_bs_read_int(bs, 5);
	inspect_printf(dump, " usacSamplerateIndex=\"%d\"", val);
	if (val==0x1f) {
		val = gf_bs_read_int(bs, 24);
		inspect_printf(dump, " usacSamplerate=\"%d\"", val);
	}
	val = gf_bs_read_int(bs, 3);
	inspect_printf(dump, " coreSbrFrameLengthIndex=\"%d\"", val);
	gf_bs_read_int(bs, 1);
	val = gf_bs_read_int(bs, 1);
	inspect_printf(dump, " receiverDelayCompensation=\"%d\"", val);
	inspect_printf(dump, "/>\n");
}
static void gf_inspect_dump_mha_frame(FILE *dump, GF_BitStream *bs, const char *indent)
{
	u32 val;
	inspect_printf(dump, "%s<MPEGHFrame", indent);
	val = gf_bs_read_int(bs, 1);
	inspect_printf(dump, " usacIndependencyFlag=\"%d\"", val);
	inspect_printf(dump, "/>\n");
}
static void gf_inspect_dump_mhas(FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc, PidCtx *pctx)
{
	u64 gf_mpegh_escaped_value(GF_BitStream *bs, u32 nBits1, u32 nBits2, u32 nBits3);
	gf_bs_reassign_buffer(pctx->bs, ptr, frame_size);
	GF_BitStream *bs = pctx->bs;

	inspect_printf(dump, "   <MHASFrame>\n");

	while (gf_bs_available(bs)) {
		u32 i, count;
		const char *type_name="unknown";
		u64 pos;
		u32 type = (u32) gf_mpegh_escaped_value(bs, 3, 8, 8);
		u64 label = gf_mpegh_escaped_value(bs, 2, 8, 32);
		u64 size = gf_mpegh_escaped_value(bs, 11, 24, 24);

		count = GF_ARRAY_LENGTH(mhas_pack_types);
		for (i=0; i<count; i++) {
			if (mhas_pack_types[i].type==type) {
				type_name = mhas_pack_types[i].name;
				break;
			}
		}
		inspect_printf(dump, "    <MHASPacket type=\"%s\" label=\""LLU"\" size=\""LLU"\"", type_name, label, size);

		pos = gf_bs_get_position(bs);
		switch (type) {
		case MHAS_CONFIG:
			inspect_printf(dump, ">\n");
			dump_mha_config(dump, bs, "     ");
			inspect_printf(dump, "    </MHASPacket>\n");
			break;
		case MHAS_FRAME:
			inspect_printf(dump, ">\n");
			gf_inspect_dump_mha_frame(dump, bs, "     ");
			inspect_printf(dump, "    </MHASPacket>\n");
			break;
		case MHAS_BUFFER_INFO:
			if (gf_bs_read_int(bs, 1)) {
				inspect_printf(dump, " buffer_fullness_present=\"1\" buffer_fullness=\""LLU"\"", gf_mpegh_escaped_value(bs, 15,24,32) );
			} else {
				inspect_printf(dump, " buffer_fullness_present=\"0\"");

			}
			inspect_printf(dump, "/>\n");
			break;
		case MHAS_SYNC:
			inspect_printf(dump, " sync=\"0x%02X\"/>\n", gf_bs_read_u8(bs) );
			break;
		case MHAS_SYNC_GAP:
			inspect_printf(dump, " syncSpacingLength=\"0x%02" LLX_SUF "\"/>\n", gf_mpegh_escaped_value(bs, 16, 24, 24) );
			break;
		case MHAS_MARKER:
		case MHAS_DESCRIPTOR:
			inspect_printf(dump, " %s=\"0x", (type==MHAS_MARKER) ? "markers" : "descriptors");
			for (i=0; i<size; i++)
				inspect_printf(dump, "%02X", gf_bs_read_u8(bs) );
			inspect_printf(dump, "\"/>\n");
			break;
		default:
			inspect_printf(dump, "/>\n");
			break;
		}
		gf_bs_align(bs);
		pos = gf_bs_get_position(bs) - pos;
		if (pos < size)
			gf_bs_skip_bytes(bs, size - pos);

	}
	inspect_printf(dump, "   </MHASFrame>\n");
}

#endif


static void finalize_dump(GF_InspectCtx *ctx, u32 streamtype, Bool concat, u32 for_service_id)
{
	char szLine[1025];
	u32 i, count = gf_list_count(ctx->src_pids);

	for (i=0; i<count; i++) {
		PidCtx *pctx = gf_list_get(ctx->src_pids, i);
		//already done
		if (!pctx->tmp) continue;

		//if service ID, dump by service
		if (for_service_id) {
			if (for_service_id!=pctx->service_id) continue;
		}
		//not our streamtype
		if (streamtype && (pctx->stream_type!=streamtype)) continue;

		if (concat) {
			gf_fseek(pctx->tmp, 0, SEEK_SET);
			while (!gf_feof(pctx->tmp)) {
				u32 read = (u32) gf_fread(szLine, 1024, pctx->tmp);
				if (ctx->dump_log) {
					szLine[1024] = 0;
					GF_LOG(GF_LOG_INFO, inspect_log_tool, ("%s", szLine));
				} else {
					if (gf_fwrite(szLine, read, ctx->dump) != read) {
						GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Inspect] failed to concatenate trace: %s\n", gf_error_to_string(GF_IO_ERR)));
						break;
					}
				}
			}
		}
		gf_fclose(pctx->tmp);
		if (ctx->xml)
			inspect_printf(ctx->dump, "</PIDInspect>\n");
		pctx->tmp = NULL;
	}
}

static void inspect_dump_stats(GF_InspectCtx *ctx);

static u32 next_service_to_dump(GF_InspectCtx *ctx)
{
	u32 i, count = gf_list_count(ctx->src_pids);
	for (i=0; i<count; i++) {
		PidCtx *pctx = gf_list_get(ctx->src_pids, i);
		if (!pctx->tmp) continue;
		return pctx->service_id;
	}
	return 0;
}

static void inspect_finalize(GF_Filter *filter)
{
	Bool concat=GF_FALSE;
	GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter);

	if (ctx->is_prober) {
		FILE *fout;
		Bool do_close=GF_FALSE;
		if (!strcmp(ctx->log, "stderr")) fout = stderr;
		else if (!strcmp(ctx->log, "stdout")) fout = stdout;
		else if (!strcmp(ctx->log, "null")) fout = NULL;
		else {
			fout = gf_fopen(ctx->log, "w");
			do_close = GF_TRUE;
		}
		if (fout) {
			inspect_printf(fout, "%u\n", gf_list_count(ctx->src_pids));
			if (do_close) gf_fclose(fout);
		}
	} else {
		if (ctx->dump) {
			if ((ctx->dump!=stderr) && (ctx->dump!=stdout)) concat=GF_TRUE;
			else if (!ctx->interleave) concat=GF_TRUE;
		}

		if (ctx->stats) {
			inspect_dump_stats(ctx);
		} else if (!ctx->interleave && ctx->dump) {
			while (1) {
				u32 for_service_id = next_service_to_dump(ctx);
				finalize_dump(ctx, GF_STREAM_AUDIO, concat, for_service_id);
				finalize_dump(ctx, GF_STREAM_VISUAL, concat, for_service_id);
				finalize_dump(ctx, GF_STREAM_SCENE, concat, for_service_id);
				finalize_dump(ctx, GF_STREAM_OD, concat, for_service_id);
				finalize_dump(ctx, GF_STREAM_TEXT, concat, for_service_id);
				finalize_dump(ctx, 0, concat, for_service_id);
				if (!for_service_id) break;
			}
		}
	}

	while (gf_list_count(ctx->src_pids)) {
		PidCtx *pctx = gf_list_pop_front(ctx->src_pids);

#ifndef GPAC_DISABLE_AV_PARSERS
		if (pctx->avc_state) gf_free(pctx->avc_state);
		if (pctx->hevc_state) gf_free(pctx->hevc_state);
		if (pctx->vvc_state) gf_free(pctx->vvc_state);
		if (pctx->av1_state) {
			if (pctx->av1_state->config) gf_odf_av1_cfg_del(pctx->av1_state->config);
			gf_av1_reset_state(pctx->av1_state, GF_TRUE);
			gf_free(pctx->av1_state);
		}
#endif
		if (pctx->vpcc) gf_odf_vp_cfg_del(pctx->vpcc);
		if (pctx->bs) gf_bs_del(pctx->bs);
		gf_free(pctx);
	}
	gf_list_del(ctx->src_pids);

	if (ctx->dump) {
		if (ctx->xml) inspect_printf(ctx->dump, "</GPACInspect>\n");
		if ((ctx->dump!=stderr) && (ctx->dump!=stdout)) {

#ifdef GPAC_ENABLE_COVERAGE
			if (gf_sys_is_cov_mode()) {
				gf_fflush(ctx->dump);
				gf_ferror(ctx->dump);
			}
#endif
			gf_fclose(ctx->dump);
		}
	}
}

static void dump_temi_loc(GF_InspectCtx *ctx, PidCtx *pctx, FILE *dump, const char *pname, const GF_PropertyValue *att)
{
	u32 val;
	Bool is_announce = GF_FALSE;

	if (ctx->xml) {
		inspect_printf(dump, " <TEMILocation");
	} else {
		inspect_printf(dump, " TEMILocation");
	}
	if (!pctx->bs)
		pctx->bs = gf_bs_new(att->value.data.ptr, att->value.data.size, GF_BITSTREAM_READ);
	else
		gf_bs_reassign_buffer(pctx->bs, att->value.data.ptr, att->value.data.size);

	while (1) {
		u8 achar =  gf_bs_read_u8(pctx->bs);
		if (!achar) break;
	}

	val = atoi(pname+7);

	DUMP_ATT_D("timeline", val)
	DUMP_ATT_STR("url", att->value.data.ptr)
	if (gf_bs_read_int(pctx->bs, 1)) {
		DUMP_ATT_D("announce", 1)
		is_announce = GF_TRUE;
	}
	if (gf_bs_read_int(pctx->bs, 1)) {
		DUMP_ATT_D("splicing", 1)
	}
	if (gf_bs_read_int(pctx->bs, 1)) {
		DUMP_ATT_D("reload", 1)
	}
	gf_bs_read_int(pctx->bs, 5);
	if (is_announce) {
		GF_Fraction time;
		time.den = gf_bs_read_u32(pctx->bs);
		time.num = gf_bs_read_u32(pctx->bs);

		DUMP_ATT_FRAC("splice_start", time)
	}

	if (ctx->xml) {
		inspect_printf(dump, "/>\n");
	} else {
		inspect_printf(dump, "\n");
	}
}

static void dump_temi_time(GF_InspectCtx *ctx, PidCtx *pctx, FILE *dump, const char *pname, const GF_PropertyValue *att)
{
	u32 val;
	u64 lval;
	if (ctx->xml) {
		inspect_printf(dump, " <TEMITiming");
	} else {
		inspect_printf(dump, " TEMITiming");
	}
	val = atoi(pname+7);
	if (!pctx->bs)
		pctx->bs = gf_bs_new(att->value.data.ptr, att->value.data.size, GF_BITSTREAM_READ);
	else
		gf_bs_reassign_buffer(pctx->bs, att->value.data.ptr, att->value.data.size);

	DUMP_ATT_D("timeline", val)
	val = gf_bs_read_u32(pctx->bs);
	DUMP_ATT_D("media_timescale", val)
	lval = gf_bs_read_u64(pctx->bs);
	DUMP_ATT_LLU("media_timestamp", lval)
	lval = gf_bs_read_u64(pctx->bs);
	DUMP_ATT_LLU("media_pts", lval)

	if (gf_bs_read_int(pctx->bs, 1)) {
		DUMP_ATT_D("reload", 1)
	}
	if (gf_bs_read_int(pctx->bs, 1)) {
		DUMP_ATT_D("paused", 1)
	}
	if (gf_bs_read_int(pctx->bs, 1)) {
		DUMP_ATT_D("discontinuity", 1)
	}
	val = gf_bs_read_int(pctx->bs, 1);
	gf_bs_read_int(pctx->bs, 4);

	if (val) {
		lval = gf_bs_read_u64(pctx->bs);
		DUMP_ATT_LLU("ntp", lval)
	}
	if (ctx->xml) {
		inspect_printf(dump, "/>\n");
	} else {
		inspect_printf(dump, "\n");
	}
}

static void scte35_parse_splice_time(GF_InspectCtx *ctx, FILE *dump, GF_BitStream *bs)
{
	inspect_printf(dump, "    <SpliceTime");
	Bool time_specified_flag = gf_bs_read_int(bs, 1);
	if (time_specified_flag == 1) {
		/*reserved = */gf_bs_read_int(bs, 6);
		u64 pts_time = gf_bs_read_long_int(bs, 33);
		DUMP_ATT_LLU("ptsTime", pts_time);
	} else {
		/*reserved = */gf_bs_read_int(bs, 7);
	}
	inspect_printf(dump, "/>\n");
}

static void scte35_parse_segmentation_descriptor(FILE *dump, GF_BitStream *bs)
{
	inspect_printf(dump, "   <SegmentationDescriptor");

	inspect_printf(dump, " segmentationEventId=\"%u\"", gf_bs_read_u32(bs));
	Bool segmentation_event_cancel_indicator = gf_bs_read_int(bs, 1);
	inspect_printf(dump, " segmentationEventCancelIndicator=\"%u\"", segmentation_event_cancel_indicator);
	inspect_printf(dump, " segmentationEventIdComplianceIndicator=\"%u\"", gf_bs_read_int(bs, 1));
	gf_bs_read_int(bs, 6); //reserved
	if (segmentation_event_cancel_indicator == 0) {
		u32 program_segmentation_flag = gf_bs_read_int(bs, 1);
		inspect_printf(dump, " programSegmentationFlag=\"%u\"", program_segmentation_flag);
		u32 segmentation_duration_flag = gf_bs_read_int(bs, 1);
		inspect_printf(dump, " segmentationDurationFlag=\"%u\"", segmentation_duration_flag);
		u32 delivery_not_restricted_flag = gf_bs_read_int(bs, 1);
		inspect_printf(dump, " deliveryNotRestrictedFlag=\"%u\"", delivery_not_restricted_flag);
		if (delivery_not_restricted_flag == 0) {
			inspect_printf(dump, " webDeliveryAllowedFlag=\"%u\"", gf_bs_read_int(bs, 1));
			inspect_printf(dump, " noRegionalBlackoutFlag=\"%u\"", gf_bs_read_int(bs, 1));
			inspect_printf(dump, " archiveAllowedFlag=\"%u\"", gf_bs_read_int(bs, 1));
			inspect_printf(dump, " deviceRestrictions=\"%u\"", gf_bs_read_int(bs, 2));
		} else {
			gf_bs_read_int(bs, 5); //reserved
		}
		if (program_segmentation_flag == 0) {
			u8 component_count = gf_bs_read_u8(bs);
			inspect_printf(dump, " componentCount=\"%u\"", component_count);
			for (u8 i=0; i<component_count; i++) {
				inspect_printf(dump, " componentTag=\"%u\"", gf_bs_read_u8(bs));
				gf_bs_read_int(bs, 7); //reserved
				inspect_printf(dump, " componentTag=\"%u\"", gf_bs_read_u8(bs));
				inspect_printf(dump, " ptsOffset=\""LLU"\"", gf_bs_read_long_int(bs, 33));
			}
		}
		if (segmentation_duration_flag == 1) {
			inspect_printf(dump, " segmentationDuration=\""LLU"\"", gf_bs_read_long_int(bs, 40));
		}

		u8 segmentation_upid_type = gf_bs_read_u8(bs);
		u8 segmentation_upid_length = gf_bs_read_u8(bs);
		u64 segmentation_upid_pos = gf_bs_get_position(bs);
		gf_bs_skip_bytes(bs, segmentation_upid_length);

		u8 segmentation_type_id = gf_bs_read_u8(bs);
		inspect_printf(dump, " segmentationTypeId=\"%u\"", segmentation_type_id);
		inspect_printf(dump, " segmentNum=\"%u\"", gf_bs_read_u8(bs));
		inspect_printf(dump, " segmentsExpected=\"%u\"", gf_bs_read_u8(bs));
		if (segmentation_type_id == 0x34 || segmentation_type_id == 0x30 || segmentation_type_id == 0x32
		 || segmentation_type_id == 0x36 || segmentation_type_id == 0x38 || segmentation_type_id == 0x3A
		 || segmentation_type_id == 0x44 || segmentation_type_id == 0x46) {
			inspect_printf(dump, " subSegmentNum=\"%u\"", gf_bs_read_u8(bs));
			inspect_printf(dump, " subSegmentsExpected=\"%u\"", gf_bs_read_u8(bs));
		}

		inspect_printf(dump, ">\n");

		if (segmentation_upid_type != 0 && segmentation_upid_length) {
			// jump back to SegmentUpid to dump values
			u64 pos = gf_bs_get_position(bs);
			gf_bs_seek(bs, segmentation_upid_pos);

			inspect_printf(dump, "    <SegmentationUpid segmentationUpidType=\"%u\">", segmentation_upid_type);
			for (u8 i=0; i<segmentation_upid_length; ++i)
				inspect_printf(dump, "%02X", gf_bs_read_u8(bs));
			inspect_printf(dump, "</SegmentationUpid>\n");

#if 0 //TODO: identify segmentationUpidType as per the example below (we don't have any sample):
<SegmentationDescriptor
segmentationDuration="2700000"
segmentationEventId="1207959671"
segmentationEventCancelIndicator="0"
segmentationTypeId="48" segmentNum="1"
segmentsExpected="1"
>
<!-- EIDR of Video Service (Cartoon Network) -->
<SegmentationUpid segmentationUpidType="10">14778BE5E3F6000000000000</SegmentationUpid>
<!-- EIDR of Content (TV/Movie) (Lord of the Rings: The Fellowship of the Ring) -->
<SegmentationUpid segmentationUpidType="10">1478E030107BC08ABF93AC79</SegmentationUpid>
<!-- Ad Id (ABCD238Q000H) -->
<SegmentationUpid segmentationUpidType="3">414243443233385130303048</SegmentationUpid>
</SegmentationDescriptor>
#endif

			gf_bs_seek(bs, pos);
		}

		inspect_printf(dump, "   </SegmentationDescriptor>\n");
	} else {
		inspect_printf(dump, "/>\n");
	}
}

static u8 scte35_parse_splice_descriptor(FILE *dump, GF_BitStream *bs)
{
	if (gf_bs_available(bs) < 2)
		return 0;

	u8 splice_descriptor_tag = gf_bs_read_u8(bs);
	u8 descriptor_length = gf_bs_read_u8(bs);
	if (descriptor_length < 4 || descriptor_length > 254) {
		GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Inspect] SCTE-35 splice descriptor: invalid descriptor_length=%u\n", descriptor_length));
		return 0;
	}
	if (gf_bs_available(bs) < descriptor_length) {
		GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Inspect] not enough bits to parse SCTE-35 splice descriptor\n"));
		return 0;
	}

	u32 identifier = gf_bs_read_u32(bs);
	if (identifier != 0x43554549/*"CUEI"*/) {
		GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Inspect] unexpected SCTE-35 splice descriptor identifier \"%s\" instead of \"CUEI\". Skipping.\n", gf_4cc_to_str(identifier)));
		return 0;
	}

	//inspect_printf(dump, "   <SpliceDescriptor spliceDescriptorTag=\"%u\" identifier=\"%s\"", splice_descriptor_tag, gf_4cc_to_str(identifier));

	if (splice_descriptor_tag == 0x02) {
		//inspect_printf(dump, ">\n");
		scte35_parse_segmentation_descriptor(dump, bs);
		//inspect_printf(dump, "   </SpliceDescriptor>\n");
	} else {
		//inspect_printf(dump, "/>\n");
	}

	return descriptor_length+2;
}

static void scte35_dump(GF_InspectCtx *ctx, FILE *dump, GF_BitStream *bs)
{
	inspect_printf(dump, "  <SpliceInfoSection xmlns=\"http://www.scte.org/schemas/35\"");

	u8 table_id = gf_bs_read_u8(bs);
	Bool section_syntax_indicator = gf_bs_read_int(bs, 1);
	Bool private_indicator = gf_bs_read_int(bs, 1);

	u8 sap_type = gf_bs_read_int(bs, 2);
	DUMP_ATT_U("sapType", sap_type);

	int section_length = gf_bs_read_int(bs, 12);

	if ((table_id != 0xFC) || (section_syntax_indicator != 0) || (private_indicator != 0) || (section_length + 3 != gf_bs_get_size(bs))) {
		DUMP_ATT_STR("error", "invalid section");
		inspect_printf(dump, "/>\n");
		return;
	}

	u8 protocol_version = gf_bs_read_u8(bs);
	DUMP_ATT_U("protocolVersion", protocol_version);
	Bool encrypted_packet = gf_bs_read_int(bs, 1);
	//DUMP_ATT_U("encryptedPacket", encrypted_packet); //should be an Element, cf xsd
	u8 encryption_algorithm = gf_bs_read_int(bs, 6);
	u64 pts_adjustment = gf_bs_read_long_int(bs, 33);
	DUMP_ATT_LLU("ptsAdjustment", pts_adjustment);

	if (encrypted_packet) {
		const char* enc_algos[4] = {"No encryption", "DES - ECB mode", "DES - CBC mode", "Triple DES EDE3 - ECB mode" };
		if (encryption_algorithm < sizeof(enc_algos)/sizeof(enc_algos[0])) {
			DUMP_ATT_STR("encryptionAlgorithm", enc_algos[encryption_algorithm]);
		} else if (encryption_algorithm < 32) {
			DUMP_ATT_STR("encryptionAlgorithm", "Reserved");
		} else {
			DUMP_ATT_STR("encryptionAlgorithm", "User private");
		}
		//early exit
		goto exit;
	}

	/*u8 cw_index = */gf_bs_read_u8(bs);
	int tier = gf_bs_read_int(bs, 12);
	DUMP_ATT_D("tier", tier);

	inspect_printf(dump, ">\n");

	int splice_command_length = gf_bs_read_int(bs, 12);
	u8 splice_command_type = gf_bs_read_u8(bs);
	u64 pos = gf_bs_get_position(bs);

	switch(splice_command_type) {
	case 0x05: //splice_insert()
		{
			inspect_printf(dump, "   <SpliceInsert");
			u32 splice_event_id = gf_bs_read_u32(bs);
			DUMP_ATT_U("spliceEventId", splice_event_id);
			Bool splice_event_cancel_indicator = gf_bs_read_int(bs, 1);
			DUMP_ATT_BOOL("spliceEventCancelIndicator", splice_event_cancel_indicator);
			/*reserved = */gf_bs_read_int(bs, 7);
			if (splice_event_cancel_indicator == 0) {
				Bool out_of_network_indicator = gf_bs_read_int(bs, 1);
				DUMP_ATT_BOOL("outOfNetworkIndicator", out_of_network_indicator);
				Bool program_splice_flag = gf_bs_read_int(bs, 1);
				DUMP_ATT_BOOL("programSpliceFlag", program_splice_flag);
				Bool duration_flag = gf_bs_read_int(bs, 1);
				DUMP_ATT_BOOL("durationFlag", duration_flag);
				Bool splice_immediate_flag = gf_bs_read_int(bs, 1);
				DUMP_ATT_BOOL("spliceImmediateFlag", splice_immediate_flag);
				/*reserved = */gf_bs_read_int(bs, 4);
				inspect_printf(dump, ">\n");

				if ((program_splice_flag == 1) && (splice_immediate_flag == 0)) {
					scte35_parse_splice_time(ctx, dump, bs);
				}

				if (program_splice_flag == 0) {
					u8 component_count = gf_bs_read_u8(bs);
					DUMP_ATT_U("componentCount", component_count);
					for (int i=0; i<component_count; i++) {
						inspect_printf(dump, "    <Program>\n");
						/*u8 component_tag = */gf_bs_read_u8(bs);
						if (splice_immediate_flag == 0) {
							scte35_parse_splice_time(ctx, dump, bs);
						}
						inspect_printf(dump, "    </Program>\n");
					}
				}
				if (duration_flag == GF_TRUE) {
					//break_duration()
					inspect_printf(dump, "    <BreakDuration");
					/*Bool auto_return = */gf_bs_read_int(bs, 1);
					/*reserved = */gf_bs_read_int(bs, 6);
					u64 duration = gf_bs_read_long_int(bs, 33);
					DUMP_ATT_LLU("duration", duration);
					inspect_printf(dump, "/>\n");
				}
				/*u16 unique_program_id = */gf_bs_read_u16(bs);
				/*u8 avail_num = */gf_bs_read_u8(bs);
				/*u8 avails_expected = */gf_bs_read_u8(bs);
			} else {
				inspect_printf(dump, "/>\n");
			}
			inspect_printf(dump, "   </SpliceInsert>\n");

			gf_bs_seek(bs, pos + splice_command_length);
		}
		break;
	case 0x06: //time_signal()
		inspect_printf(dump, "   <TimeSignal>\n");
		scte35_parse_splice_time(ctx, dump, bs);
		inspect_printf(dump, "   </TimeSignal>\n");
		break;
	case 0x00: //splice_null()
		inspect_printf(dump, "   <Null/>\n");
		DUMP_ATT_STR("splice_command_type", "null");
		GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Inspect] skip SCTE-35 splice null command\n"));
		gf_bs_skip_bytes(bs, splice_command_length);
		break;
	case 0x04: //splice_schedule()
		inspect_printf(dump, "   <SpliceSchedule/>\n");
		GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Inspect] skip SCTE-35 splice schedule command\n"));
		gf_bs_skip_bytes(bs, splice_command_length);
		break;
	case 0x07: //bandwidth_reservation()
		inspect_printf(dump, "   <BandwidthReservation/>\n");
		GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Inspect] skip SCTE-35 splice bandwidth reservation command\n"));
		gf_bs_skip_bytes(bs, splice_command_length);
		break;
	case 0xff: //private_command()
		inspect_printf(dump, "   <PrivateCommand/>\n");
		GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Inspect] skip SCTE-35 splice private command\n"));
		gf_bs_skip_bytes(bs, splice_command_length);
		break;
	default:
		inspect_printf(dump, "   <Unknown/>\n");
		GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Inspect] skip unknown SCTE-35 splice command 0x%X\n", splice_command_type));
		gf_bs_skip_bytes(bs, splice_command_length);
		break;
	}

	gf_assert(gf_bs_get_position(bs) == pos + splice_command_length);
	pos += splice_command_length;

	int descriptor_loop_length = gf_bs_read_int(bs, 16);
	u32 descriptor_start_pos = (u32) gf_bs_get_position(bs);
	while ( (descriptor_start_pos < pos + descriptor_loop_length) ) {
		u8 len = scte35_parse_splice_descriptor(dump, bs);
		if (len == 0)
			break;
		descriptor_start_pos += len;
		gf_bs_seek(bs, descriptor_start_pos);
	}

exit:
	inspect_printf(dump, "  </SpliceInfoSection>\n");
}

void scte35_dump_xml(FILE *dump, GF_BitStream *bs)
{
	GF_InspectCtx ctx = {0};
	ctx.xml = GF_TRUE;
	scte35_dump(&ctx, dump, bs);
}

static void dump_scte35_info_m2ts_section(GF_InspectCtx *ctx, PidCtx *pctx, FILE *dump, const char *pname, const GF_PropertyValue *att)
{
	if (ctx->xml) {
		inspect_printf(dump, " <SCTE35>\n");
	} else {
		inspect_printf(dump, " SCTE35");
	}

	if (!pctx->bs)
		pctx->bs = gf_bs_new(att->value.data.ptr, att->value.data.size, GF_BITSTREAM_READ);
	else
		gf_bs_reassign_buffer(pctx->bs, att->value.data.ptr, att->value.data.size);

	scte35_dump(ctx, dump, pctx->bs);

	if (ctx->xml) {
		inspect_printf(dump, " </SCTE35>\n");
	} else {
		inspect_printf(dump, "\n");
	}
}

#ifndef GPAC_DISABLE_AV_PARSERS
static void gf_inspect_dump_truehd_frame(FILE *dump, GF_BitStream *bs)
{
	u8 nibble;
	u16 frame_size, timing;
	u32 sync;
	inspect_printf(dump, " <TrueHDAudioFrame");
	nibble = gf_bs_read_int(bs, 4);
	frame_size = gf_bs_read_int(bs, 12);
	timing = gf_bs_read_u16(bs);
	sync = gf_bs_read_u32(bs);
	inspect_printf(dump, " nibble=\"%u\" size=\"%u\" timing=\"%u\"", nibble, frame_size, timing);
	if (sync != 0xF8726FBA) {
		inspect_printf(dump, " major_sync=\"no\"/>\n");
		return;
	}
	u32 fmt = gf_bs_read_u32(bs);
	u32 sig = gf_bs_read_u16(bs);
	u32 flags = gf_bs_read_u16(bs);
	gf_bs_read_u16(bs);
	Bool vrate = gf_bs_read_int(bs, 1);
	u32 prate = gf_bs_read_int(bs, 15);
	u32 nb_substreams = gf_bs_read_int(bs, 4);
	gf_bs_read_int(bs, 2);
	u32 ext_substream_info = gf_bs_read_int(bs, 2);
	inspect_printf(dump, " major_sync=\"yes\" format=\"%u\" signature=\"%u\" flags=\"0x%04X\" vrate=\"%u\" peak_data_rate=\"%u\" substreams=\"%u\" extended_substream_info=\"%u\" ", fmt, sig, flags, vrate, prate, nb_substreams, ext_substream_info);

	inspect_printf(dump, "/>\n");
}
#endif

static void inspect_dump_property(GF_InspectCtx *ctx, FILE *dump, u32 p4cc, const char *pname, const GF_PropertyValue *att, PidCtx *pctx)
{
	char szDump[GF_PROP_DUMP_ARG_SIZE];

	if (!pname)
		pname = gf_props_4cc_get_name(p4cc);
	else {
		//all properties starting with __ are not dumped
		if (!strncmp(pname, "__", 2))
			return;
		if (!strcmp(pname, "isom_force_ctts") || !strcmp(pname, "reframer_rem_edits") )
			return;
	}

	switch (p4cc) {
	case GF_PROP_PCK_TIMECODE:
		//dump raw timecode when inspecting the
		if (!ctx->analyze && ctx->props && (!ctx->fmt || !strstr(ctx->fmt, "$tmcd$")))
			break;
		return;
	case GF_PROP_PCK_SEI_LOADED:
	case GF_PROP_PID_SEI_LOADED:
	case GF_PROP_PID_DOWNLOAD_SESSION:
	case GF_PROP_PID_MUX_INDEX:
	case GF_PROP_PCK_END_RANGE:
		return;
	case GF_PROP_PCK_SENDER_NTP:
	case GF_PROP_PCK_RECEIVER_NTP:
	case GF_PROP_PCK_UTC_TIME:
	case GF_PROP_PCK_MEDIA_TIME:
	case GF_PROP_PID_CENC_HAS_ROLL:
	case GF_PROP_PID_DSI_SUPERSET:
	case GF_PROP_PID_PREMUX_STREAM_TYPE:
	case GF_PROP_PID_DURATION_AVG:
		if (gf_sys_is_test_mode())
			return;
		break;
	}

	if ((att->type==GF_PROP_DATA) && (ctx->analyze || ctx->xml)) {
#ifndef GPAC_DISABLE_AV_PARSERS
		if (p4cc==GF_PROP_PID_CONTENT_LIGHT_LEVEL) {
			GF_BitStream *bs = gf_bs_new(att->value.data.ptr, att->value.data.size, GF_BITSTREAM_READ);
			dump_clli(dump, bs);
			gf_bs_del(bs);
			return;
		}
		else if (p4cc==GF_PROP_PID_MASTER_DISPLAY_COLOUR) {
			GF_BitStream *bs = gf_bs_new(att->value.data.ptr, att->value.data.size, GF_BITSTREAM_READ);
			//mdcv property is always in MPEG units
			dump_mdcv(dump, bs, GF_TRUE);
			gf_bs_del(bs);
			return;
		}
#endif /*GPAC_DISABLE_AV_PARSERS*/
	}


	if (p4cc==GF_PROP_PID_CENC_KEY_INFO) {
		u32 i, nb_keys, kpos;
		u8 *data;
		if (ctx->xml) {
			if (ctx->dtype)
				inspect_printf(dump, " type=\"%s\"", gf_props_get_type_name(att->type) );
			inspect_printf(dump, " %s=\"", pname);
		} else {
			if (ctx->dtype) {
				inspect_printf(dump, "\t%s (%s): ", pname, gf_props_get_type_name(att->type));
			} else {
				inspect_printf(dump, "\t%s: ", pname);
			}
		}
		data = att->value.data.ptr;
		nb_keys = 1;
		if (data[0]) {
			nb_keys = data[1];
			nb_keys <<= 8;
			nb_keys |= data[2];
		}
		if (nb_keys>1) inspect_printf(dump, "[");
		kpos = 3;
		for (i=0; i<nb_keys; i++) {
			u32 j;
			bin128 KID;
			u8 iv_size = data[kpos];
			memcpy(KID, data+kpos+1, 16);
			inspect_printf(dump, "IV_size:%d,KID:0x", iv_size);
			for (j=0; j<16; j++) {
				inspect_printf(dump, "%02X", (unsigned char) data[kpos + 1 + j]);
			}
			kpos+=17;
			if (!iv_size) {
				iv_size = data[kpos];
				inspect_printf(dump, ",const_IV_size:%d", iv_size);
				if (iv_size) {
					inspect_printf(dump, ",const_IV:0x");
					for (j=0; j<iv_size; j++) {
						inspect_printf(dump, "%02X", (unsigned char) data[kpos + 1 + j]);
					}
				}
				kpos += iv_size+1;
			}
		}

		if (nb_keys>1) inspect_printf(dump, "]");
		if (ctx->xml) {
			inspect_printf(dump, "\"");
		} else {
			inspect_printf(dump, "\n");
		}

		return;
	}

	if (gf_sys_is_test_mode() || ctx->test) {
		switch (p4cc) {
		case GF_PROP_PID_FILEPATH:
		case GF_PROP_PID_URL:
		case GF_PROP_PID_MUX_SRC:
			return;
		case GF_PROP_PID_FILE_CACHED:
		case GF_PROP_PID_DURATION:
			if ((ctx->test==INSPECT_TEST_NETWORK) || (ctx->test==INSPECT_TEST_NETX))
				return;
			break;
		case GF_PROP_PID_DECODER_CONFIG:
		case GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT:
		case GF_PROP_PID_DOWN_SIZE:
			if (ctx->test>=INSPECT_TEST_ENCODE)
				return;
			break;
		case GF_PROP_PID_BITRATE:
			if (ctx->test==INSPECT_TEST_NOBR)
				return;
		case GF_PROP_PID_MEDIA_DATA_SIZE:
		case GF_PROP_PID_MAXRATE:
		case GF_PROP_PID_AVG_FRAME_SIZE:
		case GF_PROP_PID_MAX_FRAME_SIZE:
		case GF_PROP_PID_DBSIZE:
			if (ctx->test==INSPECT_TEST_ENCX)
				return;
			break;

		case GF_PROP_PID_ISOM_TRACK_TEMPLATE:
		case GF_PROP_PID_ISOM_MOVIE_TIME:
			if (ctx->test==INSPECT_TEST_NETX)
				return;
			break;

		case GF_PROP_PID_ISOM_TREX_TEMPLATE:
		case GF_PROP_PID_ISOM_STSD_TEMPLATE:
		case GF_PROP_PID_ISOM_STSD_TEMPLATE_IDX:
		case GF_PROP_PID_ISOM_STSD_ALL_TEMPLATES:
			//TODO once all OK: remove this test and regenerate all hashes
			if (gf_sys_is_test_mode())
				return;
		default:
			if (gf_sys_is_test_mode() && (att->type==GF_PROP_POINTER) )
				return;
			break;
		}
	}

	if (ctx->xml) {
		if (ctx->dtype)
			inspect_printf(dump, " type=\"%s\"", gf_props_get_type_name(att->type) );

		if (pname && (strpbrk(pname, " :"))) {
			u32 i=0, k;
			char *pname_no_space = gf_strdup(pname);
			while (pname_no_space[i]) {
				if ((pname_no_space[i]==' ') || (pname_no_space[i]==':')) pname_no_space[i]='_';
				i++;
			}

			if ((att->type==GF_PROP_UINT_LIST) || (att->type==GF_PROP_4CC_LIST)) {
				for (k=0; k < att->value.uint_list.nb_items; k++) {
					if (k) inspect_printf(dump, ", ");
					if ((att->type==GF_PROP_4CC_LIST) && ! gf_sys_is_test_mode()) {
						inspect_printf(dump, "%s", gf_4cc_to_str(att->value.uint_list.vals[k]) );
					} else {
						inspect_printf(dump, "%d", att->value.uint_list.vals[k]);
					}
				}
			} else if (att->type==GF_PROP_STRING_LIST) {
				for (k=0; k < att->value.string_list.nb_items; k++) {
					if (k) inspect_printf(dump, ", ");
					gf_xml_dump_string(dump, NULL, (char *) att->value.string_list.vals[k], NULL);
				}
			} else if ((att->type==GF_PROP_STRING) || (att->type==GF_PROP_STRING_NO_COPY)) {
				gf_xml_dump_string(dump, NULL, att->value.string, NULL);
			} else {
				inspect_printf(dump, " %s=\"%s\"", pname_no_space, gf_props_dump(p4cc, att, szDump, (GF_PropDumpDataMode) ctx->dump_data));
			}
			gf_free(pname_no_space);
		} else if (!p4cc && !strncmp(pname, "scte35", 6)) {
			dump_scte35_info_m2ts_section(ctx, pctx, dump, pname, att);
		/*} else if (!p4cc && !strncmp(pname, "temi_l", 6)) {
			dump_temi_loc(ctx, pctx, dump, pname, att);
		} else if (!p4cc && !strncmp(pname, "temi_t", 6)) {
			dump_temi_time(ctx, pctx, dump, pname, att);*/
		} else {
			inspect_printf(dump, " %s=\"%s\"", pname ? pname : gf_4cc_to_str(p4cc), gf_props_dump(p4cc, att, szDump, (GF_PropDumpDataMode) ctx->dump_data));
		}
	} else {
		if (ctx->dtype) {
			inspect_printf(dump, "\t%s (%s): ", pname ? pname : gf_4cc_to_str(p4cc), gf_props_get_type_name(att->type));
		} else {
			if (!p4cc && !strncmp(pname, "scte35", 6)) {
				dump_scte35_info_m2ts_section(ctx, pctx, dump, pname, att);
				return;
			} else if (!p4cc && !strncmp(pname, "temi_l", 6)) {
				dump_temi_loc(ctx, pctx, dump, pname, att);
				return;
			} else if (!p4cc && !strncmp(pname, "temi_t", 6)) {
				dump_temi_time(ctx, pctx, dump, pname, att);
				return;
			}

			inspect_printf(dump, "\t%s: ", pname ? pname : gf_4cc_to_str(p4cc));
		}

		if ((att->type==GF_PROP_UINT_LIST) || (att->type==GF_PROP_4CC_LIST)) {
			u32 k;
			for (k=0; k < att->value.uint_list.nb_items; k++) {
				if (k) inspect_printf(dump, ", ");
				if ((att->type==GF_PROP_4CC_LIST) && ! gf_sys_is_test_mode()) {
					inspect_printf(dump, "%s", gf_4cc_to_str(att->value.uint_list.vals[k]) );
				} else {
					inspect_printf(dump, "%d", att->value.uint_list.vals[k]);
				}
			}
		} else if (att->type==GF_PROP_STRING_LIST) {
			u32 k;
			for (k=0; k < att->value.string_list.nb_items; k++) {
				if (k) inspect_printf(dump, ", ");
				inspect_printf(dump, "%s", (const char *) att->value.string_list.vals[k]);
			}
		} else {
			inspect_printf(dump, "%s", gf_props_dump(p4cc, att, szDump, (GF_PropDumpDataMode) ctx->dump_data) );
		}
		if ((p4cc==GF_PROP_PID_DURATION) && !gf_sys_is_test_mode()) {
			format_duration(att->value.lfrac.num, att->value.lfrac.den, dump, GF_TRUE);
		}
		inspect_printf(dump, "\n");
	}
}

static void inspect_dump_packet_fmt(GF_Filter *filter, GF_InspectCtx *ctx, FILE *dump, GF_FilterPacket *pck, PidCtx *pctx, u64 pck_num)
{
	char szDump[GF_PROP_DUMP_ARG_SIZE];
	u32 size=0;
	const char *data=NULL;
	char *str = ctx->fmt;
	gf_assert(str);

	if (pck) {
		data = gf_filter_pck_get_data(pck, &size);
		pctx->csize += size;
	}

	while (str) {
		char csep;
		char *sep, *key;
		if (!str[0]) break;

		if ((str[0] != '$') && (str[0] != '%') && (str[0] != '@')) {
			inspect_printf(dump, "%c", str[0]);
			str = str+1;
			continue;
		}
		csep = str[0];
		if (str[1] == csep) {
			inspect_printf(dump, "%c", str[0]);
			str = str+2;
			continue;
		}
		sep = strchr(str+1, csep);
		if (!sep) {
			inspect_printf(dump, "%c", str[0]);
			str = str+1;
			continue;
		}
		sep[0] = 0;
		key = str+1;

		if (!pck) {
			if (!strcmp(key, "lf") || !strcmp(key, "n")) inspect_printf(dump, "\n" );
			else if (!strcmp(key, "t")) inspect_printf(dump, "\t" );
			else if (!strncmp(key, "pid.", 4)) inspect_printf(dump, "%s", key+4);
			else inspect_printf(dump, "%s", key);
			sep[0] = csep;
			str = sep+1;
			continue;
		}

		if (!strcmp(key, "pn")) inspect_printf(dump, LLU, pck_num);
		else if (!strcmp(key, "dts")) {
			u64 ts = gf_filter_pck_get_dts(pck);
			if (ts==GF_FILTER_NO_TS) inspect_printf(dump, "N/A");
			else inspect_printf(dump, LLU, ts );
		}
		else if (!strcmp(key, "cts")) {
			u64 ts = gf_filter_pck_get_cts(pck);
			if (ts==GF_FILTER_NO_TS) inspect_printf(dump, "N/A");
			else inspect_printf(dump, LLU, ts );
		}
		else if (!strcmp(key, "ddts")) {
			u64 ts = gf_filter_pck_get_dts(pck);
			if ((ts==GF_FILTER_NO_TS) || (pctx->prev_dts==GF_FILTER_NO_TS)) inspect_printf(dump, "N/A");
			else if (pctx->pck_num<=1) inspect_printf(dump, "N/A");
			else {
				s64 diff = ts;
				diff -= pctx->prev_dts;
				inspect_printf(dump, LLD, diff);
			}
			pctx->prev_dts = ts;
		}
		else if (!strcmp(key, "dcts")) {
			u64 ts = gf_filter_pck_get_cts(pck);
			if ((ts==GF_FILTER_NO_TS) || (pctx->prev_cts==GF_FILTER_NO_TS)) inspect_printf(dump, "N/A");
			else if (pctx->pck_num<=1) inspect_printf(dump, "N/A");
			else {
				s64 diff = ts;
				diff -= pctx->prev_cts;
				inspect_printf(dump, LLD, diff);
			}
			pctx->prev_cts = ts;
		}
		else if (!strcmp(key, "ctso")) {
			u64 dts = gf_filter_pck_get_dts(pck);
			u64 cts = gf_filter_pck_get_cts(pck);
			if (dts==GF_FILTER_NO_TS) dts = cts;
			if (cts==GF_FILTER_NO_TS) inspect_printf(dump, "N/A");
			else inspect_printf(dump, LLD, ((s64)cts) - ((s64)dts) );
		}

		else if (!strcmp(key, "dur")) inspect_printf(dump, "%u", gf_filter_pck_get_duration(pck) );
		else if (!strcmp(key, "frame")) {
			Bool start, end;
			gf_filter_pck_get_framing(pck, &start, &end);
			if (start && end) inspect_printf(dump, "frame_full");
			else if (start) inspect_printf(dump, "frame_start");
			else if (end) inspect_printf(dump, "frame_end");
			else inspect_printf(dump, "frame_cont");
		}
		else if (!strcmp(key, "sap") || !strcmp(key, "rap")) {
			u32 sap = gf_filter_pck_get_sap(pck);
			if (sap==GF_FILTER_SAP_4_PROL) {
				inspect_printf(dump, "4 (prol)");
			} else {
				inspect_printf(dump, "%u", sap );
			}
		}
		else if (!strcmp(key, "ilace")) inspect_printf(dump, "%d", gf_filter_pck_get_interlaced(pck) );
		else if (!strcmp(key, "corr")) inspect_printf(dump, "%d", gf_filter_pck_get_corrupted(pck) );
		else if (!strcmp(key, "seek")) inspect_printf(dump, "%d", gf_filter_pck_get_seek_flag(pck) );
		else if (!strcmp(key, "bo")) {
			u64 bo = gf_filter_pck_get_byte_offset(pck);
			if (bo==GF_FILTER_NO_BO) inspect_printf(dump, "N/A");
			else inspect_printf(dump, LLU, bo);
		}
		else if (!strcmp(key, "roll")) inspect_printf(dump, "%d", gf_filter_pck_get_roll_info(pck) );
		else if (!strcmp(key, "crypt")) inspect_printf(dump, "%d", gf_filter_pck_get_crypt_flags(pck) );
		else if (!strcmp(key, "vers")) inspect_printf(dump, "%d", gf_filter_pck_get_carousel_version(pck) );
		else if (!strcmp(key, "size")) inspect_printf(dump, "%d", size );
		else if (!strcmp(key, "csize")) inspect_printf(dump, "%d", pctx->csize);
		else if (!strcmp(key, "crc")) inspect_printf(dump, "0x%08X", gf_crc_32(data, size) );
		else if (!strcmp(key, "lf") || !strcmp(key, "n")) inspect_printf(dump, "\n" );
		else if (!strcmp(key, "t")) inspect_printf(dump, "\t" );
		else if (!strcmp(key, "data")) {
			u32 i;
			if ((pctx->stream_type==GF_STREAM_TEXT) && gf_utf8_is_legal(data, size)) {
				for (i=0; i<size; i++) {
					inspect_printf(dump, "%c", data[i]);
				}
			} else {
				for (i=0; i<size; i++) {
					inspect_printf(dump, "%02X", (unsigned char) data[i]);
				}
			}
		}
		else if (!strcmp(key, "lp")) {
			u8 flags = gf_filter_pck_get_dependency_flags(pck);
			flags >>= 6;
			inspect_printf(dump, "%u", flags);
		}
		else if (!strcmp(key, "depo")) {
			u8 flags = gf_filter_pck_get_dependency_flags(pck);
			flags >>= 4;
			flags &= 0x3;
			inspect_printf(dump, "%u", flags);
		}
		else if (!strcmp(key, "depf")) {
			u8 flags = gf_filter_pck_get_dependency_flags(pck);
			flags >>= 2;
			flags &= 0x3;
			inspect_printf(dump, "%u", flags);
		}
		else if (!strcmp(key, "red")) {
			u8 flags = gf_filter_pck_get_dependency_flags(pck);
			flags &= 0x3;
			inspect_printf(dump, "%u", flags);
		}
		else if (!strcmp(key, "start") || !strcmp(key, "end") || !strcmp(key, "startc") || !strcmp(key, "endc")) {
			u64 ts = gf_filter_pck_get_cts(pck);
			if (ts==GF_FILTER_NO_TS) inspect_printf(dump, "N/A");
			else {
				if (!strcmp(key, "end"))
					ts += gf_filter_pck_get_duration(pck);
				ts *= 1000;
				ts /= gf_filter_pid_get_timescale(pctx->src_pid);

				u32 h, m, s, ms;
				u64 time = ts/1000;
				h = (u32) (time/3600);
				m = (u32) (time/60 - h*60);
				s = (u32) (time - m*60 - h*3600);
				ms = (u32) (ts - 1000*time);
				if (!strcmp(key, "startc") || !strcmp(key, "endc"))
					inspect_printf(dump, "%02d:%02d:%02d,%03d", h, m, s, ms);
				else
					inspect_printf(dump, "%02d:%02d:%02d.%03d", h, m, s, ms);
			}
		}
		else if (!strcmp(key, "ck")) {
			GF_FilterClockType ck_type = gf_filter_pck_get_clock_type(pck);
			inspect_printf(dump, "%d", ck_type );
		} else if (!strcmp(key, "pcr") || !strcmp(key, "pcrd") || !strcmp(key, "pcrc")) {
			u64 clock_val;
			u32 ck_timescale;
			GF_FilterClockType ck_type;
			ck_type = gf_filter_pid_get_clock_info(pctx->src_pid, &clock_val, &ck_timescale);
			if (ck_type) {
				u32 timescale = gf_filter_pck_get_timescale(pck);
				pctx->last_pcr = gf_timestamp_rescale(clock_val, ck_timescale, timescale);
				pctx->last_clock_type = ck_type;
			} else {
				ck_type = pctx->last_clock_type;
			}
			clock_val = pctx->last_pcr;

			if (ck_type) {
				u64 ts = GF_FILTER_NO_TS;
				Bool dump_diff=GF_FALSE;
				u32 timescale = gf_filter_pck_get_timescale(pck);
				clock_val = gf_timestamp_rescale(clock_val, ck_timescale, timescale);
				if (!strcmp(key, "pcrd")) {
					ts = gf_filter_pck_get_dts(pck);
					dump_diff = GF_TRUE;
				}
				else if (!strcmp(key, "pcrc")) {
					ts = gf_filter_pck_get_cts(pck);
					dump_diff = GF_TRUE;
				}
				if (dump_diff) {
					if (ts>clock_val)
						inspect_printf(dump, "-"LLU, ts - clock_val);
					else
						inspect_printf(dump, LLU, clock_val - ts);
				} else {
					inspect_printf(dump, LLU, clock_val);
				}
			} else {
				inspect_printf(dump, "N/A");
			}
		}
		else if (!strncmp(key, "pid.", 4)) {
			const GF_PropertyValue *prop = NULL;
			u32 prop_4cc=0;
			const char *pkey = key+4;
			prop_4cc = gf_props_get_id(pkey);
			if (!prop_4cc && strlen(pkey)==4) prop_4cc = GF_4CC(pkey[0],pkey[1],pkey[2],pkey[3]);

			if (prop_4cc) {
				prop = gf_filter_pid_get_property(pctx->src_pid, prop_4cc);
			}
			if (!prop) prop = gf_filter_pid_get_property_str(pctx->src_pid, key);

			if (prop) {
				inspect_printf(dump, "%s", gf_props_dump(prop_4cc, prop, szDump, (GF_PropDumpDataMode) ctx->dump_data) );
			} else {
				inspect_printf(dump, "N/A");
			}
		}
		else if (!strcmp(key, "fn")) {
			inspect_printf(dump, "%s", gf_filter_get_name(filter) );
		}
		else if (!strcmp(key, "tmcd")) {
			const GF_PropertyValue *prop = gf_filter_pck_get_property(pck, GF_PROP_PCK_TIMECODE);
			if (prop && prop->value.data.size) {
				char tcBuf[100];
				GF_TimeCode *tc = (GF_TimeCode *) prop->value.data.ptr;
				inspect_printf(dump, "%s", gf_format_timecode(tc, tcBuf));
			} else {
				inspect_printf(dump, "N/A");
			}
		}
		else {
			const GF_PropertyValue *prop = NULL;
			u32 prop_4cc = gf_props_get_id(key);
			if (!prop_4cc && strlen(key)==4) prop_4cc = GF_4CC(key[0],key[1],key[2],key[3]);

			if (prop_4cc) {
				prop = gf_filter_pck_get_property(pck, prop_4cc);
			}
			if (!prop) prop = gf_filter_pck_get_property_str(pck, key);

			if (prop) {
				inspect_printf(dump, "%s", gf_props_dump(prop_4cc, prop, szDump, (GF_PropDumpDataMode) ctx->dump_data) );
			} else {
				inspect_printf(dump, "N/A");
			}
		}

		sep[0] = csep;
		str = sep+1;
	}
}

void gf_m4v_parser_set_inspect(GF_M4VParser *m4v);
u32 gf_m4v_parser_get_obj_type(GF_M4VParser *m4v);

#ifndef GPAC_DISABLE_AV_PARSERS
static const char *get_frame_type_name(u32 ftype)
{
	switch (ftype) {
	case 2: return "B";
	case 1: return "P";
	case 0: return "I";
	default: return "unknown";
	}
}
static void inspect_dump_mpeg124(PidCtx *pctx, char *data, u32 size, FILE *dump)
{
	u8 ftype;
	u32 tinc, o_type;
	u64 fsize, start;
	Bool is_coded, is_m4v=(pctx->codec_id==GF_CODECID_MPEG4_PART2) ? GF_TRUE : GF_FALSE;
	GF_Err e;
	GF_M4VParser *m4v = gf_m4v_parser_new(data, size, !is_m4v);

	gf_m4v_parser_set_inspect(m4v);
	while (1) {
		ftype = 0;
		is_coded = GF_FALSE;
		e = gf_m4v_parse_frame(m4v, &pctx->dsi, &ftype, &tinc, &fsize, &start, &is_coded);
		if (e)
			break;

		o_type = gf_m4v_parser_get_obj_type(m4v);
		if (is_m4v) {
			inspect_printf(dump, "<MPEG4P2VideoObj type=\"0x%02X\"", o_type);
			switch (o_type) {
			/*vosh*/
			case M4V_VOS_START_CODE:
				inspect_printf(dump, " name=\"VOS\" PL=\"%d\"", pctx->dsi.VideoPL);
				break;
			case M4V_VOL_START_CODE:
				inspect_printf(dump, " name=\"VOL\" RAPStream=\"%d\" objectType=\"%d\" par=\"%d/%d\" hasShape=\"%d\"", pctx->dsi.RAP_stream, pctx->dsi.objectType, pctx->dsi.par_num, pctx->dsi.par_den, pctx->dsi.has_shape);
				if (pctx->dsi.clock_rate)
					inspect_printf(dump, " clockRate=\"%d\"", pctx->dsi.clock_rate);
				if (pctx->dsi.time_increment)
					inspect_printf(dump, " timeIncrement=\"%d\"", pctx->dsi.time_increment);
				if (!pctx->dsi.has_shape)
					inspect_printf(dump, " width=\"%d\" height=\"%d\"", pctx->dsi.width, pctx->dsi.height);

				break;

			case M4V_VOP_START_CODE:
				inspect_printf(dump, " name=\"VOP\" RAP=\"%d\" frameType=\"%s\" timeInc=\"%d\" isCoded=\"%d\"", (ftype==1) ? 1 : 0, get_frame_type_name(ftype), tinc, is_coded);
				break;
			case M4V_GOV_START_CODE:
				inspect_printf(dump, " name=\"GOV\"");
				break;
			/*don't interest us*/
			case M4V_UDTA_START_CODE:
				inspect_printf(dump, " name=\"UDTA\"");
				break;

	 		case M4V_VO_START_CODE:
				inspect_printf(dump, " name=\"VO\"");
				break;
	 		case M4V_VISOBJ_START_CODE:
				inspect_printf(dump, " name=\"VisObj\"");
				break;
			default:
				break;
			}
			inspect_printf(dump, "/>\n");
		} else {
			inspect_printf(dump, "<MPEG12VideoObj type=\"0x%02X\"", o_type);
			switch (o_type) {
			case M2V_SEQ_START_CODE:
				inspect_printf(dump, " name=\"SeqStart\" width=\"%d\" height=\"%d\" sar=\"%d/%d\" fps=\"%f\"", pctx->dsi.width, pctx->dsi.height, pctx->dsi.par_num, pctx->dsi.par_den, pctx->dsi.fps);
				break;

			case M2V_EXT_START_CODE:
				inspect_printf(dump, " name=\"SeqStartEXT\" width=\"%d\" height=\"%d\" PL=\"%d\"", pctx->dsi.width, pctx->dsi.height, pctx->dsi.VideoPL);
				break;
			case M2V_PIC_START_CODE:
				inspect_printf(dump, " name=\"PicStart\" frameType=\"%s\" isCoded=\"%d\"", get_frame_type_name(ftype), is_coded);
				break;
			case M2V_GOP_START_CODE:
				inspect_printf(dump, " name=\"GOPStart\"");
				break;
			case M2V_UDTA_START_CODE:
				start = gf_m4v_get_object_start(m4v);
				if (size > start+4)
					dump_udta_m2v(dump, data + start+4, (u32) (size-start-4));
				break;
			default:
				break;
			}
			inspect_printf(dump, "/>\n");
		}
	}
	gf_m4v_parser_del(m4v);
}
#endif

static void inspect_format_tmcd_internal(const u8 *data, u32 size, u32 tmcd_flags, u32 tc_num, u32 tc_den, u32 tmcd_fpt, char szFmt[100], GF_BitStream *bs, Bool force_ff, FILE *dump)
{
	u32 h, m, s, f, value;
	Bool neg=GF_FALSE;
	u32 parse_fmt = 1;
	Bool is_drop = GF_FALSE;
	GF_BitStream *loc_bs = NULL;

	if (szFmt)
		szFmt[0] = 0;

	if (!tc_num || !tc_den)
		return;

	if (!bs) {
		loc_bs = gf_bs_new(data, size, GF_BITSTREAM_READ);
		bs = loc_bs;
	} else {
		gf_bs_reassign_buffer(bs, data, size);
	}

	value = gf_bs_read_u32(bs);
	gf_bs_seek(bs, 0);

	if (tmcd_flags & 0x00000001)
		is_drop = GF_TRUE;

	if (tmcd_flags & 0x00000004)
		neg = GF_TRUE;

	if (dump)
		inspect_printf(dump, "<TimeCode");

	if (!force_ff && !(tmcd_flags & 0x00000008)) {
		h = gf_bs_read_u8(bs);
		neg = gf_bs_read_int(bs, 1);
		m = gf_bs_read_int(bs, 7);
		s = gf_bs_read_u8(bs);
		f = gf_bs_read_u8(bs);

		if (tmcd_fpt && (f > tmcd_fpt))
			parse_fmt = 2;
		else if ((m>=60) || (s>=60))
			parse_fmt = 2;
		else
			parse_fmt = 0;
	}

	if (parse_fmt) {
		u64 nb_secs, nb_frames = value;
		neg = GF_FALSE;
		if (parse_fmt==2) force_ff = GF_TRUE;

		if (!force_ff && tmcd_fpt)
			nb_frames *= tmcd_fpt;

		if (is_drop && tc_num) {
			u32 tc_drop_frames, min_10;

			nb_secs = nb_frames;
			nb_secs *= tc_den;
			nb_secs /= tc_num;

			m = (u32) (nb_secs/60);

			min_10 = m / 10;
			m-= min_10;

			tc_drop_frames = m*2;
			nb_frames += tc_drop_frames;
		}

		if (!tmcd_fpt) tmcd_fpt = 30;

		nb_secs = nb_frames / tmcd_fpt;
		h = (u32) nb_secs/3600;
		m = (u32) (nb_secs/60 - h*60);
		s = (u32) (nb_secs - m*60 - h*3600);

		nb_secs *= tmcd_fpt;

		f = (u32) (nb_frames - nb_secs);
		if (tmcd_fpt && (f==tmcd_fpt)) {
			f = 0;
			s++;
			if (s==60) {
				s = 0;
				m++;
				if (m==60) {
					m = 0;
					h++;
				}
			}
		}
	}
	if (dump)
		inspect_printf(dump, " time=\"%s%02d:%02d:%02d%c%02d\"/>\n", neg ? "-" : "", h, m, s, is_drop ? ';' : ':', f);
	else if (szFmt)
		sprintf(szFmt, "%s%02d:%02d:%02d%c%02d", neg ? "-" : "", h, m, s, is_drop ? ';' : ':', f);

	if (loc_bs) gf_bs_del(loc_bs);
}


GF_EXPORT
void gf_inspect_format_timecode(const u8 *data, u32 size, u32 tmcd_flags, u32 tc_num, u32 tc_den, u32 tmcd_fpt, char szFmt[100])
{
	inspect_format_tmcd_internal(data, size, tmcd_flags, tc_num, tc_den, tmcd_fpt, szFmt, NULL, GF_FALSE, NULL);
}

#ifndef GPAC_DISABLE_AV_PARSERS
static void inspect_dump_tmcd(GF_InspectCtx *ctx, PidCtx *pctx, const u8 *data, u32 size, FILE *dump)
{
	inspect_format_tmcd_internal(data, size, pctx->tmcd_flags, pctx->tmcd_rate.num, pctx->tmcd_rate.den, pctx->tmcd_fpt, NULL, pctx->bs, ctx->fftmcd, dump);
}

static void inspect_dump_boxes(GF_InspectCtx *ctx, PidCtx *pctx, const u8 *data, u32 size, FILE *dump)
{
	if (ctx->dump) {
		GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ);
		GF_Err e = GF_OK;
		while (gf_bs_available(bs) > 0) {
			GF_Box *a = NULL;
			e = gf_isom_box_parse(&a, bs);
			if (e) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Inspect] Event Track: error while parsing data boxes\n"));
				break; //don't parse any further
			}
			gf_isom_box_dump(a, dump);
			data += a->size;
			gf_isom_box_del(a);
			a=NULL;
		}
		gf_bs_del(bs);
	}
}

static void inspect_dump_vpx(GF_InspectCtx *ctx, FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc, PidCtx *pctx, u32 vpversion)
{
	GF_Err e;
	Bool key_frame = GF_FALSE;
	u32 width = 0, height = 0, renderWidth=0, renderHeight=0;
	u32 num_frames_in_superframe = 0, superframe_index_size = 0, i = 0;
	u32 frame_sizes[VP9_MAX_FRAMES_IN_SUPERFRAME];
	gf_bs_reassign_buffer(pctx->bs, ptr, frame_size);
	InspectLogCbk lcbk;

	if (ctx->analyze>=INSPECT_ANALYZE_BS) {
		lcbk.dump = dump;
		lcbk.dump_bits = ctx->analyze==INSPECT_ANALYZE_BS_BITS ? GF_TRUE : GF_FALSE;
		gf_bs_set_logger(pctx->bs, regular_bs_log, &lcbk);
	}

	/*check if it is a superframe*/
	e = gf_vp9_parse_superframe(pctx->bs, frame_size, &num_frames_in_superframe, frame_sizes, &superframe_index_size);

	inspect_printf(dump, "<VP%d%sFrame", vpversion, superframe_index_size ? "Super" : "");
	if (e) {
		inspect_printf(dump, " status=\"error parsing superframe\"/>\n");
		gf_bs_set_logger(pctx->bs, NULL, NULL);
		return;
	}
	if (superframe_index_size)
		inspect_printf(dump, " nb_frames=\"%u\" index_size=\"%u\">\n", num_frames_in_superframe, superframe_index_size);
	else {
		gf_assert(num_frames_in_superframe==1);
	}
	for (i = 0; i < num_frames_in_superframe; ++i) {
		u64 pos2 = gf_bs_get_position(pctx->bs);

		if (superframe_index_size)
			inspect_printf(dump, "<VP%dFrame", vpversion);

		inspect_printf(dump, " size=\"%u\"", frame_sizes[i]);
		if (gf_vp9_parse_sample(pctx->bs, pctx->vpcc, &key_frame, &width, &height, &renderWidth, &renderHeight) != GF_OK) {
			inspect_printf(dump, " status=\"error parsing frame\"/>\n");
			goto exit;
		}
		inspect_printf(dump, " key_frame=\"%u\" width=\"%u\" height=\"%u\" renderWidth=\"%u\" renderHeight=\"%u\"", key_frame, width, height, renderWidth, renderHeight);
		e = gf_bs_seek(pctx->bs, pos2 + frame_sizes[i]);
		if (e) {
			inspect_printf(dump, " status=\"error seeking %s (offset "LLU")\"/>\n", gf_error_to_string(e), pos2 + frame_sizes[i]);
			goto exit;
		}

		inspect_printf(dump, "/>\n");
	}

exit:
	if (superframe_index_size)
		inspect_printf(dump, "</VP%dSuperFrame>\n", vpversion);
	gf_bs_set_logger(pctx->bs, NULL, NULL);
}

static void inspect_dump_ac3_eac3(GF_InspectCtx *ctx, FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc, PidCtx *pctx, Bool is_ec3)
{
	GF_AC3Header hdr;
	InspectLogCbk lcbk;

	if (!pctx->bs) pctx->bs = gf_bs_new(ptr, frame_size, GF_BITSTREAM_READ);
	else gf_bs_reassign_buffer(pctx->bs, ptr, frame_size);

	if (ctx->analyze>=INSPECT_ANALYZE_BS) {
		lcbk.dump = dump;
		lcbk.dump_bits = ctx->analyze==INSPECT_ANALYZE_BS_BITS ? GF_TRUE : GF_FALSE;
		gf_bs_set_logger(pctx->bs, regular_bs_log, &lcbk);
	}
	inspect_printf(dump, "<%sSample ", is_ec3 ? "EC3" : "AC3");
	memset(&hdr, 0, sizeof(GF_AC3Header));
	gf_ac3_parser_bs(pctx->bs, &hdr, GF_TRUE);
	if (ctx->analyze<INSPECT_ANALYZE_BS) {
		inspect_printf(dump, "bitrate=\"%u\" channels=\"%u\" framesize=\"%u\"", hdr.is_ec3 ? hdr.brcode*1000 : gf_ac3_get_bitrate(hdr.brcode), hdr.streams[0].channels, hdr.framesize);
		if (hdr.is_ec3) {
			inspect_printf(dump, " nb_streams=\"%u\" ", hdr.nb_streams);
		}
	}

	inspect_printf(dump, "/>\n");
	gf_bs_set_logger(pctx->bs, NULL, NULL);
}

#endif /*GPAC_DISABLE_AV_PARSERS*/

static void inspect_dump_packet_as_info(GF_InspectCtx *ctx, FILE *dump, GF_FilterPacket *pck, u32 pid_idx, u64 pck_num, PidCtx *pctx)
{
	u32 size, sap, flag;
	u64 ts, dts;
	GF_FilterClockType ck_type;
	GF_FilterFrameInterface *fifce=NULL;

	if (!ctx->dump_log && !dump) return;

	gf_filter_pck_get_data(pck, &size);
	ck_type = ctx->pcr ? gf_filter_pck_get_clock_type(pck) : 0;
	if (!size && !ck_type) {
		fifce = gf_filter_pck_get_frame_interface(pck);
	}

	inspect_printf(dump, "PID %d PCK "LLU, pid_idx, pck_num);

	if (ck_type) {
		ts = gf_filter_pck_get_cts(pck);
		if (ts!=GF_FILTER_NO_TS)
			inspect_printf(dump, " PCR%s "LLU"\n", (ck_type==GF_FILTER_CLOCK_PCR) ? "" : " discontinuity", ts );
		return;
	}

	ts = gf_filter_pck_get_cts(pck);
	if (ts==GF_FILTER_NO_TS) inspect_printf(dump, " cts N/A");
	else inspect_printf(dump, " cts "LLU, ts);

	dts = gf_filter_pck_get_dts(pck);
	if ((dts!=GF_FILTER_NO_TS) && (dts!= ts)) inspect_printf(dump, " dts "LLU, dts);

	flag = gf_filter_pck_get_duration(pck);
	if (flag) inspect_printf(dump, " dur %u", flag);

	sap = gf_filter_pck_get_sap(pck);
	if (sap==GF_FILTER_SAP_4_PROL) {
		inspect_printf(dump, " sap 4 (prol)");
	} else if (sap) {
		inspect_printf(dump, " sap %u", sap);
	}
	flag = gf_filter_pck_get_interlaced(pck);
	if (flag) inspect_printf(dump, " ilace %u", flag);
	flag = gf_filter_pck_get_corrupted(pck);
	if (flag) inspect_printf(dump, " corr %u", flag);
	flag = gf_filter_pck_get_seek_flag(pck);
	if (flag) inspect_printf(dump, " seek %u", flag);
	flag = gf_filter_pck_get_crypt_flags(pck);
	if (flag) inspect_printf(dump, " crypt %u", flag);
	flag = gf_filter_pck_get_carousel_version(pck);
	if (flag) inspect_printf(dump, " vers %u", flag);

	if (!fifce) inspect_printf(dump, " size %u", size);

	inspect_printf(dump, "\n");
}

static void inspect_dump_packet(GF_InspectCtx *ctx, FILE *dump, GF_FilterPacket *pck, u32 pid_idx, u64 pck_num, PidCtx *pctx)
{
	u32 idx=0, size, sap;
	u64 ts;
	u8 dflags = 0;
	GF_FilterClockType ck_type;
	GF_FilterFrameInterface *fifce=NULL;
	const GF_PropertyValue *p;
	Bool start, end;
	u8 *data;

	if (!ctx->dump_log && !dump) return;

	if (!ctx->deep && !ctx->fmt) return;
	if (!ctx->full) {
		inspect_dump_packet_as_info(ctx, dump, pck, pid_idx, pck_num, pctx);
		return;
	}

	data = (u8 *) gf_filter_pck_get_data(pck, &size);
	gf_filter_pck_get_framing(pck, &start, &end);

	ck_type = ctx->pcr ? gf_filter_pck_get_clock_type(pck) : 0;
	if (!size && !ck_type) {
		fifce = gf_filter_pck_get_frame_interface(pck);
	}

	if (ctx->xml) {
		inspect_printf(dump, "<Packet number=\""LLU"\"", pck_num);
		if (ctx->interleave)
			inspect_printf(dump, " PID=\"%d\"", pid_idx);
	} else {
		inspect_printf(dump, "PID %d PCK "LLU" - ", pid_idx, pck_num);
	}
	if (ck_type) {
		ts = gf_filter_pck_get_cts(pck);
		if (ctx->xml) {
			if (ts==GF_FILTER_NO_TS) inspect_printf(dump, " PCR=\"N/A\"");
			else inspect_printf(dump, " PCR=\""LLU"\" ", ts );
			if (ck_type!=GF_FILTER_CLOCK_PCR) inspect_printf(dump, " discontinuity=\"true\"");
			inspect_printf(dump, "/>\n");
		} else {
			if (ts==GF_FILTER_NO_TS) inspect_printf(dump, " PCR N/A");
			else inspect_printf(dump, " PCR%s "LLU"\n", (ck_type==GF_FILTER_CLOCK_PCR) ? "" : " discontinuity", ts );
		}
		return;
	}
	if (ctx->xml) {
		if (fifce) inspect_printf(dump, " framing=\"interface\"");
		else if (start && end) inspect_printf(dump, " framing=\"complete\"");
		else if (start) inspect_printf(dump, " framing=\"start\"");
		else if (end) inspect_printf(dump, " framing=\"end\"");
		else inspect_printf(dump, " framing=\"continuation\"");
	} else {
		if (fifce) inspect_printf(dump, "interface");
		else if (start && end) inspect_printf(dump, "full frame");
		else if (start) inspect_printf(dump, "frame start");
		else if (end) inspect_printf(dump, "frame end");
		else inspect_printf(dump, "frame continuation");
	}
	ts = gf_filter_pck_get_dts(pck);
	if (ts==GF_FILTER_NO_TS) DUMP_ATT_STR("dts", "N/A")
	else DUMP_ATT_LLU("dts", ts )

	ts = gf_filter_pck_get_cts(pck);
	if (ts==GF_FILTER_NO_TS) DUMP_ATT_STR("cts", "N/A")
	else DUMP_ATT_LLU("cts", ts )

	DUMP_ATT_U("dur", gf_filter_pck_get_duration(pck) )
	sap = gf_filter_pck_get_sap(pck);
	if (sap==GF_FILTER_SAP_4_PROL) {
		DUMP_ATT_STR(sap, "4 (prol)");
	} else {
		DUMP_ATT_U("sap", gf_filter_pck_get_sap(pck) )
	}
	DUMP_ATT_D("ilace", gf_filter_pck_get_interlaced(pck) )
	DUMP_ATT_D("corr", gf_filter_pck_get_corrupted(pck) )
	DUMP_ATT_D("seek", gf_filter_pck_get_seek_flag(pck) )

	ts = gf_filter_pck_get_byte_offset(pck);
	if (ts==GF_FILTER_NO_BO) DUMP_ATT_STR("bo", "N/A")
	else DUMP_ATT_LLU("bo", ts )

	DUMP_ATT_U("roll", gf_filter_pck_get_roll_info(pck) )
	DUMP_ATT_U("crypt", gf_filter_pck_get_crypt_flags(pck) )
	DUMP_ATT_U("vers", gf_filter_pck_get_carousel_version(pck) )

	if (!fifce) {
		DUMP_ATT_U("size", size )
	}
	dflags = gf_filter_pck_get_dependency_flags(pck);
	DUMP_ATT_U("lp", (dflags>>6) & 0x3 )
	DUMP_ATT_U("depo", (dflags>>4) & 0x3 )
	DUMP_ATT_U("depf", (dflags>>2) & 0x3 )
	DUMP_ATT_U("red", (dflags) & 0x3 )

	if (!data) size = 0;
	if (ctx->dump_data) {
		u32 i;
		DUMP_ATT_STR("data", "")
		for (i=0; i<size; i++) {
			inspect_printf(dump, "%02X", (unsigned char) data[i]);
		}
		if (ctx->xml) inspect_printf(dump, "\"");
	} else if (fifce) {
		u32 i;
		char *name = fifce->get_gl_texture ? "Interface_GLTexID" : "Interface_NumPlanes";
		if (ctx->xml) {
			inspect_printf(dump, " %s=\"", name);
		} else {
			inspect_printf(dump, " %s ", name);
		}
		for (i=0; i<4; i++) {
			if (fifce->get_gl_texture) {
				GF_Matrix mx;
				gf_mx_init(mx);
				u32 gl_tex_format, gl_tex_id;
				if (fifce->get_gl_texture(fifce, i, &gl_tex_format, &gl_tex_id, &mx) != GF_OK)
					break;
				if (i) inspect_printf(dump, ",");
				inspect_printf(dump, "%d", gl_tex_id);
			} else {
				u32 stride;
				const u8 *plane;
				if (fifce->get_plane(fifce, i, &plane, &stride) != GF_OK)
					break;
			}
		}
		if (!fifce->get_gl_texture) {
			inspect_printf(dump, "%d", i);
		}

		if (ctx->xml) {
			inspect_printf(dump, "\"");
		}
	} else if (data && (ctx->test!=INSPECT_TEST_NOCRC) ) {
		DUMP_ATT_X("CRC32", gf_crc_32(data, size) )
	}
	if (ctx->xml) {
		if (!ctx->props)
			goto props_done;
	} else {
		inspect_printf(dump, "\n");
	}

	if (!ctx->props) return;
	while (1) {
		u32 prop_4cc;
		const char *prop_name;
		const GF_PropertyValue * p = gf_filter_pck_enum_properties(pck, &idx, &prop_4cc, &prop_name);
		if (!p) break;
		if (idx==0) inspect_printf(dump, "properties:\n");

		//SCTE35 requires a specific description (e.g. a XML Element): process after Packet is fully described
		if (!prop_4cc && !strncmp(prop_name, "scte35", 6)) continue;

		inspect_dump_property(ctx, dump, prop_4cc, prop_name, p, pctx);
	}


props_done:
	if (!ctx->analyze || !size) {
		if (ctx->xml) {
			inspect_printf(dump, "/>\n");
		}
		return;
	}
	inspect_printf(dump, ">\n");

	// special props requiring specific description (e.g. a XML Element)
	idx=0;
	while (1) {
		u32 prop_4cc;
		const char *prop_name;
		p = gf_filter_pck_enum_properties(pck, &idx, &prop_4cc, &prop_name);
		if (!p) break;
		if (prop_4cc || strncmp(prop_name, "scte35", 6)) continue;

		inspect_dump_property(ctx, dump, prop_4cc, prop_name, p, pctx);
	}

#ifndef GPAC_DISABLE_AV_PARSERS
	if (pctx->hevc_state || pctx->avc_state || pctx->vvc_state) {
		idx=1;

		if (pctx->is_adobe_protected) {
			u8 encrypted_au = data[0];
			if (encrypted_au) {
				inspect_printf(dump, "   <!-- Packet is an Adobe's protected frame and can not be dumped -->\n");
				inspect_printf(dump, "</Packet>\n");
				return;
			}
			else {
				data++;
				size--;
			}
		}
		u32 sample_offset=0;
		u8 *sai_buffer=NULL;
		u32 sai_buffer_size=0;
		p = gf_filter_pck_get_property(pck, GF_PROP_PCK_CENC_SAI);
		if (p) {
			sai_buffer = p->value.data.ptr;
			sai_buffer_size = p->value.data.size;
		}

		while (size && pctx->nalu_size_length) {
			if (size < pctx->nalu_size_length) {
				inspect_printf(dump, "   <!-- NALU is corrupted: nalu_size_length is %u but only %d remains -->\n", pctx->nalu_size_length, size);
				break;
			}
			u32 nal_size = inspect_get_nal_size((char*)data, pctx->nalu_size_length);
			data += pctx->nalu_size_length;

			if (nal_size >= GF_UINT_MAX - pctx->nalu_size_length || pctx->nalu_size_length + nal_size > size) {
				inspect_printf(dump, "   <!-- NALU is corrupted: size is %u but only %d remains -->\n", nal_size, size);
				break;
			} else {
				inspect_printf(dump, "   <NALU size=\"%d\" ", nal_size);
				gf_inspect_dump_nalu_internal(dump, data, nal_size, pctx->has_svcc ? 1 : 0, pctx->hevc_state, pctx->avc_state, pctx->vvc_state, pctx->nalu_size_length, ctx->crc, pctx->is_cenc_protected, ctx->analyze, pctx, sai_buffer, sai_buffer_size, sample_offset);
			}
			idx++;
			data += nal_size;
			size -= nal_size + pctx->nalu_size_length;
			sample_offset += nal_size + pctx->nalu_size_length;
		}
	} else if (pctx->av1_state) {
		u32 sample_offset=0;
		gf_bs_reassign_buffer(pctx->bs, data, size);
		u8 *sai_buffer=NULL;
		u32 sai_buffer_size=0;
		p = gf_filter_pck_get_property(pck, GF_PROP_PCK_CENC_SAI);
		if (p) {
			sai_buffer = p->value.data.ptr;
			sai_buffer_size = p->value.data.size;
		}

		while (size) {
			ObuType obu_type = 0;
			u64 obu_size = 0;
			u32 hdr_size = 0;

			obu_size = gf_inspect_dump_obu_internal(dump, pctx->av1_state, (char *) data, size, obu_type, obu_size, hdr_size, ctx->crc, pctx, ctx->analyze, sai_buffer, sai_buffer_size, sample_offset);

			if (obu_size > size) {
				inspect_printf(dump, "   <!-- OBU is corrupted: size is %d but only %d remains -->\n", (u32) obu_size, size);
				break;
			}
			if (!obu_size) {
				inspect_printf(dump, "   <!-- OBU is corrupted: size 0 -->\n", (u32) obu_size, size);
				break;
			}
			data += obu_size;
			size -= (u32)obu_size;
			idx++;
			sample_offset += obu_size;
		}
	} else {
		u32 hdr, pos, fsize, i;
		u32 dflag=0;
		switch (pctx->codec_id) {
		case GF_CODECID_MPEG1:
		case GF_CODECID_MPEG2_422:
		case GF_CODECID_MPEG2_SNR:
		case GF_CODECID_MPEG2_HIGH:
		case GF_CODECID_MPEG2_MAIN:
		case GF_CODECID_MPEG2_SIMPLE:
		case GF_CODECID_MPEG2_SPATIAL:
		case GF_CODECID_MPEG4_PART2:
			inspect_dump_mpeg124(pctx, (char *) data, size, dump);
			break;
		case GF_CODECID_MPEG_AUDIO:
		case GF_CODECID_MPEG2_PART3:
		case GF_CODECID_MPEG_AUDIO_L1:
			pos = 0;
			while (size) {
				hdr = gf_mp3_get_next_header_mem(data, size, &pos);
				if (!hdr) {
					inspect_printf(dump, "<!-- Invalid MPEGAudioFrame -->\n");
					break;
				}
				fsize = gf_mp3_frame_size(hdr);
				inspect_printf(dump, "<MPEGAudioFrame size=\"%d\" layer=\"%d\" version=\"%d\" bitrate=\"%d\" channels=\"%d\" samplesPerFrame=\"%d\" samplerate=\"%d\"/>\n", fsize, gf_mp3_layer(hdr), gf_mp3_version(hdr), gf_mp3_bit_rate(hdr), gf_mp3_num_channels(hdr), gf_mp3_window_size(hdr), gf_mp3_sampling_rate(hdr));
				if (size<pos+fsize) break;
				data += pos + fsize;
				size -= pos + fsize;
			}
			break;
		case GF_CODECID_TMCD:
			inspect_dump_tmcd(ctx, pctx, (char *) data, size, dump);
			break;
		case GF_CODECID_EVTE:
			inspect_dump_boxes(ctx, pctx, (char *) data, size, dump);
			break;
		case GF_CODECID_SCTE35:
		{
			GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ);
			inspect_printf(dump, " <SCTE35>\n");
			scte35_dump(ctx, dump, bs);
			inspect_printf(dump, " </SCTE35>\n");
			gf_bs_del(bs);
		}
			break;
		case GF_CODECID_TX3G:
			data += 2;
			size -= 2;
		case GF_CODECID_SUBS_TEXT:
		case GF_CODECID_META_TEXT:
		case GF_CODECID_SIMPLE_TEXT:
			dflag=1;
		case GF_CODECID_SUBS_XML:
		case GF_CODECID_META_XML:
			if (dflag)
				inspect_printf(dump, "<![CDATA[");
			for (i=0; i<size; i++) {
				gf_fputc(data[i], dump);
			}
			if (dflag)
				inspect_printf(dump, "]]>\n");
			break;
		case GF_CODECID_WEBVTT:
		{
			GF_List *cues = gf_webvtt_parse_cues_from_data(data, size, 0, 0);
			while (gf_list_count(cues)) {
				GF_WebVTTCue *cue = gf_list_pop_front(cues);
				if (cue->pre_text) gf_fprintf(dump, "%s\n\n", cue->pre_text);
				if (cue->id) gf_fprintf(dump, "%s\n", cue->id);
				if (cue->settings) gf_fprintf(dump, " %s", cue->settings);
				if (cue->text) gf_fprintf(dump, "%s", cue->text);
				gf_fprintf(dump, "\n");
				if (cue->post_text) gf_fprintf(dump, "%s\n\n", cue->post_text);

				gf_webvtt_cue_del(cue);
			}
			gf_list_del(cues);
		}
			break;
		case GF_CODECID_APCH:
		case GF_CODECID_APCO:
		case GF_CODECID_APCN:
		case GF_CODECID_APCS:
		case GF_CODECID_AP4X:
		case GF_CODECID_AP4H:
			gf_inspect_dump_prores_internal(dump, (char *) data, size, ctx->crc, pctx);
			break;

		case GF_CODECID_MPHA:
			gf_bs_reassign_buffer(pctx->bs, data, size);
			gf_inspect_dump_mha_frame(dump, pctx->bs, "");
			break;

		case GF_CODECID_MHAS:
			gf_inspect_dump_mhas(dump, (char *) data, size, ctx->crc, pctx);
			break;
		case GF_CODECID_VP8:
			dflag=1;
		case GF_CODECID_VP9:
			inspect_dump_vpx(ctx, dump, (char *) data, size, ctx->crc, pctx, dflag ? 8 : 9);
			break;
		case GF_CODECID_AC3:
			dflag=1;
		case GF_CODECID_EAC3:
			inspect_dump_ac3_eac3(ctx, dump, (char *) data, size, ctx->crc, pctx, dflag ? 0 : 1);
			break;
		case GF_CODECID_TRUEHD:
			gf_bs_reassign_buffer(pctx->bs, data, size);
			gf_inspect_dump_truehd_frame(dump, pctx->bs);
			break;
		case GF_CODECID_OPUS:
			gf_inspect_dump_opus_internal(dump, data, size, 0, ctx->crc, pctx);
			break;
		case GF_CODECID_ALAC:
		{
			gf_bs_reassign_buffer(pctx->bs, data, size);
			u32 val, partial;

#define get_and_print(name, bits) \
			val = gf_bs_read_int(pctx->bs, bits); \
			inspect_printf(dump, " "name"=\"%u\"", val);

			inspect_printf(dump, " <ALACSegment");

			get_and_print("type", 3);
			get_and_print("reserved", 12);
			get_and_print("partial", 1);
			partial=val;
			get_and_print("shift_off", 2);
			get_and_print("escape", 1);
			if (partial) {
				get_and_print("frameLength", 32);
			}
			inspect_printf(dump, "/>\n");

#undef get_and_print

		}
			break;

		}
	}
#endif
	inspect_printf(dump, "</Packet>\n");
}

#define DUMP_ARRAY(arr, name, loc, _is_svc)\
	if (arr && gf_list_count(arr)) {\
		inspect_printf(dump, "  <%sArray location=\"%s\">\n", name, loc);\
		for (i=0; i<gf_list_count(arr); i++) {\
			slc = gf_list_get(arr, i);\
			inspect_printf(dump, "   <NALU size=\"%d\" ", slc->size);\
			gf_inspect_dump_nalu_internal(dump, slc->data, slc->size, _is_svc, pctx->hevc_state, pctx->avc_state, pctx->vvc_state, nalh_size, ctx->crc, GF_FALSE, ctx->analyze, pctx, NULL, 0, 0);\
		}\
		inspect_printf(dump, "  </%sArray>\n", name);\
	}\


#ifndef GPAC_DISABLE_AV_PARSERS
static void inspect_reset_parsers(PidCtx *pctx, void *keep_parser_address)
{
	if ((&pctx->hevc_state != keep_parser_address) && pctx->hevc_state) {
		gf_free(pctx->hevc_state);
		pctx->hevc_state = NULL;
	}
	if ((&pctx->avc_state != keep_parser_address) && pctx->avc_state) {
		gf_free(pctx->avc_state);
		pctx->avc_state = NULL;
	}
	if ((&pctx->vvc_state != keep_parser_address) && pctx->vvc_state) {
		gf_free(pctx->vvc_state);
		pctx->vvc_state = NULL;
	}
	if ((&pctx->av1_state != keep_parser_address) && pctx->av1_state) {
		if (pctx->av1_state->config) gf_odf_av1_cfg_del(pctx->av1_state->config);
		gf_free(pctx->av1_state);
		pctx->av1_state = NULL;
	}
	if ((&pctx->mv124_state != keep_parser_address) && pctx->mv124_state) {
		gf_m4v_parser_del(pctx->mv124_state);
		pctx->mv124_state = NULL;
	}
	if (pctx->vpcc) gf_odf_vp_cfg_del(pctx->vpcc);
}
#endif

static void format_duration(s64 dur, u64 timescale, FILE *dump, Bool skip_name)
{
	const char *name = "duration";
	if (dur==-1) {
		inspect_printf(dump, " duration unknown");
		return;
	}
	//duration probing was disabled
	if (!timescale)
		return;
	if (dur<0) {
		dur = -dur;
		name = "estimated duration";
	}

	char szDur[100];
	gf_format_duration(dur, (u32) timescale, szDur);
	if (skip_name)
		inspect_printf(dump, " (");
	else
		inspect_printf(dump, " %s ", name);

	inspect_printf(dump, "%s", szDur);

	if (skip_name)
		inspect_printf(dump, ")");
}


static void inspect_dump_pid_as_info(GF_InspectCtx *ctx, FILE *dump, GF_FilterPid *pid, u32 pid_idx, Bool is_connect, Bool is_remove, u64 pck_for_config, Bool is_info, PidCtx *pctx)
{
	char szCodec[RFC6381_CODEC_NAME_SIZE_MAX];
	const GF_PropertyValue *p, *dsi, *dsi_enh, *sr;
	Bool is_raw=GF_FALSE;
	Bool is_protected=GF_FALSE;
	Bool is_unknown=GF_FALSE;
	u32 codec_id=0;

	if (!ctx->dump_log && !dump) return;

	szCodec[0] = 0;
	gf_filter_pid_get_rfc_6381_codec_string(pid, szCodec, GF_FALSE, GF_FALSE, NULL, NULL);

	inspect_printf(dump, "PID");
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_ID);
	if (!p) p = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID);
	if (p) {
		if (!gf_sys_is_test_mode())
			inspect_printf(dump, " %u ID %d", pid_idx, p->value.uint);
		else
			inspect_printf(dump, " %d", p->value.uint);
	}

	if (is_remove) {
		inspect_printf(dump, " removed\n");
		return;
	}
	if (!is_connect) {
		inspect_printf(dump, " reconfigure");
	}
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
	if (p) {
		if (p->value.uint==GF_STREAM_ENCRYPTED) {
			is_protected = GF_TRUE;
			p = gf_filter_pid_get_property(pid, GF_PROP_PID_ORIG_STREAM_TYPE);
		}
		if (p) {
			inspect_printf(dump, " %s", gf_stream_type_short_name(p->value.uint));
			if (p->value.uint==GF_STREAM_UNKNOWN) is_unknown = GF_TRUE;
		}
	}
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID);
	if (p) {
		GF_PropertyEntry *pe=NULL;
		inspect_printf(dump, " service %d", p->value.uint);
		p = gf_filter_pid_get_info(pid, GF_PROP_PID_SERVICE_NAME, &pe);
		if (p && p->value.string && p->value.string[0]) inspect_printf(dump, " \"%s\"", p->value.string);
		p = gf_filter_pid_get_info(pid, GF_PROP_PID_SERVICE_PROVIDER, &pe);
		if (p && p->value.string && p->value.string[0]) inspect_printf(dump, " (%s)", p->value.string);
		gf_filter_release_property(pe);
	}

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLED);
	if (p && p->value.boolean) inspect_printf(dump, " disabled");

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_TRACK_FLAGS);
	if (p && p->value.uint) {
		if (p->value.uint & (1<<1)) inspect_printf(dump, " inMovie");
		if (p->value.uint & (1<<2)) inspect_printf(dump, " inPreview");
	}

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_LANGUAGE);
	if (p && stricmp(p->value.string, "und")) inspect_printf(dump, " language \"%s\"", p->value.string);

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION);
	if (p) {
		sr = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION_AVG);
		if (sr && sr->value.boolean) inspect_printf(dump, " estimated");
		format_duration((s64) p->value.lfrac.num, (u32) p->value.lfrac.den, dump, GF_FALSE);
	}

	sr = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE);
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE);
	if (p && (!sr || (sr->value.uint != p->value.uint)))
		inspect_printf(dump, " timescale %d", p->value.uint);

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY);
	if (p) inspect_printf(dump, " delay "LLD, p->value.longsint);

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
	if (p) {
		if (p->value.uint==GF_CODECID_RAW) {
			is_raw = GF_TRUE;
			inspect_printf(dump, " raw");
		} else {
			codec_id = p->value.uint;
			//fprintf(dump, " codec \"%s\"", gf_codecid_name(codec_id));
		}
	}

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
	if (p) {
		inspect_printf(dump, " %dx", p->value.uint);
		p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
		inspect_printf(dump, "%d", p ? p->value.uint  : 0);
		p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS);
		if (p) {
			u32 tscale = p->value.frac.num;
			u32 sdur = p->value.frac.den;
			gf_media_get_reduced_frame_rate(&tscale, &sdur);
			inspect_printf(dump, " fps %d", tscale);
			if (sdur!=1) inspect_printf(dump, "/%u", sdur);
		}

		p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR);
		if (p && (p->value.frac.num>0) && p->value.frac.num!=p->value.frac.den)
			inspect_printf(dump, " SAR %d/%u", p->value.frac.num, p->value.frac.den);
		else
			inspect_printf(dump, " SAR 1/1");
		if (is_raw) {
			p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT);
			if (p) inspect_printf(dump, " raw format %s", gf_pixel_fmt_name(p->value.uint) );
		}
	}

	if (sr) {
		inspect_printf(dump, " %d Hz", sr->value.uint);
		p = gf_filter_pid_get_property(pid, GF_PROP_PID_CHANNEL_LAYOUT);
		if (p) {
			inspect_printf(dump, " %s", gf_audio_fmt_get_layout_name(p->value.longuint));
		} else {
			p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS);
			if (p) inspect_printf(dump, " %d chan", p->value.uint);
		}
		if (is_raw) {
			p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT);
			if (p) inspect_printf(dump, " raw format %s", gf_audio_fmt_name(p->value.uint) );
		}
	}

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_BITRATE);
	if (p) {
		if (p->value.uint<5000) inspect_printf(dump, " %d bps", p->value.uint );
		else inspect_printf(dump, " %d kbps", p->value.uint/1000 );
	}
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_NB_FRAMES);
	if (p && (p->value.uint>1)) inspect_printf(dump, " %d frames", p->value.uint);


	if (is_protected) {
		p = gf_filter_pid_get_property(pid, GF_PROP_PID_PROTECTION_SCHEME_TYPE);
		inspect_printf(dump, " encryption %s", p ? gf_4cc_to_str(p->value.uint) : "unknown");
		p = gf_filter_pid_get_property(pid, GF_PROP_PID_CENC_PATTERN);
		if (p)
			inspect_printf(dump, " %d/%d pattern", p->value.frac.num, p->value.frac.den);
	}

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_ROLE);
	if (p && p->value.string_list.nb_items) {
		char *urn_sep = strrchr(p->value.string_list.vals[0], ':');
		if (!urn_sep) urn_sep = p->value.string_list.vals[0];
		inspect_printf(dump, " role %s", urn_sep);
	}

	dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG);
	dsi_enh = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT);
	if (is_raw) {
		inspect_printf(dump, "\n");
		return;
	}

	if (codec_id) {
		inspect_printf(dump, " codec");
		if (szCodec[0] && strcmp(szCodec, "unkn")) {
			inspect_printf(dump, " %s", szCodec);
		} else {
			if ((codec_id & 0xFFFFFF00) == GF_4CC('M','2','T', 0)) {
				inspect_printf(dump, " m2ts_type 0x%02X", (codec_id&0xFF));
				codec_id = 0;
			}
		}
	}

#ifndef GPAC_DISABLE_AV_PARSERS
	if ((codec_id==GF_CODECID_HEVC) || (codec_id==GF_CODECID_LHVC) || (codec_id==GF_CODECID_HEVC_TILES)) {
		u32 i, j, k;
		HEVCState *hvcs = NULL;
		GF_HEVCConfig *hvcc=NULL;
		if (dsi) {
			hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, (codec_id==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE);
			if (dsi_enh) {
				GF_SAFEALLOC(hvcs, HEVCState);
				for (i=0; i<gf_list_count(hvcc->param_array); i++) {
					GF_NALUFFParamArray *pa = gf_list_get(hvcc->param_array, i);
					for (j=0; j<gf_list_count(pa->nalus); j++) {
						GF_NALUFFParam *sl = gf_list_get(pa->nalus, j);
						u8 nut, lid, tid;
						gf_hevc_parse_nalu(sl->data, sl->size, hvcs, &nut, &tid, &lid);
					}
				}
			}
		} else if (dsi_enh) {
			hvcc = gf_odf_hevc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size, GF_TRUE);
		}
		inspect_printf(dump, " %s", (codec_id==GF_CODECID_LHVC) ? "L-HEVC" : "HEVC");
		if (codec_id==GF_CODECID_HEVC_TILES) inspect_printf(dump, " tile");

		if (hvcc) {
			if (hvcc->interlaced_source_flag)
				inspect_printf(dump, " interlaced");
			if (!hvcc->is_lhvc) {
				inspect_printf(dump, " PL %s@%g %s %d bpp compatibility 0x%08X", gf_hevc_get_profile_name(hvcc->profile_idc), ((Double)hvcc->level_idc) / 30.0, gf_avc_hevc_get_chroma_format_name(hvcc->chromaFormat), hvcc->luma_bit_depth, hvcc->general_profile_compatibility_flags);
			}
			gf_odf_hevc_cfg_del(hvcc);
		}
		if (dsi && dsi_enh) {
			hvcc = gf_odf_hevc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size, GF_TRUE);
			if (hvcc) {
				inspect_printf(dump, " scalable");
				for (k=0; k<2; k++) {
				u32 ntype = k ? GF_HEVC_NALU_SEQ_PARAM : GF_HEVC_NALU_VID_PARAM;
				for (i=0; i<gf_list_count(hvcc->param_array); i++) {
					GF_NALUFFParamArray *pa = gf_list_get(hvcc->param_array, i);
					if (pa->type!=ntype) continue;
					for (j=0; j<gf_list_count(pa->nalus); j++) {
						GF_NALUFFParam *sl = gf_list_get(pa->nalus, j);
						u8 nut, lid, tid;
						s32 idx = gf_hevc_parse_nalu(sl->data, sl->size, hvcs, &nut, &tid, &lid);
						if ((idx>=0) && (pa->type==GF_HEVC_NALU_SEQ_PARAM)) {
							idx = hvcs->last_parsed_sps_id;
							inspect_printf(dump, " %dx%d", hvcs->sps[idx].width, hvcs->sps[idx].height);
						}
					}
				}
				}
				gf_odf_hevc_cfg_del(hvcc);
			}
		}
		if (hvcs) gf_free(hvcs);
	}
	else if ((codec_id==GF_CODECID_AVC) || (codec_id==GF_CODECID_SVC) || (codec_id==GF_CODECID_MVC)) {
		GF_AVCConfig *avcc=NULL;
		if (dsi) {
			avcc = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size);
		} else if (dsi_enh) {
			avcc = gf_odf_avc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size);
		}
		if (codec_id==GF_CODECID_AVC) inspect_printf(dump, " %s", "AVC|H264");
		else if (codec_id==GF_CODECID_SVC) inspect_printf(dump, " %s", "SVC");
		else if (codec_id==GF_CODECID_MVC) inspect_printf(dump, " %s", "MVC");

		if (avcc) {
			inspect_printf(dump, " PL %s@%g %s %d bpp", gf_avc_get_profile_name(avcc->AVCProfileIndication), ((Double)avcc->AVCLevelIndication)/10.0, gf_avc_hevc_get_chroma_format_name(avcc->chroma_format), avcc->luma_bit_depth );
			gf_odf_avc_cfg_del(avcc);
		}
		if (dsi && dsi_enh) {
			inspect_printf(dump, " scalable");
			avcc = gf_odf_avc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size);
			if (avcc) {
				AVCState *avcs;
				GF_SAFEALLOC(avcs, AVCState);
				for (u32 i=0; i<gf_list_count(avcc->sequenceParameterSets); i++) {
					GF_NALUFFParam *sl = gf_list_get(avcc->sequenceParameterSets, i);
					s32 idx = gf_avc_read_sps(sl->data, sl->size, avcs, 0, NULL);
					if (idx>=0) inspect_printf(dump, " %dx%d", avcs->sps[idx].width, avcs->sps[idx].height);
				}
				gf_free(avcs);
				gf_odf_avc_cfg_del(avcc);
			}
		}
	}
	else if ((codec_id==GF_CODECID_VVC) || (codec_id==GF_CODECID_VVC_SUBPIC)) {
		GF_VVCConfig *vvcc=NULL;
		if (dsi) {
			vvcc = gf_odf_vvc_cfg_read(dsi->value.data.ptr, dsi->value.data.size);
		} else if (dsi_enh) {
			vvcc = gf_odf_vvc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size);
		}
		if (codec_id==GF_CODECID_VVC) inspect_printf(dump, " VVC%s", (codec_id==GF_CODECID_VVC) ? "" : " subpictures");

		if (vvcc) {
			inspect_printf(dump, " PL %s@%.1g %s %d bpp", gf_vvc_get_profile_name(vvcc->general_profile_idc), ((Double)vvcc->general_level_idc)/16.0, gf_avc_hevc_get_chroma_format_name(vvcc->chroma_format), vvcc->bit_depth );
			if (vvcc->numTemporalLayers>1)
			inspect_printf(dump, " %d sublayers", vvcc->numTemporalLayers);

			gf_odf_vvc_cfg_del(vvcc);
		}
	}
	else if (codec_id==GF_CODECID_AV1) {
		GF_AV1Config *av1c = NULL;
		if (dsi)
			av1c = gf_odf_av1_cfg_read(dsi->value.data.ptr, dsi->value.data.size);
		inspect_printf(dump, " AV1");
		if (av1c) {
			inspect_printf(dump, " PL %d@%d", av1c->seq_profile, av1c->seq_level_idx_0);
			if (av1c->chroma_subsampling_x) {
				if (av1c->chroma_subsampling_y) inspect_printf(dump, " YUV 4:2:0");
				else inspect_printf(dump, " YUV 4:2:2");
			} else inspect_printf(dump, " YUV 4:4:4");
			if (av1c->monochrome) inspect_printf(dump, " monochrome");
			if (av1c->twelve_bit) inspect_printf(dump, " 12 bpp");

			gf_odf_av1_cfg_del(av1c);
		}
	}
	else if ((codec_id==GF_CODECID_AAC_MPEG4) || (codec_id==GF_CODECID_AAC_MPEG2_MP) || (codec_id==GF_CODECID_AAC_MPEG2_LCP) || (codec_id==GF_CODECID_AAC_MPEG2_SSRP)) {
		if (dsi) {
			const char *name, *sep;
			GF_M4ADecSpecInfo a_cfg;
			gf_m4a_get_config(dsi->value.data.ptr, dsi->value.data.size, &a_cfg);
			char *signaling = "implicit";
			char *heaac = "";
			if ((codec_id==GF_CODECID_AAC_MPEG4) && a_cfg.has_sbr) {
				if (a_cfg.has_ps) heaac = " HEAACv2";
				else heaac = " HEAACv1";
			}
			if (a_cfg.base_object_type==2) {
				if (a_cfg.has_ps || a_cfg.has_sbr)
					signaling = "backward compatible";
			} else {
				signaling = "hierarchical";
			}
			name = gf_m4a_object_type_name(a_cfg.base_object_type);
			sep = strstr(name, "Audio");
			if (sep) name = sep+6;

			inspect_printf(dump, " %s (aot=%d %s)%s", name, a_cfg.base_object_type, signaling, heaac);
			if (a_cfg.has_sbr) inspect_printf(dump, " SBR %d Hz aot %s", a_cfg.sbr_sr, gf_m4a_object_type_name(a_cfg.sbr_object_type));
			if (a_cfg.has_ps) inspect_printf(dump, " PS");
		} else {
			inspect_printf(dump, " AAC");
		}
	} else if (codec_id==GF_CODECID_MPEG4_PART2) {
		inspect_printf(dump, " MPEG-4 Visual");
		if (dsi) {
			GF_M4VDecSpecInfo vcfg;
			gf_m4v_get_config(dsi->value.data.ptr, dsi->value.data.size, &vcfg);
			inspect_printf(dump, " PL %s", gf_m4v_get_profile_name(vcfg.VideoPL));
			if (vcfg.chroma_fmt) inspect_printf(dump, " %s", gf_avc_hevc_get_chroma_format_name(vcfg.chroma_fmt) );
			if (!vcfg.progresive) inspect_printf(dump, " interlaced");
		}
	} else
#endif//GPAC_DISABLE_AV_PARSERS
	{
		if (codec_id==GF_CODECID_FFMPEG) {
			p = gf_filter_pid_get_property(pid, GF_PROP_PID_META_DEMUX_CODEC_NAME);
			if (p) {
				inspect_printf(dump, " %s", p->value.string);
			} else {
				p = gf_filter_pid_get_property(pid, GF_PROP_PID_META_DEMUX_CODEC_ID);
				if (p && (p->type==GF_PROP_UINT)) codec_id = p->value.uint;
				inspect_printf(dump, " FFmpeg %d", codec_id);
			}
		} else if (codec_id) {
			inspect_printf(dump, " %s", gf_codecid_name(codec_id));
		}
		p = gf_filter_pid_get_property(pid, GF_PROP_PID_PROFILE_LEVEL);
		if (p) inspect_printf(dump, " PL %d", p->value.uint);

		p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_CHROMAFMT);
		if (p) inspect_printf(dump, " %s", gf_avc_hevc_get_chroma_format_name(p->value.uint) );

		p = gf_filter_pid_get_property(pid, GF_PROP_PID_INTERLACED);
		if (p && p->value.boolean) inspect_printf(dump, " interlaced");
	}

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_CLAP_W);
	u32 clap_w = p ? p->value.uint : 0;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_CLAP_H);
	u32 clap_h = p ? p->value.uint : 0;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_CLAP_X);
	s32 clap_x = p ? p->value.sint : 0;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_CLAP_Y);
	s32 clap_y = p ? p->value.sint : 0;
	if (clap_w && clap_h) {
		inspect_printf(dump, " clap %dx%d@%ux%u", clap_x, clap_y, clap_w, clap_h);
	}
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_DOLBY_VISION);
	if (p) {
		GF_BitStream *bs = gf_bs_new(p->value.data.ptr, p->value.data.size, GF_BITSTREAM_READ);
		GF_DOVIDecoderConfigurationRecord *dovi = gf_odf_dovi_cfg_read_bs(bs);
		gf_bs_del(bs);
		if (dovi) {
			inspect_printf(dump, " DolbyVision %d.%d PL %d@%d",
				dovi->dv_version_major, dovi->dv_version_minor, dovi->dv_profile, dovi->dv_level);
			if (dovi->rpu_present_flag) inspect_printf(dump, " rpu");
			if (dovi->bl_present_flag) inspect_printf(dump, " base");
			if (dovi->el_present_flag) inspect_printf(dump, " el");
			if (dovi->dv_bl_signal_compatibility_id) inspect_printf(dump, " compatID %d", dovi->dv_bl_signal_compatibility_id);
			gf_odf_dovi_cfg_del(dovi);
		}
	}

	if (dsi && (codec_id==GF_CODECID_EAC3)) {
		GF_AC3Config ac3cfg;
		gf_odf_ac3_config_parse(dsi->value.data.ptr, dsi->value.data.size, GF_TRUE, &ac3cfg);
		if (ac3cfg.atmos_ec3_ext)
			inspect_printf(dump, " Atmos (CIT %d)", ac3cfg.complexity_index_type);
	}

	if (pctx->is_fake)
		inspect_printf(dump, (is_unknown || is_protected) ? " ignored" : " fake");

	inspect_printf(dump, "\n");
}

static void inspect_dump_pid(GF_InspectCtx *ctx, FILE *dump, GF_FilterPid *pid, u32 pid_idx, Bool is_connect, Bool is_remove, u64 pck_for_config, Bool is_info, PidCtx *pctx)
{
	u32 idx=0, nalh_size, i;
#ifndef GPAC_DISABLE_AV_PARSERS
	GF_NALUFFParam *slc;
#endif
	GF_AVCConfig *avcc, *svcc;
	GF_HEVCConfig *hvcc, *lhcc;
	GF_VVCConfig *vvcC;
	Bool is_enh = GF_FALSE;
	Bool is_svc=GF_FALSE;
	char *elt_name = NULL;
	const GF_PropertyValue *p, *dsi, *dsi_enh;

	if (ctx->test==INSPECT_TEST_NOPROP) return;
	if (!ctx->dump_log && !dump) return;
	if (!pid) return;

	if (ctx->stats) {
		char szCodec[RFC6381_CODEC_NAME_SIZE_MAX];
		if (gf_filter_pid_get_rfc_6381_codec_string(pid, szCodec, GF_FALSE, GF_FALSE, NULL, NULL)==GF_OK) {
			if (!pctx->stat_codecs || !strstr(pctx->stat_codecs, szCodec))
				gf_dynstrcat(&pctx->stat_codecs, szCodec, ",");
		}
		pctx->nb_recfg++;
		return;
	}

	if (!ctx->full) {
		inspect_dump_pid_as_info(ctx, dump, pid, pid_idx, is_connect, is_remove, pck_for_config, is_info, pctx);
		return;
	}

	//disconnect of src pid (not yet supported)
	if (ctx->xml) {
		if (is_info) {
			elt_name = "PIDInfoUpdate";
		} else if (is_remove) {
			elt_name = "PIDRemove";
		} else {
			elt_name = is_connect ? "PIDConfigure" : "PIDReconfigure";
		}
		inspect_printf(dump, "<%s PID=\"%d\" name=\"%s\"",elt_name, pid_idx, gf_filter_pid_get_name(pid) );

		if (pck_for_config)
			inspect_printf(dump, " packetsSinceLastConfig=\""LLU"\"", pck_for_config);
	} else {
		if (is_info) {
			inspect_printf(dump, "PID %d name %s info update\n", pid_idx, gf_filter_pid_get_name(pid) );
		} else if (is_remove) {
			inspect_printf(dump, "PID %d name %s delete\n", pid_idx, gf_filter_pid_get_name(pid) );
		} else {
			inspect_printf(dump, "PID %d name %s %sonfigure", pid_idx, gf_filter_pid_get_name(pid), is_connect ? "C" : "Rec" );
			if (pck_for_config)
				inspect_printf(dump, " after "LLU" packets", pck_for_config);
			inspect_printf(dump, " - properties:\n");
		}
	}

	if (!is_info) {
		while (1) {
			u32 prop_4cc;
			const char *prop_name;
			p = gf_filter_pid_enum_properties(pid, &idx, &prop_4cc, &prop_name);
			if (!p) break;
			inspect_dump_property(ctx, dump, prop_4cc, prop_name, p, pctx);

			if (prop_name) {
				if (!strcmp(prop_name, "tmcd:flags"))
					pctx->tmcd_flags = p->value.uint;
				else if (!strcmp(prop_name, "tmcd:framerate"))
					pctx->tmcd_rate = p->value.frac;
				else if (!strcmp(prop_name, "tmcd:frames_per_tick"))
					pctx->tmcd_fpt = p->value.uint;

			}
		}

		p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODEC);
		if (!p &&!gf_sys_is_test_mode()) {
			char szCodec[RFC6381_CODEC_NAME_SIZE_MAX];
			szCodec[0] = 0;
			if (gf_filter_pid_get_rfc_6381_codec_string(pid, szCodec, GF_FALSE, GF_FALSE, NULL, NULL)==GF_OK) {
				GF_PropertyValue _p;
				_p.type = GF_PROP_STRING;
				_p.value.string = szCodec;
				inspect_dump_property(ctx, dump, GF_PROP_PID_CODEC, NULL, &_p, pctx);
			}
		}
	} else {
		while (ctx->info) {
			u32 prop_4cc;
			const char *prop_name;
			p = gf_filter_pid_enum_info(pid, &idx, &prop_4cc, &prop_name);
			if (!p) break;
			inspect_dump_property(ctx, dump, prop_4cc, prop_name, p, pctx);
		}
	}

	if (!ctx->analyze) {
		if (ctx->xml) {
			inspect_printf(dump, "/>\n");
		}
		return;
	}

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
	if (!p) {
		inspect_printf(dump, "/>\n");
		return;
	}
	pctx->codec_id = p->value.uint;

	dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG);
	dsi_enh = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT);

	avcc = NULL;
	svcc = NULL;
	hvcc = NULL;
	lhcc = NULL;
	vvcC = NULL;
	Bool dsi_is_text=GF_FALSE;
	pctx->has_svcc = 0;

	switch (pctx->codec_id) {
	case GF_CODECID_SVC:
	case GF_CODECID_MVC:
		if (!dsi) is_enh = GF_TRUE;
	case GF_CODECID_AVC:
	case GF_CODECID_AVC_PS:
#ifndef GPAC_DISABLE_AV_PARSERS
		inspect_reset_parsers(pctx, &pctx->avc_state);

		if (!pctx->avc_state) {
			GF_SAFEALLOC(pctx->avc_state, AVCState);
			if (!pctx->avc_state) return;
		}
#endif
		if (!dsi && !dsi_enh) {
			inspect_printf(dump, "/>\n");
			return;
		}
		inspect_printf(dump, ">\n");
		inspect_printf(dump, "<AVCParameterSets>\n");
		if (dsi) {
			if (is_enh) {
				svcc = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size);
				if (svcc)
					pctx->nalu_size_length = svcc->nal_unit_size;
			} else {
				avcc = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size);
				if (avcc)
					pctx->nalu_size_length = avcc->nal_unit_size;
			}
		}
		if (dsi_enh && !svcc) {
			svcc = gf_odf_avc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size);
			if (svcc)
				pctx->nalu_size_length = svcc->nal_unit_size;
		}
		nalh_size = pctx->nalu_size_length;
		is_svc = (svcc != NULL) ? 1 : 0;
#ifndef GPAC_DISABLE_AV_PARSERS
		if (avcc) {
			DUMP_ARRAY(avcc->sequenceParameterSets, "AVCSPS", "decoderConfig", is_svc)
			DUMP_ARRAY(avcc->pictureParameterSets, "AVCPPS", "decoderConfig", is_svc)
			DUMP_ARRAY(avcc->sequenceParameterSetExtensions, "AVCSPSEx", "decoderConfig", is_svc)
		}
		if (is_svc) {
			DUMP_ARRAY(svcc->sequenceParameterSets, "SVCSPS", dsi_enh ? "decoderConfigEnhancement" : "decoderConfig", is_svc)
			DUMP_ARRAY(svcc->pictureParameterSets, "SVCPPS", dsi_enh ? "decoderConfigEnhancement" : "decoderConfig", is_svc)
			pctx->has_svcc = 1;
		}
#endif
		inspect_printf(dump, "</AVCParameterSets>\n");
		break;
	case GF_CODECID_LHVC:
		is_enh = GF_TRUE;
	case GF_CODECID_HEVC:
	case GF_CODECID_HEVC_TILES:
#ifndef GPAC_DISABLE_AV_PARSERS
		inspect_reset_parsers(pctx, &pctx->hevc_state);

		if (!pctx->hevc_state) {
			GF_SAFEALLOC(pctx->hevc_state, HEVCState);
			if (!pctx->hevc_state) return;
		}
#endif

		if (!dsi && !dsi_enh) {
			inspect_printf(dump, "/>\n");
			return;
		}


		if (dsi) {
			if (is_enh && !dsi_enh) {
				lhcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_TRUE);
				if (lhcc)
					pctx->nalu_size_length = lhcc->nal_unit_size;
			} else {
				hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE);
				if (hvcc)
					pctx->nalu_size_length = hvcc->nal_unit_size;
			}
		}
		if (dsi_enh && !lhcc) {
			lhcc = gf_odf_hevc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size, GF_TRUE);
			if (lhcc)
				pctx->nalu_size_length = lhcc->nal_unit_size;
		}
		nalh_size = pctx->nalu_size_length;

		inspect_printf(dump, ">\n");
		inspect_printf(dump, "<HEVCParameterSets>\n");
#ifndef GPAC_DISABLE_AV_PARSERS
		if (hvcc) {
			for (idx=0; idx<gf_list_count(hvcc->param_array); idx++) {
				GF_NALUFFParamArray *ar = gf_list_get(hvcc->param_array, idx);
				if (ar->type==GF_HEVC_NALU_SEQ_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCSPS", "hvcC", 0)
				} else if (ar->type==GF_HEVC_NALU_PIC_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCPPS", "hvcC", 0)
				} else if (ar->type==GF_HEVC_NALU_VID_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCVPS", "hvcC", 0)
				} else {
					DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "hvcC", 0)
				}
			}
		}
		if (lhcc) {
			for (idx=0; idx<gf_list_count(lhcc->param_array); idx++) {
				GF_NALUFFParamArray *ar = gf_list_get(lhcc->param_array, idx);
				if (ar->type==GF_HEVC_NALU_SEQ_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCSPS", "lhcC", 0)
				} else if (ar->type==GF_HEVC_NALU_PIC_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCPPS", "lhcC", 0)
				} else if (ar->type==GF_HEVC_NALU_VID_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCVPS", "lhcC", 0)
				} else {
					DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "lhcC", 0)
				}
			}
		}
#endif
		inspect_printf(dump, "</HEVCParameterSets>\n");
		break;

	case GF_CODECID_VVC:
	case GF_CODECID_VVC_SUBPIC:
#ifndef GPAC_DISABLE_AV_PARSERS
		inspect_reset_parsers(pctx, &pctx->vvc_state);

		if (!pctx->vvc_state) {
			GF_SAFEALLOC(pctx->vvc_state, VVCState);
			if (!pctx->vvc_state) return;
		}
#endif
		if (!dsi) {
			inspect_printf(dump, "/>\n");
			return;
		}

		vvcC = gf_odf_vvc_cfg_read(dsi->value.data.ptr, dsi->value.data.size);
		if (vvcC)
			pctx->nalu_size_length = vvcC->nal_unit_size;

		nalh_size = pctx->nalu_size_length;

		inspect_printf(dump, ">\n");
		inspect_printf(dump, "<VVCParameterSets>\n");
#ifndef GPAC_DISABLE_AV_PARSERS
		if (vvcC) {
			for (idx=0; idx<gf_list_count(vvcC->param_array); idx++) {
				GF_NALUFFParamArray *ar = gf_list_get(vvcC->param_array, idx);
				if (ar->type==GF_VVC_NALU_SEQ_PARAM) {
					DUMP_ARRAY(ar->nalus, "VVCSPS", "vvcC", 0)
				} else if (ar->type==GF_VVC_NALU_PIC_PARAM) {
					DUMP_ARRAY(ar->nalus, "VVCPPS", "vvcC", 0)
				} else if (ar->type==GF_VVC_NALU_VID_PARAM) {
					DUMP_ARRAY(ar->nalus, "VVCVPS", "vvcC", 0)
				} else if (ar->type==GF_VVC_NALU_APS_PREFIX) {
					DUMP_ARRAY(ar->nalus, "VVCAPS", "vvcC", 0)
				} else if (ar->type==GF_VVC_NALU_SEI_PREFIX) {
					DUMP_ARRAY(ar->nalus, "VVCSEI", "vvcC", 0)
				} else if (ar->type==GF_VVC_NALU_DEC_PARAM) {
					DUMP_ARRAY(ar->nalus, "VVCDCI", "vvcC", 0)
				} else {
					DUMP_ARRAY(ar->nalus, "VVCUnknownPS", "vvcC", 0)
				}
			}
		}
#endif
		inspect_printf(dump, "</VVCParameterSets>\n");
		break;

	case GF_CODECID_AV1:
#ifndef GPAC_DISABLE_AV_PARSERS
		inspect_reset_parsers(pctx, &pctx->av1_state);
		if (!pctx->av1_state) {
			GF_SAFEALLOC(pctx->av1_state, AV1State);
			if (!pctx->av1_state) return;
			gf_av1_init_state(pctx->av1_state);
		}
#endif
		if (!dsi) {
			inspect_printf(dump, "/>\n");
			return;
		}
#ifndef GPAC_DISABLE_AV_PARSERS
		if (pctx->av1_state->config) gf_odf_av1_cfg_del(pctx->av1_state->config);
		pctx->av1_state->config = gf_odf_av1_cfg_read(dsi->value.data.ptr, dsi->value.data.size);
		inspect_printf(dump, ">\n");
		inspect_printf(dump, " <OBUConfig>\n");

		if (pctx && pctx->av1_state && pctx->av1_state->config) {
			idx = 1;
			for (i=0; i<gf_list_count(pctx->av1_state->config->obu_array); i++) {
				ObuType obu_type=0;
				u64 obu_size = 0;
				u32 hdr_size = 0;
				GF_AV1_OBUArrayEntry *obu = gf_list_get(pctx->av1_state->config->obu_array, i);

				if (!pctx->bs)
					pctx->bs = gf_bs_new((const u8 *) obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ);
				else
					gf_bs_reassign_buffer(pctx->bs, (const u8 *)obu->obu, (u32) obu->obu_length);

				gf_inspect_dump_obu_internal(dump, pctx->av1_state, (char*)obu->obu, obu->obu_length, obu_type, obu_size, hdr_size, ctx->crc, pctx, ctx->analyze, NULL, 0, 0);
				idx++;
			}
		}
#endif
		inspect_printf(dump, " </OBUConfig>\n");
		break;

	case GF_CODECID_MPEG1:
	case GF_CODECID_MPEG2_422:
	case GF_CODECID_MPEG2_SNR:
	case GF_CODECID_MPEG2_HIGH:
	case GF_CODECID_MPEG2_MAIN:
	case GF_CODECID_MPEG2_SIMPLE:
	case GF_CODECID_MPEG2_SPATIAL:
	case GF_CODECID_MPEG4_PART2:

#ifndef GPAC_DISABLE_AV_PARSERS
		inspect_reset_parsers(pctx, &pctx->mv124_state);
#endif
		if (!dsi) {
			inspect_printf(dump, "/>\n");
			return;
		}
		inspect_printf(dump, ">\n");
		inspect_printf(dump, " <MPEGVideoConfig>\n");
#ifndef GPAC_DISABLE_AV_PARSERS
		inspect_dump_mpeg124(pctx, dsi->value.data.ptr, dsi->value.data.size, dump);
#endif
		inspect_printf(dump, " </MPEGVideoConfig>\n");
#ifndef GPAC_DISABLE_AV_PARSERS
		if (pctx->mv124_state) gf_m4v_parser_del(pctx->mv124_state);
#endif
		break;
	case GF_CODECID_MPEG_AUDIO:
	case GF_CODECID_MPEG2_PART3:
	case GF_CODECID_MPEG_AUDIO_L1:
	case GF_CODECID_TMCD:
	case GF_CODECID_EVTE:
	case GF_CODECID_SCTE35:
		inspect_printf(dump, "/>\n");
		return;
	case GF_CODECID_SUBS_XML:
	case GF_CODECID_META_XML:
		if (!dsi) {
			inspect_printf(dump, "/>\n");
			return;
		}
		inspect_printf(dump, ">\n");
		inspect_printf(dump, " <XMLTextConfig>\n");
		for (i=0; i<dsi->value.data.size; i++) {
			gf_fputc(dsi->value.data.ptr[i], dump);
		}
		inspect_printf(dump, "\n </XMLTextConfig>\n");
		break;
	case GF_CODECID_TX3G: {
		GF_TextSampleDescriptor *tx3g = NULL;
		if (dsi) tx3g = gf_odf_tx3g_read(dsi->value.data.ptr, dsi->value.data.size);
		if (!tx3g) {
			inspect_printf(dump, "/>\n");
			return;
		}
		inspect_printf(dump, ">\n");
		inspect_printf(dump, " <TextConfig displayFlags=\"%d\" horizontalJustification=\"%d\" verticalJustification=\"%d\" backgroundColor=\"%d\">\n", tx3g->displayFlags, tx3g->horiz_justif, tx3g->vert_justif, tx3g->back_color);
		inspect_printf(dump, "  <BoxRecord top=\"%d\" left=\"%d\" bottom=\"%d\" right=\"%d\"/>\n", tx3g->default_pos.top, tx3g->default_pos.left, tx3g->default_pos.bottom, tx3g->default_pos.right);
		inspect_printf(dump, "  <StyleRecord startCharOffset=\"%d\" endCharOffset=\"%d\" fontID=\"%d\" styleFlags=\"%d\" fontSize=\"%d\" textColor=\"%d\"/>\n", tx3g->default_style.startCharOffset, tx3g->default_style.endCharOffset, tx3g->default_style.fontID, tx3g->default_style.style_flags, tx3g->default_style.font_size, tx3g->default_style.text_color);
		for (i=0; i<tx3g->font_count; i++) {
			inspect_printf(dump, "  <FontRecord fontID=\"%d\" name=\"%s\"/>\n", tx3g->fonts[i].fontID, tx3g->fonts[i].fontName);
		}
		inspect_printf(dump, " </TextConfig>\n");
		gf_odf_desc_del((GF_Descriptor *)tx3g);
		break;
	}
	case GF_CODECID_WEBVTT:
		dsi_is_text = GF_TRUE;
	case GF_CODECID_SUBS_TEXT:
	case GF_CODECID_META_TEXT:
	case GF_CODECID_SIMPLE_TEXT:
		if (!dsi) {
			inspect_printf(dump, "/>\n");
			return;
		}
		inspect_printf(dump, ">\n");
		inspect_printf(dump, " <TextConfig>");
		if (!dsi_is_text)
			inspect_printf(dump, "\n<![CDATA[");
		for (i=0; i<dsi->value.data.size; i++) {
			gf_fputc(dsi->value.data.ptr[i], dump);
		}
		if (!dsi_is_text)
			inspect_printf(dump, "]]>\n");
		inspect_printf(dump, " </TextConfig>\n");
		break;
	case GF_CODECID_APCH:
	case GF_CODECID_APCN:
	case GF_CODECID_APCS:
	case GF_CODECID_APCO:
	case GF_CODECID_AP4H:
	case GF_CODECID_AP4X:
		inspect_printf(dump, "/>\n");
		return;
	case GF_CODECID_MPHA:
	case GF_CODECID_MHAS:
		if (!dsi) {
			inspect_printf(dump, "/>\n");
			return;
		}
	{
		u16 size;
		if (!pctx->bs)
			pctx->bs = gf_bs_new(dsi->value.data.ptr, dsi->value.data.size, GF_BITSTREAM_READ);
		else
			gf_bs_reassign_buffer(pctx->bs, dsi->value.data.ptr, dsi->value.data.size);

		inspect_printf(dump, ">\n");
		inspect_printf(dump, " <MPEGHAudioConfig");
		inspect_printf(dump, " version=\"%d\"", gf_bs_read_u8(pctx->bs) );
		inspect_printf(dump, " ProfileLevelIndication=\"%d\"", gf_bs_read_u8(pctx->bs) );
		inspect_printf(dump, " ReferenceChannelLayout=\"%d\"", gf_bs_read_u8(pctx->bs) );
		size = gf_bs_read_u16(pctx->bs);
		if (size) {
			inspect_printf(dump, ">\n");
#ifndef GPAC_DISABLE_AV_PARSERS
			dump_mha_config(dump, pctx->bs, "  ");
#endif
			inspect_printf(dump, " </MPEGHAudioConfig>\n");
		} else {
			inspect_printf(dump, "/>\n");
		}
	}
		break;
	case GF_CODECID_VP8:
	case GF_CODECID_VP9:
		if (!dsi) {
			inspect_printf(dump, "/>\n");
			return;
		}

#ifndef GPAC_DISABLE_AV_PARSERS
		inspect_reset_parsers(pctx, NULL);
#endif

		pctx->vpcc = gf_odf_vp_cfg_read(dsi->value.data.ptr, dsi->value.data.size);
		inspect_printf(dump, ">\n");
		if (pctx->vpcc) {
			u32 v = (pctx->codec_id==GF_CODECID_VP9) ? 9 : 8;
			inspect_printf(dump, "<VP%dConfiguration profile=\"%d\" level=\"%d\" bit_depth=\"%u\" chroma_subsampling=\"%u\" colour_primaries=\"%u\" transfer_characteristics=\"%u\" matrix_coefficients=\"%u\" video_full_range=\"%u\"", v,
				pctx->vpcc->profile, pctx->vpcc->level, pctx->vpcc->bit_depth, pctx->vpcc->chroma_subsampling, pctx->vpcc->colour_primaries, pctx->vpcc->transfer_characteristics, pctx->vpcc->matrix_coefficients, pctx->vpcc->video_fullRange_flag
			);
			if (pctx->vpcc->codec_initdata && pctx->vpcc->codec_initdata_size) {
				inspect_printf(dump, " init_data=\"");
				for (idx=0; idx<pctx->vpcc->codec_initdata_size; idx++)
					inspect_printf(dump, "%02X", (unsigned char) pctx->vpcc->codec_initdata[idx]);
				inspect_printf(dump, "\"");
			}
			inspect_printf(dump, "/>\n");
		}
		break;

	case GF_CODECID_AAC_MPEG4:
	case GF_CODECID_AAC_MPEG2_MP:
	case GF_CODECID_AAC_MPEG2_LCP:
	case GF_CODECID_AAC_MPEG2_SSRP:
	case GF_CODECID_USAC:
		if (!pctx->no_analysis) {
			pctx->no_analysis = GF_TRUE;
			GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Inspect] bitstream analysis for codec %s not supported, only configuration is\n", gf_codecid_name(pctx->codec_id)));
		}
		if (dsi) {
#ifndef GPAC_DISABLE_AV_PARSERS
			GF_M4ADecSpecInfo acfg;
			InspectLogCbk lcbk;

			inspect_printf(dump, ">\n");
			inspect_printf(dump, "<MPEG4AudioConfiguration ");

			if (!pctx->bs)
				pctx->bs = gf_bs_new(dsi->value.data.ptr, dsi->value.data.size, GF_BITSTREAM_READ);
			else
				gf_bs_reassign_buffer(pctx->bs, dsi->value.data.ptr, dsi->value.data.size);

			if (ctx->analyze>=INSPECT_ANALYZE_BS) {
				lcbk.dump = dump;
				lcbk.dump_bits = (ctx->analyze==INSPECT_ANALYZE_BS_BITS) ? GF_TRUE : GF_FALSE;
				gf_bs_set_logger(pctx->bs, regular_bs_log, &lcbk);
			}

			gf_m4a_parse_config(pctx->bs, &acfg, GF_TRUE);
			if (ctx->analyze<INSPECT_ANALYZE_BS) {
				inspect_printf(dump, "base_object_type=\"%u\" sample_rate=\"%u\" channels=\"%u\" ", acfg.base_object_type, acfg.base_sr, acfg.nb_chan);
				if (acfg.has_sbr)
					inspect_printf(dump, "sbr_sample_rate=\"%u\" ", acfg.sbr_sr);
				if (acfg.has_ps)
					inspect_printf(dump, "parametricStereo=\"yes\" ");
			}
			if (acfg.comments[0])
				inspect_printf(dump, "comments=\"%s\" ", acfg.comments);
#endif /*GPAC_DISABLE_AV_PARSERS*/
			inspect_printf(dump, "/>\n");
		} else {
			inspect_printf(dump, "/>\n");
			return;
		}
		break;
	case GF_CODECID_AC3:
	case GF_CODECID_EAC3:
		inspect_printf(dump, "/>\n");
		return;

	case GF_CODECID_TRUEHD:
		if (dsi) {
			u16 fmt, prate;
			inspect_printf(dump, ">\n");
			inspect_printf(dump, "<TrueHDAudioConfiguration");

			if (!pctx->bs)
				pctx->bs = gf_bs_new(dsi->value.data.ptr, dsi->value.data.size, GF_BITSTREAM_READ);
			else
				gf_bs_reassign_buffer(pctx->bs, dsi->value.data.ptr, dsi->value.data.size);

			fmt = gf_bs_read_u32(pctx->bs);
			prate = gf_bs_read_int(pctx->bs, 15);

			inspect_printf(dump, " format=\"%u\" peak_data_rate=\"%u\"/>\n", fmt, prate);
		} else {
			inspect_printf(dump, "/>\n");
			return;
		}
		break;

	case GF_CODECID_OPUS:
		if (dsi) {
			GF_OpusConfig opcfg;
			inspect_printf(dump, ">\n");
			inspect_printf(dump, "<OpusConfiguration");
			gf_odf_opus_cfg_parse(dsi->value.data.ptr, dsi->value.data.size, &opcfg);

			inspect_printf(dump, " version=\"%d\" OutputChannelCount=\"%d\" PreSkip=\"%d\" InputSampleRate=\"%d\" OutputGain=\"%d\" ChannelMappingFamily=\"%d\"",
					opcfg.version, opcfg.OutputChannelCount, opcfg.PreSkip, opcfg.InputSampleRate, opcfg.OutputGain, opcfg.ChannelMappingFamily);
			if (opcfg.ChannelMappingFamily) {
				inspect_printf(dump, " StreamCount=\"%d\" CoupledStreamCount=\"%d\" channelMapping=\"", opcfg.StreamCount, opcfg.CoupledCount);
				for (i=0; i<opcfg.OutputChannelCount; i++) {
					inspect_printf(dump, "%s%d", i ? " " : "", opcfg.ChannelMapping[i]);
				}
				inspect_printf(dump, "\"");
			}
			pctx->opus_channel_count = opcfg.StreamCount;
			pctx->opus_channel_count = (opcfg.StreamCount ? opcfg.StreamCount : 1);
			inspect_printf(dump, "/>\n");
		} else {
			inspect_printf(dump, "/>\n");
			return;
		}
		break;

	case GF_CODECID_ALAC:
		if (dsi) {
			GF_BitStream *bs = gf_bs_new(dsi->value.data.ptr, dsi->value.data.size, GF_BITSTREAM_READ);
			inspect_printf(dump, ">\n");
			inspect_printf(dump, "<ALACConfiguration");
			while (gf_bs_available(bs)) {
				u32 val;

#define get_and_print(name, bits) \
				val = gf_bs_read_int(bs, bits); \
				inspect_printf(dump, " "name"=\"%u\"", val);

				u32 size = gf_bs_read_u32(bs);
				u32 type = gf_bs_read_u32(bs);
				if (type==GF_4CC('a','l','a','c') && (size==36)) {

					get_and_print("version", 32)
					get_and_print("frameLength", 32)
					get_and_print("compatibleVersion", 8)
					get_and_print("bitDepth", 8)
					get_and_print("pb", 8)
					get_and_print("mb", 8)
					get_and_print("kb", 8)
					get_and_print("numChannels", 8)
					get_and_print("maxRun", 16)
					get_and_print("maxFrameBytes", 32)
					get_and_print("avgBitRate", 32)
					get_and_print("sampleRate", 32)
				} else if (type==GF_4CC('c','h','a','n') && (size==24)) {
					get_and_print("version", 32)
					get_and_print("channelLayoutTag", 32)
					get_and_print("reserved1", 32)
					get_and_print("reserved2", 32)
				}
			}
#undef get_and_print

			inspect_printf(dump, "/>\n");
			gf_bs_del(bs);
		} else {
			inspect_printf(dump, "/>\n");
			return;
		}
		break;

	default:
		if (!pctx->no_analysis) {
			pctx->no_analysis = GF_TRUE;
			GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Inspect] bitstream analysis for codec %s not supported\n", gf_codecid_name(pctx->codec_id)));
		}
		inspect_printf(dump, "/>\n");
		return;
	}

	if (avcc) gf_odf_avc_cfg_del(avcc);
	if (svcc) gf_odf_avc_cfg_del(svcc);
	if (hvcc) gf_odf_hevc_cfg_del(hvcc);
	if (lhcc) gf_odf_hevc_cfg_del(lhcc);
	if (vvcC) gf_odf_vvc_cfg_del(vvcC);
	inspect_printf(dump, "</%s>\n", elt_name);
}

static void inspect_stats_packet(GF_InspectCtx *ctx, PidCtx *pctx, GF_FilterPacket *pck)
{
	GF_FilterSAPType sap = gf_filter_pck_get_sap(pck);
	u64 dts, cts;
	dts = gf_filter_pck_get_dts(pck);
	cts = gf_filter_pck_get_cts(pck);
	if (dts==GF_FILTER_NO_TS) dts = cts;

	u32 dur = gf_filter_pck_get_duration(pck);
	pctx->total_dur += dur;
	if (pctx->pck_num==1) {
		pctx->start_with_sap = sap;
		pctx->timescale = gf_filter_pck_get_timescale(pck);
		pctx->constant_dur = dur;
		pctx->first_dts = dts;
		pctx->first_cts = cts;
	} else if (pctx->constant_dur != dur) {
		pctx->constant_dur = 0;
	}

	if (dur>pctx->max_dur) pctx->max_dur = dur;
	u32 size;
	gf_filter_pck_get_data(pck, &size);
	switch (sap) {
	case GF_FILTER_SAP_1: pctx->nb_sap1++; break;
	case GF_FILTER_SAP_2: pctx->nb_sap2++; break;
	case GF_FILTER_SAP_3: pctx->nb_sap3++; break;
	case GF_FILTER_SAP_4:
	case GF_FILTER_SAP_4_PROL:
		pctx->nb_sap4++;
		break;
	default: break;
	}
	pctx->nb_bytes += size;
	s64 ts_diff = (s64) cts - (s64) dts;
	if (pctx->max_ctso < ts_diff) pctx->max_ctso = ts_diff;
	if (pctx->min_ctso > ts_diff) pctx->min_ctso = ts_diff;

	if (gf_filter_pck_get_corrupted(pck))
		pctx->has_corr = GF_TRUE;
	if (gf_filter_pck_get_clock_type(pck)==GF_FILTER_CLOCK_PCR_DISC)
		pctx->has_disc = GF_TRUE;
	if (gf_filter_pck_get_crypt_flags(pck) & GF_FILTER_PCK_CRYPT)
		pctx->has_crypted |= 1<<2;
	else
		pctx->has_crypted |= 1<<1;

	if (dts!=GF_FILTER_NO_TS) {
		if (!pctx->prev_dts) {
			pctx->prev_dts = dts;
		} else if (dts - pctx->prev_dts > pctx->timescale) {
			u64 rate = pctx->bytes_in_wnd*8;
			rate *= pctx->timescale ;
			rate /= dts - pctx->prev_dts;
			if (pctx->max_rate < (u32) rate)
				pctx->max_rate = (u32) rate;
			pctx->prev_dts = dts;
			pctx->bytes_in_wnd = 0;
		}
		pctx->bytes_in_wnd += size;
	}

	if (!sap || (sap>GF_FILTER_SAP_3)) {
		pctx->avg_nosap_size += size;
		pctx->nb_nosaps ++;
		return;
	}
	if (!pctx->last_sap_cts) {
		pctx->last_sap_cts = cts;
	} else {
		u32 sap_diff = (u32) (cts - pctx->last_sap_cts);
		if (!pctx->min_sap_diff || (sap_diff<pctx->min_sap_diff)) pctx->min_sap_diff = sap_diff;
		if (!pctx->max_sap_diff || (sap_diff>pctx->max_sap_diff)) pctx->max_sap_diff = sap_diff;
		pctx->avg_sap_diff += sap_diff;
		pctx->last_sap_cts = cts;
	}
	pctx->avg_sap_size += size;
	if (size>pctx->max_sap_size) pctx->max_sap_size = size;
}

static void inspect_dump_stats(GF_InspectCtx *ctx)
{
	u32 i, count = gf_list_count(ctx->src_pids);
	ctx->stats=0;
	for (i=0; i<count; i++) {
		PidCtx *pctx = gf_list_get(ctx->src_pids, i);
		//dump as usual
		inspect_dump_pid(ctx, ctx->dump, pctx->src_pid, i+1, GF_TRUE, GF_FALSE, 0, GF_FALSE, pctx);

		inspect_printf(ctx->dump, "\tNb Frames: %u\n", pctx->pck_num);
		if (!pctx->constant_dur)
			inspect_printf(ctx->dump, "\tFrame Rate: VFR - frame duration avg %d max %d", pctx->total_dur/pctx->pck_num, pctx->max_dur);
		else
			inspect_printf(ctx->dump, "\tFrame Rate: CFR - frame duration %d", pctx->constant_dur);
		inspect_printf(ctx->dump, "\n");
		inspect_printf(ctx->dump, "\tCumulated Duration: "LLU, pctx->total_dur);
		format_duration(pctx->total_dur-pctx->first_cts, pctx->timescale, ctx->dump, GF_TRUE);
		inspect_printf(ctx->dump, "\n");
		if ((pctx->first_dts==GF_FILTER_NO_TS) && (pctx->first_cts==GF_FILTER_NO_TS))
			inspect_printf(ctx->dump, "\tFirst packet: No initial timestamps\n");
		else
			inspect_printf(ctx->dump, "\tFirst packet: DTS "LLU" CTS "LLU"\n", pctx->first_dts, pctx->first_cts);
		if (pctx->max_ctso || pctx->min_ctso) {
			inspect_printf(ctx->dump, "\tCTS offset: min %d max %d\n", pctx->min_ctso, pctx->max_ctso);
		}

		u32 nb_saps = pctx->nb_sap1+pctx->nb_sap2+pctx->nb_sap3;
		inspect_printf(ctx->dump, "\tSAP frames: %u - types", nb_saps);
		if (pctx->nb_nosaps) inspect_printf(ctx->dump, " 0(%u)", pctx->nb_nosaps);
		if (pctx->nb_sap1) inspect_printf(ctx->dump, " 1(%u)", pctx->nb_sap1);
		if (pctx->nb_sap2) inspect_printf(ctx->dump, " 2(%u)", pctx->nb_sap2);
		if (pctx->nb_sap3) inspect_printf(ctx->dump, " 3(%u)", pctx->nb_sap3);
		if (pctx->nb_sap4) inspect_printf(ctx->dump, " 4(%u)", pctx->nb_sap4);
		inspect_printf(ctx->dump, "\n");
		inspect_printf(ctx->dump, "\tStart with SAP: %d\n", pctx->start_with_sap);

		if (pctx->nb_nosaps)
			inspect_printf(ctx->dump, "\tAverage non-SAP frame size: %d bytes\n", pctx->avg_nosap_size / pctx->nb_nosaps);
		if (nb_saps) {
			inspect_printf(ctx->dump, "\tAverage SAP frame size: %d bytes\n", pctx->avg_sap_size / nb_saps);
			inspect_printf(ctx->dump, "\tMax SAP frame size: %d bytes\n", pctx->max_sap_size);
			if (nb_saps>2)
				inspect_printf(ctx->dump, "\tSAP time diff: min %u max %u avg %u\n", pctx->min_sap_diff, pctx->max_sap_diff, pctx->avg_sap_diff/(nb_saps-2));
		}


		inspect_printf(ctx->dump, "\tTotal size: "LLU" bytes\n", pctx->nb_bytes);
		if (!pctx->total_dur) continue;
		u64 br = pctx->nb_bytes;
		br *= 8*pctx->timescale;
		br /= pctx->total_dur;
		if (br>10000000)
			inspect_printf(ctx->dump, "\tBitrate: avg "LLU" max "LLU" mbps\n", br/1000000, pctx->max_rate/1000000);
		else if (br>10000)
			inspect_printf(ctx->dump, "\tBitrate: avg "LLU" max "LLU" kbps\n", br/1000, pctx->max_rate/1000);
		else
			inspect_printf(ctx->dump, "\tBitrate: avg "LLU" max "LLU" bps\n", br, pctx->max_rate);
	}
	ctx->stats=1;
}

static GF_Err inspect_process(GF_Filter *filter)
{
	u32 i, count, nb_done=0, nb_hdr_done=0;
	GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter);

	count = gf_list_count(ctx->src_pids);

	if (ctx->args_updated) {
		ctx->args_updated = GF_FALSE;
		for (i=0; i<count; i++) {
			PidCtx *pctx = gf_list_get(ctx->src_pids, i);
			if (!pctx->src_pid) continue;
			switch (ctx->mode) {
			case INSPECT_MODE_PCK:
			case INSPECT_MODE_REFRAME:
				gf_filter_pid_set_framing_mode(pctx->src_pid, GF_TRUE);
				break;
			default:
				gf_filter_pid_set_framing_mode(pctx->src_pid, GF_FALSE);
				break;
			}
			gf_filter_pid_set_clock_mode(pctx->src_pid, ctx->pcr);
		}
		if (ctx->is_prober || ctx->deep || ctx->fmt) {
			ctx->dump_pck = GF_TRUE;
		} else {
			ctx->dump_pck = GF_FALSE;
		}
	}

	for (i=0; i<count; i++) {
		PidCtx *pctx = gf_list_get(ctx->src_pids, i);
		GF_FilterPacket *pck = NULL;
		pck = pctx->src_pid ? gf_filter_pid_get_packet(pctx->src_pid) : NULL;

		if (pctx->init_pid_config_done) {
			nb_hdr_done++;
		} else if (!ctx->deep && !ctx->allp && !ctx->fmt
			&& (gf_sys_clock() - ctx->last_config_time >= ctx->timeout+ctx->buffer)
		) {
			nb_hdr_done++;
		}

		if (!pck) {
			if (!pctx->is_fake && pctx->src_pid && !gf_filter_pid_is_eos(pctx->src_pid))
				continue;
			else
				ctx->has_seen_eos = GF_TRUE;
		}
		if (pctx->aborted)
			continue;

		if (!pctx->buffer_done || ctx->rbuffer) {
			u64 dur=0;
			if (pck && !ctx->has_seen_eos && !gf_filter_pid_eos_received(pctx->src_pid)) {
				dur = gf_filter_pid_query_buffer_duration(pctx->src_pid, GF_FALSE);
				if (!pctx->buffer_done) {
					if ((dur < ctx->buffer * 1000)) {
						GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Inspect] PID %d buffering buffer %u ms %.02f %%\n", pctx->idx, (u32) (dur/1000), dur*1.0/(ctx->buffer * 10.0)));
						continue;
					}
				} else if (ctx->rbuffer && (dur < ctx->rbuffer * 1000)) {
					pctx->buffer_done = GF_FALSE;
					pctx->buf_start_time = gf_sys_clock();
					GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Inspect] PID %d rebuffering buffer %u ms less than rebuffer %u\n", pctx->idx, (u32) (dur/1000), ctx->rbuffer));
					continue;
				}
			}
			if (!pctx->buffer_done) {
				if (!dur) {
					dur = gf_filter_pid_query_buffer_duration(pctx->src_pid, GF_FALSE);
					GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Inspect] PID %d buffering aborted after %u ms - level %u ms %.02f %%\n", pctx->idx, gf_sys_clock() - pctx->buf_start_time, (u32) (dur/1000), dur*1.0/(ctx->buffer * 10.0)));
				} else {
					GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Inspect] PID %d buffering done in %u ms - level %u ms %.02f %%\n", pctx->idx, gf_sys_clock() - pctx->buf_start_time, (u32) (dur/1000), dur*1.0/(ctx->buffer * 10.0)));
				}
				pctx->buffer_done = GF_TRUE;
			}
		}

		if (pctx->dump_pid) {
			inspect_dump_pid(ctx, pctx->tmp, pctx->src_pid, pctx->idx, pctx->init_pid_config_done ? GF_FALSE : GF_TRUE, GF_FALSE, pctx->pck_for_config, (pctx->dump_pid==2) ? GF_TRUE : GF_FALSE, pctx);
			pctx->dump_pid = 0;
			pctx->init_pid_config_done = 1;
			pctx->pck_for_config=0;

			if (!ctx->hdr_done) {
				ctx->hdr_done=GF_TRUE;
				//dump header on main output file, not on pid one!
				if (ctx->hdr && ctx->fmt && !ctx->xml)
					inspect_dump_packet_fmt(filter, ctx, ctx->dump, NULL, 0, 0);
			}
		}
		if (!pck) continue;

		pctx->pck_for_config++;
		pctx->pck_num++;

		if (ctx->stats) {
			inspect_stats_packet(ctx, pctx, pck);
		} else if (ctx->dump_pck) {
			if (ctx->is_prober) {
				nb_done++;
			} else {
				if (ctx->fmt) {
					inspect_dump_packet_fmt(filter, ctx, pctx->tmp, pck, pctx, pctx->pck_num);
				} else {
					inspect_dump_packet(ctx, pctx->tmp, pck, pctx->idx, pctx->pck_num, pctx);
				}
			}
		}

		if (ctx->dur.num && ctx->dur.den) {
			u64 timescale = gf_filter_pck_get_timescale(pck);
			u64 ts = gf_filter_pck_get_dts(pck);
			u64 dur = gf_filter_pck_get_duration(pck);
			if (ts == GF_FILTER_NO_TS) ts = gf_filter_pck_get_cts(pck);

			if (!pctx->init_ts) pctx->init_ts = ts+1;
			else if (gf_timestamp_greater_or_equal(ts + dur - pctx->init_ts + 1, timescale, ctx->dur.num, ctx->dur.den)) {
				GF_FilterEvent evt;
				GF_FEVT_INIT(evt, GF_FEVT_STOP, pctx->src_pid);

				GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Inspect] PID %d (codec %s) done dumping, aborting\n", pctx->idx, gf_codecid_name(pctx->codec_id) ));
				gf_filter_pid_drop_packet(pctx->src_pid);

				gf_filter_pid_send_event(pctx->src_pid, &evt);
				gf_filter_pid_set_discard(pctx->src_pid, GF_TRUE);
				pctx->aborted = GF_TRUE;
				break;
			}
		}
		gf_filter_pid_drop_packet(pctx->src_pid);
	}
	if ((ctx->is_prober && !ctx->probe_done && (nb_done==count) && !ctx->allp)
		|| (!ctx->is_prober && !ctx->allp && !ctx->dump_pck && (nb_hdr_done==count) && !gf_filter_connections_pending(filter))
	) {
		for (i=0; i<count; i++) {
			PidCtx *pctx = gf_list_get(ctx->src_pids, i);
			GF_FilterEvent evt;
			GF_FEVT_INIT(evt, GF_FEVT_STOP, pctx->src_pid);
			gf_filter_pid_send_event(pctx->src_pid, &evt);
		}
		if (ctx->is_prober)
			ctx->probe_done = GF_TRUE;
		return GF_EOS;
	}
	return GF_OK;
}

static GF_Err inspect_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
{
	GF_FilterEvent evt;
	PidCtx *pctx;
	u32 w, h, sr, ch;
	const GF_PropertyValue *p;
	GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter);

	if (!ctx->src_pids) ctx->src_pids = gf_list_new();
	ctx->last_config_time = gf_sys_clock();

	pctx = gf_filter_pid_get_udta(pid);
	if (pctx) {
		gf_assert(pctx->src_pid == pid);
		if (is_remove)
			pctx->src_pid = NULL;
		else if (!ctx->is_prober)
			pctx->dump_pid = 1;
		return GF_OK;
	}
	GF_SAFEALLOC(pctx, PidCtx);
	if (!pctx) return GF_OUT_OF_MEM;
	if (ctx->analyze) {
		pctx->bs = gf_bs_new((u8 *)pctx, 0, GF_BITSTREAM_READ);
	}

	pctx->src_pid = pid;
	gf_filter_pid_set_udta(pid, pctx);


	p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
	pctx->stream_type = p ? p->value.uint : 0;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
	pctx->codec_id = p ? p->value.uint : 0;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID);
	pctx->service_id = p ? p->value.uint : 0;
	if (gf_sys_is_test_mode())
		pctx->service_id = 0;

	if (!ctx->buffer) {
		pctx->buffer_done = GF_TRUE;
	} else {
		GF_FEVT_INIT(evt, GF_FEVT_BUFFER_REQ, pid);
		evt.buffer_req.max_playout_us = evt.buffer_req.max_buffer_us = ctx->buffer * 1000;
		if (ctx->mbuffer >= ctx->buffer)
			evt.buffer_req.max_buffer_us = ctx->mbuffer * 1000;
		if (ctx->rbuffer<ctx->buffer)
			evt.buffer_req.min_playout_us = ctx->rbuffer * 1000;
		else
			ctx->rbuffer = 0;

		//if pid is decoded media, don't ask for buffer requirement on pid, set it at decoder level
		evt.buffer_req.pid_only = gf_filter_pid_has_decoder(pid) ? GF_FALSE : GF_TRUE;
		gf_filter_pid_send_event(pid, &evt);
		pctx->buf_start_time = gf_sys_clock();
	}

	//Load SEIs for:
	//- tmcd dump
	//- props with no analyze
	if ((ctx->fmt && strstr(ctx->fmt, "$tmcd$"))
		|| (!ctx->analyze && ctx->props && !gf_sys_is_test_mode() )
	) {
		switch (pctx->codec_id) {
		case GF_CODECID_AVC:
		case GF_CODECID_SVC:
		case GF_CODECID_MVC:
		case GF_CODECID_HEVC:
		case GF_CODECID_LHVC:
		case GF_CODECID_VVC:
		case GF_CODECID_AV1:
			p = gf_filter_pid_get_property(pid, GF_PROP_PID_SEI_LOADED);
			if (!p) {
				//if unframed and our inspect mode is not framed, do not require SEI_LOAD (we don't want a reframer to be inserted)
				p = gf_filter_pid_get_property(pid, GF_PROP_PID_UNFRAMED);
				if (!p || (ctx->mode==INSPECT_MODE_REFRAME)) {
					gf_filter_pid_negotiate_property(pid, GF_PROP_PID_SEI_LOADED, &PROP_BOOL(GF_TRUE) );
				}
			}
		}
	}

	w = h = sr = ch = 0;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
	if (p) w = p->value.uint;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
	if (p) h = p->value.uint;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE);
	if (p) sr = p->value.uint;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS);
	if (p) ch = p->value.uint;

	if (! ctx->interleave) {
		u32 insert_idx = 0;
		u32 i;
		//sort all PIDs by codec IDs
		for (i=0; i<gf_list_count(ctx->src_pids); i++) {
			Bool insert = GF_FALSE;
			PidCtx *actx = gf_list_get(ctx->src_pids, i);

			if (pctx->service_id && actx->service_id) {
				if (pctx->service_id == actx->service_id)
					insert = GF_TRUE;
				else
					continue;
			} else if (pctx->codec_id < actx->codec_id) {
				insert = GF_TRUE;
			}
			//same codec ID, sort by increasing width/height/samplerate/channels
			else if (pctx->codec_id==actx->codec_id) {
				u32 aw, ah, asr, ach;

				aw = ah = asr = ach = 0;
				p = gf_filter_pid_get_property(actx->src_pid, GF_PROP_PID_WIDTH);
				if (p) aw = p->value.uint;
				p = gf_filter_pid_get_property(actx->src_pid, GF_PROP_PID_HEIGHT);
				if (p) ah = p->value.uint;
				p = gf_filter_pid_get_property(actx->src_pid, GF_PROP_PID_SAMPLE_RATE);
				if (p) asr = p->value.uint;
				p = gf_filter_pid_get_property(actx->src_pid, GF_PROP_PID_NUM_CHANNELS);
				if (p) ach = p->value.uint;

				if (w && aw && (w<aw)) insert = GF_TRUE;
				if (h && ah && (h<ah)) insert = GF_TRUE;
				if (sr && asr && (sr<asr)) insert = GF_TRUE;
				if (ch && ach && (ch<ach)) insert = GF_TRUE;
			}

			if (insert) {
				insert_idx = i+1;
				break;
			}
		}
		if (insert_idx) {
			gf_list_insert(ctx->src_pids, pctx, insert_idx-1);
			for (i=insert_idx; i<gf_list_count(ctx->src_pids); i++) {
				PidCtx *actx = gf_list_get(ctx->src_pids, i);
				actx->idx = i+1;
			}
		} else {
			gf_list_add(ctx->src_pids, pctx);
		}
	} else {
		pctx->tmp = ctx->dump;
		gf_list_add(ctx->src_pids, pctx);
	}

	pctx->idx = gf_list_find(ctx->src_pids, pctx) + 1;

	if (! ctx->interleave && !pctx->tmp && ctx->dump) {
		pctx->tmp = gf_file_temp(NULL);
		if (ctx->xml)
			inspect_printf(pctx->tmp, "<PIDInspect ID=\"%d\" name=\"%s\">\n", pctx->idx, gf_filter_pid_get_name(pid) );
	}

	switch (ctx->mode) {
	case INSPECT_MODE_PCK:
	case INSPECT_MODE_REFRAME:
		gf_filter_pid_set_framing_mode(pid, GF_TRUE);
		break;
	default:
		gf_filter_pid_set_framing_mode(pid, GF_FALSE);
		break;
	}

	if (!ctx->is_prober) {
		pctx->dump_pid = 1;
	}

	gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "Inspect");
	gf_filter_pid_send_event(pid, &evt);

	if (ctx->is_prober || ctx->deep || ctx->fmt) {
		ctx->dump_pck = GF_TRUE;
	} else {
		ctx->dump_pck = GF_FALSE;
	}
	if (ctx->pcr)
		gf_filter_pid_set_clock_mode(pid, GF_TRUE);

	p = gf_filter_pid_get_property(pid, GF_PROP_PID_FAKE);
	pctx->is_fake = (p && p->value.boolean) ? GF_TRUE : GF_FALSE;

	if (!ctx->deep)
		gf_filter_post_process_task(filter);
	return GF_OK;
}

static const GF_FilterCapability InspecterDemuxedCaps[] =
{
	//accept any stream but files, framed
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_CODECID, GF_CODECID_NONE),
	{0},
	CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_FAKE, GF_TRUE)
};

static const GF_FilterCapability InspecterReframeCaps[] =
{
	//accept any stream but files, framed
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_CODECID, GF_CODECID_NONE),
	CAP_BOOL(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_UNFRAMED, GF_TRUE),
	{0},
	CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_FAKE, GF_TRUE)
};

static const GF_FilterCapability InspecterRawCaps[] =
{
	//accept any stream but files, framed
	CAP_UINT(GF_CAPS_INPUT,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
	CAP_STRING(GF_CAPS_INPUT,  GF_PROP_PID_MIME, "*"),
	CAP_STRING(GF_CAPS_INPUT,  GF_PROP_PID_FILE_EXT, "*"),
	{0},
	CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_FAKE, GF_TRUE)
};

static GF_Err inspect_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val)
{
	GF_InspectCtx  *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter);
	ctx->args_updated = GF_TRUE;
	return GF_OK;
}

GF_Err inspect_initialize(GF_Filter *filter)
{
	const char *name = gf_filter_get_name(filter);
	GF_InspectCtx  *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter);

	if (ctx->log) {
		if (!strcmp(ctx->log, "GLOG")) {
			ctx->dump_log = GF_TRUE;
			inspect_log_tool = GF_LOG_APP;
		} else {
			u32 ltool = gf_log_parse_tool(ctx->log);
			if (ltool!=GF_LOG_TOOL_UNDEFINED) {
				ctx->dump_log = GF_TRUE;
				inspect_log_tool = ltool;
			}
		}
	}

	if (name && !strcmp(name, "probe") ) {
		ctx->is_prober = GF_TRUE;
		return GF_OK;
	}

#ifdef GPAC_DISABLE_AVPARSE_LOGS
	if (ctx->analyze>=INSPECT_ANALYZE_BS) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Inspect] Bitstream logging is disable in this build\n"));
		return GF_NOT_SUPPORTED;
	}
#endif

	if (!ctx->log) return GF_BAD_PARAM;

	if (ctx->stats) {
		ctx->analyze = GF_FALSE;
		ctx->allp = GF_TRUE;
		ctx->deep = GF_FALSE;
	}
	if (ctx->analyze) {
		ctx->xml = GF_TRUE;
		ctx->mode = INSPECT_MODE_REFRAME;
	}


	if (ctx->xml || ctx->analyze || gf_sys_is_test_mode() || ctx->fmt) {
		ctx->full = GF_TRUE;
	}
	if (!ctx->full && (ctx->mode!=INSPECT_MODE_RAW)) {
		ctx->mode = INSPECT_MODE_REFRAME;
	}

	//force reframer if working with "tmcd"
	if (ctx->fmt && strstr(ctx->fmt, "$tmcd$") && (ctx->mode!=INSPECT_MODE_RAW)) {
		ctx->mode = INSPECT_MODE_REFRAME;
	}

	switch (ctx->mode) {
	case INSPECT_MODE_RAW:
		gf_filter_override_caps(filter, InspecterRawCaps,  sizeof(InspecterRawCaps)/sizeof(GF_FilterCapability) );
		break;
	case INSPECT_MODE_REFRAME:
		gf_filter_override_caps(filter, InspecterReframeCaps,  sizeof(InspecterReframeCaps)/sizeof(GF_FilterCapability) );
		break;
	default:
		gf_filter_override_caps(filter, InspecterDemuxedCaps,  sizeof(InspecterDemuxedCaps)/sizeof(GF_FilterCapability) );
		break;
	}
	if (gf_filter_is_temporary(filter))
		return GF_OK;

	if (!strcmp(ctx->log, "stderr")) ctx->dump = stderr;
	else if (!strcmp(ctx->log, "stdout")) ctx->dump = stdout;
	else if (!strcmp(ctx->log, "null")) ctx->dump = NULL;
	else if (!ctx->dump_log) {
		ctx->dump = gf_fopen(ctx->log, "wt");
		if (!ctx->dump) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Inspect] Failed to open file %s\n", ctx->log));
			return GF_IO_ERR;
		}
	}
	if (ctx->xml && ctx->dump) {
		ctx->fmt = NULL;
		inspect_printf(ctx->dump, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		inspect_printf(ctx->dump, "<GPACInspect>\n");
	}

#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode()) {
		inspect_update_arg(filter, NULL, NULL);
		ctx->args_updated = GF_FALSE;
	}
#endif
	return GF_OK;
}

static Bool inspect_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
{
	PidCtx *pctx;
	GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter);
	if (evt->base.type != GF_FEVT_INFO_UPDATE) return GF_TRUE;
	if (!ctx->info) return GF_TRUE;
	pctx = gf_filter_pid_get_udta(evt->base.on_pid);
	pctx->dump_pid = 2;
	return GF_TRUE;
}

#define OFFS(_n)	#_n, offsetof(GF_InspectCtx, _n)
static const GF_FilterArgs InspectArgs[] =
{
	{ OFFS(log), "set probe log filename\n"
	"- _any: target file path and name\n"
	"- stderr: dump to stderr\n"
	"- stdout: dump to stdout\n"
	"- GLOG: use GPAC logs `app@info`\n"
	"- TL: use GPAC log tool `TL` at level `info`\n"
	"- null: silent mode", GF_PROP_STRING,
#ifdef GPAC_CONFIG_ANDROID
		"GLOG"
#else
		"stdout"
#endif
		, "_any|stderr|stdout|GLOG|TL|null", 0},
	{ OFFS(mode), "dump mode\n"
	"- pck: dump full packet\n"
	"- blk: dump packets before reconstruction\n"
	"- frame: force reframer\n"
	"- raw: dump source packets without demultiplexing", GF_PROP_UINT, "pck", "pck|blk|frame|raw", 0},
	{ OFFS(interleave), "dump packets as they are received on each PID. If false, logs are reported for each PID at end of session", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED},
	{ OFFS(deep), "dump packets along with PID state change, implied when [-fmt]() is set", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE},
	{ OFFS(props), "dump packet properties, ignored when [-fmt]() is set", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE},
	{ OFFS(dump_data), "enable full data dump (__very large output__), ignored when [-fmt]() is set", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE},
	{ OFFS(fmt), "set packet dump format", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED},
	{ OFFS(hdr), "print a header corresponding to fmt string without '$' or \"pid\"", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED},
	{ OFFS(allp), "analyse for the entire duration, rather than stopping when all PIDs are found", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
	{ OFFS(info), "monitor PID info changes", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE},
	{ OFFS(full), "full dump of PID properties (always on if XML)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE},
	{ OFFS(pcr), "dump M2TS PCR info", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE},
	{ OFFS(speed), "set playback command speed. If negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0},
	{ OFFS(start), "set playback start offset. A negative value means percent of media duration with -1 equal to duration", GF_PROP_DOUBLE, "0.0", NULL, 0},
	{ OFFS(dur), "set inspect duration", GF_PROP_FRACTION, "0/0", NULL, 0},
	{ OFFS(analyze), "analyze sample content (NALU, OBU), similar to `-bsdbg` option of reframer filters\n"
	"- off: no analyzing\n"
	"- on: simple analyzing\n"
	"- bs: log bitstream syntax (all elements read from bitstream)\n"
	"- full: log bitstream syntax and bit sizes signaled as `(N)` after field value, except 1-bit fields (omitted)", GF_PROP_UINT, "off", "off|on|bs|full", GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE},
	{ OFFS(xml), "use xml formatting (implied if (-analyze]() is set) and disable [-fmt]()", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE},
	{ OFFS(crc), "dump crc of samples of subsamples (NALU or OBU) when analyzing", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE},
	{ OFFS(fftmcd), "consider timecodes use ffmpeg-compatible signaling rather than QT compliant one", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE},
	{ OFFS(dtype), "dump property type", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE},
	{ OFFS(buffer), "set playback buffer in ms", GF_PROP_UINT, "0", NULL, GF_ARG_HINT_EXPERT},
	{ OFFS(mbuffer), "set max buffer occupancy in ms. If less than buffer, use buffer", GF_PROP_UINT, "0", NULL, 0},
	{ OFFS(rbuffer), "rebuffer trigger in ms. If 0 or more than buffer, disable rebuffering", GF_PROP_UINT, "0", NULL, GF_FS_ARG_UPDATE},
	{ OFFS(stats), "compute statistics for PIDs", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
	{ OFFS(timeout), "timeout in ms when doing simple inspection in case no packets are received on some PIDs", GF_PROP_UINT, "5000", NULL, GF_ARG_HINT_EXPERT},

	{ OFFS(test), "skip predefined set of properties, used for test mode\n"
		"- no: no properties skipped\n"
		"- noprop: all properties/info changes on PID are skipped, only packets are dumped\n"
		"- network: URL/path dump, cache state, file size properties skipped (used for hashing network results)\n"
		"- netx: same as network but skip track duration and templates (used for hashing progressive load of fmp4)\n"
		"- encode: same as network plus skip decoder config (used for hashing encoding results)\n"
		"- encx: same as encode and skip bitrates, media data size and co\n"
		"- nocrc: disable packet CRC dump\n"
		"- nobr: skip bitrate"
		, GF_PROP_UINT, "no", "no|noprop|network|netx|encode|encx|nocrc|nobr", GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE},
	{0}
};

static const GF_FilterCapability InspectCaps[] =
{
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN),
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE),
	{0},
	CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_FAKE, GF_TRUE)
};

const GF_FilterRegister InspectRegister = {
	.name = "inspect",
	GF_FS_SET_DESCRIPTION("Packet inspector")
	GF_FS_SET_HELP("The inspect filter can be used to dump PID and packets. It may also be used to check parts of payload of the packets.\n"
	"\n"
	"The default options inspect only PID changes.\n"
	"If [-full]() is not set, [-mode]() is forced to `frame` and PID properties are formatted in human-readable form, one PID per line.\n"
	"Otherwise, all properties are dumped.\n"
	"Note: specifying [-xml](), [-analyze](), [-fmt]() or using `-for-test` will force [-full]() to true.\n"
	"\n"
	"# Custom property dumping\n"
	"The packet inspector can be configured to dump specific properties of packets using [-fmt]().\n"
	"When the option is not present, all properties are dumped. Otherwise, only properties identified by `$TOKEN$` "
	"are printed. You may use '$', '@' or '%' for `TOKEN` separator. `TOKEN` can be:\n"
	"- pn: packet (frame in framed mode) number\n"
	"- dts: decoding time stamp in stream timescale, N/A if not available\n"
	"- ddts: difference between current and previous packets decoding time stamp in stream timescale, N/A if not available\n"
	"- cts: composition time stamp in stream timescale, N/A if not available\n"
	"- dcts: difference between current and previous packets composition time stamp in stream timescale, N/A if not available\n"
	"- ctso: difference between composition time stamp and decoding time stamp in stream timescale, N/A if not available\n"
	"- tmcd: timecode as provided in SEI, N/A if not available (requires reframer)\n"
	"- dur: duration in stream timescale\n"
	"- frame: framing status\n"
	"  - interface: complete AU, interface object (no size info). Typically a GL texture\n"
	"  - frame_full: complete AU\n"
	"  - frame_start: beginning of frame\n"
	"  - frame_end: end of frame\n"
	"  - frame_cont: frame continuation (not beginning, not end)\n"
	"- sap or rap: SAP type of the frame\n"
	"- ilace: interlacing flag (0: progressive, 1: top field, 2: bottom field)\n"
	"- corr: corrupted packet flag\n"
	"- seek: seek flag\n"
	"- bo: byte offset in source, N/A if not available\n"
	"- roll: roll info\n"
	"- crypt: crypt flag\n"
	"- vers: carousel version number\n"
	"- size: size of packet\n"
	"- csize: total size of packets received so far\n"
	"- crc: 32 bit CRC of packet\n"
	"- lf or n: insert new line\n"
	"- t: insert tab\n"
	"- data: hex dump of packet (__big output!__) or as string if legal UTF-8\n"
	"- lp: leading picture flag\n"
	"- depo: depends on other packet flag\n"
	"- depf: is depended on other packet flag\n"
	"- red: redundant coding flag\n"
	"- start: packet composition time as HH:MM:SS.ms\n"
	"- startc: packet composition time as HH:MM:SS,ms\n"
	"- end: packet end time as HH:MM:SS.ms\n"
	"- endc: packet end time as HH:MM:SS,ms\n"
	"- ck: clock type used for PCR discontinuities\n"
	"- pcr: MPEG-2 TS last PCR, n/a if not available\n"
	"- pcrd: difference between last PCR and decoding time, n/a if no PCR available\n"
	"- pcrc: difference between last PCR and composition time, n/a if no PCR available\n"
	"- P4CC: 4CC of packet property\n"
	"- PropName: Name of packet property\n"
	"- pid.P4CC: 4CC of PID property\n"
	"- pid.PropName: Name of PID property\n"
	"- fn: Filter name\n"
	"\n"
	"EX fmt=\"PID $pid.ID$ packet $pn$ DTS $dts$ CTS $cts$ $lf$\"\n"
	"This dumps packet number, cts and dts as follows: `PID 1 packet 10 DTS 100 CTS 108 \\n`\n"
	"  \n"
	"An unrecognized keyword or missing property will resolve to an empty string.\n"
	"\n"
	"Note: when dumping in interleaved mode, there is no guarantee that the packets will be dumped in their original sequence order since the inspector fetches one packet at a time on each PID.\n"
	"\n"
	"# Note on playback\n"
	"Buffering can be enabled to check the input filter chain behaviour, e.g. check HAS adaptation logic.\n"
	"The various buffering options control when packets are consumed. Buffering events are logged using `media@info` for state changes and `media@debug` for media filling events.\n"
	"The [-speed]() option is only used to configure the filter chain but is ignored by the filter when consuming packets.\n"
	"If real-time consumption is required, a reframer filter must be setup before the inspect filter.\n"
	"EX gpac -i SRC reframer:rt=on inspect:buffer=10000:rbuffer=1000:mbuffer=30000:speed=2\n"
	"This will play the session at 2x speed, using 30s of maximum buffering, consuming packets after 10s of media are ready and rebuffering if less than 1s of media.\n"
	"\n"
	)
	.private_size = sizeof(GF_InspectCtx),
	.flags = GF_FS_REG_EXPLICIT_ONLY,
	.max_extra_pids = (u32) -1,
	.args = InspectArgs,
	SETCAPS(InspectCaps),
	.initialize = inspect_initialize,
	.finalize = inspect_finalize,
	.process = inspect_process,
	.process_event = inspect_process_event,
	.configure_pid = inspect_configure_pid,
	.update_arg = inspect_update_arg,
	.hint_class_type = GF_FS_CLASS_TOOL
};

static const GF_FilterCapability ProberCaps[] =
{
	//accept any stream but files, framed
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE),
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD),
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT),
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_CODECID, GF_CODECID_NONE),
	CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
	{0},
	CAP_UINT(GF_CAPS_INPUT,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE),
	CAP_UINT(GF_CAPS_INPUT,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD),
	CAP_UINT(GF_CAPS_INPUT,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT),
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_CODECID, GF_CODECID_NONE),
	CAP_UINT(GF_CAPS_INPUT_EXCLUDED,  GF_PROP_PID_CODECID, GF_CODECID_RAW),
	CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
	{0},
};

static const GF_FilterArgs ProbeArgs[] =
{
	{ OFFS(log), "set probe log filename to print number of streams\n"
	"- _any: target file path and name\n"
	"- stderr: dump to stderr\n"
	"- stdout: dump to stdout\n"
	"- GLOG: use GPAC logs `app@info\n"
	"- null: silent mode", GF_PROP_STRING,
#ifdef GPAC_CONFIG_ANDROID
		"GLOG"
#else
		"stdout"
#endif
	, "_any|stderr|stdout|GLOG|null", 0},
	{0}
};


const GF_FilterRegister ProbeRegister = {
	.name = "probe",
	GF_FS_SET_DESCRIPTION("Source prober")
	GF_FS_SET_HELP("The Probe filter is used by applications (typically `MP4Box`) to query demultiplexed PIDs (audio, video, ...) available in a source chain.\n\n"
	"The filter outputs the number of input PIDs in the file specified by [-log]().\n"
	"It is up to the app developer to query input PIDs of the prober and take appropriated decisions.")
	.private_size = sizeof(GF_InspectCtx),
	.flags = GF_FS_REG_EXPLICIT_ONLY | GF_FS_REG_TEMP_INIT,
	.max_extra_pids = (u32) -1,
	.initialize = inspect_initialize,
	.args = ProbeArgs,
	SETCAPS(ProberCaps),
	.finalize = inspect_finalize,
	.process = inspect_process,
	.configure_pid = inspect_configure_pid,
	.hint_class_type = GF_FS_CLASS_TOOL,
};

const GF_FilterRegister *inspect_register(GF_FilterSession *session)
{
	return &InspectRegister;
}

const GF_FilterRegister *probe_register(GF_FilterSession *session)
{
	return &ProbeRegister;
}
#else
const GF_FilterRegister *inspect_register(GF_FilterSession *session)
{
	return NULL;
}

const GF_FilterRegister *probe_register(GF_FilterSession *session)
{
	return NULL;
}
#endif // GPAC_DISABLE_INSPECT
