GF_EXPORT GF_Err gf_isom_streamer_send_next_packet(GF_ISOMRTPStreamer *streamer, s32 send_ahead_delay, s32 max_sleep_time) { GF_Err e = GF_OK; GF_RTPTrack *track, *to_send; u32 time, duration; s32 diff; u64 min_ts, dts, cts; if (!streamer) return GF_BAD_PARAM; /*browse all sessions and locate most mature stream*/ to_send = NULL; min_ts = (u64) -1; time = gf_sys_clock(); /*init session timeline - all sessions are sync'ed for packet scheduling purposes*/ if (!streamer->timelineOrigin) streamer->timelineOrigin = time*1000; track = streamer->stream; while (track) { /*load next AU*/ if (!track->au) { if (track->current_au >= track->nb_aus) { Double scale; if (!streamer->loop) { track = track->next; continue; } /*increment ts offset*/ scale = track->timescale/1000.0; track->ts_offset += (u32) (streamer->duration_ms * scale); track->microsec_ts_offset = (u32) (track->ts_offset*(1000000.0/track->timescale)) + streamer->timelineOrigin; track->current_au = 0; } track->au = gf_isom_get_sample(streamer->isom, track->track_num, track->current_au + 1, &track->sample_desc_index); track->current_au ++; if (track->au) { track->microsec_dts = (u64) (track->microsec_ts_scale * (s64) (track->au->DTS)) + track->microsec_ts_offset + streamer->timelineOrigin; } } /*check timing*/ if (track->au) { if (min_ts > track->microsec_dts) { min_ts = track->microsec_dts; to_send = track; } } track = track->next; } /*no input data ...*/ if( !to_send) return GF_EOS; min_ts /= 1000; if (max_sleep_time) { diff = ((u32) min_ts) - gf_sys_clock(); if (diff>max_sleep_time) return GF_OK; } /*sleep until TS is mature*/ while (1) { diff = ((u32) min_ts) - gf_sys_clock(); if (diff > send_ahead_delay) { gf_sleep(1); } else { if (diff<10) { GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("WARNING: RTP session %s stream %d - sending packet %d ms too late\n", gf_isom_get_filename(streamer->isom), to_send->track_num, -diff)); } break; } } /*send packets*/ dts = to_send->au->DTS + to_send->ts_offset; cts = to_send->au->DTS + to_send->au->CTS_Offset + to_send->ts_offset; duration = gf_isom_get_sample_duration(streamer->isom, to_send->track_num, to_send->current_au); /*unpack nal units*/ if (to_send->avc_nalu_size) { Bool au_start, au_end; u32 v, size; u32 remain = to_send->au->dataLength; char *ptr = to_send->au->data; au_start = 1; au_end = 0; while (remain) { size = 0; v = to_send->avc_nalu_size; while (v) { size |= (u8) *ptr; ptr++; remain--; v-=1; if (v) size<<=8; } remain -= size; au_end = remain ? 0 : 1; e = gf_rtp_streamer_send_data(to_send->rtp, ptr, size, to_send->au->dataLength, cts, dts, to_send->au->IsRAP, au_start, au_end, to_send->current_au, duration, to_send->sample_desc_index); ptr += size; au_start = 0; } } else { e = gf_rtp_streamer_send_data(to_send->rtp, to_send->au->data, to_send->au->dataLength, to_send->au->dataLength, cts, dts, to_send->au->IsRAP, 1, 1, to_send->current_au, duration, to_send->sample_desc_index); } /*delete sample*/ gf_isom_sample_del(&to_send->au); return e; }
GF_EXPORT GF_Err gf_hinter_track_process(GF_RTPHinter *tkHint) { GF_Err e; u32 i, descIndex, duration; u64 ts; u8 PadBits; Double ft; GF_ISOSample *samp; tkHint->HintSample = tkHint->RTPTime = 0; tkHint->TotalSample = gf_isom_get_sample_count(tkHint->file, tkHint->TrackNum); ft = tkHint->rtp_p->sl_config.timestampResolution; ft /= tkHint->OrigTimeScale; e = GF_OK; for (i=0; i<tkHint->TotalSample; i++) { samp = gf_isom_get_sample(tkHint->file, tkHint->TrackNum, i+1, &descIndex); if (!samp) return GF_IO_ERR; //setup SL tkHint->CurrentSample = i + 1; /*keep same AU indicator if sync shadow - TODO FIXME: this assumes shadows are placed interleaved with the track content which is the case for GPAC scene carousel generation, but may not always be true*/ if (samp->IsRAP==2) { tkHint->rtp_p->sl_header.AU_sequenceNumber -= 1; samp->IsRAP = 1; } ts = (u64) (ft * (s64) (samp->DTS+samp->CTS_Offset)); tkHint->rtp_p->sl_header.compositionTimeStamp = ts; ts = (u64) (ft * (s64)(samp->DTS)); tkHint->rtp_p->sl_header.decodingTimeStamp = ts; tkHint->rtp_p->sl_header.randomAccessPointFlag = samp->IsRAP; tkHint->base_offset_in_sample = 0; /*crypted*/ if (tkHint->rtp_p->slMap.IV_length) { GF_ISMASample *s = gf_isom_get_ismacryp_sample(tkHint->file, tkHint->TrackNum, samp, descIndex); /*one byte take for selective_enc flag*/ if (s->flags & GF_ISOM_ISMA_USE_SEL_ENC) tkHint->base_offset_in_sample += 1; if (s->flags & GF_ISOM_ISMA_IS_ENCRYPTED) tkHint->base_offset_in_sample += s->IV_length + s->KI_length; gf_free(samp->data); samp->data = s->data; samp->dataLength = s->dataLength; gp_rtp_builder_set_cryp_info(tkHint->rtp_p, s->IV, (char*)s->key_indicator, (s->flags & GF_ISOM_ISMA_IS_ENCRYPTED) ? 1 : 0); s->data = NULL; s->dataLength = 0; gf_isom_ismacryp_delete_sample(s); } if (tkHint->rtp_p->sl_config.usePaddingFlag) { gf_isom_get_sample_padding_bits(tkHint->file, tkHint->TrackNum, i+1, &PadBits); tkHint->rtp_p->sl_header.paddingBits = PadBits; } else { tkHint->rtp_p->sl_header.paddingBits = 0; } duration = gf_isom_get_sample_duration(tkHint->file, tkHint->TrackNum, i+1); ts = (u32) (ft * (s64) (duration)); /*unpack nal units*/ if (tkHint->avc_nalu_size) { u32 v, size; u32 remain = samp->dataLength; char *ptr = samp->data; tkHint->rtp_p->sl_header.accessUnitStartFlag = 1; tkHint->rtp_p->sl_header.accessUnitEndFlag = 0; while (remain) { size = 0; v = tkHint->avc_nalu_size; while (v) { size |= (u8) *ptr; ptr++; remain--; v-=1; if (v) size<<=8; } tkHint->base_offset_in_sample = samp->dataLength-remain; remain -= size; tkHint->rtp_p->sl_header.accessUnitEndFlag = remain ? 0 : 1; e = gf_rtp_builder_process(tkHint->rtp_p, ptr, size, (u8) !remain, samp->dataLength, duration, (u8) (descIndex + GF_RTP_TX3G_SIDX_OFFSET) ); ptr += size; tkHint->rtp_p->sl_header.accessUnitStartFlag = 0; } } else { e = gf_rtp_builder_process(tkHint->rtp_p, samp->data, samp->dataLength, 1, samp->dataLength, duration, (u8) (descIndex + GF_RTP_TX3G_SIDX_OFFSET) ); } tkHint->rtp_p->sl_header.packetSequenceNumber += 1; //signal some progress gf_set_progress("Hinting", tkHint->CurrentSample, tkHint->TotalSample); tkHint->rtp_p->sl_header.AU_sequenceNumber += 1; gf_isom_sample_del(&samp); if (e) return e; } //flush gf_rtp_builder_process(tkHint->rtp_p, NULL, 0, 1, 0, 0, 0); gf_isom_end_hint_sample(tkHint->file, tkHint->HintTrack, (u8) tkHint->SampleIsRAP); return GF_OK; }
GF_EXPORT GF_Err gf_isom_streamer_send_next_packet(GF_ISOMRTPStreamer *streamer, s32 send_ahead_delay, s32 max_sleep_time) { GF_Err e = GF_OK; GF_RTPTrack *track, *to_send; u32 time, duration; s32 diff; u64 min_ts, dts, cts; if (!streamer) return GF_BAD_PARAM; /*browse all sessions and locate most mature stream*/ to_send = NULL; min_ts = (u64) -1; time = gf_sys_clock(); /*init session timeline - all sessions are sync'ed for packet scheduling purposes*/ if (!streamer->timelineOrigin) { streamer->timelineOrigin = time*1000; GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[FileStreamer] RTP session %s initialized - time origin set to %d\n", gf_isom_get_filename(streamer->isom), time)); } track = streamer->stream; while (track) { /*load next AU*/ gf_isom_set_nalu_extract_mode(streamer->isom, track->track_num, GF_ISOM_NALU_EXTRACT_LAYER_ONLY); if (!track->au) { if (track->current_au >= track->nb_aus) { Double scale; if (!streamer->loop) { track = track->next; continue; } /*increment ts offset*/ scale = track->timescale/1000.0; track->ts_offset += (u32) (streamer->duration_ms * scale); track->microsec_ts_offset = (u32) (track->ts_offset*(1000000.0/track->timescale)) + streamer->timelineOrigin; track->current_au = 0; } track->au = gf_isom_get_sample(streamer->isom, track->track_num, track->current_au + 1, &track->sample_desc_index); track->current_au ++; if (track->au) { track->microsec_dts = (u64) (track->microsec_ts_scale * (s64) (track->au->DTS)) + track->microsec_ts_offset + streamer->timelineOrigin; } } /*check timing*/ if (track->au) { if (min_ts > track->microsec_dts) { min_ts = track->microsec_dts; to_send = track; } } track = track->next; } /*no input data ...*/ if( !to_send) return GF_EOS; /*we are about to send scalable base: trigger RTCP reports with the same NTP. This avoids NTP drift due to system clock precision which could break sync decoding*/ if (!streamer->first_RTCP_sent || (streamer->base_track && streamer->base_track==to_send->track_num)) { u32 ntp_sec, ntp_frac; /*force sending RTCP SR every RAP ? - not really compliant but we cannot perform scalable tuning otherwise*/ u32 ntp_type = to_send->au->IsRAP ? 2 : 1; gf_net_get_ntp(&ntp_sec, &ntp_frac); track = streamer->stream; while (track) { u32 ts = (u32) (track->au->DTS + track->au->CTS_Offset + track->ts_offset); gf_rtp_streamer_send_rtcp(track->rtp, GF_TRUE, ts, ntp_type, ntp_sec, ntp_frac); track = track->next; } streamer->first_RTCP_sent = 1; } min_ts /= 1000; if (max_sleep_time) { diff = ((u32) min_ts) - gf_sys_clock(); if (diff>max_sleep_time) return GF_OK; } /*sleep until TS is mature*/ while (1) { diff = ((u32) min_ts) - gf_sys_clock(); if (diff > send_ahead_delay) { gf_sleep(1); } else { if (diff<10) { GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("WARNING: RTP session %s stream %d - sending packet %d ms too late\n", gf_isom_get_filename(streamer->isom), to_send->track_num, -diff)); } break; } } /*send packets*/ dts = to_send->au->DTS + to_send->ts_offset; cts = to_send->au->DTS + to_send->au->CTS_Offset + to_send->ts_offset; duration = gf_isom_get_sample_duration(streamer->isom, to_send->track_num, to_send->current_au); GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[FileStreamer] Sending RTP packets for track %d AU %d/%d DTS "LLU" - CTS "LLU" - RTP TS "LLU" - size %d - RAP %d\n", to_send->track_num, to_send->current_au, to_send->nb_aus, to_send->au->DTS, to_send->au->DTS+to_send->au->CTS_Offset, cts, to_send->au->dataLength, to_send->au->IsRAP ) ); /*unpack nal units*/ if (to_send->avc_nalu_size) { Bool au_start, au_end; u32 v, size; u32 remain = to_send->au->dataLength; char *ptr = to_send->au->data; au_start = 1; au_end = 0; while (remain) { size = 0; v = to_send->avc_nalu_size; while (v) { size |= (u8) *ptr; ptr++; remain--; v-=1; if (v) size<<=8; } if (remain < size) { GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[rtp hinter] Broken AVC nalu encapsulation: NALU size is %d but only %d bytes left in sample %d\n", size, remain, to_send->current_au)); break; } remain -= size; au_end = remain ? 0 : 1; e = gf_rtp_streamer_send_data(to_send->rtp, ptr, size, to_send->au->dataLength, cts, dts, (to_send->au->IsRAP==RAP) ? 1 : 0, au_start, au_end, to_send->current_au, duration, to_send->sample_desc_index); ptr += size; au_start = 0; } } else { e = gf_rtp_streamer_send_data(to_send->rtp, to_send->au->data, to_send->au->dataLength, to_send->au->dataLength, cts, dts, (to_send->au->IsRAP==RAP) ? 1 : 0, 1, 1, to_send->current_au, duration, to_send->sample_desc_index); } /*delete sample*/ gf_isom_sample_del(&to_send->au); return e; }