Commit message: fixed #1876
CVE: CVE-2021-40592
CWE: CWE-703
Vulnerability description: GPAC version before commit 71460d72ec07df766dab0a4d52687529f3efcf0a (version v1.0.1 onwards) contains loop with unreachable exit condition ('infinite loop') vulnerability in ISOBMFF reader filter, isoffin_read.c. Function isoffin_process() can result in DoS by infinite loop. To exploit, the victim must open a specially crafted mp4 file.

Project: gpac
File: isoffin_read.c

ISOMChannel *isor_get_channel(ISOMReader *reader, GF_FilterPid *pid)
{
	u32 i=0;
	ISOMChannel *ch;
	while ((ch = (ISOMChannel *)gf_list_enum(reader->channels, &i))) {
		if (ch->pid == pid) return ch;
	}
	return NULL;
}

Project: gpac
File: isoffin_read.c

void isor_set_crypt_config(ISOMChannel *ch)
{
	GF_ISOFile *mov = ch->owner->mov;
	u32 track = ch->track;
	u32 scheme_type, scheme_version, i, count;
	const char *kms_uri, *scheme_uri;
	Bool selectiveEncryption=0;
	u32 IVLength=0;
	u32 KeyIndicationLength=0;
	const char *txtHdr=NULL;
	const char *contentID=NULL;
	u32 txtHdrLen=0;
	u64 plainTextLen=0;
	u32 crypt_type=0;
	u32 stsd_idx = ch->owner->stsd ? ch->owner->stsd : 1;

	if (!ch->is_encrypted) return;

	scheme_type = scheme_version = 0;
	kms_uri = scheme_uri = NULL;

	/*ugly fix to detect when an stsd uses both clear and encrypted sample descriptions*/
	count = gf_isom_get_sample_description_count(ch->owner->mov, ch->track);
	if (count>1) {
		u32 first_crypted_stsd = 0;
		u32 nb_same_mtype = 1;
		u32 nb_clear=0, nb_encrypted=0;
		u32 base_subtype = 0;
		Bool first_is_clear = GF_FALSE;
		for (i=0; i<count; i++) {
			u32 mtype = gf_isom_get_media_subtype(ch->owner->mov, ch->track, i+1);
			if ( gf_isom_is_media_encrypted(ch->owner->mov, track, i+1)) {
				gf_isom_get_original_format_type(ch->owner->mov, ch->track, i+1, &mtype);
				nb_encrypted++;
				if (!first_crypted_stsd) first_crypted_stsd = i+1;
			} else {
				nb_clear++;
				if (!i) first_is_clear = GF_TRUE;
			}
			if (!i) base_subtype = mtype;
			else if (base_subtype==mtype) {
				nb_same_mtype++;
			}
		}
		if ((nb_same_mtype==count) && (nb_clear==nb_encrypted)) {
			gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_STSD_MODE, &PROP_UINT(first_is_clear ? 1 : 2) );
			stsd_idx = first_crypted_stsd;
		}

	}

	if (gf_isom_is_ismacryp_media(mov, track, stsd_idx)) {
		gf_isom_get_ismacryp_info(mov, track, stsd_idx, NULL, &scheme_type, &scheme_version, &scheme_uri, &kms_uri, &selectiveEncryption, &IVLength, &KeyIndicationLength);
	} else if (gf_isom_is_omadrm_media(mov, track, stsd_idx)) {
		//u8 hash[20];
		gf_isom_get_omadrm_info(mov, track, stsd_idx, NULL, &scheme_type, &scheme_version, &contentID, &kms_uri, &txtHdr, &txtHdrLen, &plainTextLen, &crypt_type, &selectiveEncryption, &IVLength, &KeyIndicationLength);

		//gf_media_get_file_hash(gf_isom_get_filename(mov), hash);
	} else if (gf_isom_is_cenc_media(mov, track, stsd_idx)) {
		ch->is_cenc = GF_TRUE;

		gf_isom_get_cenc_info(ch->owner->mov, ch->track, stsd_idx, NULL, &scheme_type, &scheme_version);

		//if no PSSH declared, DO update the properties (PSSH is not mandatory)
	} else if (gf_isom_is_adobe_protection_media(mov, track, stsd_idx)) {
		u32 ofmt;
		scheme_version = 1;
		scheme_type = GF_ISOM_ADOBE_SCHEME;
		const char *metadata = NULL;

		gf_isom_get_adobe_protection_info(mov, track, stsd_idx, &ofmt, &scheme_type, &scheme_version, &metadata);
		if (metadata)
			gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ADOBE_CRYPT_META, &PROP_DATA((char *)metadata, (u32) strlen(metadata) ) );
	}

	gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROTECTION_SCHEME_TYPE, &PROP_4CC(scheme_type) );
	gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROTECTION_SCHEME_VERSION, &PROP_UINT(scheme_version) );
	if (scheme_uri) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROTECTION_SCHEME_URI, &PROP_STRING((char*) scheme_uri) );
	if (kms_uri) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROTECTION_KMS_URI, &PROP_STRING((char*) kms_uri) );

	if (selectiveEncryption) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISMA_SELECTIVE_ENC, &PROP_BOOL(GF_TRUE) );
	if (IVLength) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISMA_IV_LENGTH, &PROP_UINT(IVLength) );
	if (KeyIndicationLength) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISMA_KI_LENGTH, &PROP_UINT(KeyIndicationLength) );
	if (crypt_type) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_OMA_CRYPT_TYPE, &PROP_UINT(crypt_type) );
	if (contentID) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_OMA_CID, &PROP_STRING(contentID) );
	if (txtHdr) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_OMA_TXT_HDR, &PROP_STRING(txtHdr) );
	if (plainTextLen) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_OMA_CLEAR_LEN, &PROP_LONGUINT(plainTextLen) );

	if (ch->is_cenc) {
		const u8 *key_info;
		u32 key_info_size;
		u32 container_type;

		isor_declare_pssh(ch);

		gf_isom_cenc_get_default_info(ch->owner->mov, ch->track, stsd_idx, &container_type, &ch->pck_encrypted, &ch->crypt_byte_block, &ch->skip_byte_block, &key_info, &key_info_size);

		gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_STORE, &PROP_4CC(container_type) );

		gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ENCRYPTED, &PROP_BOOL(ch->pck_encrypted) );

		if (ch->skip_byte_block || ch->crypt_byte_block) {
			gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_PATTERN, &PROP_FRAC_INT(ch->skip_byte_block, ch->crypt_byte_block) );
		}
		gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_KEY_INFO, &PROP_DATA((u8 *)key_info, key_info_size) );
		ch->key_info_crc = gf_crc_32(key_info, key_info_size);
	}
}

Project: gpac
File: isoffin_read_ch.c

static void isor_update_cenc_info(ISOMChannel *ch, Bool for_item)
{
	GF_Err e;
	Bool Is_Encrypted;
	u32 out_size;
	u8 crypt_byte_block, skip_byte_block;
	u8 piff_info[20];
	u8 *key_info = NULL;
	u32 key_info_size = 0;
	u8 item_mkey = 0;

	//this will be skipped anyways, don't fectch ...
	if (ch->owner->stsd && (ch->last_sample_desc_index != ch->owner->stsd) && ch->sample) {
		return;
	}


	out_size = ch->sai_alloc_size;
	if (for_item) {
		u32 aux_info_param=0;
		e = gf_isom_extract_meta_item_get_cenc_info(ch->owner->mov, GF_TRUE, 0, ch->item_id, &Is_Encrypted, &skip_byte_block, &crypt_byte_block, (const u8 **) &key_info, &key_info_size, &aux_info_param, &ch->sai_buffer, &out_size, &ch->sai_alloc_size);

		/*The ienc property is always exposed as a multiple key info in GPAC
		However the type of SAI may be single-key (aux_info_param==0) or multiple-key (aux_info_param==1) for the same ienc used
		We therefore temporary force the key info type to single key if v0 SAI CENC are used
		Note that this is thread safe as this filter is the only one using the opened file
		*/
		if (aux_info_param==0) {
			item_mkey = key_info[0];
		}
	} else {
		e = gf_isom_get_sample_cenc_info(ch->owner->mov, ch->track, ch->sample_num, &Is_Encrypted, &crypt_byte_block, &skip_byte_block, (const u8 **) &key_info, &key_info_size);
	}
	if (!key_info) {
		piff_info[0] = 0;
		piff_info[1] = 0;
		piff_info[2] = 0;
		piff_info[3] = key_info_size;
		memset(piff_info + 4, 0, 16);
		key_info_size = 20;
		key_info = (u8 *) piff_info;
	}


	if (!for_item && (e==GF_OK) && Is_Encrypted) {
		e = gf_isom_cenc_get_sample_aux_info(ch->owner->mov, ch->track, ch->sample_num, ch->last_sample_desc_index, NULL, &ch->sai_buffer, &out_size);
	}

	if (out_size > ch->sai_alloc_size) ch->sai_alloc_size = out_size;
	ch->sai_buffer_size = out_size;

	if (e) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] Failed to fetch CENC auxiliary info for %s %d: %s\n", for_item ? "item" : "track", for_item ? ch->item_id : ch->track, gf_error_to_string(e) ));
		return;
	}

	ch->pck_encrypted = Is_Encrypted;
	ch->cenc_ki = NULL;

	/*notify change of IV/KID only when packet is encrypted
	1- these info are ignored when packet is not encrypted
	2- this allows us to define the initial CENC state for multi-stsd cases*/
	if (Is_Encrypted) {
		u32 ki_crc;

		if ((ch->crypt_byte_block != crypt_byte_block) || (ch->skip_byte_block != skip_byte_block)) {
			ch->crypt_byte_block = crypt_byte_block;
			ch->skip_byte_block = skip_byte_block;

			gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_PATTERN, &PROP_FRAC_INT(ch->skip_byte_block, ch->crypt_byte_block) );
		}
		if (item_mkey)
			key_info[0] = 0;

		ki_crc = gf_crc_32(key_info, key_info_size);
		if (ch->key_info_crc != ki_crc) {
			ch->key_info_crc = ki_crc;
			gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_KEY_INFO, &PROP_DATA((u8 *)key_info, key_info_size) );
		}

		if (item_mkey)
			key_info[0] = item_mkey;

		ch->cenc_ki = gf_filter_pid_get_property(ch->pid, GF_PROP_PID_CENC_KEY_INFO);
	}
}

Project: gpac
File: isoffin_read_ch.c

static void isor_reset_seq_list(GF_List *list)
{
	while (gf_list_count(list)) {
		GF_NALUFFParam *sl = gf_list_pop_back(list);
		gf_free(sl->data);
		gf_free(sl);
	}
}

Project: gpac
File: isoffin_read_ch.c

void isor_reader_check_config(ISOMChannel *ch)
{
	u32 nalu_len, reset_state;
	if (!ch->check_hevc_ps && !ch->check_avc_ps && !ch->check_vvc_ps && !ch->check_mhas_pl) return;

	if (!ch->sample) return;
	ch->xps_mask = 0;

	//we cannot touch the payload if encrypted but not CENC !!
	if (ch->is_encrypted && !ch->is_cenc)
		return;

	if (ch->check_mhas_pl) {
		//we cannot touch the payload if encrypted !!
		if (ch->pck_encrypted) return;
		u64 ch_layout = 0;
		s32 PL = gf_mpegh_get_mhas_pl(ch->sample->data, ch->sample->dataLength, &ch_layout);
		if (PL>0) {
			gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT((u32) PL));
			ch->check_mhas_pl = GF_FALSE;
			if (ch_layout)
				gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(ch_layout));
		}
		return;
	}
	//analyze mode, do not rewrite
	if (ch->owner->analyze) return;

	//we cannot touch the payload if encrypted but no SAI buffer
	if (ch->pck_encrypted && !ch->sai_buffer)
		return;

	nalu_len = 4;
	if (ch->avcc) nalu_len = ch->avcc->nal_unit_size;
	else if (ch->hvcc) nalu_len = ch->hvcc->nal_unit_size;
	else if (ch->vvcc) nalu_len = ch->vvcc->nal_unit_size;

	reset_state = 0;

	if (!ch->nal_bs) ch->nal_bs = gf_bs_new(ch->sample->data, ch->sample->dataLength, GF_BITSTREAM_READ);
	else gf_bs_reassign_buffer(ch->nal_bs, ch->sample->data, ch->sample->dataLength);

	while (gf_bs_available(ch->nal_bs)) {
		Bool replace_nal = GF_FALSE;
		u8 nal_type=0;
		u32 pos = (u32) gf_bs_get_position(ch->nal_bs);
		u32 size = gf_bs_read_int(ch->nal_bs, nalu_len*8);
		//this takes care of size + pos + nalu_len > 0 but (s32) size < 0 ...
		if (ch->sample->dataLength < size) break;
		if (ch->sample->dataLength < size + pos + nalu_len) break;
		if (ch->check_avc_ps) {
			u8 hdr = gf_bs_peek_bits(ch->nal_bs, 8, 0);
			nal_type = hdr & 0x1F;
			switch (nal_type) {
			case GF_AVC_NALU_SEQ_PARAM:
			case GF_AVC_NALU_SEQ_PARAM_EXT:
			case GF_AVC_NALU_PIC_PARAM:
				replace_nal = GF_TRUE;
				break;
			}
		}
		else if (ch->check_hevc_ps) {
			u8 hdr = gf_bs_peek_bits(ch->nal_bs, 8, 0);
			nal_type = (hdr & 0x7E) >> 1;
			switch (nal_type) {
			case GF_HEVC_NALU_VID_PARAM:
			case GF_HEVC_NALU_SEQ_PARAM:
			case GF_HEVC_NALU_PIC_PARAM:
				replace_nal = GF_TRUE;
				break;
			}
		}
		else if (ch->check_vvc_ps) {
			u8 hdr = gf_bs_peek_bits(ch->nal_bs, 8, 1);
			nal_type = hdr >> 3;
			switch (nal_type) {
			case GF_VVC_NALU_VID_PARAM:
			case GF_VVC_NALU_SEQ_PARAM:
			case GF_VVC_NALU_PIC_PARAM:
			case GF_VVC_NALU_DEC_PARAM:
				replace_nal = GF_TRUE;
				break;
			}
		}
		gf_bs_skip_bytes(ch->nal_bs, size);

		if (replace_nal) {
			u32 move_size = ch->sample->dataLength - size - pos - nalu_len;
			isor_replace_nal(ch, ch->sample->data + pos + nalu_len, size, nal_type, &reset_state);
			if (move_size)
				memmove(ch->sample->data + pos, ch->sample->data + pos + size + nalu_len, ch->sample->dataLength - size - pos - nalu_len);

			ch->sample->dataLength -= size + nalu_len;
			gf_bs_reassign_buffer(ch->nal_bs, ch->sample->data, ch->sample->dataLength);
			gf_bs_seek(ch->nal_bs, pos);

			//remove nal from clear subsample range
			if (ch->pck_encrypted)
				isor_sai_bytes_removed(ch, pos, nalu_len+size);
		}
	}

	if (reset_state) {
		u8 *dsi=NULL;
		u32 dsi_size=0;
		if (ch->check_avc_ps) {
			gf_odf_avc_cfg_write(ch->avcc, &dsi, &dsi_size);
		}
		else if (ch->check_hevc_ps) {
			gf_odf_hevc_cfg_write(ch->hvcc, &dsi, &dsi_size);
		}
		else if (ch->check_vvc_ps) {
			gf_odf_vvc_cfg_write(ch->vvcc, &dsi, &dsi_size);
		}
		if (dsi && dsi_size) {
			u32 dsi_crc = gf_crc_32(dsi, dsi_size);
			if (ch->dsi_crc == dsi_crc) {
				gf_free(dsi);
			} else {
				ch->dsi_crc = dsi_crc;
				gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) );
			}
		}
	}
}

Project: gpac
File: isoffin_read.c

void isor_declare_pssh(ISOMChannel *ch)
{
	u32 i, PSSH_count;
	u8 *psshd;
	GF_BitStream *pssh_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
	u32 s;

	PSSH_count = gf_isom_get_pssh_count(ch->owner->mov);
	gf_bs_write_u32(pssh_bs, PSSH_count);

	/*fill PSSH in the structure. We will free it in CENC_Setup*/
	for (i=0; i<PSSH_count; i++) {
		bin128 SystemID;
		u32 version;
		u32 KID_count;
		bin128 *KIDs;
		u32 private_data_size;
		u8 *private_data;
		gf_isom_get_pssh_info(ch->owner->mov, i+1, SystemID, &version, &KID_count, (const bin128 **) & KIDs, (const u8 **) &private_data, &private_data_size);

		gf_bs_write_data(pssh_bs, SystemID, 16);
		gf_bs_write_u32(pssh_bs, version);
		gf_bs_write_u32(pssh_bs, KID_count);
		for (s=0; s<KID_count; s++) {
			gf_bs_write_data(pssh_bs, KIDs[s], 16);
		}
		gf_bs_write_u32(pssh_bs, private_data_size);
		gf_bs_write_data(pssh_bs, private_data, private_data_size);
	}
	gf_bs_get_content(pssh_bs, &psshd, &s);
	gf_bs_del(pssh_bs);
	gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_PSSH, & PROP_DATA_NO_COPY(psshd, s) );
}

Project: gpac
File: isoffin_read.c

static GF_Err isoffin_process(GF_Filter *filter)
{
	ISOMReader *read = gf_filter_get_udta(filter);
	u32 i, count = gf_list_count(read->channels);
	Bool is_active = GF_FALSE;
	Bool in_is_eos = GF_FALSE;
	Bool check_forced_end = GF_FALSE;
	Bool has_new_data = GF_FALSE;
	u64 min_offset_plus_one = 0;
	u32 nb_forced_end=0;
	if (read->in_error)
		return read->in_error;

	if (read->pid) {
		Bool fetch_input = GF_TRUE;

		//we failed at loading the init segment during a dash switch, retry
		if (!read->is_partial_download && !read->mem_load_mode && (read->moov_not_loaded==2) ) {
			isoffin_configure_pid(filter, read->pid, GF_FALSE);
			if (read->moov_not_loaded) return GF_OK;
		}
		if (read->mem_load_mode==2) {
			if (!read->force_fetch && read->mem_blob.size > read->mstore_size) {
				fetch_input = GF_FALSE;
			}
			read->force_fetch = GF_FALSE;
		}
		while (fetch_input) {
			GF_FilterPacket *pck = gf_filter_pid_get_packet(read->pid);
			if (!pck) {
				//we issued a seek, wait for the first packet to be received before fetching channels
				//otherwise we could end up reading from the wrong cache
				if (read->wait_for_source) {
					//something went wrong during the seek request
					if (gf_filter_pid_is_eos(read->pid))
						return GF_EOS;
					return GF_OK;
				}
				break;
			}
			read->wait_for_source = GF_FALSE;

			if (read->mem_load_mode) {
				u32 data_size;
				const u8 *pck_data = gf_filter_pck_get_data(pck, &data_size);
				isoffin_push_buffer(filter, read, pck_data, data_size);
			}
			//we just had a switch but init seg is not completely done: input packet is only a part of the init, drop it
			else if (read->moov_not_loaded==2) {
				gf_filter_pid_drop_packet(read->pid);
				return GF_OK;
			}
			gf_filter_pid_drop_packet(read->pid);
			has_new_data = GF_TRUE;
			if (read->in_error)
				return read->in_error;
		}
		if (gf_filter_pid_is_eos(read->pid)) {
			read->input_loaded = GF_TRUE;
			in_is_eos = GF_TRUE;
		}
		if (read->input_is_stop) {
			read->input_loaded = GF_TRUE;
			in_is_eos = GF_TRUE;
			read->input_is_stop = GF_FALSE;
		}
		if (!read->frag_type && read->input_loaded) {
			in_is_eos = GF_TRUE;
		}
        //segment is invalid, wait for eos on input an send eos on all channels
        if (read->invalid_segment) {
            if (!in_is_eos) return GF_OK;
            read->invalid_segment = GF_FALSE;

            for (i=0; i<count; i++) {
                ISOMChannel *ch = gf_list_get(read->channels, i);
                if (!ch->playing) {
                    continue;
                }
                if (!ch->eos_sent) {
                    ch->eos_sent = GF_TRUE;
                    gf_filter_pid_set_eos(ch->pid);
                }
            }
            read->eos_signaled = GF_TRUE;
            return GF_EOS;
        }
	} else if (read->extern_mov) {
		in_is_eos = GF_TRUE;
		read->input_loaded = GF_TRUE;
	}
	if (read->moov_not_loaded==1) {
		if (read->mem_load_mode)
			return GF_OK;
		read->moov_not_loaded = GF_FALSE;
		return isoffin_setup(filter, read);
	}

	if (read->refresh_fragmented) {
		const GF_PropertyValue *prop;

		if (in_is_eos) {
			read->refresh_fragmented = GF_FALSE;
		} else {
			prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILE_CACHED);
			if (prop && prop->value.boolean)
				read->refresh_fragmented = GF_FALSE;
		}

		if (has_new_data) {
			u64 bytesMissing=0;
			GF_Err e;
			const char *new_url = NULL;
			prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILEPATH);
			if (prop) new_url = prop->value.string;

			e = gf_isom_refresh_fragmented(read->mov, &bytesMissing, new_url);

			if (e && (e!= GF_ISOM_INCOMPLETE_FILE)) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Failed to refresh current segment: %s\n", gf_error_to_string(e) ));
				read->refresh_fragmented = GF_FALSE;
			} else {
				GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Refreshing current segment at UTC "LLU" - "LLU" bytes still missing - input is EOS %d\n", gf_net_get_utc(), bytesMissing, in_is_eos));
			}

			if (!read->refresh_fragmented && (e==GF_ISOM_INCOMPLETE_FILE)) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[IsoMedia] Incomplete Segment received - "LLU" bytes missing but EOF found\n", bytesMissing ));
			}

#ifndef GPAC_DISABLE_LOG
			if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_DEBUG)) {
				for (i=0; i<count; i++) {
					ISOMChannel *ch = gf_list_get(read->channels, i);
					GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] refresh track %d fragment - cur sample %d - new sample count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) ));
				}
			}
#endif
			isor_check_producer_ref_time(read);
			if (!read->frag_type)
				read->refresh_fragmented = GF_FALSE;
		}
	}

	for (i=0; i<count; i++) {
		u8 *data;
		u32 nb_pck=50;
		ISOMChannel *ch;
		ch = gf_list_get(read->channels, i);
		if (!ch->playing) {
			nb_forced_end++;
			continue;
		}
		//eos not sent on this channel, we are active
		if (!ch->eos_sent)
			is_active = GF_TRUE;

		while (nb_pck) {
			ch->sample_data_offset = 0;
			if (!read->full_segment_flush && gf_filter_pid_would_block(ch->pid) )
				break;

			if (ch->item_id) {
				isor_reader_get_sample_from_item(ch);
			} else {
				isor_reader_get_sample(ch);
			}

			if (read->stsd && (ch->last_sample_desc_index != read->stsd) && ch->sample) {
				isor_reader_release_sample(ch);
				continue;
			}
			if (ch->sample) {
				u32 sample_dur;
				u8 dep_flags;
				u8 *subs_buf;
				u32 subs_buf_size;
				GF_FilterPacket *pck;
				if (ch->needs_pid_reconfig) {
					isor_update_channel_config(ch);
					ch->needs_pid_reconfig = GF_FALSE;
				}

				//we have at least two samples, update GF_PROP_PID_HAS_SYNC if needed
				if (ch->check_has_rap && (gf_isom_get_sample_count(ch->owner->mov, ch->track)>1) && (gf_isom_has_sync_points(ch->owner->mov, ch->track)==1)) {
					ch->check_has_rap = GF_FALSE;
					ch->has_rap = GF_TRUE;
					gf_filter_pid_set_property(ch->pid, GF_PROP_PID_HAS_SYNC, &PROP_BOOL(ch->has_rap) );
				}

				//strip param sets from payload, trigger reconfig if needed
				isor_reader_check_config(ch);

				if (read->nodata) {
					pck = gf_filter_pck_new_shared(ch->pid, NULL, ch->sample->dataLength, NULL);
					if (!pck) return GF_OUT_OF_MEM;
				} else {
					pck = gf_filter_pck_new_alloc(ch->pid, ch->sample->dataLength, &data);
					if (!pck) return GF_OUT_OF_MEM;

					memcpy(data, ch->sample->data, ch->sample->dataLength);
				}
				gf_filter_pck_set_dts(pck, ch->dts);
				gf_filter_pck_set_cts(pck, ch->cts);
				if (ch->sample->IsRAP==-1) {
					gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1);
					ch->redundant = 1;
				} else {
					gf_filter_pck_set_sap(pck, (GF_FilterSAPType) ch->sample->IsRAP);
				}

				if (ch->sap_3)
					gf_filter_pck_set_sap(pck, GF_FILTER_SAP_3);
				else if (ch->sap_4_type) {
					gf_filter_pck_set_sap(pck, (ch->sap_4_type==GF_ISOM_SAMPLE_PREROLL) ? GF_FILTER_SAP_4_PROL : GF_FILTER_SAP_4);
					gf_filter_pck_set_roll_info(pck, ch->roll);
				}

				sample_dur = ch->au_duration;
				if (ch->sample->nb_pack)
					sample_dur *= ch->sample->nb_pack;
				gf_filter_pck_set_duration(pck, sample_dur);
				gf_filter_pck_set_seek_flag(pck, ch->seek_flag);

				//for now we only signal xPS mask for non-sap
				if (ch->xps_mask && !gf_filter_pck_get_sap(pck) ) {
					gf_filter_pck_set_property(pck, GF_PROP_PCK_XPS_MASK, &PROP_UINT(ch->xps_mask) );
				}

				dep_flags = ch->isLeading;
				dep_flags <<= 2;
				dep_flags |= ch->dependsOn;
				dep_flags <<= 2;
				dep_flags |= ch->dependedOn;
				dep_flags <<= 2;
				dep_flags |= ch->redundant;

				if (dep_flags)
					gf_filter_pck_set_dependency_flags(pck, dep_flags);

				gf_filter_pck_set_crypt_flags(pck, ch->pck_encrypted ? GF_FILTER_PCK_CRYPT : 0);
				gf_filter_pck_set_seq_num(pck, ch->sample_num);


				subs_buf = gf_isom_sample_get_subsamples_buffer(read->mov, ch->track, ch->sample_num, &subs_buf_size);
				if (subs_buf) {
					gf_filter_pck_set_property(pck, GF_PROP_PCK_SUBS, &PROP_DATA_NO_COPY(subs_buf, subs_buf_size) );
				}

				if (ch->sai_buffer && ch->pck_encrypted) {
					assert(ch->sai_buffer_size);
					gf_filter_pck_set_property(pck, GF_PROP_PCK_CENC_SAI, &PROP_DATA(ch->sai_buffer, ch->sai_buffer_size) );
				}

				if (read->sigfrag) {
					GF_ISOFragmentBoundaryInfo finfo;
					if (gf_isom_sample_is_fragment_start(read->mov, ch->track, ch->sample_num, &finfo) ) {
						u64 start=0;
						u32 traf_start = finfo.seg_start_plus_one ? 2 : 1;

						if (finfo.seg_start_plus_one)
							gf_filter_pck_set_property(pck, GF_PROP_PCK_CUE_START, &PROP_BOOL(GF_TRUE));

						gf_filter_pck_set_property(pck, GF_PROP_PCK_FRAG_START, &PROP_UINT(traf_start));

						start = finfo.frag_start;
						if (finfo.seg_start_plus_one) start = finfo.seg_start_plus_one-1;
						gf_filter_pck_set_property(pck, GF_PROP_PCK_FRAG_RANGE, &PROP_FRAC64_INT(start, finfo.mdat_end));
						if (finfo.moof_template) {
							gf_filter_pck_set_property(pck, GF_PROP_PCK_MOOF_TEMPLATE, &PROP_DATA((u8 *)finfo.moof_template, finfo.moof_template_size));
						}
						if (finfo.sidx_end) {
							gf_filter_pck_set_property(pck, GF_PROP_PCK_SIDX_RANGE, &PROP_FRAC64_INT(finfo.sidx_start , finfo.sidx_end));
						}

						if (read->seg_name_changed) {
							const GF_PropertyValue *p = gf_filter_pid_get_property(read->pid, GF_PROP_PID_URL);
							read->seg_name_changed = GF_FALSE;
							if (p && p->value.string) {
								gf_filter_pck_set_property(pck, GF_PROP_PID_URL, &PROP_STRING(p->value.string));
							}
						}
					}
				}
				if (ch->sender_ntp) {
					gf_filter_pck_set_property(pck, GF_PROP_PCK_SENDER_NTP, &PROP_LONGUINT(ch->sender_ntp));
					if (ch->ntp_at_server_ntp) {
						gf_filter_pck_set_property(pck, GF_PROP_PCK_RECEIVER_NTP, &PROP_LONGUINT(ch->ntp_at_server_ntp));
					}
				}
				ch->eos_sent = GF_FALSE;

				//this might not be the true end of stream
				if ((ch->streamType==GF_STREAM_AUDIO) && (ch->sample_num == gf_isom_get_sample_count(read->mov, ch->track))) {
					gf_filter_pck_set_property(pck, GF_PROP_PCK_END_RANGE, &PROP_BOOL(GF_TRUE));
				}

				gf_filter_pck_send(pck);
				isor_reader_release_sample(ch);

				ch->last_valid_sample_data_offset = ch->sample_data_offset;
				nb_pck--;
			} else if (ch->last_state==GF_EOS) {
				if (ch->playing == 2) {
					if (in_is_eos) {
						ch->playing = GF_FALSE;
					} else {
						nb_forced_end++;
						check_forced_end = GF_TRUE;
					}
				}
				if (in_is_eos && !ch->eos_sent) {
					void *tfrf;
					const void *gf_isom_get_tfrf(GF_ISOFile *movie, u32 trackNumber);

					ch->eos_sent = GF_TRUE;
					read->eos_signaled = GF_TRUE;

					tfrf = (void *) gf_isom_get_tfrf(read->mov, ch->track);
					if (tfrf) {
						gf_filter_pid_set_info_str(ch->pid, "smooth_tfrf", &PROP_POINTER(tfrf) );
						ch->last_has_tfrf = GF_TRUE;
					} else if (ch->last_has_tfrf) {
						gf_filter_pid_set_info_str(ch->pid, "smooth_tfrf", NULL);
						ch->last_has_tfrf = GF_FALSE;
					}

					gf_filter_pid_set_eos(ch->pid);
				}
				break;
			} else if (ch->last_state==GF_ISOM_INVALID_FILE) {
				if (!ch->eos_sent) {
					ch->eos_sent = GF_TRUE;
					read->eos_signaled = GF_TRUE;
					gf_filter_pid_set_eos(ch->pid);
				}
				return ch->last_state;
			} else {
				read->force_fetch = GF_TRUE;
				break;
			}
		}
		if (!min_offset_plus_one || (min_offset_plus_one - 1 > ch->last_valid_sample_data_offset))
			min_offset_plus_one = 1 + ch->last_valid_sample_data_offset;
	}
	if (read->mem_load_mode && min_offset_plus_one) {
		isoffin_purge_mem(read, min_offset_plus_one-1);
	}

	//we reached end of playback due to play range request, we must send eos - however for safety reason with DASH, we first need to cancel the input
	if (read->pid && check_forced_end && (nb_forced_end==count)) {
		//abort input
		GF_FilterEvent evt;
		GF_FEVT_INIT(evt, GF_FEVT_STOP, read->pid);
		gf_filter_pid_send_event(read->pid, &evt);
	}


	if (!is_active) {
		return GF_EOS;
	}
	//if (in_is_eos)
//	gf_filter_ask_rt_reschedule(filter, 1);
	return GF_OK;

}

Project: gpac
File: isoffin_read_ch.c

static void init_reader(ISOMChannel *ch)
{
	u32 sample_desc_index=0;

	ch->au_seq_num = 1;

	assert(ch->sample==NULL);
	if (!ch->static_sample) {
		ch->static_sample = gf_isom_sample_new();
	}

	if (ch->streamType==GF_STREAM_OCR) {
		assert(!ch->sample);
		ch->sample = gf_isom_sample_new();
		ch->sample->IsRAP = RAP;
		ch->sample->DTS = ch->start;
		ch->last_state=GF_OK;
	} else if (ch->sample_num) {
		ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, ch->static_sample, &ch->sample_data_offset);
		ch->disable_seek = GF_TRUE;
		ch->au_seq_num = ch->sample_num;
	} else {
		//if seek is disabled, get the next closest sample for this time; otherwise, get the previous RAP sample for this time
		u32 mode = ch->disable_seek ? GF_ISOM_SEARCH_BACKWARD : GF_ISOM_SEARCH_SYNC_BACKWARD;

		/*take care of seeking out of the track range*/
		if (!ch->owner->frag_type && (ch->duration<=ch->start)) {
			ch->last_state = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->duration-1, &sample_desc_index, mode, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset);
		} else if (ch->start || ch->has_edit_list) {
			ch->last_state = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->start, &sample_desc_index, mode, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset);
		} else {
			ch->sample_num = 1;
			if (ch->owner->nodata) {
				ch->sample = gf_isom_get_sample_info_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, &ch->sample_data_offset, ch->static_sample);
			} else {
				ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, ch->static_sample, &ch->sample_data_offset);
			}
			if (!ch->sample) ch->last_state = GF_EOS;
		}
		if (ch->last_state) {
			ch->sample = NULL;
			ch->last_state = GF_OK;
		} else {
			ch->sample = ch->static_sample;
		}

		if (ch->has_rap && ch->has_edit_list) {
			ch->edit_sync_frame = ch->sample_num;
		}

		if (ch->sample && !ch->sample->data && ch->owner->frag_type && !ch->has_edit_list) {
			ch->sample = NULL;
			ch->sample_num = 1;
			ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, ch->static_sample, &ch->sample_data_offset);
		}
	}


	/*no sample means we're not in the track range - stop*/
	if (!ch->sample) {
		/*incomplete file - check if we're still downloading or not*/
		if (gf_isom_get_missing_bytes(ch->owner->mov, ch->track)) {
			if (!ch->owner->input_loaded) {
				ch->last_state = GF_OK;
				return;
			}
			ch->last_state = GF_ISOM_INCOMPLETE_FILE;
		} else if (ch->sample_num) {
			ch->last_state = (ch->owner->frag_type==1) ? GF_OK : GF_EOS;
			ch->to_init = 0;
		}
		return;
	}

	ch->sample_time = ch->sample->DTS;

	ch->to_init = GF_FALSE;

	ch->seek_flag = 0;
	if (ch->disable_seek) {
		ch->dts = ch->sample->DTS;
		ch->cts = ch->sample->DTS + ch->sample->CTS_Offset;
		ch->start = 0;
	} else {
		s64 cts;
		ch->dts = ch->start;
		ch->cts = ch->start;

		cts = ch->sample->DTS + ch->sample->CTS_Offset;
		if (ch->ts_offset<0)
			cts += ch->ts_offset;

		//TODO - we need to notify scene decoder how many secs elapsed between RAP and seek point
		if (ch->cts != cts) {
			ch->seek_flag = 1;
		}
	}
	if (!sample_desc_index) sample_desc_index = 1;
	ch->last_sample_desc_index = sample_desc_index;
	ch->owner->no_order_check = ch->speed < 0 ? GF_TRUE : GF_FALSE;
}

Project: gpac
File: isoffin_read.c

static void isoffin_push_buffer(GF_Filter *filter, ISOMReader *read, const u8 *pck_data, u32 data_size)
{
	u64 bytes_missing;
	GF_Err e;

	if (!read->mem_url) {
		char szPath[200];
		sprintf(szPath, "gmem://%p", &read->mem_blob);
		read->mem_url = gf_strdup(szPath);
	}
	read->mem_blob.data = gf_realloc(read->mem_blob.data, read->mem_blob.size + data_size);
	memcpy(read->mem_blob.data + read->mem_blob.size, pck_data, data_size);
	read->mem_blob.size += data_size;

	if (read->mem_load_mode==1) {
		u32 box_type;
		e = gf_isom_open_progressive_ex(read->mem_url, 0, 0, GF_FALSE, &read->mov, &bytes_missing, &box_type);

		if (e && (e != GF_ISOM_INCOMPLETE_FILE)) {
			gf_filter_setup_failure(filter, e);
			read->mem_load_mode = 0;
			read->in_error = e;
			return;
		}
		if (!read->mov) {
			switch (box_type) {
			case GF_4CC('m','d','a','t'):
				GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] non fragmented ISOBMFF with moof after mdat and no underlying file cache (pipe or other stream input), not supported !\n"));
				gf_filter_setup_failure(filter, GF_NOT_SUPPORTED);
				read->mem_load_mode = 0;
				read->in_error = GF_NOT_SUPPORTED;
				break;
			default:
				read->moov_not_loaded = 1;
				break;
			}
			return;
		}

		read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0;
		read->timescale = gf_isom_get_timescale(read->mov);
		isor_declare_objects(read);
		read->mem_load_mode = 2;
		read->moov_not_loaded = 0;
		return;
	}
	//refresh file
	gf_isom_refresh_fragmented(read->mov, &bytes_missing, read->mem_url);

	if ((read->mem_load_mode==2) && bytes_missing)
		read->force_fetch = GF_TRUE;

}

Project: gpac
File: isoffin_read.c

const GF_FilterRegister *isoffin_register(GF_FilterSession *session)
{
#ifdef GPAC_DISABLE_ISOM
	return NULL;
#else
	return &ISOFFInRegister;
#endif /*GPAC_DISABLE_ISOM*/
}

Project: gpac
File: isoffin_read.c

u32 isoffin_channel_switch_quality(ISOMChannel *ch, GF_ISOFile *the_file, Bool switch_up)
{
	u32 i, count, next_track, trackID, cur_track;
	s32 ref_count;

	cur_track = ch->next_track ? ch->next_track : ch->track;
	count = gf_isom_get_track_count(the_file);
	trackID = gf_isom_get_track_id(the_file, cur_track);
	next_track = 0;

	if (switch_up) {
		for (i = 0; i < count; i++) {
			ref_count = gf_isom_get_reference_count(the_file, i+1, GF_ISOM_REF_SCAL);
			if (ref_count < 0)
				return cur_track; //error
			if (ref_count == 0)
				continue;
			/*next track is the one that has the last reference of type GF_ISOM_REF_SCAL refers to this current track*/
			if ((u32)ref_count == gf_isom_has_track_reference(the_file, i+1, GF_ISOM_REF_SCAL, trackID)) {
				next_track = i+1;
				break;
			}
		}
		/*this is the highest quality*/
		if (!next_track) {
			ch->playing = GF_TRUE;
			ref_count = gf_isom_get_reference_count(the_file, ch->track, GF_ISOM_REF_BASE);
			trackID = 0;
			if (ref_count) {
				gf_isom_get_reference(the_file, ch->track, GF_ISOM_REF_BASE, 1, &trackID);
				for (i=0; i<gf_list_count(ch->owner->channels) && trackID; i++) {
					ISOMChannel *base = gf_list_get(ch->owner->channels, i);
					if (base->track_id==trackID) {
						u32 sample_desc_index;
						u64 resume_at;
						GF_Err e;
						//try to locate sync after current time in base
						resume_at = gf_timestamp_rescale(base->static_sample->DTS, base->timescale, ch->timescale);
						e = gf_isom_get_sample_for_media_time(ch->owner->mov, ch->track, resume_at, &sample_desc_index, GF_ISOM_SEARCH_SYNC_FORWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset);
						//found, rewind so that next fetch is the sync
						if (e==GF_OK) {
							ch->sample = NULL;
						}
						//no further sync found, realign with base timescale
						else if (e==GF_EOS) {
							e = gf_isom_get_sample_for_media_time(ch->owner->mov, ch->track, resume_at, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset);
						}
						//unknown state, realign sample num with base
						if (e<0) {
							ch->sample_num = base->sample_num;
						}
						break;
					}
				}
			}
			return cur_track;
		}
	} else {
		if (cur_track == ch->base_track)
			return cur_track;
		ref_count = gf_isom_get_reference_count(the_file, cur_track, GF_ISOM_REF_SCAL);
		if (ref_count <= 0)
			return cur_track;
		gf_isom_get_reference(the_file, cur_track, GF_ISOM_REF_SCAL, ref_count, &next_track);
		if (!next_track)
			return cur_track;

		if (ch->track != next_track) {
			ch->playing = GF_FALSE;
			ch->eos_sent = GF_TRUE;
			gf_filter_pid_set_eos(ch->pid);
		}
	}

	/*in scalable mode add SPS/PPS in-band*/
	if (ch->owner->smode)
		gf_isom_set_nalu_extract_mode(the_file, next_track, ch->nalu_extract_mode);

	return next_track;
}

Project: gpac
File: isoffin_read_ch.c

void isor_reader_release_sample(ISOMChannel *ch)
{
	if (ch->sample)
		ch->au_seq_num++;
	ch->sample = NULL;
	ch->sai_buffer_size = 0;
}

Project: gpac
File: isoffin_read.c

static const char *isoffin_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score)
{
	if (gf_isom_probe_data(data, size)) {
		*score = GF_FPROBE_SUPPORTED;
		return "video/mp4";
	}
	return NULL;
}

Project: gpac
File: isoffin_read.c

static void isoffin_delete_channel(ISOMChannel *ch)
{
	isor_reset_reader(ch);
	if (ch->nal_bs) gf_bs_del(ch->nal_bs);
	if (ch->avcc) gf_odf_avc_cfg_del(ch->avcc);
	if (ch->hvcc) gf_odf_hevc_cfg_del(ch->hvcc);
	if (ch->vvcc) gf_odf_vvc_cfg_del(ch->vvcc);
	gf_free(ch);
}

Project: gpac
File: isoffin_read_ch.c

static void isor_replace_nal(ISOMChannel *ch, u8 *data, u32 size, u8 nal_type, u32 *reset_state)
{
	u32 i, count, state=0;
	GF_NALUFFParam *sl;
	GF_List *list=NULL;
	if (ch->avcc) {
		if (nal_type==GF_AVC_NALU_PIC_PARAM) {
			list = ch->avcc->pictureParameterSets;
			state=RESET_STATE_PPS;
		} else if (nal_type==GF_AVC_NALU_SEQ_PARAM) {
			list = ch->avcc->sequenceParameterSets;
			state=RESET_STATE_SPS;
		} else if (nal_type==GF_AVC_NALU_SEQ_PARAM_EXT) {
			list = ch->avcc->sequenceParameterSetExtensions;
			state=RESET_STATE_SPS_EXT;
		} else return;
	}
	else if (ch->hvcc) {
		GF_NALUFFParamArray *hvca=NULL;
		count = gf_list_count(ch->hvcc->param_array);
		for (i=0; i<count; i++) {
			hvca = gf_list_get(ch->hvcc->param_array, i);
			if (hvca->type==nal_type) {
				list = hvca->nalus;
				break;
			}
			hvca = NULL;
		}
		if (!hvca) {
			GF_SAFEALLOC(hvca, GF_NALUFFParamArray);
			if (hvca) {
				list = hvca->nalus = gf_list_new();
				hvca->type = nal_type;
				gf_list_add(ch->hvcc->param_array, hvca);
			}
		}
		switch (nal_type) {
		case GF_HEVC_NALU_VID_PARAM:
			state = RESET_STATE_VPS;
			break;
		case GF_HEVC_NALU_SEQ_PARAM:
			state = RESET_STATE_SPS;
			break;
		case GF_HEVC_NALU_PIC_PARAM:
			state = RESET_STATE_PPS;
			break;
		}
	}
	else if (ch->vvcc) {
		GF_NALUFFParamArray *vvca=NULL;
		count = gf_list_count(ch->vvcc->param_array);
		for (i=0; i<count; i++) {
			vvca = gf_list_get(ch->vvcc->param_array, i);
			if (vvca->type==nal_type) {
				list = vvca->nalus;
				break;
			}
			vvca = NULL;
		}
		if (!vvca) {
			GF_SAFEALLOC(vvca, GF_NALUFFParamArray);
			if (vvca) {
				list = vvca->nalus = gf_list_new();
				vvca->type = nal_type;
				gf_list_add(ch->vvcc->param_array, vvca);
			}
		}
		switch (nal_type) {
		case GF_VVC_NALU_VID_PARAM:
			state = RESET_STATE_VPS;
			break;
		case GF_VVC_NALU_SEQ_PARAM:
			state = RESET_STATE_SPS;
			break;
		case GF_VVC_NALU_PIC_PARAM:
			state = RESET_STATE_PPS;
			break;
		case GF_VVC_NALU_DEC_PARAM:
			state = RESET_STATE_DCI;
			break;
		}
	}

	ch->xps_mask |= state;

	count = gf_list_count(list);
	for (i=0; i<count; i++) {
		sl = gf_list_get(list, i);
		if ((sl->size==size) && !memcmp(sl->data, data, size)) return;
	}
	if (! (*reset_state & state))  {
		isor_reset_seq_list(list);
		*reset_state |= state;
	}
	GF_SAFEALLOC(sl, GF_NALUFFParam);
	if (!sl) return;
	sl->data = gf_malloc(sizeof(char)*size);
	memcpy(sl->data, data, size);
	sl->size = size;
	gf_list_add(list, sl);
}

Project: gpac
File: isoffin_read_ch.c

void isor_sai_bytes_removed(ISOMChannel *ch, u32 pos, u32 removed)
{
	u32 offset = 0;
	u8 *sai;
	u32 sai_size, cur_pos;
	u32 sub_count_size = 0;
	u32 i, subs_count = 0;

	if (!ch->cenc_ki || !ch->sai_buffer) return;

	sai = ch->sai_buffer;
	sai_size = ch->sai_buffer_size;

	//multikey
	if (ch->cenc_ki->value.data.ptr[0]) {
		u32 remain;
		u32 j, nb_iv_init = sai[0];
		nb_iv_init <<= 8;
		nb_iv_init |= sai[1];
		u8 *sai_p = sai + 2;
		remain = sai_size-2;

		for (j=0; j<nb_iv_init; j++) {
			u32 mk_iv_size;
			u32 idx = sai_p[0];
			idx<<=8;
			idx |= sai_p[1];

			mk_iv_size = key_info_get_iv_size(ch->cenc_ki->value.data.ptr, ch->cenc_ki->value.data.size, idx, NULL, NULL);
			mk_iv_size += 2; //idx
			if (mk_iv_size > remain) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Invalid multi-key CENC SAI, cannot modify first subsample !\n"));
				return;
			}
			sai_p += mk_iv_size;
			remain -= mk_iv_size;
		}
		offset = (u32) (sai_p - sai);
		sub_count_size = 4; //32bit sub count

	} else {
		offset = key_info_get_iv_size(ch->cenc_ki->value.data.ptr, ch->cenc_ki->value.data.size, 1, NULL, NULL);
		sub_count_size = 2; //16bit sub count
	}

	sai += offset;
	if (sub_count_size==2) {
		subs_count = ((u32) sai[0]) << 8 | sai[1];
	} else {
		subs_count = GF_4CC(sai[0], sai[1], sai[2], sai[3]);
	}
	sai += sub_count_size;
	sai_size -= offset + sub_count_size;
	cur_pos = 0;
	for (i=0; i<subs_count; i++) {
		if (sai_size<6)
			return;
		u32 clear = ((u32) sai[0]) << 8 | sai[1];
		u32 crypt = GF_4CC(sai[2], sai[3], sai[4], sai[5]);
		if (cur_pos + clear > pos) {
			clear -= removed;
			sai[0] = (clear>>8) & 0xFF;
			sai[1] = (clear) & 0xFF;
			return;
		}
		cur_pos += clear + crypt;
		sai += 6;
	}
}

Project: gpac
File: isoffin_read.c

GF_Err isoffin_initialize(GF_Filter *filter)
{
	ISOMReader *read = gf_filter_get_udta(filter);
	GF_Err e = GF_OK;
	read->filter = filter;
	read->channels = gf_list_new();

	if (read->xps_check==MP4DMX_XPS_AUTO) {
		read->xps_check = (read->smode==MP4DMX_SPLIT_EXTRACTORS) ? MP4DMX_XPS_KEEP : MP4DMX_XPS_REMOVE;
	}

	if (read->src) {
		read->input_loaded = GF_TRUE;
		return isoffin_setup(filter, read);
	}
	else if (read->mov) {
		read->extern_mov = GF_TRUE;
		read->input_loaded = GF_TRUE;
		read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0;
		read->timescale = gf_isom_get_timescale(read->mov);

		if (read->sigfrag) {
			gf_isom_enable_traf_map_templates(read->mov);
		}

		if (read->catseg) {
			e = gf_isom_open_segment(read->mov, read->catseg, 0, 0, 0);
		}
		if (!e)
			e = isor_declare_objects(read);

		gf_filter_post_process_task(filter);
	}
	return e;
}

Project: gpac
File: isoffin_read.c

GF_Err isoffin_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
{
	const GF_PropertyValue *prop;
	ISOMReader *read = gf_filter_get_udta(filter);

	if (is_remove) {
		isoffin_disconnect(read);
		return GF_OK;
	}
	//check if we  have a file path; if not, this is a pure stream of boxes (no local file cache)
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH);
	if (!prop || !prop->value.string) {
		if (!read->mem_load_mode)
			read->mem_load_mode = 1;
		if (!read->pid) read->pid = pid;
		read->input_loaded = GF_FALSE;
		return GF_OK;
	}

	if (read->pid && prop->value.string) {
		const char *next_url = prop->value.string;
		u64 sr, er;
		u32 crc = gf_crc_32(next_url, (u32) strlen(next_url) );

		sr = er = 0;
		prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILE_RANGE);
		if (prop) {
			sr = prop->value.lfrac.num;
			er = prop->value.lfrac.den;
		}

		//if eos is signaled, don't check for crc since we might have the same blob address (same alloc)
		if (!read->eos_signaled && (read->src_crc == crc) && (read->start_range==sr) && (read->end_range==er)) {
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] same URL crc and range for %s, skipping reconfigure\n", next_url));
			return GF_OK;
		}
		read->src_crc = crc;
		read->start_range = sr;
		read->end_range = er;
		read->input_loaded = GF_FALSE;
		read->eos_signaled = GF_FALSE;
		
		//we need to reconfigure
		return isoffin_reconfigure(filter, read, next_url);
	}

	read->pid = pid;
	prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_CACHED);
	if (prop && prop->value.boolean) {
		GF_FilterEvent evt;
		read->input_loaded = GF_TRUE;
		GF_FEVT_INIT(evt, GF_FEVT_PLAY_HINT, pid);
		evt.play.full_file_only=1;
		gf_filter_pid_send_event(pid, &evt);
	}
	return isoffin_setup(filter, read);
}

Project: gpac
File: isoffin_read_ch.c

void isor_reader_get_sample(ISOMChannel *ch)
{
	GF_Err e;
	u32 sample_desc_index;
	if (ch->sample) return;

	if (ch->next_track) {
		ch->track = ch->next_track;
		ch->next_track = 0;
	}

	if (ch->to_init) {
		init_reader(ch);
		sample_desc_index = ch->last_sample_desc_index;
	} else if (ch->speed < 0) {
		if (ch->last_state == GF_EOS) {
			ch->sample = NULL;
			return;
		}

		if (ch->static_sample->IsRAP) {
			ch->last_rap_sample_time = ch->sample_time;
		}

		e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->static_sample, &ch->sample_num, NULL);

		if ((e==GF_EOS) || (ch->static_sample->IsRAP)) {
			if (!ch->last_rap_sample_time) {
				e = GF_EOS;
			} else {
				e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->last_rap_sample_time - 1, &sample_desc_index, GF_ISOM_SEARCH_SYNC_BACKWARD, &ch->static_sample, &ch->sample_num, NULL);
			}
		}

		if (e) {
			if ((e==GF_EOS) && !ch->owner->frag_type) {
				ch->last_state = GF_EOS;
			}
			ch->sample = NULL;
			return;
		}
		ch->sample = ch->static_sample;

		if (ch->sample->DTS == ch->sample_time) {
			if (!ch->owner->frag_type) {
				ch->last_state = GF_EOS;
			}
		}
		if (ch->sample) {
			ch->sample_time = ch->sample->DTS;
		}

	} else if (ch->has_edit_list) {
		u32 prev_sample = ch->sample_num;
		e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset);

		if (e == GF_OK) {
			ch->sample = ch->static_sample;

			/*we are in forced seek mode: fetch all samples before the one matching the sample time*/
			if (ch->edit_sync_frame) {
				ch->edit_sync_frame++;
				if (ch->edit_sync_frame < ch->sample_num) {
					ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->edit_sync_frame, &sample_desc_index, ch->static_sample, &ch->sample_data_offset);
					ch->sample->DTS = ch->sample_time;
					ch->sample->CTS_Offset = 0;
				} else {
					ch->edit_sync_frame = 0;
					if (ch->sample) ch->sample_time = ch->sample->DTS;
				}
			} else {
				/*if we get the same sample, figure out next interesting time (current sample + DTS gap to next sample should be a good bet)*/
				if (prev_sample == ch->sample_num) {
					if (ch->owner->frag_type && (ch->sample_num==gf_isom_get_sample_count(ch->owner->mov, ch->track))) {
						ch->sample = NULL;
					} else {
						u32 sample_num = ch->sample_num ? ch->sample_num : 1;

						if (sample_num >= gf_isom_get_sample_count(ch->owner->mov, ch->track) ) {
							//e = GF_EOS;
						} else {
							u32 time_diff = gf_isom_get_sample_duration(ch->owner->mov, ch->track, sample_num);
							e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + time_diff, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset);
							if (e==GF_OK) {
								if (ch->sample_num == prev_sample) {
									ch->sample_time += time_diff;
									ch->sample = NULL;
									return;
								} else {
									ch->sample = ch->static_sample;
								}
							}
						}
					}
				}

				/*we jumped to another segment - if RAP is needed look for closest rap in decoding order and
				force seek mode*/
				if (ch->sample && !ch->sample->IsRAP && ch->has_rap && (ch->sample_num != prev_sample+1)) {
					GF_ISOSample *found = ch->static_sample;
					u32 samp_num = ch->sample_num;
					ch->sample = NULL;
					e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_SYNC_BACKWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset);

					if (e == GF_OK) ch->sample = ch->static_sample;

					/*if no sync point in the past, use the first non-sync for the given time*/
					if (!ch->sample || !ch->sample->data) {
						ch->sample = ch->static_sample = found;
						ch->sample_time = ch->sample->DTS;
						ch->sample_num = samp_num;
					} else {
						ch->sample = ch->static_sample;
						ch->edit_sync_frame = ch->sample_num;
						ch->sample->DTS = ch->sample_time;
						ch->sample->CTS_Offset = 0;
					}
				} else {
					if (ch->sample) ch->sample_time = ch->sample->DTS;
				}
			}
		}
	} else {
		Bool do_fetch = GF_TRUE;
		ch->sample_num++;

		if (ch->sap_only) {
			Bool is_rap = gf_isom_get_sample_sync(ch->owner->mov, ch->track, ch->sample_num);
			if (!is_rap) {
				GF_ISOSampleRollType roll_type;
				gf_isom_get_sample_rap_roll_info(ch->owner->mov, ch->track, ch->sample_num, &is_rap, &roll_type, NULL);
				if (roll_type) is_rap = GF_TRUE;
			}

			if (!is_rap) {
				do_fetch = GF_FALSE;
			} else if (ch->sap_only==2) {
				ch->sap_only = 0;
			}
		}
		if (do_fetch) {
			if (ch->owner->nodata) {
				ch->sample = gf_isom_get_sample_info_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, &ch->sample_data_offset, ch->static_sample);
			} else {
				ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, ch->static_sample, &ch->sample_data_offset);
			}
			/*if sync shadow / carousel RAP skip*/
			if (ch->sample && (ch->sample->IsRAP==RAP_REDUNDANT)) {
				ch->sample = NULL;
				ch->sample_num++;
				isor_reader_get_sample(ch);
				return;
			}
		}
	}

	//check scalable track change
	if (ch->sample && ch->sample->IsRAP && ch->next_track) {
		ch->track = ch->next_track;
		ch->next_track = 0;
		ch->sample = NULL;
		isor_reader_get_sample(ch);
		return;
	}

	if (!ch->sample) {
		u32 sample_count = gf_isom_get_sample_count(ch->owner->mov, ch->track);
		ch->sample_data_offset = 0;
		/*incomplete file - check if we're still downloading or not*/
		if (gf_isom_get_missing_bytes(ch->owner->mov, ch->track)) {
			ch->last_state = GF_ISOM_INCOMPLETE_FILE;
			if (ch->owner->mem_load_mode==2)
				ch->owner->force_fetch = GF_TRUE;

			if (!ch->owner->input_loaded) {
				ch->last_state = GF_OK;
				if (!ch->has_edit_list && ch->sample_num)
					ch->sample_num--;
			} else {
				if (ch->to_init && ch->sample_num) {
					GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] Failed to fetch initial sample %d for track %d\n"));
					ch->last_state = GF_ISOM_INVALID_FILE;
				}
				if (ch->sample_num >= gf_isom_get_sample_count(ch->owner->mov, ch->track)) {
					ch->last_state = GF_EOS;
				}
			}
		}
		else if (!ch->sample_num
		         || ((ch->speed >= 0) && (ch->sample_num >= sample_count))
		         || ((ch->speed < 0) && (ch->sample_time == gf_isom_get_current_tfdt(ch->owner->mov, ch->track) ))
		        ) {

			if (ch->owner->frag_type==1) {
				/*if sample cannot be found and file is fragmented, rewind sample*/
				if (ch->sample_num) ch->sample_num--;
				ch->last_state = GF_EOS;
			} else if (ch->last_state != GF_EOS) {
				GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[IsoMedia] Track #%d end of stream reached\n", ch->track));
				ch->last_state = GF_EOS;
				if (ch->sample_num>sample_count) ch->sample_num = sample_count;
			} else {
				if (ch->sample_num>sample_count) ch->sample_num = sample_count;
			}
		} else {
			e = gf_isom_last_error(ch->owner->mov);
			GF_LOG((e==GF_ISOM_INCOMPLETE_FILE) ? GF_LOG_DEBUG : GF_LOG_WARNING, GF_LOG_DASH, ("[IsoMedia] Track #%d fail to fetch sample %d / %d: %s\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track), gf_error_to_string(e) ));
		}
		return;
	}

	if (sample_desc_index != ch->last_sample_desc_index) {
		if (!ch->owner->stsd) {
			//we used sample entry 1 by default to setup, if no active prev sample (edit list might trigger this)
			//and new sample desc is 1, do not reconfigure
			if (!ch->last_sample_desc_index && (sample_desc_index==1)) {

			} else {
				ch->needs_pid_reconfig = GF_TRUE;
			}
		}
		ch->last_sample_desc_index = sample_desc_index;
	}

	ch->last_state = GF_OK;
	ch->au_duration = gf_isom_get_sample_duration(ch->owner->mov, ch->track, ch->sample_num);

	ch->sap_3 = GF_FALSE;
	ch->sap_4_type = 0;
	ch->roll = 0;
	ch->set_disc = ch->owner->clock_discontinuity ? 2 : 0;
	ch->owner->clock_discontinuity = 0;

	if (ch->sample) {
		gf_isom_get_sample_rap_roll_info(ch->owner->mov, ch->track, ch->sample_num, &ch->sap_3, &ch->sap_4_type, &ch->roll);

		/*still seeking or not ?
		 1- when speed is negative, the RAP found is "after" the seek point in playback order since we used backward RAP search: nothing to do
		 2- otherwise set DTS+CTS to start value
		 */
		if ((ch->speed < 0) || (ch->start <= ch->sample->DTS + ch->sample->CTS_Offset)) {
			ch->dts = ch->sample->DTS;
			ch->cts = ch->sample->DTS + ch->sample->CTS_Offset;
			ch->seek_flag = 0;
		} else {
			ch->cts = ch->start;
			ch->seek_flag = 1;
			ch->dts = ch->start;
		}

		if (ch->end && (ch->end < ch->sample->DTS + ch->sample->CTS_Offset + ch->au_duration)) {
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] End of Channel "LLD" (CTS "LLD")\n", ch->end, ch->sample->DTS + ch->sample->CTS_Offset));
			ch->sample = NULL;
			ch->last_state = GF_EOS;
			ch->playing = 2;
			return;
		}
	}

	if (ch->owner->last_sender_ntp && ch->cts==ch->owner->cts_for_last_sender_ntp) {
		ch->sender_ntp = ch->owner->last_sender_ntp;
		ch->ntp_at_server_ntp = ch->owner->ntp_at_last_sender_ntp;
	} else if (ch->owner->last_sender_ntp && ch->dts==ch->owner->cts_for_last_sender_ntp) {
		ch->sender_ntp = ch->owner->last_sender_ntp;
		ch->ntp_at_server_ntp = ch->owner->ntp_at_last_sender_ntp;
	} else {
		ch->sender_ntp = ch->ntp_at_server_ntp = 0;
	}

	if (!ch->sample_num) return;

	gf_isom_get_sample_flags(ch->owner->mov, ch->track, ch->sample_num, &ch->isLeading, &ch->dependsOn, &ch->dependedOn, &ch->redundant);

	if (ch->is_encrypted) {
		/*in case of CENC: we write sample auxiliary information to slh->sai; its size is in saiz*/
		if (gf_isom_is_cenc_media(ch->owner->mov, ch->track, ch->last_sample_desc_index)) {
			isor_update_cenc_info(ch, GF_FALSE);

		} else if (gf_isom_is_media_encrypted(ch->owner->mov, ch->track, ch->last_sample_desc_index)) {
			ch->pck_encrypted = GF_TRUE;
		} else {
			ch->pck_encrypted = GF_FALSE;
		}
	}
	if (ch->sample && ch->sample->nb_pack)
		ch->sample_num += ch->sample->nb_pack-1;
}

Project: gpac
File: isoffin_read.c

static Bool isoffin_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
{
	u32 count, i;
	Bool cancel_event = GF_TRUE;
	ISOMChannel *ch;
	ISOMReader *read = gf_filter_get_udta(filter);

	if (!read || read->disconnected) return GF_FALSE;

	if (evt->base.type == GF_FEVT_QUALITY_SWITCH) {
		count = gf_list_count(read->channels);
		for (i = 0; i < count; i++) {
			ch = (ISOMChannel *)gf_list_get(read->channels, i);
			if (ch->base_track && gf_isom_needs_layer_reconstruction(read->mov)) {
				/*ch->next_track = */ //old code, see not in isoffin_reconfigure
				isoffin_channel_switch_quality(ch, read->mov, evt->quality_switch.up);
			}
		}
		return GF_TRUE;
	}

	if (!evt->base.on_pid) return GF_FALSE;

	ch = isor_get_channel(read, evt->base.on_pid);
	if (!ch)
		return GF_FALSE;

	switch (evt->base.type) {
	case GF_FEVT_PLAY:
		isor_reset_reader(ch);
		ch->eos_sent = GF_FALSE;
		ch->speed = evt->play.speed;
		ch->initial_play_seen = GF_TRUE;
		read->reset_frag_state = 1;
		//it can happen that input_is_stop is still TRUE because we did not get called back after the stop - reset to FALSE since we now play
		read->input_is_stop = GF_FALSE;
		if (read->frag_type)
			read->frag_type = 1;

		ch->start = ch->end = 0;
		if (evt->play.speed>=0) {
			Double t;
			if (evt->play.start_range>=0) {
				t = evt->play.start_range;
				t *= ch->timescale;
				ch->start = (u64) t;
			}
			if (evt->play.end_range >= evt->play.start_range) {
				ch->end = (u64) -1;
				if (evt->play.end_range<FLT_MAX) {
					t = evt->play.end_range;
					t *= ch->timescale;
					ch->end = (u64) t;
				}
			}
		} else {
			Double end = evt->play.end_range;
			if (end==-1) end = 0;
			ch->start = (u64) (s64) (evt->play.start_range * ch->timescale);
			if (end <= evt->play.start_range)
				ch->end = (u64) (s64) (end  * ch->timescale);
		}
		ch->playing = GF_TRUE;
		ch->sample_num = evt->play.from_pck;

		ch->sap_only = evt->play.drop_non_ref ? GF_TRUE : GF_FALSE;

		GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[IsoMedia] Starting channel playback "LLD" to "LLD" (%g to %g)\n", ch->start, ch->end, evt->play.start_range, evt->play.end_range));

		if (!read->nb_playing)
			gf_isom_reset_seq_num(read->mov);

		if (read->is_partial_download) read->input_loaded = GF_FALSE;

		if (evt->play.no_byterange_forward) {
			//new segment will be loaded, reset
			gf_isom_reset_tables(read->mov, GF_TRUE);
			gf_isom_reset_data_offset(read->mov, NULL);
			read->refresh_fragmented = GF_TRUE;
			read->mem_blob.size = 0;
			//send play event
			cancel_event = GF_FALSE;
		} else if (!read->nb_playing && read->pid && !read->input_loaded) {
			GF_FilterEvent fevt;
			Bool is_sidx_seek = GF_FALSE;
			u64 max_offset = GF_FILTER_NO_BO;
			count = gf_list_count(read->channels);

			//try sidx
			if (read->frag_type) {
				u32 ts;
				u64 dur=0;
				GF_Err e = gf_isom_get_file_offset_for_time(read->mov, evt->play.start_range, &max_offset);
				if (e==GF_OK) {
					if (evt->play.start_range>0)
						gf_isom_reset_tables(read->mov, GF_TRUE);

					is_sidx_seek = GF_TRUE;
					//in case we loaded moov but not sidx, update duration
					if ((gf_isom_get_sidx_duration(read->mov, &dur, &ts)==GF_OK) && dur) {
						dur = gf_timestamp_rescale(dur, ts, read->timescale);
						if (ch->duration != dur) {
							ch->duration = dur;
							gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(ch->duration, read->timescale));
						}
					}
				}
			}

			if (!is_sidx_seek) {
				for (i=0; i< count; i++) {
					u32 mode, sample_desc_index, sample_num;
					u64 data_offset;
					GF_Err e;
					u64 time;
					ch = gf_list_get(read->channels, i);
					mode = ch->disable_seek ? GF_ISOM_SEARCH_BACKWARD : GF_ISOM_SEARCH_SYNC_BACKWARD;
					time = (u64) (evt->play.start_range * ch->timescale);

					/*take care of seeking out of the track range*/
					if (!read->frag_type && (ch->duration < time)) {
						e = gf_isom_get_sample_for_movie_time(read->mov, ch->track, ch->duration, 	&sample_desc_index, mode, NULL, &sample_num, &data_offset);
					} else {
						e = gf_isom_get_sample_for_movie_time(read->mov, ch->track, time, &sample_desc_index, mode, NULL, &sample_num, &data_offset);
					}
					if ((e == GF_OK) && (data_offset<max_offset))
						max_offset = data_offset;
				}
			}

			if ((evt->play.start_range || read->is_partial_download)  && (max_offset != GF_FILTER_NO_BO) ) {

				//send a seek request
				read->is_partial_download = GF_TRUE;
				read->wait_for_source = GF_TRUE;
				read->refresh_fragmented = GF_TRUE;

				GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, read->pid);
				fevt.seek.start_offset = max_offset;
				gf_filter_pid_send_event(read->pid, &fevt);
				gf_isom_set_byte_offset(read->mov, is_sidx_seek ? 0 : max_offset);

			}
		}
		//always request a process task upon a play
		gf_filter_post_process_task(read->filter);
		read->nb_playing++;
		//cancel event unless dash mode
		return cancel_event;

	case GF_FEVT_STOP:
 		if (read->nb_playing) read->nb_playing--;
		isor_reset_reader(ch);
		//don't send a stop if some of our channels are still waiting for initial play
		for (i=0; i<gf_list_count(read->channels); i++) {
			ISOMChannel *a_ch = gf_list_get(read->channels, i);
			if (ch==a_ch) continue;
			if (!a_ch->initial_play_seen) return GF_TRUE;
		}
		//cancel event if nothing playing
		if (read->nb_playing) return GF_TRUE;
		read->input_is_stop = GF_TRUE;
		return GF_FALSE;

	case GF_FEVT_SET_SPEED:
	case GF_FEVT_RESUME:
		ch->speed = evt->play.speed;
		if (ch->sap_only && !evt->play.drop_non_ref) {
			ch->sap_only = 2;
		} else {
			ch->sap_only = evt->play.drop_non_ref ? GF_TRUE : GF_FALSE;
		}
		//cancel event
		return GF_TRUE;
	default:
		break;
	}
	//by default don't cancel event
	return GF_FALSE;
}

Project: gpac
File: isoffin_read.c

static void isoffin_finalize(GF_Filter *filter)
{
	ISOMReader *read = (ISOMReader *) gf_filter_get_udta(filter);

	read->disconnected = GF_TRUE;

	while (gf_list_count(read->channels)) {
		ISOMChannel *ch = (ISOMChannel *)gf_list_get(read->channels, 0);
		gf_list_rem(read->channels, 0);
		isoffin_delete_channel(ch);
	}
	gf_list_del(read->channels);

	if (!read->extern_mov && read->mov) gf_isom_close(read->mov);
	read->mov = NULL;

	if (read->mem_blob.data) gf_free(read->mem_blob.data);
	if (read->mem_url) gf_free(read->mem_url);
}

Project: gpac
File: isoffin_read.c

static void isoffin_disconnect(ISOMReader *read)
{
	read->disconnected = GF_TRUE;
	while (gf_list_count(read->channels)) {
		ISOMChannel *ch = (ISOMChannel *)gf_list_get(read->channels, 0);
		gf_list_rem(read->channels, 0);
		if (ch->pid)
			gf_filter_pid_remove(ch->pid);
		isoffin_delete_channel(ch);
	}

	if (read->mov) gf_isom_close(read->mov);
	read->mov = NULL;

	read->pid = NULL;
}

Project: gpac
File: isoffin_read.c

ISOMChannel *isor_create_channel(ISOMReader *read, GF_FilterPid *pid, u32 track, u32 item_id, Bool force_no_extractors)
{
	ISOMChannel *ch;
	const GF_PropertyValue *p;
	s64 ts_shift;
	if (!read->mov) return NULL;

	GF_SAFEALLOC(ch, ISOMChannel);
	if (!ch) {
		return NULL;
	}
	ch->owner = read;
	ch->pid = pid;
	ch->to_init = GF_TRUE;
	gf_list_add(read->channels, ch);
	ch->track = track;
	ch->item_id = item_id;

	ch->nalu_extract_mode = 0;
	ch->track_id = gf_isom_get_track_id(read->mov, ch->track);
	switch (gf_isom_get_media_type(ch->owner->mov, ch->track)) {
	case GF_ISOM_MEDIA_OCR:
		ch->streamType = GF_STREAM_OCR;
		break;
	case GF_ISOM_MEDIA_SCENE:
		ch->streamType = GF_STREAM_SCENE;
		break;
	case GF_ISOM_MEDIA_VISUAL:
	case GF_ISOM_MEDIA_AUXV:
	case GF_ISOM_MEDIA_PICT:
		gf_isom_get_reference(ch->owner->mov, ch->track, GF_ISOM_REF_BASE, 1, &ch->base_track);
		//use base track only if avc/svc or hevc/lhvc. If avc+lhvc we need different rules
		if ( gf_isom_get_avc_svc_type(ch->owner->mov, ch->base_track, 1) == GF_ISOM_AVCTYPE_AVC_ONLY) {
			if ( gf_isom_get_hevc_lhvc_type(ch->owner->mov, ch->track, 1) >= GF_ISOM_HEVCTYPE_HEVC_ONLY) {
				ch->base_track=0;
			}
		}
		ch->next_track = 0;
		/*in scalable mode add SPS/PPS in-band*/
		if (ch->base_track)
			ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG /*| GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG*/;
		break;
	}
	if (!read->noedit) {
		ch->ts_offset = 0;
		ch->has_edit_list = gf_isom_get_edit_list_type(ch->owner->mov, ch->track, &ch->ts_offset) ? GF_TRUE : GF_FALSE;
		if (!ch->has_edit_list && ch->ts_offset) {
			//if >0 this is a hold, we signal positive delay
			//if <0 this is a skip, we signal negative delay
			gf_filter_pid_set_property(pid, GF_PROP_PID_DELAY, &PROP_LONGSINT( ch->ts_offset) );
		}
	} else
		ch->has_edit_list = GF_FALSE;

	ch->has_rap = (gf_isom_has_sync_points(ch->owner->mov, ch->track)==1) ? GF_TRUE : GF_FALSE;
	gf_filter_pid_set_property(pid, GF_PROP_PID_HAS_SYNC, &PROP_BOOL(ch->has_rap) );
	//some fragmented files do not advertize a sync sample table (legal) so we need to update as soon as we fetch a fragment
	//to see if we are all-intra (as detected here) or not
	if (!ch->has_rap && ch->owner->frag_type)
		ch->check_has_rap = GF_TRUE;
	ch->timescale = gf_isom_get_media_timescale(ch->owner->mov, ch->track);

	ts_shift = gf_isom_get_cts_to_dts_shift(ch->owner->mov, ch->track);
	if (ts_shift) {
		gf_filter_pid_set_property(pid, GF_PROP_PID_CTS_SHIFT, &PROP_UINT((u32) ts_shift) );
	}

	if (!track || !gf_isom_is_track_encrypted(read->mov, track)) {
		if (force_no_extractors) {
			ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_LAYER_ONLY;
		} else {
			switch (read->smode) {
			case MP4DMX_SPLIT_EXTRACTORS:
				ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_INSPECT | GF_ISOM_NALU_EXTRACT_TILE_ONLY;
				break;
			case MP4DMX_SPLIT:
				ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_LAYER_ONLY | GF_ISOM_NALU_EXTRACT_TILE_ONLY;
				break;
			default:
				break;
			}
		}

		if (ch->nalu_extract_mode) {
			gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode);
		}
		return ch;
	}
	if (ch->owner->nocrypt) {
		ch->is_encrypted = GF_FALSE;
		return ch;
	}
	ch->is_encrypted = GF_TRUE;
	p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
	if (p) gf_filter_pid_set_property(pid, GF_PROP_PID_ORIG_STREAM_TYPE, &PROP_UINT(p->value.uint) );

	gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_ENCRYPTED) );

	isor_set_crypt_config(ch);

	if (ch->nalu_extract_mode) {
		if (ch->is_encrypted) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] using sample NAL rewrite with encryption is not yet supported, patch welcome\n"));
		} else {
			gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode);
		}
	}
	return ch;
}

Project: gpac
File: isoffin_read.c

static GF_Err isoffin_reconfigure(GF_Filter *filter, ISOMReader *read, const char *next_url)
{
	const GF_PropertyValue *prop;
	u32 i, count;
	Bool is_new_mov = GF_FALSE;
	u64 tfdt;
//	GF_ISOTrackID trackID;
	GF_ISOSegOpenMode flags=0;
	GF_Err e;

	prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILE_CACHED);
	if (prop && prop->value.boolean)
		read->input_loaded = GF_TRUE;

	read->refresh_fragmented = GF_FALSE;
	read->full_segment_flush = GF_TRUE;
	GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] reconfigure triggered, URL %s\n", next_url));

	//no need to lock blob if next_url is a blob, all parsing and probing functions below will lock the blob if any

	switch (gf_isom_probe_file_range(next_url, read->start_range, read->end_range)) {
	//this is a fragment
	case 3:
		gf_isom_release_segment(read->mov, 1);
		gf_isom_reset_fragment_info(read->mov, GF_TRUE);

		if (read->no_order_check) flags |= GF_ISOM_SEGMENT_NO_ORDER_FLAG;

		//no longer used in filters
#if 0
		if (scalable_segment) flags |= GF_ISOM_SEGMENT_SCALABLE_FLAG;
#endif
		e = gf_isom_open_segment(read->mov, next_url, read->start_range, read->end_range, flags);
		if (!read->input_loaded && (e==GF_ISOM_INCOMPLETE_FILE)) {
			e = GF_OK;
		}
		//always refresh fragmented files, since we could have a full moof+mdat in buffer (not incomplete file)
		//but still further fragments to be pushed
		if (!read->start_range && !read->end_range)
			read->refresh_fragmented = GF_TRUE;
		read->seg_name_changed = GF_TRUE;

		for (i=0; i<gf_list_count(read->channels); i++) {
			ISOMChannel *ch = gf_list_get(read->channels, i);
			if (ch->last_state==GF_EOS)
				ch->last_state=GF_OK;
		}

#ifndef GPAC_DISABLE_LOG
		if (e<0) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error opening new segment %s at UTC "LLU": %s\n", next_url, gf_net_get_utc(), gf_error_to_string(e) ));
		} else if (read->end_range) {
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Playing new range in %s: "LLU"-"LLU"\n", next_url, read->start_range, read->end_range));
		} else {
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] playing new segment %s\n", next_url));
		}
#endif
		break;
	//this is a movie, reload
	case 2:
	case 1:
		//get tfdt of next segment (cumulated sample dur since moov load)
		//if the next segment has a tfdt or a tfrx, this will be ignored
		//otherwise this value will be used as base tfdt for next segment
		tfdt = gf_isom_get_smooth_next_tfdt(read->mov, 1);
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Switching between files - opening new init segment %s (time offset="LLU") - range "LLU"-"LLU"\n", next_url, tfdt, read->start_range, read->end_range));

		if (gf_isom_is_smooth_streaming_moov(read->mov)) {
			char *tfdt_val = strstr(next_url, "tfdt=");
			//smooth addressing, replace tfdt=0000000000000000 with proper value
			if (tfdt_val) {
				sprintf(tfdt_val+5, LLX, tfdt);
			} else {
				GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[IsoMedia] Error finding init time for init segment %s at UTC "LLU"\n", next_url, gf_net_get_utc() ));
			}
		}

		if (read->mov) gf_isom_close(read->mov);
		e = gf_isom_open_progressive(next_url, read->start_range, read->end_range, read->sigfrag, &read->mov, &read->missing_bytes);

		//init seg not completely downloaded, retry at next packet
		if (!read->input_loaded && (e==GF_ISOM_INCOMPLETE_FILE)) {
			read->src_crc = 0;
			read->moov_not_loaded = 2;
			return GF_OK;
		}

		read->moov_not_loaded = 0;
		if (e < 0) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error opening init segment %s at UTC "LLU": %s\n", next_url, gf_net_get_utc(), gf_error_to_string(e) ));
		}
		if (read->sigfrag)
			gf_isom_enable_traf_map_templates(read->mov);

		is_new_mov = GF_TRUE;
		break;
	//empty file
	case 4:
		return GF_OK;
	default:
		if (!read->mov) {
            return GF_NOT_SUPPORTED;
		}
        e = GF_ISOM_INVALID_FILE;
        break;
	}

	gf_filter_post_process_task(filter);

	count = gf_list_count(read->channels);
	
	if (e<0) {
		count = gf_list_count(read->channels);
		gf_isom_release_segment(read->mov, 1);
        read->invalid_segment = GF_TRUE;
		//error opening the segment, reset everything ...
		gf_isom_reset_fragment_info(read->mov, GF_FALSE);
		for (i=0; i<count; i++) {
			ISOMChannel *ch = gf_list_get(read->channels, i);
            if (ch) {
                ch->sample_num = 0;
                ch->eos_sent = GF_FALSE;
            }
		}
        GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[IsoMedia] Error opening current segment %s: %s\n", next_url, gf_error_to_string(e) ));
		return GF_OK;
	}
	//segment is the first in our cache, we may need a refresh
	if (!read->input_loaded) {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Opening current segment in progressive mode (download in progress)\n"));
	} else {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Opening current segment in non-progressive mode (completely downloaded)\n"));
	}

	isor_check_producer_ref_time(read);

	for (i=0; i<count; i++) {
		ISOMChannel *ch = gf_list_get(read->channels, i);
		ch->last_state = GF_OK;
		ch->eos_sent = GF_FALSE;

		//old code from master, currently no longer used
		//in filters we don't use extractors for the time being, we only do implicit reconstruction at the decoder side
#if 0
		if (ch->base_track) {
			if (scalable_segment)
				trackID = gf_isom_get_highest_track_in_scalable_segment(read->mov, ch->base_track);
				if (trackID) {
					ch->track_id = trackID;
					ch->track = gf_isom_get_track_by_id(read->mov, ch->track_id);
				}
			} else {
				ch->track = ch->base_track;
				ch->track_id = gf_isom_get_track_id(read->mov, ch->track);
			}
		}
#endif

		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track %d - cur sample %d - new sample count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) ));

		//TODO: signal all discontinuities here
		if (is_new_mov) {
			ch->track = gf_isom_get_track_by_id(read->mov, ch->track_id);
			if (!ch->track) {
				if (gf_isom_get_track_count(read->mov)==1) {
					GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Mismatch between track IDs of different representations\n"));
					ch->track = 1;
				} else {
					GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Mismatch between track IDs of different representations\n"));
				}
			}

			/*we changed our moov structure, sample_num now starts from 0*/
			ch->sample_num = 0;
			//this may happen if we reload moov before initializing the channel
			if (!ch->last_sample_desc_index)
				ch->last_sample_desc_index = 1;
			//and update channel config
			isor_update_channel_config(ch);

			/*restore NAL extraction mode*/
			gf_isom_set_nalu_extract_mode(read->mov, ch->track, ch->nalu_extract_mode);

			if (ch->is_cenc) {
				isor_set_crypt_config(ch);
			}
		}

		ch->last_state = GF_OK;
	}

Project: gpac
File: isoffin_read_ch.c

void isor_check_producer_ref_time(ISOMReader *read)
{
	GF_ISOTrackID trackID;
	u64 ntp;
	u64 timestamp;

	if (gf_sys_is_test_mode()) {
		return;
	}

	if (gf_isom_get_last_producer_time_box(read->mov, &trackID, &ntp, &timestamp, GF_TRUE)) {
#if !defined(_WIN32_WCE) && !defined(GPAC_DISABLE_LOG)

		if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_DEBUG)) {
			time_t secs;
			struct tm t;

			s32 diff = gf_net_get_ntp_diff_ms(ntp);

			secs = (ntp>>32) - GF_NTP_SEC_1900_TO_1970;
			t = *gf_gmtime(&secs);

			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] TrackID %d: Timestamp "LLU" matches sender NTP time %d-%02d-%02dT%02d:%02d:%02dZ - NTP clock diff (local - remote): %d ms\n", trackID, timestamp, 1900+t.tm_year, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, diff));
		}
#endif
		read->last_sender_ntp = ntp;
		read->cts_for_last_sender_ntp = timestamp;
		read->ntp_at_last_sender_ntp = gf_net_get_ntp_ts();
	}
}

Project: gpac
File: isoffin_read.c

static void isoffin_purge_mem(ISOMReader *read, u64 min_offset)
{
	u32 i, count;
	u64 top_offset;
	u32 nb_bytes_to_purge;
	u64 bytes_missing;

	//purge every
	if (read->mstore_purge && (min_offset - read->last_min_offset < read->mstore_purge))
		return;

	if (read->frag_type) {
		//get position of current box being parsed - if new offset is greater than this box we cannot remove
		//bytes (we would trash the top-level box header)
		gf_isom_get_current_top_box_offset(read->mov, &top_offset);
		if (top_offset<min_offset) {
			return;
		}
	}
	read->last_min_offset = min_offset;

	assert(min_offset>=read->bytes_removed);
	//min_offset is given in absolute file position
	nb_bytes_to_purge = (u32) (min_offset - read->bytes_removed);
	assert(nb_bytes_to_purge<=read->mem_blob.size);

	memmove(read->mem_blob.data, read->mem_blob.data+nb_bytes_to_purge, read->mem_blob.size - nb_bytes_to_purge);
	read->mem_blob.size -= nb_bytes_to_purge;
	read->bytes_removed += nb_bytes_to_purge;
	gf_isom_set_removed_bytes(read->mov, read->bytes_removed);

	GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[IsoMedia] mem mode %d bytes in mem, "LLU" bytes trashed since start\n", read->mem_blob.size, read->bytes_removed));

	//force a refresh
	gf_isom_refresh_fragmented(read->mov, &bytes_missing, read->mem_url);

	if (!read->frag_type)
		return;

	//fragmented file, cleanup sample tables
	count = gf_list_count(read->channels);
	for (i=0; i<count; i++) {
		ISOMChannel *ch = gf_list_get(read->channels, i);
		u32 num_samples;
		u32 prev_samples = gf_isom_get_sample_count(read->mov, ch->track);
		//don't run this too often
		if (ch->sample_num<=1+read->mstore_samples) continue;

		num_samples = ch->sample_num-1;
		if (num_samples>=prev_samples) continue;

		if (gf_isom_purge_samples(read->mov, ch->track, num_samples) == GF_OK)
			ch->sample_num = 1;

		num_samples = gf_isom_get_sample_count(read->mov, ch->track);
		assert(ch->sample_num<=num_samples);
		GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[IsoMedia] mem mode %d samples now in track %d (prev %d)\n", num_samples, ch->track_id, prev_samples));
	}
}

Project: gpac
File: isoffin_read.c

static GFINLINE Bool isor_is_local(const char *url)
{
	if (!strnicmp(url, "file://", 7)) return GF_TRUE;
	if (!strnicmp(url, "gmem://", 7)) return GF_TRUE;
	if (!strnicmp(url, "gfio://", 7)) return GF_TRUE;
	if (!strnicmp(url, "isobmff://", 10)) return GF_TRUE;
	if (strstr(url, "://")) return GF_FALSE;
	/*the rest is local (mounted on FS)*/
	return GF_TRUE;
}

Project: gpac
File: isoffin_read_ch.c

void isor_reader_get_sample_from_item(ISOMChannel *ch)
{
	if (ch->au_seq_num) {
		if (!ch->owner->itt || !isor_declare_item_properties(ch->owner, ch, 1+ch->au_seq_num)) {
			ch->last_state = GF_EOS;
			return;
		}
	}
	ch->sample_time = 0;
	ch->last_state = GF_OK;
	if (!ch->static_sample) {
		ch->static_sample = gf_isom_sample_new();
	}

	ch->sample = ch->static_sample;
	ch->sample->IsRAP = RAP;
	ch->au_duration = 1000;
	ch->dts = ch->cts = 1000 * ch->au_seq_num;
	gf_isom_extract_meta_item_mem(ch->owner->mov, GF_TRUE, 0, ch->item_id, &ch->sample->data, &ch->sample->dataLength, &ch->static_sample->alloc_size, NULL, GF_FALSE);

	if (ch->is_encrypted && ch->is_cenc) {
		isor_update_cenc_info(ch, GF_TRUE);
	}
}

Project: gpac
File: isoffin_read_ch.c

void isor_reset_reader(ISOMChannel *ch)
{
	ch->last_state = GF_OK;
	isor_reader_release_sample(ch);

	if (ch->static_sample) {
		ch->static_sample->dataLength = ch->static_sample->alloc_size;
		gf_isom_sample_del(&ch->static_sample);
	}
	ch->sample = NULL;
	ch->sample_num = 0;
	ch->speed = 1.0;
	ch->start = ch->end = 0;
	ch->to_init = 1;
	ch->playing = GF_FALSE;
	if (ch->sai_buffer) gf_free(ch->sai_buffer);
	ch->sai_buffer = NULL;
	ch->sai_alloc_size = 0;
	ch->dts = ch->cts = 0;
	ch->seek_flag = 0;
}