modules/isom_in/read_ch.c (686 lines of code) (raw):
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2012
* All rights reserved
*
* This file is part of GPAC / MP4 reader module
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include "isom_in.h"
#include <gpac/network.h>
#include <time.h>
#ifndef GPAC_DISABLE_ISOM
void isor_reset_reader(ISOMChannel *ch)
{
ch->last_state = GF_OK;
isor_reader_release_sample(ch);
ch->sample = NULL;
ch->sample_num = 0;
ch->speed = 1.0;
ch->start = ch->end = 0;
ch->to_init = 1;
ch->is_playing = 0;
memset(&ch->current_slh, 0, sizeof(GF_SLHeader));
}
void isor_check_producer_ref_time(ISOMReader *read)
{
u32 trackID;
u64 ntp;
u64 timestamp;
if (gf_isom_get_last_producer_time_box(read->mov, &trackID, &ntp, ×tamp, 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 = *gmtime(&secs);
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] TrackID %d: Timestamp %d matches sender NTP time %d-%02d-%02dT%02d:%02d:%02dZ - NTP clock diff (local - remote): %d ms\n", trackID, (u32) 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;
}
}
/*
refresh type:
0: not progressive
1: progressive
2: not progressive and don't check current download
*/
void isor_segment_switch_or_refresh(ISOMReader *read, Bool do_refresh)
{
GF_NetworkCommand param;
u32 i, count;
Bool scalable_segment = 0;
GF_Err e;
/*access to the segment switching must be protected in case several decoders are threaded on the file using GetSLPacket */
gf_mx_p(read->segment_mutex);
if (!read->frag_type || !read->input->query_proxy ) {
gf_mx_v(read->segment_mutex);
return;
}
memset(¶m, 0, sizeof(GF_NetworkCommand));
param.command_type = GF_NET_SERVICE_QUERY_NEXT;
//always check current download - this might be ignored by the dash client depending on lowLatency settings
param.url_query.current_download = 1;
count = gf_list_count(read->channels);
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Refresh seg: do_refresh %d - seg opened %d\n", do_refresh, read->seg_opened));
if (do_refresh && !read->seg_opened)
do_refresh = 0;
if ((read->seg_opened==1) && !do_refresh) {
gf_mx_v(read->segment_mutex);
return;
}
if (read->drop_next_segment) {
read->drop_next_segment = 0;
param.url_query.drop_first_segment = 1;
}
//if first time trying to fetch next segment, check if we have to discard it
if (!do_refresh && (read->seg_opened==2)) {
#ifdef DASH_USE_PULL
for (i=0; i<count; i++) {
ISOMChannel *ch = gf_list_get(read->channels, i);
/*check all playing channels are waiting for next segment*/
if (ch->is_playing && !ch->wait_for_segment_switch) {
gf_mx_v(read->segment_mutex);
return;
}
}
#endif
/*close current segment*/
gf_isom_release_segment(read->mov, 1);
read->seg_opened = 0;
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Done playing segment - querying new one\n"));
/*drop this segment*/
param.url_query.drop_first_segment = 1;
}
next_segment:
/*update current fragment if any*/
e = read->input->query_proxy(read->input, ¶m);
if (e == GF_OK) {
u32 trackID = 0;
if (param.url_query.next_url) {
u32 flags;
//previously loaded file has been aborted, reload segment !
if (do_refresh && param.url_query.discontinuity_type) {
gf_isom_release_segment(read->mov, 1);
gf_isom_reset_fragment_info(read->mov, 1);
do_refresh = 0;
}
if (read->reset_frag_state) {
read->reset_frag_state = 0;
gf_isom_reset_fragment_info(read->mov, 0);
}
//refresh file
if (do_refresh) {
//the url is the current download or we just finished downloaded it, refresh the parsing.
if ((param.url_query.current_download || (read->seg_opened==1)) && param.url_query.has_new_data) {
u64 bytesMissing=0;
e = gf_isom_refresh_fragmented(read->mov, &bytesMissing, read->use_memory ? param.url_query.next_url : NULL);
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[IsoMedia] LowLatency mode: Reparsing segment %s boxes at UTC "LLU" - "LLU" bytes still missing\n", param.url_query.next_url, gf_net_get_utc(), 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);
}
//we did the last refresh and the segment is downloaded, move to fully parsed mode
if (! param.url_query.current_download) {
read->seg_opened = 2;
}
gf_mx_v(read->segment_mutex);
return;
}
//we have to open the file
if (param.url_query.discontinuity_type==2) {
gf_isom_reset_fragment_info(read->mov, 0);
read->clock_discontinuity = 1;
}
e = GF_OK;
if (param.url_query.next_url_init_or_switch_segment) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Switching between files - opening new init segment %s\n", param.url_query.next_url_init_or_switch_segment));
if (read->mov) gf_isom_close(read->mov);
e = gf_isom_open_progressive(param.url_query.next_url_init_or_switch_segment, param.url_query.switch_start_range, param.url_query.switch_end_range, &read->mov, &read->missing_bytes);
if (e<0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error opening init segment %s at UTC "LLU": %s\n", param.url_query.next_url_init_or_switch_segment, gf_net_get_utc(), gf_error_to_string(e) ));
}
}
if (!e) {
flags = 0;
if (read->no_order_check) flags |= GF_ISOM_SEGMENT_NO_ORDER_FLAG;
if (scalable_segment) flags |= GF_ISOM_SEGMENT_SCALABLE_FLAG;
e = gf_isom_open_segment(read->mov, param.url_query.next_url, param.url_query.start_range, param.url_query.end_range, flags);
if (param.url_query.current_download && (e==GF_ISOM_INCOMPLETE_FILE)) {
e = 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", param.url_query.next_url, gf_net_get_utc(), gf_error_to_string(e) ));
} else if (param.url_query.end_range) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Playing new range in %s: "LLU"-"LLU"\n", param.url_query.next_url, param.url_query.start_range, param.url_query.end_range ));
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] playing new segment %s\n", param.url_query.next_url));
}
#endif
}
if (e<0) {
gf_isom_release_segment(read->mov, 1);
//gf_isom_reset_fragment_info(read->mov, 1);
read->drop_next_segment = 1;
//error opening the segment, reset everything ...
gf_isom_reset_fragment_info(read->mov, 0);
for (i=0; i<count; i++) {
ISOMChannel *ch = gf_list_get(read->channels, i);
if (ch)
ch->sample_num = 0;
}
//cannot open file, don't change our state
gf_mx_v(read->segment_mutex);
return;
}
//segment is the first in our cache, we may need a refresh
if (param.url_query.current_download ) {
read->seg_opened = 1;
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Opening current segment in progressive mode (download in progress)\n"));
} else {
read->seg_opened = 2;
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);
trackID = 0;
for (i=0; i<count; i++) {
ISOMChannel *ch = gf_list_get(read->channels, i);
ch->wait_for_segment_switch = 0;
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 if (ch->base_track) {
ch->track = ch->base_track;
ch->track_id = gf_isom_get_track_id(read->mov, ch->track);
}
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) ));
if (param.url_query.next_url_init_or_switch_segment) {
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;
}
//a loop was detected, our timing is no longer reliable if we use edit lists - just reset the sample time to tfdt ...
else if (param.url_query.discontinuity_type==2) {
ch->sample_num = 0;
if (ch->has_edit_list) {
ch->sample_time = gf_isom_get_current_tfdt(read->mov, ch->track);
//next read will query sample for ch->sample_time + 1
if (ch->sample_time) ch->sample_time--;
}
}
/*rewrite all upcoming SPS/PPS into the samples*/
gf_isom_set_nalu_extract_mode(read->mov, ch->track, ch->nalu_extract_mode);
ch->last_state = GF_OK;
if (ch->is_cenc) {
isor_send_cenc_config(ch);
}
}
read->use_memory = !strncmp(param.url_query.next_url, "gmem://", 7) ? GF_TRUE : GF_FALSE;
}
if (param.url_query.has_next) {
param.url_query.drop_first_segment = GF_FALSE;
param.url_query.dependent_representation_index++;
scalable_segment = 1;
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Enhancement layer available in cache - refreshing it\n"));
goto next_segment;
}
read->waiting_for_data = GF_FALSE;
} else if (e==GF_EOS) {
/*consider we are done*/
read->frag_type = 2;
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] No more segments - done playing file\n"));
} else if (e==GF_BUFFER_TOO_SMALL) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Next segment is not yet available\n"));
read->waiting_for_data = GF_TRUE;
} else {
/*consider we are done*/
read->frag_type = 2;
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error fetching next DASH segment: no more segments\n"));
}
gf_mx_v(read->segment_mutex);
}
static void init_reader(ISOMChannel *ch)
{
u32 sample_desc_index;
if (ch->is_pulling && ch->wait_for_segment_switch) {
isor_segment_switch_or_refresh(ch->owner, 0);
if (ch->wait_for_segment_switch)
return;
}
ch->current_slh.accessUnitEndFlag = 1;
ch->current_slh.accessUnitStartFlag = 1;
ch->current_slh.AU_sequenceNumber = 1;
ch->current_slh.compositionTimeStampFlag = 1;
ch->current_slh.decodingTimeStampFlag = 1;
ch->current_slh.packetSequenceNumber = 1;
ch->current_slh.randomAccessPointFlag = 0;
assert(ch->sample==NULL);
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 seek is disabled, get the next closest sample for this time; otherwose, 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, &sample_desc_index, mode, &ch->sample, &ch->sample_num);
} else {
ch->last_state = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->start, &sample_desc_index, mode, &ch->sample, &ch->sample_num);
}
ch->last_state = GF_OK;
if (ch->has_rap && ch->has_edit_list) {
ch->edit_sync_frame = ch->sample_num;
}
}
/*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)) {
GF_NetIOStatus net_status;
gf_dm_sess_get_stats(ch->owner->dnload, NULL, NULL, NULL, NULL, NULL, &net_status);
if (net_status == GF_NETIO_DATA_EXCHANGE) {
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;
}
if (ch->has_edit_list) {
ch->sample_time = ch->sample->DTS;
} else {
//store movie time in media timescale in the sample time, eg no edit list is used but we may have a shift (dts_offset) between
//movie and media timelines
if ((ch->dts_offset<0) && (ch->sample->DTS < (u64) -ch->dts_offset)) //should not happen
ch->sample_time = 0;
else
ch->sample_time = ch->sample->DTS + ch->dts_offset;
}
ch->to_init = 0;
ch->current_slh.seekFlag = 0;
if (ch->disable_seek) {
ch->current_slh.decodingTimeStamp = ch->sample->DTS;
ch->current_slh.compositionTimeStamp = ch->sample->DTS + ch->sample->CTS_Offset;
ch->start = 0;
} else {
ch->current_slh.decodingTimeStamp = ch->start;
ch->current_slh.compositionTimeStamp = ch->start;
if (ch->current_slh.compositionTimeStamp != ch->sample->DTS + ch->sample->CTS_Offset) {
ch->current_slh.seekFlag = 1;
}
}
ch->current_slh.randomAccessPointFlag = ch->sample ? ch->sample->IsRAP : 0;
ch->last_sample_desc_index = sample_desc_index;
ch->owner->no_order_check = ch->speed < 0 ? GF_TRUE : GF_FALSE;
}
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->owner->seg_opened==1) && ch->is_pulling) {
isor_segment_switch_or_refresh(ch->owner, 1);
}
if (ch->to_init) {
init_reader(ch);
sample_desc_index = ch->last_sample_desc_index;
} else if (ch->speed < 0) {
if (!ch->sample_time) {
ch->last_state = GF_EOS;
return;
} else {
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->sample, &ch->sample_num);
if (e) {
if ((e==GF_EOS) && !ch->owner->frag_type) {
ch->last_state = GF_EOS;
}
return;
}
}
if (ch->sample->DTS + ch->dts_offset == ch->sample_time) {
if (!ch->owner->frag_type) {
ch->last_state = GF_EOS;
} else {
if (ch->sample)
gf_isom_sample_del(&ch->sample);
}
}
if (ch->sample) {
if ((ch->dts_offset<0) && (ch->sample->DTS < (u64) -ch->dts_offset)) //should not happen
ch->sample_time = 0;
else
ch->sample_time = ch->sample->DTS + ch->dts_offset;
}
} 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->sample, &ch->sample_num);
if (e == GF_OK) {
/*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) {
gf_isom_sample_del(&ch->sample);
ch->sample = gf_isom_get_sample(ch->owner->mov, ch->track, ch->edit_sync_frame, &sample_desc_index);
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))) {
if (ch->sample)
gf_isom_sample_del(&ch->sample);
} else {
u32 time_diff = 2;
u32 sample_num = ch->sample_num ? ch->sample_num : 1;
GF_ISOSample *s1 = gf_isom_get_sample(ch->owner->mov, ch->track, sample_num, NULL);
GF_ISOSample *s2 = gf_isom_get_sample(ch->owner->mov, ch->track, sample_num+1, NULL);
gf_isom_sample_del(&ch->sample);
if (s2 && s1) {
assert(s2->DTS >= s1->DTS);
time_diff = (u32) (s2->DTS - s1->DTS);
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->sample, &ch->sample_num);
} else if (s1 && !s2) {
e = GF_EOS;
}
gf_isom_sample_del(&s1);
gf_isom_sample_del(&s2);
}
}
/*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->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->sample, &ch->sample_num);
assert (e == GF_OK);
/*if no sync point in the past, use the first non-sync for the given time*/
if (!ch->sample || !ch->sample->data) {
gf_isom_sample_del(&ch->sample);
ch->sample = found;
ch->sample_time = ch->sample->DTS;
ch->sample_num = samp_num;
} else {
gf_isom_sample_del(&found);
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 {
ch->sample_num++;
ch->sample = gf_isom_get_sample(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index);
/*if sync shadow / carousel RAP skip*/
if (ch->sample && (ch->sample->IsRAP==RAP_REDUNDANT)) {
gf_isom_sample_del(&ch->sample);
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;
gf_isom_sample_del(&ch->sample);
isor_reader_get_sample(ch);
return;
}
if (ch->sample && ch->dts_offset) {
if ( (ch->dts_offset<0) && (ch->sample->DTS < (u64) -ch->dts_offset)) {
ch->sample->DTS = 0;
} else {
ch->sample->DTS += ch->dts_offset;
}
}
if (!ch->sample) {
/*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->dnload) {
GF_NetIOStatus net_status;
gf_dm_sess_get_stats(ch->owner->dnload, NULL, NULL, NULL, NULL, NULL, &net_status);
if (net_status == GF_NETIO_DATA_EXCHANGE) {
ch->last_state = GF_OK;
if (!ch->has_edit_list)
ch->sample_num--;
}
}
else if (ch->owner->input->query_proxy) {
ch->last_state = GF_OK;
if (!ch->has_edit_list && ch->sample_num)
ch->sample_num--;
}
}
else if (!ch->sample_num
|| ((ch->speed >= 0) && (ch->sample_num >= gf_isom_get_sample_count(ch->owner->mov, ch->track)))
|| ((ch->speed < 0) && (ch->sample_time == gf_isom_get_current_tfdt(ch->owner->mov, ch->track) + ch->dts_offset))
) {
if (ch->owner->frag_type==1) {
//if segment is fully opened and no more data, this track is done, wait for next segment
if (!ch->wait_for_segment_switch && ch->owner->input->query_proxy && (ch->owner->seg_opened==2) ) {
ch->wait_for_segment_switch = 1;
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track #%d end of segment reached - waiting for sample %d - current count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) ));
}
/*if sample cannot be found and file is fragmented, rewind sample*/
if (ch->sample_num) ch->sample_num--;
ch->last_state = GF_OK;
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track #%d end of stream reached\n", ch->track));
ch->last_state = GF_EOS;
}
} else {
GF_LOG(GF_LOG_DEBUG, 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(gf_isom_last_error(ch->owner->mov)) ));
}
if (ch->wait_for_segment_switch && ch->is_pulling) {
isor_segment_switch_or_refresh(ch->owner, 0);
if (ch->owner->seg_opened) {
isor_reader_get_sample(ch);
return;
}
}
return;
}
if (sample_desc_index != ch->last_sample_desc_index) {
u32 mtype = gf_isom_get_media_type(ch->owner->mov, ch->track);
switch (mtype) {
case GF_ISOM_MEDIA_VISUAL:
//code is here as a reminder, but by default we use inband param set extraction so no need for it
#if 0
if ( ! (ch->nalu_extract_mode & GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG) ) {
u32 extract_mode = ch->nalu_extract_mode | GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG;
gf_isom_sample_del(&ch->sample);
ch->sample = NULL;
gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, extract_mode);
ch->sample = gf_isom_get_sample(ch->owner->mov, ch->track, ch->sample_num, &ch->last_sample_desc_index);
gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode);
}
#endif
break;
case GF_ISOM_MEDIA_TEXT:
case GF_ISOM_MEDIA_SUBPIC:
case GF_ISOM_MEDIA_MPEG_SUBT:
break;
default:
//TODO: do we want to support codec changes ?
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] Change of sample description (%d->%d) for media type %s not supported\n", ch->last_sample_desc_index, sample_desc_index, gf_4cc_to_str(mtype) ));
gf_isom_sample_del(&ch->sample);
ch->sample = NULL;
ch->last_state = GF_NOT_SUPPORTED;
return;
}
}
ch->last_state = GF_OK;
ch->current_slh.accessUnitEndFlag = ch->current_slh.accessUnitStartFlag = 1;
ch->current_slh.accessUnitLength = ch->sample->dataLength;
ch->current_slh.au_duration = gf_isom_get_sample_duration(ch->owner->mov, ch->track, ch->sample_num);
/*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 - !! FIXME we should not change the TS but rather signal the frame is a seek frame
*/
if ((ch->speed < 0) || (ch->start <= ch->sample->DTS + ch->sample->CTS_Offset)) {
ch->current_slh.decodingTimeStamp = ch->sample->DTS;
ch->current_slh.compositionTimeStamp = ch->sample->DTS + ch->sample->CTS_Offset;
ch->current_slh.seekFlag = 0;
} else {
ch->current_slh.compositionTimeStamp = ch->start;
ch->current_slh.seekFlag = 1;
if (ch->streamType==GF_STREAM_SCENE)
ch->current_slh.decodingTimeStamp = ch->sample->DTS;
else
ch->current_slh.decodingTimeStamp = ch->start;
}
ch->current_slh.randomAccessPointFlag = ch->sample->IsRAP;
ch->current_slh.OCRflag = ch->owner->clock_discontinuity ? 2 : 0;
ch->owner->clock_discontinuity = 0;
if (ch->end && (ch->end < ch->sample->DTS + ch->sample->CTS_Offset)) {
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->last_state = GF_EOS;
}
if (ch->owner->last_sender_ntp && ch->current_slh.compositionTimeStamp==ch->owner->cts_for_last_sender_ntp) {
ch->current_slh.sender_ntp = ch->owner->last_sender_ntp;
} else {
ch->current_slh.sender_ntp = 0;
}
if (ch->is_encrypted) {
GF_ISMASample *ismasamp = gf_isom_get_ismacryp_sample(ch->owner->mov, ch->track, ch->sample, 1);
if (ismasamp) {
gf_free(ch->sample->data);
ch->sample->data = ismasamp->data;
ch->sample->dataLength = ismasamp->dataLength;
ismasamp->data = NULL;
ismasamp->dataLength = 0;
ch->current_slh.isma_encrypted = (ismasamp->flags & GF_ISOM_ISMA_IS_ENCRYPTED) ? 1 : 0;
ch->current_slh.isma_BSO = ismasamp->IV;
gf_isom_ismacryp_delete_sample(ismasamp);
} else {
ch->current_slh.isma_encrypted = 0;
/*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, 1)) {
GF_CENCSampleAuxInfo *sai;
u32 Is_Encrypted;
u8 IV_size;
bin128 KID;
gf_isom_get_sample_cenc_info(ch->owner->mov, ch->track, ch->sample_num, &Is_Encrypted, &IV_size, &KID);
ch->current_slh.IV_size = IV_size;
if (Is_Encrypted) {
ch->current_slh.cenc_encrypted = 1;
sai = NULL;
gf_isom_cenc_get_sample_aux_info(ch->owner->mov, ch->track, ch->sample_num, &sai, NULL);
if (sai) {
u32 i;
GF_BitStream *bs;
bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
/*write sample auxiliary information*/
gf_bs_write_data(bs, (char *)KID, 16);
gf_bs_write_data(bs, (char *)sai->IV, IV_size);
gf_bs_write_u16(bs, sai->subsample_count);
for (i = 0; i < sai->subsample_count; i++) {
gf_bs_write_u16(bs, sai->subsamples[i].bytes_clear_data);
gf_bs_write_u32(bs, sai->subsamples[i].bytes_encrypted_data);
}
gf_bs_get_content(bs, &ch->current_slh.sai, &ch->current_slh.saiz);
gf_bs_del(bs);
gf_isom_cenc_samp_aux_info_del(sai);
sai = NULL;
}
}
else
ch->current_slh.cenc_encrypted = 0;
}
}
}
}
void isor_reader_release_sample(ISOMChannel *ch)
{
if (ch->current_slh.sai) {
gf_free(ch->current_slh.sai);
ch->current_slh.sai = NULL;
}
if (ch->sample) gf_isom_sample_del(&ch->sample);
ch->sample = NULL;
ch->current_slh.AU_sequenceNumber++;
ch->current_slh.packetSequenceNumber++;
}
void isor_flush_data(ISOMReader *read, Bool check_buffer_level, Bool is_chunk_flush)
{
u32 i, count;
GF_Err e = GF_OK;
Bool do_refresh;
GF_NetworkCommand com;
ISOMChannel *ch;
if (read->in_data_flush) {
if (check_buffer_level && !is_chunk_flush) read->has_pending_segments++;
return;
}
//if another thread grabs the mutex at the same time, just return
if (!gf_mx_try_lock(read->segment_mutex)) {
if (check_buffer_level && !is_chunk_flush) read->has_pending_segments++;
return;
}
read->in_data_flush = 1;
count = gf_list_count(read->channels);
do_refresh = (read->seg_opened==1) ? 1 : 0;
if (do_refresh) {
//query from terminal - do nothing if we are not done downloading the segment - otherwise we could access media data while it is reallocated by the downloader
if (!check_buffer_level) {
read->in_data_flush = 0;
gf_mx_v(read->segment_mutex);
return;
}
//otherwise this is new chunk or end of chunk, process
}
//this is a new file, check buffer level
else if (/*!is_chunk_flush && */ check_buffer_level) {
Bool buffer_full = 1;
for (i=0; i<count; i++) {
ch = (ISOMChannel *)gf_list_get(read->channels, i);
/*query buffer level on each channel, don't sleep if too low*/
memset(&com, 0, sizeof(GF_NetworkCommand));
com.command_type = GF_NET_BUFFER_QUERY;
com.base.on_channel = ch->channel;
gf_service_command(read->service, &com, GF_OK);
if ((com.buffer.occupancy < MAX(com.buffer.max, 1000) )) {
buffer_full = 0;
break;
}
}
if (buffer_full) {
read->has_pending_segments++;
read->in_data_flush = 0;
gf_mx_v(read->segment_mutex);
if (count) {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[IsoMedia] Buffer level %d ms higher than max allowed %d ms - skipping dispatch\n", com.buffer.occupancy, com.buffer.max));
}
return;
}
}
//flush request from terminal: only process if nothing is opened and we have pending segments
//we have to keep the polling event when no segments are pending, in order to detect period switch - we therefore tolerate a couble of requests even though no segments are pending
if (!check_buffer_level && !read->seg_opened && !read->has_pending_segments && (read->nb_force_flush > 2) && !read->drop_next_segment) {
read->in_data_flush = 0;
gf_mx_v(read->segment_mutex);
return;
}
//if this is a request from terminal to flush pending segments, try to refresh
if (!check_buffer_level && !do_refresh)
do_refresh = 1;
//update data
isor_segment_switch_or_refresh(read, do_refresh);
//for all channels, fetch and send ...
count = gf_list_count(read->channels);
for (i=0; i<count; i++) {
ch = (ISOMChannel *)gf_list_get(read->channels, i);
while (!ch->sample) {
isor_reader_get_sample(ch);
if (!ch->sample) break;
if (ch->is_playing)
gf_service_send_packet(read->service, ch->channel, ch->sample->data, ch->sample->dataLength, &ch->current_slh, GF_OK);
isor_reader_release_sample(ch);
}
if (!ch->sample && (ch->last_state==GF_EOS)) {
gf_service_send_packet(read->service, ch->channel, NULL, 0, NULL, GF_EOS);
}
}
//done playing the file after notification from DASH client:
if (read->seg_opened==2) {
GF_NetworkCommand param;
//drop this segment
memset(¶m, 0, sizeof(GF_NetworkCommand));
param.command_type = GF_NET_SERVICE_QUERY_NEXT;
param.url_query.dependent_representation_index = 0;
param.url_query.drop_first_segment = 1;
e = read->input->query_proxy(read->input, ¶m);
read->nb_force_flush = 0;
if (read->has_pending_segments) {
read->has_pending_segments--;
}
if (e==GF_EOS) {
for (i=0; i<count; i++) {
ch = (ISOMChannel *)gf_list_get(read->channels, i);
gf_service_send_packet(read->service, ch->channel, NULL, 0, NULL, GF_EOS);
}
}
//not enough data
else if (param.url_query.in_end_of_period) {
//act as if we have a pending segment to process at next call until we get to the next period
if (!read->has_pending_segments)
read->has_pending_segments++;
}
/*close current segment*/
gf_isom_release_segment(read->mov, 1);
read->seg_opened = 0;
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Done playing segment \n"));
}
read->in_data_flush = 0;
if (!read->has_pending_segments) {
read->nb_force_flush ++;
}
gf_mx_v(read->segment_mutex);
}
#endif /*GPAC_DISABLE_ISOM*/