| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2005-2026 | |
| * | |
| * This file is part of GPAC / MPEG2-TS sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <gpac/mpegts.h> | |
| #ifndef GPAC_DISABLE_MPEG2TS | |
| #include <string.h> | |
| #include <gpac/constants.h> | |
| #include <gpac/internal/media_dev.h> | |
| #include <gpac/download.h> | |
| #ifndef GPAC_DISABLE_STREAMING | |
| #include <gpac/internal/ietf_dev.h> | |
| #endif | |
| #ifdef GPAC_CONFIG_LINUX | |
| #include <unistd.h> | |
| #endif | |
| #ifdef GPAC_ENABLE_MPE | |
| #include <gpac/dvb_mpe.h> | |
| #endif | |
| #ifdef GPAC_ENABLE_DSMCC | |
| #include <gpac/ait.h> | |
| #endif | |
| #define DEBUG_TS_PACKET 0 | |
| GF_EXPORT | |
| const char *gf_m2ts_get_stream_name(GF_M2TSStreamType streamType) | |
| { | |
| switch (streamType) { | |
| case GF_M2TS_VIDEO_MPEG1: | |
| return "MPEG-1 Video"; | |
| case GF_M2TS_VIDEO_MPEG2: | |
| return "MPEG-2 Video"; | |
| case GF_M2TS_AUDIO_MPEG1: | |
| return "MPEG-1 Audio"; | |
| case GF_M2TS_AUDIO_MPEG2: | |
| return "MPEG-2 Audio"; | |
| case GF_M2TS_PRIVATE_SECTION: | |
| return "Private Section"; | |
| case GF_M2TS_PRIVATE_DATA: | |
| return "Private Data"; | |
| case GF_M2TS_AUDIO_AAC: | |
| return "AAC Audio"; | |
| case GF_M2TS_VIDEO_MPEG4: | |
| return "MPEG-4 Video"; | |
| case GF_M2TS_VIDEO_H264: | |
| return "MPEG-4/H264 Video"; | |
| case GF_M2TS_VIDEO_SVC: | |
| return "H264-SVC Video"; | |
| case GF_M2TS_VIDEO_HEVC: | |
| return "HEVC Video"; | |
| case GF_M2TS_VIDEO_SHVC: | |
| return "SHVC Video"; | |
| case GF_M2TS_VIDEO_SHVC_TEMPORAL: | |
| return "SHVC Video Temporal Sublayer"; | |
| case GF_M2TS_VIDEO_MHVC: | |
| return "MHVC Video"; | |
| case GF_M2TS_VIDEO_MHVC_TEMPORAL: | |
| return "MHVC Video Temporal Sublayer"; | |
| case GF_M2TS_VIDEO_VVC: | |
| return "VVC Video"; | |
| case GF_M2TS_VIDEO_VVC_TEMPORAL: | |
| return "VVC Video Temporal Sublayer"; | |
| case GF_M2TS_VIDEO_VC1: | |
| return "SMPTE VC-1 Video"; | |
| case GF_M2TS_AUDIO_AC3: | |
| return "Dolby AC3 Audio"; | |
| case GF_M2TS_AUDIO_EC3: | |
| return "Dolby E-AC3 Audio"; | |
| case GF_M2TS_AUDIO_DTS: | |
| return "Dolby DTS Audio"; | |
| case GF_M2TS_SUBTITLE_DVB: | |
| return "DVB Subtitle"; | |
| case GF_M2TS_SYSTEMS_MPEG4_PES: | |
| return "MPEG-4 SL (PES)"; | |
| case GF_M2TS_SYSTEMS_MPEG4_SECTIONS: | |
| return "MPEG-4 SL (Section)"; | |
| case GF_M2TS_MPE_SECTIONS: | |
| return "MPE (Section)"; | |
| case GF_M2TS_METADATA_PES: | |
| return "Metadata (PES)"; | |
| case GF_M2TS_METADATA_ID3_HLS: | |
| return "ID3/HLS Metadata (PES)"; | |
| case GF_M2TS_METADATA_ID3_KLVA: | |
| return "ID3/KLV Metadata (PES)"; | |
| case GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS: | |
| return "SCTE35 splice_info_section (Section)"; | |
| default: | |
| return "Unknown"; | |
| } | |
| } | |
| static u32 gf_m2ts_reframe_default(GF_M2TS_Demuxer *ts, GF_M2TS_PES *pes, Bool same_pts, unsigned char *data, u32 data_len, GF_M2TS_PESHeader *pes_hdr) | |
| { | |
| GF_M2TS_PES_PCK pck; | |
| pck.flags = 0; | |
| if (pes->rap) pck.flags |= GF_M2TS_PES_PCK_RAP; | |
| if (!same_pts) pck.flags |= GF_M2TS_PES_PCK_AU_START; | |
| pck.DTS = pes->DTS; | |
| pck.PTS = pes->PTS; | |
| pck.data = (char *)data; | |
| pck.data_len = data_len; | |
| pck.stream = pes; | |
| ts->on_event(ts, GF_M2TS_EVT_PES_PCK, &pck); | |
| /*we consumed all data*/ | |
| return 0; | |
| } | |
| static u32 gf_m2ts_reframe_reset(GF_M2TS_Demuxer *ts, GF_M2TS_PES *pes, Bool same_pts, unsigned char *data, u32 data_len, GF_M2TS_PESHeader *pes_hdr) | |
| { | |
| if (pes->pck_data) { | |
| gf_free(pes->pck_data); | |
| pes->pck_data = NULL; | |
| } | |
| pes->pck_data_len = pes->pck_alloc_len = 0; | |
| if (pes->prev_data) { | |
| gf_free(pes->prev_data); | |
| pes->prev_data = NULL; | |
| } | |
| pes->prev_data_len = 0; | |
| pes->pes_len = 0; | |
| pes->reframe = NULL; | |
| pes->cc = -1; | |
| pes->temi_tc_desc_len = 0; | |
| return 0; | |
| } | |
| static u32 gf_m2ts_reframe_add_prop(GF_M2TS_Demuxer *ts, GF_M2TS_PES *pes, Bool same_pts, unsigned char *data, u32 data_len, GF_M2TS_PESHeader *pes_hdr) | |
| { | |
| GF_M2TS_PES_PCK pck; | |
| pck.flags = 0; | |
| pck.DTS = pes->DTS; | |
| pck.PTS = pes->PTS; | |
| pck.data = (char *)data; | |
| pck.data_len = data_len; | |
| pck.stream = pes; | |
| pck.stream->stream_type = pes->stream_type; | |
| ts->on_event(ts, GF_M2TS_EVT_ID3/*should depend on pes->streamtype*/, &pck); | |
| return 0; | |
| } | |
| static u32 gf_m2ts_sync(GF_M2TS_Demuxer *ts, char *data, u32 size, Bool simple_check) | |
| { | |
| u32 i=0; | |
| /*if first byte is sync assume we're sync*/ | |
| if (simple_check && (data[i]==0x47)) return 0; | |
| while (i < size) { | |
| if (i+192 >= size) return size; | |
| if ((data[i]==0x47) && (data[i+188]==0x47)) | |
| break; | |
| if ((data[i]==0x47) && (data[i+192]==0x47)) { | |
| ts->prefix_present = 1; | |
| break; | |
| } | |
| i++; | |
| } | |
| if (i) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] re-sync skipped %d bytes\n", i) ); | |
| } | |
| return i; | |
| } | |
| Bool gf_m2ts_crc32_check(u8 *data, u32 len) | |
| { | |
| u32 crc = gf_crc_32(data, len); | |
| u32 crc_val = GF_4CC((u32) data[len], (u8) data[len+1], (u8) data[len+2], (u8) data[len+3]); | |
| return (crc==crc_val) ? GF_TRUE : GF_FALSE; | |
| } | |
| static GF_M2TS_SectionFilter *gf_m2ts_section_filter_new(gf_m2ts_section_callback process_section_callback, Bool process_individual) | |
| { | |
| GF_M2TS_SectionFilter *sec; | |
| GF_SAFEALLOC(sec, GF_M2TS_SectionFilter); | |
| if (!sec) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] gf_m2ts_section_filter_new : OUT OF MEMORY\n")); | |
| return NULL; | |
| } | |
| sec->cc = -1; | |
| sec->process_section = process_section_callback; | |
| sec->process_individual = process_individual; | |
| return sec; | |
| } | |
| static void gf_m2ts_reset_sections(GF_List *sections) | |
| { | |
| u32 count; | |
| //GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] Deleting sections\n")); | |
| count = gf_list_count(sections); | |
| while (count) { | |
| GF_M2TS_Section *section = gf_list_get(sections, 0); | |
| gf_list_rem(sections, 0); | |
| if (section->data) gf_free(section->data); | |
| gf_free(section); | |
| count--; | |
| } | |
| } | |
| static void gf_m2ts_section_filter_reset(GF_M2TS_SectionFilter *sf) | |
| { | |
| if (sf->section) { | |
| gf_free(sf->section); | |
| sf->section = NULL; | |
| } | |
| while (sf->table) { | |
| GF_M2TS_Table *t = sf->table; | |
| sf->table = t->next; | |
| gf_m2ts_reset_sections(t->sections); | |
| gf_list_del(t->sections); | |
| gf_free(t); | |
| } | |
| sf->cc = -1; | |
| sf->length = sf->received = 0; | |
| sf->demux_restarted = 1; | |
| } | |
| static void gf_m2ts_section_filter_del(GF_M2TS_SectionFilter *sf) | |
| { | |
| gf_m2ts_section_filter_reset(sf); | |
| gf_free(sf); | |
| } | |
| static void gf_m2ts_metadata_descriptor_del(GF_M2TS_MetadataDescriptor *metad) | |
| { | |
| if (metad) { | |
| if (metad->service_id_record) gf_free(metad->service_id_record); | |
| if (metad->decoder_config) gf_free(metad->decoder_config); | |
| if (metad->decoder_config_id) gf_free(metad->decoder_config_id); | |
| gf_free(metad); | |
| } | |
| } | |
| static void gf_m2ts_es_del(GF_M2TS_ES *es, GF_M2TS_Demuxer *ts) | |
| { | |
| //es is reused (PCR streams only, reuse at most one), remove reuse flag | |
| if (es->flags & GF_M2TS_ES_IS_PCR_REUSE) { | |
| es->flags &= ~GF_M2TS_ES_IS_PCR_REUSE; | |
| return; | |
| } | |
| gf_list_del_item(es->program->streams, es); | |
| if (ts->on_event) | |
| ts->on_event(ts, GF_M2TS_EVT_STREAM_REMOVED, es); | |
| if (es->flags & GF_M2TS_ES_IS_SECTION) { | |
| GF_M2TS_SECTION_ES *ses = (GF_M2TS_SECTION_ES *)es; | |
| if (ses->sec) gf_m2ts_section_filter_del(ses->sec); | |
| #ifdef GPAC_ENABLE_MPE | |
| if (es->flags & GF_M2TS_ES_IS_MPE) | |
| gf_dvb_mpe_section_del(es); | |
| #endif | |
| } else if (es->pid!=es->program->pmt_pid) { | |
| GF_M2TS_PES *pes = (GF_M2TS_PES *)es; | |
| if ((pes->flags & GF_M2TS_INHERIT_PCR) && ts->ess[es->program->pcr_pid]==es) | |
| ts->ess[es->program->pcr_pid] = NULL; | |
| if (pes->pck_data) gf_free(pes->pck_data); | |
| if (pes->prev_data) gf_free(pes->prev_data); | |
| if (pes->temi_tc_desc) gf_free(pes->temi_tc_desc); | |
| if (pes->metadata_descriptor) gf_m2ts_metadata_descriptor_del(pes->metadata_descriptor); | |
| if (pes->gpac_meta_dsi) gf_free(pes->gpac_meta_dsi); | |
| } | |
| if (es->slcfg) gf_free(es->slcfg); | |
| for (u32 i=0; i<GF_M2TS_MAX_STREAMS; i++) { | |
| if (ts->ess[i]==es) { | |
| ts->ess[i] = NULL; | |
| } | |
| } | |
| gf_free(es); | |
| } | |
| static void gf_m2ts_reset_sdt(GF_M2TS_Demuxer *ts) | |
| { | |
| while (gf_list_count(ts->SDTs)) { | |
| GF_M2TS_SDT *sdt = (GF_M2TS_SDT *)gf_list_last(ts->SDTs); | |
| gf_list_rem_last(ts->SDTs); | |
| if (sdt->provider) gf_free(sdt->provider); | |
| if (sdt->service) gf_free(sdt->service); | |
| gf_free(sdt); | |
| } | |
| } | |
| GF_EXPORT | |
| GF_M2TS_SDT *gf_m2ts_get_sdt_info(GF_M2TS_Demuxer *ts, u32 program_id) | |
| { | |
| u32 i; | |
| for (i=0; i<gf_list_count(ts->SDTs); i++) { | |
| GF_M2TS_SDT *sdt = (GF_M2TS_SDT *)gf_list_get(ts->SDTs, i); | |
| if (sdt->service_id==program_id) return sdt; | |
| } | |
| return NULL; | |
| } | |
| static void gf_m2ts_section_complete(GF_M2TS_Demuxer *ts, GF_M2TS_SectionFilter *sec, GF_M2TS_SECTION_ES *ses) | |
| { | |
| //seek mode, only process PAT and PMT | |
| if (ts->seek_mode && (sec->section[0] != GF_M2TS_TABLE_ID_PAT) && (sec->section[0] != GF_M2TS_TABLE_ID_PMT)) { | |
| /*clean-up (including broken sections)*/ | |
| if (sec->section) gf_free(sec->section); | |
| sec->section = NULL; | |
| sec->length = sec->received = 0; | |
| return; | |
| } | |
| if (!sec->process_section) { | |
| if ((ts->on_event && (sec->section[0]==GF_M2TS_TABLE_ID_AIT)) ) { | |
| #ifdef GPAC_ENABLE_DSMCC | |
| GF_M2TS_SL_PCK pck; | |
| pck.data_len = sec->length; | |
| pck.data = sec->section; | |
| pck.stream = (GF_M2TS_ES *)ses; | |
| //ts->on_event(ts, GF_M2TS_EVT_AIT_FOUND, &pck); | |
| on_ait_section(ts, GF_M2TS_EVT_AIT_FOUND, &pck); | |
| #endif | |
| } else if ((ts->on_event && (sec->section[0]==GF_M2TS_TABLE_ID_DSM_CC_ENCAPSULATED_DATA || sec->section[0]==GF_M2TS_TABLE_ID_DSM_CC_UN_MESSAGE || | |
| sec->section[0]==GF_M2TS_TABLE_ID_DSM_CC_DOWNLOAD_DATA_MESSAGE || sec->section[0]==GF_M2TS_TABLE_ID_DSM_CC_STREAM_DESCRIPTION || sec->section[0]==GF_M2TS_TABLE_ID_DSM_CC_PRIVATE)) ) { | |
| #ifdef GPAC_ENABLE_DSMCC | |
| GF_M2TS_SL_PCK pck; | |
| pck.data_len = sec->length; | |
| pck.data = sec->section; | |
| pck.stream = (GF_M2TS_ES *)ses; | |
| on_dsmcc_section(ts,GF_M2TS_EVT_DSMCC_FOUND,&pck); | |
| //ts->on_event(ts, GF_M2TS_EVT_DSMCC_FOUND, &pck); | |
| #endif | |
| } | |
| #ifdef GPAC_ENABLE_MPE | |
| else if (ts->on_mpe_event && ((ses && (ses->flags & GF_M2TS_EVT_DVB_MPE)) || (sec->section[0]==GF_M2TS_TABLE_ID_INT)) ) { | |
| GF_M2TS_SL_PCK pck; | |
| pck.data_len = sec->length; | |
| pck.data = sec->section; | |
| pck.stream = (GF_M2TS_ES *)ses; | |
| ts->on_mpe_event(ts, GF_M2TS_EVT_DVB_MPE, &pck); | |
| } | |
| #endif | |
| else if ((ts->on_event && (sec->section[0]==GF_M2TS_TABLE_ID_SCTE35_SPLICE_INFO)) ) { | |
| GF_M2TS_SL_PCK pck; | |
| pck.data_len = sec->length; | |
| pck.data = sec->section; | |
| pck.stream = (GF_M2TS_ES *)ses; | |
| ts->on_event(ts, GF_M2TS_EVT_SCTE35_SPLICE_INFO, &pck); | |
| } | |
| else if (ts->on_event) { | |
| GF_M2TS_SL_PCK pck; | |
| pck.data_len = sec->length; | |
| pck.data = sec->section; | |
| pck.stream = (GF_M2TS_ES *)ses; | |
| ts->on_event(ts, GF_M2TS_EVT_DVB_GENERAL, &pck); | |
| } | |
| } else { | |
| Bool has_syntax_indicator; | |
| u8 table_id; | |
| u16 extended_table_id; | |
| u32 status, section_start, i; | |
| GF_M2TS_Table *t, *prev_t; | |
| unsigned char *data; | |
| Bool section_valid = 0; | |
| status = 0; | |
| /*parse header*/ | |
| data = (u8 *)sec->section; | |
| if (sec->length < 2) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] M2TS Table parsing error, length %d is too small\n", sec->length) ); | |
| return; | |
| } | |
| /*look for proper table*/ | |
| table_id = data[0]; | |
| if (ts->on_event) { | |
| switch (table_id) { | |
| case GF_M2TS_TABLE_ID_PAT: | |
| case GF_M2TS_TABLE_ID_SDT_ACTUAL: | |
| case GF_M2TS_TABLE_ID_PMT: | |
| case GF_M2TS_TABLE_ID_NIT_ACTUAL: | |
| case GF_M2TS_TABLE_ID_TDT: | |
| case GF_M2TS_TABLE_ID_TOT: | |
| { | |
| GF_M2TS_SL_PCK pck; | |
| pck.data_len = sec->length; | |
| pck.data = sec->section; | |
| pck.stream = (GF_M2TS_ES *)ses; | |
| ts->on_event(ts, GF_M2TS_EVT_DVB_GENERAL, &pck); | |
| } | |
| } | |
| } | |
| has_syntax_indicator = (data[1] & 0x80) ? 1 : 0; | |
| if (has_syntax_indicator) { | |
| if (sec->length < 5) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] M2TS Table parsing error, length %d is too small\n", sec->length) ); | |
| return; | |
| } | |
| extended_table_id = (data[3]<<8) | data[4]; | |
| } else { | |
| extended_table_id = 0; | |
| } | |
| prev_t = NULL; | |
| t = sec->table; | |
| while (t) { | |
| if ((t->table_id==table_id) && (t->ex_table_id == extended_table_id)) break; | |
| prev_t = t; | |
| t = t->next; | |
| } | |
| /*create table*/ | |
| if (!t) { | |
| GF_SAFEALLOC(t, GF_M2TS_Table); | |
| if (!t) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Fail to alloc table %d %d\n", table_id, extended_table_id)); | |
| return; | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] Creating table %d %d\n", table_id, extended_table_id)); | |
| t->table_id = table_id; | |
| t->ex_table_id = extended_table_id; | |
| t->last_version_number = 0xFF; | |
| t->sections = gf_list_new(); | |
| if (prev_t) prev_t->next = t; | |
| else sec->table = t; | |
| } | |
| if (has_syntax_indicator) { | |
| if (sec->length < 4) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] corrupted section length %d less than CRC \n", sec->length)); | |
| } else { | |
| /*remove crc32*/ | |
| sec->length -= 4; | |
| if (gf_m2ts_crc32_check((char *)data, sec->length)) { | |
| s32 cur_sec_num; | |
| t->version_number = (data[5] >> 1) & 0x1f; | |
| if (t->last_section_number && t->section_number && (t->version_number != t->last_version_number)) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] table transmission interrupted: previous table (v=%d) %d/%d sections - new table (v=%d) %d/%d sections\n", t->last_version_number, t->section_number, t->last_section_number, t->version_number, data[6] + 1, data[7] + 1) ); | |
| gf_m2ts_reset_sections(t->sections); | |
| t->section_number = 0; | |
| } | |
| t->current_next_indicator = (data[5] & 0x1) ? 1 : 0; | |
| /*add one to section numbers to detect if we missed or not the first section in the table*/ | |
| cur_sec_num = data[6] + 1; | |
| t->last_section_number = data[7] + 1; | |
| section_start = 8; | |
| /*we missed something*/ | |
| if (!sec->process_individual && t->section_number + 1 != cur_sec_num) { | |
| /* TODO - Check how to handle sections when the first complete section does | |
| not have its sec num 0 */ | |
| section_valid = 0; | |
| if (t->is_init) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] corrupted table (lost section %d)\n", cur_sec_num ? cur_sec_num-1 : 31) ); | |
| } | |
| } else { | |
| section_valid = 1; | |
| t->section_number = cur_sec_num; | |
| } | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] corrupted section (CRC32 failed)\n")); | |
| } | |
| } | |
| } else { | |
| section_valid = 1; | |
| section_start = 3; | |
| } | |
| /*process section*/ | |
| if (section_valid && sec->length > section_start) { | |
| GF_M2TS_Section *section; | |
| GF_SAFEALLOC(section, GF_M2TS_Section); | |
| if (!section) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Fail to create section\n")); | |
| return; | |
| } | |
| section->data_size = sec->length - section_start; | |
| section->data = (unsigned char*)gf_malloc(sizeof(unsigned char)*section->data_size); | |
| memcpy(section->data, sec->section + section_start, sizeof(unsigned char)*section->data_size); | |
| gf_list_add(t->sections, section); | |
| if (t->section_number == 1) { | |
| status |= GF_M2TS_TABLE_START; | |
| if (t->last_version_number == t->version_number) { | |
| t->is_repeat = 1; | |
| } else { | |
| t->is_repeat = 0; | |
| } | |
| /*only update version number in the first section of the table*/ | |
| t->last_version_number = t->version_number; | |
| } | |
| if (t->is_init) { | |
| if (t->is_repeat) { | |
| status |= GF_M2TS_TABLE_REPEAT; | |
| } else { | |
| status |= GF_M2TS_TABLE_UPDATE; | |
| } | |
| } else { | |
| status |= GF_M2TS_TABLE_FOUND; | |
| } | |
| if (t->last_section_number == t->section_number) { | |
| u32 table_size; | |
| status |= GF_M2TS_TABLE_END; | |
| table_size = 0; | |
| for (i=0; i<gf_list_count(t->sections); i++) { | |
| GF_M2TS_Section *a_sec = gf_list_get(t->sections, i); | |
| table_size += a_sec->data_size; | |
| } | |
| if (t->is_repeat) { | |
| if (t->table_size != table_size) { | |
| status |= GF_M2TS_TABLE_UPDATE; | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] Repeated section found with different sizes (old table %d bytes, new table %d bytes)\n", t->table_size, table_size) ); | |
| t->table_size = table_size; | |
| } | |
| } else { | |
| t->table_size = table_size; | |
| } | |
| t->is_init = 1; | |
| /*reset section number*/ | |
| t->section_number = 0; | |
| t->is_repeat = 0; | |
| } | |
| if (sec->process_individual) { | |
| /*send each section of the table and not the aggregated table*/ | |
| if (sec->process_section) | |
| sec->process_section(ts, ses, t->sections, t->table_id, t->ex_table_id, t->version_number, (u8) (t->last_section_number - 1), status); | |
| gf_m2ts_reset_sections(t->sections); | |
| } else { | |
| if (status&GF_M2TS_TABLE_END) { | |
| if (sec->process_section) | |
| sec->process_section(ts, ses, t->sections, t->table_id, t->ex_table_id, t->version_number, (u8) (t->last_section_number - 1), status); | |
| gf_m2ts_reset_sections(t->sections); | |
| } | |
| } | |
| } else { | |
| sec->cc = -1; | |
| t->section_number = 0; | |
| } | |
| } | |
| /*clean-up (including broken sections)*/ | |
| if (sec->section) gf_free(sec->section); | |
| sec->section = NULL; | |
| sec->length = sec->received = 0; | |
| } | |
| static Bool gf_m2ts_is_long_section(u8 table_id) | |
| { | |
| switch (table_id) { | |
| case GF_M2TS_TABLE_ID_MPEG4_BIFS: | |
| case GF_M2TS_TABLE_ID_MPEG4_OD: | |
| case GF_M2TS_TABLE_ID_INT: | |
| case GF_M2TS_TABLE_ID_EIT_ACTUAL_PF: | |
| case GF_M2TS_TABLE_ID_EIT_OTHER_PF: | |
| case GF_M2TS_TABLE_ID_ST: | |
| case GF_M2TS_TABLE_ID_SIT: | |
| case GF_M2TS_TABLE_ID_DSM_CC_PRIVATE: | |
| case GF_M2TS_TABLE_ID_MPE_FEC: | |
| case GF_M2TS_TABLE_ID_DSM_CC_DOWNLOAD_DATA_MESSAGE: | |
| case GF_M2TS_TABLE_ID_DSM_CC_UN_MESSAGE: | |
| return 1; | |
| default: | |
| if (table_id >= GF_M2TS_TABLE_ID_EIT_SCHEDULE_MIN && table_id <= GF_M2TS_TABLE_ID_EIT_SCHEDULE_MAX) | |
| return 1; | |
| else | |
| return 0; | |
| } | |
| } | |
| static u32 gf_m2ts_get_section_length(char byte0, char byte1, char byte2) | |
| { | |
| u32 length; | |
| if (gf_m2ts_is_long_section(byte0)) { | |
| length = 3 + ( (((u8)byte1<<8) | (byte2&0xff)) & 0xfff ); | |
| } else { | |
| length = 3 + ( (((u8)byte1<<8) | (byte2&0xff)) & 0x3ff ); | |
| } | |
| return length; | |
| } | |
| static void gf_m2ts_gather_section(GF_M2TS_Demuxer *ts, GF_M2TS_SectionFilter *sec, GF_M2TS_SECTION_ES *ses, GF_M2TS_Header *hdr, unsigned char *data, u32 data_size) | |
| { | |
| u32 payload_size = data_size; | |
| u8 expect_cc = (sec->cc<0) ? hdr->continuity_counter : (sec->cc + 1) & 0xf; | |
| Bool disc = (expect_cc == hdr->continuity_counter) ? 0 : 1; | |
| sec->cc = expect_cc; | |
| /*may happen if hdr->adaptation_field=2 no payload in TS packet*/ | |
| if (!data_size) return; | |
| if (hdr->payload_start) { | |
| u32 ptr_field; | |
| ptr_field = data[0]; | |
| if (ptr_field+1>data_size) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid section start (@ptr_field=%d, @data_size=%d)\n", ptr_field, data_size) ); | |
| return; | |
| } | |
| if (!hdr->pid) | |
| ts->last_pat_start_num = ts->pck_number-1; | |
| /*end of previous section*/ | |
| if (!sec->length && sec->received) { | |
| /* the length of the section could not be determined from the previous TS packet because we had only 1 or 2 bytes */ | |
| if (sec->received == 1) | |
| sec->length = gf_m2ts_get_section_length(sec->section[0], data[1], data[2]); | |
| else /* (sec->received == 2) */ | |
| sec->length = gf_m2ts_get_section_length(sec->section[0], sec->section[1], data[1]); | |
| sec->section = (char*)gf_realloc(sec->section, sizeof(char)*sec->length); | |
| } | |
| if (sec->length && (sec->received < sec->length) && (data_size >= (u32) (1 + sec->length - sec->received))) { | |
| u32 len = sec->length - sec->received; | |
| memcpy(sec->section + sec->received, data+1, sizeof(char)*len); | |
| sec->received += len; | |
| if (ptr_field > len) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid pointer field (@ptr_field=%d, @remaining=%d)\n", ptr_field, len) ); | |
| gf_m2ts_section_complete(ts, sec, ses); | |
| } | |
| data += ptr_field+1; | |
| data_size -= ptr_field+1; | |
| payload_size -= ptr_field+1; | |
| aggregated_section: | |
| if (sec->section) gf_free(sec->section); | |
| sec->length = sec->received = 0; | |
| sec->section = (char*)gf_malloc(sizeof(char)*data_size); | |
| memcpy(sec->section, data, sizeof(char)*data_size); | |
| sec->received = data_size; | |
| } else if (disc) { | |
| if (sec->section) gf_free(sec->section); | |
| sec->section = NULL; | |
| sec->received = sec->length = 0; | |
| return; | |
| } else if (!sec->section) { | |
| return; | |
| } else { | |
| if (sec->length && sec->received+data_size > sec->length) | |
| data_size = sec->length - sec->received; | |
| if (sec->length) { | |
| memcpy(sec->section + sec->received, data, sizeof(char)*data_size); | |
| } else { | |
| sec->section = (char*)gf_realloc(sec->section, sizeof(char)*(sec->received+data_size)); | |
| memcpy(sec->section + sec->received, data, sizeof(char)*data_size); | |
| } | |
| sec->received += data_size; | |
| } | |
| /*alloc final buffer*/ | |
| if (!sec->length && (sec->received >= 3)) { | |
| sec->length = gf_m2ts_get_section_length(sec->section[0], sec->section[1], sec->section[2]); | |
| sec->section = (char*)gf_realloc(sec->section, sizeof(char)*sec->length); | |
| if (sec->received > sec->length) { | |
| data_size -= sec->received - sec->length; | |
| sec->received = sec->length; | |
| } | |
| } | |
| if (!sec->length || sec->received < sec->length) return; | |
| /*OK done*/ | |
| gf_m2ts_section_complete(ts, sec, ses); | |
| if (payload_size > data_size) { | |
| data += data_size; | |
| /* detect padding after previous section */ | |
| if (data[0] != 0xFF) { | |
| data_size = payload_size - data_size; | |
| payload_size = data_size; | |
| goto aggregated_section; | |
| } | |
| } | |
| } | |
| static void gf_m2ts_process_sdt(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *ses, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) | |
| { | |
| u32 pos, evt_type; | |
| u32 nb_sections; | |
| u32 data_size; | |
| unsigned char *data; | |
| GF_M2TS_Section *section; | |
| /*wait for the last section */ | |
| if (!(status&GF_M2TS_TABLE_END)) return; | |
| /*skip if already received*/ | |
| if (status&GF_M2TS_TABLE_REPEAT) { | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_SDT_REPEAT, NULL); | |
| return; | |
| } | |
| if (table_id != GF_M2TS_TABLE_ID_SDT_ACTUAL) { | |
| return; | |
| } | |
| gf_m2ts_reset_sdt(ts); | |
| nb_sections = gf_list_count(sections); | |
| if (nb_sections > 1) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] SDT on multiple sections not supported\n")); | |
| } | |
| section = (GF_M2TS_Section *)gf_list_get(sections, 0); | |
| data = section->data; | |
| data_size = section->data_size; | |
| //orig_net_id = (data[0] << 8) | data[1]; | |
| pos = 3; | |
| while (pos+4 < data_size) { | |
| GF_M2TS_SDT *sdt; | |
| u32 descs_size, d_pos, ulen; | |
| GF_SAFEALLOC(sdt, GF_M2TS_SDT); | |
| if (!sdt) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] Fail to create SDT\n")); | |
| return; | |
| } | |
| gf_list_add(ts->SDTs, sdt); | |
| sdt->service_id = (data[pos]<<8) + data[pos+1]; | |
| sdt->EIT_schedule = (data[pos+2] & 0x2) ? 1 : 0; | |
| sdt->EIT_present_following = (data[pos+2] & 0x1); | |
| sdt->running_status = (data[pos+3]>>5) & 0x7; | |
| sdt->free_CA_mode = (data[pos+3]>>4) & 0x1; | |
| descs_size = ((data[pos+3]&0xf)<<8) | data[pos+4]; | |
| pos += 5; | |
| if (pos+descs_size > data_size) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid descriptors size read from data (%u)\n", descs_size)); | |
| return; | |
| } | |
| d_pos = 0; | |
| while (d_pos+1 < descs_size) { | |
| u8 d_tag = data[pos+d_pos]; | |
| u8 d_len = data[pos+d_pos+1]; | |
| switch (d_tag) { | |
| case GF_M2TS_DVB_SERVICE_DESCRIPTOR: | |
| if (sdt->provider) gf_free(sdt->provider); | |
| sdt->provider = NULL; | |
| if (sdt->service) gf_free(sdt->service); | |
| sdt->service = NULL; | |
| d_pos+=2; | |
| if (pos+d_pos+1 >= data_size) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid descriptors size read from data (%u)\n", descs_size)); | |
| return; | |
| } | |
| sdt->service_type = data[pos+d_pos]; | |
| ulen = data[pos+d_pos+1]; | |
| d_pos += 2; | |
| if (pos+d_pos+ulen > data_size) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid descriptors size read from data (%u)\n", descs_size)); | |
| return; | |
| } | |
| sdt->provider = (char*)gf_malloc(sizeof(char)*(ulen+1)); | |
| memcpy(sdt->provider, data+pos+d_pos, sizeof(char)*ulen); | |
| sdt->provider[ulen] = 0; | |
| d_pos += ulen; | |
| if (pos+d_pos >= data_size) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid descriptors size read from data (%u)\n", descs_size)); | |
| gf_free(sdt->provider); | |
| sdt->provider = NULL; | |
| return; | |
| } | |
| ulen = data[pos+d_pos]; | |
| d_pos += 1; | |
| if (pos+d_pos+ulen > data_size) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid descriptors size read from data (%u)\n", descs_size)); | |
| gf_free(sdt->provider); | |
| sdt->provider = NULL; | |
| return; | |
| } | |
| sdt->service = (char*)gf_malloc(sizeof(char)*(ulen+1)); | |
| memcpy(sdt->service, data+pos+d_pos, sizeof(char)*ulen); | |
| sdt->service[ulen] = 0; | |
| d_pos += ulen; | |
| break; | |
| default: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] Skipping descriptor (0x%x) not supported\n", d_tag)); | |
| d_pos += d_len; | |
| if (d_len == 0) d_pos = descs_size; | |
| break; | |
| } | |
| } | |
| pos += descs_size; | |
| } | |
| evt_type = GF_M2TS_EVT_SDT_FOUND; | |
| if (ts->on_event) ts->on_event(ts, evt_type, NULL); | |
| } | |
| static void gf_m2ts_process_mpeg4section(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *es, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) | |
| { | |
| GF_M2TS_SL_PCK sl_pck; | |
| u32 nb_sections, i; | |
| GF_M2TS_Section *section; | |
| //only process MPEG4 tables | |
| if ((table_id != GF_M2TS_TABLE_ID_MPEG4_BIFS) && (table_id != GF_M2TS_TABLE_ID_MPEG4_OD) ) | |
| return; | |
| /*skip if already received*/ | |
| if (status & GF_M2TS_TABLE_REPEAT) | |
| if (!(es->flags & GF_M2TS_ES_SEND_REPEATED_SECTIONS)) | |
| return; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] Sections for PID %d\n", es->pid) ); | |
| /*send all sections (eg SL-packets)*/ | |
| nb_sections = gf_list_count(sections); | |
| for (i=0; i<nb_sections; i++) { | |
| section = (GF_M2TS_Section *)gf_list_get(sections, i); | |
| sl_pck.data = (char *)section->data; | |
| sl_pck.data_len = section->data_size; | |
| sl_pck.stream = (GF_M2TS_ES *)es; | |
| sl_pck.version_number = version_number; | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_SL_PCK, &sl_pck); | |
| } | |
| } | |
| static void gf_m2ts_process_generic_section(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *es, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) | |
| { | |
| GF_M2TS_GenericSectionInfo sinfo; | |
| u32 i, nb_sec, evt_type; | |
| if (!ts->on_event) return; | |
| /*skip if already received*/ | |
| if (status & GF_M2TS_TABLE_REPEAT) | |
| if (!(es->flags & GF_M2TS_ES_SEND_REPEATED_SECTIONS)) | |
| return; | |
| evt_type = (status & GF_M2TS_TABLE_UPDATE) ? GF_M2TS_EVT_SECTION_UPDATE : GF_M2TS_EVT_SECTION; | |
| memset(&sinfo, 0, sizeof(GF_M2TS_GenericSectionInfo)); | |
| sinfo.stream = (GF_M2TS_ES *) es; | |
| sinfo.table_id = table_id; | |
| sinfo.version_number = version_number; | |
| sinfo.ex_table_id = ex_table_id; | |
| sinfo.pts = es->program->last_pcr_value/300; | |
| sinfo.num_sections = nb_sec = gf_list_count(sections); | |
| for (i=0; i<nb_sec; i++) { | |
| GF_M2TS_Section *section = (GF_M2TS_Section *)gf_list_get(sections, i); | |
| sinfo.section_idx = i; | |
| sinfo.section_data = section->data; | |
| sinfo.section_data_len = section->data_size; | |
| ts->on_event(ts, evt_type, &sinfo); | |
| } | |
| } | |
| static void gf_m2ts_process_nit(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *nit_es, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) | |
| { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] NIT table processing (not yet implemented)")); | |
| } | |
| static void gf_m2ts_process_tdt_tot(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *tdt_tot_es, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) | |
| { | |
| unsigned char *data; | |
| u32 data_size, nb_sections; | |
| u32 date, yp, mp, k; | |
| GF_M2TS_Section *section; | |
| GF_M2TS_TDT_TOT *time_table; | |
| const char *table_name; | |
| /*wait for the last section */ | |
| if ( !(status & GF_M2TS_TABLE_END) ) | |
| return; | |
| switch (table_id) { | |
| case GF_M2TS_TABLE_ID_TDT: | |
| table_name = "TDT"; | |
| break; | |
| case GF_M2TS_TABLE_ID_TOT: | |
| table_name = "TOT"; | |
| break; | |
| default: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] Unimplemented table_id %u for PID %u\n", table_id, GF_M2TS_PID_TDT_TOT_ST)); | |
| return; | |
| } | |
| nb_sections = gf_list_count(sections); | |
| if (nb_sections > 1) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] %s on multiple sections not supported\n", table_name)); | |
| } | |
| section = (GF_M2TS_Section *)gf_list_get(sections, 0); | |
| data = section->data; | |
| data_size = section->data_size; | |
| /*TOT only contains 40 bits of UTC_time; TDT add descriptors and a CRC*/ | |
| if ((table_id==GF_M2TS_TABLE_ID_TDT) && (data_size != 5)) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] Corrupted TDT size\n", table_name)); | |
| } | |
| GF_SAFEALLOC(time_table, GF_M2TS_TDT_TOT); | |
| if (!time_table) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Fail to alloc DVB time table\n")); | |
| return; | |
| } | |
| if (data_size < 5) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Section data size too small to read date (len: %u)\n", data_size)); | |
| goto error_exit; | |
| } | |
| /*UTC_time - see annex C of DVB-SI ETSI EN 300468*/ | |
| /* decodes an Modified Julian Date (MJD) into a Co-ordinated Universal Time (UTC) | |
| See annex C of DVB-SI ETSI EN 300468 */ | |
| date = data[0]*256 + data[1]; | |
| yp = GF_FLOAT_TO_U32((date - 15078.2)/365.25); | |
| mp = GF_FLOAT_TO_U32((date - 14956.1 - (u32)(yp * 365.25))/30.6001); | |
| time_table->day = GF_FLOAT_TO_U32(date - 14956 - (u32)(yp * 365.25) - (u32)(mp * 30.6001)); | |
| if (mp == 14 || mp == 15) k = 1; | |
| else k = 0; | |
| time_table->year = yp + k + 1900; | |
| time_table->month = mp - 1 - k*12; | |
| time_table->hour = 10*((data[2]&0xf0)>>4) + (data[2]&0x0f); | |
| time_table->minute = 10*((data[3]&0xf0)>>4) + (data[3]&0x0f); | |
| time_table->second = 10*((data[4]&0xf0)>>4) + (data[4]&0x0f); | |
| if (!(time_table->hour<24 && time_table->minute<60 && time_table->second<60)) | |
| goto error_exit; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] Stream UTC time is %u/%02u/%02u %02u:%02u:%02u\n", time_table->year, time_table->month, time_table->day, time_table->hour, time_table->minute, time_table->second)); | |
| switch (table_id) { | |
| case GF_M2TS_TABLE_ID_TDT: | |
| if (ts->TDT_time) gf_free(ts->TDT_time); | |
| ts->TDT_time = time_table; | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_TDT, time_table); | |
| break; | |
| case GF_M2TS_TABLE_ID_TOT: | |
| #if 0 | |
| { | |
| u32 pos, loop_len; | |
| loop_len = ((data[5]&0x0f) << 8) | (data[6] & 0xff); | |
| data += 7; | |
| pos = 0; | |
| while (pos < loop_len) { | |
| u8 tag = data[pos]; | |
| pos += 2; | |
| if (tag == GF_M2TS_DVB_LOCAL_TIME_OFFSET_DESCRIPTOR) { | |
| char tmp_time[10]; | |
| u16 offset_hours, offset_minutes; | |
| now->country_code[0] = data[pos]; | |
| now->country_code[1] = data[pos+1]; | |
| now->country_code[2] = data[pos+2]; | |
| now->country_region_id = data[pos+3]>>2; | |
| sprintf(tmp_time, "%02x", data[pos+4]); | |
| offset_hours = atoi(tmp_time); | |
| sprintf(tmp_time, "%02x", data[pos+5]); | |
| offset_minutes = atoi(tmp_time); | |
| now->local_time_offset_seconds = (offset_hours * 60 + offset_minutes) * 60; | |
| if (data[pos+3] & 1) now->local_time_offset_seconds *= -1; | |
| dvb_decode_mjd_to_unix_time(data+pos+6, &now->unix_next_toc); | |
| sprintf(tmp_time, "%02x", data[pos+11]); | |
| offset_hours = atoi(tmp_time); | |
| sprintf(tmp_time, "%02x", data[pos+12]); | |
| offset_minutes = atoi(tmp_time); | |
| now->next_time_offset_seconds = (offset_hours * 60 + offset_minutes) * 60; | |
| if (data[pos+3] & 1) now->next_time_offset_seconds *= -1; | |
| pos+= 13; | |
| } | |
| } | |
| /*TODO: check lengths are ok*/ | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_TOT, time_table); | |
| } | |
| #endif | |
| /*check CRC32*/ | |
| if (ts->tdt_tot->length<4) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] corrupted %s table (less than 4 bytes but CRC32 should be present\n", table_name)); | |
| goto error_exit; | |
| } | |
| if (!gf_m2ts_crc32_check(ts->tdt_tot->section, ts->tdt_tot->length-4)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] corrupted %s table (CRC32 failed)\n", table_name)); | |
| goto error_exit; | |
| } | |
| if (ts->TDT_time) gf_free(ts->TDT_time); | |
| ts->TDT_time = time_table; | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_TOT, time_table); | |
| break; | |
| default: | |
| gf_assert(0); | |
| goto error_exit; | |
| } | |
| return; /*success*/ | |
| error_exit: | |
| gf_free(time_table); | |
| return; | |
| } | |
| static GF_M2TS_MetadataPointerDescriptor *gf_m2ts_read_metadata_pointer_descriptor(GF_BitStream *bs, u32 length) | |
| { | |
| u32 size; | |
| GF_M2TS_MetadataPointerDescriptor *d; | |
| GF_SAFEALLOC(d, GF_M2TS_MetadataPointerDescriptor); | |
| if (!d) return NULL; | |
| d->application_format = gf_bs_read_u16(bs); | |
| size = 2; | |
| if (d->application_format == 0xFFFF) { | |
| d->application_format_identifier = gf_bs_read_u32(bs); | |
| size += 4; | |
| } | |
| d->format = gf_bs_read_u8(bs); | |
| size += 1; | |
| if (d->format == 0xFF) { | |
| d->format_identifier = gf_bs_read_u32(bs); | |
| size += 4; | |
| } | |
| d->service_id = gf_bs_read_u8(bs); | |
| d->locator_record_flag = (gf_bs_read_int(bs, 1) ? GF_TRUE : GF_FALSE); | |
| d->carriage_flag = (enum metadata_carriage)gf_bs_read_int(bs, 2); | |
| gf_bs_read_int(bs, 5); /*reserved */ | |
| size += 2; | |
| if (d->locator_record_flag) { | |
| d->locator_length = gf_bs_read_u8(bs); | |
| d->locator_data = (char *)gf_malloc(d->locator_length); | |
| size += 1 + d->locator_length; | |
| gf_bs_read_data(bs, d->locator_data, d->locator_length); | |
| } | |
| if (d->carriage_flag != 3) { | |
| d->program_number = gf_bs_read_u16(bs); | |
| size += 2; | |
| } | |
| if (d->carriage_flag == 1) { | |
| d->ts_location = gf_bs_read_u16(bs); | |
| d->ts_id = gf_bs_read_u16(bs); | |
| size += 4; | |
| } | |
| if (length > size) { | |
| d->data_size = length-size; | |
| d->data = (char *)gf_malloc(d->data_size); | |
| gf_bs_read_data(bs, d->data, d->data_size); | |
| } | |
| return d; | |
| } | |
| static void gf_m2ts_metadata_pointer_descriptor_del(GF_M2TS_MetadataPointerDescriptor *metapd) | |
| { | |
| if (metapd) { | |
| if (metapd->locator_data) gf_free(metapd->locator_data); | |
| if (metapd->data) gf_free(metapd->data); | |
| gf_free(metapd); | |
| } | |
| } | |
| static GF_M2TS_MetadataDescriptor *gf_m2ts_read_metadata_descriptor(GF_BitStream *bs, u32 length) | |
| { | |
| //u32 size; | |
| GF_M2TS_MetadataDescriptor *d; | |
| GF_SAFEALLOC(d, GF_M2TS_MetadataDescriptor); | |
| if (!d) return NULL; | |
| d->application_format = gf_bs_read_u16(bs); | |
| //size = 2; | |
| if (d->application_format == 0xFFFF) { | |
| d->application_format_identifier = gf_bs_read_u32(bs); | |
| //size += 4; | |
| } | |
| d->format = gf_bs_read_u8(bs); | |
| //size += 1; | |
| if (d->format == 0xFF) { | |
| d->format_identifier = gf_bs_read_u32(bs); | |
| //size += 4; | |
| } | |
| d->service_id = gf_bs_read_u8(bs); | |
| d->decoder_config_flags = gf_bs_read_int(bs, 3); | |
| d->dsmcc_flag = (gf_bs_read_int(bs, 1) ? GF_TRUE : GF_FALSE); | |
| gf_bs_read_int(bs, 4); /* reserved */ | |
| //size += 2; | |
| if (d->dsmcc_flag) { | |
| d->service_id_record_length = gf_bs_read_u8(bs); | |
| d->service_id_record = (char *)gf_malloc(d->service_id_record_length); | |
| //size += 1 + d->service_id_record_length; | |
| gf_bs_read_data(bs, d->service_id_record, d->service_id_record_length); | |
| } | |
| if (d->decoder_config_flags == 1) { | |
| d->decoder_config_length = gf_bs_read_u8(bs); | |
| d->decoder_config = (char *)gf_malloc(d->decoder_config_length); | |
| //size += 1 + d->decoder_config_length; | |
| gf_bs_read_data(bs, d->decoder_config, d->decoder_config_length); | |
| } | |
| if (d->decoder_config_flags == 3) { | |
| d->decoder_config_id_length = gf_bs_read_u8(bs); | |
| d->decoder_config_id = (char *)gf_malloc(d->decoder_config_id_length); | |
| //size += 1 + d->decoder_config_id_length; | |
| gf_bs_read_data(bs, d->decoder_config_id, d->decoder_config_id_length); | |
| } | |
| if (d->decoder_config_flags == 4) { | |
| d->decoder_config_service_id = gf_bs_read_u8(bs); | |
| //size++; | |
| } | |
| return d; | |
| } | |
| static void gf_m2ts_process_pmt(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *pmt, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) | |
| { | |
| u32 info_length, pos, desc_len, evt_type, nb_es,i; | |
| u32 nb_sections; | |
| u32 data_size; | |
| u32 nb_hevc_temp, nb_shvc, nb_shvc_temp, nb_mhvc, nb_mhvc_temp; | |
| unsigned char *data; | |
| GF_M2TS_Section *section; | |
| GF_Err e = GF_OK; | |
| //only process PMT tables | |
| if (table_id != GF_M2TS_TABLE_ID_PMT) return; | |
| /*wait for the last section */ | |
| if (!(status&GF_M2TS_TABLE_END)) return; | |
| nb_es = 0; | |
| /*skip if already received but no update detected (eg same data) */ | |
| if ((status&GF_M2TS_TABLE_REPEAT) && !(status&GF_M2TS_TABLE_UPDATE)) { | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_PMT_REPEAT, pmt->program); | |
| return; | |
| } | |
| if (pmt->sec->demux_restarted) { | |
| pmt->sec->demux_restarted = 0; | |
| return; | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PMT Found or updated\n")); | |
| nb_sections = gf_list_count(sections); | |
| if (nb_sections > 1) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("PMT on multiple sections not supported\n")); | |
| } | |
| section = (GF_M2TS_Section *)gf_list_get(sections, 0); | |
| data = section->data; | |
| data_size = section->data_size; | |
| if (data_size < 4) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PMT header data size %d\n", data_size ) ); | |
| return; | |
| } | |
| pmt->program->pcr_pid = ((data[0] & 0x1f) << 8) | data[1]; | |
| Bool is_scrambled = GF_FALSE; | |
| info_length = ((data[2]&0xf)<<8) | data[3]; | |
| if (info_length + 4 > data_size) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Broken PMT first loop, %d bytes avail but first loop size %d\n", data_size, info_length)); | |
| return; | |
| } else if (info_length != 0) { | |
| /* ...Read Descriptors ... */ | |
| u32 tag, len; | |
| u32 first_loop_len = 0; | |
| if (data_size < 6) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PMT header data size %d\n", data_size ) ); | |
| return; | |
| } | |
| tag = (u32) data[4]; | |
| len = (u32) data[5]; | |
| while (info_length > first_loop_len) { | |
| if (tag == GF_M2TS_MPEG4_IOD_DESCRIPTOR) { | |
| if ((len>2) && (len - 2 <= info_length) && (data_size>8) && (data_size-8 > (u32)len-2)) { | |
| u32 size; | |
| GF_BitStream *iod_bs; | |
| iod_bs = gf_bs_new((char *)data+8, len-2, GF_BITSTREAM_READ); | |
| if (pmt->program->pmt_iod) gf_odf_desc_del((GF_Descriptor *)pmt->program->pmt_iod); | |
| pmt->program->pmt_iod = NULL; | |
| e = gf_odf_parse_descriptor(iod_bs , (GF_Descriptor **) &pmt->program->pmt_iod, &size); | |
| gf_bs_del(iod_bs ); | |
| if (pmt->program->pmt_iod && pmt->program->pmt_iod->tag != GF_ODF_IOD_TAG) { | |
| GF_LOG( GF_LOG_ERROR, GF_LOG_CONTAINER, ("pmt iod has wrong tag %d\n", pmt->program->pmt_iod->tag) ); | |
| gf_odf_desc_del((GF_Descriptor *)pmt->program->pmt_iod); | |
| pmt->program->pmt_iod = NULL; | |
| } | |
| if (e==GF_OK) { | |
| /*remember program number for service/program selection*/ | |
| if (pmt->program->pmt_iod) { | |
| pmt->program->pmt_iod->ServiceID = pmt->program->number; | |
| /*if empty IOD (freebox case), discard it and use dynamic declaration of object*/ | |
| if (!gf_list_count(pmt->program->pmt_iod->ESDescriptors)) { | |
| gf_odf_desc_del((GF_Descriptor *)pmt->program->pmt_iod); | |
| pmt->program->pmt_iod = NULL; | |
| } | |
| } | |
| } | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Broken IOD! len %d less than 2 bytes to declare IOD\n", len)); | |
| } | |
| } else if (tag == GF_M2TS_METADATA_POINTER_DESCRIPTOR) { | |
| GF_BitStream *metadatapd_bs; | |
| GF_M2TS_MetadataPointerDescriptor *metapd; | |
| if (data_size <= 8 || data_size-6 < (u32)len) | |
| break; | |
| metadatapd_bs = gf_bs_new((char *)data+6, len, GF_BITSTREAM_READ); | |
| metapd = gf_m2ts_read_metadata_pointer_descriptor(metadatapd_bs, len); | |
| gf_bs_del(metadatapd_bs); | |
| if (metapd->application_format_identifier == GF_M2TS_META_ID3 && | |
| metapd->format_identifier == GF_M2TS_META_ID3 && | |
| metapd->carriage_flag == METADATA_CARRIAGE_SAME_TS) { | |
| /*HLS ID3 Metadata */ | |
| if (pmt->program->metadata_pointer_descriptor) | |
| gf_m2ts_metadata_pointer_descriptor_del(pmt->program->metadata_pointer_descriptor); | |
| pmt->program->metadata_pointer_descriptor = metapd; | |
| } else { | |
| /* don't know what to do with it for now, delete */ | |
| gf_m2ts_metadata_pointer_descriptor_del(metapd); | |
| } | |
| } else if(tag == GF_M2TS_REGISTRATION_DESCRIPTOR && len >= 4 && data_size>9) { | |
| u32 reg_desc_format = GF_4CC(data[6], data[7], data[8], data[9]); | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[MPEG-2 TS] Registration descriptor with format_identifier \"%s\"\n", gf_4cc_to_str(reg_desc_format))); | |
| } else if (tag==GF_M2TS_CA_DESCRIPTOR) { | |
| is_scrambled = GF_TRUE; | |
| } else { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] Skipping descriptor (0x%x) and others not supported\n", tag)); | |
| } | |
| first_loop_len += 2 + len; | |
| } | |
| } | |
| if (data_size <= 4 + info_length) return; | |
| data += 4 + info_length; | |
| data_size -= 4 + info_length; | |
| pos = 0; | |
| nb_hevc_temp = nb_shvc = nb_shvc_temp = nb_mhvc = nb_mhvc_temp = 0; | |
| while (pos<data_size) { | |
| GF_M2TS_PES *pes = NULL; | |
| GF_M2TS_SECTION_ES *ses = NULL; | |
| GF_M2TS_ES *es = NULL; | |
| Bool inherit_pcr = 0; | |
| Bool pmt_pid_reused = GF_FALSE; | |
| u32 pid, stream_type, reg_desc_format; | |
| if (pos + 5 > data_size) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Broken PMT! size %d but position %d and need at least 5 bytes to declare es\n", data_size, pos)); | |
| break; | |
| } | |
| stream_type = data[0]; | |
| pid = ((data[1] & 0x1f) << 8) | data[2]; | |
| desc_len = ((data[3] & 0xf) << 8) | data[4]; | |
| if (!pid) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PID 0 for es descriptor in PMT of program %d, reserved for PAT\n", pmt->pid) ); | |
| break; | |
| } | |
| if (pid==1) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PID 1 for es descriptor in PMT of program %d, reserved for CAT\n", pmt->pid) ); | |
| break; | |
| } | |
| if (pid==pmt->pid) { | |
| pmt_pid_reused = GF_TRUE; | |
| } else { | |
| u32 pcount = gf_list_count(ts->programs); | |
| for(i=0; i<pcount; i++) { | |
| GF_M2TS_Program *prog = (GF_M2TS_Program *)gf_list_get(ts->programs,i); | |
| if(prog->pmt_pid == pid) { | |
| pmt_pid_reused = GF_TRUE; | |
| break; | |
| } | |
| } | |
| } | |
| if (pmt_pid_reused) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PID %d for es descriptor in PMT of program %d, this PID is already assigned to a PMT\n", pid, pmt->pid) ); | |
| break; | |
| } | |
| if (desc_len > data_size-5) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PMT es descriptor size for PID %d\n", pid ) ); | |
| break; | |
| } | |
| if (!pid) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PID 0 for es descriptor in PMT of program %d, reserved for PAT\n", pmt->pid) ); | |
| break; | |
| } | |
| if (pid==1) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PID 1 for es descriptor in PMT of program %d, reserved for CAT\n", pmt->pid) ); | |
| break; | |
| } | |
| if (pid==pmt->pid) { | |
| pmt_pid_reused = GF_TRUE; | |
| } else { | |
| u32 pcount = gf_list_count(ts->programs); | |
| for(i=0; i<pcount; i++) { | |
| GF_M2TS_Program *prog = (GF_M2TS_Program *)gf_list_get(ts->programs,i); | |
| if(prog->pmt_pid == pid) { | |
| pmt_pid_reused = GF_TRUE; | |
| break; | |
| } | |
| } | |
| } | |
| if (pmt_pid_reused) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PID %d for es descriptor in PMT of program %d, this PID is already assigned to a PMT\n", pid, pmt->pid) ); | |
| break; | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("stream_type :%d \n",stream_type)); | |
| if ((stream_type==GF_M2TS_JPEG_XS) && gf_opts_get_bool("core", "m2ts-vvc-old")) | |
| stream_type = GF_M2TS_VIDEO_VVC; | |
| switch (stream_type) { | |
| /* PES */ | |
| case GF_M2TS_VIDEO_MPEG1: | |
| case GF_M2TS_VIDEO_MPEG2: | |
| case GF_M2TS_VIDEO_DCII: | |
| case GF_M2TS_VIDEO_MPEG4: | |
| case GF_M2TS_SYSTEMS_MPEG4_PES: | |
| case GF_M2TS_VIDEO_H264: | |
| case GF_M2TS_VIDEO_SVC: | |
| case GF_M2TS_VIDEO_MVCD: | |
| case GF_M2TS_VIDEO_HEVC: | |
| case GF_M2TS_VIDEO_HEVC_MCTS: | |
| case GF_M2TS_VIDEO_HEVC_TEMPORAL: | |
| case GF_M2TS_VIDEO_SHVC: | |
| case GF_M2TS_VIDEO_SHVC_TEMPORAL: | |
| case GF_M2TS_VIDEO_MHVC: | |
| case GF_M2TS_VIDEO_MHVC_TEMPORAL: | |
| case GF_M2TS_VIDEO_VVC: | |
| case GF_M2TS_VIDEO_VVC_TEMPORAL: | |
| case GF_M2TS_VIDEO_VC1: | |
| case GF_M2TS_HLS_AVC_CRYPT: | |
| inherit_pcr = 1; | |
| case GF_M2TS_AUDIO_MPEG1: | |
| case GF_M2TS_AUDIO_MPEG2: | |
| case GF_M2TS_AUDIO_AAC: | |
| case GF_M2TS_AUDIO_LATM_AAC: | |
| case GF_M2TS_AUDIO_AC3: | |
| case GF_M2TS_AUDIO_EC3: | |
| case GF_M2TS_AUDIO_DTS: | |
| case GF_M2TS_AUDIO_TRUEHD: | |
| case GF_M2TS_AUDIO_OPUS: | |
| case GF_M2TS_MHAS_MAIN: | |
| case GF_M2TS_MHAS_AUX: | |
| case GF_M2TS_SUBTITLE_DVB: | |
| case GF_M2TS_METADATA_PES: | |
| case GF_M2TS_HLS_AAC_CRYPT: | |
| case GF_M2TS_HLS_AC3_CRYPT: | |
| case GF_M2TS_HLS_EC3_CRYPT: | |
| case GF_M2TS_VIDEO_AVS2: | |
| case GF_M2TS_AUDIO_AVS2: | |
| case GF_M2TS_VIDEO_AVS3: | |
| case GF_M2TS_AUDIO_AVS3: | |
| case 0xA1: | |
| GF_SAFEALLOC(pes, GF_M2TS_PES); | |
| if (!pes) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG2TS] Failed to allocate ES for pid %d\n", pid)); | |
| return; | |
| } | |
| pes->cc = -1; | |
| pes->flags = GF_M2TS_ES_IS_PES; | |
| if (inherit_pcr) | |
| pes->flags |= GF_M2TS_INHERIT_PCR; | |
| es = (GF_M2TS_ES *)pes; | |
| break; | |
| case GF_M2TS_PRIVATE_DATA: | |
| GF_SAFEALLOC(pes, GF_M2TS_PES); | |
| if (!pes) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG2TS] Failed to allocate ES for pid %d\n", pid)); | |
| return; | |
| } | |
| pes->cc = -1; | |
| pes->flags = GF_M2TS_ES_IS_PES; | |
| es = (GF_M2TS_ES *)pes; | |
| break; | |
| /* Sections */ | |
| case GF_M2TS_SYSTEMS_MPEG4_SECTIONS: | |
| GF_SAFEALLOC(ses, GF_M2TS_SECTION_ES); | |
| if (!ses) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG2TS] Failed to allocate ES for pid %d\n", pid)); | |
| return; | |
| } | |
| es = (GF_M2TS_ES *)ses; | |
| es->flags |= GF_M2TS_ES_IS_SECTION; | |
| /* carriage of ISO_IEC_14496 data in sections */ | |
| if (stream_type == GF_M2TS_SYSTEMS_MPEG4_SECTIONS) { | |
| /*MPEG-4 sections need to be fully checked: if one section is lost, this means we lost | |
| one SL packet in the AU so we must wait for the complete section again*/ | |
| ses->sec = gf_m2ts_section_filter_new(gf_m2ts_process_mpeg4section, 0); | |
| /*create OD container*/ | |
| if (!pmt->program->additional_ods) { | |
| pmt->program->additional_ods = gf_list_new(); | |
| ts->has_4on2 = 1; | |
| } | |
| } | |
| break; | |
| case GF_M2TS_13818_6_ANNEX_A: | |
| case GF_M2TS_13818_6_ANNEX_B: | |
| case GF_M2TS_13818_6_ANNEX_C: | |
| case GF_M2TS_13818_6_ANNEX_D: | |
| case GF_M2TS_PRIVATE_SECTION: | |
| case GF_M2TS_QUALITY_SEC: | |
| case GF_M2TS_MORE_SEC: | |
| case GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS: | |
| GF_SAFEALLOC(ses, GF_M2TS_SECTION_ES); | |
| if (!ses) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG2TS] Failed to allocate ES for pid %d\n", pid)); | |
| return; | |
| } | |
| es = (GF_M2TS_ES *)ses; | |
| es->flags |= GF_M2TS_ES_IS_SECTION; | |
| es->pid = pid; | |
| es->service_id = pmt->program->number; | |
| if (stream_type == GF_M2TS_PRIVATE_SECTION) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("AIT sections on pid %d\n", pid)); | |
| } else if (stream_type == GF_M2TS_QUALITY_SEC) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("Quality metadata sections on pid %d\n", pid)); | |
| } else if (stream_type == GF_M2TS_MORE_SEC) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("MORE sections on pid %d\n", pid)); | |
| } else if (stream_type == GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("SCTE35 Splice Info sections on pid %d\n", pid)); | |
| } else { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("stream type DSM CC user private sections on pid %d \n", pid)); | |
| } | |
| /* NULL means: trigger the call to on_event with DVB_GENERAL type and the raw section as payload */ | |
| ses->sec = gf_m2ts_section_filter_new(NULL, 1); | |
| //ses->sec->service_id = pmt->program->number; | |
| break; | |
| case GF_M2TS_MPE_SECTIONS: | |
| if (! ts->prefix_present) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("stream type MPE found : pid = %d \n", pid)); | |
| #ifdef GPAC_ENABLE_MPE | |
| es = gf_dvb_mpe_section_new(); | |
| if (es->flags & GF_M2TS_ES_IS_SECTION) { | |
| /* NULL means: trigger the call to on_event with DVB_GENERAL type and the raw section as payload */ | |
| ((GF_M2TS_SECTION_ES*)es)->sec = gf_m2ts_section_filter_new(NULL, 1); | |
| } | |
| #endif | |
| break; | |
| } | |
| default: | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] Stream type (0x%x) for PID %d not supported\n", stream_type, pid ) ); | |
| break; | |
| } | |
| if (es) { | |
| es->stream_type = (stream_type==GF_M2TS_PRIVATE_DATA) ? 0 : stream_type; | |
| es->program = pmt->program; | |
| es->pid = pid; | |
| es->component_tag = -1; | |
| } | |
| pos += 5; | |
| data += 5; | |
| while (desc_len) { | |
| if (pos + 2 > data_size) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Broken PMT descriptor! size %d but position %d and need at least 2 bytes to parse descriptor\n", data_size, pos)); | |
| break; | |
| } | |
| u8 tag = data[0]; | |
| u32 len = data[1]; | |
| if (pos + 2 + len > data_size) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Broken PMT descriptor! size %d, desc size %d but position %d\n", data_size, len, pos)); | |
| break; | |
| } | |
| if (es) { | |
| switch (tag) { | |
| case GF_M2TS_ISO_639_LANGUAGE_DESCRIPTOR: | |
| if (pes && (len>=3) ) | |
| pes->lang = GF_4CC(' ', data[2], data[3], data[4]); | |
| break; | |
| case GF_M2TS_MPEG4_SL_DESCRIPTOR: | |
| if (len>=2) { | |
| es->mpeg4_es_id = ( (u32) data[2] & 0x1f) << 8 | data[3]; | |
| es->flags |= GF_M2TS_ES_IS_SL; | |
| } | |
| break; | |
| case GF_M2TS_REGISTRATION_DESCRIPTOR: | |
| if (len>=4) { | |
| reg_desc_format = GF_4CC(data[2], data[3], data[4], data[5]); | |
| /* cf https://smpte-ra.org/registered-mpeg-ts-ids */ | |
| switch (reg_desc_format) { | |
| case GF_M2TS_RA_STREAM_AC3: | |
| //don't overwrite if already EAC3 or TrueHD | |
| if ((es->stream_type != GF_M2TS_AUDIO_EC3) && (es->stream_type != GF_M2TS_AUDIO_TRUEHD)) | |
| es->stream_type = GF_M2TS_AUDIO_AC3; | |
| break; | |
| case GF_M2TS_RA_STREAM_EAC3: | |
| //don't overwrite if already AC3 or TrueHD | |
| if ((es->stream_type != GF_M2TS_AUDIO_AC3) && (es->stream_type != GF_M2TS_AUDIO_TRUEHD)) | |
| es->stream_type = GF_M2TS_AUDIO_EC3; | |
| break; | |
| case GF_M2TS_RA_STREAM_AC4: | |
| es->stream_type = GF_M2TS_AUDIO_AC4; | |
| break; | |
| case GF_M2TS_RA_STREAM_VC1: | |
| es->stream_type = GF_M2TS_VIDEO_VC1; | |
| break; | |
| case GF_M2TS_RA_STREAM_HEVC: | |
| es->stream_type = GF_M2TS_VIDEO_HEVC; | |
| break; | |
| case GF_M2TS_RA_STREAM_DTS1: | |
| case GF_M2TS_RA_STREAM_DTS2: | |
| case GF_M2TS_RA_STREAM_DTS3: | |
| es->stream_type = GF_M2TS_AUDIO_DTS; | |
| break; | |
| case GF_M2TS_RA_STREAM_AVSA: | |
| es->stream_type = GF_M2TS_AUDIO_AVS3; | |
| break; | |
| case GF_M2TS_RA_STREAM_AVSV: | |
| es->stream_type = GF_M2TS_VIDEO_AVS3; | |
| // AVS3-P6-TAI 109.6 Table-9 | |
| if (len<10) break; | |
| memcpy(pes->avs3_video_descriptor, data+6, 10); | |
| break; | |
| case GF_M2TS_RA_STREAM_OPUS: | |
| es->stream_type = GF_M2TS_AUDIO_OPUS; | |
| break; | |
| case GF_M2TS_RA_STREAM_DOVI: | |
| break; | |
| case GF_M2TS_RA_STREAM_AV1: | |
| es->stream_type = GF_M2TS_VIDEO_AV1; | |
| break; | |
| case GF_M2TS_RA_STREAM_SCTE35: | |
| es->stream_type = GF_M2TS_SCTE35_SPLICE_INFO_SECTIONS; | |
| break; | |
| case GF_M2TS_RA_STREAM_SRT: | |
| es->stream_type = GF_M2TS_METADATA_SRT; | |
| break; | |
| case GF_M2TS_RA_STREAM_TXT: | |
| es->stream_type = GF_M2TS_METADATA_TEXT; | |
| break; | |
| case GF_M2TS_RA_STREAM_GPAC: | |
| if (len<8) break; | |
| es->stream_type = GF_4CC(data[6], data[7], data[8], data[9]); | |
| es->flags |= GF_M2TS_GPAC_CODEC_ID; | |
| if ((len>12) && (es->flags & GF_M2TS_ES_IS_PES)) { | |
| pes = (GF_M2TS_PES*)es; | |
| pes->gpac_meta_dsi_size = len-4; | |
| pes->gpac_meta_dsi = gf_realloc(pes->gpac_meta_dsi, pes->gpac_meta_dsi_size); | |
| if (pes->gpac_meta_dsi) | |
| memcpy(pes->gpac_meta_dsi, data+6, pes->gpac_meta_dsi_size); | |
| } | |
| break; | |
| default: | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("Unknown registration descriptor %s\n", gf_4cc_to_str(reg_desc_format) )); | |
| break; | |
| } | |
| } | |
| break; | |
| case GF_M2TS_DVB_EAC3_DESCRIPTOR: | |
| es->stream_type = GF_M2TS_AUDIO_EC3; | |
| break; | |
| case GF_M2TS_DVB_DATA_BROADCAST_ID_DESCRIPTOR: | |
| if (len>=2) { | |
| u32 id = data[2]<<8 | data[3]; | |
| if ((id == 0xB) && ses && !ses->sec) { | |
| ses->sec = gf_m2ts_section_filter_new(NULL, 1); | |
| } | |
| } | |
| break; | |
| case GF_M2TS_DVB_SUBTITLING_DESCRIPTOR: | |
| if (pes) { | |
| if (len>=8) { | |
| pes->sub.language[0] = data[2]; | |
| pes->sub.language[1] = data[3]; | |
| pes->sub.language[2] = data[4]; | |
| pes->sub.type = data[5]; | |
| pes->sub.composition_page_id = (data[6]<<8) | data[7]; | |
| pes->sub.ancillary_page_id = (data[8]<<8) | data[9]; | |
| } else { | |
| memset(& (pes->sub), 0, sizeof(pes->sub)); | |
| } | |
| es->stream_type = GF_M2TS_DVB_SUBTITLE; | |
| } | |
| break; | |
| case GF_M2TS_DVB_STREAM_IDENTIFIER_DESCRIPTOR: | |
| if (len>=1) { | |
| es->component_tag = data[2]; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Component Tag: %d on Program %d PID %d\n", es->component_tag, es->program->number, es->pid)); | |
| } | |
| break; | |
| case GF_M2TS_DVB_TELETEXT_DESCRIPTOR: | |
| es->stream_type = GF_M2TS_DVB_TELETEXT; | |
| break; | |
| case GF_M2TS_DVB_VBI_DATA_DESCRIPTOR: | |
| es->stream_type = GF_M2TS_DVB_VBI; | |
| break; | |
| case GF_M2TS_HIERARCHY_DESCRIPTOR: | |
| if (pes && (len>=4)) { | |
| u8 hierarchy_embedded_layer_index; | |
| GF_BitStream *hbs = gf_bs_new((const char *)data, data_size, GF_BITSTREAM_READ); | |
| /*u32 skip = */gf_bs_read_int(hbs, 16); | |
| /*u8 res1 = */gf_bs_read_int(hbs, 1); | |
| /*u8 temp_scal = */gf_bs_read_int(hbs, 1); | |
| /*u8 spatial_scal = */gf_bs_read_int(hbs, 1); | |
| /*u8 quality_scal = */gf_bs_read_int(hbs, 1); | |
| /*u8 hierarchy_type = */gf_bs_read_int(hbs, 4); | |
| /*u8 res2 = */gf_bs_read_int(hbs, 2); | |
| /*u8 hierarchy_layer_index = */gf_bs_read_int(hbs, 6); | |
| /*u8 tref_not_present = */gf_bs_read_int(hbs, 1); | |
| /*u8 res3 = */gf_bs_read_int(hbs, 1); | |
| hierarchy_embedded_layer_index = gf_bs_read_int(hbs, 6); | |
| /*u8 res4 = */gf_bs_read_int(hbs, 2); | |
| /*u8 hierarchy_channel = */gf_bs_read_int(hbs, 6); | |
| gf_bs_del(hbs); | |
| pes->depends_on_pid = 1+hierarchy_embedded_layer_index; | |
| } | |
| break; | |
| case GF_M2TS_METADATA_DESCRIPTOR: | |
| { | |
| GF_BitStream *metadatad_bs; | |
| GF_M2TS_MetadataDescriptor *metad; | |
| metadatad_bs = gf_bs_new((char *)data+2, len, GF_BITSTREAM_READ); | |
| metad = gf_m2ts_read_metadata_descriptor(metadatad_bs, len); | |
| gf_bs_del(metadatad_bs); | |
| if (metad->application_format_identifier == GF_M2TS_META_ID3 && | |
| metad->format_identifier == GF_M2TS_META_ID3) { | |
| /*HLS ID3 Metadata*/ | |
| if (pes) { | |
| if (pes->metadata_descriptor) | |
| gf_m2ts_metadata_descriptor_del(pes->metadata_descriptor); | |
| pes->metadata_descriptor = metad; | |
| pes->stream_type = GF_M2TS_METADATA_ID3_HLS; | |
| } | |
| else { | |
| gf_m2ts_metadata_descriptor_del(metad); | |
| } | |
| } else if (metad->format_identifier == GF_M2TS_META_KLVA) { | |
| /*ID3 with KLVA generic encoding (https://en.wikipedia.org/wiki/KLV)*/ | |
| if (pes) { | |
| if (pes->metadata_descriptor) | |
| gf_m2ts_metadata_descriptor_del(pes->metadata_descriptor); | |
| pes->metadata_descriptor = metad; | |
| pes->stream_type = GF_M2TS_METADATA_ID3_KLVA; | |
| } | |
| else { | |
| gf_m2ts_metadata_descriptor_del(metad); | |
| } | |
| } else { | |
| /* don't know what to do with it for now, delete */ | |
| gf_m2ts_metadata_descriptor_del(metad); | |
| } | |
| } | |
| break; | |
| case GF_M2TS_HEVC_VIDEO_DESCRIPTOR: | |
| if (es) es->stream_type = GF_M2TS_VIDEO_HEVC; | |
| break; | |
| case GF_M2TS_AVC_VIDEO_DESCRIPTOR: | |
| if (es) es->stream_type = GF_M2TS_VIDEO_H264; | |
| break; | |
| case GF_M2TS_DOLBY_VISION_DESCRIPTOR: | |
| if (pes && (len>=5)) { | |
| GF_BitStream *hbs = gf_bs_new((const char *)data+2, len, GF_BITSTREAM_READ); | |
| pes->dv_info[0] = gf_bs_read_u8(hbs); | |
| pes->dv_info[1] = gf_bs_read_u8(hbs); | |
| pes->dv_info[2] = gf_bs_read_u8(hbs); | |
| pes->dv_info[3] = gf_bs_read_u8(hbs); | |
| if (! (pes->dv_info[3] & 0x1) ) { | |
| pes->depends_on_pid = gf_bs_read_int(hbs, 13); | |
| gf_bs_read_int(hbs, 3); | |
| } | |
| pes->dv_info[4] = gf_bs_read_u8(hbs); | |
| gf_bs_del(hbs); | |
| } | |
| break; | |
| case GF_M2TS_MAX_BITRATE_DESCRIPTOR: | |
| break; | |
| case GF_M2TS_DVB_EXT_DESCRIPTOR: | |
| if ((len>4) && (data[2] == 0x06) && pes) { | |
| u32 flags = data[3]; | |
| u32 aflags = (flags >> 2) & 0x1F; | |
| if ((flags & 0x80) == 0) | |
| pes->audio_flags |= GF_M2TS_AUDIO_SUBSTREAM_COMP; | |
| switch (aflags) { | |
| case 0x01: | |
| pes->audio_flags |= GF_M2TS_AUDIO_DESCRIPTION; | |
| break; | |
| case 0x02: | |
| pes->audio_flags |= GF_M2TS_AUDIO_HEARING_IMPAIRED; | |
| break; | |
| case 0x03: | |
| pes->audio_flags |= GF_M2TS_AUDIO_SUB_DESCRIPTION; | |
| break; | |
| } | |
| if ((flags & 0x01) && (len>=7)) { | |
| pes->lang = ' '; | |
| pes->lang <<= 8; | |
| pes->lang = data[4]; | |
| pes->lang <<= 8; | |
| pes->lang = data[5]; | |
| pes->lang <<= 8; | |
| pes->lang = data[6]; | |
| } | |
| } | |
| break; | |
| default: | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] skipping descriptor (0x%x) not supported\n", tag)); | |
| break; | |
| } | |
| } | |
| data += len+2; | |
| pos += len+2; | |
| if (desc_len < len+2) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Invalid PMT es descriptor size for PID %d\n", pid ) ); | |
| break; | |
| } | |
| desc_len-=len+2; | |
| } | |
| if (es && !es->stream_type) { | |
| #if 0 | |
| gf_free(es); | |
| es = NULL; | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Private Stream type (0x%x) for PID %d not supported\n", stream_type, pid ) ); | |
| #else | |
| es->stream_type = stream_type; | |
| #endif | |
| } | |
| if (!es) continue; | |
| if (pes && (stream_type==GF_M2TS_PRIVATE_DATA) && (es->stream_type!=stream_type) && pes->dv_info[0]) { | |
| //non-compatible base layer dolby vision | |
| pes->dv_info[24] = 1; | |
| } | |
| if (pes) pes->is_protected = is_scrambled; | |
| if (ts->ess[pid]) { | |
| //this is component reuse across programs, overwrite the previously declared stream ... | |
| if (status & GF_M2TS_TABLE_FOUND) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d reused across programs %d and %d, not completely supported\n", pid, ts->ess[pid]->program->number, es->program->number ) ); | |
| //add stream to program but don't reassign the pid table until the stream is playing (>GF_M2TS_PES_FRAMING_SKIP) | |
| gf_list_add(pmt->program->streams, es); | |
| if (!(es->flags & GF_M2TS_ES_IS_SECTION) ) gf_m2ts_set_pes_framing(pes, GF_M2TS_PES_FRAMING_SKIP); | |
| nb_es++; | |
| //skip assignment below | |
| es = NULL; | |
| } | |
| /*watchout for pmt update - FIXME this likely won't work in most cases*/ | |
| else { | |
| GF_M2TS_ES *o_es = ts->ess[es->pid]; | |
| if ((o_es->stream_type == es->stream_type) | |
| && ((o_es->flags & GF_M2TS_ES_STATIC_FLAGS_MASK) == (es->flags & GF_M2TS_ES_STATIC_FLAGS_MASK)) | |
| && (o_es->mpeg4_es_id == es->mpeg4_es_id) | |
| && ((o_es->flags & GF_M2TS_ES_IS_SECTION) || ((GF_M2TS_PES *)o_es)->lang == ((GF_M2TS_PES *)es)->lang) | |
| ) { | |
| gf_m2ts_es_del(es, ts); | |
| es = NULL; | |
| } else { | |
| gf_m2ts_es_del(o_es, ts); | |
| ts->ess[es->pid] = NULL; | |
| } | |
| } | |
| } | |
| if (es) { | |
| ts->ess[es->pid] = es; | |
| gf_list_add(pmt->program->streams, es); | |
| if (!(es->flags & GF_M2TS_ES_IS_SECTION) ) gf_m2ts_set_pes_framing(pes, GF_M2TS_PES_FRAMING_SKIP); | |
| nb_es++; | |
| if (es->stream_type == GF_M2TS_VIDEO_HEVC_TEMPORAL) nb_hevc_temp++; | |
| else if (es->stream_type == GF_M2TS_VIDEO_SHVC) nb_shvc++; | |
| else if (es->stream_type == GF_M2TS_VIDEO_SHVC_TEMPORAL) nb_shvc_temp++; | |
| else if (es->stream_type == GF_M2TS_VIDEO_MHVC) nb_mhvc++; | |
| else if (es->stream_type == GF_M2TS_VIDEO_MHVC_TEMPORAL) nb_mhvc_temp++; | |
| } | |
| } | |
| //Table 2-139, implied hierarchy indexes | |
| if (nb_hevc_temp + nb_shvc + nb_shvc_temp + nb_mhvc + nb_mhvc_temp) { | |
| for (i=0; i<gf_list_count(pmt->program->streams); i++) { | |
| GF_M2TS_PES *es = (GF_M2TS_PES *)gf_list_get(pmt->program->streams, i); | |
| if ( !(es->flags & GF_M2TS_ES_IS_PES)) continue; | |
| if (es->depends_on_pid) continue; | |
| switch (es->stream_type) { | |
| case GF_M2TS_VIDEO_HEVC_TEMPORAL: | |
| es->depends_on_pid = 1; | |
| break; | |
| case GF_M2TS_VIDEO_SHVC: | |
| if (!nb_hevc_temp) es->depends_on_pid = 1; | |
| else es->depends_on_pid = 2; | |
| break; | |
| case GF_M2TS_VIDEO_SHVC_TEMPORAL: | |
| es->depends_on_pid = 3; | |
| break; | |
| case GF_M2TS_VIDEO_MHVC: | |
| if (!nb_hevc_temp) es->depends_on_pid = 1; | |
| else es->depends_on_pid = 2; | |
| break; | |
| case GF_M2TS_VIDEO_MHVC_TEMPORAL: | |
| if (!nb_hevc_temp) es->depends_on_pid = 2; | |
| else es->depends_on_pid = 3; | |
| break; | |
| } | |
| } | |
| } | |
| if (nb_es) { | |
| //translate hierarchy descriptors indexes into PIDs - check whether the PMT-index rules are the same for HEVC | |
| for (i=0; i<gf_list_count(pmt->program->streams); i++) { | |
| GF_M2TS_PES *an_es = NULL; | |
| GF_M2TS_PES *es = (GF_M2TS_PES *)gf_list_get(pmt->program->streams, i); | |
| if ( !(es->flags & GF_M2TS_ES_IS_PES)) continue; | |
| if (!es->depends_on_pid) continue; | |
| //fixeme we are not always assured that hierarchy_layer_index matches the stream index... | |
| //+1 is because our first stream is the PMT | |
| an_es = (GF_M2TS_PES *)gf_list_get(pmt->program->streams, es->depends_on_pid); | |
| if (an_es) { | |
| es->depends_on_pid = an_es->pid; | |
| } else { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TS] Wrong dependency index in hierarchy descriptor, assuming non-scalable stream\n")); | |
| es->depends_on_pid = 0; | |
| } | |
| } | |
| evt_type = (status&GF_M2TS_TABLE_FOUND) ? GF_M2TS_EVT_PMT_FOUND : GF_M2TS_EVT_PMT_UPDATE; | |
| if (ts->on_event) ts->on_event(ts, evt_type, pmt->program); | |
| } else { | |
| /* if we found no new ES it's simply a repeat of the PMT */ | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_PMT_REPEAT, pmt->program); | |
| } | |
| } | |
| static void gf_m2ts_process_pat(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *ses, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) | |
| { | |
| GF_M2TS_Program *prog; | |
| GF_M2TS_SECTION_ES *pmt; | |
| u32 i, nb_progs, evt_type; | |
| u32 nb_sections; | |
| u32 data_size; | |
| unsigned char *data; | |
| GF_M2TS_Section *section; | |
| //only process PAT tables | |
| if (table_id != GF_M2TS_TABLE_ID_PAT) return; | |
| /*wait for the last section */ | |
| if (!(status&GF_M2TS_TABLE_END)) return; | |
| /*skip if already received*/ | |
| if (status&GF_M2TS_TABLE_REPEAT) { | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_PAT_REPEAT, NULL); | |
| return; | |
| } | |
| nb_sections = gf_list_count(sections); | |
| if (nb_sections > 1) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("PAT on multiple sections not supported\n")); | |
| } | |
| section = (GF_M2TS_Section *)gf_list_get(sections, 0); | |
| data = section->data; | |
| data_size = section->data_size; | |
| if (!(status&GF_M2TS_TABLE_UPDATE) && gf_list_count(ts->programs)) { | |
| if (ts->pat->demux_restarted) { | |
| ts->pat->demux_restarted = 0; | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Multiple different PAT on single TS found, ignoring new PAT declaration (table id %d - extended table id %d)\n", table_id, ex_table_id)); | |
| } | |
| return; | |
| } | |
| nb_progs = data_size / 4; | |
| for (i=0; i<nb_progs; i++) { | |
| u16 number, pid; | |
| number = (data[0]<<8) | data[1]; | |
| pid = (data[2]&0x1f)<<8 | data[3]; | |
| data += 4; | |
| if (number==0) { | |
| if (!ts->nit) { | |
| ts->nit = gf_m2ts_section_filter_new(gf_m2ts_process_nit, 0); | |
| } | |
| } else if (!pid) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Broken PAT found reserved PID 0, ignoring\n", pid)); | |
| } else if (! ts->ess[pid]) { | |
| GF_SAFEALLOC(prog, GF_M2TS_Program); | |
| if (!prog) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Fail to allocate program for pid %d\n", pid)); | |
| return; | |
| } | |
| prog->streams = gf_list_new(); | |
| prog->pmt_pid = pid; | |
| prog->number = number; | |
| prog->ts = ts; | |
| gf_list_add(ts->programs, prog); | |
| GF_SAFEALLOC(pmt, GF_M2TS_SECTION_ES); | |
| if (!pmt) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Fail to allocate pmt filter for pid %d\n", pid)); | |
| return; | |
| } | |
| pmt->flags = GF_M2TS_ES_IS_SECTION | GF_M2TS_ES_IS_PMT; | |
| gf_list_add(prog->streams, pmt); | |
| pmt->pid = prog->pmt_pid; | |
| pmt->program = prog; | |
| ts->ess[pmt->pid] = (GF_M2TS_ES *)pmt; | |
| pmt->sec = gf_m2ts_section_filter_new(gf_m2ts_process_pmt, 0); | |
| } | |
| } | |
| evt_type = (status&GF_M2TS_TABLE_UPDATE) ? GF_M2TS_EVT_PAT_UPDATE : GF_M2TS_EVT_PAT_FOUND; | |
| if (ts->on_event) { | |
| GF_M2TS_SectionInfo sinfo; | |
| sinfo.ex_table_id = ex_table_id; | |
| sinfo.table_id = table_id; | |
| sinfo.version_number = version_number; | |
| ts->on_event(ts, evt_type, &sinfo); | |
| } | |
| } | |
| static void gf_m2ts_process_cat(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *ses, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status) | |
| { | |
| u32 evt_type; | |
| /* | |
| GF_M2TS_Program *prog; | |
| GF_M2TS_SECTION_ES *pmt; | |
| u32 i, nb_progs; | |
| u32 nb_sections; | |
| u32 data_size; | |
| unsigned char *data; | |
| GF_M2TS_Section *section; | |
| */ | |
| //only process CAT tables | |
| if (table_id != GF_M2TS_TABLE_ID_CAT) return; | |
| /*wait for the last section */ | |
| if (!(status&GF_M2TS_TABLE_END)) return; | |
| /*skip if already received*/ | |
| if (status&GF_M2TS_TABLE_REPEAT) { | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_CAT_REPEAT, NULL); | |
| return; | |
| } | |
| /* | |
| nb_sections = gf_list_count(sections); | |
| if (nb_sections > 1) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("CAT on multiple sections not supported\n")); | |
| } | |
| section = (GF_M2TS_Section *)gf_list_get(sections, 0); | |
| data = section->data; | |
| data_size = section->data_size; | |
| nb_progs = data_size / 4; | |
| for (i=0; i<nb_progs; i++) { | |
| u16 number, pid; | |
| number = (data[0]<<8) | data[1]; | |
| pid = (data[2]&0x1f)<<8 | data[3]; | |
| data += 4; | |
| if (number==0) { | |
| if (!ts->nit) { | |
| ts->nit = gf_m2ts_section_filter_new(gf_m2ts_process_nit, 0); | |
| } | |
| } else { | |
| GF_SAFEALLOC(prog, GF_M2TS_Program); | |
| prog->streams = gf_list_new(); | |
| prog->pmt_pid = pid; | |
| prog->number = number; | |
| gf_list_add(ts->programs, prog); | |
| GF_SAFEALLOC(pmt, GF_M2TS_SECTION_ES); | |
| pmt->flags = GF_M2TS_ES_IS_SECTION; | |
| gf_list_add(prog->streams, pmt); | |
| pmt->pid = prog->pmt_pid; | |
| pmt->program = prog; | |
| ts->ess[pmt->pid] = (GF_M2TS_ES *)pmt; | |
| pmt->sec = gf_m2ts_section_filter_new(gf_m2ts_process_pmt, 0); | |
| } | |
| } | |
| */ | |
| evt_type = (status&GF_M2TS_TABLE_UPDATE) ? GF_M2TS_EVT_CAT_UPDATE : GF_M2TS_EVT_CAT_FOUND; | |
| if (ts->on_event) ts->on_event(ts, evt_type, NULL); | |
| } | |
| u64 gf_m2ts_get_pts(unsigned char *data) | |
| { | |
| u64 pts; | |
| u32 val; | |
| pts = (u64)((data[0] >> 1) & 0x07) << 30; | |
| val = (data[1] << 8) | data[2]; | |
| pts |= (u64)(val >> 1) << 15; | |
| val = (data[3] << 8) | data[4]; | |
| pts |= (u64)(val >> 1); | |
| return pts; | |
| } | |
| void gf_m2ts_pes_header(GF_M2TS_PES *pes, unsigned char *data, u32 data_size, GF_M2TS_PESHeader *pesh) | |
| { | |
| u32 has_pts, has_dts; | |
| u32 len_check; | |
| memset(pesh, 0, sizeof(GF_M2TS_PESHeader)); | |
| if (data_size < 6) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("PES Header is too small (%d < 6)\n", data_size)); | |
| return; | |
| } | |
| len_check = 0; | |
| pesh->id = data[0]; | |
| pesh->pck_len = (data[1]<<8) | data[2]; | |
| /* | |
| 2bits | |
| scrambling_control = gf_bs_read_int(bs,2); | |
| priority = gf_bs_read_int(bs,1); | |
| */ | |
| pesh->data_alignment = (data[3] & 0x4) ? 1 : 0; | |
| /* | |
| copyright = gf_bs_read_int(bs,1); | |
| original = gf_bs_read_int(bs,1); | |
| */ | |
| has_pts = (data[4]&0x80); | |
| has_dts = has_pts ? (data[4]&0x40) : 0; | |
| /* | |
| ESCR_flag = gf_bs_read_int(bs,1); | |
| ES_rate_flag = gf_bs_read_int(bs,1); | |
| DSM_flag = gf_bs_read_int(bs,1); | |
| additional_copy_flag = gf_bs_read_int(bs,1); | |
| prev_crc_flag = gf_bs_read_int(bs,1); | |
| extension_flag = gf_bs_read_int(bs,1); | |
| */ | |
| pesh->hdr_data_len = data[5]; | |
| data += 6; | |
| if (has_pts) { | |
| pesh->PTS = gf_m2ts_get_pts(data); | |
| data+=5; | |
| len_check += 5; | |
| pesh->has_pts = 1; | |
| } | |
| if (has_dts) { | |
| pesh->DTS = gf_m2ts_get_pts(data); | |
| //data+=5; | |
| len_check += 5; | |
| } else { | |
| pesh->DTS = pesh->PTS; | |
| } | |
| if (len_check < pesh->hdr_data_len) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d Skipping %d bytes in pes header\n", pes->pid, pesh->hdr_data_len - len_check)); | |
| } else if (len_check > pesh->hdr_data_len) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d Wrong pes_header_data_length field %d bytes - read %d\n", pes->pid, pesh->hdr_data_len, len_check)); | |
| } | |
| if ((pesh->PTS<90000) && ((s32)pesh->DTS<0) && (GF_M2TS_MAX_PCR_90K - pesh->DTS >= 18000)) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d Wrong DTS %d negative for PTS %d - forcing to 0\n", pes->pid, pesh->DTS, pesh->PTS)); | |
| pesh->DTS=0; | |
| } | |
| } | |
| static void gf_m2ts_store_temi(GF_M2TS_Demuxer *ts, GF_M2TS_PES *pes) | |
| { | |
| GF_BitStream *bs = gf_bs_new(pes->temi_tc_desc, pes->temi_tc_desc_len, GF_BITSTREAM_READ); | |
| u32 has_timestamp = gf_bs_read_int(bs, 2); | |
| Bool has_ntp = (Bool) gf_bs_read_int(bs, 1); | |
| /*u32 has_ptp = */gf_bs_read_int(bs, 1); | |
| /*u32 has_timecode = */gf_bs_read_int(bs, 2); | |
| memset(&pes->temi_tc, 0, sizeof(GF_M2TS_TemiTimecodeDescriptor)); | |
| pes->temi_tc.force_reload = gf_bs_read_int(bs, 1); | |
| pes->temi_tc.is_paused = gf_bs_read_int(bs, 1); | |
| pes->temi_tc.is_discontinuity = gf_bs_read_int(bs, 1); | |
| gf_bs_read_int(bs, 7); | |
| pes->temi_tc.timeline_id = gf_bs_read_int(bs, 8); | |
| if (has_timestamp) { | |
| pes->temi_tc.media_timescale = gf_bs_read_u32(bs); | |
| if (has_timestamp==2) | |
| pes->temi_tc.media_timestamp = gf_bs_read_u64(bs); | |
| else | |
| pes->temi_tc.media_timestamp = gf_bs_read_u32(bs); | |
| } | |
| if (has_ntp) { | |
| pes->temi_tc.ntp = gf_bs_read_u64(bs); | |
| } | |
| gf_bs_del(bs); | |
| pes->temi_tc_desc_len = 0; | |
| pes->temi_pending = GF_TRUE; | |
| } | |
| void gf_m2ts_flush_pes(GF_M2TS_Demuxer *ts, GF_M2TS_PES *pes, u32 force_flush_type) | |
| { | |
| GF_M2TS_PESHeader pesh; | |
| if (!ts) return; | |
| /*we need at least a full, valid start code and PES header !!*/ | |
| if ((pes->pck_data_len >= 4) && !pes->pck_data[0] && !pes->pck_data[1] && (pes->pck_data[2] == 0x1)) { | |
| u32 len; | |
| Bool has_pes_header = GF_TRUE; | |
| Bool has_data = GF_TRUE; | |
| u32 stream_id = pes->pck_data[3]; | |
| Bool same_pts = GF_FALSE; | |
| switch (stream_id) { | |
| case GF_M2_STREAMID_PADDING: | |
| has_data = GF_FALSE; | |
| case GF_M2_STREAMID_PROGRAM_STREAM_MAP: | |
| case GF_M2_STREAMID_PRIVATE_2: | |
| case GF_M2_STREAMID_ECM: | |
| case GF_M2_STREAMID_EMM: | |
| case GF_M2_STREAMID_PROGRAM_STREAM_DIRECTORY: | |
| case GF_M2_STREAMID_DSMCC: | |
| case GF_M2_STREAMID_H222_TYPE_E: | |
| has_pes_header = GF_FALSE; | |
| break; | |
| } | |
| if (has_pes_header) { | |
| /*OK read header*/ | |
| gf_m2ts_pes_header(pes, pes->pck_data + 3, pes->pck_data_len - 3, &pesh); | |
| /*send PES timing*/ | |
| if (ts->notify_pes_timing && pesh.has_pts) { | |
| GF_M2TS_PES_PCK pck; | |
| memset(&pck, 0, sizeof(GF_M2TS_PES_PCK)); | |
| pck.PTS = pesh.PTS; | |
| pck.DTS = pesh.DTS; | |
| pck.stream = pes; | |
| if (pes->rap) pck.flags |= GF_M2TS_PES_PCK_RAP; | |
| pes->pes_end_packet_number = ts->pck_number; | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_PES_TIMING, &pck); | |
| } | |
| if (pesh.has_pts) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d Got PES header DTS %u PTS %u\n", pes->pid, pesh.DTS, pesh.PTS)); | |
| if (pes->before_last_pes_start_pn && (pesh.PTS == pes->PTS)) { | |
| same_pts = GF_TRUE; | |
| if ((pes->stream_type==GF_M2TS_AUDIO_TRUEHD) || (pes->stream_type==GF_M2TS_AUDIO_EC3)) { | |
| same_pts = GF_FALSE; | |
| } else if (!pes->is_resume) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d - same PTS "LLU" for two consecutive PES packets \n", pes->pid, pes->PTS)); | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| /*FIXME - this test should only be done for non bi-directionally coded media | |
| else if (pesh.PTS < pes->PTS) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d - PTS "LLU" less than previous packet PTS "LLU"\n", pes->pid, pesh.PTS, pes->PTS) ); | |
| } | |
| */ | |
| #endif | |
| pes->PTS = pesh.PTS; | |
| #ifndef GPAC_DISABLE_LOG | |
| if (!pes->is_resume && pes->before_last_pes_start_pn && (pesh.DTS == pes->DTS)) { | |
| if ((pes->stream_type==GF_M2TS_AUDIO_TRUEHD) || (pes->stream_type==GF_M2TS_AUDIO_EC3)) { | |
| } else { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d - same DTS "LLU" for two consecutive PES packets \n", pes->pid, pes->DTS)); | |
| } | |
| } | |
| if (pesh.DTS < pes->DTS) { | |
| if (GF_M2TS_MAX_PCR_90K + pesh.DTS < pes->DTS) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d - DTS "LLU" less than previous DTS "LLU"\n", pes->pid, pesh.DTS, pes->DTS)); | |
| } | |
| } | |
| #endif | |
| pes->DTS = pesh.DTS; | |
| } | |
| /*no PTSs were coded, same time*/ | |
| else { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d Got PES header no timestamps\n", pes->pid)); | |
| if (!pesh.hdr_data_len) | |
| same_pts = GF_TRUE; | |
| } | |
| pes->is_resume = GF_FALSE; | |
| /*3-byte start-code + 6 bytes header + hdr extensions*/ | |
| len = 9 + pesh.hdr_data_len; | |
| } else { | |
| if (!has_data) goto exit; | |
| /*3-byte start-code + 1 byte streamid*/ | |
| len = 4; | |
| memset(&pesh, 0, sizeof(pesh)); | |
| } | |
| if ((u8) pes->pck_data[3]==0xfa) { | |
| GF_M2TS_SL_PCK sl_pck; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] SL Packet in PES for %d - ES ID %d\n", pes->pid, pes->mpeg4_es_id)); | |
| if (pes->pck_data_len > len) { | |
| sl_pck.data = (char *)pes->pck_data + len; | |
| sl_pck.data_len = pes->pck_data_len - len; | |
| sl_pck.stream = (GF_M2TS_ES *)pes; | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_SL_PCK, &sl_pck); | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] Bad SL Packet size: (%d indicated < %d header)\n", pes->pid, pes->pck_data_len, len)); | |
| } | |
| goto exit; | |
| } | |
| if (pes->reframe) { | |
| u32 remain = 0; | |
| u32 offset = len; | |
| if (pesh.pck_len && (pesh.pck_len-3-pesh.hdr_data_len != pes->pck_data_len-len)) { | |
| if (!force_flush_type) { | |
| pes->is_resume = GF_TRUE; | |
| return; | |
| } | |
| if (force_flush_type==1) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d PES payload size %d but received %d bytes\n", pes->pid, (u32) ( pesh.pck_len-3-pesh.hdr_data_len), pes->pck_data_len-len)); | |
| } | |
| } | |
| //copy over the remaining of previous PES payload before start of this PES payload | |
| if (pes->prev_data_len) { | |
| if (pes->prev_data_len < len) { | |
| offset = len - pes->prev_data_len; | |
| memcpy(pes->pck_data + offset, pes->prev_data, pes->prev_data_len); | |
| } else { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d PES reassembly buffer overflow (%d bytes not processed from previous PES) - discarding prev data\n", pes->pid, pes->prev_data_len )); | |
| } | |
| } | |
| if (pes->temi_tc_desc_len && !pes->temi_pending) { | |
| gf_m2ts_store_temi(ts, pes); | |
| } | |
| if (pes->temi_pending) { | |
| pes->temi_pending = GF_FALSE; | |
| pes->temi_tc.pes_pts = pes->PTS; | |
| pes->temi_tc.pid = pes->pid; | |
| if (ts->on_event) | |
| ts->on_event(ts, GF_M2TS_EVT_TEMI_TIMECODE, &pes->temi_tc); | |
| } | |
| if (!ts->seek_mode && pes->pck_data_len > offset) { | |
| remain = pes->reframe(ts, pes, same_pts, pes->pck_data+offset, pes->pck_data_len-offset, &pesh); | |
| } | |
| //CLEANUP alloc stuff | |
| if (pes->prev_data) gf_free(pes->prev_data); | |
| pes->prev_data = NULL; | |
| pes->prev_data_len = 0; | |
| if (remain) { | |
| pes->prev_data = gf_malloc(sizeof(char)*remain); | |
| if (pes->pck_data_len >= remain) | |
| memcpy(pes->prev_data, pes->pck_data + pes->pck_data_len - remain, remain); | |
| pes->prev_data_len = remain; | |
| } | |
| } | |
| } else if (pes->pck_data_len < 4) { | |
| if (pes->pck_data_len) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d invalid PES header size %u\n", pes->pid, pes->pck_data_len)); | |
| } | |
| } else { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d invalid PES startcode %02x%02x%02x\n", pes->pid, pes->pck_data[0], pes->pck_data[1], pes->pck_data[2])); | |
| } | |
| exit: | |
| pes->pck_data_len = 0; | |
| pes->pes_len = 0; | |
| pes->rap = 0; | |
| } | |
| static void gf_m2ts_process_pes(GF_M2TS_Demuxer *ts, GF_M2TS_PES *pes, GF_M2TS_Header *hdr, unsigned char *data, u32 data_size, GF_M2TS_AdaptationField *paf) | |
| { | |
| u8 expect_cc; | |
| Bool disc=0; | |
| Bool flush_pes = 0; | |
| /*duplicated packet, NOT A DISCONTINUITY, we should discard the packet - however we may encounter this configuration in DASH at segment boundaries. | |
| If payload start is set, ignore duplication*/ | |
| if (hdr->continuity_counter==pes->cc) { | |
| if (!hdr->payload_start || (hdr->adaptation_field!=3) ) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[MPEG-2 TS] PES %d: Duplicated Packet found (CC %d) - skipping\n", pes->pid, pes->cc)); | |
| return; | |
| } | |
| } else { | |
| expect_cc = (pes->cc<0) ? hdr->continuity_counter : (pes->cc + 1) & 0xf; | |
| if (expect_cc != hdr->continuity_counter) | |
| disc = 1; | |
| } | |
| pes->cc = hdr->continuity_counter; | |
| if (disc) { | |
| if (pes->flags & GF_M2TS_ES_IGNORE_NEXT_DISCONTINUITY) { | |
| pes->flags &= ~GF_M2TS_ES_IGNORE_NEXT_DISCONTINUITY; | |
| disc = 0; | |
| } | |
| if (disc) { | |
| if (hdr->payload_start) { | |
| if (pes->pck_data_len) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PES %d: Packet discontinuity (%d expected - got %d) - may have lost end of previous PES\n", pes->pid, expect_cc, hdr->continuity_counter)); | |
| } | |
| } else { | |
| if (pes->pck_data_len) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] PES %d: Packet discontinuity (%d expected - got %d) - trashing PES packet\n", pes->pid, expect_cc, hdr->continuity_counter)); | |
| } | |
| pes->pck_data_len = 0; | |
| pes->pes_len = 0; | |
| pes->cc = -1; | |
| return; | |
| } | |
| } | |
| } | |
| if (!pes->reframe) return; | |
| if (hdr->payload_start) { | |
| flush_pes = 1; | |
| pes->before_last_pat_pn = pes->last_pat_packet_number; | |
| pes->before_last_pes_start_pn = pes->pes_start_packet_number; | |
| pes->pes_start_packet_number = ts->pck_number; | |
| pes->before_last_pcr_value = pes->program->before_last_pcr_value; | |
| pes->before_last_pcr_value_pck_number = pes->program->before_last_pcr_value_pck_number; | |
| pes->last_pcr_value = pes->program->last_pcr_value; | |
| pes->last_pcr_value_pck_number = pes->program->last_pcr_value_pck_number; | |
| pes->last_pat_packet_number = ts->last_pat_start_num; | |
| } else if (pes->pes_len && (pes->pck_data_len + data_size == pes->pes_len + 6)) { | |
| /* 6 = startcode+stream_id+length*/ | |
| /*reassemble pes*/ | |
| if (pes->pck_data_len + data_size > pes->pck_alloc_len) { | |
| pes->pck_alloc_len = pes->pck_data_len + data_size; | |
| pes->pck_data = (u8*)gf_realloc(pes->pck_data, pes->pck_alloc_len); | |
| } | |
| memcpy(pes->pck_data+pes->pck_data_len, data, data_size); | |
| pes->pck_data_len += data_size; | |
| /*force discard*/ | |
| data_size = 0; | |
| flush_pes = 1; | |
| } | |
| /*PES first fragment: flush previous packet*/ | |
| if (flush_pes && pes->pck_data_len) { | |
| gf_m2ts_flush_pes(ts, pes, 1); | |
| if (!data_size) return; | |
| } | |
| /*we need to wait for first packet of PES*/ | |
| if (!pes->pck_data_len && !hdr->payload_start) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Waiting for PES header, trashing data\n", hdr->pid)); | |
| return; | |
| } | |
| /*reassemble*/ | |
| if (pes->pck_data_len + data_size > pes->pck_alloc_len ) { | |
| pes->pck_alloc_len = pes->pck_data_len + data_size; | |
| pes->pck_data = (u8*)gf_realloc(pes->pck_data, pes->pck_alloc_len); | |
| } | |
| memcpy(pes->pck_data + pes->pck_data_len, data, data_size); | |
| pes->pck_data_len += data_size; | |
| if (paf && paf->random_access_indicator) pes->rap = 1; | |
| if (hdr->payload_start && !pes->pes_len && (pes->pck_data_len>=6)) { | |
| pes->pes_len = (pes->pck_data[4]<<8) | pes->pck_data[5]; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Got PES packet len %d\n", pes->pid, pes->pes_len)); | |
| if (pes->pes_len + 6 == pes->pck_data_len) { | |
| gf_m2ts_flush_pes(ts, pes, 1); | |
| } | |
| } | |
| } | |
| void gf_m2ts_flush_all(GF_M2TS_Demuxer *ts, Bool no_force_flush) | |
| { | |
| u32 i; | |
| for (i=0; i<GF_M2TS_MAX_STREAMS; i++) { | |
| GF_M2TS_ES *stream = ts->ess[i]; | |
| if (stream && (stream->flags & GF_M2TS_ES_IS_PES)) { | |
| gf_m2ts_flush_pes(ts, (GF_M2TS_PES *) stream, no_force_flush ? 0 : 2); | |
| } | |
| } | |
| } | |
| static void gf_m2ts_get_adaptation_field(GF_M2TS_Demuxer *ts, GF_M2TS_AdaptationField *paf, u8 *data, u32 size, u32 pid) | |
| { | |
| unsigned char *af_extension; | |
| paf->discontinuity_indicator = (data[0] & 0x80) ? 1 : 0; | |
| paf->random_access_indicator = (data[0] & 0x40) ? 1 : 0; | |
| paf->priority_indicator = (data[0] & 0x20) ? 1 : 0; | |
| paf->PCR_flag = (data[0] & 0x10) ? 1 : 0; | |
| paf->OPCR_flag = (data[0] & 0x8) ? 1 : 0; | |
| paf->splicing_point_flag = (data[0] & 0x4) ? 1 : 0; | |
| paf->transport_private_data_flag = (data[0] & 0x2) ? 1 : 0; | |
| paf->adaptation_field_extension_flag = (data[0] & 0x1) ? 1 : 0; | |
| af_extension = data + 1; | |
| if (paf->PCR_flag == 1) { | |
| u32 base = ((u32)data[1] << 24) | ((u32)data[2] << 16) | ((u32)data[3] << 8) | (u32) data[4]; | |
| u64 PCR = (u64) base; | |
| paf->PCR_base = (PCR << 1) | (data[5] >> 7); | |
| paf->PCR_ext = ((data[5] & 1) << 8) | data[6]; | |
| af_extension += 6; | |
| } | |
| if (paf->adaptation_field_extension_flag) { | |
| u32 afext_bytes; | |
| Bool ltw_flag, pwr_flag, seamless_flag, af_desc_not_present; | |
| if (paf->OPCR_flag) { | |
| af_extension += 6; | |
| } | |
| if (paf->splicing_point_flag) { | |
| af_extension += 1; | |
| } | |
| if (paf->transport_private_data_flag) { | |
| u32 priv_bytes = af_extension[0]; | |
| af_extension += 1 + priv_bytes; | |
| } | |
| //we need the next 2 bytes | |
| if ((u32)(af_extension-data) >= size-1) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Bad Adaptation Extension found\n", pid)); | |
| return; | |
| } | |
| afext_bytes = af_extension[0]; | |
| ltw_flag = (af_extension[1] & 0x80) ? 1 : 0; | |
| pwr_flag = (af_extension[1] & 0x40) ? 1 : 0; | |
| seamless_flag = (af_extension[1] & 0x20) ? 1 : 0; | |
| af_desc_not_present = (af_extension[1] & 0x10) ? 1 : 0; | |
| af_extension += 2; | |
| if (!afext_bytes) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Bad Adaptation Extension found\n", pid)); | |
| return; | |
| } | |
| afext_bytes-=1; | |
| if (ltw_flag) { | |
| af_extension += 2; | |
| if (afext_bytes<2) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Bad Adaptation Extension found\n", pid)); | |
| return; | |
| } | |
| afext_bytes-=2; | |
| } | |
| if (pwr_flag) { | |
| af_extension += 3; | |
| if (afext_bytes<3) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Bad Adaptation Extension found\n", pid)); | |
| return; | |
| } | |
| afext_bytes-=3; | |
| } | |
| if (seamless_flag) { | |
| af_extension += 3; | |
| if (afext_bytes<3) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Bad Adaptation Extension found\n", pid)); | |
| return; | |
| } | |
| afext_bytes-=3; | |
| } | |
| if (! af_desc_not_present) { | |
| if (af_extension + afext_bytes > data+size) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Bad Adaptation Extension found\n", pid)); | |
| return; | |
| } | |
| while (afext_bytes) { | |
| GF_BitStream *bs; | |
| char *desc; | |
| u8 desc_tag = af_extension[0]; | |
| u8 desc_len = af_extension[1]; | |
| if (!desc_len || (u32) desc_len+2 > afext_bytes) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Bad Adaptation Descriptor found (tag %d) size is %d but only %d bytes available\n", pid, desc_tag, desc_len, afext_bytes)); | |
| break; | |
| } | |
| desc = (char *) af_extension+2; | |
| bs = gf_bs_new(desc, desc_len, GF_BITSTREAM_READ); | |
| switch (desc_tag) { | |
| case GF_M2TS_AFDESC_LOCATION_DESCRIPTOR: | |
| { | |
| Bool use_base_temi_url; | |
| char URL[255]; | |
| GF_M2TS_TemiLocationDescriptor temi_loc; | |
| memset(&temi_loc, 0, sizeof(GF_M2TS_TemiLocationDescriptor) ); | |
| temi_loc.pid = pid; | |
| temi_loc.reload_external = gf_bs_read_int(bs, 1); | |
| temi_loc.is_announce = gf_bs_read_int(bs, 1); | |
| temi_loc.is_splicing = gf_bs_read_int(bs, 1); | |
| use_base_temi_url = gf_bs_read_int(bs, 1); | |
| gf_bs_read_int(bs, 5); //reserved | |
| temi_loc.timeline_id = gf_bs_read_int(bs, 7); | |
| if (temi_loc.is_announce) { | |
| temi_loc.activation_countdown.den = gf_bs_read_u32(bs); | |
| temi_loc.activation_countdown.num = gf_bs_read_u32(bs); | |
| } | |
| if (!use_base_temi_url) { | |
| char *_url = URL; | |
| u8 scheme = gf_bs_read_int(bs, 8); | |
| u8 url_len = gf_bs_read_int(bs, 8); | |
| if (url_len + 4 > desc_len) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Invalid AF Location descriptor (size=%u) found (scheme=%u, url_len=%u))\n", pid, desc_len, scheme, url_len)); | |
| break; | |
| } | |
| u8 scheme_len = 0; | |
| switch (scheme) { | |
| case 1: | |
| gf_strcpy(URL, "http://"); | |
| scheme_len = 7; | |
| break; | |
| case 2: | |
| gf_strcpy(URL, "https://"); | |
| scheme_len = 8; | |
| break; | |
| } | |
| _url = URL + scheme_len; | |
| gf_bs_read_data(bs, _url, url_len); | |
| _url[MIN(url_len, GF_ARRAY_LENGTH(URL)-1-scheme_len)] = 0; | |
| } | |
| temi_loc.external_URL = URL; | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d AF Location descriptor found - URL %s\n", pid, URL)); | |
| if (ts->on_event) ts->on_event(ts, GF_M2TS_EVT_TEMI_LOCATION, &temi_loc); | |
| } | |
| break; | |
| case GF_M2TS_AFDESC_TIMELINE_DESCRIPTOR: | |
| if (ts->ess[pid] && (ts->ess[pid]->flags & GF_M2TS_ES_IS_PES)) { | |
| GF_M2TS_PES *pes = (GF_M2TS_PES *) ts->ess[pid]; | |
| if (pes->temi_tc_desc_len) | |
| gf_m2ts_store_temi(ts, pes); | |
| if (pes->temi_tc_desc_alloc_size < desc_len) { | |
| pes->temi_tc_desc = gf_realloc(pes->temi_tc_desc, desc_len); | |
| pes->temi_tc_desc_alloc_size = desc_len; | |
| } | |
| memcpy(pes->temi_tc_desc, desc, desc_len); | |
| pes->temi_tc_desc_len = desc_len; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d AF Timeline descriptor found\n", pid)); | |
| } | |
| break; | |
| } | |
| gf_bs_del(bs); | |
| af_extension += 2+desc_len; | |
| afext_bytes -= 2+desc_len; | |
| } | |
| } | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d: Adaptation Field found: Discontinuity %d - RAP %d - PCR: "LLD"\n", pid, paf->discontinuity_indicator, paf->random_access_indicator, paf->PCR_flag ? paf->PCR_base * 300 + paf->PCR_ext : 0)); | |
| } | |
| static void gf_m2ts_reset_parsers_for_program_ex(GF_M2TS_Demuxer *ts, GF_M2TS_Program *prog, Bool keep_pcr); | |
| static GF_Err gf_m2ts_process_packet(GF_M2TS_Demuxer *ts, unsigned char *data) | |
| { | |
| GF_M2TS_ES *es; | |
| GF_M2TS_Header hdr; | |
| GF_M2TS_AdaptationField af, *paf; | |
| u32 payload_size, af_size; | |
| u32 pos = 0; | |
| ts->pck_number++; | |
| /* read TS packet header*/ | |
| hdr.sync = data[0]; | |
| if (hdr.sync != 0x47) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] TS Packet %d does not start with sync marker\n", ts->pck_number)); | |
| if (ts->raw_mode == GF_M2TS_RAW_PROBE) | |
| ts->pck_errors++; | |
| else | |
| ts->pck_number--; | |
| return GF_CORRUPTED_DATA; | |
| } | |
| hdr.error = (data[1] & 0x80) ? 1 : 0; | |
| hdr.payload_start = (data[1] & 0x40) ? 1 : 0; | |
| hdr.priority = (data[1] & 0x20) ? 1 : 0; | |
| hdr.pid = ( (data[1]&0x1f) << 8) | data[2]; | |
| hdr.scrambling_ctrl = (data[3] >> 6) & 0x3; | |
| hdr.adaptation_field = (data[3] >> 4) & 0x3; | |
| hdr.continuity_counter = data[3] & 0xf; | |
| if (hdr.error) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] TS Packet %d has error (PID could be %d)\n", ts->pck_number, hdr.pid)); | |
| ts->pck_errors++; | |
| return GF_CORRUPTED_DATA; | |
| } | |
| //#if DEBUG_TS_PACKET | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] TS Packet %d PID %d CC %d Encrypted %d\n", ts->pck_number, hdr.pid, hdr.continuity_counter, hdr.scrambling_ctrl)); | |
| //#endif | |
| if (hdr.scrambling_ctrl) { | |
| if (ts->raw_mode==GF_M2TS_RAW_FORWARD) { | |
| GF_M2TS_TSPCK tspck; | |
| memset(&tspck, 0, sizeof(GF_M2TS_TSPCK)); | |
| tspck.data = data - pos; | |
| ts->on_event(ts, GF_M2TS_EVT_PCK, &tspck); | |
| return GF_OK; | |
| } | |
| //TODO add decyphering API ? | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] TS Packet %d is scrambled - not supported\n", ts->pck_number, hdr.pid)); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| paf = NULL; | |
| payload_size = 184; | |
| pos = 4; | |
| switch (hdr.adaptation_field) { | |
| /*adaptation+data*/ | |
| case 3: | |
| af_size = data[4]; | |
| if (af_size>183) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MPEG-2 TS] TS Packet %d AF field larger than 183 for AF type 3!\n", ts->pck_number)); | |
| //error | |
| ts->pck_errors++; | |
| return GF_CORRUPTED_DATA; | |
| } | |
| if (ts->raw_mode==GF_M2TS_RAW_PROBE) return GF_OK; | |
| paf = ⁡ | |
| memset(paf, 0, sizeof(GF_M2TS_AdaptationField)); | |
| if (af_size) gf_m2ts_get_adaptation_field(ts, paf, data+5, af_size, hdr.pid); | |
| pos += 1+af_size; | |
| payload_size = 183 - af_size; | |
| break; | |
| /*adaptation only - still process in case of PCR*/ | |
| case 2: | |
| af_size = data[4]; | |
| if (af_size != 183) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] TS Packet %d AF size is %d when it must be 183 for AF type 2\n", ts->pck_number, af_size)); | |
| ts->pck_errors++; | |
| return GF_CORRUPTED_DATA; | |
| } | |
| if (ts->raw_mode==GF_M2TS_RAW_PROBE) return GF_OK; | |
| paf = ⁡ | |
| memset(paf, 0, sizeof(GF_M2TS_AdaptationField)); | |
| gf_m2ts_get_adaptation_field(ts, paf, data+5, af_size, hdr.pid); | |
| payload_size = 0; | |
| /*no payload and no PCR, return*/ | |
| if (!paf->PCR_flag) | |
| return GF_OK; | |
| break; | |
| /*reserved*/ | |
| case 0: | |
| return GF_OK; | |
| default: | |
| if (ts->raw_mode==GF_M2TS_RAW_PROBE) return GF_OK; | |
| break; | |
| } | |
| data += pos; | |
| /*PAT*/ | |
| if (hdr.pid == GF_M2TS_PID_PAT) { | |
| if (ts->raw_mode==GF_M2TS_RAW_FORWARD) { | |
| GF_M2TS_TSPCK tspck; | |
| memset(&tspck, 0, sizeof(GF_M2TS_TSPCK)); | |
| tspck.data = data - pos; | |
| ts->on_event(ts, GF_M2TS_EVT_PCK, &tspck); | |
| return GF_OK; | |
| } | |
| gf_m2ts_gather_section(ts, ts->pat, NULL, &hdr, data, payload_size); | |
| return GF_OK; | |
| } | |
| es = ts->ess[hdr.pid]; | |
| //we work in split mode | |
| if (ts->raw_mode) { | |
| if (ts->raw_mode==GF_M2TS_RAW_PROBE) return GF_OK; | |
| GF_M2TS_TSPCK tspck; | |
| //process PMT table | |
| if (es && (es->flags & GF_M2TS_ES_IS_PMT)) { | |
| GF_M2TS_SECTION_ES *ses = (GF_M2TS_SECTION_ES *)es; | |
| if (ses->sec) gf_m2ts_gather_section(ts, ses->sec, ses, &hdr, data, payload_size); | |
| } | |
| //and forward every packet other than PAT | |
| memset(&tspck, 0, sizeof(GF_M2TS_TSPCK)); | |
| tspck.stream = es; | |
| tspck.pid = hdr.pid; | |
| tspck.data = data - pos; | |
| if (paf && paf->PCR_flag) { | |
| tspck.pcr_plus_one = paf->PCR_base * 300 + paf->PCR_ext; | |
| } | |
| ts->on_event(ts, GF_M2TS_EVT_PCK, &tspck); | |
| return GF_OK; | |
| } | |
| if (hdr.pid == GF_M2TS_PID_CAT) { | |
| gf_m2ts_gather_section(ts, ts->cat, NULL, &hdr, data, payload_size); | |
| return GF_OK; | |
| } | |
| if (paf && paf->PCR_flag) { | |
| if (!es) { | |
| u32 i, j; | |
| for(i=0; i<gf_list_count(ts->programs); i++) { | |
| GF_M2TS_PES *first_pes = NULL; | |
| GF_M2TS_Program *program = (GF_M2TS_Program *)gf_list_get(ts->programs,i); | |
| if(program->pcr_pid != hdr.pid) continue; | |
| for (j=0; j<gf_list_count(program->streams); j++) { | |
| GF_M2TS_PES *pes = (GF_M2TS_PES *) gf_list_get(program->streams, j); | |
| if (pes->flags & GF_M2TS_INHERIT_PCR) { | |
| ts->ess[hdr.pid] = (GF_M2TS_ES *) pes; | |
| pes->flags |= GF_M2TS_FAKE_PCR | GF_M2TS_ES_IS_PCR_REUSE; | |
| break; | |
| } | |
| if (pes->flags & GF_M2TS_ES_IS_PES) { | |
| first_pes = pes; | |
| } | |
| } | |
| //non found, use the first media stream as a PCR destination - Q: is it legal to have PCR only streams not declared in PMT ? | |
| if (!es && first_pes) { | |
| es = (GF_M2TS_ES *) first_pes; | |
| first_pes->flags |= GF_M2TS_FAKE_PCR; | |
| } | |
| break; | |
| } | |
| if (!es) | |
| es = ts->ess[hdr.pid]; | |
| } | |
| if (es) { | |
| GF_M2TS_PES_PCK pck; | |
| s64 prev_diff_in_us; | |
| Bool discontinuity; | |
| s32 cc = -1; | |
| if (es->flags & GF_M2TS_FAKE_PCR) { | |
| cc = es->program->pcr_cc; | |
| es->program->pcr_cc = hdr.continuity_counter; | |
| } | |
| else if (es->flags & GF_M2TS_ES_IS_PES) cc = ((GF_M2TS_PES*)es)->cc; | |
| else if (((GF_M2TS_SECTION_ES*)es)->sec) cc = ((GF_M2TS_SECTION_ES*)es)->sec->cc; | |
| discontinuity = paf->discontinuity_indicator; | |
| if ((cc>=0) && es->program->before_last_pcr_value) { | |
| //no increment of CC if AF only packet | |
| if (hdr.adaptation_field == 2) { | |
| if (hdr.continuity_counter != cc) { | |
| discontinuity = GF_TRUE; | |
| } | |
| } else if (hdr.continuity_counter != ((cc + 1) & 0xF)) { | |
| discontinuity = GF_TRUE; | |
| } | |
| } | |
| s64 pcr_diff=0; | |
| memset(&pck, 0, sizeof(GF_M2TS_PES_PCK)); | |
| prev_diff_in_us = (s64) (es->program->last_pcr_value - es->program->before_last_pcr_value)/27; | |
| es->program->before_last_pcr_value = es->program->last_pcr_value; | |
| es->program->before_last_pcr_value_pck_number = es->program->last_pcr_value_pck_number; | |
| es->program->last_pcr_value_pck_number = ts->pck_number; | |
| es->program->last_pcr_value = paf->PCR_base * 300 + paf->PCR_ext; | |
| if (!es->program->last_pcr_value) es->program->last_pcr_value = 1; | |
| if (es->program->before_last_pcr_value) { | |
| pcr_diff = es->program->last_pcr_value; | |
| pcr_diff -= es->program->before_last_pcr_value; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d PCR found "LLU" ("LLU" at 90kHz) - PCR diff is %d us\n", hdr.pid, es->program->last_pcr_value, es->program->last_pcr_value/300, (s32) (pcr_diff)/27 )); | |
| } else { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d PCR found "LLU" ("LLU" at 90kHz)\n", hdr.pid, es->program->last_pcr_value, es->program->last_pcr_value/300)); | |
| } | |
| pck.PTS = es->program->last_pcr_value; | |
| pck.stream = (GF_M2TS_PES *)es; | |
| //try to ignore all discontinuities that are less than 200 ms (seen in some HLS setup ...) | |
| Bool adjust_pcr=GF_FALSE; | |
| if (discontinuity) { | |
| s64 diff_in_us = (s64) (es->program->last_pcr_value - es->program->before_last_pcr_value) / 27; | |
| u64 diff = ABS(diff_in_us - prev_diff_in_us); | |
| if ((diff_in_us<0) && (diff_in_us >= -200000)) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d new PCR, with discontinuity signaled, is less than previously received PCR (diff %d us) but not too large, trying to ignore discontinuity\n", hdr.pid, diff_in_us)); | |
| } | |
| //ignore PCR discontinuity indicator if PCR found is larger than previously received PCR and diffence between PCR before and after discontinuity indicator is smaller than 50ms | |
| else if ((diff_in_us > 0) && (diff < 200000)) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d PCR discontinuity signaled but diff is small (diff %d us - PCR diff %d vs prev PCR diff %d) - ignore it\n", hdr.pid, diff, diff_in_us, prev_diff_in_us)); | |
| } else if (paf->discontinuity_indicator) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d PCR discontinuity signaled (diff %d us - PCR diff %d vs prev PCR diff %d)\n", hdr.pid, diff, diff_in_us, prev_diff_in_us)); | |
| pck.flags = GF_M2TS_PES_PCK_DISCONTINUITY; | |
| } else { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d PCR discontinuity not signaled (diff %d us - PCR diff %d vs prev PCR diff %d)\n", hdr.pid, diff, diff_in_us, prev_diff_in_us)); | |
| pck.flags = GF_M2TS_PES_PCK_DISCONTINUITY; | |
| } | |
| adjust_pcr = GF_TRUE; | |
| } | |
| else if ((es->flags & GF_M2TS_CHECK_DISC) && ABS(pcr_diff)>270000000 ) { | |
| s64 diff_in_us = (s64) (pcr_diff) / 27; | |
| //if less than 200 ms before PCR loop at the last PCR, this is a PCR loop | |
| if (GF_M2TS_MAX_PCR - es->program->before_last_pcr_value < 5400000 /*2*2700000*/) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d PCR loop found from "LLU" to "LLU" \n", hdr.pid, es->program->before_last_pcr_value, es->program->last_pcr_value)); | |
| es->program->pcr_base_offset += GF_M2TS_MAX_PCR; | |
| //do not adjust PCR | |
| } else if ((diff_in_us<0) && (diff_in_us >= -200000)) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d new PCR, without discontinuity signaled, is less than previously received PCR (diff %d us) but not too large, trying to ignore discontinuity\n", hdr.pid, diff_in_us)); | |
| adjust_pcr = GF_TRUE; | |
| } else { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MPEG-2 TS] PID %d PCR found "LLU" is less than previously received PCR "LLU" (PCR diff %g sec) but no discontinuity signaled\n", hdr.pid, es->program->last_pcr_value, es->program->before_last_pcr_value, (GF_M2TS_MAX_PCR - es->program->before_last_pcr_value + es->program->last_pcr_value) / 27000000.0)); | |
| pck.flags = GF_M2TS_PES_PCK_DISCONTINUITY; | |
| adjust_pcr = GF_TRUE; | |
| } | |
| } | |
| //adjust pcr_base_offset to absorb the discontinuity | |
| if (adjust_pcr) { | |
| s64 pcr_base_offset_delta = es->program->before_last_pcr_value + prev_diff_in_us*27; | |
| pcr_base_offset_delta -= es->program->last_pcr_value; | |
| es->program->pcr_base_offset += pcr_base_offset_delta; | |
| } | |
| if (pck.flags & GF_M2TS_PES_PCK_DISCONTINUITY) { | |
| gf_m2ts_reset_parsers_for_program_ex(ts, es->program, GF_TRUE); | |
| } | |
| if (ts->on_event) { | |
| ts->on_event(ts, GF_M2TS_EVT_PES_PCR, &pck); | |
| } | |
| } | |
| } | |
| /*check for DVB reserved PIDs*/ | |
| if (!es) { | |
| if (hdr.pid == GF_M2TS_PID_SDT_BAT_ST) { | |
| gf_m2ts_gather_section(ts, ts->sdt, NULL, &hdr, data, payload_size); | |
| return GF_OK; | |
| } else if (hdr.pid == GF_M2TS_PID_NIT_ST) { | |
| /*ignore them, unused at application level*/ | |
| gf_m2ts_gather_section(ts, ts->nit, NULL, &hdr, data, payload_size); | |
| return GF_OK; | |
| } else if (hdr.pid == GF_M2TS_PID_EIT_ST_CIT) { | |
| /* ignore EIT messages for the moment */ | |
| gf_m2ts_gather_section(ts, ts->eit, NULL, &hdr, data, payload_size); | |
| return GF_OK; | |
| } else if (hdr.pid == GF_M2TS_PID_TDT_TOT_ST) { | |
| gf_m2ts_gather_section(ts, ts->tdt_tot, NULL, &hdr, data, payload_size); | |
| } else { | |
| /* ignore packet */ | |
| } | |
| } else if (es->flags & GF_M2TS_ES_IS_SECTION) { /* The stream uses sections to carry its payload */ | |
| GF_M2TS_SECTION_ES *ses = (GF_M2TS_SECTION_ES *)es; | |
| if (ses->sec) gf_m2ts_gather_section(ts, ses->sec, ses, &hdr, data, payload_size); | |
| } else { | |
| GF_M2TS_PES *pes = (GF_M2TS_PES *)es; | |
| /* regular stream using PES packets */ | |
| if (pes->reframe && payload_size) gf_m2ts_process_pes(ts, pes, &hdr, data, payload_size, paf); | |
| } | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_m2ts_process_data(GF_M2TS_Demuxer *ts, u8 *data, u32 data_size) | |
| { | |
| GF_Err e=GF_OK; | |
| u32 pos, pck_size; | |
| Bool is_align = 1; | |
| if (ts->buffer_size) { | |
| //we are sync, copy remaining bytes | |
| if ( (ts->buffer[0]==0x47) && (ts->buffer_size<200)) { | |
| u32 copy_size; | |
| pck_size = ts->prefix_present ? 192 : 188; | |
| if (ts->alloc_size < 200) { | |
| ts->alloc_size = 200; | |
| ts->buffer = (char*)gf_realloc(ts->buffer, sizeof(char)*ts->alloc_size); | |
| } | |
| copy_size = pck_size - ts->buffer_size; | |
| if (copy_size > data_size) { | |
| memcpy(ts->buffer + ts->buffer_size, data, data_size); | |
| ts->buffer_size += data_size; | |
| return GF_OK; | |
| } | |
| memcpy(ts->buffer + ts->buffer_size, data, copy_size); | |
| e |= gf_m2ts_process_packet(ts, (unsigned char *)ts->buffer); | |
| gf_assert(data_size >= copy_size); | |
| data += copy_size; | |
| data_size = data_size - copy_size; | |
| } | |
| //not sync, copy over the complete buffer | |
| else { | |
| if (ts->alloc_size < ts->buffer_size+data_size) { | |
| ts->alloc_size = ts->buffer_size+data_size; | |
| ts->buffer = (char*)gf_realloc(ts->buffer, sizeof(char)*ts->alloc_size); | |
| } | |
| memcpy(ts->buffer + ts->buffer_size, data, sizeof(char)*data_size); | |
| ts->buffer_size += data_size; | |
| is_align = 0; | |
| data = ts->buffer; | |
| data_size = ts->buffer_size; | |
| } | |
| } | |
| /*sync input data*/ | |
| pos = gf_m2ts_sync(ts, data, data_size, is_align); | |
| if (pos==data_size) { | |
| if (is_align && data_size) { | |
| if (ts->alloc_size<data_size) { | |
| ts->buffer = (char*)gf_realloc(ts->buffer, sizeof(char)*data_size); | |
| ts->alloc_size = data_size; | |
| } | |
| memcpy(ts->buffer, data, sizeof(char)*data_size); | |
| ts->buffer_size = data_size; | |
| } | |
| return GF_OK; | |
| } | |
| pck_size = ts->prefix_present ? 192 : 188; | |
| for (;;) { | |
| /*wait for a complete packet*/ | |
| if (data_size < pos + pck_size) { | |
| ts->buffer_size = data_size - pos; | |
| data += pos; | |
| if (!ts->buffer_size) { | |
| return e; | |
| } | |
| gf_assert(ts->buffer_size<pck_size); | |
| if (is_align) { | |
| u32 s = ts->buffer_size; | |
| if (s<200) s = 200; | |
| if (ts->alloc_size < s) { | |
| ts->alloc_size = s; | |
| ts->buffer = (char*)gf_realloc(ts->buffer, sizeof(char)*ts->alloc_size); | |
| } | |
| memcpy(ts->buffer, data, sizeof(char)*ts->buffer_size); | |
| } else { | |
| memmove(ts->buffer, data, sizeof(char)*ts->buffer_size); | |
| } | |
| return e; | |
| } | |
| /*process*/ | |
| GF_Err pck_e = gf_m2ts_process_packet(ts, (unsigned char *)data + pos); | |
| if (pck_e==GF_NOT_SUPPORTED) pck_e = GF_OK; | |
| e |= pck_e; | |
| pos += pck_size; | |
| } | |
| return e; | |
| } | |
| //unused | |
| #if 0 | |
| GF_ESD *gf_m2ts_get_esd(GF_M2TS_ES *es) | |
| { | |
| GF_ESD *esd; | |
| u32 k, esd_count; | |
| esd = NULL; | |
| if (es->program->pmt_iod && es->program->pmt_iod->ESDescriptors) { | |
| esd_count = gf_list_count(es->program->pmt_iod->ESDescriptors); | |
| for (k = 0; k < esd_count; k++) { | |
| GF_ESD *esd_tmp = (GF_ESD *)gf_list_get(es->program->pmt_iod->ESDescriptors, k); | |
| if (esd_tmp->ESID != es->mpeg4_es_id) continue; | |
| esd = esd_tmp; | |
| break; | |
| } | |
| } | |
| if (!esd && es->program->additional_ods) { | |
| u32 od_count, od_index; | |
| od_count = gf_list_count(es->program->additional_ods); | |
| for (od_index = 0; od_index < od_count; od_index++) { | |
| GF_ObjectDescriptor *od = (GF_ObjectDescriptor *)gf_list_get(es->program->additional_ods, od_index); | |
| esd_count = gf_list_count(od->ESDescriptors); | |
| for (k = 0; k < esd_count; k++) { | |
| GF_ESD *esd_tmp = (GF_ESD *)gf_list_get(od->ESDescriptors, k); | |
| if (esd_tmp->ESID != es->mpeg4_es_id) continue; | |
| esd = esd_tmp; | |
| break; | |
| } | |
| } | |
| } | |
| return esd; | |
| } | |
| void gf_m2ts_set_segment_switch(GF_M2TS_Demuxer *ts) | |
| { | |
| u32 i; | |
| for (i=0; i<GF_M2TS_MAX_STREAMS; i++) { | |
| GF_M2TS_ES *es = (GF_M2TS_ES *) ts->ess[i]; | |
| if (!es) continue; | |
| es->flags |= GF_M2TS_ES_IGNORE_NEXT_DISCONTINUITY; | |
| } | |
| } | |
| #endif | |
| static void gf_m2ts_reset_parsers_for_program_ex(GF_M2TS_Demuxer *ts, GF_M2TS_Program *prog, Bool keep_pcr) | |
| { | |
| u32 i; | |
| for (i=0; i<GF_M2TS_MAX_STREAMS; i++) { | |
| GF_M2TS_ES *es = (GF_M2TS_ES *) ts->ess[i]; | |
| if (!es) continue; | |
| if (prog && (es->program != prog) ) continue; | |
| if (es->flags & GF_M2TS_ES_IS_SECTION) { | |
| GF_M2TS_SECTION_ES *ses = (GF_M2TS_SECTION_ES *)es; | |
| gf_m2ts_section_filter_reset(ses->sec); | |
| } else { | |
| GF_M2TS_PES *pes = (GF_M2TS_PES *)es; | |
| if (pes->pid==pes->program->pmt_pid) continue; | |
| pes->cc = -1; | |
| pes->pck_data_len = 0; | |
| if (pes->prev_data) gf_free(pes->prev_data); | |
| pes->prev_data = NULL; | |
| pes->prev_data_len = 0; | |
| pes->PTS = pes->DTS = 0; | |
| // pes->prev_PTS = 0; | |
| // pes->first_dts = 0; | |
| pes->pes_len = pes->pes_end_packet_number = pes->pes_start_packet_number = 0; | |
| if (pes->temi_tc_desc) gf_free(pes->temi_tc_desc); | |
| pes->temi_tc_desc = NULL; | |
| pes->temi_tc_desc_len = pes->temi_tc_desc_alloc_size = 0; | |
| if (keep_pcr) continue; | |
| pes->before_last_pcr_value = pes->before_last_pcr_value_pck_number = 0; | |
| pes->last_pcr_value = pes->last_pcr_value_pck_number = 0; | |
| if (pes->program->pcr_pid==pes->pid) { | |
| pes->program->last_pcr_value = pes->program->last_pcr_value_pck_number = 0; | |
| pes->program->before_last_pcr_value = pes->program->before_last_pcr_value_pck_number = 0; | |
| pes->program->pcr_base_offset = 0; | |
| } | |
| } | |
| } | |
| } | |
| GF_EXPORT | |
| void gf_m2ts_reset_parsers_for_program(GF_M2TS_Demuxer *ts, GF_M2TS_Program *prog) | |
| { | |
| gf_m2ts_reset_parsers_for_program_ex(ts, prog, GF_FALSE); | |
| } | |
| GF_EXPORT | |
| void gf_m2ts_reset_parsers(GF_M2TS_Demuxer *ts) | |
| { | |
| gf_m2ts_reset_parsers_for_program(ts, NULL); | |
| ts->pck_number = 0; | |
| ts->buffer_size = 0; | |
| gf_m2ts_section_filter_reset(ts->cat); | |
| gf_m2ts_section_filter_reset(ts->pat); | |
| gf_m2ts_section_filter_reset(ts->sdt); | |
| gf_m2ts_section_filter_reset(ts->nit); | |
| gf_m2ts_section_filter_reset(ts->eit); | |
| gf_m2ts_section_filter_reset(ts->tdt_tot); | |
| } | |
| void gf_m2ts_mark_seg_start(GF_M2TS_Demuxer *ts) | |
| { | |
| u32 i; | |
| for (i=0; i<GF_M2TS_MAX_STREAMS; i++) { | |
| GF_M2TS_ES *es = (GF_M2TS_ES *) ts->ess[i]; | |
| if (!es) continue; | |
| if (es->flags & GF_M2TS_ES_IS_SECTION) { | |
| GF_M2TS_SECTION_ES *ses = (GF_M2TS_SECTION_ES *)es; | |
| ses->is_seg_start = GF_TRUE; | |
| } else { | |
| GF_M2TS_PES *pes = (GF_M2TS_PES *)es; | |
| pes->is_seg_start = GF_TRUE; | |
| } | |
| } | |
| } | |
| #if 0 //unused | |
| u32 gf_m2ts_pes_get_framing_mode(GF_M2TS_PES *pes) | |
| { | |
| if (pes->flags & GF_M2TS_ES_IS_SECTION) { | |
| if (pes->flags & GF_M2TS_ES_IS_SL) { | |
| if ( ((GF_M2TS_SECTION_ES *)pes)->sec->process_section == NULL) | |
| return GF_M2TS_PES_FRAMING_DEFAULT; | |
| } | |
| return GF_M2TS_PES_FRAMING_SKIP_NO_RESET; | |
| } | |
| if (!pes->reframe ) return GF_M2TS_PES_FRAMING_SKIP_NO_RESET; | |
| if (pes->reframe == gf_m2ts_reframe_default) return GF_M2TS_PES_FRAMING_RAW; | |
| if (pes->reframe == gf_m2ts_reframe_reset) return GF_M2TS_PES_FRAMING_SKIP; | |
| return GF_M2TS_PES_FRAMING_DEFAULT; | |
| } | |
| #endif | |
| GF_EXPORT | |
| GF_Err gf_m2ts_set_pes_framing(GF_M2TS_PES *pes, GF_M2TSPesFraming mode) | |
| { | |
| if (!pes) return GF_BAD_PARAM; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MPEG-2 TS] Setting pes framing mode of PID %d to %d\n", pes->pid, mode) ); | |
| /*ignore request for section PIDs*/ | |
| if (pes->flags & GF_M2TS_ES_IS_SECTION) { | |
| if (pes->flags & GF_M2TS_ES_IS_SL) { | |
| if (mode==GF_M2TS_PES_FRAMING_DEFAULT) { | |
| ((GF_M2TS_SECTION_ES *)pes)->sec->process_section = gf_m2ts_process_mpeg4section; | |
| } else { | |
| ((GF_M2TS_SECTION_ES *)pes)->sec->process_section = NULL; | |
| } | |
| } | |
| else if (mode==GF_M2TS_PES_FRAMING_DEFAULT) { | |
| ((GF_M2TS_SECTION_ES *)pes)->sec->process_section = gf_m2ts_process_generic_section; | |
| } else { | |
| ((GF_M2TS_SECTION_ES *)pes)->sec->process_section = NULL; | |
| } | |
| return GF_OK; | |
| } | |
| if (pes->pid==pes->program->pmt_pid) return GF_BAD_PARAM; | |
| //if component reuse, disable previous pes | |
| if ((mode > GF_M2TS_PES_FRAMING_SKIP) && (pes->program->ts->ess[pes->pid] != (GF_M2TS_ES *) pes)) { | |
| GF_M2TS_PES *o_pes = (GF_M2TS_PES *) pes->program->ts->ess[pes->pid]; | |
| if (o_pes->flags & GF_M2TS_ES_IS_PES) | |
| gf_m2ts_set_pes_framing(o_pes, GF_M2TS_PES_FRAMING_SKIP); | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[MPEG-2 TS] Reassinging PID %d from program %d to program %d\n", pes->pid, o_pes->program->number, pes->program->number) ); | |
| gf_m2ts_es_del((GF_M2TS_ES*)o_pes, pes->program->ts); | |
| pes->program->ts->ess[pes->pid] = (GF_M2TS_ES *) pes; | |
| } | |
| switch (mode) { | |
| case GF_M2TS_PES_FRAMING_RAW: | |
| pes->reframe = gf_m2ts_reframe_default; | |
| break; | |
| case GF_M2TS_PES_FRAMING_SKIP: | |
| pes->reframe = gf_m2ts_reframe_reset; | |
| break; | |
| case GF_M2TS_PES_FRAMING_SKIP_NO_RESET: | |
| pes->reframe = NULL; | |
| break; | |
| case GF_M2TS_PES_FRAMING_DEFAULT: | |
| default: | |
| switch (pes->stream_type) { | |
| case GF_M2TS_VIDEO_MPEG1: | |
| case GF_M2TS_VIDEO_MPEG2: | |
| case GF_M2TS_VIDEO_H264: | |
| case GF_M2TS_VIDEO_SVC: | |
| case GF_M2TS_VIDEO_HEVC: | |
| case GF_M2TS_VIDEO_HEVC_TEMPORAL: | |
| case GF_M2TS_VIDEO_HEVC_MCTS: | |
| case GF_M2TS_VIDEO_SHVC: | |
| case GF_M2TS_VIDEO_SHVC_TEMPORAL: | |
| case GF_M2TS_VIDEO_MHVC: | |
| case GF_M2TS_VIDEO_MHVC_TEMPORAL: | |
| case GF_M2TS_AUDIO_MPEG1: | |
| case GF_M2TS_AUDIO_MPEG2: | |
| case GF_M2TS_AUDIO_AAC: | |
| case GF_M2TS_AUDIO_LATM_AAC: | |
| case GF_M2TS_AUDIO_AC3: | |
| case GF_M2TS_AUDIO_EC3: | |
| case 0xA1: | |
| //for all our supported codec types, use a reframer filter | |
| pes->reframe = gf_m2ts_reframe_default; | |
| break; | |
| case GF_M2TS_PRIVATE_DATA: | |
| /* TODO: handle DVB subtitle streams */ | |
| break; | |
| case GF_M2TS_METADATA_ID3_HLS: | |
| case GF_M2TS_METADATA_ID3_KLVA: | |
| pes->reframe = gf_m2ts_reframe_add_prop; | |
| break; | |
| default: | |
| pes->reframe = gf_m2ts_reframe_default; | |
| break; | |
| } | |
| break; | |
| } | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_M2TS_Demuxer *gf_m2ts_demux_new() | |
| { | |
| GF_M2TS_Demuxer *ts; | |
| GF_SAFEALLOC(ts, GF_M2TS_Demuxer); | |
| if (!ts) return NULL; | |
| ts->programs = gf_list_new(); | |
| ts->SDTs = gf_list_new(); | |
| ts->pat = gf_m2ts_section_filter_new(gf_m2ts_process_pat, 0); | |
| ts->cat = gf_m2ts_section_filter_new(gf_m2ts_process_cat, 0); | |
| ts->sdt = gf_m2ts_section_filter_new(gf_m2ts_process_sdt, 1); | |
| ts->nit = gf_m2ts_section_filter_new(gf_m2ts_process_nit, 0); | |
| ts->eit = gf_m2ts_section_filter_new(NULL/*gf_m2ts_process_eit*/, 1); | |
| ts->tdt_tot = gf_m2ts_section_filter_new(gf_m2ts_process_tdt_tot, 1); | |
| #ifdef GPAC_ENABLE_MPE | |
| gf_dvb_mpe_init(ts); | |
| #endif | |
| ts->nb_prog_pmt_received = 0; | |
| ts->ChannelAppList = gf_list_new(); | |
| return ts; | |
| } | |
| GF_EXPORT | |
| void gf_m2ts_demux_dmscc_init(GF_M2TS_Demuxer *ts) { | |
| char temp_dir[GF_MAX_PATH]; | |
| u32 length; | |
| GF_Err e; | |
| ts->dsmcc_controler = gf_list_new(); | |
| ts->process_dmscc = 1; | |
| gf_strcpy(temp_dir, gf_get_default_cache_directory() ); | |
| length = (u32) strlen(temp_dir); | |
| if(temp_dir[length-1] == GF_PATH_SEPARATOR) { | |
| temp_dir[length-1] = 0; | |
| } | |
| ts->dsmcc_root_dir = (char*)gf_calloc(strlen(temp_dir)+strlen("CarouselData")+2,sizeof(char)); | |
| sprintf(ts->dsmcc_root_dir,"%s%cCarouselData",temp_dir,GF_PATH_SEPARATOR); | |
| e = gf_mkdir(ts->dsmcc_root_dir); | |
| if(e) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[Process DSMCC] Error during the creation of the directory %s \n",ts->dsmcc_root_dir)); | |
| } | |
| } | |
| GF_EXPORT | |
| void gf_m2ts_demux_del(GF_M2TS_Demuxer *ts) | |
| { | |
| u32 i; | |
| if (ts->pat) gf_m2ts_section_filter_del(ts->pat); | |
| if (ts->cat) gf_m2ts_section_filter_del(ts->cat); | |
| if (ts->sdt) gf_m2ts_section_filter_del(ts->sdt); | |
| if (ts->nit) gf_m2ts_section_filter_del(ts->nit); | |
| if (ts->eit) gf_m2ts_section_filter_del(ts->eit); | |
| if (ts->tdt_tot) gf_m2ts_section_filter_del(ts->tdt_tot); | |
| for (i=0; i<GF_M2TS_MAX_STREAMS; i++) { | |
| //because of pure PCR streams, an ES might be reassigned on 2 PIDs, one for the ES and one for the PCR | |
| if (ts->ess[i] && (ts->ess[i]->pid==i)) { | |
| gf_m2ts_es_del(ts->ess[i], ts); | |
| } | |
| } | |
| if (ts->buffer) gf_free(ts->buffer); | |
| while (gf_list_count(ts->programs)) { | |
| GF_M2TS_Program *p = (GF_M2TS_Program *)gf_list_last(ts->programs); | |
| gf_list_rem_last(ts->programs); | |
| while (gf_list_count(p->streams)) { | |
| GF_M2TS_ES *es = (GF_M2TS_ES *)gf_list_last(p->streams); | |
| gf_list_rem_last(p->streams); | |
| //force destroy | |
| es->flags &= ~GF_M2TS_ES_IS_PCR_REUSE; | |
| gf_m2ts_es_del(es, ts); | |
| } | |
| gf_list_del(p->streams); | |
| /*reset OD list*/ | |
| if (p->additional_ods) { | |
| gf_odf_desc_list_del(p->additional_ods); | |
| gf_list_del(p->additional_ods); | |
| } | |
| if (p->pmt_iod) gf_odf_desc_del((GF_Descriptor *)p->pmt_iod); | |
| if (p->metadata_pointer_descriptor) gf_m2ts_metadata_pointer_descriptor_del(p->metadata_pointer_descriptor); | |
| gf_free(p); | |
| } | |
| gf_list_del(ts->programs); | |
| if (ts->TDT_time) gf_free(ts->TDT_time); | |
| gf_m2ts_reset_sdt(ts); | |
| if (ts->tdt_tot) | |
| gf_list_del(ts->SDTs); | |
| #ifdef GPAC_ENABLE_MPE | |
| gf_dvb_mpe_shutdown(ts); | |
| #endif | |
| if (ts->dsmcc_controler) { | |
| if (gf_list_count(ts->dsmcc_controler)) { | |
| #ifdef GPAC_ENABLE_DSMCC | |
| GF_M2TS_DSMCC_OVERLORD* dsmcc_overlord = (GF_M2TS_DSMCC_OVERLORD*)gf_list_get(ts->dsmcc_controler,0); | |
| gf_dir_cleanup(dsmcc_overlord->root_dir); | |
| gf_rmdir(dsmcc_overlord->root_dir); | |
| gf_m2ts_delete_dsmcc_overlord(dsmcc_overlord); | |
| if(ts->dsmcc_root_dir) { | |
| gf_free(ts->dsmcc_root_dir); | |
| } | |
| #endif | |
| } | |
| gf_list_del(ts->dsmcc_controler); | |
| } | |
| while(gf_list_count(ts->ChannelAppList)) { | |
| #ifdef GPAC_ENABLE_DSMCC | |
| GF_M2TS_CHANNEL_APPLICATION_INFO* ChanAppInfo = (GF_M2TS_CHANNEL_APPLICATION_INFO*)gf_list_get(ts->ChannelAppList,0); | |
| gf_m2ts_delete_channel_application_info(ChanAppInfo); | |
| gf_list_rem(ts->ChannelAppList,0); | |
| #endif | |
| } | |
| gf_list_del(ts->ChannelAppList); | |
| if (ts->dsmcc_root_dir) gf_free(ts->dsmcc_root_dir); | |
| gf_free(ts); | |
| } | |
| #if 0//unused | |
| void gf_m2ts_print_info(GF_M2TS_Demuxer *ts) | |
| { | |
| #ifdef GPAC_ENABLE_MPE | |
| gf_dvb_mpe_print_info(ts); | |
| #endif | |
| } | |
| #endif | |
| //20 packets max | |
| #define M2TS_PROBE_SIZE 188*20 | |
| static Bool gf_m2ts_probe_buffer(char *buf, u32 size) | |
| { | |
| GF_Err e; | |
| GF_M2TS_Demuxer *ts; | |
| u32 lt; | |
| lt = gf_log_get_tool_level(GF_LOG_CONTAINER); | |
| gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_QUIET); | |
| ts = gf_m2ts_demux_new(); | |
| ts->raw_mode = GF_M2TS_RAW_PROBE; | |
| e = gf_m2ts_process_data(ts, buf, size); | |
| if (!ts->pck_number) { | |
| e = GF_BAD_PARAM; | |
| } else { | |
| u32 nb_pck; | |
| //max number of packets | |
| if (ts->prefix_present) | |
| nb_pck = size/192; | |
| else | |
| nb_pck = size/188; | |
| //incomplete last packet | |
| if (ts->buffer_size) nb_pck--; | |
| //probe success if after align we have nb_pck - 2 and at least 2 packets | |
| if ((nb_pck<2) || (ts->pck_number + 2 < nb_pck)) | |
| e = GF_BAD_PARAM; | |
| //accept if we have not too few errors (triggered on error bit and corrupted AF fields) | |
| else if ((nb_pck>3) && ts->pck_errors*3<ts->pck_number) | |
| e = GF_OK; | |
| } | |
| gf_m2ts_demux_del(ts); | |
| gf_log_set_tool_level(GF_LOG_CONTAINER, lt); | |
| if (e) return GF_FALSE; | |
| return GF_TRUE; | |
| } | |
| GF_EXPORT | |
| Bool gf_m2ts_probe_file(const char *fileName) | |
| { | |
| char buf[M2TS_PROBE_SIZE]; | |
| u32 size; | |
| if (!strncmp(fileName, "gmem://", 7)) { | |
| u8 *mem_address; | |
| if (gf_blob_get(fileName, &mem_address, &size, NULL) != GF_OK) { | |
| return GF_FALSE; | |
| } | |
| if (size>M2TS_PROBE_SIZE) size = M2TS_PROBE_SIZE; | |
| memcpy(buf, mem_address, size); | |
| gf_blob_release(fileName); | |
| } else { | |
| FILE *t = gf_fopen(fileName, "rb"); | |
| if (!t) return 0; | |
| size = (u32) gf_fread(buf, M2TS_PROBE_SIZE, t); | |
| gf_fclose(t); | |
| if ((s32) size <= 0) return 0; | |
| } | |
| return gf_m2ts_probe_buffer(buf, size); | |
| } | |
| GF_EXPORT | |
| Bool gf_m2ts_probe_data(const u8 *data, u32 size) | |
| { | |
| size /= 188; | |
| size *= 188; | |
| if (!size) return GF_FALSE; | |
| return gf_m2ts_probe_buffer((char *) data, size); | |
| } | |
| static void rewrite_pts_dts(unsigned char *ptr, u64 TS) | |
| { | |
| ptr[0] &= 0xf1; | |
| ptr[0] |= (unsigned char)((TS&0x1c0000000ULL)>>29); | |
| ptr[1] = (unsigned char)((TS&0x03fc00000ULL)>>22); | |
| ptr[2] &= 0x1; | |
| ptr[2] |= (unsigned char)((TS&0x0003f8000ULL)>>14); | |
| ptr[3] = (unsigned char)((TS&0x000007f80ULL)>>7); | |
| ptr[4] &= 0x1; | |
| ptr[4] |= (unsigned char)((TS&0x00000007fULL)<<1); | |
| gf_assert(((u64)(ptr[0]&0xe)<<29) + ((u64)ptr[1]<<22) + ((u64)(ptr[2]&0xfe)<<14) + ((u64)ptr[3]<<7) + ((ptr[4]&0xfe)>>1) == TS); | |
| } | |
| #define ADJUST_TIMESTAMP(_TS) \ | |
| if (_TS < (u64) -ts_shift) _TS = pcr_mod + _TS + ts_shift; \ | |
| else _TS = _TS + ts_shift; \ | |
| while (_TS > pcr_mod) _TS -= pcr_mod; \ | |
| GF_EXPORT | |
| GF_Err gf_m2ts_restamp(u8 *buffer, u32 size, s64 ts_shift, u8 is_pes[GF_M2TS_MAX_STREAMS]) | |
| { | |
| u32 done = 0; | |
| u64 pcr_mod; | |
| // if (!ts_shift) return GF_OK; | |
| pcr_mod = 0x80000000; | |
| pcr_mod*=4; | |
| while (done + 188 <= size) { | |
| u8 *pesh; | |
| u8 *pck; | |
| u64 pcr_base=0, pcr_ext=0; | |
| u16 pid; | |
| u8 adaptation_field, adaptation_field_length; | |
| pck = (u8*) buffer+done; | |
| if (pck[0]!=0x47) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TS Restamp] Invalid sync byte %X\n", pck[0])); | |
| return GF_NON_COMPLIANT_BITSTREAM; | |
| } | |
| pid = ((pck[1] & 0x1f) <<8 ) + pck[2]; | |
| adaptation_field_length = 0; | |
| adaptation_field = (pck[3] >> 4) & 0x3; | |
| if ((adaptation_field==2) || (adaptation_field==3)) { | |
| adaptation_field_length = pck[4]; | |
| if ( pck[5]&0x10 /*PCR_flag*/) { | |
| pcr_base = (((u64)pck[6])<<25) + (pck[7]<<17) + (pck[8]<<9) + (pck[9]<<1) + (pck[10]>>7); | |
| pcr_ext = ((pck[10]&1)<<8) + pck[11]; | |
| ADJUST_TIMESTAMP(pcr_base); | |
| pck[6] = (unsigned char)(0xff&(pcr_base>>25)); | |
| pck[7] = (unsigned char)(0xff&(pcr_base>>17)); | |
| pck[8] = (unsigned char)(0xff&(pcr_base>>9)); | |
| pck[9] = (unsigned char)(0xff&(pcr_base>>1)); | |
| pck[10] = (unsigned char)(((0x1&pcr_base)<<7) | 0x7e | ((0x100&pcr_ext)>>8)); | |
| if (pcr_ext != ((pck[10]&1)<<8) + pck[11]) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TS Restamp] Sanity check failed for PCR restamping\n")); | |
| return GF_IO_ERR; | |
| } | |
| pck[11] = (unsigned char)(0xff&pcr_ext); | |
| } | |
| /*add adaptation_field_length field*/ | |
| adaptation_field_length++; | |
| } | |
| if (!is_pes[pid] || !(pck[1]&0x40)) { | |
| done+=188; | |
| continue; | |
| } | |
| pesh = &pck[4+adaptation_field_length]; | |
| if ((pesh[0]==0x00) && (pesh[1]==0x00) && (pesh[2]==0x01)) { | |
| Bool has_pts, has_dts; | |
| if ((pesh[6]&0xc0)!=0x80) { | |
| done+=188; | |
| continue; | |
| } | |
| has_pts = (pesh[7]&0x80); | |
| has_dts = has_pts ? (pesh[7]&0x40) : 0; | |
| if (has_pts) { | |
| u64 PTS; | |
| if (((pesh[9]&0xe0)>>4)!=0x2) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TS Restamp] PID %4d: Wrong PES header, PTS decoding: '0010' expected\n", pid)); | |
| done+=188; | |
| continue; | |
| } | |
| PTS = gf_m2ts_get_pts(pesh + 9); | |
| ADJUST_TIMESTAMP(PTS); | |
| rewrite_pts_dts(pesh+9, PTS); | |
| } | |
| if (has_dts) { | |
| u64 DTS = gf_m2ts_get_pts(pesh + 14); | |
| ADJUST_TIMESTAMP(DTS); | |
| rewrite_pts_dts(pesh+14, DTS); | |
| } | |
| } else { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TS Restamp] PID %4d: Wrong PES not beginning with start code\n", pid)); | |
| } | |
| done+=188; | |
| } | |
| return GF_OK; | |
| } | |
| #endif /*GPAC_DISABLE_MPEG2TS*/ |