static void imagetexture_destroy(GF_Node *node, void *rs, Bool is_destroy) { if (is_destroy) { GF_TextureHandler *txh = (GF_TextureHandler *) gf_node_get_private(node); /*cleanup cache if needed*/ if (gf_node_get_tag(node)==TAG_MPEG4_CacheTexture) { char section[64]; const char *opt, *file; Bool delete_file = 1; M_CacheTexture *ct = (M_CacheTexture*)node; sprintf(section, "@cache=%p", ct); file = gf_cfg_get_key(txh->compositor->user->config, section, "cacheFile"); opt = gf_cfg_get_key(txh->compositor->user->config, section, "expireAfterNTP"); if (opt) { u32 sec, frac, exp; sscanf(opt, "%u", &exp); gf_net_get_ntp(&sec, &frac); if (!exp || (exp>sec)) delete_file=0; } if (delete_file) { gf_delete_file((char*)file); gf_cfg_del_section(txh->compositor->user->config, section); } if (txh->data) gf_free(txh->data); txh->data = NULL; } gf_sc_texture_destroy(txh); gf_free(txh); } }
void gf_storage_save(M_Storage *storage) { char szID[20]; u32 i; GF_Config *cfg = storage_get_cfg(storage); char *section = storage_get_section(storage); if (!cfg || !section) return; gf_cfg_del_section(cfg, section); if (storage->expireAfter) { u32 sec, frac; char szNTP[100]; gf_net_get_ntp(&sec, &frac); sec += storage->expireAfter; sprintf(szNTP, "%u", sec); gf_cfg_set_key(cfg, section, "expireAfterNTP", szNTP); } else { gf_cfg_set_key(cfg, section, "expireAfterNTP", "0"); } for (i=0; i<storage->storageList.count; i++) { char *val; GF_FieldInfo info; sprintf(szID, "%d", i); if (!storage->storageList.vals[i].node) break; if (gf_node_get_field(storage->storageList.vals[i].node, storage->storageList.vals[i].fieldIndex, &info) != GF_OK) break; if (gf_sg_vrml_is_sf_field(info.fieldType)) { val = storage_serialize_sf(info.far_ptr, info.fieldType); } else { //u32 sftype = gf_sg_vrml_get_sf_type(info.fieldType); char *slotval; void *slot; val = NULL; for (i=0; i<((GenMFField *)info.far_ptr)->count; i++) { if (gf_sg_vrml_mf_get_item(info.far_ptr, info.fieldType, &slot, i) != GF_OK) break; slotval = storage_serialize_sf(info.far_ptr, info.fieldType); if (!slotval) break; if (val) { val = gf_realloc(val, strlen(val) + 3 + strlen(slot)); } else { val = gf_malloc(3 + strlen(slot)); val[0] = 0; } strcat(val, "'"); strcat(val, slotval); strcat(val, "'"); gf_free(slot); } } if (val) { gf_cfg_set_key(cfg, section, szID, val); gf_free(val); } } gf_free(section); }
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; }
static void gf_storage_load(M_Storage *storage) { const char *opt; char szID[20]; u32 i, count; u32 sec, exp, frac; GF_Config *cfg = storage_get_cfg(storage); char *section = storage_get_section(storage); if (!cfg || !section) return; if (!gf_cfg_get_key_count(cfg, section)) { gf_free(section); return; } opt = gf_cfg_get_key(cfg, section, "expireAfterNTP"); gf_net_get_ntp(&sec, &frac); sscanf(opt, "%u", &exp); if (exp && (exp<=sec)) { gf_cfg_del_section(cfg, section); gf_free(section); return; } count = gf_cfg_get_key_count(cfg, section)-1; if (!count || (count!=storage->storageList.count)) { gf_cfg_del_section(cfg, section); gf_free(section); return; } for (i=0; i<count; i++) { GF_FieldInfo info; sprintf(szID, "%d", i); opt = gf_cfg_get_key(cfg, section, szID); if (!opt) break; if (!storage->storageList.vals[i].node) break; if (gf_node_get_field(storage->storageList.vals[i].node, storage->storageList.vals[i].fieldIndex, &info) != GF_OK) break; if (gf_sg_vrml_is_sf_field(info.fieldType)) { storage_parse_sf(info.far_ptr, info.fieldType, (char *) opt); } else { u32 sftype = gf_sg_vrml_get_sf_type(info.fieldType); char *sep, *val; void *slot; gf_sg_vrml_mf_reset(info.far_ptr, info.fieldType); while (1) { val = strchr(opt, '\''); sep = val ? strchr(val+1, '\'') : NULL; if (!val || !sep) break; sep[0] = 0; gf_sg_vrml_mf_append(info.far_ptr, info.fieldType, &slot); storage_parse_sf(slot, sftype, val+1); sep[0] = '\''; opt = sep+1; } } gf_node_changed(storage->storageList.vals[i].node, &info); } gf_free(section); }
static void imagetexture_update(GF_TextureHandler *txh) { if (gf_node_get_tag(txh->owner)!=TAG_MPEG4_CacheTexture) { MFURL url = ((M_ImageTexture *) txh->owner)->url; /*setup texture if needed*/ if (!txh->is_open && url.count) { gf_sc_texture_play(txh, &url); } gf_sc_texture_update_frame(txh, 0); if ( /*URL is present but not opened - redraw till fetch*/ /* (txh->stream && !txh->tx_io) && */ /*image has been updated*/ txh->needs_refresh) { /*mark all subtrees using this image as dirty*/ gf_node_dirty_parents(txh->owner); gf_sc_invalidate(txh->compositor, NULL); } return; } /*cache texture case*/ else { M_CacheTexture *ct = (M_CacheTexture *) txh->owner; /*decode cacheTexture data */ if ((ct->data || ct->image.buffer) && !txh->data) { #ifndef GPAC_DISABLE_AV_PARSERS u32 out_size; GF_Err e; /*BT/XMT playback: load to memory*/ if (ct->image.buffer) { char *par = (char *) gf_scene_get_service_url( gf_node_get_graph(txh->owner ) ); char *src_url = gf_url_concatenate(par, ct->image.buffer); FILE *test = gf_fopen( src_url ? src_url : ct->image.buffer, "rb"); if (test) { fseek(test, 0, SEEK_END); ct->data_len = (u32) gf_ftell(test); ct->data = gf_malloc(sizeof(char)*ct->data_len); fseek(test, 0, SEEK_SET); if (ct->data_len != fread(ct->data, 1, ct->data_len, test)) { GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to load CacheTexture data from file %s: IO err\n", src_url ? src_url : ct->image.buffer ) ); gf_free(ct->data); ct->data = NULL; ct->data_len = 0; } gf_fclose(test); } else { GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to load CacheTexture data from file %s: not found\n", src_url ? src_url : ct->image.buffer ) ); } ct->image.buffer = NULL; if (src_url) gf_free(src_url); } /*BIFS decoded playback*/ switch (ct->objectTypeIndication) { case GPAC_OTI_IMAGE_JPEG: out_size = 0; e = gf_img_jpeg_dec((char *) ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size, 3); if (e==GF_BUFFER_TOO_SMALL) { u32 BPP; txh->data = gf_malloc(sizeof(char) * out_size); if (txh->pixelformat==GF_PIXEL_GREYSCALE) BPP = 1; else BPP = 3; e = gf_img_jpeg_dec((char *) ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size, BPP); if (e==GF_OK) { gf_sc_texture_allocate(txh); gf_sc_texture_set_data(txh); txh->needs_refresh = 1; txh->stride = out_size / txh->height; } } break; case GPAC_OTI_IMAGE_PNG: out_size = 0; e = gf_img_png_dec((char *) ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size); if (e==GF_BUFFER_TOO_SMALL) { txh->data = gf_malloc(sizeof(char) * out_size); e = gf_img_png_dec((char *) ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size); if (e==GF_OK) { gf_sc_texture_allocate(txh); gf_sc_texture_set_data(txh); txh->needs_refresh = 1; txh->stride = out_size / txh->height; } } break; } #endif // GPAC_DISABLE_AV_PARSERS /*cacheURL is specified, store the image*/ if (ct->cacheURL.buffer) { u32 i; u8 hash[20]; FILE *cached_texture; char szExtractName[GF_MAX_PATH], section[64], *opt, *src_url; opt = (char *) gf_cfg_get_key(txh->compositor->user->config, "General", "CacheDirectory"); if (opt) { strcpy(szExtractName, opt); } else { opt = gf_get_default_cache_directory(); strcpy(szExtractName, opt); gf_free(opt); } strcat(szExtractName, "/"); src_url = (char *) gf_scene_get_service_url( gf_node_get_graph(txh->owner ) ); gf_sha1_csum((u8 *)src_url, (u32) strlen(src_url), hash); for (i=0; i<20; i++) { char t[3]; t[2] = 0; sprintf(t, "%02X", hash[i]); strcat(szExtractName, t); } strcat(szExtractName, "_"); strcat(szExtractName, ct->cacheURL.buffer); cached_texture = gf_fopen(szExtractName, "wb"); if (cached_texture) { gf_fwrite(ct->data, 1, ct->data_len, cached_texture); gf_fclose(cached_texture); } /*and write cache info*/ if (ct->expirationDate!=0) { sprintf(section, "@cache=%p", ct); gf_cfg_set_key(txh->compositor->user->config, section, "serviceURL", src_url); gf_cfg_set_key(txh->compositor->user->config, section, "cacheFile", szExtractName); gf_cfg_set_key(txh->compositor->user->config, section, "cacheName", ct->cacheURL.buffer); if (ct->expirationDate>0) { char exp[50]; u32 sec, frac; gf_net_get_ntp(&sec, &frac); sec += ct->expirationDate; sprintf(exp, "%u", sec); gf_cfg_set_key(txh->compositor->user->config, section, "expireAfterNTP", exp); } else { gf_cfg_set_key(txh->compositor->user->config, section, "expireAfterNTP", "0"); } } } /*done with image, destroy buffer*/ if (ct->data) gf_free(ct->data); ct->data = NULL; ct->data_len = 0; } } }
int dc_video_muxer_write(VideoOutputFile *video_output_file, int frame_nb, Bool insert_utc) { u64 frame_dur; GF_Err ret; switch (video_output_file->muxer_type) { case FFMPEG_VIDEO_MUXER: return dc_ffmpeg_video_muxer_write(video_output_file); case RAW_VIDEO_H264: return dc_raw_h264_write(video_output_file); case GPAC_VIDEO_MUXER: case GPAC_INIT_VIDEO_MUXER_AVC1: case GPAC_INIT_VIDEO_MUXER_AVC3: if (video_output_file->use_source_timing) { if (!video_output_file->fragment_started) { video_output_file->fragment_started = 1; ret = gf_isom_start_fragment(video_output_file->isof, 1); if (ret < 0) return -1; video_output_file->first_dts = video_output_file->codec_ctx->coded_frame->pkt_dts; if (!video_output_file->segment_started) { u32 sec, frac; u64 ntpts; video_output_file->pts_at_segment_start = video_output_file->codec_ctx->coded_frame->pts; video_output_file->segment_started = 1; if (insert_utc) { gf_net_get_ntp(&sec, &frac); ntpts = sec; ntpts <<= 32; ntpts |= frac; gf_isom_set_fragment_reference_time(video_output_file->isof, video_output_file->trackID, ntpts, video_output_file->pts_at_segment_start); } } gf_isom_set_traf_base_media_decode_time(video_output_file->isof, video_output_file->trackID, video_output_file->first_dts); } //keep track of previous frame dur and use last dur as the default duration for next sample //this works fine because we perform frame rate regulation at the capture stage frame_dur = video_output_file->codec_ctx->coded_frame->pts - video_output_file->last_pts; if (frame_dur && (video_output_file->frame_dur>(u32) frame_dur)) { GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("New frame dur detected: %d vs %d old\n", (u32) frame_dur, (u32) video_output_file->frame_dur)); video_output_file->frame_dur = (u32)frame_dur; } if (dc_gpac_video_isom_write(video_output_file) < 0) { return -1; } video_output_file->last_pts = video_output_file->codec_ctx->coded_frame->pts; video_output_file->last_dts = video_output_file->codec_ctx->coded_frame->pkt_dts; if (( video_output_file->last_dts - video_output_file->first_dts + video_output_file->frame_dur) /video_output_file->timescale >= video_output_file->frag_dur / 1000) { gf_isom_flush_fragments(video_output_file->isof, 1); video_output_file->fragment_started = 0; GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DashCast] Flushed fragment to disk at UTC "LLU" ms - last coded frame PTS %d\n", gf_net_get_utc(), video_output_file->codec_ctx->coded_frame->pts)); } //we may have rounding errors on the input PTS :( add half frame dur safety if (1000 * ( video_output_file->last_pts - video_output_file->pts_at_segment_start + 3*video_output_file->frame_dur/2) /video_output_file->timescale >= video_output_file->seg_dur ) { return 1; } return 0; } if (frame_nb % video_output_file->frame_per_fragment == 0) { gf_isom_start_fragment(video_output_file->isof, 1); if (!video_output_file->segment_started) { u32 sec, frac; u64 ntpts; video_output_file->pts_at_segment_start = video_output_file->codec_ctx->coded_frame->pts; video_output_file->segment_started = 1; if (insert_utc) { time_t secs; struct tm t; gf_net_get_ntp(&sec, &frac); ntpts = sec; ntpts <<= 32; ntpts |= frac; gf_isom_set_fragment_reference_time(video_output_file->isof, video_output_file->trackID, ntpts, video_output_file->pts_at_segment_start); secs = sec - GF_NTP_SEC_1900_TO_1970; t = *gmtime(&secs); GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DashCast] Producer reference clock: Timestamp %d matches sender NTP time %d-%02d-%02dT%02d:%02d:%02dZ (NTP frac part %u) \n", (u32) video_output_file->pts_at_segment_start, 1900+t.tm_year, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, (u32) frac )); } } gf_isom_set_traf_base_media_decode_time(video_output_file->isof, video_output_file->trackID, video_output_file->first_dts); video_output_file->first_dts += video_output_file->frame_per_fragment; } dc_gpac_video_isom_write(video_output_file); if (frame_nb % video_output_file->frame_per_fragment == video_output_file->frame_per_fragment - 1) { gf_isom_flush_fragments(video_output_file->isof, 1); GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DashCast] Flushed fragment to disk at UTC "LLU" ms - last coded frame PTS %d\n", gf_net_get_utc(), video_output_file->codec_ctx->coded_frame->pts)); } if (frame_nb + 1 == video_output_file->frame_per_segment) return 1; return 0; default: return -2; } return -2; }