| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Romain Bouqueau, Jean Le Feuvre | |
| * Copyright (c) Motion Spell, Telecom ParisTech 2024 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / common tools 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/xml.h> | |
| #include <gpac/internal/isomedia_dev.h> //GF_EventMessageBox | |
| #define XML_SCAN_INT(_fmt, _value) \ | |
| {\ | |
| if (strstr(att->value, "0x")) { u32 __i; sscanf(att->value+2, "%x", &__i); _value = __i; }\ | |
| else if (strstr(att->value, "0X")) { u32 __i; sscanf(att->value+2, "%X", &__i); _value = __i; }\ | |
| else sscanf(att->value, _fmt, &_value); \ | |
| }\ | |
| // SCTE-35 XML parsing ---------------------------------------------- | |
| static void xml_scte35_parse_splice_time(GF_XMLNode *root, GF_BitStream *bs) | |
| { | |
| u32 i = 0, j = 0; | |
| GF_XMLNode *node = NULL; | |
| GF_XMLAttribute *att = NULL; | |
| while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { | |
| if (!strcmp(att->name, "ptsTime")) { | |
| u64 ptsTime = 0; | |
| if (sscanf(att->value, LLU, &ptsTime) != 1) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Invalid value for ptsTime=\"%s\"\n", att->value)); | |
| Bool time_specified_flag = GF_TRUE; | |
| gf_bs_write_int(bs, time_specified_flag, 1); | |
| if (time_specified_flag == GF_TRUE) { | |
| gf_bs_write_int(bs, 0xFF/*reserved*/, 6); | |
| gf_bs_write_long_int(bs, ptsTime, 33); | |
| } else { | |
| gf_bs_write_int(bs, 0xFF/*reserved*/, 7); | |
| } | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown attribute \"%s\" in SpliceTime\n", att->name)); | |
| } | |
| } | |
| while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { | |
| if (node->type) continue; | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown node \"%s\" in SpliceTime\n", node->name)); | |
| } | |
| } | |
| static void xml_scte35_parse_break_duration(GF_XMLNode *root, GF_BitStream *bs) | |
| { | |
| u32 i = 0, j = 0; | |
| GF_XMLNode *node = NULL; | |
| GF_XMLAttribute *att = NULL; | |
| while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { | |
| if (!strcmp(att->name, "duration")) { | |
| u64 duration = 0; | |
| if (sscanf(att->value, LLU, &duration) != 1) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Invalid value for duration=\"%s\"\n", att->value)); | |
| gf_bs_write_int(bs, 0/*auto_return*/, 1); | |
| gf_bs_write_int(bs, 0/*reserved*/, 6); | |
| gf_bs_write_long_int(bs, duration, 33); | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown attribute \"%s\" in SpliceTime\n", att->name)); | |
| } | |
| } | |
| while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { | |
| if (node->type) continue; | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown node \"%s\" in SpliceTime\n", node->name)); | |
| } | |
| } | |
| static void xml_scte35_parse_time_signal(GF_XMLNode *root, GF_BitStream *bs) | |
| { | |
| u32 i = 0, j = 0; | |
| GF_XMLNode *node = NULL; | |
| GF_XMLAttribute *att = NULL; | |
| while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown attribute \"%s\" in TimeSignal\n", att->name)); | |
| } | |
| while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { | |
| if (node->type) continue; | |
| if (!strcmp(node->name, "SpliceTime")) { | |
| xml_scte35_parse_splice_time(node, bs); | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown node \"%s\" in TimeSignal\n", node->name)); | |
| } | |
| } | |
| } | |
| static void xml_scte35_parse_segmentation_descriptor(GF_XMLNode *root, GF_BitStream *bs) | |
| { | |
| u64 descriptor_loop_length_pos = gf_bs_get_position(bs); | |
| gf_bs_write_u16(bs, 0/*descriptor_loop_length*/); //placeholder | |
| //for (u16 i=0; i<descriptor_loop_length; i++) { | |
| gf_bs_write_u8(bs, 0x02/*splice_descriptor_tag*/); | |
| gf_bs_write_u8(bs, 0/*descriptor_length*/); //placeholder | |
| gf_bs_write_u32(bs, GF_4CC('C', 'U', 'E', 'I')); | |
| u32 i = 0, j = 0; | |
| GF_XMLNode *node = NULL; | |
| GF_XMLAttribute *att = NULL; | |
| int segmentationEventId=0; | |
| Bool segmentationEventCancelIndicator = GF_FALSE, webDeliveryAllowedFlag = GF_FALSE, noRegionalBlackoutFlag = GF_FALSE, archiveAllowedFlag = GF_FALSE; | |
| Bool segmentationEventIdComplianceIndicator = GF_FALSE; | |
| Bool programSegmentationFlag = GF_FALSE, segmentationDurationFlag = GF_FALSE, deliveryNotRestrictedFlag = GF_FALSE; | |
| u8 deviceRestrictions = 0; | |
| u8 segmentationTypeId = 0, segmentNum = 0, segmentsExpected = 0; | |
| u8 subSegmentNum = 0, subSegmentsExpected = 0; | |
| u64 segmentationDuration = 0; | |
| while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { | |
| if (!strcmp(att->name, "segmentationEventId")) { | |
| segmentationEventId = atoi(att->value); | |
| } else if (!strcmp(att->name, "segmentationEventCancelIndicator")) { | |
| segmentationEventCancelIndicator = atoi(att->value); | |
| } else if (!strcmp(att->name, "segmentationEventIdComplianceIndicator")) { | |
| segmentationEventIdComplianceIndicator = atoi(att->value); | |
| } else if (!strcmp(att->name, "webDeliveryAllowedFlag")) { | |
| webDeliveryAllowedFlag = atoi(att->value); | |
| } else if (!strcmp(att->name, "noRegionalBlackoutFlag")) { | |
| noRegionalBlackoutFlag = atoi(att->value); | |
| } else if (!strcmp(att->name, "archiveAllowedFlag")) { | |
| archiveAllowedFlag = atoi(att->value); | |
| } else if (!strcmp(att->name, "deviceRestrictions")) { | |
| deviceRestrictions = atoi(att->value); | |
| } else if (!strcmp(att->name, "segmentationTypeId")) { | |
| segmentationTypeId = atoi(att->value); | |
| } else if (!strcmp(att->name, "segmentNum")) { | |
| segmentNum = atoi(att->value); | |
| } else if (!strcmp(att->name, "segmentsExpected")) { | |
| segmentsExpected = atoi(att->value); | |
| } else if (!strcmp(att->name, "programSegmentationFlag")) { | |
| programSegmentationFlag = atoi(att->value); | |
| } else if (!strcmp(att->name, "segmentationDurationFlag")) { | |
| segmentationDurationFlag = atoi(att->value); | |
| } else if (!strcmp(att->name, "deliveryNotRestrictedFlag")) { | |
| deliveryNotRestrictedFlag = atoi(att->value); | |
| } else if (!strcmp(att->name, "segmentationDuration")) { | |
| segmentationDuration = atoi(att->value); | |
| } else if (!strcmp(att->name, "subSegmentNum")) { | |
| subSegmentNum = atoi(att->value); | |
| } else if (!strcmp(att->name, "subSegmentsExpected")) { | |
| subSegmentsExpected = atoi(att->value); | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown attribute \"%s\" in SegmentationDescriptor\n", att->name)); | |
| } | |
| } | |
| gf_bs_write_u32(bs, segmentationEventId); | |
| gf_bs_write_int(bs, segmentationEventCancelIndicator, 1); | |
| gf_bs_write_int(bs, segmentationEventIdComplianceIndicator, 1); | |
| gf_bs_write_int(bs, 0x3F/*reserved*/, 6); | |
| if (segmentationEventCancelIndicator == 0) { | |
| gf_bs_write_int(bs, programSegmentationFlag, 1); | |
| gf_bs_write_int(bs, segmentationDurationFlag, 1); | |
| gf_bs_write_int(bs, deliveryNotRestrictedFlag, 1); | |
| if (deliveryNotRestrictedFlag == 0) { | |
| gf_bs_write_int(bs, webDeliveryAllowedFlag, 1); | |
| gf_bs_write_int(bs, noRegionalBlackoutFlag, 1); | |
| gf_bs_write_int(bs, archiveAllowedFlag, 1); | |
| gf_bs_write_int(bs, deviceRestrictions, 2); | |
| } else { | |
| gf_bs_write_int(bs, 0x1F/*reserved*/, 5); | |
| } | |
| if (programSegmentationFlag == 0) { | |
| assert(0); | |
| #if 0 //not implemented | |
| gf_bs_write_u8(bs, component_count); | |
| for (u8 i=0; i<component_count; i++) { | |
| gf_bs_write_u8(bs, componentTag); | |
| gf_bs_write_int(bs, 7, 0x7F/*reserved*/); | |
| gf_bs_write_u8(bs, componentTag); | |
| gf_bs_write_long_int(bs, ptsOffset, 33); | |
| } | |
| #endif | |
| } | |
| if (segmentationDurationFlag == 1) { | |
| gf_bs_write_long_int(bs, segmentationDuration, 40); | |
| } | |
| gf_bs_write_u8(bs, 0/*u8 segmentation_upid_type*/); | |
| gf_bs_write_u8(bs, 0/*u8 segmentation_upid_length*/); | |
| gf_bs_write_u8(bs, segmentationTypeId); | |
| gf_bs_write_u8(bs, segmentNum); | |
| gf_bs_write_u8(bs, segmentsExpected); | |
| if (segmentationTypeId == 0x34 || segmentationTypeId == 0x30 || segmentationTypeId == 0x32 | |
| || segmentationTypeId == 0x36 || segmentationTypeId == 0x38 || segmentationTypeId == 0x3A | |
| || segmentationTypeId == 0x44 || segmentationTypeId == 0x46) { | |
| gf_bs_write_u8(bs, subSegmentNum); | |
| gf_bs_write_u8(bs, subSegmentsExpected); | |
| } | |
| } | |
| while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { | |
| if (node->type) continue; | |
| if (!strcmp(node->name, "SegmentationUpid")) { | |
| GF_XMLAttribute *attx = NULL; | |
| while ((attx = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { | |
| if (!strcmp(attx->name, "segmentationUpidType")) { | |
| gf_bs_write_u8(bs, segmentationTypeId); | |
| gf_bs_write_u8(bs, segmentNum); | |
| gf_bs_write_u8(bs, segmentsExpected); | |
| #if 0 //not implemented: seen at the end of TimeSignal | |
| <SegmentationDescriptor segmentationEventId="1207959603" segmentationEventCancelIndicator="0" segmentationEventIdComplianceIndicator="1" | |
| webDeliveryAllowedFlag="1" noRegionalBlackoutFlag="1" archiveAllowedFlag="1" deviceRestrictions="3" | |
| segmentationTypeId="53" segmentNum="0" segmentsExpected="0"> | |
| <SegmentationUpid segmentationUpidType="8">000000003484D6D5</SegmentationUpid> | |
| </SegmentationDescriptor> | |
| #endif | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown attribute \"%s\" in SegmentationUpid\n", attx->name)); | |
| } | |
| } | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown node \"%s\" in TimeSignal\n", node->name)); | |
| } | |
| } | |
| // write descriptor_loop_length and descriptor_length | |
| { | |
| u64 pos = gf_bs_get_position(bs); | |
| u32 descriptor_length = (u32) (pos - descriptor_loop_length_pos - 2); | |
| gf_bs_seek(bs, descriptor_loop_length_pos + 1); | |
| gf_bs_write_u8(bs, descriptor_length); //descriptor_loop_length | |
| gf_bs_seek(bs, descriptor_loop_length_pos + 3); | |
| gf_bs_write_u8(bs, descriptor_length-2); //descriptor_length | |
| gf_bs_seek(bs, pos); | |
| } | |
| } | |
| static void xml_scte35_parse_splice_insert(GF_XMLNode *root, GF_BitStream *bs) | |
| { | |
| u32 i = 0, j = 0; | |
| GF_XMLNode *node = NULL; | |
| GF_XMLAttribute *att = NULL; | |
| int spliceEventId = 0, spliceEventCancelIndicator = 0, outOfNetworkIndicator = 0, segmentationEventId = 0, programSpliceFlag = 0, durationFlag = 0, spliceImmediateFlag = 0; | |
| while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { | |
| if (!strcmp(att->name, "spliceEventId")) { | |
| spliceEventId = atoi(att->value); | |
| } else if (!strcmp(att->name, "spliceEventCancelIndicator")) { | |
| spliceEventCancelIndicator = atoi(att->value); | |
| } else if (!strcmp(att->name, "outOfNetworkIndicator")) { | |
| outOfNetworkIndicator = atoi(att->value); | |
| } else if (!strcmp(att->name, "segmentationEventId")) { | |
| segmentationEventId = atoi(att->value); | |
| } else if (!strcmp(att->name, "programSpliceFlag")) { | |
| programSpliceFlag = atoi(att->value); | |
| } else if (!strcmp(att->name, "durationFlag")) { | |
| durationFlag = atoi(att->value); | |
| } else if (!strcmp(att->name, "spliceImmediateFlag")) { | |
| spliceImmediateFlag = atoi(att->value); | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown attribute \"%s\" in SpliceInsert\n", att->name)); | |
| } | |
| } | |
| while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { | |
| if (node->type) continue; | |
| if (!strcmp(node->name, "SpliceTime")) { | |
| xml_scte35_parse_splice_time(node, bs); | |
| } else if (!strcmp(node->name, "BreakDuration")) { | |
| xml_scte35_parse_break_duration(node, bs); | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown node \"%s\" in SpliceInsert\n", node->name)); | |
| } | |
| } | |
| #if 0 //not implemented | |
| u64 splice_time = 0; | |
| gf_bs_write_u32(bs, spliceEventId); | |
| gf_bs_write_int(bs, 1, spliceEventCancelIndicator); | |
| /*reserved = */gf_bs_write_int(bs, 7, 0x7F); | |
| if (spliceEventCancelIndicator == 0) { | |
| gf_bs_write_int(bs, 1, outOfNetworkIndicator); | |
| gf_bs_write_int(bs, 1, programSpliceFlag); | |
| gf_bs_write_int(bs, 1, durationFlag); | |
| gf_bs_write_int(bs, 1, spliceImmediateFlag); | |
| /*reserved = */gf_bs_write_int(bs, 4, 0xF); | |
| if ((programSpliceFlag == 1) && (spliceImmediateFlag == 0)) { | |
| splice_time = scte35dec_parse_splice_time(bs); | |
| *pts = splice_time + pts_adjustment; | |
| } | |
| if (programSpliceFlag == 0) { | |
| u32 i; | |
| u32 component_count = gf_bs_read_u8(bs); | |
| for (i=0; i<component_count; i++) { | |
| /*u8 component_tag = */gf_bs_read_u8(bs); | |
| if (spliceImmediateFlag == 0) { | |
| splice_time = scte35dec_parse_splice_time(bs); | |
| *pts = splice_time + pts_adjustment; | |
| } | |
| } | |
| } | |
| if (durationFlag == 1) { | |
| //break_duration() | |
| /*Bool auto_return = */gf_bs_write_int(bs, 1, 0); | |
| /*reserved = */gf_bs_write_int(bs, 6, 0x3F); | |
| gf_bs_write_long_int(bs, 33, dur); | |
| } | |
| } | |
| #else | |
| (void)spliceEventId; | |
| (void)spliceEventCancelIndicator; | |
| (void)outOfNetworkIndicator; | |
| (void)segmentationEventId; | |
| (void)programSpliceFlag; | |
| (void)durationFlag; | |
| (void)spliceImmediateFlag; | |
| #endif | |
| } | |
| static void xml_scte35_parse_splice_info(GF_XMLNode *root, GF_BitStream *bs) | |
| { | |
| u32 i = 0, j = 0; | |
| GF_XMLNode *node = NULL; | |
| GF_XMLAttribute *att = NULL; | |
| char xmlns[256] = "http://www.scte.org/schemas/35"; | |
| u32 sap_type = 0; | |
| u32 protocol_version = 0; | |
| u64 pts_adjustment = 0; | |
| u32 tier = 0; | |
| while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &j))) { | |
| if (!strcmp(att->name, "xmlns")) { | |
| snprintf(xmlns, 255, "%s", att->value); | |
| } else if (!strcmp(att->name, "sapType")) { | |
| XML_SCAN_INT("%u", sap_type); | |
| } else if (!strcmp(att->name, "protocolVersion")) { | |
| XML_SCAN_INT("%u", protocol_version); | |
| } else if (!strcmp(att->name, "ptsAdjustment")) { | |
| XML_SCAN_INT(LLU, pts_adjustment); | |
| } else if (!strcmp(att->name, "tier")) { | |
| XML_SCAN_INT("%u", tier); | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown attribute \"%s\" in SpliceInfoSection\n", att->name)); | |
| } | |
| } | |
| gf_bs_write_u8(bs, 0xFC); //table_id | |
| gf_bs_write_int(bs, 0/*section_syntax_indicator*/, 1); | |
| gf_bs_write_int(bs, 0/*private_indicator*/, 1); | |
| gf_bs_write_int(bs, sap_type, 2); | |
| u64 section_length_pos = gf_bs_get_position(bs); | |
| gf_bs_write_int(bs, 0, 12); //placeholder | |
| gf_bs_write_u8(bs, protocol_version); | |
| gf_bs_write_int(bs, 0/*encrypted_packet*/, 1); | |
| gf_bs_write_int(bs, 0/*encryption_algorithm*/, 6); | |
| gf_bs_write_long_int(bs, pts_adjustment, 33); | |
| gf_bs_write_u8(bs, 0/*cw_index*/); | |
| gf_bs_write_int(bs, tier, 12); | |
| u64 splice_command_length_pos = gf_bs_get_position(bs); | |
| gf_bs_write_int(bs, 0, 12); //placeholder | |
| #define WRITE_CMD_LEN() { \ | |
| u64 pos = gf_bs_get_position(bs); \ | |
| u32 splice_command_length = (u32) (pos - splice_command_length_pos - 3); \ | |
| gf_bs_seek(bs, splice_command_length_pos + 1); \ | |
| gf_bs_write_u8(bs, splice_command_length); \ | |
| gf_bs_seek(bs, pos); \ | |
| } | |
| while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { | |
| if (node->type) continue; | |
| if (!strcmp(node->name, "TimeSignal")) { | |
| gf_bs_write_u8(bs, 0x06); | |
| xml_scte35_parse_time_signal(node, bs); | |
| WRITE_CMD_LEN(); | |
| continue; | |
| } else if (!strcmp(node->name, "SpliceInsert")) { | |
| gf_bs_write_u8(bs, 0x05); | |
| xml_scte35_parse_splice_insert(node, bs); | |
| WRITE_CMD_LEN(); | |
| break; | |
| } else if (!strcmp(node->name, "SegmentationDescriptor")) { | |
| xml_scte35_parse_segmentation_descriptor(node, bs); | |
| break; | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown node \"%s\" in SpliceInfoSection\n", node->name)); | |
| } | |
| } | |
| // post write section length | |
| { | |
| u64 pos = gf_bs_get_position(bs); | |
| u32 section_length = (u32) (pos + 4 /*CRC32*/ - section_length_pos - 2); | |
| gf_bs_seek(bs, section_length_pos); | |
| gf_bs_write_u16(bs, (sap_type << 12) | section_length); | |
| gf_bs_seek(bs, pos); | |
| } | |
| // write CRC32 | |
| { | |
| u8 *data = NULL; | |
| u32 size = 0; | |
| GF_BitStream *bs_tmp = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| gf_bs_transfer(bs_tmp, bs, GF_TRUE); | |
| gf_bs_get_content(bs_tmp, &data, &size); | |
| gf_bs_seek(bs, gf_bs_get_size(bs)); | |
| gf_bs_write_u32(bs, gf_crc_32(data+(section_length_pos-1), size - (u32) (section_length_pos-1))); | |
| gf_bs_del(bs_tmp); | |
| gf_free(data); | |
| } | |
| #undef WRITE_CMD_LEN | |
| } | |
| // raw SCTE-35 payload | |
| void xml_scte35_parse(GF_XMLNode *root, GF_BitStream *bs) | |
| { | |
| u32 i=0; | |
| GF_XMLNode *node = NULL; | |
| while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { | |
| if (node->type) continue; | |
| if (!strcmp(node->name, "SpliceInfoSection")) { | |
| xml_scte35_parse_splice_info(node, bs); | |
| continue; | |
| } | |
| } | |
| } | |
| // SCTE-35 encapsulated in an EMEB box | |
| static void xml_emeb_parse(GF_XMLNode *root, GF_BitStream *bs) | |
| { | |
| #ifndef GPAC_DISABLE_ISOM | |
| GF_EventMessageBox *emeb = (GF_EventMessageBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_EMEB); | |
| gf_isom_box_size((GF_Box*)emeb); | |
| if (gf_isom_box_write((GF_Box*)emeb, bs) != GF_OK) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] EventMessageEmptyBox serialization failed\n")); | |
| gf_isom_box_del((GF_Box*)emeb); | |
| #endif | |
| } | |
| // SCTE-35 encapsulated in an EMIB box | |
| static void xml_emib_parse(GF_XMLNode *root, GF_BitStream *bs) | |
| { | |
| #ifndef GPAC_DISABLE_ISOM | |
| int i = 0; | |
| GF_XMLAttribute *att = NULL; | |
| GF_EventMessageBox *emib = (GF_EventMessageBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_EMIB); | |
| while ((att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &i))) { | |
| if (!stricmp(att->name, "version")) { | |
| if (sscanf(att->value, "%hu", &emib->version) != 1) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Invalid value for version=\"%s\"\n", att->value)); | |
| } else if (!stricmp(att->name, "flags")) { | |
| if (sscanf(att->value, "%u", &emib->flags) != 1) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Invalid value for flags=\"%s\"\n", att->value)); | |
| } else if (!stricmp(att->name, "scheme_id_uri")) { | |
| emib->scheme_id_uri = gf_strdup(att->value); | |
| } else if (!stricmp(att->name, "value")) { | |
| emib->value = gf_strdup(att->value); | |
| } else if (!stricmp(att->name, "presentation_time_delta")) { | |
| if (sscanf(att->value, LLD, &emib->presentation_time_delta) != 1) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Invalid value for presentation_time_delta=\"%s\"\n", att->value)); | |
| } else if (!stricmp(att->name, "event_duration")) { | |
| if (sscanf(att->value, "%u", &emib->event_duration) != 1) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Invalid value for event_duration=\"%s\"\n", att->value)); | |
| } else if (!stricmp(att->name, "event_id")) { | |
| if (sscanf(att->value, "%u", &emib->event_id) != 1) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Invalid value for event_id=\"%s\"\n", att->value)); | |
| } else if (!stricmp(att->name, "message_data")) { | |
| u8 *ptr = att->value; | |
| if (!strnicmp(ptr, "0x", 2)) ptr +=2; | |
| u32 len = (u32)strlen(ptr)/2; | |
| emib->message_data = gf_malloc(len); | |
| emib->message_data_size = len; | |
| for (u32 i=0; i<len; ++i, ptr+=2) { | |
| int val=0; | |
| sscanf(ptr, "%02x", &val); | |
| emib->message_data[i] = (u8)val; | |
| } | |
| } else if (!stricmp(att->name, "Size")) { | |
| } else if (!stricmp(att->name, "Type")) { | |
| } else if (!stricmp(att->name, "Specification")) { | |
| } else if (!stricmp(att->name, "Container")) { | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] Unknown attribute \"%s\" in EventMessageInstanceBox parsing\n", att->name)); | |
| } | |
| } | |
| u64 start = gf_bs_get_position(bs); | |
| gf_isom_box_size((GF_Box*)emib); | |
| if (gf_isom_box_write((GF_Box*)emib, bs) != GF_OK) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML] EventMessageInstanceBox serialization failed\n")); | |
| if (!emib->message_data) { | |
| // append emib->message_data as it is the last field | |
| u64 before = gf_bs_get_position(bs); | |
| xml_scte35_parse(root, bs); | |
| u64 end = gf_bs_get_position(bs); | |
| gf_bs_seek(bs, start); | |
| emib->message_data = (u8*)(uintptr_t)before; //dumb non-null pointer | |
| emib->message_data_size = (u32) (end - before); | |
| gf_isom_box_size((GF_Box*)emib); | |
| gf_isom_full_box_write((GF_Box*)emib, bs); //rewrite size | |
| gf_bs_seek(bs, end); | |
| emib->message_data = NULL; | |
| emib->message_data_size = 0; | |
| } | |
| gf_isom_box_del((GF_Box*)emib); | |
| #endif | |
| } | |
| // generic bit parsing | |
| #include <gpac/base_coding.h> | |
| #include <gpac/network.h> // gf_url_concatenate | |
| GF_EXPORT | |
| GF_Err gf_xml_parse_bit_sequence_bs(GF_XMLNode *bsroot, const char *parent_url, const char *base_media_file, GF_BitStream *bs_orig) | |
| { | |
| u32 i, j; | |
| GF_Err e = GF_OK; | |
| GF_XMLNode *node; | |
| GF_XMLAttribute *att; | |
| GF_BitStream *bs = bs_orig; | |
| u32 enc_base64 = 0; | |
| i=0; | |
| while ((node = (GF_XMLNode *) gf_list_enum(bsroot->content, &i))) { | |
| u32 nb_bits = 0; | |
| u32 size = 0; | |
| u64 offset = 0; | |
| s64 value = 0; | |
| Bool use_file = GF_FALSE; | |
| bin128 word128; | |
| Float val_float = 0; | |
| Double val_double = 0; | |
| Bool use_word128 = GF_FALSE; | |
| Bool use_text = GF_FALSE; | |
| Bool base64_prefix_bits = 0; | |
| Bool big_endian = GF_TRUE; | |
| Bool has_float = GF_FALSE; | |
| Bool has_double = GF_FALSE; | |
| const char *szFile = NULL; | |
| const char *szString = NULL; | |
| const char *szBase64 = NULL; | |
| const char *szData = NULL; | |
| if (node->type) continue; | |
| if (!stricmp(node->name, "SCTE35")) { | |
| xml_scte35_parse(node, bs); | |
| continue; | |
| } else if (!stricmp(node->name, "EventMessageEmptyBox")) { | |
| xml_emeb_parse(node, bs); | |
| continue; | |
| } else if (!stricmp(node->name, "EventMessageInstanceBox")) { | |
| xml_emib_parse(node, bs); | |
| continue; | |
| } else if (stricmp(node->name, "BS") ) { | |
| e = gf_xml_parse_bit_sequence_bs(node, parent_url, base_media_file, bs); | |
| if (e) goto exit; | |
| continue; | |
| } | |
| j=0; | |
| while ( (att = (GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) { | |
| if (!stricmp(att->name, "bits")) { | |
| XML_SCAN_INT("%d", nb_bits); | |
| } else if (!stricmp(att->name, "value")) { | |
| XML_SCAN_INT(LLD, value); | |
| } else if (!stricmp(att->name, "float")) { | |
| sscanf(att->value, "%f", &val_float); | |
| has_float = GF_TRUE; | |
| } else if (!stricmp(att->name, "double")) { | |
| sscanf(att->value, "%lf", &val_double); | |
| has_double = GF_TRUE; | |
| } else if (!stricmp(att->name, "mediaOffset") || !stricmp(att->name, "dataOffset")) { | |
| XML_SCAN_INT(LLU, offset); | |
| use_file = GF_TRUE; | |
| } else if (!stricmp(att->name, "dataLength")) { | |
| XML_SCAN_INT("%u", size); | |
| use_file = GF_TRUE; | |
| } else if (!stricmp(att->name, "mediaFile") || !stricmp(att->name, "dataFile")) { | |
| szFile = att->value; | |
| use_file = GF_TRUE; | |
| } else if (!stricmp(att->name, "text") || !stricmp(att->name, "string")) { | |
| szString = att->value; | |
| } else if (!stricmp(att->name, "fcc")) { | |
| if (!att->value || strlen(att->value) < 4) { | |
| e = GF_BAD_PARAM; | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] Cannot parse fcc attribute\n")); | |
| goto exit; | |
| } | |
| value = GF_4CC(att->value[0], att->value[1], att->value[2], att->value[3]); | |
| nb_bits = 32; | |
| } else if (!stricmp(att->name, "ID128")) { | |
| e = gf_bin128_parse(att->value, word128); | |
| if (e != GF_OK) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] Cannot parse ID128\n")); | |
| goto exit; | |
| } | |
| use_word128 = GF_TRUE; | |
| } else if (!stricmp(att->name, "textmode")) { | |
| if (!strcmp(att->value, "yes")) use_text = GF_TRUE; | |
| } else if (!stricmp(att->name, "data64")) { | |
| szBase64 = att->value; | |
| } else if (!stricmp(att->name, "data")) { | |
| szData = att->value; | |
| if (!strnicmp(szData, "0x", 2)) szData += 2; | |
| } else if (!stricmp(att->name, "endian") && !stricmp(att->value, "little")) { | |
| big_endian = GF_FALSE; | |
| } else if (!stricmp(att->name, "base64")) { | |
| if (!stricmp(att->value, "yes") || !stricmp(att->value, "true") ) { | |
| if (!enc_base64) enc_base64 = 1; | |
| } else if (!stricmp(att->value, "start")) { | |
| if (!enc_base64) enc_base64 = 2; | |
| } else if (!stricmp(att->value, "end")) { | |
| if (enc_base64==2) enc_base64 = 3; | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] Invalid base64 attribute \"%s\", expecting yes/no, start or end\n", att->value)); | |
| e = GF_NON_COMPLIANT_BITSTREAM; | |
| goto exit; | |
| } | |
| } else if (!stricmp(att->name, "base64Prefix")) { | |
| base64_prefix_bits = atoi(att->value); | |
| } else if (!stricmp(att->name, "id")) { | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] Unknown attribute \"%s\", ignoring\n", att->name)); | |
| } | |
| } | |
| if (enc_base64 && (enc_base64<3)) { | |
| if (bs == bs_orig) { | |
| bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| if (!bs) { | |
| e = GF_OUT_OF_MEM; | |
| goto exit; | |
| } | |
| } | |
| } | |
| if (use_file && !szFile) | |
| szFile = base_media_file; | |
| if (nb_bits && nb_bits > 64) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] Error encoding value on too many bits (max: 64, got: %u)\n", nb_bits)); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } | |
| if (szString) { | |
| u32 len = (u32) strlen(szString); | |
| if (nb_bits) | |
| gf_bs_write_int(bs, len, nb_bits); | |
| gf_bs_write_data(bs, szString, len); | |
| } else if (szBase64) { | |
| u32 len = (u32) strlen(szBase64); | |
| char *data = (char *) gf_malloc(sizeof(char)*len); | |
| u32 ret; | |
| if (!data) { | |
| e = GF_OUT_OF_MEM; | |
| goto exit; | |
| } | |
| ret = (u32) gf_base64_decode((char *)szBase64, len, data, len); | |
| if ((s32) ret >=0) { | |
| gf_bs_write_int(bs, ret, nb_bits); | |
| gf_bs_write_data(bs, data, ret); | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] Error decoding base64 \"%s\"\n", att->value)); | |
| gf_free(data); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } | |
| gf_free(data); | |
| } else if (szData) { | |
| u32 len = (u32) strlen(szData); | |
| if (len%2) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] Error decoding hexadecimal data argument: length needs to be even\n")); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } | |
| char *data = (char *) gf_malloc(sizeof(char)*len/2); | |
| if (!data) { | |
| e = GF_OUT_OF_MEM; | |
| goto exit; | |
| } | |
| for (j=0; j<len; j+=2) { | |
| u32 v; | |
| char szV[5]; | |
| sprintf(szV, "%c%c", szData[j], szData[j+1]); | |
| sscanf(szV, "%x", &v); | |
| data[j/2] = v; | |
| } | |
| gf_bs_write_int(bs, len/2, nb_bits); | |
| gf_bs_write_data(bs, data, len/2); | |
| gf_free(data); | |
| } else if (has_float) { | |
| gf_bs_write_float(bs, val_float); | |
| } else if (has_double) { | |
| gf_bs_write_double(bs, val_double); | |
| } else if (nb_bits) { | |
| if (!big_endian) { | |
| if (nb_bits == 16) | |
| gf_bs_write_u16_le(bs, (u32)value); | |
| else if (nb_bits == 32) | |
| gf_bs_write_u32_le(bs, (u32)value); | |
| else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] Little-endian values can only be 16 or 32-bit\n")); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } | |
| } | |
| else { | |
| if (nb_bits<33) gf_bs_write_int(bs, (s32) value, nb_bits); | |
| else gf_bs_write_long_int(bs, value, nb_bits); | |
| } | |
| } else if (szFile) { | |
| u32 read, remain; | |
| char block[1024]; | |
| FILE *_tmp = NULL; | |
| if (parent_url) { | |
| char *f_url = gf_url_concatenate(parent_url, szFile); | |
| _tmp = gf_fopen(f_url, use_text ? "rt" : "rb"); | |
| gf_free(f_url); | |
| } else { | |
| _tmp = gf_fopen(szFile, use_text ? "rt" : "rb"); | |
| } | |
| if (!_tmp) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] Error opening file \"%s\"\n", szFile)); | |
| e = GF_URL_ERROR; | |
| goto exit; | |
| } | |
| if (!size) { | |
| size = (u32) gf_fsize(_tmp); | |
| //if offset only copy from offset until end | |
| if ((u64) size > offset) | |
| size -= (u32) offset; | |
| } | |
| remain = size; | |
| gf_fseek(_tmp, offset, SEEK_SET); | |
| while (remain) { | |
| u32 bsize = remain; | |
| if (bsize>1024) bsize=1024; | |
| read = (u32) gf_fread(block, bsize, _tmp); | |
| if ((s32) read < 0) { | |
| gf_fclose(_tmp); | |
| e = GF_IO_ERR; | |
| goto exit; | |
| } | |
| gf_bs_write_data(bs, block, read); | |
| remain -= bsize; | |
| } | |
| gf_fclose(_tmp); | |
| } else if (use_word128) { | |
| gf_bs_write_data(bs, (char *)word128, 16); | |
| } | |
| if ((enc_base64==1) || (enc_base64==3)) { | |
| u8 *bs_data; | |
| u32 bs_data_size; | |
| assert (bs != bs_orig); | |
| gf_bs_get_content(bs, &bs_data, &bs_data_size); | |
| gf_bs_del(bs); | |
| enc_base64 = 0; | |
| bs = bs_orig; | |
| if (bs_data) { | |
| u8 *bs_data_out; | |
| u32 res = 2*bs_data_size + 3; | |
| bs_data_out = gf_malloc(sizeof(char) * res); | |
| if (!bs_data_out) { | |
| e = GF_OUT_OF_MEM; | |
| goto exit; | |
| } | |
| res = gf_base64_encode(bs_data, bs_data_size, bs_data_out, res); | |
| bs_data_out[res] = 0; | |
| if (base64_prefix_bits) { | |
| if (base64_prefix_bits % 8) { | |
| gf_bs_write_int(bs, res, base64_prefix_bits); | |
| } else { | |
| u32 nb_bytes = base64_prefix_bits/8; | |
| if (!big_endian && (nb_bytes==8)) gf_bs_write_u64_le(bs, res); | |
| else if (!big_endian && (nb_bytes==4)) gf_bs_write_u32_le(bs, res); | |
| else if (!big_endian && (nb_bytes==2)) gf_bs_write_u16_le(bs, res); | |
| else | |
| gf_bs_write_int(bs, res, base64_prefix_bits); | |
| } | |
| } | |
| gf_bs_write_data(bs, bs_data_out, res); | |
| gf_free(bs_data); | |
| gf_free(bs_data_out); | |
| } | |
| } | |
| } | |
| exit: | |
| if (bs != bs_orig) { | |
| if (!e) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[XML/NHML] base64 encoding context not closed\n")); | |
| e = GF_NON_COMPLIANT_BITSTREAM; | |
| } | |
| gf_bs_del(bs); | |
| } | |
| return e; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_xml_parse_bit_sequence(GF_XMLNode *bsroot, const char *parent_url, u8 **data, u32 *data_size) | |
| { | |
| GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| if (!bs) return GF_OUT_OF_MEM; | |
| gf_xml_parse_bit_sequence_bs(bsroot, parent_url, NULL, bs); | |
| gf_bs_align(bs); | |
| gf_bs_get_content(bs, data, data_size); | |
| gf_bs_del(bs); | |
| return GF_OK; | |
| } |