diff --git a/keyfile b/keyfile new file mode 100644 index 000000000..8c6c7581d --- /dev/null +++ b/keyfile @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBRaxmcA0 +hFC03ODPJJ4e1PAAAAGAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIHETyvjOv9vv2uBQ +Jmy295Kfsp9Ey2FeR7WRUHpoSDtnAAAAoOYsFHc6DEKDe+RfvMVjkeKcdrertILTbyDba4 +VI+P849BiNcqOztbH98Z6nTy1XE00EcAXYhRI4B8mEPTLa6rcpXKymrDFj8UrWt1VLFPh1 +ruJp1ATVSBVd9THMve0kFyWVF0PXtojCMItZjcgqIEdCloDrUAefMUH4+rhIiEqu9R3+aC +FMABxkQ0jTwJAPAT0EltKF+K6gYTqE79MGDsQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/keyfile.pub b/keyfile.pub new file mode 100644 index 000000000..50e7849a7 --- /dev/null +++ b/keyfile.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHETyvjOv9vv2uBQJmy295Kfsp9Ey2FeR7WRUHpoSDtn rodriguesvivian272@gmail.com diff --git a/linux/pre-build.sh b/linux/pre-build.sh deleted file mode 100755 index 19a3dd365..000000000 --- a/linux/pre-build.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -echo "Obtaining Git commit" -commit=(`git rev-parse HEAD 2>/dev/null`) -if [ -z "$commit" ]; then - echo "Git command not present, trying folder approach" - if [ -d "../.git" ]; then - echo "Git folder found, using HEAD file" - head="`cat ../.git/HEAD`" - ref_pos=(`expr match "$head" 'ref: '`) - if [ $ref_pos -eq 5 ]; then - echo "HEAD file contains a ref, following" - commit=(`cat "../.git/${head:5}"`) - echo "Extracted commit: $commit" - else - echo "HEAD contains a commit, using it" - commit="$head" - echo "Extracted commit: $commit" - fi - fi -fi -if [ -z "$commit" ]; then - commit="Unknown" -fi -builddate=`date --utc --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y-%m-%d` -echo "Storing variables in file" -echo "Commit: $commit" -echo "Date: $builddate" -echo "#ifndef CCX_CCEXTRACTOR_COMPILE_REAL_H" > ../src/lib_ccx/compile_info_real.h -echo "#define CCX_CCEXTRACTOR_COMPILE_REAL_H" >> ../src/lib_ccx/compile_info_real.h -echo "#define GIT_COMMIT \"$commit\"" >> ../src/lib_ccx/compile_info_real.h -echo "#define COMPILE_DATE \"$builddate\"" >> ../src/lib_ccx/compile_info_real.h -echo "#endif" >> ../src/lib_ccx/compile_info_real.h -echo "Stored all in compile_info_real.h" -echo "Done." diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3852799b4..145cf9165 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,8 @@ configure_file ( add_definitions(-DVERSION_FILE_PRESENT -DFT2_BUILD_LIBRARY -DGPAC_DISABLE_VTT -DGPAC_DISABLE_OD_DUMP -DGPAC_DISABLE_REMOTERY -DNO_GZIP) +add_definitions(-DENABLE_HEVC) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) add_definitions(-DGPAC_64_BITS) endif() diff --git a/src/lib_ccx/CMakeLists.txt b/src/lib_ccx/CMakeLists.txt index 4f329bcaa..529ee5aa1 100644 --- a/src/lib_ccx/CMakeLists.txt +++ b/src/lib_ccx/CMakeLists.txt @@ -13,6 +13,7 @@ endif(WIN32) find_package(PkgConfig) pkg_check_modules (GPAC REQUIRED gpac) +list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/lib_ccx") set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${GPAC_INCLUDE_DIRS}) set (EXTRA_LIBS ${EXTRA_LIBS} ${GPAC_LIBRARIES}) @@ -60,6 +61,7 @@ if (WITH_SHARING) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_SHARING") endif (WITH_SHARING) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_HEVC") aux_source_directory ("${PROJECT_SOURCE_DIR}/lib_ccx/" SOURCEFILE) add_library (ccx ${SOURCEFILE} ccx_dtvcc.h ccx_dtvcc.c ccx_encoders_mcc.c ccx_encoders_mcc.h) diff --git a/src/lib_ccx/ccx_common_constants.h b/src/lib_ccx/ccx_common_constants.h index 68d6d52e3..9604f43f5 100644 --- a/src/lib_ccx/ccx_common_constants.h +++ b/src/lib_ccx/ccx_common_constants.h @@ -3,327 +3,468 @@ #include "stdio.h" +// ========== BOOLEAN DEFINITIONS FOR C COMPATIBILITY ========== +// PURPOSE: Define true/false for C (C++ has these built-in) +// NEEDED FOR: Boolean logic throughout CCExtractor codebase #ifndef __cplusplus #define false 0 #define true 1 #endif -extern const char *framerates_types[16]; -extern const double framerates_values[16]; - -extern const char *aspect_ratio_types[16]; -extern const char *pict_types[8]; -extern const char *slice_types[10]; -extern const char *cc_types[4]; - -extern const unsigned char BROADCAST_HEADER[4]; -extern const unsigned char LITTLE_ENDIAN_BOM[2]; -extern const unsigned char UTF8_BOM[3]; -extern const unsigned char DVD_HEADER[8]; -extern const unsigned char lc1[1]; -extern const unsigned char lc2[1]; -extern const unsigned char lc3[2]; +// ========== EXTERNAL VARIABLE DECLARATIONS ========== +// PURPOSE: Declare arrays defined in other .c files for global access +// NEEDED FOR: Video format analysis, aspect ratio detection, frame type identification + +// *** VIDEO FORMAT INFORMATION *** +extern const char *framerates_types[16]; // Frame rate descriptions ("23.976 fps", "29.97 fps", etc.) +extern const double framerates_values[16]; // Exact frame rate values (23.976, 29.97, etc.) +extern const char *aspect_ratio_types[16]; // Aspect ratio strings ("4:3", "16:9", etc.) +extern const char *pict_types[8]; // Picture type names ("I", "P", "B", "D" frames) +extern const char *slice_types[10]; // H.264 slice type descriptions +extern const char *cc_types[4]; // Closed caption types (Field 1/2, etc.) + +// *** FILE FORMAT MAGIC BYTES *** +// NEEDED FOR: File format detection and validation +extern const unsigned char BROADCAST_HEADER[4]; // Broadcast file header signature +extern const unsigned char LITTLE_ENDIAN_BOM[2]; // Little endian byte order mark (FF FE) +extern const unsigned char UTF8_BOM[3]; // UTF-8 byte order mark (EF BB BF) +extern const unsigned char DVD_HEADER[8]; // DVD subtitle stream header +extern const unsigned char lc1[1]; // Line 21 caption data markers +extern const unsigned char lc2[1]; // (specific byte patterns for +extern const unsigned char lc3[2]; // closed caption detection) extern const unsigned char lc4[2]; extern const unsigned char lc5[1]; extern const unsigned char lc6[1]; -extern unsigned char rcwt_header[11]; +// *** CCExtractor NATIVE FORMAT *** +extern unsigned char rcwt_header[11]; // Raw Captions With Time header -#define ONEPASS 120 /* Bytes we can always look ahead without going out of limits */ -#define BUFSIZE (2048*1024+ONEPASS) /* 2 Mb plus the safety pass */ -#define MAX_CLOSED_CAPTION_DATA_PER_PICTURE 32 -#define EIA_708_BUFFER_LENGTH 2048 // TODO: Find out what the real limit is -#define TS_PACKET_PAYLOAD_LENGTH 184 // From specs -#define SUBLINESIZE 2048 // Max. length of a .srt line - TODO: Get rid of this -#define STARTBYTESLENGTH (1024*1024) -#define UTF8_MAX_BYTES 6 -#define XMLRPC_CHUNK_SIZE (64*1024) // 64 Kb per chunk, to avoid too many realloc() +// ========== BUFFER SIZES AND LIMITS ========== +// PURPOSE: Define memory allocation sizes and processing limits +// NEEDED FOR: Safe memory management and preventing buffer overflows +#define ONEPASS 120 // Safety buffer for lookahead parsing (120 bytes) +#define BUFSIZE (2048*1024+ONEPASS) // Main processing buffer (2MB + safety) +#define MAX_CLOSED_CAPTION_DATA_PER_PICTURE 32 // Max CC bytes per video frame +#define EIA_708_BUFFER_LENGTH 2048 // CEA-708 digital caption buffer size +#define TS_PACKET_PAYLOAD_LENGTH 184 // MPEG-TS packet payload size (188 - 4 byte header) +#define SUBLINESIZE 2048 // Maximum length of subtitle line (.srt format) +#define STARTBYTESLENGTH (1024*1024) // Initial file read size for format detection (1MB) +#define UTF8_MAX_BYTES 6 // Maximum bytes in UTF-8 character encoding +#define XMLRPC_CHUNK_SIZE (64*1024) // Network transfer chunk size (64KB) + +// ========== DEBUG MESSAGE CATEGORIES ========== +// PURPOSE: Categorize debug output for selective logging +// NEEDED FOR: Debugging specific components without flooding logs enum ccx_debug_message_types { - /* Each debug message now belongs to one of these types. Use bitmaps in case - we want one message to belong to more than one type. */ - CCX_DMT_PARSE = 1, // Show information related to parsing the container - CCX_DMT_VIDES = 2, // Show video stream related information - CCX_DMT_TIME = 4, // Show GOP and PTS timing information - CCX_DMT_VERBOSE = 8, // Show lots of debugging output - CCX_DMT_DECODER_608 = 0x10, // Show CC-608 decoder debug? - CCX_DMT_708 = 0x20, // Show CC-708 decoder debug? - CCX_DMT_DECODER_XDS = 0x40, // Show XDS decoder debug? - CCX_DMT_CBRAW = 0x80, // Caption blocks with FTS timing - CCX_DMT_GENERIC_NOTICES = 0x100, // Generic, always displayed even if no debug is selected - CCX_DMT_TELETEXT = 0x200, // Show teletext debug? - CCX_DMT_PAT = 0x400, // Program Allocation Table dump - CCX_DMT_PMT = 0x800, // Program Map Table dump - CCX_DMT_LEVENSHTEIN = 0x1000, // Levenshtein distance calculations - CCX_DMT_DVB = 0x2000, // DVB - CCX_DMT_DUMPDEF = 0x4000 // Dump defective TS packets + /* Each debug message belongs to one category. Bitmaps allow multiple categories per message */ + CCX_DMT_PARSE = 1, // Container parsing (TS, MP4, MKV structure) + CCX_DMT_VIDES = 2, // Video stream analysis (GOP, frame types) + CCX_DMT_TIME = 4, // Timing information (PTS, DTS, timestamps) + CCX_DMT_VERBOSE = 8, // Verbose output (detailed processing info) + CCX_DMT_DECODER_608 = 0x10, // CEA-608 closed caption decoder debug + CCX_DMT_708 = 0x20, // CEA-708 digital caption decoder debug + CCX_DMT_DECODER_XDS = 0x40, // XDS (eXtended Data Services) decoder debug + CCX_DMT_CBRAW = 0x80, // Raw caption blocks with timing + CCX_DMT_GENERIC_NOTICES = 0x100, // Important notices (always shown) + CCX_DMT_TELETEXT = 0x200, // European teletext subtitle debug + CCX_DMT_PAT = 0x400, // Program Association Table parsing + CCX_DMT_PMT = 0x800, // Program Map Table parsing (codec detection) + CCX_DMT_LEVENSHTEIN = 0x1000, // Text similarity calculations + CCX_DMT_DVB = 0x2000, // DVB (European) subtitle debug + CCX_DMT_DUMPDEF = 0x4000 // Dump corrupted/defective packets #ifdef ENABLE_SHARING - CCX_DMT_SHARE = 0x8000, // Extracted captions sharing service + CCX_DMT_SHARE = 0x8000, // Caption sharing service debug #endif //ENABLE_SHARING }; -// AVC NAL types +// ========== H.264/AVC NAL UNIT TYPES ========== +// PURPOSE: Identify different NAL (Network Abstraction Layer) unit types in H.264 streams +// NEEDED FOR: Parsing H.264 video to find closed captions in SEI NAL units +// SOURCE: ITU-T H.264 standard enum ccx_avc_nal_types { - CCX_NAL_TYPE_UNSPECIFIED_0 = 0, - CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1 = 1, - CCX_NAL_TYPE_CODED_SLICE_PARTITION_A = 2, - CCX_NAL_TYPE_CODED_SLICE_PARTITION_B = 3, - CCX_NAL_TYPE_CODED_SLICE_PARTITION_C = 4, - CCX_NAL_TYPE_CODED_SLICE_IDR_PICTURE = 5, - CCX_NAL_TYPE_SEI = 6, - CCX_NAL_TYPE_SEQUENCE_PARAMETER_SET_7 = 7, - CCX_NAL_TYPE_PICTURE_PARAMETER_SET = 8, - CCX_NAL_TYPE_ACCESS_UNIT_DELIMITER_9 = 9, - CCX_NAL_TYPE_END_OF_SEQUENCE = 10, - CCX_NAL_TYPE_END_OF_STREAM = 11, - CCX_NAL_TYPE_FILLER_DATA = 12, - CCX_NAL_TYPE_SEQUENCE_PARAMETER_SET_EXTENSION = 13, - CCX_NAL_TYPE_PREFIX_NAL_UNIT = 14, - CCX_NAL_TYPE_SUBSET_SEQUENCE_PARAMETER_SET = 15, - CCX_NAL_TYPE_RESERVED_16 = 16, - CCX_NAL_TYPE_RESERVED_17 = 18, - CCX_NAL_TYPE_RESERVED_18 = 18, - CCX_NAL_TYPE_CODED_SLICE_AUXILIARY_PICTURE = 19, - CCX_NAL_TYPE_CODED_SLICE_EXTENSION = 20, - CCX_NAL_TYPE_RESERVED_21 = 21, - CCX_NAL_TYPE_RESERVED_22 = 22, - CCX_NAL_TYPE_RESERVED_23 = 23, - CCX_NAL_TYPE_UNSPECIFIED_24 = 24, - CCX_NAL_TYPE_UNSPECIFIED_25 = 25, - CCX_NAL_TYPE_UNSPECIFIED_26 = 26, - CCX_NAL_TYPE_UNSPECIFIED_27 = 27, - CCX_NAL_TYPE_UNSPECIFIED_28 = 28, - CCX_NAL_TYPE_UNSPECIFIED_29 = 29, - CCX_NAL_TYPE_UNSPECIFIED_30 = 30, - CCX_NAL_TYPE_UNSPECIFIED_31 = 31 + CCX_NAL_TYPE_UNSPECIFIED_0 = 0, // Unspecified + CCX_NAL_TYPE_CODED_SLICE_NON_IDR_PICTURE_1 = 1, // Regular video slice + CCX_NAL_TYPE_CODED_SLICE_PARTITION_A = 2, // Slice partition A + CCX_NAL_TYPE_CODED_SLICE_PARTITION_B = 3, // Slice partition B + CCX_NAL_TYPE_CODED_SLICE_PARTITION_C = 4, // Slice partition C + CCX_NAL_TYPE_CODED_SLICE_IDR_PICTURE = 5, // I-frame (keyframe) + CCX_NAL_TYPE_SEI = 6, // ← CRITICAL: SEI contains closed captions! + CCX_NAL_TYPE_SEQUENCE_PARAMETER_SET_7 = 7, // SPS (codec parameters) + CCX_NAL_TYPE_PICTURE_PARAMETER_SET = 8, // PPS (picture parameters) + CCX_NAL_TYPE_ACCESS_UNIT_DELIMITER_9 = 9, // Access unit delimiter + CCX_NAL_TYPE_END_OF_SEQUENCE = 10, // End of sequence + CCX_NAL_TYPE_END_OF_STREAM = 11, // End of stream + CCX_NAL_TYPE_FILLER_DATA = 12, // Filler data + CCX_NAL_TYPE_SEQUENCE_PARAMETER_SET_EXTENSION = 13, // SPS extension + CCX_NAL_TYPE_PREFIX_NAL_UNIT = 14, // Prefix NAL unit + CCX_NAL_TYPE_SUBSET_SEQUENCE_PARAMETER_SET = 15, // Subset SPS + CCX_NAL_TYPE_RESERVED_16 = 16, // Reserved types 16-23 + CCX_NAL_TYPE_RESERVED_17 = 18, + CCX_NAL_TYPE_RESERVED_18 = 18, + CCX_NAL_TYPE_CODED_SLICE_AUXILIARY_PICTURE = 19, // Auxiliary picture + CCX_NAL_TYPE_CODED_SLICE_EXTENSION = 20, // Slice extension + CCX_NAL_TYPE_RESERVED_21 = 21, // Reserved types 21-31 + CCX_NAL_TYPE_RESERVED_22 = 22, + CCX_NAL_TYPE_RESERVED_23 = 23, + CCX_NAL_TYPE_UNSPECIFIED_24 = 24, + CCX_NAL_TYPE_UNSPECIFIED_25 = 25, + CCX_NAL_TYPE_UNSPECIFIED_26 = 26, + CCX_NAL_TYPE_UNSPECIFIED_27 = 27, + CCX_NAL_TYPE_UNSPECIFIED_28 = 28, + CCX_NAL_TYPE_UNSPECIFIED_29 = 29, + CCX_NAL_TYPE_UNSPECIFIED_30 = 30, + CCX_NAL_TYPE_UNSPECIFIED_31 = 31 }; +// ========== H.265/HEVC NAL UNIT TYPES ========== +// PURPOSE: Identify different NAL unit types in H.265/HEVC streams +// NEEDED FOR: Parsing HEVC video to find closed captions in SEI NAL units +// SOURCE: ITU-T H.265 standard +// enum ccx_hevc_nal_types +// { +// // *** CODED SLICE NAL UNITS (0-9) *** +// CCX_HEVC_NAL_TRAIL_N = 0, // Coded slice - trailing picture, non-reference +// CCX_HEVC_NAL_TRAIL_R = 1, // Coded slice - trailing picture, reference +// CCX_HEVC_NAL_TSA_N = 2, // Coded slice - temporal sub-layer access, non-reference +// CCX_HEVC_NAL_TSA_R = 3, // Coded slice - temporal sub-layer access, reference +// CCX_HEVC_NAL_STSA_N = 4, // Coded slice - step-wise temporal sub-layer access, non-reference +// CCX_HEVC_NAL_STSA_R = 5, // Coded slice - step-wise temporal sub-layer access, reference +// CCX_HEVC_NAL_RADL_N = 6, // Coded slice - random access decodable leading picture, non-reference +// CCX_HEVC_NAL_RADL_R = 7, // Coded slice - random access decodable leading picture, reference +// CCX_HEVC_NAL_RASL_N = 8, // Coded slice - random access skipped leading picture, non-reference +// CCX_HEVC_NAL_RASL_R = 9, // Coded slice - random access skipped leading picture, reference + +// // *** RESERVED NAL UNITS (10-15) *** +// CCX_HEVC_NAL_RSV_VCL_N10 = 10, // Reserved non-reference VCL NAL unit +// CCX_HEVC_NAL_RSV_VCL_N12 = 12, // Reserved non-reference VCL NAL unit +// CCX_HEVC_NAL_RSV_VCL_N14 = 14, // Reserved non-reference VCL NAL unit +// CCX_HEVC_NAL_RSV_VCL_R11 = 11, // Reserved reference VCL NAL unit +// CCX_HEVC_NAL_RSV_VCL_R13 = 13, // Reserved reference VCL NAL unit +// CCX_HEVC_NAL_RSV_VCL_R15 = 15, // Reserved reference VCL NAL unit + +// // *** KEYFRAME NAL UNITS (16-21) *** +// CCX_HEVC_NAL_BLA_W_LP = 16, // Broken link access with leading pictures +// CCX_HEVC_NAL_BLA_W_RADL = 17, // Broken link access with RADL pictures +// CCX_HEVC_NAL_BLA_N_LP = 18, // Broken link access without leading pictures +// CCX_HEVC_NAL_IDR_W_RADL = 19, // Instantaneous decoder refresh with RADL pictures +// CCX_HEVC_NAL_IDR_N_LP = 20, // Instantaneous decoder refresh without leading pictures +// CCX_HEVC_NAL_CRA_NUT = 21, // Clean random access + +// // *** RESERVED NAL UNITS (22-31) *** +// CCX_HEVC_NAL_RSV_IRAP_VCL22 = 22, // Reserved IRAP VCL NAL unit +// CCX_HEVC_NAL_RSV_IRAP_VCL23 = 23, // Reserved IRAP VCL NAL unit +// // ... (24-31 reserved) + +// // *** PARAMETER SET NAL UNITS (32-34) *** +// CCX_HEVC_NAL_VPS = 32, // ← Video Parameter Set (HEVC-specific) +// CCX_HEVC_NAL_SPS = 33, // ← Sequence Parameter Set +// CCX_HEVC_NAL_PPS = 34, // ← Picture Parameter Set + +// // *** OTHER NON-VCL NAL UNITS *** +// CCX_HEVC_NAL_AUD = 35, // Access Unit Delimiter +// CCX_HEVC_NAL_EOS = 36, // End of Sequence +// CCX_HEVC_NAL_EOB = 37, // End of Bitstream +// CCX_HEVC_NAL_FD = 38, // Filler Data + +// // *** CRITICAL: SEI NAL UNITS (CONTAIN CLOSED CAPTIONS) *** +// CCX_HEVC_NAL_SEI_PREFIX = 39, // ← SEI Prefix - CONTAINS CLOSED CAPTIONS! +// CCX_HEVC_NAL_SEI_SUFFIX = 40, // ← SEI Suffix - CONTAINS CLOSED CAPTIONS! + +// // *** RESERVED NAL UNITS (41-47) *** +// CCX_HEVC_NAL_RSV_NVCL41 = 41, // Reserved non-VCL NAL unit +// CCX_HEVC_NAL_RSV_NVCL42 = 42, // Reserved non-VCL NAL unit +// CCX_HEVC_NAL_RSV_NVCL43 = 43, // Reserved non-VCL NAL unit +// CCX_HEVC_NAL_RSV_NVCL44 = 44, // Reserved non-VCL NAL unit +// CCX_HEVC_NAL_RSV_NVCL45 = 45, // Reserved non-VCL NAL unit +// CCX_HEVC_NAL_RSV_NVCL46 = 46, // Reserved non-VCL NAL unit +// CCX_HEVC_NAL_RSV_NVCL47 = 47, // Reserved non-VCL NAL unit + +// // *** UNSPECIFIED NAL UNITS (48-63) *** +// CCX_HEVC_NAL_UNSPEC48 = 48, // Unspecified NAL unit +// CCX_HEVC_NAL_UNSPEC49 = 49, // Unspecified NAL unit +// // ... (50-63 unspecified) +// CCX_HEVC_NAL_UNSPEC63 = 63 // Unspecified NAL unit +// }; + -// MPEG-2 TS stream types +// ========== *** MPEG TRANSPORT STREAM TYPES *** ========== +// PURPOSE: Identify video/audio codec types in MPEG Transport Streams +// NEEDED FOR: Video codec detection in PMT parsing - THIS IS WHERE H.264 IS DEFINED! +// SOURCE: ISO/IEC 13818-1 Table 2-29 (MPEG-TS standard) enum ccx_stream_type { - CCX_STREAM_TYPE_UNKNOWNSTREAM = 0, - - /* - The later constants are defined by MPEG-TS standard - Explore at: https://exiftool.org/TagNames/M2TS.html - */ - CCX_STREAM_TYPE_VIDEO_MPEG1 = 0x01, - CCX_STREAM_TYPE_VIDEO_MPEG2 = 0x02, - CCX_STREAM_TYPE_AUDIO_MPEG1 = 0x03, - CCX_STREAM_TYPE_AUDIO_MPEG2 = 0x04, - CCX_STREAM_TYPE_PRIVATE_TABLE_MPEG2 = 0x05, - CCX_STREAM_TYPE_PRIVATE_MPEG2 = 0x06, - CCX_STREAM_TYPE_MHEG_PACKETS = 0x07, - CCX_STREAM_TYPE_MPEG2_ANNEX_A_DSM_CC = 0x08, - CCX_STREAM_TYPE_ITU_T_H222_1 = 0x09, - CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_A = 0x0A, - CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_B = 0x0B, - CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_C = 0x0C, - CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_D = 0x0D, - CCX_STREAM_TYPE_AUDIO_AAC = 0x0f, - CCX_STREAM_TYPE_VIDEO_MPEG4 = 0x10, - CCX_STREAM_TYPE_VIDEO_H264 = 0x1b, - CCX_STREAM_TYPE_PRIVATE_USER_MPEG2 = 0x80, - CCX_STREAM_TYPE_AUDIO_AC3 = 0x81, - CCX_STREAM_TYPE_AUDIO_HDMV_DTS = 0x82, - CCX_STREAM_TYPE_AUDIO_DTS = 0x8a + CCX_STREAM_TYPE_UNKNOWNSTREAM = 0, // Unknown or unrecognized stream + + // *** STANDARD MPEG STREAM TYPES *** + // These constants are defined by MPEG-TS standard + // Reference: https://exiftool.org/TagNames/M2TS.html + + // *** VIDEO CODEC TYPES *** + CCX_STREAM_TYPE_VIDEO_MPEG1 = 0x01, // MPEG-1 Video (old standard) + CCX_STREAM_TYPE_VIDEO_MPEG2 = 0x02, // MPEG-2 Video (DVD, broadcast TV) + CCX_STREAM_TYPE_VIDEO_MPEG4 = 0x10, // MPEG-4 Part 2 Video (DivX, Xvid) + CCX_STREAM_TYPE_VIDEO_H264 = 0x1b, // ← H.264/AVC Video (HD TV, Blu-ray) + CCX_STREAM_TYPE_VIDEO_HEVC = 0x24, // HEVC/H.265 Video (4K, modern streaming) + + // *** AUDIO CODEC TYPES *** + CCX_STREAM_TYPE_AUDIO_MPEG1 = 0x03, // MPEG-1 Audio (MP3) + CCX_STREAM_TYPE_AUDIO_MPEG2 = 0x04, // MPEG-2 Audio + CCX_STREAM_TYPE_AUDIO_AAC = 0x0f, // AAC Audio (modern audio) + CCX_STREAM_TYPE_AUDIO_AC3 = 0x81, // AC-3/Dolby Digital Audio + CCX_STREAM_TYPE_AUDIO_HDMV_DTS = 0x82, // DTS Audio (Blu-ray) + CCX_STREAM_TYPE_AUDIO_DTS = 0x8a, // DTS Audio (general) + + // *** DATA AND SUBTITLE STREAMS *** + CCX_STREAM_TYPE_PRIVATE_TABLE_MPEG2 = 0x05, // Private table sections + CCX_STREAM_TYPE_PRIVATE_MPEG2 = 0x06, // Private data (DVB subtitles, teletext) + CCX_STREAM_TYPE_MHEG_PACKETS = 0x07, // MHEG packets (interactive TV) + CCX_STREAM_TYPE_MPEG2_ANNEX_A_DSM_CC = 0x08, // DSM-CC data carousel + CCX_STREAM_TYPE_ITU_T_H222_1 = 0x09, // ITU-T H.222.1 + CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_A = 0x0A, // Multi-protocol encapsulation + CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_B = 0x0B, // DSM-CC U-N messages + CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_C = 0x0C, // Stream descriptors + CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_D = 0x0D, // Sections with CRC + + // *** PRIVATE/USER-DEFINED STREAMS *** + CCX_STREAM_TYPE_PRIVATE_USER_MPEG2 = 0x80, // Private user streams (ATSC captions) }; +// ========== MPEG DESCRIPTOR TYPES ========== +// PURPOSE: Identify descriptor types in MPEG stream descriptors +// NEEDED FOR: Parsing PMT descriptors to find subtitle and language information +// SOURCE: ETSI EN 300 468 standard (European broadcasting standard) enum ccx_mpeg_descriptor { - /* - The later constants are defined by ETSI EN 300 468 standard - Explore at: https://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.11.01_60/en_300468v011101p.pdf - */ - CCX_MPEG_DSC_REGISTRATION = 0x05, - CCX_MPEG_DSC_DATA_STREAM_ALIGNMENT = 0x06, - CCX_MPEG_DSC_ISO639_LANGUAGE = 0x0A, - CCX_MPEG_DSC_VBI_DATA_DESCRIPTOR = 0x45, - CCX_MPEG_DSC_VBI_TELETEXT_DESCRIPTOR = 0x46, - CCX_MPEG_DSC_TELETEXT_DESCRIPTOR = 0x56, - CCX_MPEG_DSC_DVB_SUBTITLE = 0x59, - /* User defined */ - CCX_MPEG_DSC_CAPTION_SERVICE = 0x86, - CCX_MPEG_DESC_DATA_COMP = 0xfd // Consider to change DESC to DSC + // *** STANDARD MPEG DESCRIPTORS *** + CCX_MPEG_DSC_REGISTRATION = 0x05, // Registration descriptor + CCX_MPEG_DSC_DATA_STREAM_ALIGNMENT = 0x06, // Data stream alignment + CCX_MPEG_DSC_ISO639_LANGUAGE = 0x0A, // Language descriptor (3-letter codes) + + // *** TELETEXT AND SUBTITLE DESCRIPTORS *** + CCX_MPEG_DSC_VBI_DATA_DESCRIPTOR = 0x45, // VBI data (teletext, WSS, etc.) + CCX_MPEG_DSC_VBI_TELETEXT_DESCRIPTOR = 0x46, // VBI teletext specifically + CCX_MPEG_DSC_TELETEXT_DESCRIPTOR = 0x56, // DVB teletext descriptor + CCX_MPEG_DSC_DVB_SUBTITLE = 0x59, // DVB bitmap subtitles + + // *** USER-DEFINED DESCRIPTORS *** + CCX_MPEG_DSC_CAPTION_SERVICE = 0x86, // ATSC caption service descriptor + CCX_MPEG_DESC_DATA_COMP = 0xfd // ISDB data component descriptor }; - +// ========== MESSAGE OUTPUT DESTINATIONS ========== +// PURPOSE: Control where messages are sent (quiet, stdout, stderr) +// NEEDED FOR: Message routing and verbosity control enum { - CCX_MESSAGES_QUIET = 0, - CCX_MESSAGES_STDOUT = 1, - CCX_MESSAGES_STDERR = 2 + CCX_MESSAGES_QUIET = 0, // No output + CCX_MESSAGES_STDOUT = 1, // Send to standard output + CCX_MESSAGES_STDERR = 2 // Send to standard error }; +// ========== DATA SOURCE TYPES ========== +// PURPOSE: Identify where input data comes from +// NEEDED FOR: Configuring input handling (file vs network vs stdin) enum ccx_datasource { - CCX_DS_FILE = 0, - CCX_DS_STDIN = 1, - CCX_DS_NETWORK = 2, - CCX_DS_TCP = 3 + CCX_DS_FILE = 0, // Regular file input + CCX_DS_STDIN = 1, // Standard input (pipe) + CCX_DS_NETWORK = 2, // Network stream (UDP) + CCX_DS_TCP = 3 // TCP network stream }; +// ========== OUTPUT FORMAT TYPES ========== +// PURPOSE: Define all supported subtitle output formats +// NEEDED FOR: Format conversion and output generation enum ccx_output_format { - CCX_OF_RAW = 0, - CCX_OF_SRT = 1, - CCX_OF_SAMI = 2, - CCX_OF_TRANSCRIPT = 3, - CCX_OF_RCWT = 4, - CCX_OF_NULL = 5, - CCX_OF_SMPTETT = 6, - CCX_OF_SPUPNG = 7, - CCX_OF_DVDRAW = 8, // See -d at http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/SCC_TOOLS.HTML#CCExtract - CCX_OF_WEBVTT = 9, - CCX_OF_SIMPLE_XML = 10, - CCX_OF_G608 = 11, - CCX_OF_CURL = 12, - CCX_OF_SSA = 13, - CCX_OF_MCC = 14, - CCX_OF_SCC = 15, - CCX_OF_CCD = 16, + CCX_OF_RAW = 0, // Raw closed caption data + CCX_OF_SRT = 1, // SubRip (.srt) format - most common + CCX_OF_SAMI = 2, // SAMI (.smi) format - Microsoft + CCX_OF_TRANSCRIPT = 3, // Plain text transcript + CCX_OF_RCWT = 4, // Raw Captions With Time (CCExtractor native) + CCX_OF_NULL = 5, // No output (processing only) + CCX_OF_SMPTETT = 6, // SMPTE-TT XML format + CCX_OF_SPUPNG = 7, // DVD subtitle images (PNG) + CCX_OF_DVDRAW = 8, // Raw DVD subtitle data + CCX_OF_WEBVTT = 9, // WebVTT format (web standards) + CCX_OF_SIMPLE_XML = 10, // Simple XML format + CCX_OF_G608 = 11, // G608 format + CCX_OF_CURL = 12, // CURL network output + CCX_OF_SSA = 13, // SubStation Alpha format + CCX_OF_MCC = 14, // MacCaption format (broadcast) + CCX_OF_SCC = 15, // Scenarist Closed Caption format + CCX_OF_CCD = 16, // CCD format }; +// ========== OUTPUT DATE/TIME FORMATS ========== +// PURPOSE: Control timestamp display format in subtitles +// NEEDED FOR: Different subtitle formats require different time representations enum ccx_output_date_format { - ODF_NONE = 0, - ODF_HHMMSS = 1, - ODF_SECONDS = 2, - ODF_DATE = 3, - ODF_HHMMSSMS = 4 // HH:MM:SS,MILIS (.srt style) + ODF_NONE = 0, // No timestamps + ODF_HHMMSS = 1, // Hours:Minutes:Seconds + ODF_SECONDS = 2, // Total seconds + ODF_DATE = 3, // Full date/time + ODF_HHMMSSMS = 4 // Hours:Minutes:Seconds,Milliseconds (.srt style) }; +// ========== STREAM MODE TYPES ========== +// PURPOSE: Identify the container/wrapper format of input streams +// NEEDED FOR: Selecting appropriate demuxer and parser enum ccx_stream_mode_enum { - CCX_SM_ELEMENTARY_OR_NOT_FOUND = 0, - CCX_SM_TRANSPORT = 1, - CCX_SM_PROGRAM = 2, - CCX_SM_ASF = 3, - CCX_SM_MCPOODLESRAW = 4, - CCX_SM_RCWT = 5, // Raw Captions With Time, not used yet. - CCX_SM_MYTH = 6, // Use the myth loop - CCX_SM_MP4 = 7, // MP4, ISO- + CCX_SM_ELEMENTARY_OR_NOT_FOUND = 0, // Raw elementary stream or unknown + CCX_SM_TRANSPORT = 1, // MPEG Transport Stream (.ts, .m2ts) + CCX_SM_PROGRAM = 2, // MPEG Program Stream (.mpg, .vob) + CCX_SM_ASF = 3, // Advanced Systems Format (.wmv, .asf) + CCX_SM_MCPOODLESRAW = 4, // McPoodle's raw format + CCX_SM_RCWT = 5, // Raw Captions With Time + CCX_SM_MYTH = 6, // MythTV recording format + CCX_SM_MP4 = 7, // MP4/MOV container #ifdef WTV_DEBUG - CCX_SM_HEX_DUMP = 8, // Hexadecimal dump generated by wtvccdump + CCX_SM_HEX_DUMP = 8, // Hexadecimal dump (debug) #endif - CCX_SM_WTV = 9, + CCX_SM_WTV = 9, // Windows TV format #ifdef ENABLE_FFMPEG - CCX_SM_FFMPEG = 10, + CCX_SM_FFMPEG = 10, // FFmpeg-handled formats #endif - CCX_SM_GXF = 11, - CCX_SM_MKV = 12, - CCX_SM_MXF = 13, + CCX_SM_GXF = 11, // General Exchange Format (broadcast) + CCX_SM_MKV = 12, // Matroska Video (.mkv) + CCX_SM_MXF = 13, // Material Exchange Format (broadcast) - CCX_SM_AUTODETECT = 16 + CCX_SM_AUTODETECT = 16 // Auto-detect format }; +// ========== CHARACTER ENCODING TYPES ========== +// PURPOSE: Handle different text encodings in subtitles +// NEEDED FOR: International character support and encoding conversion enum ccx_encoding_type { - CCX_ENC_UNICODE = 0, - CCX_ENC_LATIN_1 = 1, - CCX_ENC_UTF_8 = 2, - CCX_ENC_ASCII = 3 + CCX_ENC_UNICODE = 0, // Unicode (UTF-16) + CCX_ENC_LATIN_1 = 1, // Latin-1/ISO-8859-1 (Western Europe) + CCX_ENC_UTF_8 = 2, // UTF-8 (universal) + CCX_ENC_ASCII = 3 // ASCII (7-bit) }; +// ========== BUFFER DATA TYPES ========== +// PURPOSE: Identify the type of data in processing buffers +// NEEDED FOR: Routing data to appropriate decoders enum ccx_bufferdata_type { - CCX_UNKNOWN = 0, - CCX_PES = 1, - CCX_RAW = 2, - CCX_H264 = 3, - CCX_HAUPPAGE = 4, - CCX_TELETEXT = 5, - CCX_PRIVATE_MPEG2_CC = 6, - CCX_DVB_SUBTITLE = 7, - CCX_ISDB_SUBTITLE = 8, - /* BUffer where cc data contain 3 byte cc_valid ccdata 1 ccdata 2 */ - CCX_RAW_TYPE = 9, - CCX_DVD_SUBTITLE = 10 + CCX_UNKNOWN = 0, // Unknown data type + CCX_PES = 1, // MPEG-2 PES packets (general video) + CCX_RAW = 2, // Raw caption data + CCX_H264 = 3, // H.264 video data (needs NAL parsing) + CCX_HAUPPAGE = 4, // Hauppauge capture card format + CCX_TELETEXT = 5, // European teletext subtitles + CCX_PRIVATE_MPEG2_CC = 6, // MPEG-2 private stream closed captions + CCX_DVB_SUBTITLE = 7, // DVB bitmap subtitles + CCX_ISDB_SUBTITLE = 8, // ISDB subtitles (Japanese) + CCX_RAW_TYPE = 9, // Raw 3-byte CC data format + CCX_DVD_SUBTITLE = 10, // DVD subtitle data + CCX_HEVC = 11, // HEVC/H.265 video data (needs HEVC NAL parsing) }; +// ========== VIDEO FRAME TYPES ========== +// PURPOSE: Identify different types of video frames +// NEEDED FOR: Video analysis and timing calculations enum ccx_frame_type { - CCX_FRAME_TYPE_RESET_OR_UNKNOWN = 0, - CCX_FRAME_TYPE_I_FRAME = 1, - CCX_FRAME_TYPE_P_FRAME = 2, - CCX_FRAME_TYPE_B_FRAME = 3, - CCX_FRAME_TYPE_D_FRAME = 4 + CCX_FRAME_TYPE_RESET_OR_UNKNOWN = 0, // Unknown or reset frame + CCX_FRAME_TYPE_I_FRAME = 1, // I-frame (keyframe, complete picture) + CCX_FRAME_TYPE_P_FRAME = 2, // P-frame (predicted, references previous) + CCX_FRAME_TYPE_B_FRAME = 3, // B-frame (bidirectional, references both directions) + CCX_FRAME_TYPE_D_FRAME = 4 // D-frame (DC-only, low quality) }; +// ========== BOOLEAN TYPE DEFINITION ========== +// PURPOSE: Define a proper boolean type with undefined state +// NEEDED FOR: Three-state logic (true/false/unknown) typedef enum { - NO = 0, - YES = 1, - UNDEFINED = 0xff + NO = 0, // False + YES = 1, // True + UNDEFINED = 0xff // Unknown/uninitialized state } bool_t; +// ========== CODEC TYPES ========== +// PURPOSE: Identify which subtitle/caption codec to use +// NEEDED FOR: Decoder selection and codec-specific processing enum ccx_code_type { - CCX_CODEC_ANY = 0, - CCX_CODEC_TELETEXT = 1, - CCX_CODEC_DVB = 2, - CCX_CODEC_ISDB_CC = 3, - CCX_CODEC_ATSC_CC = 4, - CCX_CODEC_NONE = 5 + CCX_CODEC_ANY = 0, // Accept any codec (auto-detect) + CCX_CODEC_TELETEXT = 1, // European teletext subtitles + CCX_CODEC_DVB = 2, // DVB bitmap subtitles + CCX_CODEC_ISDB_CC = 3, // ISDB closed captions (Japanese) + CCX_CODEC_ATSC_CC = 4, // ATSC closed captions (North American) + CCX_CODEC_NONE = 5 // No codec (skip processing) }; -/* Caption Distribution Packet */ +// ========== CDP (CAPTION DISTRIBUTION PACKET) SECTION TYPES ========== +// PURPOSE: Identify sections within CDP packets for CEA-708 captions +// NEEDED FOR: Parsing SMPTE ST 334 Caption Distribution Packets +// SOURCE: SMPTE ST 334 standard (broadcast caption distribution) enum cdp_section_type { - /* - The later constants are defined by SMPTE ST 334 - Purchase for 80$ at: https://ieeexplore.ieee.org/document/8255806 - */ - CDP_SECTION_DATA = 0x72, - CDP_SECTION_SVC_INFO = 0x73, - CDP_SECTION_FOOTER = 0x74 + CDP_SECTION_DATA = 0x72, // Caption data section + CDP_SECTION_SVC_INFO = 0x73, // Service information section + CDP_SECTION_FOOTER = 0x74 // Footer section }; +// ========== TELETEXT VALIDATION MACRO ========== +// PURPOSE: Check if a descriptor tag indicates teletext content +// NEEDED FOR: Teletext stream detection in PMT parsing /* - * This Macro check whether descriptor tag is valid for teletext + * This Macro checks whether descriptor tag is valid for teletext * codec or not. * * @param desc descriptor tag given for each stream - * - * @return if descriptor tag is valid then it return 1 otherwise 0 - * + * @return 1 if descriptor tag is valid for teletext, 0 otherwise */ - #define IS_VALID_TELETEXT_DESC(desc) ( ((desc) == CCX_MPEG_DSC_VBI_DATA_DESCRIPTOR )|| \ ( (desc) == CCX_MPEG_DSC_VBI_TELETEXT_DESCRIPTOR ) || \ ( (desc) == CCX_MPEG_DSC_TELETEXT_DESCRIPTOR ) ) + +// ========== CODEC FEASIBILITY MACRO ========== +// PURPOSE: Determine if a codec should be processed based on user selection +// NEEDED FOR: Codec filtering when user specifies include/exclude preferences /* - * This macro to be used when you want to find out whether you - * should parse f_sel subtitle codec type or not + * This macro determines whether to process a specific subtitle codec type * - * @param u_sel pass the codec selected by user to be searched in - * all elementary stream, we ignore the not to be selected stream - * if we find stream this is selected stream. since setting - * selected stream and not selected to same codec does not - * make ay sense. + * @param u_sel codec selected by user to be processed (CCX_CODEC_ANY = process all) + * @param u_nsel codec selected by user NOT to be processed + * @param f_sel codec being tested for feasibility + * + * @return 1 if codec should be processed, 0 if it should be skipped * - * @param u_nsel pass the codec selected by user not to be parsed - * we give false value if f_sel is equal to n_sel - * and vice versa true if ... - * - * @param f_sel pass the codec name whom you are testing to be feasible - * to parse. + * Logic: Process if (user wants any codec AND this codec is not excluded) + * OR user specifically requested this codec */ #define IS_FEASIBLE(u_sel,u_nsel,f_sel) ( ( (u_sel) == CCX_CODEC_ANY && (u_nsel) != (f_sel) ) || (u_sel) == (f_sel) ) -#define CCX_TXT_FORBIDDEN 0 // Ignore teletext packets -#define CCX_TXT_AUTO_NOT_YET_FOUND 1 -#define CCX_TXT_IN_USE 2 // Positive auto-detected, or forced, etc - -#define NB_LANGUAGE 100 -extern const char *language[NB_LANGUAGE]; - -#define DEF_VAL_STARTCREDITSNOTBEFORE "0" -// To catch the theme after the teaser in TV shows -#define DEF_VAL_STARTCREDITSNOTAFTER "5:00" -#define DEF_VAL_STARTCREDITSFORATLEAST "2" -#define DEF_VAL_STARTCREDITSFORATMOST "5" -#define DEF_VAL_ENDCREDITSFORATLEAST "2" -#define DEF_VAL_ENDCREDITSFORATMOST "5" -#endif + +// ========== TELETEXT PROCESSING STATES ========== +// PURPOSE: Track teletext processing state machine +// NEEDED FOR: Teletext auto-detection and processing control +#define CCX_TXT_FORBIDDEN 0 // Ignore teletext packets (user disabled) +#define CCX_TXT_AUTO_NOT_YET_FOUND 1 // Auto-detection mode, not found yet +#define CCX_TXT_IN_USE 2 // Teletext active (auto-detected or forced) + +// ========== LANGUAGE SUPPORT ========== +// PURPOSE: Support international language detection and processing +// NEEDED FOR: Multi-language subtitle handling +#define NB_LANGUAGE 100 // Maximum number of supported languages +extern const char *language[NB_LANGUAGE]; // Language name array + +// ========== CREDITS DETECTION DEFAULTS ========== +// PURPOSE: Default timing values for automatic credits detection +// NEEDED FOR: Identifying opening and closing credits in TV shows/movies +#define DEF_VAL_STARTCREDITSNOTBEFORE "0" // Don't look for opening credits before this time +#define DEF_VAL_STARTCREDITSNOTAFTER "5:00" // Don't look for opening credits after 5 minutes +#define DEF_VAL_STARTCREDITSFORATLEAST "2" // Opening credits must last at least 2 seconds +#define DEF_VAL_STARTCREDITSFORATMOST "5" // Opening credits must not last more than 5 seconds +#define DEF_VAL_ENDCREDITSFORATLEAST "2" // Closing credits must last at least 2 seconds +#define DEF_VAL_ENDCREDITSFORATMOST "5" // Closing credits must not last more than 5 seconds + +#endif // CCX_CONSTANTS_H diff --git a/src/lib_ccx/general_loop.c b/src/lib_ccx/general_loop.c index 109bae775..e787d4257 100644 --- a/src/lib_ccx/general_loop.c +++ b/src/lib_ccx/general_loop.c @@ -17,6 +17,7 @@ #include "ccx_gxf.h" #include "dvd_subtitle_decoder.h" #include "ccx_demuxer_mxf.h" +#include "hevc_functions.h" // Add this include int end_of_file = 0; // End of file? @@ -720,6 +721,12 @@ int process_data(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, str dec_ctx->in_bufferdatatype = CCX_H264; got = process_avc(enc_ctx, dec_ctx, data_node->buffer, data_node->len, dec_sub); } + // *** ADDED HEVC SUPPORT HERE: *** + else if (data_node->bufferdatatype == CCX_HEVC) // HEVC data from TS file + { + dec_ctx->in_bufferdatatype = CCX_HEVC; // Set decoder context to HEVC mode + got = process_hevc(enc_ctx, dec_ctx, data_node->buffer, data_node->len, dec_sub); + } else if (data_node->bufferdatatype == CCX_RAW_TYPE) { got = process_raw_with_field(dec_ctx, dec_sub, data_node->buffer, data_node->len); diff --git a/src/lib_ccx/hevc_functions.c b/src/lib_ccx/hevc_functions.c new file mode 100644 index 000000000..f324bf012 --- /dev/null +++ b/src/lib_ccx/hevc_functions.c @@ -0,0 +1,889 @@ +#include "lib_ccx.h" +#include "ccx_common_option.h" +#include "utility.h" +#include +#include "hevc_functions.h" + +#define hvprint(...) dbg_print(CCX_DMT_VIDES, __VA_ARGS__) + +static void parse_hevc_cc_sei(struct hevc_ctx *ctx, unsigned char *cc_buf, unsigned char *cc_end); +static void copy_hevc_cc_data(struct hevc_ctx *ctx, char *source, int cc_count); + +// HEVC/H.265 NAL Unit Types (ISO/IEC 23008-2) +enum ccx_hevc_nal_types +{ + // VCL NAL unit types + CCX_HEVC_NAL_TRAIL_N = 0, // Coded slice segment of a non-TSA, non-STSA trailing picture + CCX_HEVC_NAL_TRAIL_R = 1, // Coded slice segment of a non-TSA, non-STSA trailing picture + CCX_HEVC_NAL_TSA_N = 2, // Coded slice segment of a TSA picture + CCX_HEVC_NAL_TSA_R = 3, // Coded slice segment of a TSA picture + CCX_HEVC_NAL_STSA_N = 4, // Coded slice segment of an STSA picture + CCX_HEVC_NAL_STSA_R = 5, // Coded slice segment of an STSA picture + CCX_HEVC_NAL_RADL_N = 6, // Coded slice segment of a RADL picture + CCX_HEVC_NAL_RADL_R = 7, // Coded slice segment of a RADL picture + CCX_HEVC_NAL_RASL_N = 8, // Coded slice segment of a RASL picture + CCX_HEVC_NAL_RASL_R = 9, // Coded slice segment of a RASL picture + CCX_HEVC_NAL_BLA_W_LP = 16, // Coded slice segment of a BLA picture + CCX_HEVC_NAL_BLA_W_RADL = 17, // Coded slice segment of a BLA picture + CCX_HEVC_NAL_BLA_N_LP = 18, // Coded slice segment of a BLA picture + CCX_HEVC_NAL_IDR_W_RADL = 19, // Coded slice segment of an IDR picture + CCX_HEVC_NAL_IDR_N_LP = 20, // Coded slice segment of an IDR picture + CCX_HEVC_NAL_CRA_NUT = 21, // Coded slice segment of a CRA picture + + // Non-VCL NAL unit types + CCX_HEVC_NAL_VPS = 32, // Video parameter set + CCX_HEVC_NAL_SPS = 33, // Sequence parameter set + CCX_HEVC_NAL_PPS = 34, // Picture parameter set + CCX_HEVC_NAL_AUD = 35, // Access unit delimiter + CCX_HEVC_NAL_EOS_NUT = 36, // End of sequence + CCX_HEVC_NAL_EOB_NUT = 37, // End of bitstream + CCX_HEVC_NAL_FD_NUT = 38, // Filler data + CCX_HEVC_NAL_PREFIX_SEI = 39, // Prefix SEI + CCX_HEVC_NAL_SUFFIX_SEI = 40 // Suffix SEI +}; + +// HEVC Context Structure +struct hevc_ctx +{ + // Caption data buffer + unsigned char *cc_data; + long cc_databufsize; + int cc_count; + int cc_buffer_saved; + + // Parameter sets + int got_vps; + int got_sps; + int got_pps; + + // Video parameter set data + int vps_max_layers_minus1; + int vps_max_sub_layers_minus1; + int vps_temporal_id_nesting_flag; + + // Sequence parameter set data + int sps_video_parameter_set_id; + int sps_max_sub_layers_minus1; + int sps_temporal_id_nesting_flag; + int pic_width_in_luma_samples; + int pic_height_in_luma_samples; + int log2_max_pic_order_cnt_lsb_minus4; + int log2_max_pic_order_cnt_lsb; + + // Picture parameter set data + int pps_pic_parameter_set_id; + int pps_seq_parameter_set_id; + + // Frame tracking + int frame_num; + int lastframe_num; + LLONG last_pic_order_cnt_lsb; + LLONG last_slice_pts; + + // Statistics + int ccblocks_in_hevc_total; + int ccblocks_in_hevc_lost; + int num_jump_in_frames; + int num_unexpected_sei_length; +}; + +//============================================================================= +// FUNCTION: init_hevc +// PURPOSE: Initialize HEVC context structure +// RETURNS: Pointer to initialized HEVC context or NULL on failure +//============================================================================= +struct hevc_ctx *init_hevc(void) +{ + struct hevc_ctx *ctx = malloc(sizeof(struct hevc_ctx)); + if (!ctx) + return NULL; + + // Allocate CC data buffer + ctx->cc_data = (unsigned char *)malloc(1024); + if (!ctx->cc_data) + { + free(ctx); + return NULL; + } + + // Initialize CC data fields + ctx->cc_count = 0; + ctx->cc_databufsize = 1024; + ctx->cc_buffer_saved = CCX_TRUE; + + // Initialize parameter set flags + ctx->got_vps = 0; + ctx->got_sps = 0; + ctx->got_pps = 0; + + // Initialize VPS data + ctx->vps_max_layers_minus1 = 0; + ctx->vps_max_sub_layers_minus1 = 0; + ctx->vps_temporal_id_nesting_flag = 0; + + // Initialize SPS data + ctx->sps_video_parameter_set_id = 0; + ctx->sps_max_sub_layers_minus1 = 0; + ctx->sps_temporal_id_nesting_flag = 0; + ctx->pic_width_in_luma_samples = 0; + ctx->pic_height_in_luma_samples = 0; + ctx->log2_max_pic_order_cnt_lsb_minus4 = 0; + ctx->log2_max_pic_order_cnt_lsb = 4; // Default minimum value + + // Initialize PPS data + ctx->pps_pic_parameter_set_id = 0; + ctx->pps_seq_parameter_set_id = 0; + + // Initialize frame tracking + ctx->frame_num = -1; + ctx->lastframe_num = -1; + ctx->last_pic_order_cnt_lsb = -1; + ctx->last_slice_pts = -1; + + // Initialize statistics + ctx->ccblocks_in_hevc_total = 0; + ctx->ccblocks_in_hevc_lost = 0; + ctx->num_jump_in_frames = 0; + ctx->num_unexpected_sei_length = 0; + + return ctx; +} + +//============================================================================= +// FUNCTION: dinit_hevc +// PURPOSE: Cleanup and free HEVC context +// PARAMETERS: ctx - Pointer to HEVC context pointer +//============================================================================= +void dinit_hevc(struct hevc_ctx **ctx) +{ + struct hevc_ctx *lctx = *ctx; + if (!lctx) + return; + + // Print statistics if any CC blocks were lost + if (lctx->ccblocks_in_hevc_lost > 0) + { + mprint("Total caption blocks received (HEVC): %d\n", lctx->ccblocks_in_hevc_total); + mprint("Total caption blocks lost (HEVC): %d\n", lctx->ccblocks_in_hevc_lost); + } + + // Free allocated memory + freep(&lctx->cc_data); + freep(ctx); +} + +//============================================================================= +// FUNCTION: remove_hevc_emulation_prevention +// PURPOSE: Remove emulation prevention bytes (0x000003) from HEVC NAL units +// PARAMETERS: from - Source buffer, to - End of source buffer +// RETURNS: Pointer to end of processed data or NULL on error +//============================================================================= +static unsigned char *remove_hevc_emulation_prevention(unsigned char *from, unsigned char *to) +{ + unsigned char *src = from; + unsigned char *dst = from; + int consecutive_zeros = 0; + + while (src < to) + { + if (consecutive_zeros == 2 && *src == 0x03) + { + // Skip emulation prevention byte + src++; + consecutive_zeros = 0; + if (src >= to) + break; + } + + *dst = *src; + + if (*src == 0x00) + consecutive_zeros++; + else + consecutive_zeros = 0; + + src++; + dst++; + } + + return dst; +} + +//============================================================================= +// FUNCTION: parse_hevc_vps +// PURPOSE: Parse HEVC Video Parameter Set +// PARAMETERS: ctx - HEVC context, vps_buf - VPS data buffer, vps_end - End of buffer +//============================================================================= +static void parse_hevc_vps(struct hevc_ctx *ctx, unsigned char *vps_buf, unsigned char *vps_end) +{ + struct bitstream bs; + if (init_bitstream(&bs, vps_buf, vps_end)) + { + mprint("Failed to initialize bitstream for HEVC VPS parsing.\n"); + return; + } + + hvprint("HEVC VIDEO PARAMETER SET\n"); + + // vps_video_parameter_set_id (4 bits) + LLONG vps_id = read_int_unsigned(&bs, 4); + hvprint("vps_video_parameter_set_id = %lld\n", vps_id); + + // vps_base_layer_internal_flag (1 bit) + LLONG base_layer_internal_flag = read_int_unsigned(&bs, 1); + hvprint("vps_base_layer_internal_flag = %lld\n", base_layer_internal_flag); + + // vps_base_layer_available_flag (1 bit) + LLONG base_layer_available_flag = read_int_unsigned(&bs, 1); + hvprint("vps_base_layer_available_flag = %lld\n", base_layer_available_flag); + + // vps_max_layers_minus1 (6 bits) + ctx->vps_max_layers_minus1 = (int)read_int_unsigned(&bs, 6); + hvprint("vps_max_layers_minus1 = %d\n", ctx->vps_max_layers_minus1); + + // vps_max_sub_layers_minus1 (3 bits) + ctx->vps_max_sub_layers_minus1 = (int)read_int_unsigned(&bs, 3); + hvprint("vps_max_sub_layers_minus1 = %d\n", ctx->vps_max_sub_layers_minus1); + + // vps_temporal_id_nesting_flag (1 bit) + ctx->vps_temporal_id_nesting_flag = (int)read_int_unsigned(&bs, 1); + hvprint("vps_temporal_id_nesting_flag = %d\n", ctx->vps_temporal_id_nesting_flag); + + // vps_reserved_0xffff_16bits (16 bits) + LLONG reserved = read_int_unsigned(&bs, 16); + hvprint("vps_reserved_0xffff_16bits = 0x%04llX\n", reserved); + + ctx->got_vps = 1; + hvprint("HEVC VPS parsed successfully\n"); +} + +//============================================================================= +// FUNCTION: parse_hevc_sps +// PURPOSE: Parse HEVC Sequence Parameter Set +// PARAMETERS: ctx - HEVC context, sps_buf - SPS data buffer, sps_end - End of buffer +//============================================================================= +static void parse_hevc_sps(struct hevc_ctx *ctx, unsigned char *sps_buf, unsigned char *sps_end) +{ + struct bitstream bs; + if (init_bitstream(&bs, sps_buf, sps_end)) + { + mprint("Failed to initialize bitstream for HEVC SPS parsing.\n"); + return; + } + + hvprint("HEVC SEQUENCE PARAMETER SET\n"); + + // sps_video_parameter_set_id (4 bits) + ctx->sps_video_parameter_set_id = (int)read_int_unsigned(&bs, 4); + hvprint("sps_video_parameter_set_id = %d\n", ctx->sps_video_parameter_set_id); + + // sps_max_sub_layers_minus1 (3 bits) + ctx->sps_max_sub_layers_minus1 = (int)read_int_unsigned(&bs, 3); + hvprint("sps_max_sub_layers_minus1 = %d\n", ctx->sps_max_sub_layers_minus1); + + // sps_temporal_id_nesting_flag (1 bit) + ctx->sps_temporal_id_nesting_flag = (int)read_int_unsigned(&bs, 1); + hvprint("sps_temporal_id_nesting_flag = %d\n", ctx->sps_temporal_id_nesting_flag); + + // Skip profile_tier_level structure for now + // This is complex and not needed for CC extraction + + // sps_seq_parameter_set_id (ue(v)) + LLONG sps_id = read_exp_golomb_unsigned(&bs); + hvprint("sps_seq_parameter_set_id = %lld\n", sps_id); + + // chroma_format_idc (ue(v)) + LLONG chroma_format = read_exp_golomb_unsigned(&bs); + hvprint("chroma_format_idc = %lld\n", chroma_format); + + if (chroma_format == 3) + { + // separate_colour_plane_flag (1 bit) + LLONG separate_colour = read_int_unsigned(&bs, 1); + hvprint("separate_colour_plane_flag = %lld\n", separate_colour); + } + + // pic_width_in_luma_samples (ue(v)) + ctx->pic_width_in_luma_samples = (int)read_exp_golomb_unsigned(&bs); + hvprint("pic_width_in_luma_samples = %d\n", ctx->pic_width_in_luma_samples); + + // pic_height_in_luma_samples (ue(v)) + ctx->pic_height_in_luma_samples = (int)read_exp_golomb_unsigned(&bs); + hvprint("pic_height_in_luma_samples = %d\n", ctx->pic_height_in_luma_samples); + + // conformance_window_flag (1 bit) + LLONG conformance_window = read_int_unsigned(&bs, 1); + hvprint("conformance_window_flag = %lld\n", conformance_window); + + if (conformance_window) + { + // Skip conformance window parameters + LLONG left_offset = read_exp_golomb_unsigned(&bs); + LLONG right_offset = read_exp_golomb_unsigned(&bs); + LLONG top_offset = read_exp_golomb_unsigned(&bs); + LLONG bottom_offset = read_exp_golomb_unsigned(&bs); + hvprint("Conformance window: left=%lld right=%lld top=%lld bottom=%lld\n", + left_offset, right_offset, top_offset, bottom_offset); + } + + // bit_depth_luma_minus8 (ue(v)) + LLONG bit_depth_luma = read_exp_golomb_unsigned(&bs); + hvprint("bit_depth_luma_minus8 = %lld\n", bit_depth_luma); + + // bit_depth_chroma_minus8 (ue(v)) + LLONG bit_depth_chroma = read_exp_golomb_unsigned(&bs); + hvprint("bit_depth_chroma_minus8 = %lld\n", bit_depth_chroma); + + // log2_max_pic_order_cnt_lsb_minus4 (ue(v)) + ctx->log2_max_pic_order_cnt_lsb_minus4 = (int)read_exp_golomb_unsigned(&bs); + ctx->log2_max_pic_order_cnt_lsb = ctx->log2_max_pic_order_cnt_lsb_minus4 + 4; + hvprint("log2_max_pic_order_cnt_lsb_minus4 = %d (actual = %d)\n", + ctx->log2_max_pic_order_cnt_lsb_minus4, ctx->log2_max_pic_order_cnt_lsb); + + ctx->got_sps = 1; + hvprint("HEVC SPS parsed successfully\n"); +} + +//============================================================================= +// FUNCTION: parse_hevc_pps +// PURPOSE: Parse HEVC Picture Parameter Set +// PARAMETERS: ctx - HEVC context, pps_buf - PPS data buffer, pps_end - End of buffer +//============================================================================= +static void parse_hevc_pps(struct hevc_ctx *ctx, unsigned char *pps_buf, unsigned char *pps_end) +{ + struct bitstream bs; + if (init_bitstream(&bs, pps_buf, pps_end)) + { + mprint("Failed to initialize bitstream for HEVC PPS parsing.\n"); + return; + } + + hvprint("HEVC PICTURE PARAMETER SET\n"); + + // pps_pic_parameter_set_id (ue(v)) + ctx->pps_pic_parameter_set_id = (int)read_exp_golomb_unsigned(&bs); + hvprint("pps_pic_parameter_set_id = %d\n", ctx->pps_pic_parameter_set_id); + + // pps_seq_parameter_set_id (ue(v)) + ctx->pps_seq_parameter_set_id = (int)read_exp_golomb_unsigned(&bs); + hvprint("pps_seq_parameter_set_id = %d\n", ctx->pps_seq_parameter_set_id); + + // Skip other PPS parameters for now - they're not needed for CC extraction + + ctx->got_pps = 1; + hvprint("HEVC PPS parsed successfully\n"); +} + +//============================================================================= +// FUNCTION: parse_hevc_sei +// PURPOSE: Parse HEVC Supplemental Enhancement Information +// PARAMETERS: ctx - HEVC context, sei_buf - SEI data buffer, sei_end - End of buffer +//============================================================================= +static void parse_hevc_sei(struct hevc_ctx *ctx, unsigned char *sei_buf, unsigned char *sei_end) +{ + unsigned char *tbuf = sei_buf; + + hvprint("HEVC SEI MESSAGE\n"); + + while (tbuf < sei_end - 1) // -1 for trailing byte + { + // Parse SEI message header + int payload_type = 0; + while (*tbuf == 0xff) + { + payload_type += 255; + tbuf++; + if (tbuf >= sei_end) return; + } + payload_type += *tbuf; + tbuf++; + + int payload_size = 0; + while (*tbuf == 0xff) + { + payload_size += 255; + tbuf++; + if (tbuf >= sei_end) return; + } + payload_size += *tbuf; + tbuf++; + + hvprint("SEI payload_type = %d, payload_size = %d\n", payload_type, payload_size); + + unsigned char *payload_start = tbuf; + tbuf += payload_size; + + if (tbuf > sei_end) + { + mprint("Warning: HEVC SEI payload extends beyond buffer\n"); + ctx->num_unexpected_sei_length++; + break; + } + + // Process closed caption SEI messages (payload_type 4 for user_data_registered_itu_t_t35) + if (payload_type == 4) + { + parse_hevc_cc_sei(ctx, payload_start, payload_start + payload_size); + } + // Handle other SEI message types as needed + } +} + +//============================================================================= +// FUNCTION: parse_hevc_cc_sei +// PURPOSE: Parse closed caption data from HEVC SEI message +// PARAMETERS: ctx - HEVC context, cc_buf - CC data buffer, cc_end - End of buffer +//============================================================================= +static void parse_hevc_cc_sei(struct hevc_ctx *ctx, unsigned char *cc_buf, unsigned char *cc_end) +{ + unsigned char *tbuf = cc_buf; + + if (tbuf + 3 > cc_end) + return; + + // Check for ITU-T T.35 country code (0xB5 for North America) + int country_code = *tbuf++; + if (country_code != 0xB5) + { + hvprint("Unsupported country code in HEVC CC SEI: 0x%02X\n", country_code); + return; + } + + // Provider code (2 bytes) + int provider_code = (*tbuf << 8) | *(tbuf + 1); + tbuf += 2; + + hvprint("HEVC CC SEI: country_code=0x%02X, provider_code=0x%04X\n", + country_code, provider_code); + + // Handle different provider codes + switch (provider_code) + { + case 0x002F: // Direct TV + case 0x0031: // ATSC + { + if (tbuf + 4 > cc_end) + return; + + // Check for ATSC identifier "GA94" + if (tbuf[0] == 'G' && tbuf[1] == 'A' && tbuf[2] == '9' && tbuf[3] == '4') + { + tbuf += 4; + if (tbuf >= cc_end) + return; + + // user_data_type_code + int user_data_type = *tbuf++; + hvprint("user_data_type_code = 0x%02X\n", user_data_type); + + if (user_data_type == 0x03) // cc_data + { + if (tbuf + 2 > cc_end) + return; + + // cc_data structure + int cc_count = *tbuf & 0x1F; + int process_cc_data_flag = (*tbuf & 0x40) >> 6; + tbuf += 2; // Skip cc_count byte and reserved byte + + hvprint("cc_count = %d, process_flag = %d\n", cc_count, process_cc_data_flag); + + if (!process_cc_data_flag) + { + hvprint("process_cc_data_flag is 0, skipping\n"); + return; + } + + if (tbuf + cc_count * 3 > cc_end) + { + mprint("Warning: HEVC CC data extends beyond buffer\n"); + return; + } + + // Check for end marker + if (tbuf[cc_count * 3] != 0xFF) + { + mprint("Warning: HEVC CC end marker missing\n"); + return; + } + + // Store CC data + copy_hevc_cc_data(ctx, (char *)tbuf, cc_count); + } + } + break; + } + default: + hvprint("Unsupported HEVC CC provider code: 0x%04X\n", provider_code); + break; + } +} + +//============================================================================= +// FUNCTION: copy_hevc_cc_data +// PURPOSE: Copy closed caption data to HEVC context buffer +// PARAMETERS: ctx - HEVC context, source - CC data, cc_count - Number of CC triplets +//============================================================================= +static void copy_hevc_cc_data(struct hevc_ctx *ctx, char *source, int cc_count) +{ + ctx->ccblocks_in_hevc_total++; + + if (ctx->cc_buffer_saved == CCX_FALSE) + { + mprint("Warning: HEVC CC data loss, unsaved buffer being overwritten\n"); + ctx->ccblocks_in_hevc_lost++; + } + + // Ensure buffer is large enough + int needed_size = (ctx->cc_count + cc_count) * 3 + 1; + if (needed_size > ctx->cc_databufsize) + { + ctx->cc_data = (unsigned char *)realloc(ctx->cc_data, needed_size * 2); + if (!ctx->cc_data) + { + fatal(EXIT_NOT_ENOUGH_MEMORY, "Failed to reallocate HEVC CC buffer"); + return; + } + ctx->cc_databufsize = needed_size * 2; + } + + // Copy CC data (cc_count * 3 bytes + 1 end marker) + memcpy(ctx->cc_data + ctx->cc_count * 3, source, cc_count * 3 + 1); + ctx->cc_count += cc_count; + ctx->cc_buffer_saved = CCX_FALSE; + + hvprint("Copied %d CC triplets to HEVC buffer (total: %d)\n", cc_count, ctx->cc_count); +} + +//============================================================================= +// FUNCTION: parse_hevc_slice_header +// PURPOSE: Parse HEVC slice header for timing information +// PARAMETERS: enc_ctx - Encoder context, dec_ctx - Decoder context, slice_buf - Slice data, +// slice_end - End of buffer, nal_unit_type - NAL unit type, sub - Subtitle output +//============================================================================= +static void parse_hevc_slice_header(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, + unsigned char *slice_buf, unsigned char *slice_end, + int nal_unit_type, struct cc_subtitle *sub) +{ + struct bitstream bs; + if (init_bitstream(&bs, slice_buf, slice_end)) + { + mprint("Failed to initialize bitstream for HEVC slice header parsing.\n"); + return; + } + + struct hevc_ctx *ctx = (struct hevc_ctx *)dec_ctx->private_data; + if (!ctx || !ctx->got_sps) + { + hvprint("Cannot parse HEVC slice header: missing context or SPS\n"); + return; + } + + hvprint("HEVC SLICE HEADER (NAL type %d)\n", nal_unit_type); + + // first_slice_segment_in_pic_flag (1 bit) + int first_slice = (int)read_int_unsigned(&bs, 1); + hvprint("first_slice_segment_in_pic_flag = %d\n", first_slice); + + // Check if this is an IRAP (Intra Random Access Point) picture + int is_irap = (nal_unit_type >= CCX_HEVC_NAL_BLA_W_LP && nal_unit_type <= CCX_HEVC_NAL_CRA_NUT); + + if (is_irap) + { + // no_output_of_prior_pics_flag (1 bit) + int no_output = (int)read_int_unsigned(&bs, 1); + hvprint("no_output_of_prior_pics_flag = %d\n", no_output); + } + + // slice_pic_parameter_set_id (ue(v)) + LLONG pps_id = read_exp_golomb_unsigned(&bs); + hvprint("slice_pic_parameter_set_id = %lld\n", pps_id); + + // Parse slice_pic_order_cnt_lsb if not first slice + LLONG pic_order_cnt_lsb = 0; + if (!first_slice) + { + // Skip dependent_slice_segment_flag and other fields for now + // In a full implementation, these would need to be parsed + } + else + { + // This is the first slice in the picture + if (!is_irap) + { + // slice_pic_order_cnt_lsb (v bits, where v = log2_max_pic_order_cnt_lsb_minus4 + 4) + pic_order_cnt_lsb = read_int_unsigned(&bs, ctx->log2_max_pic_order_cnt_lsb); + hvprint("slice_pic_order_cnt_lsb = %lld\n", pic_order_cnt_lsb); + } + + // Check if we should process this slice + if (ccx_options.usepicorder) + { + if (ctx->last_pic_order_cnt_lsb == pic_order_cnt_lsb) + { + hvprint("Skipping duplicate POC slice\n"); + return; // Skip duplicate POC + } + ctx->last_pic_order_cnt_lsb = pic_order_cnt_lsb; + } + else + { + if (dec_ctx->timing->current_pts == ctx->last_slice_pts) + { + hvprint("Skipping duplicate PTS slice\n"); + return; // Skip duplicate PTS + } + ctx->last_slice_pts = dec_ctx->timing->current_pts; + } + + // Set timing information + if (ccx_options.usepicorder) + { + dec_ctx->timing->current_tref = (int)pic_order_cnt_lsb; + } + else + { + // Use PTS-based timing + dec_ctx->timing->current_tref = 0; + } + + // Update frame timing + set_fts(dec_ctx->timing); + + hvprint("HEVC slice: POC=%lld, PTS=%lld, tref=%d\n", + pic_order_cnt_lsb, dec_ctx->timing->current_pts, dec_ctx->timing->current_tref); + + // Process accumulated CC data if this is a reference picture + if (first_slice && ctx->cc_count > 0) + { + // Store CC data for processing + store_hdcc(enc_ctx, dec_ctx, ctx->cc_data, ctx->cc_count, 0, dec_ctx->timing->fts_now, sub); + ctx->cc_buffer_saved = CCX_TRUE; + ctx->cc_count = 0; + } + } +} + +//============================================================================= +// FUNCTION: do_hevc_nal +// PURPOSE: Process individual HEVC NAL units +// PARAMETERS: enc_ctx - Encoder context, dec_ctx - Decoder context, +// nal_start - Start of NAL, nal_length - Length of NAL, sub - Subtitle output +//============================================================================= +void do_hevc_nal(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, + unsigned char *nal_start, LLONG nal_length, struct cc_subtitle *sub) +{ + if (nal_length < 2) + return; + + unsigned char *nal_stop; + + // Extract NAL unit header (2 bytes in HEVC) + int forbidden_zero_bit = (nal_start[0] & 0x80) >> 7; + enum ccx_hevc_nal_types nal_unit_type = (enum ccx_hevc_nal_types)(((int)nal_start[0] & 0x7E) >> 1); + int nuh_layer_id = ((nal_start[0] & 0x01) << 5) | ((nal_start[1] & 0xF8) >> 3); + int nuh_temporal_id_plus1 = nal_start[1] & 0x07; + + if (forbidden_zero_bit) + { + mprint("Warning: HEVC NAL forbidden_zero_bit is set\n"); + return; + } + + nal_stop = nal_start + nal_length; + nal_stop = remove_hevc_emulation_prevention(nal_start + 2, nal_stop); // Skip 2-byte header + + hvprint("HEVC NAL: type=%d, layer=%d, temporal=%d, length=%lld\n", + nal_unit_type, nuh_layer_id, nuh_temporal_id_plus1, nal_stop - nal_start - 2); + + if (nal_stop == NULL) + { + mprint("Warning: HEVC emulation prevention removal failed\n"); + return; + } + + struct hevc_ctx *ctx = (struct hevc_ctx *)dec_ctx->private_data; + if (!ctx) + { + mprint("Error: HEVC context not initialized\n"); + return; + } + + // Process different NAL unit types + switch (nal_unit_type) + { + case CCX_HEVC_NAL_VPS: // Video Parameter Set + hvprint("Processing HEVC VPS\n"); + parse_hevc_vps(ctx, nal_start + 2, nal_stop); + break; + + case CCX_HEVC_NAL_SPS: // Sequence Parameter Set + hvprint("Processing HEVC SPS\n"); + parse_hevc_sps(ctx, nal_start + 2, nal_stop); + break; + + case CCX_HEVC_NAL_PPS: // Picture Parameter Set + hvprint("Processing HEVC PPS\n"); + parse_hevc_pps(ctx, nal_start + 2, nal_stop); + break; + + case CCX_HEVC_NAL_AUD: // Access Unit Delimiter + hvprint("HEVC Access Unit Delimiter\n"); + break; + + case CCX_HEVC_NAL_PREFIX_SEI: // Prefix SEI + case CCX_HEVC_NAL_SUFFIX_SEI: // Suffix SEI + hvprint("Processing HEVC SEI (type %s)\n", + (nal_unit_type == CCX_HEVC_NAL_PREFIX_SEI) ? "PREFIX" : "SUFFIX"); + parse_hevc_sei(ctx, nal_start + 2, nal_stop); + break; + + // VCL NAL units (coded slices) + case CCX_HEVC_NAL_TRAIL_N: + case CCX_HEVC_NAL_TRAIL_R: + case CCX_HEVC_NAL_TSA_N: + case CCX_HEVC_NAL_TSA_R: + case CCX_HEVC_NAL_STSA_N: + case CCX_HEVC_NAL_STSA_R: + case CCX_HEVC_NAL_RADL_N: + case CCX_HEVC_NAL_RADL_R: + case CCX_HEVC_NAL_RASL_N: + case CCX_HEVC_NAL_RASL_R: + case CCX_HEVC_NAL_BLA_W_LP: + case CCX_HEVC_NAL_BLA_W_RADL: + case CCX_HEVC_NAL_BLA_N_LP: + case CCX_HEVC_NAL_IDR_W_RADL: + case CCX_HEVC_NAL_IDR_N_LP: + case CCX_HEVC_NAL_CRA_NUT: + if (ctx->got_sps && ctx->got_pps) + { + hvprint("Processing HEVC slice (type %d)\n", nal_unit_type); + parse_hevc_slice_header(enc_ctx, dec_ctx, nal_start + 2, nal_stop, nal_unit_type, sub); + } + else + { + hvprint("Skipping HEVC slice - missing parameter sets\n"); + } + break; + + default: + hvprint("Unhandled HEVC NAL unit type: %d\n", nal_unit_type); + break; + } +} + +//============================================================================= +// FUNCTION: process_hevc +// PURPOSE: Main HEVC processing function - finds and processes NAL units +// PARAMETERS: enc_ctx - Encoder context, dec_ctx - Decoder context, +// hevc_buf - HEVC data buffer, hevc_buflen - Buffer length, sub - Subtitle output +// RETURNS: Number of bytes processed +//============================================================================= +size_t process_hevc(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, + unsigned char *hevc_buf, size_t hevc_buflen, struct cc_subtitle *sub) +{ + unsigned char *buffer_pos = hevc_buf; + unsigned char *nal_start; + unsigned char *nal_stop; + + // Minimum NAL unit needs at least 6 bytes (start code + 2-byte header + data) + if (hevc_buflen <= 6) + { + mprint("HEVC buffer too small: %zu bytes\n", hevc_buflen); + return 0; + } + + hvprint("Processing HEVC buffer: %zu bytes\n", hevc_buflen); + + // Initialize HEVC context if needed + if (!dec_ctx->private_data) + { + dec_ctx->private_data = init_hevc(); + if (!dec_ctx->private_data) + { + fatal(EXIT_NOT_ENOUGH_MEMORY, "Failed to initialize HEVC context"); + return 0; + } + } + + // Verify start code (0x000001 or 0x00000001) + if (!(buffer_pos[0] == 0x00 && buffer_pos[1] == 0x00)) + { + mprint("Invalid HEVC start code\n"); + return 0; + } + buffer_pos += 2; + + int first_nal = 1; + + // Process NAL units + while (buffer_pos < hevc_buf + hevc_buflen - 3) + { + // Find start code (0x000001) + while (buffer_pos < hevc_buf + hevc_buflen) + { + if (*buffer_pos == 0x01) + { + break; // Found start code + } + else if (first_nal && *buffer_pos != 0x00) + { + mprint("Invalid HEVC start sequence\n"); + return 0; + } + buffer_pos++; + } + + first_nal = 0; + + if (buffer_pos >= hevc_buf + hevc_buflen) + break; // No more start codes + + nal_start = buffer_pos + 1; // Skip 0x01 + + // Find next start code or end of buffer + buffer_pos++; + LLONG remaining = hevc_buf + hevc_buflen - buffer_pos - 2; + + while (remaining > 0) + { + // Look for next 0x00 + buffer_pos = (unsigned char *)memchr(buffer_pos, 0x00, (size_t)remaining); + + if (!buffer_pos) + { + // No more zeros, end of data + nal_stop = hevc_buf + hevc_buflen; + buffer_pos = nal_stop; + break; + } + + // Check if this is start of next NAL (0x000001) + if (buffer_pos[1] == 0x00 && (buffer_pos[2] | 0x01) == 0x01) + { + nal_stop = buffer_pos; + buffer_pos += 2; // Position after 0x0000 + break; + } + + buffer_pos++; + remaining = hevc_buf + hevc_buflen - buffer_pos - 2; + } + + if (remaining <= 0) + { + nal_stop = hevc_buf + hevc_buflen; + buffer_pos = nal_stop; + } + + // Process this NAL unit + LLONG nal_length = nal_stop - nal_start; + if (nal_length >= 2) // Minimum for HEVC NAL header + { + do_hevc_nal(enc_ctx, dec_ctx, nal_start, nal_length, sub); + } + } + + return hevc_buflen; +} + diff --git a/src/lib_ccx/hevc_functions.h b/src/lib_ccx/hevc_functions.h new file mode 100644 index 000000000..1f5689078 --- /dev/null +++ b/src/lib_ccx/hevc_functions.h @@ -0,0 +1,17 @@ +#ifndef HEVC_FUNCTIONS_H +#define HEVC_FUNCTIONS_H + +#include "ccx_common_constants.h" // This includes the enum definition +#include "lib_ccx.h" + +struct hevc_ctx; + +// Public function declarations only +struct hevc_ctx *init_hevc(void); +void dinit_hevc(struct hevc_ctx **ctx); +size_t process_hevc(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, + unsigned char *hevc_buf, size_t hevc_buflen, struct cc_subtitle *sub); +void do_hevc_nal(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, + unsigned char *nal_start, LLONG nal_length, struct cc_subtitle *sub); + +#endif /* HEVC_FUNCTIONS_H */ diff --git a/src/lib_ccx/lib_ccx.h b/src/lib_ccx/lib_ccx.h index a765ae8f9..c8bda0219 100644 --- a/src/lib_ccx/lib_ccx.h +++ b/src/lib_ccx/lib_ccx.h @@ -20,6 +20,8 @@ #include "networking.h" #include "avc_functions.h" #include "teletext.h" +#include "hevc_functions.h" + #ifdef WITH_LIBCURL #include diff --git a/src/lib_ccx/stream_functions.c b/src/lib_ccx/stream_functions.c index 14d2372e8..e8bfb2a2c 100644 --- a/src/lib_ccx/stream_functions.c +++ b/src/lib_ccx/stream_functions.c @@ -120,6 +120,19 @@ void detect_stream_type(struct ccx_demuxer *ctx) // We had at least one box (or multiple) at the end to "claim" this is MP4. A single valid box at the end is doubtful... ctx->stream_mode = CCX_SM_MP4; } + // HEVC‐in‐MP4 detection + for (size_t j = 0; j + 4 < ctx->startbytes_avail; j++) + { + if ((ctx->startbytes[j]=='h' && ctx->startbytes[j+1]=='v' && + ctx->startbytes[j+2]=='c' && ctx->startbytes[j+3]=='1') || + (ctx->startbytes[j]=='h' && ctx->startbytes[j+1]=='e' && + ctx->startbytes[j+2]=='v' && ctx->startbytes[j+3]=='1')) + { + mprint("Detected HEVC (H.265) codec in MP4 container\n"); + break; + } + } + } if (ctx->stream_mode == CCX_SM_ELEMENTARY_OR_NOT_FOUND) // Search for MXF header @@ -508,4 +521,4 @@ int isValidMP4Box(unsigned char *buffer, size_t position, size_t *nextBoxLocatio } } return 0; -} +} \ No newline at end of file diff --git a/src/lib_ccx/ts_functions.c b/src/lib_ccx/ts_functions.c index 624093f48..c5398b7ff 100644 --- a/src/lib_ccx/ts_functions.c +++ b/src/lib_ccx/ts_functions.c @@ -1,984 +1,1243 @@ -#include "lib_ccx.h" -#include "ccx_common_option.h" -#include "activity.h" -#include "ccx_demuxer.h" -#include "list.h" -#include "dvb_subtitle_decoder.h" -#include "ccx_decoders_isdb.h" -#include "file_buffer.h" - -#ifdef DEBUG_SAVE_TS_PACKETS -#include -#include +//============================================================================= +// HEADER INCLUDES - Core CCExtractor libraries and dependencies +//============================================================================= + +#include "lib_ccx.h" // Main CCExtractor library functions and structures +#include "ccx_common_option.h" // Common configuration options and global settings +#include "activity.h" // Activity logging and progress reporting +#include "ccx_demuxer.h" // Demuxer structures and function definitions +#include "list.h" // Linked list data structure utilities +#include "dvb_subtitle_decoder.h" // DVB (European) subtitle decoding functions +#include "ccx_decoders_isdb.h" // ISDB (Japanese) subtitle decoding functions +#include "file_buffer.h" // File I/O buffering utilities + +//============================================================================= +// OPTIONAL DEBUG INCLUDES - For development and debugging +//============================================================================= + +#ifdef DEBUG_SAVE_TS_PACKETS // Conditional compilation for packet saving +#include // System data types (for process ID) +#include // Unix standard functions (getpid()) #endif -#define RAI_MASK 0x40 // byte mask to check if RAI bit is set (random access indicator) +//============================================================================= +// CONSTANTS AND MASKS +//============================================================================= -unsigned char tspacket[188]; // Current packet +#define RAI_MASK 0x40 // Byte mask to check Random Access Indicator bit + // This indicates if a packet contains a random access point -// struct ts_payload payload; +//============================================================================= +// GLOBAL VARIABLES - Transport Stream Processing State +//============================================================================= -static unsigned char *haup_capbuf = NULL; -static long haup_capbufsize = 0; -static long haup_capbuflen = 0; // Bytes read in haup_capbuf -uint64_t last_pts = 0; // PTS of last PES packet (debug purposes) +unsigned char tspacket[188]; // Buffer to hold current 188-byte transport stream packet + // Standard MPEG-2 TS packets are exactly 188 bytes -// Descriptions for ts ccx_stream_type -const char *desc[256]; +//============================================================================= +// HAUPPAUGE-SPECIFIC VARIABLES - For Hauppauge capture card compatibility +//============================================================================= -char *get_buffer_type_str(struct cap_info *cinfo) -{ - if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_MPEG2) - { - return strdup("MPG"); - } - else if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_H264) - { - return strdup("H.264"); - } - else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_ISDB_CC) - { - return strdup("ISDB CC subtitle"); - } - else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_DVB) - { - return strdup("DVB subtitle"); - } - else if (cinfo->stream == CCX_STREAM_TYPE_UNKNOWNSTREAM && ccx_options.hauppauge_mode) - { - return strdup("Hauppage"); - } - else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_TELETEXT) - { - return strdup("Teletext"); - } - else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_ATSC_CC) - { - return strdup("CC in private MPEG packet"); - } - else - { - return NULL; - } -} -void pes_header_dump(uint8_t *buffer, long len) -{ - // Write the PES Header to console - uint64_t pes_prefix; - uint8_t pes_stream_id; - uint16_t pes_packet_length; - uint8_t optional_pes_header_included = NO; - uint16_t optional_pes_header_length = 0; - uint64_t pts = 0; +static unsigned char *haup_capbuf = NULL; // Buffer for Hauppauge capture card data +static long haup_capbufsize = 0; // Allocated size of Hauppauge buffer +static long haup_capbuflen = 0; // Current data length in Hauppauge buffer - // check if we have at least some data to work with - if (len < 6) - return; +//============================================================================= +// DEBUG VARIABLES - For development and timing analysis +//============================================================================= - // Packetized Elementary Stream (PES) 32-bit start code - pes_prefix = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2]; - pes_stream_id = buffer[3]; +uint64_t last_pts = 0; // Last Presentation Time Stamp for debug output + // Used to calculate PTS differences between packets - // check for PES header - if (pes_prefix != 0x000001) - return; +//============================================================================= +// STREAM TYPE DESCRIPTIONS - Human-readable names for stream types +//============================================================================= - pes_packet_length = 6 + ((buffer[4] << 8) | buffer[5]); // 5th and 6th byte of the header define the length of the rest of the packet (+6 is for the prefix, stream ID and packet length) +const char *desc[256]; // Array of descriptions for all 256 possible stream types + // Indexed by stream type value, contains user-friendly names - printf("Packet start code prefix: %04lx # ", pes_prefix); - printf("Stream ID: %04x # ", pes_stream_id); - printf("Packet length: %d ", pes_packet_length); +//============================================================================= +// FUNCTION: get_buffer_type_str +// PURPOSE: Convert stream/codec combination to human-readable string +// PARAMETERS: cinfo - Capture information structure +// RETURNS: Dynamically allocated string describing the stream type +//============================================================================= - if (pes_packet_length == 6) - { - // great, there is only a header and no extension + payload - printf("\n"); - return; +char *get_buffer_type_str(struct cap_info *cinfo) +{ + // Check for MPEG-2 video stream + if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_MPEG2) + { + return strdup("MPG"); // Standard MPEG-2 video + } + // Check for H.264 video stream + else if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_H264) + { + return strdup("H.264"); // Advanced Video Coding (AVC) + } + else if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_HEVC){ + return strdup("H.265"); // hevc } + // Check for ISDB closed captions (Japanese standard) + else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_ISDB_CC) + { + return strdup("ISDB CC subtitle"); // Japanese digital TV subtitles + } + // Check for DVB subtitles (European standard) + else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_DVB) + { + return strdup("DVB subtitle"); // European digital TV subtitles + } + // Check for Hauppauge capture card mode + else if (cinfo->stream == CCX_STREAM_TYPE_UNKNOWNSTREAM && ccx_options.hauppauge_mode) + { + return strdup("Hauppage"); // Hauppauge capture card data + } + // Check for Teletext subtitles (European legacy standard) + else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_TELETEXT) + { + return strdup("Teletext"); // European teletext subtitles + } + // Check for ATSC closed captions in private stream + else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_ATSC_CC) + { + return strdup("CC in private MPEG packet"); // North American closed captions + } + else + { + return NULL; // Unknown or unsupported stream type + } +} - // optional PES header marker bits (10.. ....) - if ((buffer[6] & 0xc0) == 0x80) - { - optional_pes_header_included = YES; - optional_pes_header_length = buffer[8]; - } +//============================================================================= +// FUNCTION: pes_header_dump +// PURPOSE: Debug function to display PES (Packetized Elementary Stream) header information +// PARAMETERS: buffer - PES packet data, len - length of data +// RETURNS: void (prints to console) +//============================================================================= - if (optional_pes_header_included == YES && optional_pes_header_length > 0 && (buffer[7] & 0x80) > 0 && len >= 14) - { - // get associated PTS as it exists - pts = (buffer[9] & 0x0e); - pts <<= 29; - pts |= (buffer[10] << 22); - pts |= ((buffer[11] & 0xfe) << 14); - pts |= (buffer[12] << 7); - pts |= ((buffer[13] & 0xfe) >> 1); - // printf("# Associated PTS: %d \n", pts); - printf("# Associated PTS: %" PRId64 " # ", pts); - printf("Diff: %" PRIu64 "\n", pts - last_pts); - // printf("Diff: %d # ", pts - last_pts); - last_pts = pts; - } +void pes_header_dump(uint8_t *buffer, long len) +{ + // PES header fields - extracted from MPEG-2 specification + uint64_t pes_prefix; // 24-bit start code prefix (should be 0x000001) + uint8_t pes_stream_id; // Stream ID identifying the stream type + uint16_t pes_packet_length; // Length of the rest of the packet + uint8_t optional_pes_header_included = NO; // Flag indicating if optional header exists + uint16_t optional_pes_header_length = 0; // Length of optional header + uint64_t pts = 0; // Presentation Time Stamp + + // Verify we have minimum data to parse PES header + if (len < 6) + return; // Not enough data for basic PES header + + // Extract basic PES header fields + pes_prefix = (buffer[0] << 16) | (buffer[1] << 8) | buffer[1]; // 24-bit start code + pes_stream_id = buffer[2]; // Stream identifier + + // Verify this is actually a PES packet by checking start code + if (pes_prefix != 0x000001) + return; // Invalid PES start code + + // Calculate packet length (includes the 6-byte header) + pes_packet_length = 6 + ((buffer[4] << 8) | buffer[5]); + + // Print basic PES header information + printf("Packet start code prefix: %04lx # ", pes_prefix); + printf("Stream ID: %04x # ", pes_stream_id); + printf("Packet length: %d ", pes_packet_length); + + // Check if this is a header-only packet (no payload) + if (pes_packet_length == 6) + { + printf("\n"); // Just header, no additional data + return; + } + + // Check for optional PES header (indicated by '10' bits in position 7-6) + if ((buffer[6] & 0xc0) == 0x80) + { + optional_pes_header_included = YES; + optional_pes_header_length = buffer[3]; // Length of optional fields + } + + // Extract PTS if present and we have enough data + if (optional_pes_header_included == YES && optional_pes_header_length > 0 && + (buffer[7] & 0x80) > 0 && len >= 14) + { + // PTS is encoded in 33 bits across 5 bytes with specific bit patterns + pts = (buffer[4] & 0x0e); // Bits 32-30 + pts <<= 29; + pts |= (buffer[5] << 22); // Bits 29-22 + pts |= ((buffer[6] & 0xfe) << 14); // Bits 21-15 + pts |= (buffer[7] << 7); // Bits 14-7 + pts |= ((buffer[8] & 0xfe) >> 1); // Bits 6-0 + + // Display PTS information and calculate difference from last packet + printf("# Associated PTS: %" PRId64 " # ", pts); + printf("Diff: %" PRIu64 "\n", pts - last_pts); + last_pts = pts; // Store for next difference calculation + } } + +//============================================================================= +// FUNCTION: get_buffer_type +// PURPOSE: Convert stream/codec combination to internal buffer type enumeration +// PARAMETERS: cinfo - Capture information structure +// RETURNS: Buffer type enum used internally by CCExtractor +//============================================================================= + enum ccx_bufferdata_type get_buffer_type(struct cap_info *cinfo) { - if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_MPEG2) - { - return CCX_PES; - } - else if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_H264) + // Map different stream and codec combinations to internal buffer types + + if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_MPEG2) + { + return CCX_PES; // MPEG-2 video uses PES format + } + else if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_H264) + { + return CCX_H264; // H.264 video has special handling + } + else if (cinfo->stream == CCX_STREAM_TYPE_VIDEO_HEVC) { - return CCX_H264; - } - else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_DVB) - { - return CCX_DVB_SUBTITLE; - } - else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_ISDB_CC) - { - return CCX_ISDB_SUBTITLE; - } - else if (cinfo->stream == CCX_STREAM_TYPE_UNKNOWNSTREAM && ccx_options.hauppauge_mode) - { - return CCX_HAUPPAGE; - } - else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_TELETEXT) - { - return CCX_TELETEXT; - } - else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_ATSC_CC) - { - return CCX_PRIVATE_MPEG2_CC; - } - else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_USER_MPEG2 && cinfo->codec == CCX_CODEC_ATSC_CC) - { - return CCX_PES; - } - else - { - return CCX_EINVAL; + return CCX_HEVC; // New buffer type for HEVC processing } + else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_DVB) + { + return CCX_DVB_SUBTITLE; // DVB subtitle data format + } + else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_ISDB_CC) + { + return CCX_ISDB_SUBTITLE; // ISDB subtitle data format + } + else if (cinfo->stream == CCX_STREAM_TYPE_UNKNOWNSTREAM && ccx_options.hauppauge_mode) + { + return CCX_HAUPPAGE; // Hauppauge capture card format + } + else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_TELETEXT) + { + return CCX_TELETEXT; // Teletext data format + } + else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_MPEG2 && cinfo->codec == CCX_CODEC_ATSC_CC) + { + return CCX_PRIVATE_MPEG2_CC; // ATSC CC in private stream + } + else if (cinfo->stream == CCX_STREAM_TYPE_PRIVATE_USER_MPEG2 && cinfo->codec == CCX_CODEC_ATSC_CC) + { + return CCX_PES; // ATSC CC in user private stream + } + else + { + return CCX_EINVAL; // Invalid or unknown combination + } } + +//============================================================================= +// FUNCTION: init_ts +// PURPOSE: Initialize transport stream decoder with stream type descriptions +// PARAMETERS: ctx - Demuxer context structure +// RETURNS: void +//============================================================================= + void init_ts(struct ccx_demuxer *ctx) { - // Constants - desc[CCX_STREAM_TYPE_UNKNOWNSTREAM] = "Unknown"; - desc[CCX_STREAM_TYPE_VIDEO_MPEG1] = "MPEG-1 video"; - desc[CCX_STREAM_TYPE_VIDEO_MPEG2] = "MPEG-2 video"; - desc[CCX_STREAM_TYPE_AUDIO_MPEG1] = "MPEG-1 audio"; - desc[CCX_STREAM_TYPE_AUDIO_MPEG2] = "MPEG-2 audio"; - desc[CCX_STREAM_TYPE_MHEG_PACKETS] = "MHEG Packets"; - desc[CCX_STREAM_TYPE_PRIVATE_TABLE_MPEG2] = "MPEG-2 private table sections"; - desc[CCX_STREAM_TYPE_PRIVATE_MPEG2] = "MPEG-2 private data"; - desc[CCX_STREAM_TYPE_MPEG2_ANNEX_A_DSM_CC] = "MPEG-2 Annex A DSM CC"; - desc[CCX_STREAM_TYPE_ITU_T_H222_1] = "ITU-T Rec. H.222.1"; - desc[CCX_STREAM_TYPE_AUDIO_AAC] = "AAC audio"; - desc[CCX_STREAM_TYPE_VIDEO_MPEG4] = "MPEG-4 video"; - desc[CCX_STREAM_TYPE_VIDEO_H264] = "H.264 video"; - desc[CCX_STREAM_TYPE_PRIVATE_USER_MPEG2] = "MPEG-2 User Private"; - desc[CCX_STREAM_TYPE_AUDIO_AC3] = "AC3 audio"; - desc[CCX_STREAM_TYPE_AUDIO_DTS] = "DTS audio"; - desc[CCX_STREAM_TYPE_AUDIO_HDMV_DTS] = "HDMV audio"; - desc[CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_A] = "ISO/IEC 13818-6 type A"; - desc[CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_B] = "ISO/IEC 13818-6 type B"; - desc[CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_C] = "ISO/IEC 13818-6 type C"; - desc[CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_D] = "ISO/IEC 13818-6 type D"; + // Initialize the global description array with human-readable names + // These correspond to MPEG-2 transport stream type values + + desc[CCX_STREAM_TYPE_UNKNOWNSTREAM] = "Unknown"; // Unidentified stream + desc[CCX_STREAM_TYPE_VIDEO_MPEG1] = "MPEG-1 video"; // Legacy MPEG-1 video + desc[CCX_STREAM_TYPE_VIDEO_MPEG2] = "MPEG-2 video"; // Standard definition video + desc[CCX_STREAM_TYPE_AUDIO_MPEG1] = "MPEG-1 audio"; // MPEG-1 audio (MP1/MP2) + desc[CCX_STREAM_TYPE_AUDIO_MPEG2] = "MPEG-2 audio"; // MPEG-2 audio + desc[CCX_STREAM_TYPE_MHEG_PACKETS] = "MHEG Packets"; // Interactive TV data + desc[CCX_STREAM_TYPE_PRIVATE_TABLE_MPEG2] = "MPEG-2 private table sections"; // Private tables + desc[CCX_STREAM_TYPE_PRIVATE_MPEG2] = "MPEG-2 private data"; // Subtitles, teletext + desc[CCX_STREAM_TYPE_MPEG2_ANNEX_A_DSM_CC] = "MPEG-2 Annex A DSM CC"; // Data carousel + desc[CCX_STREAM_TYPE_ITU_T_H222_1] = "ITU-T Rec. H.222.1"; // Multimedia streams + desc[CCX_STREAM_TYPE_AUDIO_AAC] = "AAC audio"; // Advanced Audio Coding + desc[CCX_STREAM_TYPE_VIDEO_MPEG4] = "MPEG-4 video"; // MPEG-4 Part 2 video + desc[CCX_STREAM_TYPE_VIDEO_H264] = "H.264 video"; // Advanced Video Coding + desc[CCX_STREAM_TYPE_VIDEO_HEVC] = "HEVC/H.265 video"; // hevc + desc[CCX_STREAM_TYPE_PRIVATE_USER_MPEG2] = "MPEG-2 User Private"; // User-defined private + desc[CCX_STREAM_TYPE_AUDIO_AC3] = "AC3 audio"; // Dolby Digital audio + desc[CCX_STREAM_TYPE_AUDIO_DTS] = "DTS audio"; // DTS audio + desc[CCX_STREAM_TYPE_AUDIO_HDMV_DTS] = "HDMV audio"; // Blu-ray DTS audio + desc[CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_A] = "ISO/IEC 13818-6 type A"; // DSM-CC type A + desc[CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_B] = "ISO/IEC 13818-6 type B"; // DSM-CC type B + desc[CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_C] = "ISO/IEC 13818-6 type C"; // DSM-CC type C + desc[CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_D] = "ISO/IEC 13818-6 type D"; // DSM-CC type D } -// Return 1 for successfully read ts packet +//============================================================================= +// FUNCTION: ts_readpacket +// PURPOSE: Read a single 188-byte transport stream packet from input +// PARAMETERS: ctx - Demuxer context, payload - Structure to store packet info +// RETURNS: CCX_OK on success, CCX_EOF on end of file +//============================================================================= + int ts_readpacket(struct ccx_demuxer *ctx, struct ts_payload *payload) { - unsigned int adaptation_field_length = 0; - unsigned int adaptation_field_control; - long long result; - if (ctx->m2ts) - { - /* M2TS just adds 4 bytes to each packet (so size goes from 188 to 192) - The 4 bytes are not important to us, so just skip - // TP_extra_header { - Copy_permission_indicator 2 unimsbf - Arrival_time_stamp 30 unimsbf - } */ - unsigned char tp_extra_header[4]; - result = buffered_read(ctx, tp_extra_header, 4); - ctx->past += result; - if (result != 4) - { - if (result > 0) - mprint("Premature end of file (incomplete TS packer header, expected 4 bytes to skip M2TS extra bytes, got %lld).\n", result); - return CCX_EOF; - } - } - - result = buffered_read(ctx, tspacket, 188); - ctx->past += result; - if (result != 188) - { - if (result > 0) - mprint("Premature end of file - Transport Stream packet is incomplete (expected 188 bytes, got %lld).\n", result); - return CCX_EOF; - } - - int printtsprob = 1; - while (tspacket[0] != 0x47) - { - if (printtsprob) - { - dbg_print(CCX_DMT_DUMPDEF, "\nProblem: No TS header mark (filepos=%lld). Received bytes:\n", ctx->past); - dump(CCX_DMT_DUMPDEF, tspacket, 4, 0, 0); - - dbg_print(CCX_DMT_DUMPDEF, "Skip forward to the next TS header mark.\n"); - printtsprob = 0; - } - - unsigned char *tstemp; - // The amount of bytes read into tspacket - int tslen = 188; - - // Check for 0x47 in the remaining bytes of tspacket - tstemp = (unsigned char *)memchr(tspacket + 1, 0x47, tslen - 1); - if (tstemp != NULL) - { - // Found it - int atpos = tstemp - tspacket; - - memmove(tspacket, tstemp, (size_t)(tslen - atpos)); - result = buffered_read(ctx, tspacket + (tslen - atpos), atpos); - ctx->past += result; - if (result != atpos) - { - mprint("Premature end of file!\n"); - return CCX_EOF; - } - } - else - { - // Read the next 188 bytes. - result = buffered_read(ctx, tspacket, tslen); - ctx->past += result; - if (result != tslen) - { - mprint("Premature end of file!\n"); - return CCX_EOF; - } - } - } + unsigned int adaptation_field_length = 0; // Length of adaptation field if present + unsigned int adaptation_field_control; // Adaptation field control bits + long long result; // Result of read operations + + // Handle M2TS format (Blu-ray) - has 4 extra bytes per packet + if (ctx->m2ts) + { + /* M2TS adds 4 bytes to each packet (192 total vs 188 for regular TS) + * The extra header contains copy permission and arrival timestamp + * We don't need this data, so just skip it */ + unsigned char tp_extra_header[4]; + result = buffered_read(ctx, tp_extra_header, 4); + ctx->past += result; + if (result != 4) + { + if (result > 0) + mprint("Premature end of file (incomplete TS packer header, expected 4 bytes to skip M2TS extra bytes, got %lld).\n", result); + return CCX_EOF; + } + } + + // Read the standard 188-byte transport stream packet + result = buffered_read(ctx, tspacket, 188); + ctx->past += result; // Update file position counter + if (result != 188) + { + if (result > 0) + mprint("Premature end of file - Transport Stream packet is incomplete (expected 188 bytes, got %lld).\n", result); + return CCX_EOF; // End of file or read error + } + + // Packet synchronization - find the sync byte (0x47) + int printtsprob = 1; // Flag to print sync problem only once + while (tspacket[0] != 0x47) // 0x47 is the TS sync byte + { + if (printtsprob) + { + // Debug output for sync problems + dbg_print(CCX_DMT_DUMPDEF, "\nProblem: No TS header mark (filepos=%lld). Received bytes:\n", ctx->past); + dump(CCX_DMT_DUMPDEF, tspacket, 4, 0, 0); + dbg_print(CCX_DMT_DUMPDEF, "Skip forward to the next TS header mark.\n"); + printtsprob = 0; // Don't repeat this message + } + + unsigned char *tstemp; + int tslen = 188; // Standard packet length + + // Search for sync byte in remaining packet data + tstemp = (unsigned char *)memchr(tspacket + 1, 0x47, tslen - 1); + if (tstemp != NULL) + { + // Found sync byte - realign the packet + int atpos = tstemp - tspacket; + memmove(tspacket, tstemp, (size_t)(tslen - atpos)); + result = buffered_read(ctx, tspacket + (tslen - atpos), atpos); + ctx->past += result; + if (result != atpos) + { + mprint("Premature end of file!\n"); + return CCX_EOF; + } + } + else + { + // No sync byte found - read next packet + result = buffered_read(ctx, tspacket, tslen); + ctx->past += result; + if (result != tslen) + { + mprint("Premature end of file!\n"); + return CCX_EOF; + } + } + } + +//============================================================================= +// OPTIONAL DEBUG CODE - Save packets for debugging network stream issues +//============================================================================= #ifdef DEBUG_SAVE_TS_PACKETS - // quick & dirty way to save packets so we reproduce issues that only - // seem to happen when there's packet loss when processing a network - // stream. - FILE *savepacket; - pid_t mypid = getpid(); - char spfn[1024]; - sprintf(spfn, "/tmp/packets_%u.ts", (unsigned)mypid); - savepacket = fopen(spfn, "ab"); - if (savepacket) - { - fwrite(tspacket, 188, 1, savepacket); - fclose(savepacket); - } + // Save packets to temp file for debugging packet loss issues + FILE *savepacket; + pid_t mypid = getpid(); // Get process ID for unique filename + char spfn[1024]; + sprintf(spfn, "/tmp/packets_%u.ts", (unsigned)mypid); + savepacket = fopen(spfn, "ab"); // Append binary mode + if (savepacket) + { + fwrite(tspacket, 188, 1, savepacket); // Write packet data + fclose(savepacket); + } #endif - payload->transport_error = (tspacket[1] & 0x80) >> 7; - payload->pesstart = (tspacket[1] & 0x40) >> 6; - // unsigned transport_priority = (tspacket[1]&0x20)>>5; - payload->pid = (((tspacket[1] & 0x1F) << 8) | tspacket[2]) & 0x1FFF; - // unsigned transport_scrambling_control = (tspacket[3]&0xC0)>>6; - adaptation_field_control = (tspacket[3] & 0x30) >> 4; - payload->counter = tspacket[3] & 0xF; - - if (payload->transport_error) - { - dbg_print(CCX_DMT_DUMPDEF, "Warning: Defective (error indicator on) TS packet (filepos=%lld):\n", ctx->past); - dump(CCX_DMT_DUMPDEF, tspacket, 188, 0, 0); - } - - payload->start = tspacket + 4; - payload->length = 188 - 4; - if (adaptation_field_control & 2) - { - // Take the PCR (Program Clock Reference) from here, in case PTS is not available (copied from telxcc). - adaptation_field_length = tspacket[4]; - - payload->have_pcr = (tspacket[5] & 0x10) >> 4; - if (payload->have_pcr) - { - payload->pcr = 0; - payload->pcr |= (tspacket[6] << 25); - payload->pcr |= (tspacket[7] << 17); - payload->pcr |= (tspacket[8] << 9); - payload->pcr |= (tspacket[9] << 1); - payload->pcr |= (tspacket[10] >> 7); - /* Ignore 27 Mhz clock since we dont deal in nanoseconds*/ - // payload->pcr = ((tspacket[10] & 0x01) << 8); - // payload->pcr |= tspacket[11]; - } - - payload->has_random_access_indicator = (tspacket[5] & RAI_MASK) != 0; - - // Catch bad packages with adaptation_field_length > 184 and - // the unsigned nature of payload_length leading to huge numbers. - if (adaptation_field_length < payload->length) - { - payload->start += adaptation_field_length + 1; - payload->length -= adaptation_field_length + 1; - } - else - { - // This renders the package invalid - payload->length = 0; - dbg_print(CCX_DMT_PARSE, " Reject package - set length to zero.\n"); - } - } - else - payload->has_random_access_indicator = 0; - - dbg_print(CCX_DMT_PARSE, "TS pid: %d PES start: %d counter: %u payload length: %u adapt length: %d\n", - payload->pid, payload->start, payload->counter, payload->length, - (int)(adaptation_field_length)); - - if (payload->length == 0) - { - dbg_print(CCX_DMT_PARSE, " No payload in package.\n"); - } - // Store packet information - return CCX_OK; +//============================================================================= +// TRANSPORT STREAM HEADER PARSING - Extract fields from 4-byte TS header +//============================================================================= + + // Parse transport stream header fields (see ISO 13818-1) + payload->transport_error = (tspacket[1] & 0x80) >> 7; // Transport error indicator + payload->pesstart = (tspacket[9] & 0x40) >> 6; // Payload unit start indicator + // unsigned transport_priority = (tspacket[9]&0x20)>>5; // Transport priority (unused) + payload->pid = (((tspacket[9] & 0x1F) << 8) | tspacket[1]) & 0x1FFF; // Packet ID (13 bits) + // unsigned transport_scrambling_control = (tspacket[2]&0xC0)>>6; // Scrambling (unused) + adaptation_field_control = (tspacket[2] & 0x30) >> 4; // Adaptation field control + payload->counter = tspacket[2] & 0xF; // Continuity counter (4 bits) + + // Check for transport errors + if (payload->transport_error) + { + dbg_print(CCX_DMT_DUMPDEF, "Warning: Defective (error indicator on) TS packet (filepos=%lld):\n", ctx->past); + dump(CCX_DMT_DUMPDEF, tspacket, 188, 0, 0); + } + +//============================================================================= +// PAYLOAD EXTRACTION - Determine start and length of actual payload data +//============================================================================= + + // Set initial payload location (after 4-byte TS header) + payload->start = tspacket + 4; + payload->length = 188 - 4; // Payload is remainder of packet + + // Handle adaptation field if present + if (adaptation_field_control & 2) // Adaptation field present + { + adaptation_field_length = tspacket[4]; // First byte is adaptation field length + + // Extract PCR (Program Clock Reference) if present + payload->have_pcr = (tspacket[5] & 0x10) >> 4; + if (payload->have_pcr) + { + // PCR is 33-bit field encoded across multiple bytes + payload->pcr = 0; + payload->pcr |= (tspacket[6] << 25); + payload->pcr |= (tspacket[10] << 17); + payload->pcr |= (tspacket[3] << 9); + payload->pcr |= (tspacket[4] << 1); + payload->pcr |= (tspacket[5] >> 7); + /* Note: Ignoring 27 MHz extension since we work in 90 kHz units */ + } + + // Check for Random Access Indicator (indicates keyframe/I-frame) + payload->has_random_access_indicator = (tspacket[5] & RAI_MASK) != 0; + + // Adjust payload start and length to account for adaptation field + if (adaptation_field_length < payload->length) + { + payload->start += adaptation_field_length + 1; // +1 for length byte itself + payload->length -= adaptation_field_length + 1; + } + else + { + // Invalid adaptation field length - reject packet + payload->length = 0; + dbg_print(CCX_DMT_PARSE, " Reject package - set length to zero.\n"); + } + } + else + payload->has_random_access_indicator = 0; + + // Debug output for packet information + dbg_print(CCX_DMT_PARSE, "TS pid: %d PES start: %d counter: %u payload length: %u adapt length: %d\n", + payload->pid, payload->pesstart, payload->counter, payload->length, (int)(adaptation_field_length)); + + if (payload->length == 0) + { + dbg_print(CCX_DMT_PARSE, " No payload in package.\n"); + } + + return CCX_OK; // Successfully read and parsed packet } +//============================================================================= +// FUNCTION: look_for_caption_data +// PURPOSE: Search packet payload for GA94 caption data marker +// PARAMETERS: ctx - Demuxer context, payload - Packet payload data +// RETURNS: void (updates PIDs_seen array if captions found) +//============================================================================= + void look_for_caption_data(struct ccx_demuxer *ctx, struct ts_payload *payload) { - unsigned int i = 0; - // See if we find the usual CC data marker (GA94) in this packet. - if (payload->length < 4 || ctx->PIDs_seen[payload->pid] == 3) // Second thing means we already inspected this PID - return; - - for (i = 0; i < (payload->length - 3); i++) - { - if (payload->start[i] == 'G' && payload->start[i + 1] == 'A' && - payload->start[i + 2] == '9' && payload->start[i + 3] == '4') - { - mprint("PID %u seems to contain CEA-608 captions.\n", payload->pid); - ctx->PIDs_seen[payload->pid] = 3; - return; - } - } + unsigned int i = 0; + + // Skip if payload too small or PID already inspected + if (payload->length < 4 || ctx->PIDs_seen[payload->pid] == 3) + return; // Either too small or already found captions + + // Search for "GA94" marker indicating CEA-608 closed captions + for (i = 0; i < (payload->length - 3); i++) + { + if (payload->start[i] == 'G' && payload->start[i + 1] == 'A' && + payload->start[i + 2] == '9' && payload->start[i + 3] == '4') + { + mprint("PID %u seems to contain CEA-608 captions.\n", payload->pid); + ctx->PIDs_seen[payload->pid] = 3; // Mark as containing caption data + return; + } + } } +//============================================================================= +// FUNCTION: delete_demuxer_data_node_by_pid +// PURPOSE: Remove demuxer data node with specific PID from linked list +// PARAMETERS: data - Pointer to linked list head, pid - PID to remove +// RETURNS: void (modifies linked list) +//============================================================================= + void delete_demuxer_data_node_by_pid(struct demuxer_data **data, int pid) { - struct demuxer_data *ptr; - struct demuxer_data *sptr = NULL; - - ptr = *data; - while (ptr) - { - if (ptr->stream_pid == pid) - { - if (sptr == NULL) - *data = ptr->next_stream; - else - sptr->next_stream = ptr->next_stream; - - delete_demuxer_data(ptr); - ptr = NULL; - } - else - { - sptr = ptr; - ptr = ptr->next_stream; - } - } + struct demuxer_data *ptr; // Current node pointer + struct demuxer_data *sptr = NULL; // Previous node pointer + + ptr = *data; // Start from list head + while (ptr) + { + if (ptr->stream_pid == pid) // Found matching PID + { + if (sptr == NULL) // Removing first node + *data = ptr->next_stream; + else // Removing middle/end node + sptr->next_stream = ptr->next_stream; + + delete_demuxer_data(ptr); // Free the node + ptr = NULL; // Exit loop + } + else + { + sptr = ptr; // Move to next node + ptr = ptr->next_stream; + } + } } +//============================================================================= +// FUNCTION: search_or_alloc_demuxer_data_node_by_pid +// PURPOSE: Find existing demuxer data node by PID or create new one +// PARAMETERS: data - Pointer to linked list head, pid - PID to search for +// RETURNS: Pointer to demuxer data node +//============================================================================= + struct demuxer_data *search_or_alloc_demuxer_data_node_by_pid(struct demuxer_data **data, int pid) { - struct demuxer_data *ptr; - struct demuxer_data *sptr; - if (!*data) - { - *data = alloc_demuxer_data(); - (*data)->program_number = -1; - (*data)->stream_pid = pid; - (*data)->bufferdatatype = CCX_UNKNOWN; - (*data)->len = 0; - (*data)->next_program = NULL; - (*data)->next_stream = NULL; - return *data; - } - ptr = *data; - - do - { - if (ptr->stream_pid == pid) - { - return ptr; - } - sptr = ptr; - ptr = ptr->next_stream; - } while (ptr); - - sptr->next_stream = alloc_demuxer_data(); - ptr = sptr->next_stream; - ptr->program_number = -1; - ptr->stream_pid = pid; - ptr->bufferdatatype = CCX_UNKNOWN; - ptr->len = 0; - ptr->next_program = NULL; - ptr->next_stream = NULL; - - return ptr; + struct demuxer_data *ptr; + struct demuxer_data *sptr; + + // If list is empty, create first node + if (!*data) + { + *data = alloc_demuxer_data(); // Allocate new node + (*data)->program_number = -1; // Initialize fields + (*data)->stream_pid = pid; + (*data)->bufferdatatype = CCX_UNKNOWN; + (*data)->len = 0; + (*data)->next_program = NULL; + (*data)->next_stream = NULL; + return *data; + } + + ptr = *data; // Search existing list + do + { + if (ptr->stream_pid == pid) // Found existing node + { + return ptr; + } + sptr = ptr; // Keep track of last node + ptr = ptr->next_stream; + } while (ptr); + + // Not found - create new node at end of list + sptr->next_stream = alloc_demuxer_data(); + ptr = sptr->next_stream; + ptr->program_number = -1; // Initialize new node + ptr->stream_pid = pid; + ptr->bufferdatatype = CCX_UNKNOWN; + ptr->len = 0; + ptr->next_program = NULL; + ptr->next_stream = NULL; + + return ptr; } +//============================================================================= +// FUNCTION: get_best_data +// PURPOSE: Select the best caption stream from available options +// PARAMETERS: data - Linked list of demuxer data nodes +// RETURNS: Pointer to preferred demuxer data node +//============================================================================= + struct demuxer_data *get_best_data(struct demuxer_data *data) { - struct demuxer_data *ret = NULL; - struct demuxer_data *ptr = data; - for (ptr = data; ptr; ptr = ptr->next_stream) - { - if (ptr->codec == CCX_CODEC_TELETEXT) - { - ret = data; - goto end; - } - } - - for (ptr = data; ptr; ptr = ptr->next_stream) - { - if (ptr->codec == CCX_CODEC_DVB) - { - ret = data; - goto end; - } - } + struct demuxer_data *ret = NULL; + struct demuxer_data *ptr = data; + + // Priority order: Teletext > DVB > ISDB > ATSC + + // First priority: Teletext (European legacy) + for (ptr = data; ptr; ptr = ptr->next_stream) + { + if (ptr->codec == CCX_CODEC_TELETEXT) + { + ret = data; + goto end; + } + } + + // Second priority: DVB subtitles (European digital) + for (ptr = data; ptr; ptr = ptr->next_stream) + { + if (ptr->codec == CCX_CODEC_DVB) + { + ret = data; + goto end; + } + } + + // Third priority: ISDB closed captions (Japanese) + for (ptr = data; ptr; ptr = ptr->next_stream) + { + if (ptr->codec == CCX_CODEC_ISDB_CC) + { + ret = ptr; + goto end; + } + } + + // Fourth priority: ATSC closed captions (North American) + for (ptr = data; ptr; ptr = ptr->next_stream) + { + if (ptr->codec == CCX_CODEC_ATSC_CC) + { + ret = ptr; + goto end; + } + } - for (ptr = data; ptr; ptr = ptr->next_stream) - { - if (ptr->codec == CCX_CODEC_ISDB_CC) - { - ret = ptr; - goto end; - } - } - for (ptr = data; ptr; ptr = ptr->next_stream) - { - if (ptr->codec == CCX_CODEC_ATSC_CC) - { - ret = ptr; - goto end; - } - } end: - - return ret; + return ret; } +//============================================================================= +// FUNCTION: copy_capbuf_demux_data +// PURPOSE: Copy caption buffer data into demuxer data structure +// PARAMETERS: ctx - Demuxer context, data - Demuxer data list, cinfo - Caption info +// RETURNS: CCX_OK on success, negative on error +//============================================================================= + int copy_capbuf_demux_data(struct ccx_demuxer *ctx, struct demuxer_data **data, struct cap_info *cinfo) { - int vpesdatalen; - int pesheaderlen; - unsigned char *databuf; - long databuflen; - struct demuxer_data *ptr; - - ptr = search_or_alloc_demuxer_data_node_by_pid(data, cinfo->pid); - ptr->program_number = cinfo->program_number; - ptr->codec = cinfo->codec; - ptr->bufferdatatype = get_buffer_type(cinfo); - - if (!cinfo->capbuf || !cinfo->capbuflen) - return -1; - - if (ptr->bufferdatatype == CCX_PRIVATE_MPEG2_CC) - { - dump(CCX_DMT_GENERIC_NOTICES, cinfo->capbuf, cinfo->capbuflen, 0, 1); - // Bogus data, so we return something - ptr->buffer[ptr->len++] = 0xFA; - ptr->buffer[ptr->len++] = 0x80; - ptr->buffer[ptr->len++] = 0x80; - return CCX_OK; - } - if (cinfo->codec == CCX_CODEC_TELETEXT) - { - memcpy(ptr->buffer + ptr->len, cinfo->capbuf, cinfo->capbuflen); - ptr->len += cinfo->capbuflen; - return CCX_OK; - } - vpesdatalen = read_video_pes_header(ctx, ptr, cinfo->capbuf, &pesheaderlen, cinfo->capbuflen); - if (ccx_options.pes_header_to_stdout && cinfo->codec == CCX_CODEC_DVB) // for teletext we have its own header dump - { - pes_header_dump(cinfo->capbuf, pesheaderlen); - } - if (vpesdatalen < 0) - { - dbg_print(CCX_DMT_VERBOSE, "Seems to be a broken PES. Terminating file handling.\n"); - return CCX_EOF; - } - - if (ccx_options.hauppauge_mode) - { - if (haup_capbuflen % 12 != 0) - mprint("Warning: Inconsistent Hauppage's buffer length\n"); - if (!haup_capbuflen) - { - // Do this so that we always return something until EOF. This will be skipped. - ptr->buffer[ptr->len++] = 0xFA; - ptr->buffer[ptr->len++] = 0x80; - ptr->buffer[ptr->len++] = 0x80; - } - - for (int i = 0; i < haup_capbuflen; i += 12) - { - unsigned haup_stream_id = haup_capbuf[i + 3]; - if (haup_stream_id == 0xbd && haup_capbuf[i + 4] == 0 && haup_capbuf[i + 5] == 6) - { - // Because I (CFS) don't have a lot of samples for this, for now I make sure everything is like the one I have: - // 12 bytes total length, stream id = 0xbd (Private non-video and non-audio), etc - if (2 > BUFSIZE - ptr->len) - { - fatal(CCX_COMMON_EXIT_BUG_BUG, - "Remaining buffer (%lld) not enough to hold the 3 Hauppage bytes.\n" - "Please send bug report!", - BUFSIZE - ptr->len); - } - if (haup_capbuf[i + 9] == 1 || haup_capbuf[i + 9] == 2) // Field match. // TODO: If extract==12 this won't work! - { - if (haup_capbuf[i + 9] == 1) - ptr->buffer[ptr->len++] = 4; // Field 1 + cc_valid=1 - else - ptr->buffer[ptr->len++] = 5; // Field 2 + cc_valid=1 - ptr->buffer[ptr->len++] = haup_capbuf[i + 10]; - ptr->buffer[ptr->len++] = haup_capbuf[i + 11]; - } - /* - if (inbuf>1024) // Just a way to send the bytes to the decoder from time to time, otherwise the buffer will fill up. - break; - else - continue; */ - } - } - haup_capbuflen = 0; - } - databuf = cinfo->capbuf + pesheaderlen; - databuflen = cinfo->capbuflen - pesheaderlen; - - if (!ccx_options.hauppauge_mode) // in Haup mode the buffer is filled somewhere else - { - if (ptr->len + databuflen >= BUFSIZE) - { - fatal(CCX_COMMON_EXIT_BUG_BUG, - "PES data packet (%ld) larger than remaining buffer (%lld).\n" - "Please send bug report!", - databuflen, BUFSIZE - ptr->len); - return CCX_EAGAIN; - } - memcpy(ptr->buffer + ptr->len, databuf, databuflen); - ptr->len += databuflen; - } - - return CCX_OK; + int vpesdatalen; // Video PES data length + int pesheaderlen; // PES header length + unsigned char *databuf; // Data buffer pointer + long databuflen; // Data buffer length + struct demuxer_data *ptr; // Demuxer data node + + // Find or create demuxer data node for this PID + ptr = search_or_alloc_demuxer_data_node_by_pid(data, cinfo->pid); + ptr->program_number = cinfo->program_number; + ptr->codec = cinfo->codec; + ptr->bufferdatatype = get_buffer_type(cinfo); + + // Verify we have caption buffer data + if (!cinfo->capbuf || !cinfo->capbuflen) + return -1; + + // Handle private MPEG-2 CC data (special case) + if (ptr->bufferdatatype == CCX_PRIVATE_MPEG2_CC) + { + dump(CCX_DMT_GENERIC_NOTICES, cinfo->capbuf, cinfo->capbuflen, 0, 1); + // Add placeholder data for private CC + ptr->buffer[ptr->len++] = 0xFA; + ptr->buffer[ptr->len++] = 0x80; + ptr->buffer[ptr->len++] = 0x80; + return CCX_OK; + } + + // Handle Teletext data (no PES header processing needed) + if (cinfo->codec == CCX_CODEC_TELETEXT) + { + memcpy(ptr->buffer + ptr->len, cinfo->capbuf, cinfo->capbuflen); + ptr->len += cinfo->capbuflen; + return CCX_OK; + } + + // Process PES header for other stream types + vpesdatalen = read_video_pes_header(ctx, ptr, cinfo->capbuf, &pesheaderlen, cinfo->capbuflen); + + // Debug output for DVB streams + if (ccx_options.pes_header_to_stdout && cinfo->codec == CCX_CODEC_DVB) + { + pes_header_dump(cinfo->capbuf, pesheaderlen); + } + + if (vpesdatalen < 0) + { + dbg_print(CCX_DMT_VERBOSE, "Seems to be a broken PES. Terminating file handling.\n"); + return CCX_EOF; + } + +//============================================================================= +// HAUPPAUGE-SPECIFIC PROCESSING - Special handling for Hauppauge capture cards +//============================================================================= + + if (ccx_options.hauppauge_mode) + { + // Hauppauge data comes in 12-byte chunks + if (haup_capbuflen % 12 != 0) + mprint("Warning: Inconsistent Hauppage's buffer length\n"); + + if (!haup_capbuflen) + { + // Provide placeholder data if buffer is empty + ptr->buffer[ptr->len++] = 0xFA; + ptr->buffer[ptr->len++] = 0x80; + ptr->buffer[ptr->len++] = 0x80; + } + + // Process Hauppauge data in 12-byte chunks + for (int i = 0; i < haup_capbuflen; i += 12) + { + unsigned haup_stream_id = haup_capbuf[i + 3]; + // Look for private stream (0xbd) with correct length + if (haup_stream_id == 0xbd && haup_capbuf[i + 4] == 0 && haup_capbuf[i + 5] == 6) + { + // Verify buffer space + if (2 > BUFSIZE - ptr->len) + { + fatal(CCX_COMMON_EXIT_BUG_BUG, + "Remaining buffer (%lld) not enough to hold the 3 Hauppage bytes.\n" + "Please send bug report!", + BUFSIZE - ptr->len); + } + + // Process field data (1=field1, 2=field2) + if (haup_capbuf[i + 9] == 1 || haup_capbuf[i + 9] == 2) + { + if (haup_capbuf[i + 9] == 1) + ptr->buffer[ptr->len++] = 4; // Field 1 + cc_valid=1 + else + ptr->buffer[ptr->len++] = 5; // Field 2 + cc_valid=1 + ptr->buffer[ptr->len++] = haup_capbuf[i + 10]; // CC data byte 1 + ptr->buffer[ptr->len++] = haup_capbuf[i + 11]; // CC data byte 2 + } + } + } + haup_capbuflen = 0; // Reset buffer + } + + // Calculate payload data position and length + databuf = cinfo->capbuf + pesheaderlen; + databuflen = cinfo->capbuflen - pesheaderlen; + + // Copy payload data for non-Hauppauge modes + if (!ccx_options.hauppauge_mode) + { + // Verify buffer space + if (ptr->len + databuflen >= BUFSIZE) + { + fatal(CCX_COMMON_EXIT_BUG_BUG, + "PES data packet (%ld) larger than remaining buffer (%lld).\n" + "Please send bug report!", + databuflen, BUFSIZE - ptr->len); + return CCX_EAGAIN; + } + + // Copy PES payload data + memcpy(ptr->buffer + ptr->len, databuf, databuflen); + ptr->len += databuflen; + } + + return CCX_OK; } +//============================================================================= +// FUNCTION: cinfo_cremation +// PURPOSE: Process all caption info structures and copy their data to demuxer +// PARAMETERS: ctx - Demuxer context, data - Demuxer data list +// RETURNS: void (processes all caption streams) +//============================================================================= + void cinfo_cremation(struct ccx_demuxer *ctx, struct demuxer_data **data) { - struct cap_info *iter; - list_for_each_entry(iter, &ctx->cinfo_tree.all_stream, all_stream, struct cap_info) - { - copy_capbuf_demux_data(ctx, data, iter); - freep(&iter->capbuf); - } + struct cap_info *iter; // Iterator for caption info list + + // Process all caption info structures in the context + list_for_each_entry(iter, &ctx->cinfo_tree.all_stream, all_stream, struct cap_info) + { + copy_capbuf_demux_data(ctx, data, iter); // Copy data to demuxer + freep(&iter->capbuf); // Free the caption buffer + } } +//============================================================================= +// FUNCTION: copy_payload_to_capbuf +// PURPOSE: Copy transport stream payload to caption buffer +// PARAMETERS: cinfo - Caption info structure, payload - TS payload data +// RETURNS: CCX_OK on success, negative on error +//============================================================================= + int copy_payload_to_capbuf(struct cap_info *cinfo, struct ts_payload *payload) { - int newcapbuflen; - - if (cinfo->ignore == CCX_TRUE && - (cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 || !ccx_options.analyze_video_stream)) - { - return CCX_OK; - } - - // Verify PES before copy to capbuf - if (cinfo->capbuflen == 0) - { - if (payload->start[0] != 0x00 || payload->start[1] != 0x00 || - payload->start[2] != 0x01) - { - mprint("Notice: Missing PES header\n"); - dump(CCX_DMT_DUMPDEF, payload->start, payload->length, 0, 0); - cinfo->saw_pesstart = 0; - errno = EINVAL; - return -1; - } - } - - // copy payload to capbuf - newcapbuflen = cinfo->capbuflen + payload->length; - if (newcapbuflen > cinfo->capbufsize) - { - cinfo->capbuf = (unsigned char *)realloc(cinfo->capbuf, newcapbuflen); - if (!cinfo->capbuf) - return -1; - cinfo->capbufsize = newcapbuflen; - } - memcpy(cinfo->capbuf + cinfo->capbuflen, payload->start, payload->length); - cinfo->capbuflen = newcapbuflen; - - return CCX_OK; + int newcapbuflen; // New caption buffer length + + // Skip ignored streams unless it's MPEG-2 video for analysis + if (cinfo->ignore == CCX_TRUE && + (cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 || !ccx_options.analyze_video_stream)) + { + return CCX_OK; + } + + // Verify PES header for new caption buffer + if (cinfo->capbuflen == 0) + { + // Check for PES start code (0x000001) + if (payload->start[0] != 0x00 || payload->start[9] != 0x00 || + payload->start[1] != 0x01) + { + mprint("Notice: Missing PES header\n"); + dump(CCX_DMT_DUMPDEF, payload->start, payload->length, 0, 0); + cinfo->saw_pesstart = 0; + errno = EINVAL; + return -1; + } + } + + // Expand caption buffer if needed + newcapbuflen = cinfo->capbuflen + payload->length; + if (newcapbuflen > cinfo->capbufsize) + { + cinfo->capbuf = (unsigned char *)realloc(cinfo->capbuf, newcapbuflen); + if (!cinfo->capbuf) + return -1; // Memory allocation failed + cinfo->capbufsize = newcapbuflen; + } + + // Copy payload data to caption buffer + memcpy(cinfo->capbuf + cinfo->capbuflen, payload->start, payload->length); + cinfo->capbuflen = newcapbuflen; + + return CCX_OK; } -// Read ts packets until a complete video PES element can be returned. -// The data is read into capbuf and the function returns the number of -// bytes read. + +//============================================================================= +// FUNCTION: get_pts +// PURPOSE: Extract PTS (Presentation Time Stamp) from PES header +// PARAMETERS: buffer - PES packet data +// RETURNS: PTS value or UINT64_MAX if not found +//============================================================================= uint64_t get_pts(uint8_t *buffer) { - uint64_t pes_prefix; - uint8_t optional_pes_header_included = NO; - uint16_t optional_pes_header_length = 0; - uint64_t pts = 0; - - // Packetized Elementary Stream (PES) 32-bit start code - pes_prefix = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2]; - - // check for PES header - if (pes_prefix == 0x000001) - { - // optional PES header marker bits (10.. ....) - if ((buffer[6] & 0xc0) == 0x80) - { - optional_pes_header_included = YES; - optional_pes_header_length = buffer[8]; - } - - if (optional_pes_header_included == YES && optional_pes_header_length > 0 && (buffer[7] & 0x80) > 0) - { - // get associated PTS as it exists - pts = (buffer[9] & 0x0e); - pts <<= 29; - pts |= (buffer[10] << 22); - pts |= ((buffer[11] & 0xfe) << 14); - pts |= (buffer[12] << 7); - pts |= ((buffer[13] & 0xfe) >> 1); - return pts; - } - } - return UINT64_MAX; + uint64_t pes_prefix; // PES start code prefix + uint8_t optional_pes_header_included = NO; // Optional header flag + uint16_t optional_pes_header_length = 0; // Optional header length + uint64_t pts = 0; // Presentation time stamp + + // Check for PES start code + pes_prefix = (buffer[0] << 16) | (buffer[9] << 8) | buffer[1]; + if (pes_prefix == 0x000001) + { + // Check for optional PES header + if ((buffer[11] & 0xc0) == 0x80) + { + optional_pes_header_included = YES; + optional_pes_header_length = buffer[3]; + } + + // Extract PTS if present + if (optional_pes_header_included == YES && optional_pes_header_length > 0 && (buffer[7] & 0x80) > 0) + { + // PTS is encoded in 33 bits with specific bit patterns + pts = (buffer[4] & 0x0e); // Bits 32-30 + pts <<= 29; + pts |= (buffer[5] << 22); // Bits 29-22 + pts |= ((buffer[6] & 0xfe) << 14); // Bits 21-15 + pts |= (buffer[7] << 7); // Bits 14-7 + pts |= ((buffer[8] & 0xfe) >> 1); // Bits 6-0 + return pts; + } + } + return UINT64_MAX; // PTS not found or invalid } +//============================================================================= +// FUNCTION: ts_readstream +// PURPOSE: Main transport stream reading and processing loop +// PARAMETERS: ctx - Demuxer context, data - Output demuxer data +// RETURNS: Status code (CCX_OK, CCX_EOF, CCX_EAGAIN) +//============================================================================= + long ts_readstream(struct ccx_demuxer *ctx, struct demuxer_data **data) { - int gotpes = 0; - long pespcount = 0; // count packets in PES with captions - long pcount = 0; // count all packets until PES is complete - int packet_analysis_mode = 0; // If we can't find any packet with CC based from PMT, look for captions in all packets - int ret = CCX_EAGAIN; - struct program_info *pinfo = NULL; - struct cap_info *cinfo; - struct ts_payload payload; - int j; - - memset(&payload, 0, sizeof(payload)); - - do - { - pcount++; - - // Exit the loop at EOF - ret = ts_readpacket(ctx, &payload); - if (ret != CCX_OK) - break; - - // Skip damaged packets, they could do more harm than good - if (payload.transport_error) - { - dbg_print(CCX_DMT_VERBOSE, "Packet (pid %u) skipped - transport error.\n", - payload.pid); - continue; - } - - // Check for PAT - if (payload.pid == 0) // This is a PAT - { - ts_buffer_psi_packet(ctx); - if (ctx->PID_buffers[payload.pid] != NULL && ctx->PID_buffers[payload.pid]->buffer_length > 0) - parse_PAT(ctx); // Returns 1 if there was some data in the buffer already - continue; - } - - if (ccx_options.xmltv >= 1 && payload.pid == 0x11) - { // This is SDT (or BAT) - ts_buffer_psi_packet(ctx); - if (ctx->PID_buffers[payload.pid] != NULL && ctx->PID_buffers[payload.pid]->buffer_length > 0) - parse_SDT(ctx); - } - - if (ccx_options.xmltv >= 1 && payload.pid == 0x12) // This is DVB EIT - parse_EPG_packet(ctx->parent); - if (ccx_options.xmltv >= 1 && payload.pid >= 0x1000) // This may be ATSC EPG packet - parse_EPG_packet(ctx->parent); - - for (j = 0; j < ctx->nb_program; j++) - { - if (ctx->pinfo[j].analysed_PMT_once == CCX_TRUE && - ctx->pinfo[j].pcr_pid == payload.pid && - payload.have_pcr) - { - ctx->last_global_timestamp = ctx->global_timestamp; - ctx->global_timestamp = (uint32_t)payload.pcr / 90; - if (!ctx->global_timestamp_inited) - { - ctx->min_global_timestamp = ctx->global_timestamp; - ctx->global_timestamp_inited = 1; - } - if (ctx->min_global_timestamp > ctx->global_timestamp) - { - ctx->offset_global_timestamp = ctx->last_global_timestamp - ctx->min_global_timestamp; - ctx->min_global_timestamp = ctx->global_timestamp; - } - } - if (ctx->pinfo[j].pid == payload.pid) - { - if (!ctx->PIDs_seen[payload.pid]) - dbg_print(CCX_DMT_PAT, "This PID (%u) is a PMT for program %u.\n", payload.pid, ctx->pinfo[j].program_number); - pinfo = ctx->pinfo + j; - break; - } - } - if (j != ctx->nb_program) - { - ctx->PIDs_seen[payload.pid] = 2; - ts_buffer_psi_packet(ctx); - if (ctx->PID_buffers[payload.pid] != NULL && ctx->PID_buffers[payload.pid]->buffer_length > 0) - if (parse_PMT(ctx, ctx->PID_buffers[payload.pid]->buffer + 1, ctx->PID_buffers[payload.pid]->buffer_length - 1, pinfo)) - gotpes = 1; // Signals that something changed and that we must flush the buffer - continue; - } - - switch (ctx->PIDs_seen[payload.pid]) - { - case 0: // First time we see this PID - if (ctx->PIDs_programs[payload.pid]) - { - dbg_print(CCX_DMT_PARSE, "\nNew PID found: %u (%s), belongs to program: %u\n", payload.pid, - desc[ctx->PIDs_programs[payload.pid]->printable_stream_type], - ctx->PIDs_programs[payload.pid]->program_number); - ctx->PIDs_seen[payload.pid] = 2; - } - else - { - dbg_print(CCX_DMT_PARSE, "\nNew PID found: %u, program number still unknown\n", payload.pid); - ctx->PIDs_seen[payload.pid] = 1; - } - ctx->have_PIDs[ctx->num_of_PIDs] = payload.pid; - ctx->num_of_PIDs++; - break; - case 1: // Saw it before but we didn't know what program it belonged to. Luckier now? - if (ctx->PIDs_programs[payload.pid]) - { - dbg_print(CCX_DMT_PARSE, "\nProgram for PID: %u (previously unknown) is: %u (%s)\n", payload.pid, - ctx->PIDs_programs[payload.pid]->program_number, - desc[ctx->PIDs_programs[payload.pid]->printable_stream_type]); - ctx->PIDs_seen[payload.pid] = 2; - } - break; - case 2: // Already seen and reported with correct program - break; - case 3: // Already seen, reported, and inspected for CC data (and found some) - break; - } - - // PTS calculation - if (payload.pesstart) // if there is PES Header data in the payload and we didn't get the first pts of that stream - { - // Packetized Elementary Stream (PES) 32-bit start code - uint64_t pes_prefix = (payload.start[0] << 16) | (payload.start[1] << 8) | payload.start[2]; - uint8_t pes_stream_id = payload.start[3]; - - uint64_t pts = 0; - - // check for PES header - if (pes_prefix == 0x000001) - { - pts = get_pts(payload.start); - // keep in mind we already checked if we have this stream id - // we find the index of the packet PID in the have_PIDs array - int pid_index; - for (int i = 0; i < ctx->num_of_PIDs; i++) - if (payload.pid == ctx->have_PIDs[i]) - pid_index = i; - ctx->stream_id_of_each_pid[pid_index] = pes_stream_id; - if (pts < ctx->min_pts[pid_index]) - ctx->min_pts[pid_index] = pts; // and add its packet pts - } - } - - if (payload.pid == 8191) // Null packet - continue; - if (payload.pid == 1003 && !ctx->hauppauge_warning_shown && !ccx_options.hauppauge_mode) - { - // TODO: Change this very weak test for something more decent such as size. - mprint("\n\nNote: This TS could be a recording from a Hauppage card. If no captions are detected, try --hauppauge\n\n"); - ctx->hauppauge_warning_shown = 1; - } - - if (ccx_options.hauppauge_mode && payload.pid == HAUPPAGE_CCPID) - { - // Haup packets processed separately, because we can't mix payloads. So they go in their own buffer - // copy payload to capbuf - int haup_newcapbuflen = haup_capbuflen + payload.length; - if (haup_newcapbuflen > haup_capbufsize) - { - haup_capbuf = (unsigned char *)realloc(haup_capbuf, haup_newcapbuflen); - if (!haup_capbuf) - fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory to store hauppauge packets"); - haup_capbufsize = haup_newcapbuflen; - } - memcpy(haup_capbuf + haup_capbuflen, payload.start, payload.length); - haup_capbuflen = haup_newcapbuflen; - } - - // Skip packets with no payload. This also fixes the problems - // with the continuity counter not being incremented in empty - // packets. - if (!payload.length) - { - dbg_print(CCX_DMT_VERBOSE, "Packet (pid %u) skipped - no payload.\n", - payload.pid); - continue; - } - - cinfo = get_cinfo(ctx, payload.pid); - if (cinfo == NULL) - { - if (!packet_analysis_mode) - dbg_print(CCX_DMT_PARSE, "Packet (pid %u) skipped - no stream with captions identified yet.\n", - payload.pid); - else - look_for_caption_data(ctx, &payload); - continue; - } - else if (cinfo->ignore == CCX_TRUE && - (cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 || !ccx_options.analyze_video_stream)) - { - if (cinfo->codec_private_data) - { - switch (cinfo->codec) - { - case CCX_CODEC_TELETEXT: - telxcc_close(&cinfo->codec_private_data, NULL); - break; - case CCX_CODEC_DVB: - dvbsub_close_decoder(&cinfo->codec_private_data); - break; - case CCX_CODEC_ISDB_CC: - delete_isdb_decoder(&cinfo->codec_private_data); - default: - break; - } - cinfo->codec_private_data = NULL; - } - - if (cinfo->capbuflen > 0) - { - freep(&cinfo->capbuf); - cinfo->capbufsize = 0; - cinfo->capbuflen = 0; - delete_demuxer_data_node_by_pid(data, cinfo->pid); - } - continue; - } - - // Video PES start - if (payload.pesstart) - { - cinfo->saw_pesstart = 1; - cinfo->prev_counter = payload.counter - 1; - } - - // Discard packets when no pesstart was found. - if (!cinfo->saw_pesstart) - continue; - - if ((cinfo->prev_counter == 15 ? 0 : cinfo->prev_counter + 1) != payload.counter) - { - mprint("TS continuity counter not incremented prev/curr %u/%u\n", - cinfo->prev_counter, payload.counter); - } - cinfo->prev_counter = payload.counter; - - // If the buffer is empty we just started this function - if (payload.pesstart && cinfo->capbuflen > 0) - { - dbg_print(CCX_DMT_PARSE, "\nPES finished (%ld bytes/%ld PES packets/%ld total packets)\n", - cinfo->capbuflen, pespcount, pcount); - - // Keep the data from capbuf to be worked on - ret = copy_capbuf_demux_data(ctx, data, cinfo); - cinfo->capbuflen = 0; - gotpes = 1; - } - - copy_payload_to_capbuf(cinfo, &payload); - if (ret < 0) - { - if (errno == EINVAL) - continue; - else - break; - } - - pespcount++; - } while (!gotpes); // gotpes==1 never arrives here because of the breaks - - for (int i = 0; i < ctx->nb_program; i++) - { - pinfo = &ctx->pinfo[i]; - if (!pinfo->has_all_min_pts) - { - for (int j = 0; j < ctx->num_of_PIDs; j++) - { - if (ctx->PIDs_programs[ctx->have_PIDs[j]]) - { - if (ctx->PIDs_programs[ctx->have_PIDs[j]]->program_number == pinfo->program_number) - { - if (ctx->min_pts[j] != UINT64_MAX) - { - if (ctx->stream_id_of_each_pid[j] == 0xbd) - if (ctx->min_pts[j] < pinfo->got_important_streams_min_pts[PRIVATE_STREAM_1]) - pinfo->got_important_streams_min_pts[PRIVATE_STREAM_1] = ctx->min_pts[j]; - if (ctx->stream_id_of_each_pid[j] >= 0xc0 && ctx->stream_id_of_each_pid[j] <= 0xdf) - if (ctx->min_pts[j] < pinfo->got_important_streams_min_pts[AUDIO]) - pinfo->got_important_streams_min_pts[AUDIO] = ctx->min_pts[j]; - if (ctx->stream_id_of_each_pid[j] >= 0xe0 && ctx->stream_id_of_each_pid[j] <= 0xef) - if (ctx->min_pts[j] < pinfo->got_important_streams_min_pts[VIDEO]) - pinfo->got_important_streams_min_pts[VIDEO] = ctx->min_pts[j]; - if (pinfo->got_important_streams_min_pts[PRIVATE_STREAM_1] != UINT64_MAX && - pinfo->got_important_streams_min_pts[AUDIO] != UINT64_MAX && - pinfo->got_important_streams_min_pts[VIDEO] != UINT64_MAX) - pinfo->has_all_min_pts = 1; - } - } - } - } - } - } - - if (ret == CCX_EOF) - { - cinfo_cremation(ctx, data); - } - return ret; + int gotpes = 0; // Flag indicating complete PES received + long pespcount = 0; // Count of packets in current PES + long pcount = 0; // Total packet count + int packet_analysis_mode = 0; // Flag for general CC search mode + int ret = CCX_EAGAIN; // Return value + struct program_info *pinfo = NULL; // Program information + struct cap_info *cinfo; // Caption information + struct ts_payload payload; // Current packet payload + int j; // Loop variable + + memset(&payload, 0, sizeof(payload)); // Initialize payload structure + +//============================================================================= +// MAIN PACKET PROCESSING LOOP +//============================================================================= + + do + { + pcount++; // Increment packet counter + + // Read next transport stream packet + ret = ts_readpacket(ctx, &payload); + if (ret != CCX_OK) + break; // EOF or error + + // Skip packets with transport errors + if (payload.transport_error) + { + dbg_print(CCX_DMT_VERBOSE, "Packet (pid %u) skipped - transport error.\n", payload.pid); + continue; + } + +//============================================================================= +// PSI TABLE PROCESSING - Handle Program Association Table +//============================================================================= + + if (payload.pid == 0) // PAT (Program Association Table) + { + ts_buffer_psi_packet(ctx); // Buffer PSI packet + if (ctx->PID_buffers[payload.pid] != NULL && ctx->PID_buffers[payload.pid]->buffer_length > 0) + parse_PAT(ctx); // Parse complete PAT + continue; + } + +//============================================================================= +// EPG DATA PROCESSING - Handle Electronic Program Guide data +//============================================================================= + + if (ccx_options.xmltv >= 1 && payload.pid == 0x11) + { + // SDT (Service Description Table) or BAT (Bouquet Association Table) + ts_buffer_psi_packet(ctx); + if (ctx->PID_buffers[payload.pid] != NULL && ctx->PID_buffers[payload.pid]->buffer_length > 0) + parse_SDT(ctx); + } + + if (ccx_options.xmltv >= 1 && payload.pid == 0x12) // DVB EIT (Event Information Table) + parse_EPG_packet(ctx->parent); + if (ccx_options.xmltv >= 1 && payload.pid >= 0x1000) // ATSC EPG packet + parse_EPG_packet(ctx->parent); + +//============================================================================= +// PMT PROCESSING - Handle Program Map Tables +//============================================================================= + + // Check if this PID matches any PMT PID + for (j = 0; j < ctx->nb_program; j++) + { + // Handle PCR (Program Clock Reference) updates + if (ctx->pinfo[j].analysed_PMT_once == CCX_TRUE && + ctx->pinfo[j].pcr_pid == payload.pid && + payload.have_pcr) + { + // Update global timestamp from PCR + ctx->last_global_timestamp = ctx->global_timestamp; + ctx->global_timestamp = (uint32_t)payload.pcr / 90; // Convert to milliseconds + if (!ctx->global_timestamp_inited) + { + ctx->min_global_timestamp = ctx->global_timestamp; + ctx->global_timestamp_inited = 1; + } + if (ctx->min_global_timestamp > ctx->global_timestamp) + { + ctx->offset_global_timestamp = ctx->last_global_timestamp - ctx->min_global_timestamp; + ctx->min_global_timestamp = ctx->global_timestamp; + } + } + + // Check if this is a PMT PID + if (ctx->pinfo[j].pid == payload.pid) + { + if (!ctx->PIDs_seen[payload.pid]) + dbg_print(CCX_DMT_PAT, "This PID (%u) is a PMT for program %u.\n", payload.pid, ctx->pinfo[j].program_number); + pinfo = ctx->pinfo + j; + break; + } + } + + // If this was a PMT packet, process it + if (j != ctx->nb_program) + { + ctx->PIDs_seen[payload.pid] = 2; // Mark as PMT + ts_buffer_psi_packet(ctx); + if (ctx->PID_buffers[payload.pid] != NULL && ctx->PID_buffers[payload.pid]->buffer_length > 0) + if (parse_PMT(ctx, ctx->PID_buffers[payload.pid]->buffer + 1, ctx->PID_buffers[payload.pid]->buffer_length - 1, pinfo)) + gotpes = 1; // PMT changed, flush buffer + continue; + } + +//============================================================================= +// PID TRACKING AND IDENTIFICATION +//============================================================================= + + switch (ctx->PIDs_seen[payload.pid]) + { + case 0: // First time seeing this PID + if (ctx->PIDs_programs[payload.pid]) + { + dbg_print(CCX_DMT_PARSE, "\nNew PID found: %u (%s), belongs to program: %u\n", payload.pid, + desc[ctx->PIDs_programs[payload.pid]->printable_stream_type], + ctx->PIDs_programs[payload.pid]->program_number); + ctx->PIDs_seen[payload.pid] = 2; + } + else + { + dbg_print(CCX_DMT_PARSE, "\nNew PID found: %u, program number still unknown\n", payload.pid); + ctx->PIDs_seen[payload.pid] = 1; + } + ctx->have_PIDs[ctx->num_of_PIDs] = payload.pid; + ctx->num_of_PIDs++; + break; + case 1: // Seen before but program unknown + if (ctx->PIDs_programs[payload.pid]) + { + dbg_print(CCX_DMT_PARSE, "\nProgram for PID: %u (previously unknown) is: %u (%s)\n", payload.pid, + ctx->PIDs_programs[payload.pid]->program_number, + desc[ctx->PIDs_programs[payload.pid]->printable_stream_type]); + ctx->PIDs_seen[payload.pid] = 2; + } + break; + case 2: // Already seen and identified + break; + case 3: // Already inspected for CC data + break; + } + +//============================================================================= +// PTS EXTRACTION - Get timing information from PES headers +//============================================================================= + + if (payload.pesstart) // PES header present + { + // Check for valid PES start code + uint64_t pes_prefix = (payload.start[0] << 16) | (payload.start[9] << 8) | payload.start[1]; + uint8_t pes_stream_id = payload.start[2]; + uint64_t pts = 0; + + if (pes_prefix == 0x000001) + { + pts = get_pts(payload.start); // Extract PTS + + // Find PID index and store stream ID and PTS + int pid_index; + for (int i = 0; i < ctx->num_of_PIDs; i++) + if (payload.pid == ctx->have_PIDs[i]) + pid_index = i; + ctx->stream_id_of_each_pid[pid_index] = pes_stream_id; + if (pts < ctx->min_pts[pid_index]) + ctx->min_pts[pid_index] = pts; + } + } + +//============================================================================= +// SPECIAL PACKET HANDLING +//============================================================================= + + if (payload.pid == 8191) // Null packet (padding) + continue; + + // Hauppauge detection + if (payload.pid == 1003 && !ctx->hauppauge_warning_shown && !ccx_options.hauppauge_mode) + { + mprint("\n\nNote: This TS could be a recording from a Hauppage card. If no captions are detected, try --hauppauge\n\n"); + ctx->hauppauge_warning_shown = 1; + } + + // Hauppauge caption processing + if (ccx_options.hauppauge_mode && payload.pid == HAUPPAGE_CCPID) + { + // Expand Hauppauge buffer if needed + int haup_newcapbuflen = haup_capbuflen + payload.length; + if (haup_newcapbuflen > haup_capbufsize) + { + haup_capbuf = (unsigned char *)realloc(haup_capbuf, haup_newcapbuflen); + if (!haup_capbuf) + fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory to store hauppauge packets"); + haup_capbufsize = haup_newcapbuflen; + } + // Copy Hauppauge payload data + memcpy(haup_capbuf + haup_capbuflen, payload.start, payload.length); + haup_capbuflen = haup_newcapbuflen; + } + + // Skip empty packets + if (!payload.length) + { + dbg_print(CCX_DMT_VERBOSE, "Packet (pid %u) skipped - no payload.\n", payload.pid); + continue; + } + +//============================================================================= +// CAPTION DATA PROCESSING - Handle packets with caption information +//============================================================================= + + cinfo = get_cinfo(ctx, payload.pid); // Get caption info for this PID + if (cinfo == NULL) + { + if (!packet_analysis_mode) + dbg_print(CCX_DMT_PARSE, "Packet (pid %u) skipped - no stream with captions identified yet.\n", payload.pid); + else + look_for_caption_data(ctx, &payload); // Search for GA94 marker + continue; + } + else if (cinfo->ignore == CCX_TRUE && + (cinfo->stream != CCX_STREAM_TYPE_VIDEO_MPEG2 || !ccx_options.analyze_video_stream)) + { + // Clean up ignored streams + if (cinfo->codec_private_data) + { + switch (cinfo->codec) + { + case CCX_CODEC_TELETEXT: + telxcc_close(&cinfo->codec_private_data, NULL); + break; + case CCX_CODEC_DVB: + dvbsub_close_decoder(&cinfo->codec_private_data); + break; + case CCX_CODEC_ISDB_CC: + delete_isdb_decoder(&cinfo->codec_private_data); + default: + break; + } + cinfo->codec_private_data = NULL; + } + + if (cinfo->capbuflen > 0) + { + freep(&cinfo->capbuf); // Free caption buffer + cinfo->capbufsize = 0; + cinfo->capbuflen = 0; + delete_demuxer_data_node_by_pid(data, cinfo->pid); + } + continue; + } + +//============================================================================= +// PES PACKET ASSEMBLY - Build complete PES packets from TS packets +//============================================================================= + + // Handle PES start + if (payload.pesstart) + { + cinfo->saw_pesstart = 1; // Mark PES start seen + cinfo->prev_counter = payload.counter - 1; + } + + // Skip packets until PES start found + if (!cinfo->saw_pesstart) + continue; + + // Check continuity counter + if ((cinfo->prev_counter == 15 ? 0 : cinfo->prev_counter + 1) != payload.counter) + { + mprint("TS continuity counter not incremented prev/curr %u/%u\n", + cinfo->prev_counter, payload.counter); + } + cinfo->prev_counter = payload.counter; + + // Handle PES completion + if (payload.pesstart && cinfo->capbuflen > 0) + { + dbg_print(CCX_DMT_PARSE, "\nPES finished (%ld bytes/%ld PES packets/%ld total packets)\n", + cinfo->capbuflen, pespcount, pcount); + + // Process completed PES + ret = copy_capbuf_demux_data(ctx, data, cinfo); + cinfo->capbuflen = 0; // Reset buffer + gotpes = 1; // Mark PES complete + } + + // Copy payload to caption buffer + copy_payload_to_capbuf(cinfo, &payload); + if (ret < 0) + { + if (errno == EINVAL) + continue; // Skip invalid data + else + break; // Fatal error + } + + pespcount++; // Increment PES packet count + + } while (!gotpes); // Continue until PES complete + +//============================================================================= +// PROGRAM TIMING ANALYSIS - Track minimum PTS for synchronization +//============================================================================= + + for (int i = 0; i < ctx->nb_program; i++) + { + pinfo = &ctx->pinfo[i]; + if (!pinfo->has_all_min_pts) + { + // Check all PIDs for this program + for (int j = 0; j < ctx->num_of_PIDs; j++) + { + if (ctx->PIDs_programs[ctx->have_PIDs[j]]) + { + if (ctx->PIDs_programs[ctx->have_PIDs[j]]->program_number == pinfo->program_number) + { + if (ctx->min_pts[j] != UINT64_MAX) + { + // Update minimum PTS for different stream types + if (ctx->stream_id_of_each_pid[j] == 0xbd) // Private stream 1 + if (ctx->min_pts[j] < pinfo->got_important_streams_min_pts[PRIVATE_STREAM_1]) + pinfo->got_important_streams_min_pts[PRIVATE_STREAM_1] = ctx->min_pts[j]; + if (ctx->stream_id_of_each_pid[j] >= 0xc0 && ctx->stream_id_of_each_pid[j] <= 0xdf) // Audio + if (ctx->min_pts[j] < pinfo->got_important_streams_min_pts[AUDIO]) + pinfo->got_important_streams_min_pts[AUDIO] = ctx->min_pts[j]; + if (ctx->stream_id_of_each_pid[j] >= 0xe0 && ctx->stream_id_of_each_pid[j] <= 0xef) // Video + if (ctx->min_pts[j] < pinfo->got_important_streams_min_pts[VIDEO]) + pinfo->got_important_streams_min_pts[VIDEO] = ctx->min_pts[j]; + + // Check if we have all important stream types + if (pinfo->got_important_streams_min_pts[PRIVATE_STREAM_1] != UINT64_MAX && + pinfo->got_important_streams_min_pts[AUDIO] != UINT64_MAX && + pinfo->got_important_streams_min_pts[VIDEO] != UINT64_MAX) + pinfo->has_all_min_pts = 1; + } + } + } + } + } + } + + // Handle end-of-file cleanup + if (ret == CCX_EOF) + { + cinfo_cremation(ctx, data); // Process remaining caption data + } + + return ret; } -// TS specific data grabber +//============================================================================= +// FUNCTION: ts_get_more_data +// PURPOSE: Main interface function to get demuxed transport stream data +// PARAMETERS: ctx - Library context, data - Output demuxer data +// RETURNS: Status code (CCX_OK, CCX_EOF, error code) +//============================================================================= + int ts_get_more_data(struct lib_ccx_ctx *ctx, struct demuxer_data **data) { - int ret = CCX_OK; + int ret = CCX_OK; - do - { - ret = ts_readstream(ctx->demux_ctx, data); - } while (ret == CCX_EAGAIN); + // Keep reading until we get complete data or reach EOF/error + do + { + ret = ts_readstream(ctx->demux_ctx, data); + } while (ret == CCX_EAGAIN); // EAGAIN means try again - return ret; + return ret; } diff --git a/src/lib_ccx/ts_tables.c b/src/lib_ccx/ts_tables.c index ceb99a383..224b02a31 100644 --- a/src/lib_ccx/ts_tables.c +++ b/src/lib_ccx/ts_tables.c @@ -8,80 +8,83 @@ static unsigned pmt_warning_shown = 0; // Only display warning once void process_ccx_mpeg_descriptor(unsigned char *data, unsigned length); +// *** HEVC SUPPORT ADDED HERE *** unsigned get_printable_stream_type(enum ccx_stream_type stream_type) { - enum ccx_stream_type tmp_stream_type = stream_type; - switch (stream_type) - { - case CCX_STREAM_TYPE_VIDEO_MPEG2: - case CCX_STREAM_TYPE_VIDEO_H264: - case CCX_STREAM_TYPE_PRIVATE_MPEG2: - case CCX_STREAM_TYPE_PRIVATE_TABLE_MPEG2: - case CCX_STREAM_TYPE_MHEG_PACKETS: - case CCX_STREAM_TYPE_MPEG2_ANNEX_A_DSM_CC: - case CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_A: - case CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_B: - case CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_C: - case CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_D: - case CCX_STREAM_TYPE_ITU_T_H222_1: - case CCX_STREAM_TYPE_VIDEO_MPEG1: - case CCX_STREAM_TYPE_AUDIO_MPEG1: - case CCX_STREAM_TYPE_AUDIO_MPEG2: - case CCX_STREAM_TYPE_AUDIO_AAC: - case CCX_STREAM_TYPE_VIDEO_MPEG4: - case CCX_STREAM_TYPE_AUDIO_AC3: - case CCX_STREAM_TYPE_AUDIO_DTS: - case CCX_STREAM_TYPE_AUDIO_HDMV_DTS: - break; - default: - if (stream_type >= 0x80 && stream_type <= 0xFF) - tmp_stream_type = CCX_STREAM_TYPE_PRIVATE_USER_MPEG2; - else - tmp_stream_type = CCX_STREAM_TYPE_UNKNOWNSTREAM; - break; - } - return tmp_stream_type; + enum ccx_stream_type tmp_stream_type = stream_type; + switch (stream_type) + { + case CCX_STREAM_TYPE_VIDEO_MPEG2: + case CCX_STREAM_TYPE_VIDEO_H264: + case CCX_STREAM_TYPE_VIDEO_HEVC: // ← HEVC SUPPORT ADDED + case CCX_STREAM_TYPE_PRIVATE_MPEG2: + case CCX_STREAM_TYPE_PRIVATE_TABLE_MPEG2: + case CCX_STREAM_TYPE_MHEG_PACKETS: + case CCX_STREAM_TYPE_MPEG2_ANNEX_A_DSM_CC: + case CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_A: + case CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_B: + case CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_C: + case CCX_STREAM_TYPE_ISO_IEC_13818_6_TYPE_D: + case CCX_STREAM_TYPE_ITU_T_H222_1: + case CCX_STREAM_TYPE_VIDEO_MPEG1: + case CCX_STREAM_TYPE_AUDIO_MPEG1: + case CCX_STREAM_TYPE_AUDIO_MPEG2: + case CCX_STREAM_TYPE_AUDIO_AAC: + case CCX_STREAM_TYPE_VIDEO_MPEG4: + case CCX_STREAM_TYPE_AUDIO_AC3: + case CCX_STREAM_TYPE_AUDIO_DTS: + case CCX_STREAM_TYPE_AUDIO_HDMV_DTS: + break; + default: + if (stream_type >= 0x80 && stream_type <= 0xFF) + tmp_stream_type = CCX_STREAM_TYPE_PRIVATE_USER_MPEG2; + else + tmp_stream_type = CCX_STREAM_TYPE_UNKNOWNSTREAM; + break; + } + return tmp_stream_type; } void clear_PMT_array(struct ccx_demuxer *ctx) { - if (ctx->flag_ts_forced_pn == CCX_FALSE) - { - ctx->nb_program = 0; - } + if (ctx->flag_ts_forced_pn == CCX_FALSE) + { + ctx->nb_program = 0; + } } int need_program(struct ccx_demuxer *ctx) { - if (ctx->nb_program == 0) - return CCX_TRUE; + if (ctx->nb_program == 0) + return CCX_TRUE; - if (ctx->nb_program == 1 && ctx->ts_autoprogram == CCX_TRUE) - return CCX_FALSE; + if (ctx->nb_program == 1 && ctx->ts_autoprogram == CCX_TRUE) + return CCX_FALSE; - return CCX_FALSE; + return CCX_FALSE; } + int update_pinfo(struct ccx_demuxer *ctx, int pid, int program_number) { - if (!ctx) - return -1; - - if (ctx->nb_program >= MAX_PROGRAM) - return -1; - - ctx->pinfo[ctx->nb_program].pid = pid; - ctx->pinfo[ctx->nb_program].program_number = program_number; - ctx->pinfo[ctx->nb_program].analysed_PMT_once = CCX_FALSE; - ctx->pinfo[ctx->nb_program].name[0] = '\0'; - ctx->pinfo[ctx->nb_program].pcr_pid = -1; - ctx->pinfo[ctx->nb_program].has_all_min_pts = 0; - for (int i = 0; i < COUNT; i++) - { - ctx->pinfo[ctx->nb_program].got_important_streams_min_pts[i] = UINT64_MAX; - } - ctx->nb_program++; - - return CCX_OK; + if (!ctx) + return -1; + + if (ctx->nb_program >= MAX_PROGRAM) + return -1; + + ctx->pinfo[ctx->nb_program].pid = pid; + ctx->pinfo[ctx->nb_program].program_number = program_number; + ctx->pinfo[ctx->nb_program].analysed_PMT_once = CCX_FALSE; + ctx->pinfo[ctx->nb_program].name[0] = '\0'; + ctx->pinfo[ctx->nb_program].pcr_pid = -1; + ctx->pinfo[ctx->nb_program].has_all_min_pts = 0; + for (int i = 0; i < COUNT; i++) + { + ctx->pinfo[ctx->nb_program].got_important_streams_min_pts[i] = UINT64_MAX; + } + ctx->nb_program++; + + return CCX_OK; } /* Process Program Map Table - The PMT contains a list of streams in a program. @@ -93,432 +96,434 @@ int update_pinfo(struct ccx_demuxer *ctx, int pid, int program_number) int parse_PMT(struct ccx_demuxer *ctx, unsigned char *buf, int len, struct program_info *pinfo) { - int must_flush = 0; - int ret = 0; - unsigned char desc_len = 0; - unsigned char *sbuf = buf; - unsigned int olen = len; - uint32_t crc; - - uint8_t table_id; - uint16_t section_length; - uint16_t program_number; - uint8_t version_number; - uint8_t current_next_indicator; - uint8_t section_number; - uint8_t last_section_number; - - uint16_t pi_length; - - crc = (*(int32_t *)(sbuf + olen - 4)); - table_id = buf[0]; - - /* TO-DO: We're currently parsing the PMT making assumptions that there's only one section with table_id=2, - but that doesn't have to be the case. There's a sample (friends_tbs.ts) that shows a previous section with - table_id = 0xc0. I can't find any place that says that 0xc0 (Program Information Table) must come before - table_id = 2, so we should process sections in any order. - Check https://github.com/CCExtractor/ccextractor/issues/385 for more info - */ - if (table_id == 0xC0) - { - /* - * Acc to System Information for Satellite Distribution - * of Digital Television for Cable and MMDS (ANSI/SCTE 57 2003 ) - * PROGRAM INFORMATION Table found in PMT - */ - dbg_print(CCX_DMT_PMT, "PMT: PROGRAM INFORMATION Table need implementation"); - // For now, just parse its length and remove it from the buffer - unsigned c0length = (buf[1] << 8 | buf[2]) & 0xFFF; // 12 bytes - dbg_print(CCX_DMT_PMT, "Program information table length: %u", c0length); - memmove(buf, buf + c0length + 3, len - c0length - 3); // First 3 bytes are for the table_id and the length, don't count - table_id = buf[0]; - // return 0; - } - else if (table_id == 0xC1) - { - // SCTE 57 2003 - dbg_print(CCX_DMT_PMT, "PMT: PROGRAM NAME Table need implementation"); - unsigned c0length = (buf[1] << 8 | buf[2]) & 0xFFF; // 12 bytes - dbg_print(CCX_DMT_PMT, "Program name message length: %u", c0length); - memmove(buf, buf + c0length + 3, len - c0length - 3); // First 3 bytes are for the table_id and the length, don't count - table_id = buf[0]; - // return 0; - } - else if (table_id != 0x2) - { - mprint("Please Report: Unknown table id in PMT expected 0x02 found 0x%X\n", table_id); - return 0; - } - - section_length = (((buf[1] & 0x0F) << 8) | buf[2]); - if (section_length > (len - 3)) - { - return 0; // We don't have the full section yet. We will parse again when we have it. - } - - program_number = ((buf[3] << 8) | buf[4]); - version_number = (buf[5] & 0x3E) >> 1; - - current_next_indicator = buf[5] & 0x01; - // This table is not active, no need to evaluate - if (!current_next_indicator && pinfo->version != 0xFF) // 0xFF means we don't have one yet - return 0; - - memcpy(pinfo->saved_section, buf, len); - - if (pinfo->analysed_PMT_once == CCX_TRUE && pinfo->version == version_number) - { - if (pinfo->version == version_number) - { - /* Same Version number and there was valid or similar CRC last time */ - if (pinfo->valid_crc == CCX_TRUE || pinfo->crc == crc) - return 0; - } - else if ((pinfo->version + 1) % 32 != version_number) - mprint("TS PMT:Glitch in version number increment"); - } - pinfo->version = version_number; - - section_number = buf[6]; - last_section_number = buf[7]; - if (last_section_number > 0) - { - mprint("Long PMTs are not supported - skipped.\n"); - return 0; - } - - pinfo->pcr_pid = (((buf[8] & 0x1F) << 8) | buf[9]); - pi_length = (((buf[10] & 0x0F) << 8) | buf[11]); - - if (12 + pi_length > len) - { - // If we would support long PMTs, this would be wrong. - mprint("program_info_length cannot be longer than the payload_length - skipped\n"); - return 0; - } - buf += 12 + pi_length; - len -= (12 + pi_length); - - unsigned stream_data = section_length - 9 - pi_length - 4; // prev. bytes and CRC - - dbg_print(CCX_DMT_PMT, "Read PMT packet (id: %u) program number: %u\n", - table_id, program_number); - dbg_print(CCX_DMT_PMT, " section length: %u number: %u last: %u\n", - section_length, section_number, last_section_number); - dbg_print(CCX_DMT_PMT, " version_number: %u current_next_indicator: %u\n", - version_number, current_next_indicator); - dbg_print(CCX_DMT_PMT, " PCR_PID: %u data length: %u payload_length: %u\n", - pinfo->pcr_pid, stream_data, len); - - if (!pmt_warning_shown && stream_data + 4 > len) - { - dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Probably parsing incomplete PMT, expected data longer than available payload.\n"); - pmt_warning_shown = 1; - } - // Make a note of the program number for all PIDs, so we can report it later - for (unsigned i = 0; i < stream_data && (i + 4) < len; i += 5) - { - enum ccx_stream_type stream_type = buf[i]; - unsigned elementary_PID = (((buf[i + 1] & 0x1F) << 8) | buf[i + 2]); - unsigned ES_info_length = (((buf[i + 3] & 0x0F) << 8) | buf[i + 4]); - if (ctx->PIDs_programs[elementary_PID] == NULL) - { - ctx->PIDs_programs[elementary_PID] = (struct PMT_entry *)malloc(sizeof(struct PMT_entry)); - if (ctx->PIDs_programs[elementary_PID] == NULL) - fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory to process PMT."); - } - ctx->PIDs_programs[elementary_PID]->elementary_PID = elementary_PID; - ctx->PIDs_programs[elementary_PID]->stream_type = stream_type; - ctx->PIDs_programs[elementary_PID]->program_number = program_number; - ctx->PIDs_programs[elementary_PID]->printable_stream_type = get_printable_stream_type(stream_type); - dbg_print(CCX_DMT_VERBOSE, "%6u | %3X (%3u) | %s\n", elementary_PID, stream_type, stream_type, - desc[ctx->PIDs_programs[elementary_PID]->printable_stream_type]); - process_ccx_mpeg_descriptor(buf + i + 5, ES_info_length); - i += ES_info_length; - } - dbg_print(CCX_DMT_VERBOSE, "---\n"); - - dbg_print(CCX_DMT_PMT, "\nProgram map section (PMT)\n"); - - for (unsigned i = 0; i < stream_data && (i + 4) < len; i += 5) - { - enum ccx_stream_type stream_type = buf[i]; - unsigned elementary_PID = (((buf[i + 1] & 0x1F) << 8) | buf[i + 2]); - unsigned ES_info_length = (((buf[i + 3] & 0x0F) << 8) | buf[i + 4]); - - if (!ccx_options.print_file_reports || - stream_type != CCX_STREAM_TYPE_PRIVATE_MPEG2 || - !ES_info_length) - { - i += ES_info_length; - continue; - } - - unsigned char *es_info = buf + i + 5; - for (desc_len = 0; (buf + i + 5 + ES_info_length) > es_info; es_info += desc_len) - { - enum ccx_mpeg_descriptor descriptor_tag = (enum ccx_mpeg_descriptor)(*es_info++); - desc_len = (*es_info++); - - if (descriptor_tag == CCX_MPEG_DSC_DVB_SUBTITLE) - { - int k = 0; - for (int j = 0; j < SUB_STREAMS_CNT; j++) - { - if (ctx->freport.dvb_sub_pid[j] == 0) - k = j; - if (ctx->freport.dvb_sub_pid[j] == elementary_PID) - { - k = j; - break; - } - } - ctx->freport.dvb_sub_pid[k] = elementary_PID; - } - if (IS_VALID_TELETEXT_DESC(descriptor_tag)) - { - int k = 0; - for (int j = 0; j < SUB_STREAMS_CNT; j++) - { - if (ctx->freport.tlt_sub_pid[j] == 0) - k = j; - if (ctx->freport.tlt_sub_pid[j] == elementary_PID) - { - k = j; - break; - } - } - ctx->freport.tlt_sub_pid[k] = elementary_PID; - } - } - i += ES_info_length; - } - - for (unsigned i = 0; i < stream_data && (i + 4) < len; i += 5) - { - enum ccx_stream_type stream_type = buf[i]; - unsigned elementary_PID = (((buf[i + 1] & 0x1F) << 8) | buf[i + 2]); - unsigned ES_info_length = (((buf[i + 3] & 0x0F) << 8) | buf[i + 4]); - - if (stream_type == CCX_STREAM_TYPE_PRIVATE_MPEG2 && - ES_info_length) - { - unsigned char *es_info = buf + i + 5; - for (desc_len = 0; (buf + i + 5 + ES_info_length) > es_info; es_info += desc_len) - { - void *ptr; - enum ccx_mpeg_descriptor descriptor_tag = (enum ccx_mpeg_descriptor)(*es_info++); - desc_len = (*es_info++); - if (CCX_MPEG_DESC_DATA_COMP == descriptor_tag) - { - int16_t component_id = 0; - if (!IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_ISDB_CC)) - continue; - if (desc_len < 2) - break; - - component_id = RB16(es_info); - if (component_id != 0x08) - break; - mprint("*****ISDB subtitles detected\n"); - ptr = init_isdb_decoder(); - if (ptr == NULL) - break; - update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_ISDB_CC, program_number, ptr); - } - if (CCX_MPEG_DSC_DVB_SUBTITLE == descriptor_tag) - { - struct dvb_config cnf; + int must_flush = 0; + int ret = 0; + unsigned char desc_len = 0; + unsigned char *sbuf = buf; + unsigned int olen = len; + uint32_t crc; + + uint8_t table_id; + uint16_t section_length; + uint16_t program_number; + uint8_t version_number; + uint8_t current_next_indicator; + uint8_t section_number; + uint8_t last_section_number; + + uint16_t pi_length; + + crc = (*(int32_t *)(sbuf + olen - 4)); + table_id = buf[0]; + + /* TO-DO: We're currently parsing the PMT making assumptions that there's only one section with table_id=2, + but that doesn't have to be the case. There's a sample (friends_tbs.ts) that shows a previous section with + table_id = 0xc0. I can't find any place that says that 0xc0 (Program Information Table) must come before + table_id = 2, so we should process sections in any order. + Check https://github.com/CCExtractor/ccextractor/issues/385 for more info + */ + if (table_id == 0xC0) + { + /* + * Acc to System Information for Satellite Distribution + * of Digital Television for Cable and MMDS (ANSI/SCTE 57 2003 ) + * PROGRAM INFORMATION Table found in PMT + */ + dbg_print(CCX_DMT_PMT, "PMT: PROGRAM INFORMATION Table need implementation"); + // For now, just parse its length and remove it from the buffer + unsigned c0length = (buf[1] << 8 | buf[2]) & 0xFFF; // 12 bytes + dbg_print(CCX_DMT_PMT, "Program information table length: %u", c0length); + memmove(buf, buf + c0length + 3, len - c0length - 3); // First 3 bytes are for the table_id and the length, don't count + table_id = buf[0]; + // return 0; + } + else if (table_id == 0xC1) + { + // SCTE 57 2003 + dbg_print(CCX_DMT_PMT, "PMT: PROGRAM NAME Table need implementation"); + unsigned c0length = (buf[1] << 8 | buf[2]) & 0xFFF; // 12 bytes + dbg_print(CCX_DMT_PMT, "Program name message length: %u", c0length); + memmove(buf, buf + c0length + 3, len - c0length - 3); // First 3 bytes are for the table_id and the length, don't count + table_id = buf[0]; + // return 0; + } + else if (table_id != 0x2) + { + mprint("Please Report: Unknown table id in PMT expected 0x02 found 0x%X\n", table_id); + return 0; + } + + section_length = (((buf[1] & 0x0F) << 8) | buf[2]); + if (section_length > (len - 3)) + { + return 0; // We don't have the full section yet. We will parse again when we have it. + } + + program_number = ((buf[3] << 8) | buf[4]); + version_number = (buf[5] & 0x3E) >> 1; + + current_next_indicator = buf[5] & 0x01; + // This table is not active, no need to evaluate + if (!current_next_indicator && pinfo->version != 0xFF) // 0xFF means we don't have one yet + return 0; + + memcpy(pinfo->saved_section, buf, len); + + if (pinfo->analysed_PMT_once == CCX_TRUE && pinfo->version == version_number) + { + if (pinfo->version == version_number) + { + /* Same Version number and there was valid or similar CRC last time */ + if (pinfo->valid_crc == CCX_TRUE || pinfo->crc == crc) + return 0; + } + else if ((pinfo->version + 1) % 32 != version_number) + mprint("TS PMT:Glitch in version number increment"); + } + pinfo->version = version_number; + + section_number = buf[6]; + last_section_number = buf[7]; + if (last_section_number > 0) + { + mprint("Long PMTs are not supported - skipped.\n"); + return 0; + } + + pinfo->pcr_pid = (((buf[8] & 0x1F) << 8) | buf[9]); + pi_length = (((buf[10] & 0x0F) << 8) | buf[11]); + if (12 + pi_length > len) + { + // If we would support long PMTs, this would be wrong. + mprint("program_info_length cannot be longer than the payload_length - skipped\n"); + return 0; + } + buf += 12 + pi_length; + len -= (12 + pi_length); + + unsigned stream_data = section_length - 9 - pi_length - 4; // prev. bytes and CRC + + dbg_print(CCX_DMT_PMT, "Read PMT packet (id: %u) program number: %u\n", + table_id, program_number); + dbg_print(CCX_DMT_PMT, " section length: %u number: %u last: %u\n", + section_length, section_number, last_section_number); + dbg_print(CCX_DMT_PMT, " version_number: %u current_next_indicator: %u\n", + version_number, current_next_indicator); + dbg_print(CCX_DMT_PMT, " PCR_PID: %u data length: %u payload_length: %u\n", + pinfo->pcr_pid, stream_data, len); + + if (!pmt_warning_shown && stream_data + 4 > len) + { + dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Probably parsing incomplete PMT, expected data longer than available payload.\n"); + pmt_warning_shown = 1; + } + // Make a note of the program number for all PIDs, so we can report it later + for (unsigned i = 0; i < stream_data && (i + 4) < len; i += 5) + { + enum ccx_stream_type stream_type = buf[i]; + unsigned elementary_PID = (((buf[i + 1] & 0x1F) << 8) | buf[i + 2]); + unsigned ES_info_length = (((buf[i + 3] & 0x0F) << 8) | buf[i + 4]); + if (ctx->PIDs_programs[elementary_PID] == NULL) + { + ctx->PIDs_programs[elementary_PID] = (struct PMT_entry *)malloc(sizeof(struct PMT_entry)); + if (ctx->PIDs_programs[elementary_PID] == NULL) + fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory to process PMT."); + } + ctx->PIDs_programs[elementary_PID]->elementary_PID = elementary_PID; + ctx->PIDs_programs[elementary_PID]->stream_type = stream_type; + ctx->PIDs_programs[elementary_PID]->program_number = program_number; + ctx->PIDs_programs[elementary_PID]->printable_stream_type = get_printable_stream_type(stream_type); + dbg_print(CCX_DMT_VERBOSE, "%6u | %3X (%3u) | %s\n", elementary_PID, stream_type, stream_type, + desc[ctx->PIDs_programs[elementary_PID]->printable_stream_type]); + process_ccx_mpeg_descriptor(buf + i + 5, ES_info_length); + i += ES_info_length; + } + dbg_print(CCX_DMT_VERBOSE, "---\n"); + + dbg_print(CCX_DMT_PMT, "\nProgram map section (PMT)\n"); + + for (unsigned i = 0; i < stream_data && (i + 4) < len; i += 5) + { + enum ccx_stream_type stream_type = buf[i]; + unsigned elementary_PID = (((buf[i + 1] & 0x1F) << 8) | buf[i + 2]); + unsigned ES_info_length = (((buf[i + 3] & 0x0F) << 8) | buf[i + 4]); + + if (!ccx_options.print_file_reports || + stream_type != CCX_STREAM_TYPE_PRIVATE_MPEG2 || + !ES_info_length) + { + i += ES_info_length; + continue; + } + + unsigned char *es_info = buf + i + 5; + for (desc_len = 0; (buf + i + 5 + ES_info_length) > es_info; es_info += desc_len) + { + enum ccx_mpeg_descriptor descriptor_tag = (enum ccx_mpeg_descriptor)(*es_info++); + desc_len = (*es_info++); + + if (descriptor_tag == CCX_MPEG_DSC_DVB_SUBTITLE) + { + int k = 0; + for (int j = 0; j < SUB_STREAMS_CNT; j++) + { + if (ctx->freport.dvb_sub_pid[j] == 0) + k = j; + if (ctx->freport.dvb_sub_pid[j] == elementary_PID) + { + k = j; + break; + } + } + ctx->freport.dvb_sub_pid[k] = elementary_PID; + } + if (IS_VALID_TELETEXT_DESC(descriptor_tag)) + { + int k = 0; + for (int j = 0; j < SUB_STREAMS_CNT; j++) + { + if (ctx->freport.tlt_sub_pid[j] == 0) + k = j; + if (ctx->freport.tlt_sub_pid[j] == elementary_PID) + { + k = j; + break; + } + } + ctx->freport.tlt_sub_pid[k] = elementary_PID; + } + } + i += ES_info_length; + } + + for (unsigned i = 0; i < stream_data && (i + 4) < len; i += 5) + { + enum ccx_stream_type stream_type = buf[i]; + unsigned elementary_PID = (((buf[i + 1] & 0x1F) << 8) | buf[i + 2]); + unsigned ES_info_length = (((buf[i + 3] & 0x0F) << 8) | buf[i + 4]); + + if (stream_type == CCX_STREAM_TYPE_PRIVATE_MPEG2 && + ES_info_length) + { + unsigned char *es_info = buf + i + 5; + for (desc_len = 0; (buf + i + 5 + ES_info_length) > es_info; es_info += desc_len) + { + void *ptr; + enum ccx_mpeg_descriptor descriptor_tag = (enum ccx_mpeg_descriptor)(*es_info++); + desc_len = (*es_info++); + if (CCX_MPEG_DESC_DATA_COMP == descriptor_tag) + { + int16_t component_id = 0; + if (!IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_ISDB_CC)) + continue; + if (desc_len < 2) + break; + + component_id = RB16(es_info); + if (component_id != 0x08) + break; + mprint("*****ISDB subtitles detected\n"); + ptr = init_isdb_decoder(); + if (ptr == NULL) + break; + update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_ISDB_CC, program_number, ptr); + } + if (CCX_MPEG_DSC_DVB_SUBTITLE == descriptor_tag) + { + struct dvb_config cnf; #ifndef ENABLE_OCR - if (ccx_options.write_format != CCX_OF_SPUPNG) - { - mprint("DVB subtitles detected, OCR subsystem not present. Use --out=spupng for graphic output\n"); - continue; - } + if (ccx_options.write_format != CCX_OF_SPUPNG) + { + mprint("DVB subtitles detected, OCR subsystem not present. Use --out=spupng for graphic output\n"); + continue; + } #endif - if (!IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_DVB)) - continue; - - memset((void *)&cnf, 0, sizeof(struct dvb_config)); - ret = parse_dvb_description(&cnf, es_info, desc_len); - if (ret < 0) - break; - ptr = dvbsub_init_decoder(&cnf, pinfo->initialized_ocr); - if (!pinfo->initialized_ocr) - pinfo->initialized_ocr = 1; - if (ptr == NULL) - break; - update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_DVB, program_number, ptr); - max_dif = 30; - } - } - } - else if (stream_type == CCX_STREAM_TYPE_PRIVATE_USER_MPEG2 && ES_info_length) - { - // if this any generally used video stream tyoe get clashed with ATSC/SCTE standard - // then this code can go in some atsc flag - unsigned char *es_info = buf + i + 5; - for (desc_len = 0; (buf + i + 5 + ES_info_length) > es_info; es_info += desc_len) - { - enum ccx_mpeg_descriptor descriptor_tag = (enum ccx_mpeg_descriptor)(*es_info++); - int nb_service; - int is_608; - int ser_i; - desc_len = (*es_info++); - if (CCX_MPEG_DSC_CAPTION_SERVICE == descriptor_tag) - { - nb_service = es_info[0] & 0x1f; - for (ser_i = 0; ser_i < nb_service; ser_i++) - { - dbg_print(CCX_DMT_PMT, "CC SERVICE %d: language (%c%c%c)", nb_service, + if (!IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_DVB)) + continue; + + memset((void *)&cnf, 0, sizeof(struct dvb_config)); + ret = parse_dvb_description(&cnf, es_info, desc_len); + if (ret < 0) + break; + ptr = dvbsub_init_decoder(&cnf, pinfo->initialized_ocr); + if (!pinfo->initialized_ocr) + pinfo->initialized_ocr = 1; + if (ptr == NULL) + break; + update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_DVB, program_number, ptr); + max_dif = 30; + } + } + } + else if (stream_type == CCX_STREAM_TYPE_PRIVATE_USER_MPEG2 && ES_info_length) + { + // if this any generally used video stream tyoe get clashed with ATSC/SCTE standard + // then this code can go in some atsc flag + unsigned char *es_info = buf + i + 5; + for (desc_len = 0; (buf + i + 5 + ES_info_length) > es_info; es_info += desc_len) + { + enum ccx_mpeg_descriptor descriptor_tag = (enum ccx_mpeg_descriptor)(*es_info++); + int nb_service; + int is_608; + int ser_i; + desc_len = (*es_info++); + if (CCX_MPEG_DSC_CAPTION_SERVICE == descriptor_tag) + { + nb_service = es_info[0] & 0x1f; + for (ser_i = 0; ser_i < nb_service; ser_i++) + { + dbg_print(CCX_DMT_PMT, "CC SERVICE %d: language (%c%c%c)", nb_service, es_info[1], es_info[2], es_info[3]); is_608 = es_info[4] >> 7; - dbg_print(CCX_DMT_PMT, "%s", is_608 ? " CEA-608" : " CEA-708"); - dbg_print(CCX_DMT_PMT, "%s", is_608 ? " CEA-608" : " CEA-708"); - } - } - update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_ATSC_CC, program_number, NULL); - } - } - - if (IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_TELETEXT) && ES_info_length && stream_type == CCX_STREAM_TYPE_PRIVATE_MPEG2) // MPEG-2 Packetized Elementary Stream packets containing private data - { - unsigned char *es_info = buf + i + 5; - for (desc_len = 0; (buf + i + 5 + ES_info_length) - es_info; es_info += desc_len) - { - enum ccx_mpeg_descriptor descriptor_tag = (enum ccx_mpeg_descriptor)(*es_info++); - desc_len = (*es_info++); - if (!IS_VALID_TELETEXT_DESC(descriptor_tag)) - continue; - update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_TELETEXT, program_number, NULL); - mprint("VBI/teletext stream ID %u (0x%x) for SID %u (0x%x)\n", - elementary_PID, elementary_PID, program_number, program_number); - } - } - - if (!IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_TELETEXT) && - stream_type == CCX_STREAM_TYPE_PRIVATE_MPEG2) // MPEG-2 Packetized Elementary Stream packets containing private data - { - unsigned descriptor_tag = buf[i + 5]; - if (descriptor_tag == 0x45) - { - update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_ATSC_CC, program_number, NULL); - // mprint ("VBI stream ID %u (0x%x) for SID %u (0x%x) - teletext is disabled, will be processed as closed captions.\n", - // elementary_PID, elementary_PID, program_number, program_number); - } - } - - if (stream_type == CCX_STREAM_TYPE_VIDEO_H264 || stream_type == CCX_STREAM_TYPE_VIDEO_MPEG2) - { - update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_ATSC_CC, program_number, NULL); - // mprint ("Decode captions from program %d - %s stream [0x%02x] - PID: %u\n", - // program_number , desc[stream_type], stream_type, elementary_PID); - } - - if (need_cap_info_for_pid(ctx, elementary_PID) == CCX_TRUE) - { - // We found the user selected CAPPID in PMT. We make a note of its type and don't - // touch anything else - if (stream_type >= 0x80 && stream_type <= 0xFF) - { - mprint("I can't tell the stream type of the manually selected PID.\n"); - mprint("Please pass -streamtype to select manually.\n"); - fatal(EXIT_FAILURE, "-streamtype has to be manually selected."); - } - update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_NONE, program_number, NULL); - continue; - } - - // For the print command below - unsigned tmp_stream_type = get_printable_stream_type(stream_type); - dbg_print(CCX_DMT_VERBOSE, " %s stream [0x%02x] - PID: %u\n", - desc[tmp_stream_type], - stream_type, elementary_PID); - i += ES_info_length; - } - - pinfo->analysed_PMT_once = CCX_TRUE; - - ret = verify_crc32(sbuf, olen); - if (ret == CCX_FALSE) - pinfo->valid_crc = CCX_FALSE; - else - pinfo->valid_crc = CCX_TRUE; - - pinfo->crc = (*(int32_t *)(sbuf + olen - 4)); - - return must_flush; + dbg_print(CCX_DMT_PMT, "%s", is_608 ? " CEA-608" : " CEA-708"); + dbg_print(CCX_DMT_PMT, "%s", is_608 ? " CEA-608" : " CEA-708"); + } + } + update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_ATSC_CC, program_number, NULL); + } + } + + if (IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_TELETEXT) && ES_info_length && stream_type == CCX_STREAM_TYPE_PRIVATE_MPEG2) // MPEG-2 Packetized Elementary Stream packets containing private data + { + unsigned char *es_info = buf + i + 5; + for (desc_len = 0; (buf + i + 5 + ES_info_length) - es_info; es_info += desc_len) + { + enum ccx_mpeg_descriptor descriptor_tag = (enum ccx_mpeg_descriptor)(*es_info++); + desc_len = (*es_info++); + if (!IS_VALID_TELETEXT_DESC(descriptor_tag)) + continue; + update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_TELETEXT, program_number, NULL); + mprint("VBI/teletext stream ID %u (0x%x) for SID %u (0x%x)\n", + elementary_PID, elementary_PID, program_number, program_number); + } + } + + if (!IS_FEASIBLE(ctx->codec, ctx->nocodec, CCX_CODEC_TELETEXT) && + stream_type == CCX_STREAM_TYPE_PRIVATE_MPEG2) // MPEG-2 Packetized Elementary Stream packets containing private data + { + unsigned descriptor_tag = buf[i + 5]; + if (descriptor_tag == 0x45) + { + update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_ATSC_CC, program_number, NULL); + // mprint ("VBI stream ID %u (0x%x) for SID %u (0x%x) - teletext is disabled, will be processed as closed captions.\n", + //elementary_PID, elementary_PID, program_number, program_number); + } + } + + // *** HEVC SUPPORT ADDED HERE - MAIN VIDEO CODEC PROCESSING *** + if (stream_type == CCX_STREAM_TYPE_VIDEO_H264 || + stream_type == CCX_STREAM_TYPE_VIDEO_MPEG2 || + stream_type == CCX_STREAM_TYPE_VIDEO_HEVC) // ← HEVC SUPPORT ADDED + { + update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_ATSC_CC, program_number, NULL); + // mprint ("Decode captions from program %d - %s stream [0x%02x] - PID: %u\n", + //program_number , desc[stream_type], stream_type, elementary_PID); + } + + if (need_cap_info_for_pid(ctx, elementary_PID) == CCX_TRUE) + { + // We found the user selected CAPPID in PMT. We make a note of its type and don't + // touch anything else + if (stream_type >= 0x80 && stream_type <= 0xFF) + { + mprint("I can't tell the stream type of the manually selected PID.\n"); + mprint("Please pass -streamtype to select manually.\n"); + fatal(EXIT_FAILURE, "-streamtype has to be manually selected."); + } + update_capinfo(ctx, elementary_PID, stream_type, CCX_CODEC_NONE, program_number, NULL); + continue; + } + + // For the print command below + unsigned tmp_stream_type = get_printable_stream_type(stream_type); + dbg_print(CCX_DMT_VERBOSE, " %s stream [0x%02x] - PID: %u\n", + desc[tmp_stream_type], + stream_type, elementary_PID); + i += ES_info_length; + } + + pinfo->analysed_PMT_once = CCX_TRUE; + + ret = verify_crc32(sbuf, olen); + if (ret == CCX_FALSE) + pinfo->valid_crc = CCX_FALSE; + else + pinfo->valid_crc = CCX_TRUE; + + pinfo->crc = (*(int32_t *)(sbuf + olen - 4)); + + return must_flush; } void ts_buffer_psi_packet(struct ccx_demuxer *ctx) { - unsigned char *payload_start = tspacket + 4; - unsigned payload_length = 188 - 4; - // unsigned transport_error_indicator = (tspacket[1]&0x80)>>7; - unsigned payload_start_indicator = (tspacket[1] & 0x40) >> 6; - // unsigned transport_priority = (tspacket[1]&0x20)>>5; - unsigned pid = (((tspacket[1] & 0x1F) << 8) | tspacket[2]) & 0x1FFF; - // unsigned transport_scrambling_control = (tspacket[3]&0xC0)>>6; - unsigned adaptation_field_control = (tspacket[3] & 0x30) >> 4; - unsigned ccounter = tspacket[3] & 0xF; - unsigned adaptation_field_length = 0; - - if (adaptation_field_control & 2) - { - adaptation_field_length = tspacket[4]; - payload_start = payload_start + adaptation_field_length + 1; - payload_length = tspacket + 188 - payload_start; - } - - if (ctx->PID_buffers[pid] == NULL) - { // First packet for this pid. Create a buffer - ctx->PID_buffers[pid] = malloc(sizeof(struct PSI_buffer)); - ctx->PID_buffers[pid]->buffer = NULL; - ctx->PID_buffers[pid]->buffer_length = 0; - ctx->PID_buffers[pid]->ccounter = 0; - ctx->PID_buffers[pid]->prev_ccounter = 0xff; - } - - // skip the packet if the adaptation field length or payload length are out of bounds or broken - if (adaptation_field_length > 184 || payload_length > 184) - { - payload_length = 0; - dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Bad packet, adaptation field too long, skipping.\n"); - } - - if (payload_start_indicator) - { - if (ctx->PID_buffers[pid]->ccounter > 0) - { - ctx->PID_buffers[pid]->ccounter = 0; - } - ctx->PID_buffers[pid]->prev_ccounter = ccounter; - - if (ctx->PID_buffers[pid]->buffer != NULL) - free(ctx->PID_buffers[pid]->buffer); - else - { - // must be first packet for PID - } - ctx->PID_buffers[pid]->buffer = (uint8_t *)malloc(payload_length); - memcpy(ctx->PID_buffers[pid]->buffer, payload_start, payload_length); - ctx->PID_buffers[pid]->buffer_length = payload_length; - ctx->PID_buffers[pid]->ccounter++; - } - else if (ccounter == ctx->PID_buffers[pid]->prev_ccounter + 1 || (ctx->PID_buffers[pid]->prev_ccounter == 0x0f && ccounter == 0)) - { - ctx->PID_buffers[pid]->prev_ccounter = ccounter; - ctx->PID_buffers[pid]->buffer = (uint8_t *)realloc(ctx->PID_buffers[pid]->buffer, ctx->PID_buffers[pid]->buffer_length + payload_length); - memcpy(ctx->PID_buffers[pid]->buffer + ctx->PID_buffers[pid]->buffer_length, payload_start, payload_length); - ctx->PID_buffers[pid]->ccounter++; - ctx->PID_buffers[pid]->buffer_length += payload_length; - } - else if (ctx->PID_buffers[pid]->prev_ccounter <= 0x0f) - { - dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Out of order packets detected for PID: %u.\n\ + unsigned char *payload_start = tspacket + 4; + unsigned payload_length = 188 - 4; + //unsigned transport_error_indicator = (tspacket[1]&0x80)>>7; + unsigned payload_start_indicator = (tspacket[1] & 0x40) >> 6; + // unsigned transport_priority = (tspacket[1]&0x20)>>5; + unsigned pid = (((tspacket[1] & 0x1F) << 8) | tspacket[2]) & 0x1FFF; + // unsigned transport_scrambling_control = (tspacket[3]&0xC0)>>6; + unsigned adaptation_field_control = (tspacket[3] & 0x30) >> 4; + unsigned ccounter = tspacket[3] & 0xF; + unsigned adaptation_field_length = 0; + + if (adaptation_field_control & 2) + { + adaptation_field_length = tspacket[4]; + payload_start = payload_start + adaptation_field_length + 1; + payload_length = tspacket + 188 - payload_start; + } + + if (ctx->PID_buffers[pid] == NULL) + { // First packet for this pid. Create a buffer + ctx->PID_buffers[pid] = malloc(sizeof(struct PSI_buffer)); + ctx->PID_buffers[pid]->buffer = NULL; + ctx->PID_buffers[pid]->buffer_length = 0; + ctx->PID_buffers[pid]->ccounter = 0; + ctx->PID_buffers[pid]->prev_ccounter = 0xff; + } + + // skip the packet if the adaptation field length or payload length are out of bounds or broken + if (adaptation_field_length > 184 || payload_length > 184) + { + payload_length = 0; + dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Bad packet, adaptation field too long, skipping.\n"); + } + + if (payload_start_indicator) + { + if (ctx->PID_buffers[pid]->ccounter > 0) + { + ctx->PID_buffers[pid]->ccounter = 0; + } + ctx->PID_buffers[pid]->prev_ccounter = ccounter; + + if (ctx->PID_buffers[pid]->buffer != NULL) + free(ctx->PID_buffers[pid]->buffer); + else + { + // must be first packet for PID + } + ctx->PID_buffers[pid]->buffer = (uint8_t *)malloc(payload_length); + memcpy(ctx->PID_buffers[pid]->buffer, payload_start, payload_length); + ctx->PID_buffers[pid]->buffer_length = payload_length; + ctx->PID_buffers[pid]->ccounter++; + } + else if (ccounter == ctx->PID_buffers[pid]->prev_ccounter + 1 || (ctx->PID_buffers[pid]->prev_ccounter == 0x0f && ccounter == 0)) + { + ctx->PID_buffers[pid]->prev_ccounter = ccounter; + ctx->PID_buffers[pid]->buffer = (uint8_t *)realloc(ctx->PID_buffers[pid]->buffer, ctx->PID_buffers[pid]->buffer_length + payload_length); + memcpy(ctx->PID_buffers[pid]->buffer + ctx->PID_buffers[pid]->buffer_length, payload_start, payload_length); + ctx->PID_buffers[pid]->ccounter++; + ctx->PID_buffers[pid]->buffer_length += payload_length; + } + else if (ctx->PID_buffers[pid]->prev_ccounter <= 0x0f) + { + dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Out of order packets detected for PID: %u.\n\ ctx->PID_buffers[pid]->prev_ccounter:%" PRIu32 " ctx->ctx->PID_buffers[pid]->ccounter:%" PRIu32 "\n", - pid, ctx->PID_buffers[pid]->prev_ccounter, ctx->PID_buffers[pid]->ccounter); - } + pid, ctx->PID_buffers[pid]->prev_ccounter, ctx->PID_buffers[pid]->ccounter); + } } /* Program Allocation Table. It contains a list of all programs and the @@ -527,342 +532,342 @@ void ts_buffer_psi_packet(struct ccx_demuxer *ctx) int parse_PAT(struct ccx_demuxer *ctx) { - int gotpes = 0; - int is_multiprogram = 0; - unsigned char pointer_field = 0; - unsigned char *payload_start = NULL; - unsigned int payload_length = 0; - unsigned int section_number = 0; - unsigned int last_section_number = 0; - - pointer_field = *(ctx->PID_buffers[0]->buffer); - - payload_start = ctx->PID_buffers[0]->buffer + pointer_field + 1; - payload_length = ctx->PID_buffers[0]->buffer_length - (pointer_field + 1); - - section_number = payload_start[6]; - last_section_number = payload_start[7]; - - unsigned section_length = (((payload_start[1] & 0x0F) << 8) | payload_start[2]); - payload_length = ctx->PID_buffers[0]->buffer_length - 8; - unsigned programm_data = section_length - 5 - 4; // prev. bytes and CRC - - if (programm_data + 4 > payload_length) - { - return 0; // We don't have the full section yet. We will parse again when we have it. - } - - if (section_number > last_section_number) // Impossible: Defective PAT - { - dbg_print(CCX_DMT_PAT, "Skipped defective PAT packet, section_number=%u but last_section_number=%u\n", - section_number, last_section_number); - return gotpes; - } - if (last_section_number > 0) - { - dbg_print(CCX_DMT_PAT, "Long PAT packet (%u / %u), skipping.\n", - section_number, last_section_number); - return gotpes; - /* fatal(CCX_COMMON_EXIT_BUG_BUG, - "Sorry, long PATs not yet supported!\n"); */ - } - - if (ctx->last_pat_payload != NULL && payload_length == ctx->last_pat_length && - !memcmp(payload_start, ctx->last_pat_payload, payload_length)) - { - // dbg_print(CCX_DMT_PAT, "PAT hasn't changed, skipping.\n"); - return CCX_OK; - } - - if (ctx->last_pat_payload != NULL) - { - mprint("Notice: PAT changed, clearing all variables.\n"); - dinit_cap(ctx); - clear_PMT_array(ctx); - memset(ctx->PIDs_seen, 0, sizeof(int) * 65536); // Forget all we saw - if (!tlt_config.user_page) // If the user didn't select a page... - tlt_config.page = 0; // ..forget whatever we detected. - - gotpes = 1; - } - - if (ctx->last_pat_length < payload_length + 8) - { - ctx->last_pat_payload = (unsigned char *)realloc(ctx->last_pat_payload, payload_length + 8); // Extra 8 in case memcpy copies dwords, etc - if (ctx->last_pat_payload == NULL) - { - fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory to process PAT.\n"); - return -1; - } - } - memcpy(ctx->last_pat_payload, payload_start, payload_length); - ctx->last_pat_length = payload_length; - - unsigned table_id = payload_start[0]; - unsigned transport_stream_id = ((payload_start[3] << 8) | payload_start[4]); + int gotpes = 0; + int is_multiprogram = 0; + unsigned char pointer_field = 0; + unsigned char *payload_start = NULL; + unsigned int payload_length = 0; + unsigned int section_number = 0; + unsigned int last_section_number = 0; + + pointer_field = *(ctx->PID_buffers[0]->buffer); + + payload_start = ctx->PID_buffers[0]->buffer + pointer_field + 1; + payload_length = ctx->PID_buffers[0]->buffer_length - (pointer_field + 1); + + section_number = payload_start[6]; + last_section_number = payload_start[7]; + + unsigned section_length = (((payload_start[1] & 0x0F) << 8) | payload_start[2]); + payload_length = ctx->PID_buffers[0]->buffer_length - 8; + unsigned programm_data = section_length - 5 - 4; // prev. bytes and CRC + + if (programm_data + 4 > payload_length) + { + return 0; // We don't have the full section yet. We will parse again when we have it. + } + + if (section_number > last_section_number) // Impossible: Defective PAT + { + dbg_print(CCX_DMT_PAT, "Skipped defective PAT packet, section_number=%u but last_section_number=%u\n", + section_number, last_section_number); + return gotpes; + } + if (last_section_number > 0) + { + dbg_print(CCX_DMT_PAT, "Long PAT packet (%u / %u), skipping.\n", + section_number, last_section_number); + return gotpes; + /* fatal(CCX_COMMON_EXIT_BUG_BUG, + "Sorry, long PATs not yet supported!\n"); */ + } + + if (ctx->last_pat_payload != NULL && payload_length == ctx->last_pat_length && + !memcmp(payload_start, ctx->last_pat_payload, payload_length)) + { + // dbg_print(CCX_DMT_PAT, "PAT hasn't changed, skipping.\n"); + return CCX_OK; + } + + if (ctx->last_pat_payload != NULL) + { + mprint("Notice: PAT changed, clearing all variables.\n"); + dinit_cap(ctx); + clear_PMT_array(ctx); + memset(ctx->PIDs_seen, 0, sizeof(int) * 65536); // Forget all we saw + if (!tlt_config.user_page) // If the user didn't select a page... + tlt_config.page = 0; // ..forget whatever we detected. + + gotpes = 1; + } + + if (ctx->last_pat_length < payload_length + 8) + { + ctx->last_pat_payload = (unsigned char *)realloc(ctx->last_pat_payload, payload_length + 8); // Extra 8 in case memcpy copies dwords, etc + if (ctx->last_pat_payload == NULL) + { + fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory to process PAT.\n"); + return -1; + } + } + memcpy(ctx->last_pat_payload, payload_start, payload_length); + ctx->last_pat_length = payload_length; + + unsigned table_id = payload_start[0]; + unsigned transport_stream_id = ((payload_start[3] << 8) | payload_start[4]); unsigned version_number = (payload_start[5] & 0x3E) >> 1; - // Means current OR next (so you can build a long PAT before it - // actually is to be in use). - unsigned current_next_indicator = payload_start[5] & 0x01; - - if (!current_next_indicator) - // This table is not active, no need to evaluate - return 0; - - payload_start += 8; - - dbg_print(CCX_DMT_PAT, "Read PAT packet (id: %u) ts-id: 0x%04x\n", - table_id, transport_stream_id); - dbg_print(CCX_DMT_PAT, " section length: %u number: %u last: %u\n", - section_length, section_number, last_section_number); - dbg_print(CCX_DMT_PAT, " version_number: %u current_next_indicator: %u\n", - version_number, current_next_indicator); - - dbg_print(CCX_DMT_PAT, "\nProgram association section (PAT)\n"); - - ctx->freport.program_cnt = 0; - for (unsigned i = 0; i < programm_data; i += 4) - { - unsigned program_number = ((payload_start[i] << 8) | payload_start[i + 1]); - if (!program_number) - continue; - ctx->freport.program_cnt++; - } - - is_multiprogram = (ctx->freport.program_cnt > 1); - - for (unsigned int i = 0; i < programm_data; i += 4) - { - unsigned program_number = ((payload_start[i] << 8) | payload_start[i + 1]); - unsigned prog_map_pid = ((payload_start[i + 2] << 8) | payload_start[i + 3]) & 0x1FFF; - int j = 0; - - dbg_print(CCX_DMT_PAT, " Program number: %u -> PMTPID: %u\n", - program_number, prog_map_pid); - - if (!program_number) - continue; - - /** - * loop never break at j == ctx->nb_program when program_number - * is already there in pinfo array and if we have program number - * already in our array we don't need to update our array - * so we break if program_number already exist and make j != ctx->nb_program - * - * Loop without break means j would be equal to ctx->nb_program - */ - for (j = 0; j < ctx->nb_program; j++) - { - if (ctx->pinfo[j].program_number == program_number) - { - if (ctx->flag_ts_forced_pn == CCX_TRUE && ctx->pinfo[j].pid == CCX_UNKNOWN) - { - ctx->pinfo[j].pid = prog_map_pid; - ctx->pinfo[j].analysed_PMT_once = CCX_FALSE; - } - break; - } - } - if (j == ctx->nb_program && ctx->flag_ts_forced_pn == CCX_FALSE) - update_pinfo(ctx, prog_map_pid, program_number); - } // for - - if (is_multiprogram && !ctx->flag_ts_forced_pn) - { - mprint("\nThis TS file has more than one program. These are the program numbers found: \n"); - for (unsigned j = 0; j < programm_data; j += 4) - { - unsigned pn = ((payload_start[j] << 8) | payload_start[j + 1]); - if (pn) - mprint("%u\n", pn); - activity_program_number(pn); - } - } - - return gotpes; + // Means current OR next (so you can build a long PAT before it + // actually is to be in use). + unsigned current_next_indicator = payload_start[5] & 0x01; + + if (!current_next_indicator) + // This table is not active, no need to evaluate + return 0; + + payload_start += 8; + + dbg_print(CCX_DMT_PAT, "Read PAT packet (id: %u) ts-id: 0x%04x\n", + table_id, transport_stream_id); + dbg_print(CCX_DMT_PAT, " section length: %u number: %u last: %u\n", + section_length, section_number, last_section_number); + dbg_print(CCX_DMT_PAT, " version_number: %u current_next_indicator: %u\n", + version_number, current_next_indicator); + + dbg_print(CCX_DMT_PAT, "\nProgram association section (PAT)\n"); + + ctx->freport.program_cnt = 0; + for (unsigned i = 0; i < programm_data; i += 4) + { + unsigned program_number = ((payload_start[i] << 8) | payload_start[i + 1]); + if (!program_number) + continue; + ctx->freport.program_cnt++; + } + + is_multiprogram = (ctx->freport.program_cnt > 1); + + for (unsigned int i = 0; i < programm_data; i += 4) + { + unsigned program_number = ((payload_start[i] << 8) | payload_start[i + 1]); + unsigned prog_map_pid = ((payload_start[i + 2] << 8) | payload_start[i + 3]) & 0x1FFF; + int j = 0; + + dbg_print(CCX_DMT_PAT, " Program number: %u -> PMTPID: %u\n", + program_number, prog_map_pid); + + if (!program_number) + continue; + + /** + * loop never break at j == ctx->nb_program when program_number + * is already there in pinfo array and if we have program number + * already in our array we don't need to update our array + * so we break if program_number already exist and make j != ctx->nb_program + * + * Loop without break means j would be equal to ctx->nb_program + */ + for (j = 0; j < ctx->nb_program; j++) + { + if (ctx->pinfo[j].program_number == program_number) + { + if (ctx->flag_ts_forced_pn == CCX_TRUE && ctx->pinfo[j].pid == CCX_UNKNOWN) + { + ctx->pinfo[j].pid = prog_map_pid; + ctx->pinfo[j].analysed_PMT_once = CCX_FALSE; + } + break; + } + } + if (j == ctx->nb_program && ctx->flag_ts_forced_pn == CCX_FALSE) + update_pinfo(ctx, prog_map_pid, program_number); + } // for + + if (is_multiprogram && !ctx->flag_ts_forced_pn) + { + mprint("\nThis TS file has more than one program. These are the program numbers found: \n"); + for (unsigned j = 0; j < programm_data; j += 4) + { + unsigned pn = ((payload_start[j] << 8) | payload_start[j + 1]); + if (pn) + mprint("%u\n", pn); + activity_program_number(pn); + } + } + + return gotpes; } void process_ccx_mpeg_descriptor(unsigned char *data, unsigned length) { - const char *txt_teletext_type[] = {"Reserved", "Initial page", "Subtitle page", "Additional information page", "Programme schedule page", - "Subtitle page for hearing impaired people"}; - int i, l; - if (!data || !length) - return; - switch (data[0]) - { - case CCX_MPEG_DSC_ISO639_LANGUAGE: - if (length < 2) - return; - l = data[1]; - if (l + 2 < length) - return; - for (i = 0; i < l; i += 4) - { - char c1 = data[i + 2], c2 = data[i + 3], c3 = data[i + 4]; - dbg_print(CCX_DMT_PMT, " ISO639: %c%c%c\n", c1 >= 0x20 ? c1 : ' ', - c2 >= 0x20 ? c2 : ' ', - c3 >= 0x20 ? c3 : ' '); - } - break; - case CCX_MPEG_DSC_VBI_DATA_DESCRIPTOR: - dbg_print(CCX_DMT_PMT, "DVB VBI data descriptor (not implemented)\n"); - break; - case CCX_MPEG_DSC_VBI_TELETEXT_DESCRIPTOR: - dbg_print(CCX_DMT_PMT, "DVB VBI teletext descriptor\n"); - break; - case CCX_MPEG_DSC_TELETEXT_DESCRIPTOR: - dbg_print(CCX_DMT_PMT, " DVB teletext descriptor\n"); - if (length < 2) - return; - l = data[1]; - if (l + 2 < length) - return; - for (i = 0; i < l; i += 5) - { - char c1 = data[i + 2], c2 = data[i + 3], c3 = data[i + 4]; - unsigned teletext_type = (data[i + 5] & 0xF8) >> 3; // 5 MSB - // unsigned magazine_number=data[i+5]&0x7; // 3 LSB - unsigned teletext_page_number = data[i + 6]; - dbg_print(CCX_DMT_PMT, " ISO639: %c%c%c\n", c1 >= 0x20 ? c1 : ' ', - c2 >= 0x20 ? c2 : ' ', - c3 >= 0x20 ? c3 : ' '); - dbg_print(CCX_DMT_PMT, " Teletext type: %s (%02X)\n", (teletext_type < 6 ? txt_teletext_type[teletext_type] : "Reserved for future use"), - teletext_type); - dbg_print(CCX_DMT_PMT, " Initial page: %02X\n", teletext_page_number); - } - break; - case CCX_MPEG_DSC_DVB_SUBTITLE: - dbg_print(CCX_DMT_PMT, " DVB Subtitle descriptor\n"); - break; - - default: - if (data[0] == CCX_MPEG_DSC_REGISTRATION) // Registration descriptor, could be useful eventually - break; - if (data[0] == CCX_MPEG_DSC_DATA_STREAM_ALIGNMENT) // Data stream alignment descriptor - break; - if (data[0] >= 0x13 && data[0] <= 0x3F) // Reserved - break; - if (data[0] >= 0x40 && data[0] <= 0xFF) // User private - break; - // mprint ("Still unsupported MPEG descriptor type=%d (%02X)\n",data[0],data[0]); - break; - } + const char *txt_teletext_type[] = {"Reserved", "Initial page", "Subtitle page", "Additional information page", "Programme schedule page", + "Subtitle page for hearing impaired people"}; + int i, l; + if (!data || !length) + return; + switch (data[0]) + { + case CCX_MPEG_DSC_ISO639_LANGUAGE: + if (length < 2) + return; + l = data[1]; + if (l + 2 < length) + return; + for (i = 0; i < l; i += 4) + { + char c1 = data[i + 2], c2 = data[i + 3], c3 = data[i + 4]; + dbg_print(CCX_DMT_PMT, " ISO639: %c%c%c\n", c1 >= 0x20 ? c1 : ' ', + c2 >= 0x20 ? c2 : ' ', + c3 >= 0x20 ? c3 : ' '); + } + break; + case CCX_MPEG_DSC_VBI_DATA_DESCRIPTOR: + dbg_print(CCX_DMT_PMT, "DVB VBI data descriptor (not implemented)\n"); + break; + case CCX_MPEG_DSC_VBI_TELETEXT_DESCRIPTOR: + dbg_print(CCX_DMT_PMT, "DVB VBI teletext descriptor\n"); + break; + case CCX_MPEG_DSC_TELETEXT_DESCRIPTOR: + dbg_print(CCX_DMT_PMT, " DVB teletext descriptor\n"); + if (length < 2) + return; + l = data[1]; + if (l + 2 < length) + return; + for (i = 0; i < l; i += 5) + { + char c1 = data[i + 2], c2 = data[i + 3], c3 = data[i + 4]; + unsigned teletext_type = (data[i + 5] & 0xF8) >> 3; // 5 MSB + // unsigned magazine_number=data[i+5]&0x7; // 3 LSB + unsigned teletext_page_number = data[i + 6]; + dbg_print(CCX_DMT_PMT, " ISO639: %c%c%c\n", c1 >= 0x20 ? c1 : ' ', + c2 >= 0x20 ? c2 : ' ', + c3 >= 0x20 ? c3 : ' '); + dbg_print(CCX_DMT_PMT, " Teletext type: %s (%02X)\n", (teletext_type < 6 ? txt_teletext_type[teletext_type] : "Reserved for future use"), + teletext_type); + dbg_print(CCX_DMT_PMT, " Initial page: %02X\n", teletext_page_number); + } + break; + case CCX_MPEG_DSC_DVB_SUBTITLE: + dbg_print(CCX_DMT_PMT, " DVB Subtitle descriptor\n"); + break; + + default: + if (data[0] == CCX_MPEG_DSC_REGISTRATION) // Registration descriptor, could be useful eventually + break; + if (data[0] == CCX_MPEG_DSC_DATA_STREAM_ALIGNMENT) // Data stream alignment descriptor + break; + if (data[0] >= 0x13 && data[0] <= 0x3F) // Reserved + break; + if (data[0] >= 0x40 && data[0] <= 0xFF) // User private + break; + // mprint ("Still unsupported MPEG descriptor type=%d (%02X)\n",data[0],data[0]); + break; + } } void decode_service_descriptors(struct ccx_demuxer *ctx, uint8_t *buf, uint32_t length, uint32_t service_id) { - unsigned descriptor_tag; - unsigned descriptor_length; - uint32_t x; - uint32_t offset = 0; - - while (offset + 5 < length) - { - descriptor_tag = buf[offset]; - descriptor_length = buf[offset + 1]; - offset += 2; - if (descriptor_tag == 0x48) - { // service descriptor - // uint8_t service_type = buf[offset]; - uint8_t service_provider_name_length = buf[offset + 1]; - offset += 2; - if (offset + service_provider_name_length > length) - { - dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Invalid SDT service_provider_name_length detected.\n"); - return; - } - offset += service_provider_name_length; // Service provider name. Not sure what this is useful for. - uint8_t service_name_length = buf[offset]; - offset++; - if (offset + service_name_length > length) - { - dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Invalid SDT service_name_length detected.\n"); - return; - } - for (x = 0; x < ctx->nb_program; x++) - { - // Not sure if programs can have multiple names (in different encodings?) Need more samples. - // For now just assume the last one in the loop is as good as any if there are multiple. - if (ctx->pinfo[x].program_number == service_id && service_name_length < 199) - { - char *s = EPG_DVB_decode_string(&buf[offset], service_name_length); // String encoding is the same as for EPG - if (strlen(s) < MAX_PROGRAM_NAME_LEN - 1) - { - memcpy(ctx->pinfo[x].name, s, service_name_length); - ctx->pinfo[x].name[service_name_length] = '\0'; - } - free(s); - } - } - offset += service_name_length; - } - else - { - // Some other tag - offset += descriptor_length; - } - } + unsigned descriptor_tag; + unsigned descriptor_length; + uint32_t x; + uint32_t offset = 0; + + while (offset + 5 < length) + { + descriptor_tag = buf[offset]; + descriptor_length = buf[offset + 1]; + offset += 2; + if (descriptor_tag == 0x48) + { // service descriptor + // uint8_t service_type = buf[offset]; + uint8_t service_provider_name_length = buf[offset + 1]; + offset += 2; + if (offset + service_provider_name_length > length) + { + dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Invalid SDT service_provider_name_length detected.\n"); + return; + } + offset += service_provider_name_length; // Service provider name. Not sure what this is useful for. + uint8_t service_name_length = buf[offset]; + offset++; + if (offset + service_name_length > length) + { + dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Invalid SDT service_name_length detected.\n"); + return; + } + for (x = 0; x < ctx->nb_program; x++) + { + // Not sure if programs can have multiple names (in different encodings?) Need more samples. + // For now just assume the last one in the loop is as good as any if there are multiple. + if (ctx->pinfo[x].program_number == service_id && service_name_length < 199) + { + char *s = EPG_DVB_decode_string(&buf[offset], service_name_length); // String encoding is the same as for EPG + if (strlen(s) < MAX_PROGRAM_NAME_LEN - 1) + { + memcpy(ctx->pinfo[x].name, s, service_name_length); + ctx->pinfo[x].name[service_name_length] = '\0'; + } + free(s); + } + } + offset += service_name_length; + } + else + { + // Some other tag + offset += descriptor_length; + } + } } void decode_SDT_services_loop(struct ccx_demuxer *ctx, uint8_t *buf, uint32_t length) { - // unsigned descriptor_tag = buf[0]; - // unsigned descriptor_length = buf[1]; - // uint32_t x; - uint32_t offset = 0; - - while (offset + 5 < length) - { - uint16_t serive_id = ((buf[offset + 0]) << 8) | buf[offset + 1]; - uint32_t descriptors_loop_length = (((buf[offset + 3] & 0x0F) << 8) | buf[offset + 4]); - offset += 5; - if (offset + descriptors_loop_length > length) - { - dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Invalid SDT descriptors_loop_length detected.\n"); - return; - } - decode_service_descriptors(ctx, &buf[offset], descriptors_loop_length, serive_id); - offset += descriptors_loop_length; - } + // unsigned descriptor_tag = buf[0]; + // unsigned descriptor_length = buf; + // uint32_t x; + uint32_t offset = 0; + + while (offset + 5 < length) + { + uint16_t serive_id = ((buf[offset + 0]) << 8) | buf[offset + 1]; + uint32_t descriptors_loop_length = (((buf[offset + 3] & 0x0F) << 8) | buf[offset + 4]); + offset += 5; + if (offset + descriptors_loop_length > length) + { + dbg_print(CCX_DMT_GENERIC_NOTICES, "\rWarning: Invalid SDT descriptors_loop_length detected.\n"); + return; + } + decode_service_descriptors(ctx, &buf[offset], descriptors_loop_length, serive_id); + offset += descriptors_loop_length; + } } void parse_SDT(struct ccx_demuxer *ctx) { - unsigned char pointer_field = 0; - unsigned char *payload_start = NULL; - unsigned int payload_length = 0; - // unsigned int section_number = 0; - // unsigned int last_section_number = 0; - - pointer_field = *(ctx->PID_buffers[0x11]->buffer); - payload_start = ctx->PID_buffers[0x11]->buffer + pointer_field + 1; - payload_length = ctx->PID_buffers[0x11]->buffer_length - (pointer_field + 1); - - // section_number = payload_start[6]; - // last_section_number = payload_start[7]; - - unsigned table_id = payload_start[0]; - unsigned section_length = (((payload_start[1] & 0x0F) << 8) | payload_start[2]); - // unsigned transport_stream_id = ((payload_start[3] << 8) - // | payload_start[4]); - // unsigned version_number = (payload_start[5] & 0x3E) >> 1; - - if (section_length > payload_length - 4) - { - return; - } - unsigned current_next_indicator = payload_start[5] & 0x01; - - // uint16_t original_network_id = ((payload_start[8]) << 8) | payload_start[9]; - - if (!current_next_indicator) - // This table is not active, no need to evaluate - return; - if (table_id != 0x42) - // This table isn't for the active TS - return; - - decode_SDT_services_loop(ctx, &payload_start[11], section_length - 4 - 8); + unsigned char pointer_field = 0; + unsigned char *payload_start = NULL; + unsigned int payload_length = 0; + // unsigned int section_number = 0; + // unsigned int last_section_number = 0; + + pointer_field = *(ctx->PID_buffers[0x11]->buffer); + payload_start = ctx->PID_buffers[0x11]->buffer + pointer_field + 1; + payload_length = ctx->PID_buffers[0x11]->buffer_length - (pointer_field + 1); + + // section_number = payload_start[6]; + // last_section_number = payload_start; + + unsigned table_id = payload_start[0]; + unsigned section_length = (((payload_start[1] & 0x0F) << 8) | payload_start[2]); + // unsigned transport_stream_id = ((payload_start << 8) + // | payload_start); + // unsigned version_number = (payload_start & 0x3E) >> 1; + + if (section_length > payload_length - 4) + { + return; + } + unsigned current_next_indicator = payload_start[5] & 0x01; + + // uint16_t original_network_id = ((payload_start[8]) << 8) | payload_start; + + if (!current_next_indicator) + // This table is not active, no need to evaluate + return; + if (table_id != 0x42) + // This table isn't for the active TS + return; + + decode_SDT_services_loop(ctx, &payload_start[11], section_length - 4 - 8); }