├── .github └── workflows │ ├── macos.yml │ ├── ubuntu1804.yml │ ├── unittests.yml │ └── windows.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bars_dmx └── readme.txt ├── doc └── video_file_format_spec_v10.pdf ├── generate_bars.sh ├── kissnet ├── LICENSE └── kissnet.hpp ├── main_dmx.cpp ├── main_mx.cpp ├── mpegts ├── common.cpp ├── common.h ├── crc.cpp ├── crc.h ├── mpegts_demuxer.cpp ├── mpegts_demuxer.h ├── mpegts_muxer.cpp ├── mpegts_muxer.h ├── simple_buffer.cpp ├── simple_buffer.h ├── ts_packet.cpp └── ts_packet.h ├── unit_tests.cpp └── unit_tests ├── unit_test_1.cpp ├── unit_test_1.h ├── unit_test_2.cpp ├── unit_test_2.h ├── unit_test_3.cpp ├── unit_test_3.h ├── unit_test_4.cpp ├── unit_test_4.h ├── unit_test_5.cpp ├── unit_test_5.h ├── unit_test_6.cpp ├── unit_test_6.h ├── unit_test_7.cpp ├── unit_test_7.h ├── unit_test_8.cpp ├── unit_test_8.h ├── unit_test_8_data.h ├── unit_test_9.cpp └── unit_test_9.h /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: CMake set-up 17 | run: cmake -DCMAKE_BUILD_TYPE=Release . 18 | - name: make 19 | run: make 20 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu1804.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 18.04 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: CMake set-up 17 | run: cmake -DCMAKE_BUILD_TYPE=Release . 18 | - name: make 19 | run: make 20 | -------------------------------------------------------------------------------- /.github/workflows/unittests.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: get the ts test file 17 | run: | 18 | wget https://github.com/andersc/assets/raw/master/bars.ts 19 | cp bars.ts bars-github.ts 20 | - name: build unit tests 21 | run: | 22 | mkdir build 23 | cd build 24 | cmake -DCMAKE_BUILD_TYPE=Release .. 25 | cmake --build . --config Release --target mpeg_ts_unit_tests 26 | - name: run unit tests 27 | run: | 28 | build/mpeg_ts_unit_tests 29 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: CMake set-up 17 | run: cmake -S . `-D CMAKE_BUILD_TYPE=Release` 18 | - name: make 19 | run: cmake --build . --config Release 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | # CMake 3 | cmake-build-*/ 4 | amf0/ 5 | bars.ts 6 | output.ts 7 | bars_dmx/* 8 | !bars_dmx/readme.txt 9 | 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(mpegts) 3 | set(CMAKE_CXX_STANDARD 17) 4 | 5 | #Macro for printing all CMake variables 6 | macro(print_all_variables) 7 | message(STATUS "print_all_variables------------------------------------------{") 8 | get_cmake_property(_variableNames VARIABLES) 9 | foreach (_variableName ${_variableNames}) 10 | message(STATUS "${_variableName}=${${_variableName}}") 11 | endforeach() 12 | message(STATUS "print_all_variables------------------------------------------}") 13 | endmacro() 14 | 15 | find_package (Threads REQUIRED) 16 | 17 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") 18 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 19 | 20 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/mpegts/) 21 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/unit_tests/) 22 | 23 | file(GLOB libfiles ${CMAKE_CURRENT_SOURCE_DIR}/mpegts/*.cpp) 24 | add_library(mpegts STATIC ${libfiles}) 25 | 26 | add_executable(mpeg_ts_dmx_tests ${CMAKE_CURRENT_SOURCE_DIR}/main_dmx.cpp) 27 | target_link_libraries(mpeg_ts_dmx_tests mpegts Threads::Threads) 28 | 29 | add_executable(mpeg_ts_mx_tests ${CMAKE_CURRENT_SOURCE_DIR}/main_mx.cpp) 30 | target_link_libraries(mpeg_ts_mx_tests mpegts Threads::Threads) 31 | 32 | file(GLOB unit_tests_sources ${CMAKE_CURRENT_SOURCE_DIR}/unit_tests/*.cpp) 33 | add_executable(mpeg_ts_unit_tests ${unit_tests_sources} ${CMAKE_CURRENT_SOURCE_DIR}/unit_tests.cpp) 34 | target_link_libraries(mpeg_ts_unit_tests mpegts Threads::Threads) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 akanchi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MPEG-TS 2 | 3 | 4 | A simple C++ implementation of a MPEG-TS Muxer and Demuxer. 5 | 6 | The multiplexer and demultiplexer is **FAR** from supporting ITU-T H.222. Only the basics are supported meaning a video stream and a audio stream without using any 'trick mode' flags or extensions. Enough for multiplexing some elementary streams to watch using FFPLAY for example. This code should not be used to do anything advanced as it's not suited for that. 7 | 8 | This is a [cloned](https://github.com/akanchi/mpegts) projet. This clone adds support for using the multiplexer / demultiplexer as a library into your projects among other features as can be seen in the examples and unit tests. 9 | 10 | The upstream master project also contains a lot of serious bugs fixed in this clone. There might still be bugs in the code please let me know if you find any. 11 | 12 | 13 | **Build status ->** 14 | 15 | ![Ubuntu 18.04](https://github.com/Unit-X/mpegts/workflows/Ubuntu%2018.04/badge.svg) 16 | 17 | ![Windows](https://github.com/Unit-X/mpegts/workflows/Windows/badge.svg) 18 | 19 | ![MacOS](https://github.com/Unit-X/mpegts/workflows/MacOS/badge.svg) 20 | 21 | **Unit tests status ->** 22 | 23 | ![Unit tests](https://github.com/Unit-X/mpegts/workflows/Unit%20tests/badge.svg) 24 | 25 | 26 | # Build 27 | 28 | Requires cmake version >= **3.10** and **C++14** 29 | 30 | **Release:** 31 | 32 | ```sh 33 | mkdir build 34 | cd build 35 | cmake -DCMAKE_BUILD_TYPE=Release .. 36 | cmake --build . --config Release 37 | ``` 38 | 39 | ***Debug:*** 40 | 41 | ```sh 42 | mkdir build 43 | cd build 44 | cmake -DCMAKE_BUILD_TYPE=Debug .. 45 | cmake --build . --config Debug 46 | ``` 47 | 48 | Outputs the mpegts static library and the example executables 49 | 50 | # Use in your CMake project 51 | 52 | It's simple to add the MPEG-TS multiplexer/demultiplexer into your existing CMake project. Just follow the simple steps below. 53 | 54 | **Add the mpegts library as a external project ->** 55 | 56 | ``` 57 | include(ExternalProject) 58 | ExternalProject_Add(project_mpegts 59 | GIT_REPOSITORY https://github.com/Unit-X/mpegts.git 60 | GIT_SUBMODULES "" 61 | UPDATE_COMMAND git pull 62 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mpegts 63 | BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mpegts 64 | GIT_PROGRESS 1 65 | CONFIGURE_COMMAND cmake -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} ${CMAKE_CURRENT_SOURCE_DIR}/mpegts 66 | BUILD_COMMAND cmake --build ${CMAKE_CURRENT_SOURCE_DIR}/mpegts --config ${CMAKE_BUILD_TYPE} --target mpegts 67 | STEP_TARGETS build 68 | EXCLUDE_FROM_ALL TRUE 69 | INSTALL_COMMAND "" 70 | ) 71 | add_library(mpegts STATIC IMPORTED) 72 | set_property(TARGET mpegts PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/mpegts/libmpegts.a) 73 | ``` 74 | 75 | **Add header search paths ->** 76 | 77 | ``` 78 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/mpegts/mpegts/) 79 | ``` 80 | 81 | **Link against the library ->** 82 | 83 | ``` 84 | add_executable((your_executable) (source_files)) 85 | add_dependencies((your_executable) project_mpegts) 86 | target_link_libraries((your_executable) mpegts) 87 | ``` 88 | 89 | 90 | That will trigger building and linking the mpegts library 91 | (For windows the path and name of the library needs to be changed) 92 | 93 | 94 | # Usage 95 | 96 | Demuxer -> 97 | 98 | ```cpp 99 | 100 | #include "mpegts_demuxer.h" 101 | 102 | //Callback where the demuxed data ends up 103 | void dmxOutput(EsFrame *pEs) { 104 | //The EsFrame contains all information about the Elementary data 105 | } 106 | 107 | 108 | //Create a input buffer 109 | SimpleBuffer lIn; 110 | //Create a demuxer 111 | MpegTsDemuxer lDemuxer; 112 | 113 | //Provide a callback for the ES data 114 | lDemuxer.esOutCallback = std::bind(&dmxOutput, std::placeholders::_1); 115 | 116 | //Append data to the input buffer 117 | lIn.append(packet, 188); 118 | 119 | //Demux the data 120 | demuxer.decode(&lIn); 121 | 122 | //Example usage of the demuxer can be seen here -> 123 | //https://github.com/Unit-X/ts2efp/blob/master/main.cpp 124 | 125 | 126 | ``` 127 | 128 | Muxer -> 129 | 130 | ```cpp 131 | 132 | #include "mpegts_muxer.h" 133 | 134 | //AAC audio 135 | #define TYPE_AUDIO 0x0f 136 | //H.264 video 137 | #define TYPE_VIDEO 0x1b 138 | 139 | //Audio PID 140 | #define AUDIO_PID 257 141 | //Video PID 142 | #define VIDEO_PID 256 143 | //PMT PID 144 | #define PMT_PID 100 145 | 146 | //A callback where all the TS-packets are sent from the multiplexer 147 | void muxOutput(SimpleBuffer &rTsOutBuffer){ 148 | 149 | } 150 | 151 | //Create the map defining what datatype to map to what PID 152 | std::map streamPidMap; 153 | streamPidMap[TYPE_AUDIO] = AUDIO_PID; 154 | streamPidMap[TYPE_VIDEO] = VIDEO_PID; 155 | 156 | //Create the multiplexer 157 | //param1 = PID map 158 | //param2 = PMT PID 159 | //param3 = PCR PID 160 | MpegTsMuxer lMuxer(streamPidMap, PMT_PID, VIDEO_PID); 161 | 162 | //Provide the callback where TS packets are fed to 163 | lMuxer.tsOutCallback = std::bind(&muxOutput, std::placeholders::_1); 164 | 165 | //Build a frame of data (ES) 166 | EsFrame esFrame; 167 | esFrame.mData = std::make_shared(); 168 | //Append your ES-Data 169 | esFrame.mData->append((const char *) rPacket->pFrameData, rPacket->mFrameSize); 170 | esFrame.mPts = rPacket->mPts; 171 | esFrame.mDts = rPacket->mPts; 172 | esFrame.mPcr = 0; 173 | esFrame.mStreamType = TYPE_AUDIO; 174 | esFrame.mStreamId = 192; 175 | esFrame.mPid = AUDIO_PID; 176 | esFrame.mExpectedPesPacketLength = 0; 177 | esFrame.mCompleted = true; 178 | 179 | //Multiplex your data 180 | lMuxer.encode(&esFrame); 181 | 182 | ``` 183 | 184 | # Runing the included demux/mux example 185 | 186 | (Currently only MacOS and Linux) 187 | 188 | The tests has to be run in the order as described below 189 | 190 | * Generate 'bars.ts' file by running the script *./generate_bars.sh* . This generates the file to be used by the tests. The build instructions has to be followed also since the tests expect the file to ba available from the location ../bars.ts meaning the executables needs to be a directory level lower IE.. {source_root}/build/{test_file_location} 191 | 192 | * Run the demuxer *./mpeg_ts_dmx_tests* . The demuxer is demuxing all ES frames and generates one file per audio/video frame in the directory **bars_dmx** 193 | 194 | * Run the muxer *./mpeg_ts_mx_tests*. The multiplexer is assembling the frames from the demuxed files and generates a TS over UDP output on local host port 8100. In order to view the output you can for example use *ffplay udp://127.0.0.1:8100* 195 | 196 | 197 | 198 | 199 | 200 | Example usage of the mux library can alse be seen here -> [Example multiplexer/demultiplexer](https://github.com/Unit-X/ts2efp) 201 | -------------------------------------------------------------------------------- /bars_dmx/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is populated with ES data from the demultiplexer example. The Demultiplexer must be run before the multiplexer example. -------------------------------------------------------------------------------- /doc/video_file_format_spec_v10.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnZones/mpegts/d381f2b72b3d99bff6b7e24c9e13be311e62ed01/doc/video_file_format_spec_v10.pdf -------------------------------------------------------------------------------- /generate_bars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | #Script generating AAC (ADTS) + H.264 (NAL) Elementary data 4 | rate=50 5 | goplength=48 6 | 7 | 8 | #Generate MPEG-TS multiplexed AAC 9 | ffmpeg \ 10 | -f lavfi -i "smptebars=size=1280x720:rate="$rate \ 11 | -f lavfi -i sine=frequency=3:beep_factor=1000:sample_rate=48000 \ 12 | -vf drawtext="fontfile=Verdana.ttf:\ timecode='00\:00\:00\:00':rate="$rate":fontsize=64:fontcolor='white':\ boxcolor=0x00000088:box=1:boxborderw=5:x=20:y=400" \ 13 | -c:a aac -b:a 96k -ac 2\ 14 | -c:v libx264 -preset medium -b:v 1000k -minrate 1000k -maxrate 1000k -bufsize 1500k\ 15 | -x264opts "no-scenecut:keyint="$goplength":min-keyint="$goplength":nal-hrd=cbr:no-open-gop=1:force-cfr=1:aud=1"\ 16 | -y -f mpegts -t 25 \ 17 | bars.ts 18 | -------------------------------------------------------------------------------- /kissnet/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2020 Arthur Brainville (Ybalrid) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /kissnet/kissnet.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018-2019 Arthur Brainville (Ybalrid) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | * INTRODUCTION 25 | * ============ 26 | * 27 | * Kissnet is a simple C++17 layer around the raw OS provided socket API to be 28 | * used on IP networks with the TCP and UDP protocols. 29 | * 30 | * Kissnet is not a networking framework, and it will not process your data or 31 | * assist you in any way. Kissnet's only goal is to provide a simple API to send 32 | * and receive bytes, 33 | * without having to play around with a bunch of structure, file descriptors, 34 | * handles and pointers given to a C-style API. The other goal of kissnet is to 35 | * provide an API that will works in a cross platform setting. 36 | * 37 | * Kissnet will automatically manage the eventual startup/shutdown of the 38 | * library needed to perform socket operations on a particular platform. (e.g. 39 | * the Windows Socket API on MS-Windows. 40 | * 41 | * Kissnet leverages (and expect you to do so), multiple features from C++17, 42 | * including: std::byte, if constexpr, structured bindings, if-initializer and 43 | * template parameter type deduction. 44 | * 45 | * The library is structured across 4 exposed data types: 46 | * 47 | * - buffer : a static array of std::byte implemented via std::array. 48 | * This is what you should use to hold raw data you are getting from a socket, 49 | * before extracting what you need from the bytes 50 | * - port_t : a 16 bit unsigned number. Represent a network port number 51 | * - endpoint : a structure that represent a location where you need to connect 52 | * to. Contains a hostname (as std::string) and a port number (as port_t) 53 | * - socket : a templated class that represent a socket. Protocol 54 | * is either TCP or UDP, and ip is either v4 or v6 55 | * 56 | * Kissnet does error handling in 2 ways: 57 | * 58 | * 1: 59 | * When an operation can generate an error that the user should handle by hand 60 | * anyway, a tuple containing the expected type returned, and an object that 61 | * represent the status of what happens is returned. 62 | * 63 | * For example, socket send/receive operation can discover that the connection 64 | * was closed, or was shut down properly. It could also be the fact that a 65 | * socket was configured "non blocking" and would have blocked in this 66 | * situation. On both occasion, these methods will return the fact that 0 bytes 67 | * came across as the transaction size, and the status will indicate either an 68 | * error (socket no longer valid), or an actual status message (connection 69 | * closed, socket would have blocked) 70 | * 71 | * These status objects will behave like a const bool that equals "false" when 72 | * an error occurred, and "true" when it's just a status notification 73 | * 74 | * 2: 75 | * Fatal errors are by default handled by throwing a runtime_error exception. 76 | * But, for many reasons, you may want to 77 | * not use exceptions entirely. 78 | * 79 | * kissnet give you some facilities to get fatal errors information back, and 80 | * to choose how to handle it. Kissnet give you a few levers you can use: 81 | * 82 | * - You can deactivate the exception support by #defining KISSNET_NO_EXCEP 83 | * before #including kissnet.hpp. Instead, kissnet will use a function based 84 | * error handler 85 | * - By default, the error handler prints to stderr the error message, and 86 | * abort the program 87 | * - kissnet::error::callback is a function pointer that gets a string, and a 88 | * context pointer. The string is the error message, and the context pointer 89 | * what ever you gave kissnet for the occasion. This is a global pointer that 90 | * you can set as you want. This will override the "print to stderr" behavior 91 | * at fatal error time. 92 | * - kissnet::error::ctx is a void*, this will be passed to your error handler 93 | * as a "context" pointer. If you need your handler to write to a log, 94 | * or to turn on the HTCPCP enabled teapot on John's desk, you can. 95 | * - kissnet::abortOnFatalError is a boolean that will control the call to 96 | * abort(). This is independent to the fact that you did set or not an error 97 | * callback. please note that any object involved with the operation that 98 | * triggered the fatal error is probably in an invalid state, and probably 99 | * deserve to be thrown away. 100 | */ 101 | 102 | #ifndef KISS_NET 103 | #define KISS_NET 104 | 105 | ///Define this to not use exceptions 106 | #ifndef KISSNET_NO_EXCEP 107 | #define kissnet_fatal_error(STR) throw std::runtime_error(STR) 108 | #else 109 | #define kissnet_fatal_error(STR) kissnet::error::handle(STR); 110 | #endif 111 | 112 | #include 113 | #include 114 | #include 115 | #include 116 | #include 117 | #include 118 | #include 119 | #include 120 | #include 121 | 122 | #ifdef _WIN32 123 | 124 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 125 | #define WIN32_LEAN_AND_MEAN 126 | 127 | #ifndef NOMINMAX 128 | #define NOMINMAX 129 | #endif 130 | 131 | #include 132 | #include 133 | #include 134 | 135 | using ioctl_setting = u_long; 136 | using buffsize_t = int; 137 | 138 | #define AI_ADDRCONFIG 0x00000400 139 | 140 | // taken from: https://github.com/rxi/dyad/blob/915ae4939529b9aaaf6ebfd2f65c6cff45fc0eac/src/dyad.c#L58 141 | inline const char* inet_ntop(int af, const void* src, char* dst, socklen_t size) 142 | { 143 | union 144 | { 145 | struct sockaddr sa; 146 | struct sockaddr_in sai; 147 | struct sockaddr_in6 sai6; 148 | } addr; 149 | int res; 150 | memset(&addr, 0, sizeof(addr)); 151 | addr.sa.sa_family = af; 152 | if(af == AF_INET6) 153 | { 154 | memcpy(&addr.sai6.sin6_addr, src, sizeof(addr.sai6.sin6_addr)); 155 | } 156 | else 157 | { 158 | memcpy(&addr.sai.sin_addr, src, sizeof(addr.sai.sin_addr)); 159 | } 160 | res = WSAAddressToStringA(&addr.sa, sizeof(addr), 0, dst, reinterpret_cast(&size)); 161 | if(res != 0) return NULL; 162 | return dst; 163 | } 164 | 165 | //Handle WinSock2/Windows Socket API initialization and cleanup 166 | #pragma comment(lib, "Ws2_32.lib") 167 | namespace kissnet 168 | { 169 | 170 | namespace win32_specific 171 | { 172 | ///Forward declare the object that will permit to manage the WSAStartup/Cleanup automatically 173 | struct WSA; 174 | 175 | ///Enclose the global pointer in this namespace. Only use this inside a shared_ptr 176 | namespace internal_state 177 | { 178 | static WSA* global_WSA = nullptr; 179 | } 180 | 181 | ///WSA object. Only to be constructed with std::make_shared() 182 | struct WSA : std::enable_shared_from_this 183 | { 184 | //For safety, only initialize Windows Socket API once, and delete it once 185 | ///Prevent copy construct 186 | WSA(const WSA&) = delete; 187 | ///Prevent copy assignment 188 | WSA& operator=(const WSA&) = delete; 189 | ///Prevent moving 190 | WSA(WSA&&) = delete; 191 | ///Prevent move assignment 192 | WSA& operator=(WSA&&) = delete; 193 | 194 | ///data storage 195 | WSADATA wsa_data; 196 | 197 | ///Startup 198 | WSA() : 199 | wsa_data {} 200 | { 201 | if(const auto status = WSAStartup(MAKEWORD(2, 2), &wsa_data); status != 0) 202 | { 203 | std::string error_message; 204 | switch(status) // https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup#return-value 205 | { 206 | default: 207 | error_message = "Unknown error happened."; 208 | break; 209 | case WSASYSNOTREADY: 210 | error_message = "The underlying network subsystem is not ready for network communication."; 211 | break; 212 | case WSAVERNOTSUPPORTED: //unlikely, we specify 2.2! 213 | error_message = " The version of Windows Sockets support requested " 214 | "(2.2)" //we know here the version was 2.2, add that to the error message copied from MSDN 215 | " is not provided by this particular Windows Sockets implementation. "; 216 | break; 217 | case WSAEINPROGRESS: 218 | error_message = "A blocking Windows Sockets 1.1 operation is in progress."; 219 | break; 220 | case WSAEPROCLIM: 221 | error_message = "A limit on the number of tasks supported by the Windows Sockets implementation has been reached."; 222 | break; 223 | case WSAEFAULT: //unlikely, if this ctor is running, wsa_data is part of this object's "stack" data 224 | error_message = "The lpWSAData parameter is not a valid pointer."; 225 | break; 226 | } 227 | kissnet_fatal_error(error_message); 228 | } 229 | #ifdef KISSNET_WSA_DEBUG 230 | std::cerr << "Initialized Windows Socket API\n"; 231 | #endif 232 | } 233 | 234 | ///Cleanup 235 | ~WSA() 236 | { 237 | WSACleanup(); 238 | internal_state::global_WSA = nullptr; 239 | #ifdef KISSNET_WSA_DEBUG 240 | std::cerr << "Cleanup Windows Socket API\n"; 241 | #endif 242 | } 243 | 244 | ///get the shared pointer 245 | std::shared_ptr getPtr() 246 | { 247 | return shared_from_this(); 248 | } 249 | }; 250 | 251 | ///Get-or-create the global pointer 252 | inline std::shared_ptr getWSA() 253 | { 254 | //If it has been created already: 255 | if(internal_state::global_WSA) 256 | return internal_state::global_WSA->getPtr(); //fetch the smart pointer from the naked pointer 257 | 258 | //Create in wsa 259 | auto wsa = std::make_shared(); 260 | 261 | //Save the raw address in the global state 262 | internal_state::global_WSA = wsa.get(); 263 | 264 | //Return the smart pointer 265 | return wsa; 266 | } 267 | } 268 | 269 | #define KISSNET_OS_SPECIFIC_PAYLOAD_NAME wsa_ptr 270 | #define KISSNET_OS_SPECIFIC std::shared_ptr KISSNET_OS_SPECIFIC_PAYLOAD_NAME 271 | #define KISSNET_OS_INIT KISSNET_OS_SPECIFIC_PAYLOAD_NAME = kissnet::win32_specific::getWSA() 272 | 273 | ///Return the last error code 274 | inline int get_error_code() 275 | { 276 | const auto error = WSAGetLastError(); 277 | 278 | //We need to posixify the values that we are actually using inside this header. 279 | switch(error) 280 | { 281 | case WSAEWOULDBLOCK: 282 | return EWOULDBLOCK; 283 | case WSAEBADF: 284 | return EBADF; 285 | case WSAEINTR: 286 | return EINTR; 287 | default: 288 | return error; 289 | } 290 | } 291 | } 292 | #else //UNIX platform 293 | 294 | #include 295 | #include 296 | #include 297 | #include 298 | #include 299 | #include 300 | #include 301 | #include 302 | #include 303 | 304 | using ioctl_setting = int; 305 | using buffsize_t = size_t; 306 | 307 | //To get consistent socket API between Windows and Linux: 308 | static const int INVALID_SOCKET = -1; 309 | static const int SOCKET_ERROR = -1; 310 | using SOCKET = int; 311 | using SOCKADDR_IN = sockaddr_in; 312 | using SOCKADDR = sockaddr; 313 | using IN_ADDR = in_addr; 314 | 315 | //Wrap them in their WIN32 names 316 | inline int closesocket(SOCKET in) 317 | { 318 | return close(in); 319 | } 320 | 321 | template 322 | inline int ioctlsocket(int fd, int request, Params&&... params) 323 | { 324 | return ioctl(fd, request, params...); 325 | } 326 | 327 | #define KISSNET_OS_SPECIFIC_PAYLOAD_NAME dummy 328 | #define KISSNET_OS_SPECIFIC char dummy 329 | #define KISSNET_OS_INIT dummy = 42; 330 | 331 | namespace unix_specific 332 | { 333 | } 334 | 335 | inline int get_error_code() 336 | { 337 | return errno; 338 | } 339 | 340 | #endif 341 | 342 | ///Main namespace of kissnet 343 | namespace kissnet 344 | { 345 | 346 | ///Exception-less error handling infrastructure 347 | namespace error 348 | { 349 | static void (*callback)(const std::string&, void* ctx) = nullptr; 350 | static void* ctx = nullptr; 351 | static bool abortOnFatalError = true; 352 | 353 | inline void handle(const std::string& str) 354 | { 355 | //if the error::callback function has been provided, call that 356 | if(callback) 357 | { 358 | callback(str, ctx); 359 | } 360 | //Print error into the standard error output 361 | else 362 | { 363 | fputs(str.c_str(), stderr); 364 | } 365 | 366 | //If the error abort hasn't been deactivated 367 | if(abortOnFatalError) 368 | { 369 | abort(); 370 | } 371 | } 372 | } 373 | 374 | ///low level protocol used, between TCP and UDP 375 | enum class protocol { tcp, 376 | udp }; 377 | 378 | ///Represent ipv4 vs ipv6 379 | enum class ip { 380 | v4, 381 | v6 382 | }; 383 | 384 | ///buffer is an array of std::byte 385 | template 386 | using buffer = std::array; 387 | 388 | ///port_t is the port 389 | using port_t = uint16_t; 390 | 391 | ///An endpoint is where the network will connect to (address and port) 392 | struct endpoint 393 | { 394 | ///The address to connect to 395 | std::string address {}; 396 | 397 | ///The port to connect to 398 | port_t port {}; 399 | 400 | ///Default constructor, the endpoint is not valid at that point, but you can set the address/port manually 401 | endpoint() = default; 402 | 403 | ///Basically create the endpoint with what you give it 404 | endpoint(std::string addr, port_t prt) : 405 | address { std::move(addr) }, port { prt } 406 | { } 407 | 408 | static bool is_valid_port_number(unsigned long n) 409 | { 410 | return n < 1 << 16; 411 | } 412 | 413 | ///Construct the endpoint from "address:port" 414 | endpoint(std::string addr) 415 | { 416 | const auto separator = addr.find_last_of(':'); 417 | 418 | //Check if input wasn't missformed 419 | if(separator == std::string::npos) 420 | kissnet_fatal_error("string is not of address:port form"); 421 | if(separator == addr.size() - 1) 422 | kissnet_fatal_error("string has ':' as last character. Expected port number here"); 423 | 424 | //Isolate address 425 | address = addr.substr(0, separator); 426 | 427 | //Read from string as unsigned 428 | const auto parsed_port = strtoul(addr.substr(separator + 1).c_str(), nullptr, 10); 429 | 430 | //In all other cases, port was always given as a port_t type, strongly preventing it to be a number outside of the [0; 65535] range. Here it's not the case. 431 | //To detect errors early, check it here : 432 | if(!is_valid_port_number(parsed_port)) 433 | kissnet_fatal_error("Invalid port number " + std::to_string(parsed_port)); 434 | 435 | //Store it 436 | port = static_cast(parsed_port); 437 | } 438 | 439 | ///Construct an endpoint from a SOCKADDR 440 | endpoint(SOCKADDR* addr) 441 | { 442 | switch(addr->sa_family) 443 | { 444 | case AF_INET: { 445 | auto ip_addr = (SOCKADDR_IN*)(addr); 446 | address = inet_ntoa(ip_addr->sin_addr); 447 | port = ntohs(ip_addr->sin_port); 448 | } 449 | break; 450 | 451 | case AF_INET6: { 452 | auto ip_addr = (sockaddr_in6*)(addr); 453 | char buffer[INET6_ADDRSTRLEN]; 454 | address = inet_ntop(AF_INET6, &(ip_addr->sin6_addr), buffer, INET6_ADDRSTRLEN); 455 | port = ntohs(ip_addr->sin6_port); 456 | } 457 | break; 458 | 459 | default: { 460 | kissnet_fatal_error("Trying to construct an endpoint for a protocol familly that is neither AF_INET or AF_INET6"); 461 | } 462 | } 463 | 464 | if(address.empty()) 465 | kissnet_fatal_error("Couldn't construct endpoint from sockaddr(_storage) struct"); 466 | } 467 | }; 468 | 469 | //Wrap "system calls" here to avoid conflicts with the names used in the socket class 470 | 471 | ///socket() 472 | inline auto syscall_socket = [](int af, int type, int protocol) { 473 | return ::socket(af, type, protocol); 474 | }; 475 | 476 | ///recv() 477 | inline auto syscall_recv = [](SOCKET s, char* buff, buffsize_t len, int flags) { 478 | return ::recv(s, buff, len, flags); 479 | }; 480 | 481 | ///send() 482 | inline auto syscall_send = [](SOCKET s, const char* buff, buffsize_t len, int flags) { 483 | return ::send(s, buff, len, flags); 484 | }; 485 | 486 | ///bind() 487 | inline auto syscall_bind = [](SOCKET s, const struct sockaddr* name, socklen_t namelen) { 488 | return ::bind(s, name, namelen); 489 | }; 490 | 491 | ///connect() 492 | inline auto syscall_connect = [](SOCKET s, const struct sockaddr* name, socklen_t namelen) { 493 | return ::connect(s, name, namelen); 494 | }; 495 | 496 | ///listen() 497 | inline auto syscall_listen = [](SOCKET s, int backlog) { 498 | return ::listen(s, backlog); 499 | }; 500 | 501 | ///accept() 502 | inline auto syscall_accept = [](SOCKET s, struct sockaddr* addr, socklen_t* addrlen) { 503 | return ::accept(s, addr, addrlen); 504 | }; 505 | 506 | ///Represent the status of a socket as returned by a socket operation (send, received). Implicitly convertible to bool 507 | struct socket_status 508 | { 509 | ///Enumeration of socket status, with a 1 byte footprint 510 | enum values : int8_t { 511 | errored = 0x0, 512 | valid = 0x1, 513 | cleanly_disconnected = 0x2, 514 | non_blocking_would_have_blocked = 0x3 515 | 516 | /* ... any other info on a "still valid socket" goes here ... */ 517 | 518 | }; 519 | 520 | ///Actual value of the socket_status. 521 | const values value; 522 | 523 | ///Use the default constructor 524 | socket_status() : 525 | value { errored } { } 526 | 527 | ///Construct a "errored/valid" status for a true/false 528 | explicit socket_status(bool state) : 529 | value(values(state ? valid : errored)) { } 530 | 531 | socket_status(values v) : 532 | value(v) { } 533 | 534 | ///Copy socket status by default 535 | socket_status(const socket_status&) = default; 536 | 537 | ///Move socket status by default 538 | socket_status(socket_status&&) = default; 539 | 540 | ///implicitly convert this object to const bool (as the status should not change) 541 | operator bool() const 542 | { 543 | //See the above enum: every value <= 0 correspond to an error, and will return false. Every value > 0 returns true 544 | return value > 0; 545 | } 546 | 547 | int8_t get_value() 548 | { 549 | return value; 550 | } 551 | 552 | bool operator==(values v) 553 | { 554 | return v == value; 555 | } 556 | }; 557 | 558 | ///Class that represent a socket 559 | template 560 | class socket 561 | { 562 | ///Represent a number of bytes with a status information. Some of the methods of this class returns this. 563 | using bytes_with_status = std::tuple; 564 | 565 | ///OS specific stuff. payload we have to hold onto for RAII management of the Operating System's socket library (e.g. Windows Socket API WinSock2) 566 | KISSNET_OS_SPECIFIC; 567 | 568 | ///operatic-system type for a socket object 569 | SOCKET sock; 570 | 571 | ///Location where this socket is bound 572 | endpoint bind_loc; 573 | 574 | ///Address information structures 575 | addrinfo getaddrinfo_hints {}; 576 | addrinfo* getaddrinfo_results = nullptr; 577 | 578 | void initialize_addrinfo(int& type, short& family) 579 | { 580 | int iprotocol {}; 581 | if constexpr(sock_proto == protocol::tcp) 582 | { 583 | type = SOCK_STREAM; 584 | iprotocol = IPPROTO_TCP; 585 | } 586 | 587 | else if constexpr(sock_proto == protocol::udp) 588 | { 589 | type = SOCK_DGRAM; 590 | iprotocol = IPPROTO_UDP; 591 | } 592 | 593 | if constexpr(ipver == ip::v4) 594 | { 595 | family = AF_INET; 596 | } 597 | 598 | else if constexpr(ipver == ip::v6) 599 | { 600 | family = AF_INET6; 601 | } 602 | 603 | (void)memset(&getaddrinfo_hints, 0, sizeof getaddrinfo_hints); 604 | getaddrinfo_hints.ai_family = family; 605 | getaddrinfo_hints.ai_socktype = type; 606 | getaddrinfo_hints.ai_protocol = iprotocol; 607 | getaddrinfo_hints.ai_flags = AI_ADDRCONFIG; 608 | } 609 | 610 | ///sockaddr struct 611 | sockaddr_storage socket_output = {}; 612 | sockaddr_storage socket_input = {}; 613 | socklen_t socket_input_socklen {}; 614 | 615 | public: 616 | ///Construct an invalid socket 617 | socket() : 618 | sock { INVALID_SOCKET }, 619 | getaddrinfo_hints(), 620 | socket_input_socklen(0) 621 | { 622 | } 623 | 624 | ///socket<> isn't copyable 625 | socket(const socket&) = delete; 626 | 627 | ///socket<> isn't copyable 628 | socket& operator=(const socket&) = delete; 629 | 630 | ///Move constructor. socket<> isn't copyable 631 | socket(socket&& other) noexcept : 632 | getaddrinfo_hints() 633 | { 634 | KISSNET_OS_SPECIFIC_PAYLOAD_NAME = std::move(other.KISSNET_OS_SPECIFIC_PAYLOAD_NAME); 635 | bind_loc = std::move(other.bind_loc); 636 | sock = std::move(other.sock); 637 | socket_output = std::move(other.socket_output); 638 | socket_input = std::move(other.socket_input); 639 | socket_input_socklen = std::move(other.socket_input_socklen); 640 | getaddrinfo_results = std::move(other.getaddrinfo_results); 641 | 642 | other.sock = INVALID_SOCKET; 643 | } 644 | 645 | ///Move assign operation 646 | socket& operator=(socket&& other) noexcept 647 | { 648 | 649 | if(this != &other) 650 | { 651 | 652 | if(!(sock < 0) || sock != INVALID_SOCKET) 653 | closesocket(sock); 654 | 655 | KISSNET_OS_SPECIFIC_PAYLOAD_NAME = std::move(other.KISSNET_OS_SPECIFIC_PAYLOAD_NAME); 656 | bind_loc = std::move(other.bind_loc); 657 | sock = std::move(other.sock); 658 | socket_output = std::move(other.socket_output); 659 | socket_input = std::move(other.socket_input); 660 | socket_input_socklen = std::move(other.socket_input_socklen); 661 | getaddrinfo_results = std::move(other.getaddrinfo_results); 662 | 663 | other.sock = INVALID_SOCKET; 664 | } 665 | return *this; 666 | } 667 | 668 | ///Return true if the underlying OS provided socket representation (file descriptor, handle...). Both socket are pointing to the same thing in this case 669 | bool operator==(const socket& other) const 670 | { 671 | return sock == other.sock; 672 | } 673 | 674 | ///Return true if socket is valid. If this is false, you probably shouldn't attempt to send/receive anything, it will probably explode in your face! 675 | bool is_valid() const 676 | { 677 | return sock != INVALID_SOCKET; 678 | } 679 | 680 | inline operator bool() const 681 | { 682 | return is_valid(); 683 | } 684 | 685 | ///Construct socket and (if applicable) connect to the endpoint 686 | socket(endpoint bind_to) : 687 | bind_loc { std::move(bind_to) } 688 | { 689 | //operating system related housekeeping 690 | KISSNET_OS_INIT; 691 | 692 | //Do we use streams or datagrams 693 | int type; 694 | short family; 695 | initialize_addrinfo(type, family); 696 | 697 | sock = syscall_socket(family, type, 0); 698 | if(sock == INVALID_SOCKET) 699 | { 700 | kissnet_fatal_error("socket() syscall failed!"); 701 | } 702 | 703 | if(getaddrinfo(bind_loc.address.c_str(), std::to_string(bind_loc.port).c_str(), &getaddrinfo_hints, &getaddrinfo_results) != 0) 704 | { 705 | //TODO There can be more than one address to attempt to use in the getaddrinfo_results (It's a list). Try to go further down that list in error condition 706 | kissnet_fatal_error("getaddrinfo failed!"); 707 | } 708 | 709 | //Fill socket_input with 0s 710 | memset(static_cast(&socket_input), 0, sizeof socket_input); 711 | } 712 | 713 | ///Construct a socket from an operating system socket, an additional endpoint to remember from where we are 714 | socket(SOCKET native_sock, endpoint bind_to) : 715 | sock { native_sock }, bind_loc(std::move(bind_to)), getaddrinfo_hints {} 716 | { 717 | KISSNET_OS_INIT; 718 | 719 | short family; 720 | int type; 721 | 722 | initialize_addrinfo(type, family); 723 | 724 | //Fill socket_input with 0s 725 | memset(static_cast(&socket_input), 0, sizeof socket_input); 726 | } 727 | 728 | ///Set the socket in non blocking mode 729 | /// \param state By default "true". If put to false, it will set the socket back into blocking, normal mode 730 | void set_non_blocking(bool state = true) const 731 | { 732 | #ifdef _WIN32 733 | ioctl_setting set = state ? 1 : 0; 734 | if(ioctlsocket(sock, FIONBIO, &set) < 0) 735 | #else 736 | const auto flags = fcntl(sock, F_GETFL, 0); 737 | const auto newflags = state ? flags | O_NONBLOCK : flags ^ O_NONBLOCK; 738 | if(fcntl(sock, F_SETFL, newflags) < 0) 739 | #endif 740 | kissnet_fatal_error("setting socket to nonblock returned an error"); 741 | } 742 | 743 | ///Bind socket locally using the address and port of the endpoint 744 | void bind() 745 | { 746 | 747 | memcpy(&socket_output, getaddrinfo_results->ai_addr, sizeof(SOCKADDR)); 748 | 749 | if(syscall_bind(sock, static_cast(getaddrinfo_results->ai_addr), socklen_t(getaddrinfo_results->ai_addrlen)) == SOCKET_ERROR) 750 | { 751 | kissnet_fatal_error("bind() failed\n"); 752 | } 753 | } 754 | 755 | ///(For TCP) connect to the endpoint as client 756 | bool connect() 757 | { 758 | if constexpr(sock_proto == protocol::tcp) //only TCP is a connected protocol 759 | { 760 | 761 | memcpy(&socket_output, getaddrinfo_results->ai_addr, sizeof(SOCKADDR)); 762 | 763 | return static_cast(syscall_connect(sock, reinterpret_cast(&socket_output), sizeof(SOCKADDR)) != SOCKET_ERROR); 764 | } 765 | } 766 | 767 | ///(for TCP= setup socket to listen to connection. Need to be called on binded socket, before being able to accept() 768 | void listen() 769 | { 770 | if constexpr(sock_proto == protocol::tcp) 771 | { 772 | if(syscall_listen(sock, SOMAXCONN) == SOCKET_ERROR) 773 | { 774 | kissnet_fatal_error("listen failed\n"); 775 | } 776 | } 777 | } 778 | 779 | ///(for TCP) Wait for incoming connection, return socket connect to the client. Blocking. 780 | socket accept() 781 | { 782 | if constexpr(sock_proto != protocol::tcp) 783 | { 784 | return { INVALID_SOCKET, {} }; 785 | } 786 | 787 | SOCKADDR socket_address; 788 | SOCKET s; 789 | socklen_t size = sizeof socket_address; 790 | 791 | if((s = syscall_accept(sock, &socket_address, &size)) == INVALID_SOCKET) 792 | { 793 | 794 | const auto error = get_error_code(); 795 | switch(error) 796 | { 797 | case EWOULDBLOCK: //if socket "would have blocked" from the call, ignore 798 | case EINTR: //if blocking call got interrupted, ignore; 799 | return {}; 800 | } 801 | 802 | kissnet_fatal_error("accept() returned an invalid socket\n"); 803 | } 804 | 805 | return { s, endpoint(&socket_address) }; 806 | } 807 | 808 | void close() 809 | { 810 | if(!(sock == INVALID_SOCKET)) 811 | closesocket(sock); 812 | 813 | sock = INVALID_SOCKET; 814 | } 815 | 816 | ///Close socket on destruction 817 | ~socket() 818 | { 819 | close(); 820 | 821 | if(getaddrinfo_results) 822 | freeaddrinfo(getaddrinfo_results); 823 | } 824 | 825 | template 826 | bytes_with_status send(const buffer& buff, const size_t length = buff_size) 827 | { 828 | assert(buff_size >= length); 829 | return send(buff.data(), length); 830 | } 831 | 832 | ///Send some bytes through the pipe 833 | bytes_with_status send(const std::byte* read_buff, size_t length) 834 | { 835 | auto received_bytes { 0 }; 836 | if constexpr(sock_proto == protocol::tcp) 837 | { 838 | received_bytes = syscall_send(sock, reinterpret_cast(read_buff), static_cast(length), 0); 839 | } 840 | 841 | else if constexpr(sock_proto == protocol::udp) 842 | { 843 | memcpy(&socket_output, getaddrinfo_results->ai_addr, getaddrinfo_results->ai_addrlen); 844 | received_bytes = sendto(sock, reinterpret_cast(read_buff), static_cast(length), 0, static_cast(getaddrinfo_results->ai_addr), socklen_t(getaddrinfo_results->ai_addrlen)); 845 | } 846 | 847 | if(received_bytes < 0) 848 | { 849 | if(get_error_code() == EWOULDBLOCK) 850 | { 851 | return { 0, socket_status::non_blocking_would_have_blocked }; 852 | } 853 | 854 | return { 0, socket_status::errored }; 855 | } 856 | 857 | return { received_bytes, socket_status::valid }; 858 | } 859 | 860 | ///receive bytes inside the buffer, return the number of bytes you got. You can choose to write inside the buffer at a specific start offset (in number of bytes) 861 | template 862 | bytes_with_status recv(buffer& write_buff, size_t start_offset = 0) 863 | { 864 | 865 | auto received_bytes = 0; 866 | if constexpr(sock_proto == protocol::tcp) 867 | { 868 | received_bytes = syscall_recv(sock, reinterpret_cast(write_buff.data()) + start_offset, static_cast(buff_size - start_offset), 0); 869 | } 870 | 871 | else if constexpr(sock_proto == protocol::udp) 872 | { 873 | socket_input_socklen = sizeof socket_input; 874 | 875 | received_bytes = ::recvfrom(sock, reinterpret_cast(write_buff.data()) + start_offset, static_cast(buff_size - start_offset), 0, reinterpret_cast(&socket_input), &socket_input_socklen); 876 | } 877 | 878 | if(received_bytes < 0) 879 | { 880 | const auto error = get_error_code(); 881 | if(error == EWOULDBLOCK) 882 | return { 0, socket_status::non_blocking_would_have_blocked }; 883 | if(error == EAGAIN) 884 | return { 0, socket_status::non_blocking_would_have_blocked }; 885 | return { 0, socket_status::errored }; 886 | } 887 | 888 | if(received_bytes == 0) 889 | { 890 | return { received_bytes, socket_status::cleanly_disconnected }; 891 | } 892 | 893 | return { size_t(received_bytes), socket_status::valid }; 894 | } 895 | 896 | ///recive up-to len bytes inside the memory location pointed by buffer 897 | bytes_with_status recv(std::byte* buffer, size_t len) 898 | { 899 | auto received_bytes = 0; 900 | if constexpr(sock_proto == protocol::tcp) 901 | { 902 | received_bytes = syscall_recv(sock, reinterpret_cast(buffer), static_cast(len), 0); 903 | } 904 | 905 | else if constexpr(sock_proto == protocol::udp) 906 | { 907 | socket_input_socklen = sizeof socket_input; 908 | 909 | received_bytes = ::recvfrom(sock, reinterpret_cast(buffer), static_cast(len), 0, reinterpret_cast(&socket_input), &socket_input_socklen); 910 | } 911 | 912 | if(received_bytes < 0) 913 | { 914 | const auto error = get_error_code(); 915 | if(error == EWOULDBLOCK) 916 | return { 0, socket_status::non_blocking_would_have_blocked }; 917 | if(error == EAGAIN) 918 | return { 0, socket_status::non_blocking_would_have_blocked }; 919 | return { 0, socket_status::errored }; 920 | } 921 | 922 | if(received_bytes == 0) 923 | { 924 | return { received_bytes, socket_status::cleanly_disconnected }; 925 | } 926 | 927 | return { size_t(received_bytes), socket_status::valid }; 928 | } 929 | 930 | ///Return the endpoint where this socket is talking to 931 | endpoint get_bind_loc() const 932 | { 933 | return bind_loc; 934 | } 935 | 936 | ///Return an endpoint that originated the data in the last recv 937 | endpoint get_recv_endpoint() const 938 | { 939 | if constexpr(sock_proto == protocol::tcp) 940 | { 941 | return get_bind_loc(); 942 | } 943 | if constexpr(sock_proto == protocol::udp) 944 | { 945 | return { (sockaddr*)&socket_input }; 946 | } 947 | } 948 | 949 | ///Return the number of bytes available inside the socket 950 | size_t bytes_available() const 951 | { 952 | static ioctl_setting size = 0; 953 | const auto status = ioctlsocket(sock, FIONREAD, &size); 954 | 955 | if(status < 0) 956 | { 957 | kissnet_fatal_error("ioctlsocket status is negative when getting FIONREAD\n"); 958 | } 959 | 960 | return size > 0 ? size : 0; 961 | } 962 | 963 | ///Return the protocol used by this socket 964 | static protocol get_protocol() 965 | { 966 | return sock_proto; 967 | } 968 | }; 969 | 970 | ///Alias for socket 971 | using tcp_socket = socket; 972 | ///Alias for socket 973 | using udp_socket = socket; 974 | ///IPV6 version of a TCP socket 975 | using tcp_socket_v6 = socket; 976 | ///IPV6 version of an UDP socket 977 | using udp_socket_v6 = socket; 978 | } 979 | 980 | //cleanup preprocessor macros 981 | #undef KISSNET_OS_SPECIFIC_PAYLOAD_NAME 982 | #undef KISSNET_OS_SPECIFIC 983 | #undef KISSNET_OS_INIT 984 | #undef kissnet_fatal_error 985 | 986 | #endif //KISS_NET 987 | -------------------------------------------------------------------------------- /main_dmx.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "mpegts_demuxer.h" 5 | 6 | //AAC (ADTS) audio 7 | #define TYPE_AUDIO 0x0f 8 | //H.264 video 9 | #define TYPE_VIDEO_H264 0x1b 10 | //H.265 video 11 | #define TYPE_VIDEO_H265 0x24 12 | 13 | using namespace mpegts; 14 | 15 | MpegTsDemuxer gDemuxer; 16 | 17 | int aFrameCounter = {1}; 18 | int vFrameCounter = {1}; 19 | 20 | void dmxOutput(const EsFrame &esFrame) { 21 | 22 | if (esFrame.mStreamType == TYPE_AUDIO) { 23 | std::cout << " AAC Frame, PID: " << unsigned(esFrame.mPid) << " PTS:" << unsigned(esFrame.mPts) << std::endl; 24 | size_t lDataPointer = 0; 25 | 26 | //Break out the individual ADTS frames 27 | do { 28 | uint8_t b1= esFrame.mData->data()[lDataPointer]; 29 | uint8_t b2= esFrame.mData->data()[lDataPointer + 1] & 0xf0; 30 | if (b1 == 0xff && b2 == 0xf0) { 31 | if ((esFrame.mData->data()[lDataPointer + 1] & 0x08)) { 32 | std::cout << std::endl; 33 | std::cout << "Error -> Not MPEG-4 ADTS Audio " << std::endl; 34 | break; 35 | } 36 | } else { 37 | std::cout << std::endl; 38 | std::cout << "Error -> ADTS sync word missing " << std::endl; 39 | break; 40 | } 41 | 42 | b1 = esFrame.mData->data()[lDataPointer + 3] & 0x3; 43 | b2 = esFrame.mData->data()[lDataPointer + 4]; 44 | uint8_t b3 = esFrame.mData->data()[lDataPointer + 5]; 45 | uint32_t lLength = (((uint32_t)b1 << 16) | ((uint32_t)b2 << 8) | (uint32_t)b3) >> 5; 46 | 47 | std::string aFilename; 48 | std::ostringstream aFilenameBld; 49 | aFilenameBld << "bars_dmx/af" << aFrameCounter++ << ".adts"; 50 | aFilename = aFilenameBld.str(); 51 | std::cout << " Filename: " << aFilename << std::endl; 52 | std::ofstream oAFile(aFilename, std::ofstream::binary | std::ofstream::out); 53 | oAFile.write((const char *)esFrame.mData->data() + lDataPointer, lLength); 54 | oAFile.close(); 55 | lDataPointer += lLength; 56 | } while (lDataPointer < esFrame.mData->size()); 57 | } else if (esFrame.mStreamType == TYPE_VIDEO_H264) { 58 | std::cout << " H.264 , PID: " << unsigned(esFrame.mPid) << " PTS:" << unsigned(esFrame.mPts) << " DTS:" << unsigned(esFrame.mDts) << std::endl; 59 | 60 | std::string vFilename; 61 | std::ostringstream vFilenameBld; 62 | vFilenameBld << "bars_dmx/vf" << vFrameCounter << ".h264"; 63 | vFilename = vFilenameBld.str(); 64 | std::cout << " Filename: " << vFilename << std::endl; 65 | std::ofstream oVFile(vFilename, std::ofstream::binary | std::ofstream::out); 66 | oVFile.write((const char *)esFrame.mData->data(), esFrame.mData->size()); 67 | oVFile.close(); 68 | 69 | std::string ptsDtsFilename; 70 | std::ostringstream ptsDtsFilenameBld; 71 | ptsDtsFilenameBld << "bars_dmx/ptsdts" << vFrameCounter++ << ".txt"; 72 | ptsDtsFilename = ptsDtsFilenameBld.str(); 73 | std::cout << " Filename: " << ptsDtsFilename << std::endl; 74 | std::ofstream oPtsDtsFile(ptsDtsFilename); 75 | oPtsDtsFile << std::to_string(esFrame.mPts) << std::endl; 76 | oPtsDtsFile << std::to_string(esFrame.mDts) << std::endl; 77 | oPtsDtsFile.close(); 78 | 79 | 80 | } else if (esFrame.mStreamType == TYPE_VIDEO_H265) { 81 | std::cout << " H.265 frame, PID: " << unsigned(esFrame.mPid) << std::endl; 82 | } else { 83 | std::cout << " streamType: " << unsigned(esFrame.mStreamType) << " streamId: " 84 | << unsigned(esFrame.mStreamId) << " PID:" << unsigned(esFrame.mPid) << std::endl; 85 | } 86 | 87 | } 88 | 89 | int main(int argc, char *argv[]) { 90 | std::cout << "TS - demuxlib test " << std::endl; 91 | gDemuxer.esOutCallback = std::bind(&dmxOutput, std::placeholders::_1); 92 | std::ifstream ifile("bars.ts", std::ios::binary | std::ios::in); 93 | uint8_t packet[188] = {0}; 94 | SimpleBuffer in; 95 | 96 | while (!ifile.eof()) { 97 | ifile.read((char*)&packet[0], 188); 98 | in.append(packet, 188); 99 | gDemuxer.decode(in); 100 | } 101 | ifile.close(); 102 | return EXIT_SUCCESS; 103 | } 104 | -------------------------------------------------------------------------------- /main_mx.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-06-24. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "kissnet/kissnet.hpp" 11 | #include "mpegts_muxer.h" 12 | 13 | //If defined save TS data to file 14 | #define SAVE_TO_FILE 15 | #ifdef SAVE_TO_FILE 16 | #define SAVED_TS_NUM_SECONDS 25 17 | std::ofstream oMpegTs("output.ts", std::ofstream::binary | std::ofstream::out); 18 | int savedOutputSeconds = {0}; 19 | bool savingFrames = {true}; 20 | bool closeOnce = {false}; 21 | #endif 22 | 23 | //AAC audio 24 | #define TYPE_AUDIO 0x0f 25 | //H.264 video 26 | #define TYPE_VIDEO 0x1b 27 | 28 | //Audio PID 29 | #define AUDIO_PID 257 30 | //Video PID 31 | #define VIDEO_PID 256 32 | //PMT PID 33 | #define PMT_PID 100 34 | 35 | #define SECOND_SLEEP 1000 * 1000 36 | #define LOOP_FRAME_COUNT_VIDEO 1200 37 | #define LOOP_FRAME_COUNT_AUDIO 1125 38 | 39 | //Network part 40 | #define TARGET_INTERFACE "127.0.0.1" 41 | #define TARGET_PORT 8100 42 | kissnet::udp_socket mpegTsOut(kissnet::endpoint(TARGET_INTERFACE, TARGET_PORT)); 43 | //Network part end 44 | 45 | int64_t gTimeReference; 46 | uint64_t gVideoFrameCounter = 0; 47 | uint64_t gAudioFrameCounter = 0; 48 | 49 | uint64_t gFirstPts = 0; 50 | uint64_t gPtsLoopDuration = 0; 51 | 52 | using namespace mpegts; 53 | 54 | //Create the multiplexer 55 | MpegTsMuxer *gpMuxer; 56 | 57 | //Debug 58 | int fRCnt = {0}; 59 | 60 | uint8_t* findNal(uint8_t *start, uint8_t *end) 61 | { 62 | uint8_t *p = start; 63 | while (p <= end-3 && (p[0] || p[1] || p[2]!=1)) ++p; 64 | if (p > end-3) return nullptr; 65 | /* Include 8 bits leading zero */ 66 | //if (p>start && *(p-1)==0) 67 | // return (p-1); 68 | return p; 69 | } 70 | 71 | void muxOutput(SimpleBuffer &rTsOutBuffer, uint8_t lTag, bool lRandom){ 72 | 73 | //Double to fail at non integer data and be able to visualize in the print-out 74 | double packets = (double)rTsOutBuffer.size() / 188.0; 75 | //std::cout << "Sending -> " << packets << " MPEG-TS packets" << std::endl; 76 | uint8_t* lpData = rTsOutBuffer.data(); 77 | for (int lI = 0 ; lI < packets ; lI++) { 78 | mpegTsOut.send((const std::byte *)lpData+(lI*188), 188); 79 | #ifdef SAVE_TO_FILE 80 | if (savingFrames) { 81 | oMpegTs.write((char*)lpData + (lI * 188), 188); 82 | } else { 83 | if (!closeOnce) { 84 | oMpegTs.close(); 85 | std::cout << "Stopped saving to file." << std::endl; 86 | closeOnce = true; 87 | } 88 | } 89 | #endif 90 | } 91 | } 92 | 93 | void fakeAudioEncoder() { 94 | double lAdtsTimeDistance = (1024.0/48000.0) * 1000000.0; 95 | double lNextTriger = (double)gTimeReference + lAdtsTimeDistance; //Meaning every 21.33333..ms from baseTime we send a adts frame 96 | uint64_t lFrameCounter = 0; 97 | uint8_t* adtsFrame; 98 | std::string lThisPTS; 99 | uint64_t lPts = gFirstPts-1920; 100 | uint64_t lCurrentOffset = 0; 101 | EsFrame lEsFrame; 102 | 103 | //The loop counter is there to silence my compilers infinitive loop warning. 104 | uint64_t lLoopCounter = UINT64_MAX; 105 | while (lLoopCounter) { 106 | std::this_thread::sleep_for(std::chrono::microseconds(500)); 107 | int64_t lTimeNow = std::chrono::duration_cast( 108 | std::chrono::steady_clock::now().time_since_epoch()).count(); 109 | if (lTimeNow >= lNextTriger) { 110 | lNextTriger += (1024.0/48000.0) * 1000000.0; 111 | gAudioFrameCounter++; 112 | 113 | std::string aFilename; 114 | std::ostringstream aFilenameBld; 115 | aFilenameBld << "bars_dmx/af" << lFrameCounter+1 << ".adts"; 116 | aFilename = aFilenameBld.str(); 117 | //std::cout << " Filename: " << aFilename << std::endl; 118 | 119 | std::ifstream adtsFile (aFilename, std::ios::in|std::ios::binary|std::ios::ate); 120 | if (adtsFile.is_open()) 121 | { 122 | size_t lFileSize= adtsFile.tellg(); 123 | adtsFrame = new uint8_t [lFileSize]; 124 | adtsFile.seekg (0, std::ios::beg); 125 | adtsFile.read ((char*)&adtsFrame[0], lFileSize); 126 | adtsFile.close(); 127 | 128 | lPts += 90000.0/(48000.0/1024.0); 129 | uint64_t lRecalcPts = lPts + lCurrentOffset; 130 | 131 | lEsFrame.mData = std::make_shared(); 132 | lEsFrame.mData->append(adtsFrame, lFileSize); 133 | lEsFrame.mPts = lRecalcPts; 134 | lEsFrame.mDts = lRecalcPts; 135 | lEsFrame.mPcr = 0; 136 | lEsFrame.mStreamType = TYPE_AUDIO; 137 | lEsFrame.mStreamId = 192; 138 | lEsFrame.mPid = AUDIO_PID; 139 | lEsFrame.mExpectedPesPacketLength = 0; 140 | lEsFrame.mRandomAccess = 0x01; 141 | lEsFrame.mCompleted = true; 142 | 143 | gpMuxer->encode(lEsFrame); 144 | 145 | delete[] adtsFrame; 146 | } 147 | else std::cout << "Unable to open file" << std::endl; 148 | 149 | 150 | if ((lFrameCounter+1) == LOOP_FRAME_COUNT_AUDIO) { 151 | lCurrentOffset += gPtsLoopDuration; 152 | lPts = gFirstPts-1920; 153 | } 154 | 155 | lFrameCounter = (lFrameCounter + 1) % LOOP_FRAME_COUNT_AUDIO; 156 | 157 | } 158 | } 159 | } 160 | 161 | void fakeVideoEncoder() { 162 | int64_t lNextTriger = gTimeReference + (20*1000); //Meaning every 20ms from baseTime we send a picture 163 | uint64_t lFrameCounter = 0; 164 | uint8_t* videoNal; 165 | std::string lThisPTS; 166 | std::string lThisDTS; 167 | uint64_t lPts = 0; 168 | uint64_t lDts = 0; 169 | uint64_t lCurrentOffset = 0; 170 | EsFrame lEsFrame; 171 | 172 | uint64_t lLoopCounter = UINT64_MAX; 173 | 174 | while (lLoopCounter) { 175 | std::this_thread::sleep_for(std::chrono::microseconds(500)); 176 | int64_t lTimeNow = std::chrono::duration_cast( 177 | std::chrono::steady_clock::now().time_since_epoch()).count(); 178 | if (lTimeNow >= lNextTriger) { 179 | lNextTriger += (20*1000); 180 | gVideoFrameCounter++; 181 | 182 | std::string vFilename; 183 | std::ostringstream vFilenameBld; 184 | vFilenameBld << "bars_dmx/vf" << lFrameCounter+1 << ".h264"; 185 | vFilename = vFilenameBld.str(); 186 | //std::cout << " Filename: " << vFilename << std::endl; 187 | 188 | std::string ptsDtsFilename; 189 | std::ostringstream ptsDtsFilenameBld; 190 | ptsDtsFilenameBld << "bars_dmx/ptsdts" << lFrameCounter+1 << ".txt"; 191 | ptsDtsFilename = ptsDtsFilenameBld.str(); 192 | 193 | std::ifstream thisPtsFile(ptsDtsFilename); 194 | std::getline(thisPtsFile, lThisPTS); 195 | std::getline(thisPtsFile, lThisDTS); 196 | thisPtsFile.close(); 197 | std::stringstream lPtsSS(lThisPTS); 198 | lPtsSS >> lPts; 199 | std::stringstream lDtsSS(lThisDTS); 200 | lDtsSS >> lDts; 201 | 202 | std::ifstream nalFile (vFilename, std::ios::in|std::ios::binary|std::ios::ate); 203 | if (nalFile.is_open()) 204 | { 205 | size_t lFileSize= nalFile.tellg(); 206 | videoNal = new uint8_t [lFileSize]; 207 | nalFile.seekg (0, std::ios::beg); 208 | nalFile.read ((char*)&videoNal[0], lFileSize); 209 | nalFile.close(); 210 | 211 | uint8_t lIdrFound = 0x00; 212 | uint8_t *lpNalStart = (uint8_t *) videoNal; 213 | while (true) { 214 | lpNalStart = findNal(lpNalStart, (uint8_t *) videoNal + lFileSize); 215 | if (lpNalStart == nullptr) break; 216 | uint8_t startcode = lpNalStart[3] & 0x1f; 217 | if (startcode == 0x05) { 218 | lIdrFound = 0x01; 219 | } 220 | lpNalStart++; 221 | } 222 | 223 | uint64_t lRecalcPts = lPts + lCurrentOffset; 224 | uint64_t lRecalcDts = lDts + lCurrentOffset; 225 | 226 | lEsFrame.mData = std::make_shared(); 227 | lEsFrame.mData->append(videoNal, lFileSize); 228 | lEsFrame.mPts = lRecalcPts; 229 | lEsFrame.mDts = lRecalcDts; 230 | lEsFrame.mPcr = lRecalcDts; 231 | lEsFrame.mStreamType = TYPE_VIDEO; 232 | lEsFrame.mStreamId = 224; 233 | lEsFrame.mPid = VIDEO_PID; 234 | lEsFrame.mExpectedPesPacketLength = 0; 235 | lEsFrame.mRandomAccess = lIdrFound; 236 | lEsFrame.mCompleted = true; 237 | 238 | gpMuxer->encode(lEsFrame); 239 | 240 | delete[] videoNal; 241 | } 242 | else std::cout << "Unable to open file" << std::endl; 243 | 244 | 245 | if ((lFrameCounter+1) == LOOP_FRAME_COUNT_VIDEO) { 246 | lCurrentOffset += gPtsLoopDuration; 247 | } 248 | 249 | lFrameCounter = (lFrameCounter + 1) % LOOP_FRAME_COUNT_VIDEO; 250 | 251 | } 252 | } 253 | } 254 | 255 | int main(int argc, char *argv[]) { 256 | std::cout << "TS - muxlib test " << std::endl; 257 | 258 | std::map gStreamPidMap; 259 | gStreamPidMap[TYPE_AUDIO] = AUDIO_PID; 260 | gStreamPidMap[TYPE_VIDEO] = VIDEO_PID; 261 | gpMuxer = new MpegTsMuxer(gStreamPidMap, PMT_PID, VIDEO_PID, MpegTsMuxer::MuxType::h222Type); 262 | 263 | gpMuxer->tsOutCallback = std::bind(&muxOutput, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); 264 | 265 | std::string lStartPTS; 266 | std::string lEndPTS; 267 | std::ifstream startPtsFile("bars_dmx/ptsdts1.txt"); 268 | std::getline(startPtsFile, lStartPTS); 269 | startPtsFile.close(); 270 | std::ifstream endPtsFile("bars_dmx/ptsdts1201.txt"); 271 | std::getline(endPtsFile, lEndPTS); 272 | endPtsFile.close(); 273 | std::stringstream frst(lStartPTS); 274 | frst >> gFirstPts; 275 | uint64_t lLastPts = 0; 276 | std::stringstream lst(lEndPTS); 277 | lst >> lLastPts; 278 | gPtsLoopDuration = lLastPts - gFirstPts; 279 | 280 | uint64_t lVideoFrameCounter = 0; 281 | 282 | gTimeReference = std::chrono::duration_cast( 283 | std::chrono::steady_clock::now().time_since_epoch()).count(); 284 | 285 | std::thread(std::bind(&fakeAudioEncoder)).detach(); 286 | std::thread(std::bind(&fakeVideoEncoder)).detach(); 287 | 288 | uint64_t lLoopCounter = UINT64_MAX; 289 | while (lLoopCounter) { 290 | gTimeReference += SECOND_SLEEP; 291 | int64_t lTimeNow = std::chrono::duration_cast( 292 | std::chrono::steady_clock::now().time_since_epoch()).count(); 293 | int64_t lTimeCompensation = gTimeReference - lTimeNow; 294 | if (lTimeCompensation < 0) { 295 | std::cout << "Stats printing overloaded" << std::endl; 296 | gTimeReference = lTimeNow; 297 | } else { 298 | std::this_thread::sleep_for(std::chrono::microseconds(lTimeCompensation)); 299 | } 300 | 301 | std::cout << "Video FPS:" << unsigned(gVideoFrameCounter - lVideoFrameCounter) << std::endl; 302 | lVideoFrameCounter = gVideoFrameCounter; 303 | 304 | #ifdef SAVE_TO_FILE 305 | if (savedOutputSeconds++ == SAVED_TS_NUM_SECONDS) { 306 | savingFrames = false; 307 | } 308 | #endif 309 | } 310 | 311 | //Will never execute 312 | delete gpMuxer; 313 | 314 | return EXIT_SUCCESS; 315 | } 316 | -------------------------------------------------------------------------------- /mpegts/common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "simple_buffer.h" 3 | 4 | namespace mpegts { 5 | 6 | void writePcr(SimpleBuffer &rSb, uint64_t lPcr) { 7 | rSb.write1Byte((int8_t) (lPcr >> 25)); 8 | rSb.write1Byte((int8_t) (lPcr >> 17)); 9 | rSb.write1Byte((int8_t) (lPcr >> 9)); 10 | rSb.write1Byte((int8_t) (lPcr >> 1)); 11 | rSb.write1Byte((int8_t) (lPcr << 7 | 0x7e)); 12 | rSb.write1Byte(0); 13 | } 14 | 15 | void writePts(SimpleBuffer &rSb, uint32_t lFb, uint64_t lPts) { 16 | uint32_t lVal; 17 | 18 | lVal = lFb << 4 | (((lPts >> 30) & 0x07) << 1) | 1; 19 | rSb.write1Byte((uint8_t) lVal); 20 | 21 | lVal = (((lPts >> 15) & 0x7fff) << 1) | 1; 22 | rSb.write2Bytes((uint16_t) lVal); 23 | 24 | lVal = (((lPts) & 0x7fff) << 1) | 1; 25 | rSb.write2Bytes((uint16_t) lVal); 26 | } 27 | 28 | uint64_t readPts(SimpleBuffer &rSb) { 29 | uint64_t lPts = 0; 30 | uint64_t lVal = 0; 31 | lVal = rSb.read1Byte(); 32 | lPts |= ((lVal >> 1) & 0x07) << 30; 33 | 34 | lVal = rSb.read2Bytes(); 35 | lPts |= ((lVal >> 1) & 0x7fff) << 15; 36 | 37 | lVal = rSb.read2Bytes(); 38 | lPts |= ((lVal >> 1) & 0x7fff); 39 | 40 | return lPts; 41 | } 42 | 43 | uint64_t readPcr(SimpleBuffer &rSb) { 44 | uint64_t lPcr = 0; 45 | uint64_t lVal = rSb.read1Byte(); 46 | lPcr |= (lVal << 25) & 0x1FE000000; 47 | 48 | lVal = rSb.read1Byte(); 49 | lPcr |= (lVal << 17) & 0x1FE0000; 50 | 51 | lVal = rSb.read1Byte(); 52 | lPcr |= (lVal << 9) & 0x1FE00; 53 | 54 | lVal = rSb.read1Byte(); 55 | lPcr |= (lVal << 1) & 0x1FE; 56 | 57 | lVal = rSb.read1Byte(); 58 | lPcr |= ((lVal >> 7) & 0x01); 59 | 60 | rSb.read1Byte(); 61 | 62 | return lPcr; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /mpegts/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Prefixes used 4 | // m class member 5 | // p pointer (*) 6 | // r reference (&) 7 | // l local scope 8 | 9 | #include 10 | 11 | namespace mpegts { 12 | 13 | class MpegTsAdaptationFieldType { 14 | public: 15 | // Reserved for future use by ISO/IEC 16 | static const uint8_t mReserved = 0x00; 17 | // No adaptation_field, payload only 18 | static const uint8_t mPayloadOnly = 0x01; 19 | // Adaptation_field only, no payload 20 | static const uint8_t mAdaptionOnly = 0x02; 21 | // Adaptation_field followed by payload 22 | static const uint8_t mPayloadAdaptionBoth = 0x03; 23 | }; 24 | 25 | /// 26 | /// @brief Log levels 27 | enum class LogLevel { 28 | kTrace, // Detailed diagnostics (for development only) 29 | kDebug, // Messages intended for debugging only 30 | kInfo, // Messages about normal behavior (default log level) 31 | kWarning, // Warnings (functionality intact) 32 | kError, // Recoverable errors (functionality impaired) 33 | kCritical, // Unrecoverable errors (application must stop) 34 | kOff // Turns off all logging 35 | }; 36 | 37 | class SimpleBuffer; 38 | 39 | extern void writePcr(SimpleBuffer &rSb, uint64_t lPcr); 40 | 41 | extern void writePts(SimpleBuffer &rSb, uint32_t lFb, uint64_t lPts); 42 | 43 | extern uint64_t readPts(SimpleBuffer &rSb); 44 | 45 | extern uint64_t readPcr(SimpleBuffer &rSb); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /mpegts/crc.cpp: -------------------------------------------------------------------------------- 1 | #include "crc.h" 2 | 3 | namespace mpegts { 4 | 5 | uint32_t crc32(const uint8_t *pData, int lLen) { 6 | uint32_t lCrc = 0xffffffff; 7 | for (int lI = 0; lI < lLen; lI++) { 8 | lCrc = (lCrc << 8) ^ crcTable[((lCrc >> 24) ^ *pData++) & 0xff]; 9 | } 10 | return lCrc; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mpegts/crc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Prefixes used 4 | // m class member 5 | // p pointer (*) 6 | // r reference (&) 7 | // l local scope 8 | 9 | #include 10 | 11 | namespace mpegts { 12 | 13 | static const uint32_t crcTable[256] = { 14 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 15 | 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 16 | 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, 17 | 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 18 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 19 | 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 20 | 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, 21 | 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 22 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 23 | 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 24 | 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, 25 | 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 26 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 27 | 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 28 | 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 29 | 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 30 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 31 | 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 32 | 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 33 | 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 34 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 35 | 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 36 | 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, 37 | 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 38 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 39 | 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 40 | 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, 41 | 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 42 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 43 | 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 44 | 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, 45 | 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 46 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 47 | 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 48 | 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 49 | 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 50 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 51 | 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 52 | 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 53 | 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 54 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 55 | 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 56 | 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 57 | }; 58 | 59 | extern uint32_t crc32(const uint8_t *pData, int lLen); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /mpegts/mpegts_demuxer.cpp: -------------------------------------------------------------------------------- 1 | #include "mpegts_demuxer.h" 2 | 3 | #include "common.h" 4 | 5 | namespace mpegts { 6 | 7 | #define LOG(level, msg) if (streamInfoCallback != nullptr) { streamInfoCallback(level, msg); } 8 | 9 | bool MpegTsDemuxer::checkSync(SimpleBuffer& rIn) { 10 | uint8_t currentSyncByte = *(rIn.currentData()); 11 | 12 | if (rIn.dataLeft() > kTsPacketSize_188) { 13 | uint8_t nextSyncByte = *(rIn.currentData() + kTsPacketSize_188); 14 | return currentSyncByte == kTsPacketSyncByte && nextSyncByte == kTsPacketSyncByte; 15 | } 16 | 17 | return currentSyncByte == kTsPacketSyncByte; 18 | } 19 | 20 | uint8_t MpegTsDemuxer::decode(SimpleBuffer &rIn) { 21 | if (!mRestData.empty()) { 22 | rIn.prepend(mRestData.data(), mRestData.size()); 23 | mRestData.clear(); 24 | } 25 | 26 | while (rIn.dataLeft() >= kTsPacketSize_188) { 27 | if (!checkSync(rIn)) { 28 | // If TS packet sync is lost, skip one byte and continue until the 188 bytes packet sync is found again 29 | mBytesDroppedToRecoverSync++; 30 | rIn.skip(1); 31 | continue; 32 | } 33 | 34 | if (mBytesDroppedToRecoverSync > 0) { 35 | // Here we have recovered sync again, notify the user about the lost sync and the number of bytes dropped 36 | // and reset the counter and state flag. 37 | LOG(LogLevel::kWarning, "Lost sync in TS stream, dropped " + 38 | std::to_string(mBytesDroppedToRecoverSync) + 39 | " bytes to recover TS sync"); 40 | mBytesDroppedToRecoverSync = 0; 41 | } 42 | 43 | // We have a full TS packet starting at the current position in rIn, start parsing 44 | parseTsPacket(rIn); 45 | } 46 | 47 | if (rIn.dataLeft()) { 48 | // Save the rest of the data for next decode-call 49 | mRestData.append(rIn.currentData(), rIn.dataLeft()); 50 | } 51 | 52 | rIn.clear(); 53 | return 0; 54 | } 55 | 56 | void MpegTsDemuxer::parseTsPacket(mpegts::SimpleBuffer& rSb) { 57 | const size_t lTSPacketStartPos = rSb.pos(); 58 | 59 | TsHeader lTsHeader; 60 | lTsHeader.decode(rSb); 61 | if (lTsHeader.mPid == 0x1FFF) { 62 | // Null packet, skip the rest 63 | rSb.skip(kTsPacketSize_188 - (rSb.pos() - lTSPacketStartPos)); 64 | return; 65 | } 66 | 67 | uint8_t lRandomAccessIndicator = 0; 68 | if (!parseAdaptationField(lTsHeader, rSb, kTsPacketSize_188 - (rSb.pos() - lTSPacketStartPos), lRandomAccessIndicator)) { 69 | LOG(LogLevel::kWarning, "Failed to parse adaptation field"); 70 | return; 71 | } 72 | 73 | if (!lTsHeader.hasPayload()) { 74 | rSb.skip(kTsPacketSize_188 - (rSb.pos() - lTSPacketStartPos)); 75 | return; 76 | } 77 | 78 | if (lTsHeader.mPid == kPatId && mPmtId == 0) { 79 | // Found PAT, parse it to get PMT pid 80 | if (!parsePat(lTsHeader, rSb)) { 81 | LOG(LogLevel::kWarning, "Failed to parse PAT"); 82 | } 83 | } 84 | else if (mEsFrames.empty() && mPmtId != 0 && lTsHeader.mPid == mPmtId) { 85 | // Found PMT, parse it to get ES PIDs 86 | if (!parsePmt(lTsHeader, rSb)) { 87 | LOG(LogLevel::kWarning, "Failed to parse PMT"); 88 | } 89 | } 90 | else if (mEsFrames.find(lTsHeader.mPid) != mEsFrames.end()) { 91 | // Found an ES PID, parse it, may contain a PCR as well 92 | if (kTsPacketSize_188 > rSb.pos() - lTSPacketStartPos) { 93 | size_t sizeOfPesPayload = kTsPacketSize_188 - (rSb.pos() - lTSPacketStartPos); 94 | if (!parsePes(lTsHeader, lRandomAccessIndicator, rSb, sizeOfPesPayload)) { 95 | LOG(LogLevel::kWarning, "Failed to parse PES"); 96 | } 97 | } 98 | } 99 | 100 | if (kTsPacketSize_188 < rSb.pos() - lTSPacketStartPos) { 101 | LOG(LogLevel::kWarning, "Read more than expected data from the packet, bytes consumed: " + 102 | std::to_string(rSb.pos() - lTSPacketStartPos) + 103 | " expected less than 188"); 104 | } else { 105 | // Some functions might not consume the whole packet, which is ok, skip the rest 106 | rSb.skip(kTsPacketSize_188 - (rSb.pos() - lTSPacketStartPos)); 107 | } 108 | } 109 | 110 | bool MpegTsDemuxer::parseAdaptationField(const mpegts::TsHeader& rTsHeader, mpegts::SimpleBuffer& rSb, size_t bytesLeftInPacket, uint8_t& randomAccessIndicator) { 111 | uint8_t lDiscontinuityIndicator = 0; 112 | randomAccessIndicator = 0; 113 | 114 | if (rTsHeader.hasAdaptationField()) { 115 | AdaptationFieldHeader lAdaptionField; 116 | lAdaptionField.decode(rSb); 117 | lDiscontinuityIndicator = lAdaptionField.mDiscontinuityIndicator; 118 | randomAccessIndicator = lAdaptionField.mRandomAccessIndicator; 119 | 120 | if (lAdaptionField.mAdaptationFieldLength > 0) { 121 | bytesLeftInPacket -= 2; // Decode of adaptation field with a length > 0 consumes 2 bytes 122 | if (lAdaptionField.mAdaptationFieldLength > bytesLeftInPacket) { 123 | LOG(LogLevel::kWarning, "Adaptation field length is larger than the remaining packet size, broken packet"); 124 | return false; 125 | } 126 | 127 | if (shouldParsePcr(rTsHeader.mPid)) { 128 | parsePcr(lAdaptionField, rSb); 129 | } else { 130 | // Just skip the adaptation field, -1 for the byte following the length field 131 | rSb.skip(lAdaptionField.mAdaptationFieldLength - 1); 132 | } 133 | } 134 | } 135 | checkContinuityCounter(rTsHeader, lDiscontinuityIndicator); 136 | 137 | return true; 138 | } 139 | 140 | bool MpegTsDemuxer::parsePat(const TsHeader& rTsHeader, SimpleBuffer &rSb) { 141 | if (rTsHeader.mPayloadUnitStartIndicator == 0x01) { 142 | uint8_t lPointField = rSb.read1Byte(); 143 | rSb.skip(lPointField); 144 | 145 | mPatHeader.decode(rSb); 146 | if (mPatHeader.mSectionLength > 0x3FD) { 147 | LOG(LogLevel::kWarning, "PAT section length is too large"); 148 | return false; 149 | } else if (mPatHeader.mB0 != 0 || mPatHeader.mSectionSyntaxIndicator != 1) { 150 | LOG(LogLevel::kWarning, "PAT section syntax is not as expected"); 151 | return false; 152 | } 153 | 154 | uint16_t bytesLeftMinusCRC32 = mPatHeader.mSectionLength - 5 - 4; // 5 bytes for the header, 5 bytes for CRC_32 155 | while (bytesLeftMinusCRC32 >= 4) { 156 | uint16_t programNumber = rSb.read2Bytes(); 157 | if (programNumber == 0) { 158 | // Network PID, skip 159 | rSb.skip(2); 160 | } else { 161 | mPmtId = rSb.read2Bytes() & 0x1fff; 162 | mPatIsValid = true; 163 | } 164 | bytesLeftMinusCRC32 -= 4; 165 | } 166 | 167 | rSb.skip(4); // Skip CRC_32 168 | 169 | if (streamInfoCallback != nullptr) { 170 | mPatHeader.print(LogLevel::kTrace, streamInfoCallback); 171 | } 172 | } 173 | return mPatIsValid; 174 | } 175 | 176 | bool MpegTsDemuxer::parsePmt(const TsHeader& rTsHeader, SimpleBuffer &rSb) { 177 | if (rTsHeader.mPayloadUnitStartIndicator == 0x01) { 178 | uint8_t lPointField = rSb.read1Byte(); 179 | rSb.skip(lPointField); 180 | 181 | mPmtHeader.decode(rSb); 182 | 183 | if (mPmtHeader.mSectionLength > 0x3FD) { 184 | LOG(LogLevel::kWarning, "PMT section length is too large"); 185 | return false; 186 | } else if (mPmtHeader.mB0 != 0 || mPmtHeader.mSectionSyntaxIndicator != 1) { 187 | LOG(LogLevel::kWarning, "PMT section syntax is not as expected"); 188 | return false; 189 | } 190 | 191 | mPcrId = mPmtHeader.mPcrPid; 192 | for (const std::shared_ptr& mInfo : mPmtHeader.mInfos) { 193 | mEsFrames[mInfo->mElementaryPid] = EsFrame(mInfo->mStreamType, mInfo->mElementaryPid); 194 | 195 | mStreamPidMap[mInfo->mStreamType] = mInfo->mElementaryPid; 196 | } 197 | mPmtIsValid = true; 198 | 199 | if (streamInfoCallback != nullptr) { 200 | mPmtHeader.print(LogLevel::kTrace, streamInfoCallback); 201 | } 202 | } 203 | 204 | return mPmtIsValid; 205 | } 206 | 207 | bool MpegTsDemuxer::parsePes(const mpegts::TsHeader& rTsHeader, uint8_t randomAccessIndicator, mpegts::SimpleBuffer& rSb, size_t payloadSize) { 208 | size_t lPesStartPos = rSb.pos(); 209 | 210 | EsFrame& lEsFrame = mEsFrames[rTsHeader.mPid]; 211 | if (rTsHeader.mPayloadUnitStartIndicator == 0x01) { 212 | // TODO: Transport streams with no PES packet size set on video streams will not output the very 213 | // last frame, since there will be no final packet with mPayloadUnitStartIndicator set. Add a flush 214 | // function to the demuxer API that the user can call once it has ran out of input data. 215 | 216 | if (!lEsFrame.mData->empty()) { 217 | // We have an old packet with data, flush and reset it before starting a new one 218 | if (esOutCallback) { 219 | if (lEsFrame.mExpectedPesPacketLength == 0) { 220 | lEsFrame.mCompleted = true; 221 | } else { 222 | // Expected length is set, but we didn't get enough data, deliver what we have as broken 223 | lEsFrame.mBroken = true; 224 | } 225 | esOutCallback(lEsFrame); 226 | } 227 | lEsFrame.reset(); 228 | } 229 | 230 | // Potential old package has been flushed, set random access indicator and start new package 231 | lEsFrame.mRandomAccess = randomAccessIndicator; 232 | 233 | PESHeader lPesHeader; 234 | lPesHeader.decode(rSb); 235 | if (lPesHeader.mMarkerBits != 0x02) { 236 | LOG(LogLevel::kWarning, "Corrupt PES header, marker bits not as expected"); 237 | return false; 238 | } 239 | 240 | const size_t lPositionAfterPesHeaderDataLength = rSb.pos(); 241 | 242 | lEsFrame.mStreamId = lPesHeader.mStreamId; 243 | lEsFrame.mExpectedPesPacketLength = lPesHeader.mPesPacketLength; 244 | if (lPesHeader.mPtsDtsFlags == 0x02) { 245 | lEsFrame.mPts = readPts(rSb); 246 | lEsFrame.mDts = lEsFrame.mPts; 247 | } else if (lPesHeader.mPtsDtsFlags == 0x03) { 248 | lEsFrame.mPts = readPts(rSb); 249 | // readPts function reads a "PTS", but the bitstream syntax is the same for DTS 250 | lEsFrame.mDts = readPts(rSb); 251 | } 252 | 253 | if (lPesHeader.mPesPacketLength != 0) { 254 | size_t payloadLength = lPesHeader.mPesPacketLength - 3 - lPesHeader.mHeaderDataLength; 255 | lEsFrame.mExpectedPayloadLength = payloadLength; 256 | } 257 | 258 | // Skip the rest of the header data 259 | size_t bytesRead = rSb.pos() - lPositionAfterPesHeaderDataLength; 260 | if (bytesRead > lPesHeader.mHeaderDataLength) { 261 | LOG(LogLevel::kWarning, "Corrupt PES header, skipping packet"); 262 | return false; 263 | } 264 | rSb.skip(lPesHeader.mHeaderDataLength - bytesRead); 265 | } 266 | 267 | if (rSb.pos() - lPesStartPos > payloadSize) { 268 | LOG(LogLevel::kWarning, "Read more PES data than expected, bytes read: " + 269 | std::to_string(rSb.pos() - lPesStartPos) + 270 | " payload size: " + std::to_string(payloadSize)); 271 | return false; 272 | } 273 | size_t bytesLeftOfPayload = payloadSize - (rSb.pos() - lPesStartPos); 274 | 275 | lEsFrame.mData->append(rSb.currentData(), bytesLeftOfPayload); 276 | rSb.skip(bytesLeftOfPayload); 277 | 278 | // Enough data to deliver? 279 | if (lEsFrame.mExpectedPayloadLength == lEsFrame.mData->size()) { 280 | if (esOutCallback) { 281 | lEsFrame.mCompleted = true; 282 | esOutCallback(lEsFrame); 283 | } 284 | lEsFrame.reset(); 285 | } 286 | 287 | return true; 288 | } 289 | 290 | void MpegTsDemuxer::parsePcr(const mpegts::AdaptationFieldHeader& rAdaptionField, mpegts::SimpleBuffer& rSb) const { 291 | int bytesRead = 1; // 1 byte for the fields following the length field 292 | if (rAdaptionField.mPcrFlag == 1) { 293 | uint64_t lPcr = readPcr(rSb); 294 | bytesRead += 6; // 6 bytes for the PCR 295 | 296 | if (pcrOutCallback) { 297 | pcrOutCallback(lPcr); 298 | } 299 | } 300 | // Skip rest of the adaptation field 301 | rSb.skip(rAdaptionField.mAdaptationFieldLength - bytesRead); 302 | } 303 | 304 | bool MpegTsDemuxer::shouldParsePcr(uint16_t pid) const { 305 | return pcrOutCallback != nullptr && mPcrId != 0 && pid == mPcrId; 306 | } 307 | 308 | void MpegTsDemuxer::checkContinuityCounter(const TsHeader &rTsHeader, uint8_t discontinuityIndicator) { 309 | uint16_t pid = rTsHeader.mPid; 310 | if (pid == 0x1FFF) { 311 | // CC is undefined for null packets 312 | return; 313 | } 314 | 315 | auto it = mCCs.find(pid); 316 | if (it == mCCs.end()) { 317 | // First CC on this PID 318 | mCCs[pid] = rTsHeader.mContinuityCounter; 319 | return; 320 | } 321 | 322 | if (discontinuityIndicator) { 323 | // First CC after discontinuity 324 | mCCs[pid] = rTsHeader.mContinuityCounter; 325 | return; 326 | } 327 | 328 | uint8_t expectedCC; 329 | uint8_t actualCC = rTsHeader.mContinuityCounter; 330 | if (rTsHeader.hasPayload()) { 331 | // CC should have been incremented 332 | expectedCC = (it->second + 1) & 0xF; 333 | } else { 334 | // CC should remain the same 335 | expectedCC = it->second; 336 | } 337 | 338 | mCCs[pid] = actualCC; 339 | 340 | if (actualCC != expectedCC) { 341 | if (ccErrorCallback != nullptr) { 342 | ccErrorCallback(pid, expectedCC, actualCC); 343 | } 344 | } 345 | } 346 | 347 | } 348 | -------------------------------------------------------------------------------- /mpegts/mpegts_demuxer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Prefixes used 4 | // m class member 5 | // p pointer (*) 6 | // r reference (&) 7 | // l local scope 8 | 9 | #include "common.h" 10 | #include "ts_packet.h" 11 | #include "simple_buffer.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace mpegts { 20 | 21 | class MpegTsDemuxer final { 22 | public: 23 | 24 | MpegTsDemuxer() = default; 25 | 26 | ~MpegTsDemuxer() = default; 27 | 28 | uint8_t decode(SimpleBuffer &rIn); 29 | 30 | std::function esOutCallback = nullptr; 31 | std::function pcrOutCallback = nullptr; 32 | std::function streamInfoCallback = nullptr; 33 | std::function ccErrorCallback = nullptr; 34 | 35 | // stream, pid 36 | const int kPatId = 0x0; 37 | std::map mStreamPidMap; 38 | int mPmtId = 0; 39 | 40 | // PAT 41 | PATHeader mPatHeader; 42 | bool mPatIsValid = false; 43 | 44 | // PMT 45 | PMTHeader mPmtHeader; 46 | bool mPmtIsValid = false; 47 | 48 | private: 49 | // Check that the CC is incremented according to spec. ccErrorCallback is called on error. 50 | void checkContinuityCounter(const TsHeader &rTsHeader, uint8_t discontinuityIndicator); 51 | 52 | // Check that the current position in buffer is pointing to a sync byte and if the buffer is larger than a TS packet, 53 | // the function checks that the first byte after the TS packet is also a sync byte. Returns true if the buffer is in 54 | // sync with the TS packet boundaries, otherwise false. 55 | static bool checkSync(SimpleBuffer& rIn); 56 | 57 | // Parse a TS packet, the input buffer should be aligned to the start of a TS packet and the buffer should be at least 58 | // 188 bytes long. 59 | void parseTsPacket(SimpleBuffer& rSb); 60 | 61 | // Parse the adaptation field, will look for PCR values and pass to the pcrOutCallback. Returns true if parsing was 62 | // successful, otherwise false. The randomAccessIndicator is set to 1 if the packet contains a random access point. 63 | bool parseAdaptationField(const TsHeader& rTsHeader, SimpleBuffer& rSb, size_t bytesLeftInPacket, uint8_t& randomAccessIndicator); 64 | 65 | // Parse the PAT table 66 | bool parsePat(const TsHeader& rTsHeader, SimpleBuffer& rSb); 67 | 68 | // Parse the PMT table 69 | bool parsePmt(const TsHeader& rTsHeader, SimpleBuffer& rSb); 70 | 71 | // Parse the PES data and create EsFrames. The EsFrames are passed to the esOutCallback. 72 | bool parsePes(const TsHeader& rTsHeader, uint8_t randomAccessIndicator, SimpleBuffer& rSb, size_t payloadSize); 73 | 74 | // Parse the PCR value from the adaptation field and pass to the pcrOutCallback. 75 | void parsePcr(const AdaptationFieldHeader& rAdaptionField, SimpleBuffer& rSb) const; 76 | 77 | // Check if the PCR value should be parsed based on the PID and the pcrOutCallback. 78 | bool shouldParsePcr(uint16_t pid) const; 79 | 80 | // Elementary stream data frames 81 | std::map mEsFrames; 82 | int mPcrId = 0; 83 | SimpleBuffer mRestData; 84 | 85 | // Continuity counters. Map from PID to current CC value. 86 | std::unordered_map mCCs; 87 | 88 | size_t mBytesDroppedToRecoverSync = 0; // Keep track of how many bytes were dropped to recover sync 89 | }; 90 | 91 | } 92 | -------------------------------------------------------------------------------- /mpegts/mpegts_muxer.cpp: -------------------------------------------------------------------------------- 1 | #include "mpegts_muxer.h" 2 | #include "crc.h" 3 | #include "common.h" 4 | #include 5 | 6 | static const uint16_t MPEGTS_NULL_PACKET_PID = 0x1FFF; 7 | static const uint16_t MPEGTS_PAT_PID = 0x0000; 8 | static const uint32_t MPEGTS_PAT_INTERVAL = 20; 9 | 10 | namespace mpegts { 11 | 12 | MpegTsMuxer::MpegTsMuxer(std::map lStreamPidMap, uint16_t lPmtPid, uint16_t lPcrPid, MuxType lType) { 13 | mPmtPid = lPmtPid; 14 | mStreamPidMap = lStreamPidMap; 15 | mPcrPid = lPcrPid; 16 | mMuxType = lType; 17 | } 18 | 19 | MpegTsMuxer::~MpegTsMuxer() { 20 | } 21 | 22 | void MpegTsMuxer::createPat(SimpleBuffer &rSb, uint16_t lPmtPid, uint8_t lCc) { 23 | SimpleBuffer lPatSb; 24 | TsHeader lTsHeader; 25 | lTsHeader.mSyncByte = 0x47; 26 | lTsHeader.mTransportErrorIndicator = 0; 27 | lTsHeader.mPayloadUnitStartIndicator = 1; 28 | lTsHeader.mTransportPriority = 0; 29 | lTsHeader.mPid = MPEGTS_PAT_PID; 30 | lTsHeader.mTransportScramblingControl = 0; 31 | lTsHeader.mAdaptationFieldControl = MpegTsAdaptationFieldType::mPayloadOnly; 32 | lTsHeader.mContinuityCounter = lCc; 33 | 34 | AdaptationFieldHeader lAdaptField; 35 | 36 | PATHeader lPatHeader; 37 | lPatHeader.mTableId = 0x00; 38 | lPatHeader.mSectionSyntaxIndicator = 1; 39 | lPatHeader.mB0 = 0; 40 | lPatHeader.mReserved0 = 0x3; 41 | lPatHeader.mTransportStreamId = 0; 42 | lPatHeader.mReserved1 = 0x3; 43 | lPatHeader.mVersionNumber = 0; 44 | lPatHeader.mCurrentNextIndicator = 1; 45 | lPatHeader.mSectionNumber = 0x0; 46 | lPatHeader.mLastSectionNumber = 0x0; 47 | 48 | //program_number 49 | uint16_t lProgramNumber = 0x0001; 50 | //program_map_PID 51 | uint16_t lProgramMapPid = 0xe000 | (lPmtPid & 0x1fff); 52 | 53 | unsigned int lSectionLength = 4 + 4 + 5; 54 | lPatHeader.mSectionLength = lSectionLength & 0x3ff; 55 | 56 | lTsHeader.encode(lPatSb); 57 | lAdaptField.encode(lPatSb); 58 | lPatHeader.encode(lPatSb); 59 | lPatSb.write2Bytes(lProgramNumber); 60 | lPatSb.write2Bytes(lProgramMapPid); 61 | 62 | // crc32 63 | uint32_t lCrc32 = crc32((uint8_t *)lPatSb.data() + 5, lPatSb.size() - 5); 64 | lPatSb.write4Bytes(lCrc32); 65 | 66 | std::vector lStuff(188 - lPatSb.size(), 0xff); 67 | lPatSb.append((const uint8_t *)lStuff.data(),lStuff.size()); 68 | rSb.append(lPatSb.data(), lPatSb.size()); 69 | } 70 | 71 | void MpegTsMuxer::createPmt(SimpleBuffer &rSb, std::map lStreamPidMap, uint16_t lPmtPid, uint8_t lCc) { 72 | SimpleBuffer lPmtSb; 73 | TsHeader lTsHeader; 74 | lTsHeader.mSyncByte = 0x47; 75 | lTsHeader.mTransportErrorIndicator = 0; 76 | lTsHeader.mPayloadUnitStartIndicator = 1; 77 | lTsHeader.mTransportPriority = 0; 78 | lTsHeader.mPid = lPmtPid; 79 | lTsHeader.mTransportScramblingControl = 0; 80 | lTsHeader.mAdaptationFieldControl = MpegTsAdaptationFieldType::mPayloadOnly; 81 | lTsHeader.mContinuityCounter = lCc; 82 | 83 | AdaptationFieldHeader lAdaptField; 84 | 85 | PMTHeader lPmtHeader; 86 | lPmtHeader.mTableId = 0x02; 87 | lPmtHeader.mSectionSyntaxIndicator = 1; 88 | lPmtHeader.mB0 = 0; 89 | lPmtHeader.mReserved0 = 0x3; 90 | lPmtHeader.mSectionLength = 0; 91 | lPmtHeader.mProgramNumber = 0x0001; 92 | lPmtHeader.mReserved1 = 0x3; 93 | lPmtHeader.mVersionNumber = 0; 94 | lPmtHeader.mCurrentNextIndicator = 1; 95 | lPmtHeader.mSectionNumber = 0x00; 96 | lPmtHeader.mLastSectionNumber = 0x00; 97 | lPmtHeader.mReserved2 = 0x7; 98 | lPmtHeader.mReserved3 = 0xf; 99 | lPmtHeader.mProgramInfoLength = 0; 100 | lPmtHeader.mPcrPid = mPcrPid; 101 | for (auto lIt = lStreamPidMap.begin(); lIt != lStreamPidMap.end(); lIt++) { 102 | lPmtHeader.mInfos.push_back(std::shared_ptr(new PMTElementInfo(lIt->first, lIt->second))); 103 | } 104 | 105 | uint16_t lSectionLength = lPmtHeader.size() - 3 + 4; 106 | lPmtHeader.mSectionLength = lSectionLength & 0x3ff; 107 | 108 | lTsHeader.encode(lPmtSb); 109 | lAdaptField.encode(lPmtSb); 110 | lPmtHeader.encode(lPmtSb); 111 | 112 | // crc32 113 | uint32_t crc_32 = crc32((uint8_t *)lPmtSb.data() + 5, lPmtSb.size() - 5); 114 | lPmtSb.write4Bytes(crc_32); 115 | 116 | std::vector lStuff(188 - lPmtSb.size(), 0xff); 117 | lPmtSb.append((const uint8_t *)lStuff.data(),lStuff.size()); 118 | rSb.append(lPmtSb.data(), lPmtSb.size()); 119 | } 120 | 121 | void MpegTsMuxer::createPes(EsFrame &rFrame, SimpleBuffer &rSb) { 122 | bool lFirst = true; 123 | while (!rFrame.mData->empty()) { 124 | SimpleBuffer lPacket; 125 | 126 | TsHeader lTsHeader; 127 | lTsHeader.mPid = rFrame.mPid; 128 | lTsHeader.mAdaptationFieldControl = MpegTsAdaptationFieldType::mPayloadOnly; 129 | lTsHeader.mContinuityCounter = getCc(rFrame.mStreamType); 130 | 131 | if (lFirst) { 132 | lTsHeader.mPayloadUnitStartIndicator = 0x01; 133 | if (rFrame.mPid == mPcrPid) { 134 | lTsHeader.mAdaptationFieldControl |= 0x02; 135 | AdaptationFieldHeader adapt_field_header; 136 | adapt_field_header.mAdaptationFieldLength = 0x07; 137 | adapt_field_header.mRandomAccessIndicator = rFrame.mRandomAccess; 138 | adapt_field_header.mPcrFlag = 0x01; 139 | 140 | lTsHeader.encode(lPacket); 141 | adapt_field_header.encode(lPacket); 142 | writePcr(lPacket, rFrame.mPcr); 143 | } else if (rFrame.mRandomAccess) { 144 | lTsHeader.mAdaptationFieldControl |= 0x02; 145 | AdaptationFieldHeader adapt_field_header; 146 | adapt_field_header.mAdaptationFieldLength = 0x01; 147 | adapt_field_header.mRandomAccessIndicator = rFrame.mRandomAccess; 148 | 149 | lTsHeader.encode(lPacket); 150 | adapt_field_header.encode(lPacket); 151 | 152 | } else{ 153 | lTsHeader.encode(lPacket); 154 | } 155 | 156 | PESHeader lPesHeader; 157 | lPesHeader.mPacketStartCode = 0x000001; 158 | lPesHeader.mStreamId = rFrame.mStreamId; 159 | lPesHeader.mMarkerBits = 0x02; 160 | lPesHeader.mOriginalOrCopy = 0x01; 161 | 162 | if (rFrame.mPts != rFrame.mDts) { 163 | lPesHeader.mPtsDtsFlags = 0x03; 164 | lPesHeader.mHeaderDataLength = 0x0A; 165 | } else { 166 | lPesHeader.mPtsDtsFlags = 0x2; 167 | lPesHeader.mHeaderDataLength = 0x05; 168 | } 169 | 170 | uint32_t lPesSize = (lPesHeader.mHeaderDataLength + rFrame.mData->size() + 3); 171 | lPesHeader.mPesPacketLength = lPesSize > 0xffff ? 0 : lPesSize; 172 | lPesHeader.encode(lPacket); 173 | 174 | if (lPesHeader.mPtsDtsFlags == 0x03) { 175 | writePts(lPacket, 3, rFrame.mPts); 176 | writePts(lPacket, 1, rFrame.mDts); 177 | } else { 178 | writePts(lPacket, 2, rFrame.mPts); 179 | } 180 | } else { 181 | lTsHeader.encode(lPacket); 182 | } 183 | 184 | uint32_t lPos = lPacket.size(); 185 | uint32_t lBodySize = 188 - lPos; 186 | 187 | std::vector lFiller(lBodySize, 0x00); 188 | lPacket.append((const uint8_t *)lFiller.data(),lFiller.size()); 189 | 190 | lPacket.skip(lPos); 191 | 192 | uint32_t lInSize = rFrame.mData->size() - rFrame.mData->pos(); 193 | if (lBodySize <= 194 | lInSize) { 195 | lPacket.setData(lPos, rFrame.mData->data()+rFrame.mData->pos(), lBodySize); 196 | rFrame.mData->skip(lBodySize); 197 | } else { 198 | uint16_t lStuffSize = lBodySize - lInSize; 199 | 200 | //Take care of the case 1 stuffing byte 201 | 202 | if (lTsHeader.mAdaptationFieldControl == MpegTsAdaptationFieldType::mAdaptionOnly || 203 | lTsHeader.mAdaptationFieldControl == MpegTsAdaptationFieldType::mPayloadAdaptionBoth) { 204 | uint8_t* lpBase = lPacket.data() + 5 + lPacket.data()[4]; 205 | if (lFirst) { 206 | //We need to move the PES header 207 | size_t pesHeaderSize = lPos - (lPacket.data()[4] + 5); 208 | uint8_t *pesHeader = new uint8_t[pesHeaderSize]; 209 | memcpy(pesHeader,lpBase,pesHeaderSize); 210 | memcpy(lpBase+lStuffSize,pesHeader,pesHeaderSize); 211 | delete[] pesHeader; 212 | } else { 213 | lPacket.setData(lpBase - lPacket.data() + lStuffSize, lpBase, 214 | lPacket.data() + lPacket.pos() - lpBase); 215 | } 216 | 217 | // 218 | memset(lpBase, 0xff, lStuffSize); 219 | lPacket.skip(lStuffSize); 220 | lPacket.data()[4] += lStuffSize; 221 | 222 | } else { 223 | // adaptationFieldControl |= 0x20 == MpegTsAdaptationFieldType::payload_adaption_both 224 | // std::cout << "here" << std::endl; 225 | 226 | if (lFirst) { 227 | //we are here since there is no adaption field.. and first is set . That means we got a PES header. 228 | lPacket.data()[3] |= 0x20; 229 | uint8_t* lpBase = lPacket.data() + 4; 230 | size_t pesHeaderSize = lPos - 4; 231 | uint8_t *pesHeader = new uint8_t[pesHeaderSize]; 232 | memcpy(pesHeader,lpBase,pesHeaderSize); 233 | memcpy(lpBase+lStuffSize,pesHeader,pesHeaderSize); 234 | delete[] pesHeader; 235 | 236 | lPacket.data()[4] = lStuffSize - 1; 237 | if (lStuffSize >= 2) { 238 | lPacket.data()[5] = 0; 239 | memset(&(lPacket.data()[6]), 0xff, lStuffSize - 2); 240 | } 241 | 242 | lPacket.skip(lStuffSize); 243 | lPacket.data()[4] = lStuffSize - 1; 244 | if (lStuffSize >= 2) { 245 | lPacket.data()[5] = 0; 246 | memset(&(lPacket.data()[6]), 0xff, lStuffSize - 2); 247 | } 248 | 249 | } else { 250 | lPacket.data()[3] |= 0x20; 251 | int lDestPosition = 188 - 4 - lStuffSize; 252 | uint8_t *lSrcPosition = lPacket.data() + 4; 253 | int lLength = lPacket.pos() - 4; 254 | lPacket.setData(lDestPosition, lSrcPosition, lLength); 255 | lPacket.skip(lStuffSize); 256 | lPacket.data()[4] = lStuffSize - 1; 257 | if (lStuffSize >= 2) { 258 | lPacket.data()[5] = 0; 259 | memset(&(lPacket.data()[6]), 0xff, lStuffSize - 2); 260 | } 261 | } 262 | } 263 | lPacket.setData(lPacket.pos(), rFrame.mData->data()+rFrame.mData->pos(), lInSize); 264 | rFrame.mData->skip(lInSize); 265 | } 266 | 267 | rSb.append(lPacket.data(), lPacket.size()); 268 | lFirst = false; 269 | } 270 | } 271 | 272 | void MpegTsMuxer::createPcr(uint64_t lPcr, uint8_t lTag) { 273 | std::lock_guard lock(mMuxMtx); 274 | TsHeader lTsHeader; 275 | lTsHeader.mSyncByte = 0x47; 276 | lTsHeader.mTransportErrorIndicator = 0; 277 | lTsHeader.mPayloadUnitStartIndicator = 0; 278 | lTsHeader.mTransportPriority = 0; 279 | lTsHeader.mPid = mPcrPid; 280 | lTsHeader.mTransportScramblingControl = 0; 281 | lTsHeader.mAdaptationFieldControl = MpegTsAdaptationFieldType::mAdaptionOnly; 282 | lTsHeader.mContinuityCounter = 0; 283 | 284 | AdaptationFieldHeader lAdaptField; 285 | lAdaptField.mAdaptationFieldLength = 188 - 4 - 1; 286 | lAdaptField.mDiscontinuityIndicator = 0; 287 | lAdaptField.mRandomAccessIndicator = 0; 288 | lAdaptField.mElementaryStreamPriorityIndicator = 0; 289 | lAdaptField.mPcrFlag = 1; 290 | lAdaptField.mOpcrFlag = 0; 291 | lAdaptField.mSplicingPointFlag = 0; 292 | lAdaptField.mTransportPrivateDataFlag = 0; 293 | lAdaptField.mAdaptationFieldExtensionFlag = 0; 294 | 295 | SimpleBuffer lSb; 296 | lTsHeader.encode(lSb); 297 | lAdaptField.encode(lSb); 298 | writePcr(lSb, lPcr); 299 | std::vector lStuff(188 - lSb.size(), 0xff); 300 | lSb.append((const uint8_t *)lStuff.data(),lStuff.size()); 301 | if (tsOutCallback) { 302 | tsOutCallback(lSb, lTag, false); 303 | } 304 | } 305 | 306 | void MpegTsMuxer::createNull(uint8_t lTag) { 307 | std::lock_guard lock(mMuxMtx); 308 | TsHeader lTsHeader; 309 | lTsHeader.mSyncByte = 0x47; 310 | lTsHeader.mTransportErrorIndicator = 0; 311 | lTsHeader.mPayloadUnitStartIndicator = 0; 312 | lTsHeader.mTransportPriority = 0; 313 | lTsHeader.mPid = MPEGTS_NULL_PACKET_PID; 314 | lTsHeader.mTransportScramblingControl = 0; 315 | lTsHeader.mAdaptationFieldControl = MpegTsAdaptationFieldType::mPayloadOnly; 316 | lTsHeader.mContinuityCounter = 0; 317 | SimpleBuffer lSb; 318 | lTsHeader.encode(lSb); 319 | if (tsOutCallback) { 320 | tsOutCallback(lSb, lTag, false); 321 | } 322 | } 323 | 324 | void MpegTsMuxer::encode(EsFrame &rFrame, uint8_t lTag, bool lRandomAccess) { 325 | std::lock_guard lock(mMuxMtx); 326 | SimpleBuffer lSb; 327 | 328 | if (mMuxType == MpegTsMuxer::MuxType::segmentType && lRandomAccess) { 329 | uint8_t lPatPmtCc = getCc(0); 330 | createPat(lSb, mPmtPid, lPatPmtCc); 331 | createPmt(lSb, mStreamPidMap, mPmtPid, lPatPmtCc); 332 | } else if (mMuxType == MpegTsMuxer::MuxType::h222Type && shouldCreatePat()) { //FIXME h222Type is NOT implemented correct 333 | uint8_t lPatPmtCc = getCc(0); 334 | createPat(lSb, mPmtPid, lPatPmtCc); 335 | createPmt(lSb, mStreamPidMap, mPmtPid, lPatPmtCc); 336 | } 337 | 338 | createPes(rFrame, lSb); 339 | if (tsOutCallback) { 340 | tsOutCallback(lSb, lTag, lRandomAccess); 341 | } 342 | } 343 | 344 | uint8_t MpegTsMuxer::getCc(uint32_t lWithPid) { 345 | if (mPidCcMap.find(lWithPid) != mPidCcMap.end()) { 346 | mPidCcMap[lWithPid] = (mPidCcMap[lWithPid] + 1) & 0x0F; 347 | return mPidCcMap[lWithPid]; 348 | } 349 | 350 | mPidCcMap[lWithPid] = 0; 351 | return 0; 352 | } 353 | 354 | bool MpegTsMuxer::shouldCreatePat() { 355 | bool lRet = false; 356 | if (mCurrentIndex % MPEGTS_PAT_INTERVAL == 0) { 357 | if (mCurrentIndex > 0) { 358 | mCurrentIndex = 0; 359 | } 360 | lRet = true; 361 | } 362 | 363 | mCurrentIndex++; 364 | 365 | return lRet; 366 | } 367 | 368 | } 369 | -------------------------------------------------------------------------------- /mpegts/mpegts_muxer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Prefixes used 4 | // m class member 5 | // p pointer (*) 6 | // r reference (&) 7 | // l local scope 8 | 9 | #include "ts_packet.h" 10 | #include "simple_buffer.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace mpegts { 17 | 18 | class MpegTsMuxer { 19 | public: 20 | enum class MuxType: uint8_t { 21 | unknown, 22 | segmentType, //Generate a PAT + PMT when lRandomAccess is set to true in the encode() method 23 | h222Type, //Not implemented correct 24 | dvbType //Not implemented at all 25 | }; 26 | 27 | MpegTsMuxer(std::map lStreamPidMap, uint16_t lPmtPid, uint16_t lPcrPid, MuxType lType); 28 | 29 | virtual ~MpegTsMuxer(); 30 | 31 | void createPat(SimpleBuffer &rSb, uint16_t lPmtPid, uint8_t lCc); 32 | 33 | void createPmt(SimpleBuffer &rSb, std::map lStreamPidMap, uint16_t lPmtPid, uint8_t lCc); 34 | 35 | void createPes(EsFrame &rFrame, SimpleBuffer &rSb); 36 | 37 | void createPcr(uint64_t lPcr, uint8_t lTag = 0); 38 | 39 | void createNull(uint8_t lTag = 0); 40 | 41 | void encode(EsFrame &rFrame, uint8_t lTag = 0, bool lRandomAccess = false); 42 | 43 | std::function tsOutCallback = nullptr; 44 | 45 | 46 | private: 47 | uint8_t getCc(uint32_t lWithPid); 48 | 49 | uint32_t mCurrentIndex = 0; 50 | bool shouldCreatePat(); 51 | 52 | std::map mPidCcMap; 53 | 54 | uint16_t mPmtPid = 0; 55 | 56 | std::map mStreamPidMap; 57 | 58 | uint16_t mPcrPid; 59 | 60 | MuxType mMuxType = MuxType::unknown; 61 | 62 | std::mutex mMuxMtx; 63 | 64 | }; 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /mpegts/simple_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "simple_buffer.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace mpegts { 9 | 10 | SimpleBuffer::SimpleBuffer() = default; 11 | 12 | SimpleBuffer::SimpleBuffer(int32_t size, int8_t value) 13 | { 14 | mData = std::vector(size, value); 15 | } 16 | 17 | SimpleBuffer::~SimpleBuffer() = default; 18 | 19 | void SimpleBuffer::write1Byte(uint8_t val) 20 | { 21 | mData.push_back(val); 22 | } 23 | 24 | void SimpleBuffer::write2Bytes(uint16_t val) 25 | { 26 | uint8_t *p = reinterpret_cast(&val); 27 | 28 | for (int i = 1; i >= 0; --i) { 29 | mData.push_back(p[i]); 30 | } 31 | } 32 | 33 | void SimpleBuffer::write3Bytes(uint32_t val) 34 | { 35 | uint8_t *p = reinterpret_cast(&val); 36 | 37 | for (int i = 2; i >= 0; --i) { 38 | mData.push_back(p[i]); 39 | } 40 | } 41 | 42 | void SimpleBuffer::write4Bytes(uint32_t val) 43 | { 44 | uint8_t *p = reinterpret_cast(&val); 45 | 46 | for (int i = 3; i >= 0; --i) { 47 | mData.push_back(p[i]); 48 | } 49 | } 50 | 51 | void SimpleBuffer::write8Bytes(uint64_t val) 52 | { 53 | uint8_t *p = reinterpret_cast(&val); 54 | 55 | for (int i = 7; i >= 0; --i) { 56 | mData.push_back(p[i]); 57 | } 58 | } 59 | 60 | void SimpleBuffer::append(const uint8_t* bytes, size_t size) 61 | { 62 | if (!bytes || size <= 0) { 63 | #ifdef DEBUG 64 | std::cout << " append error " << std::endl; 65 | #endif 66 | return; 67 | } 68 | 69 | mData.insert(mData.end(), bytes, bytes + size); 70 | } 71 | 72 | void SimpleBuffer::prepend(const uint8_t* bytes, size_t size) 73 | { 74 | if (!bytes || size <= 0) { 75 | #ifdef DEBUG 76 | std::cout << " prepend error " << std::endl; 77 | #endif 78 | return; 79 | } 80 | 81 | mData.insert(mData.begin(), bytes, bytes + size); 82 | } 83 | 84 | uint8_t SimpleBuffer::read1Byte() 85 | { 86 | assert(require(1)); 87 | 88 | uint8_t val = mData.at(0 + mPos); 89 | mPos++; 90 | 91 | return val; 92 | } 93 | 94 | uint16_t SimpleBuffer::read2Bytes() 95 | { 96 | assert(require(2)); 97 | 98 | uint16_t val = 0; 99 | uint8_t *p = reinterpret_cast(&val); 100 | 101 | for (int i = 1; i >= 0; --i) { 102 | p[i] = mData.at(0 + mPos); 103 | mPos++; 104 | } 105 | 106 | return val; 107 | } 108 | 109 | uint32_t SimpleBuffer::read3Bytes() 110 | { 111 | assert(require(3)); 112 | 113 | uint32_t val = 0; 114 | uint8_t *p = reinterpret_cast(&val); 115 | 116 | for (int i = 2; i >= 0; --i) { 117 | p[i] = mData.at(0 + mPos); 118 | mPos++; 119 | } 120 | 121 | return val; 122 | } 123 | 124 | uint32_t SimpleBuffer::read4Bytes() 125 | { 126 | assert(require(4)); 127 | 128 | int32_t val = 0; 129 | uint8_t *p = reinterpret_cast(&val); 130 | 131 | for (int i = 3; i >= 0; --i) { 132 | p[i] = mData.at(0 + mPos); 133 | mPos++; 134 | } 135 | 136 | return val; 137 | } 138 | 139 | uint64_t SimpleBuffer::read8Bytes() 140 | { 141 | assert(require(8)); 142 | 143 | uint64_t val = 0; 144 | uint8_t *p = reinterpret_cast(&val); 145 | 146 | for (int i = 7; i >= 0; --i) { 147 | p[i] = mData.at(0 + mPos); 148 | mPos++; 149 | } 150 | 151 | return val; 152 | } 153 | 154 | std::string SimpleBuffer::readString(size_t len) 155 | { 156 | assert(require(len)); 157 | 158 | std::string val(reinterpret_cast(&mData.at(mPos)), len); 159 | mPos += len; 160 | 161 | return val; 162 | } 163 | 164 | void SimpleBuffer::skip(size_t size) 165 | { 166 | mPos += size; 167 | } 168 | 169 | bool SimpleBuffer::require(size_t required_size) const 170 | { 171 | assert(required_size >= 0); 172 | 173 | return required_size <= mData.size() - mPos; 174 | } 175 | 176 | bool SimpleBuffer::empty() const 177 | { 178 | return mPos >= mData.size(); 179 | } 180 | 181 | size_t SimpleBuffer::size() const 182 | { 183 | return mData.size(); 184 | } 185 | 186 | size_t SimpleBuffer::pos() const 187 | { 188 | return mPos; 189 | } 190 | 191 | uint8_t* SimpleBuffer::data() 192 | { 193 | return mData.empty() ? nullptr : &mData[0]; 194 | } 195 | 196 | uint8_t* SimpleBuffer::currentData() 197 | { 198 | return (mData.empty() || mPos >= mData.size()) ? nullptr : &mData[mPos]; 199 | } 200 | 201 | size_t SimpleBuffer::dataLeft() const 202 | { 203 | return mData.size() - mPos; 204 | } 205 | 206 | void SimpleBuffer::clear() 207 | { 208 | mPos = 0; 209 | mData.clear(); 210 | } 211 | 212 | void SimpleBuffer::setData(size_t pos, const uint8_t* data, size_t len) 213 | { 214 | if (!data) { 215 | #ifdef DEBUG 216 | std::cout << " setData error data ptr == nullpts " << std::endl; 217 | #endif 218 | return; 219 | } 220 | 221 | if (pos + len > size()) { 222 | #ifdef DEBUG 223 | std::cout << " setData error data out of bounds " << std::endl; 224 | #endif 225 | return; 226 | } 227 | 228 | std::memcpy(currentData(), data, len); 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /mpegts/simple_buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef __SIMPLE_BUFFER_H__ 2 | #define __SIMPLE_BUFFER_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace mpegts { 10 | 11 | // only support little endian 12 | class SimpleBuffer 13 | { 14 | public: 15 | SimpleBuffer(); 16 | SimpleBuffer(int32_t size, int8_t value); 17 | virtual ~SimpleBuffer(); 18 | 19 | public: 20 | void write1Byte(uint8_t val); 21 | void write2Bytes(uint16_t val); 22 | void write3Bytes(uint32_t val); 23 | void write4Bytes(uint32_t val); 24 | void write8Bytes(uint64_t val); 25 | void append(const uint8_t* bytes, size_t size); 26 | void prepend(const uint8_t* bytes, size_t size); 27 | 28 | uint8_t read1Byte(); 29 | uint16_t read2Bytes(); 30 | uint32_t read3Bytes(); 31 | uint32_t read4Bytes(); 32 | uint64_t read8Bytes(); 33 | std::string readString(size_t len); 34 | 35 | void skip(size_t size); 36 | [[nodiscard]] bool require(size_t required_size) const; 37 | [[nodiscard]] bool empty() const; 38 | [[nodiscard]] size_t size() const; 39 | [[nodiscard]] size_t pos() const; 40 | uint8_t* data(); 41 | uint8_t* currentData(); 42 | [[nodiscard]] size_t dataLeft() const; 43 | void clear(); 44 | void setData(size_t pos, const uint8_t* data, size_t len); 45 | 46 | private: 47 | std::vector mData; 48 | size_t mPos = 0; 49 | }; 50 | 51 | } 52 | #endif /* __SIMPLE_BUFFER_H__ */ 53 | -------------------------------------------------------------------------------- /mpegts/ts_packet.cpp: -------------------------------------------------------------------------------- 1 | #include "ts_packet.h" 2 | #include "simple_buffer.h" 3 | 4 | #include 5 | 6 | namespace mpegts { 7 | 8 | EsFrame::EsFrame(uint8_t streamType, uint16_t pid) : mStreamType(streamType), mPid(pid) { 9 | } 10 | 11 | bool EsFrame::empty() const { 12 | return mData->size() == 0; 13 | } 14 | 15 | void EsFrame::reset() { 16 | mPts = 0; 17 | mDts = 0; 18 | mPcr = 0; 19 | mRandomAccess = 0; 20 | mExpectedPesPacketLength = 0; 21 | mExpectedPayloadLength = 0; 22 | mCompleted = false; 23 | mBroken = false; 24 | mData = std::make_shared(); 25 | } 26 | 27 | void TsHeader::encode(SimpleBuffer& rSb) const { 28 | rSb.write1Byte(mSyncByte); 29 | 30 | uint16_t lB1b2 = mPid & 0x1FFF; 31 | lB1b2 |= (mTransportPriority << 13) & 0x2000; 32 | lB1b2 |= (mPayloadUnitStartIndicator << 14) & 0x4000; 33 | lB1b2 |= (mTransportErrorIndicator << 15) & 0x8000; 34 | rSb.write2Bytes(lB1b2); 35 | 36 | uint8_t lB3 = mContinuityCounter & 0x0F; 37 | lB3 |= (mAdaptationFieldControl << 4) & 0x30; 38 | lB3 |= (mTransportScramblingControl << 6) & 0xC0; 39 | rSb.write1Byte(lB3); 40 | } 41 | 42 | void TsHeader::decode(SimpleBuffer& rSb) { 43 | mSyncByte = rSb.read1Byte(); 44 | 45 | uint16_t lB1b2 = rSb.read2Bytes(); 46 | mPid = lB1b2 & 0x1FFF; 47 | mTransportErrorIndicator = (lB1b2 >> 13) & 0x01; 48 | mPayloadUnitStartIndicator = (lB1b2 >> 14) & 0x01; 49 | mTransportErrorIndicator = (lB1b2 >> 15) & 0x01; 50 | 51 | uint8_t lB3 = rSb.read1Byte(); 52 | mContinuityCounter = lB3 & 0x0F; 53 | mAdaptationFieldControl = (lB3 >> 4) & 0x03; 54 | mTransportScramblingControl = (lB3 >> 6) & 0x03; 55 | } 56 | 57 | bool TsHeader::hasPayload() const { 58 | return mAdaptationFieldControl == MpegTsAdaptationFieldType::mPayloadOnly || 59 | mAdaptationFieldControl == MpegTsAdaptationFieldType::mPayloadAdaptionBoth; 60 | } 61 | 62 | bool TsHeader::hasAdaptationField() const { 63 | return mAdaptationFieldControl == MpegTsAdaptationFieldType::mAdaptionOnly || 64 | mAdaptationFieldControl == MpegTsAdaptationFieldType::mPayloadAdaptionBoth; 65 | } 66 | 67 | void PATHeader::encode(SimpleBuffer& rSb) const { 68 | rSb.write1Byte(mTableId); 69 | 70 | uint16_t lB1b2 = mSectionLength & 0x0FFF; 71 | lB1b2 |= (mReserved0 << 12) & 0x3000; 72 | lB1b2 |= (mB0 << 14) & 0x4000; 73 | lB1b2 |= (mSectionSyntaxIndicator << 15) & 0x8000; 74 | rSb.write2Bytes(lB1b2); 75 | 76 | rSb.write2Bytes(mTransportStreamId); 77 | 78 | uint8_t lB5 = mCurrentNextIndicator & 0x01; 79 | lB5 |= (mVersionNumber << 1) & 0x3E; 80 | lB5 |= (mReserved1 << 6) & 0xC0; 81 | rSb.write1Byte(lB5); 82 | 83 | rSb.write1Byte(mSectionNumber); 84 | rSb.write1Byte(mLastSectionNumber); 85 | } 86 | 87 | void PATHeader::decode(SimpleBuffer& rSb) { 88 | mTableId = rSb.read1Byte(); 89 | 90 | uint16_t lB1b2 = rSb.read2Bytes(); 91 | mSectionSyntaxIndicator = (lB1b2 >> 15) & 0x01; 92 | mB0 = (lB1b2 >> 14) & 0x01; 93 | mSectionLength = lB1b2 & 0x0FFF; 94 | 95 | mTransportStreamId = rSb.read2Bytes(); 96 | 97 | uint8_t lB5 = rSb.read1Byte(); 98 | mReserved1 = (lB5 >> 6) & 0x03; 99 | mVersionNumber = (lB5 >> 1) & 0x1F; 100 | mCurrentNextIndicator = lB5 & 0x01; 101 | 102 | mSectionNumber = rSb.read1Byte(); 103 | 104 | mLastSectionNumber = rSb.read1Byte(); 105 | } 106 | 107 | void PATHeader::print(LogLevel logLevel, 108 | const std::function& streamInfoCallback) const { 109 | streamInfoCallback(logLevel, "----------PAT information----------"); 110 | streamInfoCallback(logLevel, "table_id: " + std::to_string(mTableId)); 111 | streamInfoCallback(logLevel, "section_syntax_indicator: " + std::to_string(mSectionSyntaxIndicator)); 112 | streamInfoCallback(logLevel, "b0: " + std::to_string(mB0)); 113 | streamInfoCallback(logLevel, "reserved0: " + std::to_string(mReserved0)); 114 | streamInfoCallback(logLevel, "section_length: " + std::to_string(mSectionLength)); 115 | streamInfoCallback(logLevel, "transport_stream_id: " + std::to_string(mTransportStreamId)); 116 | streamInfoCallback(logLevel, "reserved1: " + std::to_string(mReserved1)); 117 | streamInfoCallback(logLevel, "version_number: " + std::to_string(mVersionNumber)); 118 | streamInfoCallback(logLevel, "current_next_indicator: " + std::to_string(mCurrentNextIndicator)); 119 | streamInfoCallback(logLevel, "section_number: " + std::to_string(mSectionNumber)); 120 | streamInfoCallback(logLevel, "last_section_number: " + std::to_string(mLastSectionNumber)); 121 | } 122 | 123 | PMTElementInfo::PMTElementInfo(uint8_t lSt, uint16_t lPid) 124 | : mStreamType(lSt), mElementaryPid(lPid) { 125 | } 126 | 127 | void PMTElementInfo::encode(SimpleBuffer& rSb) const { 128 | rSb.write1Byte(mStreamType); 129 | 130 | uint16_t lB1b2 = mElementaryPid & 0x1FFF; 131 | lB1b2 |= (mReserved0 << 13) & 0xE000; 132 | rSb.write2Bytes(lB1b2); 133 | 134 | uint16_t lB3b4 = mEsInfoLength & 0x0FFF; 135 | lB3b4 |= (mReserved1 << 12) & 0xF000; 136 | rSb.write2Bytes(lB3b4); 137 | 138 | if (mEsInfoLength > 0) { 139 | // TODO: 140 | } 141 | } 142 | 143 | void PMTElementInfo::decode(SimpleBuffer& rSb) { 144 | mStreamType = rSb.read1Byte(); 145 | 146 | uint16_t lB1b2 = rSb.read2Bytes(); 147 | mReserved0 = (lB1b2 >> 13) & 0x07; 148 | mElementaryPid = lB1b2 & 0x1FFF; 149 | 150 | uint16_t lB3b4 = rSb.read2Bytes(); 151 | mReserved1 = (lB3b4 >> 12) & 0xF; 152 | mEsInfoLength = lB3b4 & 0xFFF; 153 | 154 | if (mEsInfoLength > 0) { 155 | mEsInfo = rSb.readString(mEsInfoLength); 156 | } 157 | } 158 | 159 | uint16_t PMTElementInfo::size() const { 160 | return 5 + mEsInfoLength; 161 | } 162 | 163 | void PMTElementInfo::print(LogLevel logLevel, 164 | const std::function& streamInfoCallback) const { 165 | streamInfoCallback(logLevel, "**********PMTElement information**********"); 166 | streamInfoCallback(logLevel, "stream_type: " + std::to_string(mStreamType)); 167 | streamInfoCallback(logLevel, "reserved0: " + std::to_string(mReserved0)); 168 | streamInfoCallback(logLevel, "elementary_PID: " + std::to_string(mElementaryPid)); 169 | streamInfoCallback(logLevel, "reserved1: " + std::to_string(mReserved1)); 170 | streamInfoCallback(logLevel, "ES_info_length: " + std::to_string(mEsInfoLength)); 171 | streamInfoCallback(logLevel, "ES_info: " + mEsInfo); 172 | } 173 | 174 | void PMTHeader::encode(SimpleBuffer& rSb) { 175 | rSb.write1Byte(mTableId); 176 | 177 | uint16_t lB1b2 = mSectionLength & 0xFFFF; 178 | lB1b2 |= (mReserved0 << 12) & 0x3000; 179 | lB1b2 |= (mB0 << 14) & 0x4000; 180 | lB1b2 |= (mSectionSyntaxIndicator << 15) & 0x8000; 181 | rSb.write2Bytes(lB1b2); 182 | 183 | rSb.write2Bytes(mProgramNumber); 184 | 185 | uint8_t lB5 = mCurrentNextIndicator & 0x01; 186 | lB5 |= (mVersionNumber << 1) & 0x3E; 187 | lB5 |= (mReserved1 << 6) & 0xC0; 188 | rSb.write1Byte(lB5); 189 | 190 | rSb.write1Byte(mSectionNumber); 191 | rSb.write1Byte(mLastSectionNumber); 192 | 193 | uint16_t lB8b9 = mPcrPid & 0x1FFF; 194 | lB8b9 |= (mReserved2 << 13) & 0xE000; 195 | rSb.write2Bytes(lB8b9); 196 | 197 | uint16_t lB10b11 = mProgramInfoLength & 0xFFF; 198 | lB10b11 |= (mReserved3 << 12) & 0xF000; 199 | rSb.write2Bytes(lB10b11); 200 | 201 | for (std::shared_ptr& mInfo : mInfos) { 202 | mInfo->encode(rSb); 203 | } 204 | } 205 | 206 | void PMTHeader::decode(SimpleBuffer& rSb) { 207 | mTableId = rSb.read1Byte(); 208 | 209 | uint16_t lB1b2 = rSb.read2Bytes(); 210 | mSectionSyntaxIndicator = (lB1b2 >> 15) & 0x01; 211 | mB0 = (lB1b2 >> 14) & 0x01; 212 | mReserved0 = (lB1b2 >> 12) & 0x03; 213 | mSectionLength = lB1b2 & 0xFFF; 214 | 215 | mProgramNumber = rSb.read2Bytes(); 216 | 217 | uint8_t lB5 = rSb.read1Byte(); 218 | mReserved1 = (lB5 >> 6) & 0x03; 219 | mVersionNumber = (lB5 >> 1) & 0x1F; 220 | mCurrentNextIndicator = lB5 & 0x01; 221 | 222 | mSectionNumber = rSb.read1Byte(); 223 | mLastSectionNumber = rSb.read1Byte(); 224 | 225 | uint16_t lB8b9 = rSb.read2Bytes(); 226 | mReserved2 = (lB8b9 >> 13) & 0x07; 227 | mPcrPid = lB8b9 & 0x1FFF; 228 | 229 | uint16_t lB10b11 = rSb.read2Bytes(); 230 | mReserved3 = (lB10b11 >> 12) & 0xF; 231 | mProgramInfoLength = lB10b11 & 0xFFF; 232 | 233 | if (mProgramInfoLength > 0) { 234 | rSb.readString(mProgramInfoLength); 235 | } 236 | 237 | int lRemainBytes = mSectionLength - 4 - 9 - mProgramInfoLength; 238 | while (lRemainBytes > 0) { 239 | std::shared_ptr element_info(new PMTElementInfo); 240 | element_info->decode(rSb); 241 | mInfos.push_back(element_info); 242 | lRemainBytes -= element_info->size(); 243 | } 244 | } 245 | 246 | uint16_t PMTHeader::size() const { 247 | uint16_t lRet = 12; 248 | for (const std::shared_ptr& mInfo : mInfos) { 249 | lRet += mInfo->size(); 250 | } 251 | 252 | return lRet; 253 | } 254 | 255 | void PMTHeader::print(LogLevel logLevel, 256 | const std::function& streamInfoCallback) { 257 | streamInfoCallback(logLevel, "----------PMT information----------"); 258 | streamInfoCallback(logLevel, "table_id: " + std::to_string(mTableId)); 259 | streamInfoCallback(logLevel, "section_syntax_indicator: " + std::to_string(mSectionSyntaxIndicator)); 260 | streamInfoCallback(logLevel, "b0: " + std::to_string(mB0)); 261 | streamInfoCallback(logLevel, "reserved0: " + std::to_string(mReserved0)); 262 | streamInfoCallback(logLevel, "section_length: " + std::to_string(mSectionLength)); 263 | streamInfoCallback(logLevel, "program_number: " + std::to_string(mProgramNumber)); 264 | streamInfoCallback(logLevel, "reserved1: " + std::to_string(mReserved1)); 265 | streamInfoCallback(logLevel, "version_number: " + std::to_string(mVersionNumber)); 266 | streamInfoCallback(logLevel, "current_next_indicator: " + std::to_string(mCurrentNextIndicator)); 267 | streamInfoCallback(logLevel, "section_number: " + std::to_string(mSectionNumber)); 268 | streamInfoCallback(logLevel, "last_section_number: " + std::to_string(mLastSectionNumber)); 269 | streamInfoCallback(logLevel, "reserved2: " + std::to_string(mReserved2)); 270 | streamInfoCallback(logLevel, "PCR_PID: " + std::to_string(mPcrPid)); 271 | streamInfoCallback(logLevel, "reserved3: " + std::to_string(mReserved3)); 272 | streamInfoCallback(logLevel, "program_info_length: " + std::to_string(mProgramInfoLength)); 273 | for (std::shared_ptr& mInfo : mInfos) { 274 | mInfo->print(logLevel, streamInfoCallback); 275 | } 276 | } 277 | 278 | void AdaptationFieldHeader::encode(SimpleBuffer& rSb) const { 279 | rSb.write1Byte(mAdaptationFieldLength); 280 | if (mAdaptationFieldLength != 0) { 281 | uint8_t lVal = mAdaptationFieldExtensionFlag & 0x01; 282 | lVal |= (mTransportPrivateDataFlag << 1) & 0x02; 283 | lVal |= (mSplicingPointFlag << 2) & 0x04; 284 | lVal |= (mOpcrFlag << 3) & 0x08; 285 | lVal |= (mPcrFlag << 4) & 0x10; 286 | lVal |= (mElementaryStreamPriorityIndicator << 5) & 0x20; 287 | lVal |= (mRandomAccessIndicator << 6) & 0x40; 288 | lVal |= (mDiscontinuityIndicator << 7) & 0x80; 289 | rSb.write1Byte(lVal); 290 | } 291 | } 292 | 293 | void AdaptationFieldHeader::decode(SimpleBuffer& rSb) { 294 | mAdaptationFieldLength = rSb.read1Byte(); 295 | if (mAdaptationFieldLength != 0) { 296 | uint8_t lVal = rSb.read1Byte(); 297 | mAdaptationFieldExtensionFlag = lVal & 0x01; 298 | mTransportPrivateDataFlag = (lVal >> 1) & 0x01; 299 | mSplicingPointFlag = (lVal >> 2) & 0x01; 300 | mOpcrFlag = (lVal >> 3) & 0x01; 301 | mPcrFlag = (lVal >> 4) & 0x01; 302 | mElementaryStreamPriorityIndicator = (lVal >> 5) & 0x01; 303 | mRandomAccessIndicator = (lVal >> 6) & 0x01; 304 | mDiscontinuityIndicator = (lVal >> 7) & 0x01; 305 | } 306 | } 307 | 308 | void PESHeader::encode(SimpleBuffer& rSb) const { 309 | uint32_t lB0b1b2b3 = (mPacketStartCode << 8) & 0xFFFFFF00; 310 | lB0b1b2b3 |= mStreamId & 0xFF; 311 | rSb.write4Bytes(lB0b1b2b3); 312 | 313 | rSb.write2Bytes(mPesPacketLength); 314 | 315 | uint8_t lB6 = mOriginalOrCopy & 0x01; 316 | lB6 |= (mCopyright << 1) & 0x02; 317 | lB6 |= (mDataAlignmentIndicator << 2) & 0x04; 318 | lB6 |= (mPesPriority << 3) & 0x08; 319 | lB6 |= (mPesScramblingControl << 4) & 0x30; 320 | lB6 |= (mMarkerBits << 6) & 0xC0; 321 | rSb.write1Byte(lB6); 322 | 323 | uint8_t lB7 = mPesExtFlag & 0x01; 324 | lB7 |= (mPesCrcFlag << 1) & 0x02; 325 | lB7 |= (mAddCopyInfoFlag << 2) & 0x04; 326 | lB7 |= (mDsmTrickModeFlag << 3) & 0x08; 327 | lB7 |= (mEsRateFlag << 4) & 0x10; 328 | lB7 |= (mEscrFlag << 5) & 0x20; 329 | lB7 |= (mPtsDtsFlags << 6) & 0xC0; 330 | rSb.write1Byte(lB7); 331 | 332 | rSb.write1Byte(mHeaderDataLength); 333 | } 334 | 335 | void PESHeader::decode(SimpleBuffer& rSb) { 336 | uint32_t lB0b1b2b3 = rSb.read4Bytes(); 337 | mPacketStartCode = (lB0b1b2b3 >> 8) & 0x00FFFFFF; 338 | mStreamId = (lB0b1b2b3) & 0xFF; 339 | 340 | mPesPacketLength = rSb.read2Bytes(); 341 | 342 | // TODO: Verify the stream_id, the bits below are not always present 343 | 344 | uint8_t lB6 = rSb.read1Byte(); 345 | mOriginalOrCopy = lB6 & 0x01; 346 | mCopyright = (lB6 >> 1) & 0x01; 347 | mDataAlignmentIndicator = (lB6 >> 2) & 0x01; 348 | mPesPriority = (lB6 >> 3) & 0x01; 349 | mPesScramblingControl = (lB6 >> 4) & 0x03; 350 | mMarkerBits = (lB6 >> 6) & 0x03; 351 | 352 | uint8_t lB7 = rSb.read1Byte(); 353 | mPesExtFlag = lB7 & 0x01; 354 | mPesCrcFlag = (lB7 >> 1) & 0x01; 355 | mAddCopyInfoFlag = (lB7 >> 2) & 0x01; 356 | mDsmTrickModeFlag = (lB7 >> 3) & 0x01; 357 | mEsRateFlag = (lB7 >> 4) & 0x01; 358 | mEscrFlag = (lB7 >> 5) & 0x01; 359 | mPtsDtsFlags = (lB7 >> 6) & 0x03; 360 | 361 | mHeaderDataLength = rSb.read1Byte(); 362 | } 363 | 364 | } 365 | -------------------------------------------------------------------------------- /mpegts/ts_packet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Prefixes used 4 | // m class member 5 | // p pointer (*) 6 | // r reference (&) 7 | // l local scope 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "common.h" 16 | 17 | namespace mpegts { 18 | 19 | static constexpr size_t kTsPacketSize_188 = 188; 20 | static constexpr uint8_t kTsPacketSyncByte = 0x47; 21 | 22 | class EsFrame final { 23 | public: 24 | EsFrame() = default; 25 | 26 | EsFrame(uint8_t streamType, uint16_t pid); 27 | 28 | ~EsFrame() = default; 29 | 30 | [[nodiscard]] bool empty() const; 31 | 32 | void reset(); 33 | 34 | std::shared_ptr mData = std::make_shared(); 35 | uint64_t mPts = 0; 36 | uint64_t mDts = 0; 37 | uint64_t mPcr = 0; 38 | uint8_t mRandomAccess = 0; 39 | uint8_t mStreamType = 0; 40 | uint8_t mStreamId = 0; 41 | uint16_t mPid = 0; 42 | uint16_t mExpectedPesPacketLength = 0; 43 | uint16_t mExpectedPayloadLength = 0; 44 | bool mCompleted = false; 45 | bool mBroken = false; 46 | }; 47 | 48 | class TsHeader final { 49 | public: 50 | TsHeader() = default; 51 | 52 | ~TsHeader() = default; 53 | 54 | void encode(SimpleBuffer& rSb) const; 55 | 56 | void decode(SimpleBuffer& rSb); 57 | 58 | [[nodiscard]] bool hasPayload() const; 59 | [[nodiscard]] bool hasAdaptationField() const; 60 | 61 | uint8_t mSyncByte = kTsPacketSyncByte; // 8 bits 62 | uint8_t mTransportErrorIndicator = 0; // 1 bit 63 | uint8_t mPayloadUnitStartIndicator = 0; // 1 bit 64 | uint8_t mTransportPriority = 0; // 1 bit 65 | uint16_t mPid = 0; // 13 bits 66 | uint8_t mTransportScramblingControl = 0; // 2 bits 67 | uint8_t mAdaptationFieldControl = 0; // 2 bits 68 | uint8_t mContinuityCounter = 0; // 4 bits 69 | }; 70 | 71 | class PATHeader final { 72 | public: 73 | PATHeader() = default; 74 | 75 | ~PATHeader() = default; 76 | 77 | void encode(SimpleBuffer& rSb) const; 78 | 79 | void decode(SimpleBuffer& rSb); 80 | 81 | void print(LogLevel logLevel, const std::function& streamInfoCallback) const; 82 | 83 | uint8_t mTableId = 0; // 8 bits 84 | uint8_t mSectionSyntaxIndicator = 0; // 1 bit 85 | uint8_t mB0 = 0; // 1 bit 86 | uint8_t mReserved0 = 0; // 2 bits 87 | uint16_t mSectionLength = 0; // 12 bits 88 | uint16_t mTransportStreamId = 0; // 16 bits 89 | uint8_t mReserved1 = 0; // 2 bits 90 | uint8_t mVersionNumber = 0; // 5 bits 91 | uint8_t mCurrentNextIndicator = 0; // 1 bit 92 | uint8_t mSectionNumber = 0; // 8 bits 93 | uint8_t mLastSectionNumber = 0; // 8 bits 94 | }; 95 | 96 | class PMTElementInfo final { 97 | public: 98 | PMTElementInfo() = default; 99 | 100 | PMTElementInfo(uint8_t lSt, uint16_t lPid); 101 | 102 | ~PMTElementInfo() = default; 103 | 104 | void encode(SimpleBuffer& rSb) const; 105 | 106 | void decode(SimpleBuffer& rSb); 107 | 108 | [[nodiscard]] uint16_t size() const; 109 | 110 | void print(LogLevel logLevel, const std::function& streamInfoCallback) const; 111 | 112 | uint8_t mStreamType = 0; // 8 bits 113 | uint8_t mReserved0 = 0x7; // 3 bits 114 | uint16_t mElementaryPid = 0; // 13 bits 115 | uint8_t mReserved1 = 0xf; // 4 bits 116 | uint16_t mEsInfoLength = 0; // 12 bits 117 | std::string mEsInfo; 118 | }; 119 | 120 | class PMTHeader final { 121 | public: 122 | PMTHeader() = default; 123 | 124 | ~PMTHeader() = default; 125 | 126 | void encode(SimpleBuffer& rSb); 127 | 128 | void decode(SimpleBuffer& rSb); 129 | 130 | [[nodiscard]] uint16_t size() const; 131 | 132 | void print(LogLevel logLevel, const std::function& streamInfoCallback); 133 | 134 | uint8_t mTableId = 0x02; // 8 bits 135 | uint8_t mSectionSyntaxIndicator = 0; // 1 bit 136 | uint8_t mB0 = 0; // 1 bit 137 | uint8_t mReserved0 = 0; // 2 bits 138 | uint16_t mSectionLength = 0; // 12 bits 139 | uint16_t mProgramNumber = 0; // 16 bits 140 | uint8_t mReserved1 = 0; // 2 bits 141 | uint8_t mVersionNumber = 0; // 5 bits 142 | uint8_t mCurrentNextIndicator = 0; // 1 bit 143 | uint8_t mSectionNumber = 0; // 8 bits 144 | uint8_t mLastSectionNumber = 0; // 8 bits 145 | uint8_t mReserved2 = 0; // 3 bits 146 | uint16_t mPcrPid = 0; // 13 bits 147 | uint8_t mReserved3 = 0; // 4 bits 148 | uint16_t mProgramInfoLength = 0; // 12 bits 149 | std::vector> mInfos; 150 | }; 151 | 152 | class AdaptationFieldHeader final { 153 | public: 154 | AdaptationFieldHeader() = default; 155 | 156 | ~AdaptationFieldHeader() = default; 157 | 158 | void encode(SimpleBuffer& rSb) const; 159 | 160 | void decode(SimpleBuffer& rSb); 161 | 162 | uint8_t mAdaptationFieldLength = 0; // 8 bits 163 | uint8_t mAdaptationFieldExtensionFlag = 0; // 1 bit 164 | uint8_t mTransportPrivateDataFlag = 0; // 1 bit 165 | uint8_t mSplicingPointFlag = 0; // 1 bit 166 | uint8_t mOpcrFlag = 0; // 1 bit 167 | uint8_t mPcrFlag = 0; // 1 bit 168 | uint8_t mElementaryStreamPriorityIndicator = 0; // 1 bit 169 | uint8_t mRandomAccessIndicator = 0; // 1 bit 170 | uint8_t mDiscontinuityIndicator = 0; // 1 bit 171 | }; 172 | 173 | class PESHeader final { 174 | public: 175 | PESHeader() = default; 176 | 177 | ~PESHeader() = default; 178 | 179 | void encode(SimpleBuffer& rSb) const; 180 | 181 | void decode(SimpleBuffer& rSb); 182 | 183 | uint32_t mPacketStartCode = 0x000001; // 24 bits 184 | uint8_t mStreamId = 0; // 8 bits 185 | uint16_t mPesPacketLength = 0; // 16 bits 186 | uint8_t mOriginalOrCopy = 0; // 1 bit 187 | uint8_t mCopyright = 0; // 1 bit 188 | uint8_t mDataAlignmentIndicator = 0; // 1 bit 189 | uint8_t mPesPriority = 0; // 1 bit 190 | uint8_t mPesScramblingControl = 0; // 2 bits 191 | uint8_t mMarkerBits = 0x02; // 2 bits 192 | uint8_t mPesExtFlag = 0; // 1 bit 193 | uint8_t mPesCrcFlag = 0; // 1 bit 194 | uint8_t mAddCopyInfoFlag = 0; // 1 bit 195 | uint8_t mDsmTrickModeFlag = 0; // 1 bit 196 | uint8_t mEsRateFlag = 0; // 1 bit 197 | uint8_t mEscrFlag = 0; // 1 bit 198 | uint8_t mPtsDtsFlags = 0; // 2 bits 199 | uint8_t mHeaderDataLength = 0; // 8 bits 200 | }; 201 | 202 | } 203 | -------------------------------------------------------------------------------- /unit_tests.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | #include 6 | #include "unit_test_1.h" 7 | #include "unit_test_2.h" 8 | #include "unit_test_3.h" 9 | #include "unit_test_4.h" 10 | #include "unit_test_5.h" 11 | #include "unit_test_6.h" 12 | #include "unit_test_7.h" 13 | #include "unit_test_8.h" 14 | #include "unit_test_9.h" 15 | 16 | int main(int argc, char *argv[]) { 17 | std::cout << "Running all unit tests." << std::endl; 18 | 19 | int returnCode = EXIT_SUCCESS; 20 | 21 | //mux/demux a vector increasing in size from 1 byte to a set size inside the unit test 22 | //please read the comment inside the unit test for more information 23 | 24 | std::cout << "Unit test1 started." << std::endl; 25 | 26 | UnitTest1 unitTest1; 27 | if (!unitTest1.runTest()) { 28 | std::cout << "Unit test 1 failed" << std::endl; 29 | returnCode = EXIT_FAILURE; 30 | } 31 | 32 | //mux/demux a vector increasing in size from 1 byte to a set size inside the unit test 33 | // Difference is that the demuxer is fed multiple ts packets to parse 34 | //please read the comment inside the unit test for more information 35 | 36 | std::cout << "Unit test2 started." << std::endl; 37 | UnitTest2 unitTest2; 38 | if (!unitTest2.runTest()) { 39 | std::cout << "Unit test 2 failed" << std::endl; 40 | returnCode = EXIT_FAILURE; 41 | } 42 | 43 | //Same as Unit Test 1 but is not embedding PCR 44 | std::cout << "Unit test3 started." << std::endl; 45 | UnitTest3 unitTest3; 46 | if (!unitTest3.runTest()) { 47 | std::cout << "Unit test 3 failed" << std::endl; 48 | returnCode = EXIT_FAILURE; 49 | } 50 | 51 | //Same as Unit Test 2 but is not embedding PCR 52 | std::cout << "Unit test4 started." << std::endl; 53 | UnitTest4 unitTest4; 54 | if (!unitTest4.runTest()) { 55 | std::cout << "Unit test 4 failed" << std::endl; 56 | returnCode = EXIT_FAILURE; 57 | } 58 | 59 | //Demultiplex a PES where the length is exactly 2 TS packets where the last TS packet is 60 | //able to carry all data meaning there is no need for a adaption field (stuffing) 61 | std::cout << "Unit test5 started." << std::endl; 62 | UnitTest5 unitTest5; 63 | if (!unitTest5.runTest()) { 64 | std::cout << "Unit test 5 failed" << std::endl; 65 | returnCode = EXIT_FAILURE; 66 | } 67 | 68 | //Check PTS/DTS/PCR values 69 | std::cout << "Unit test6 started." << std::endl; 70 | UnitTest6 unitTest6; 71 | if (!unitTest6.runTest()) { 72 | std::cout << "Unit test 6 failed" << std::endl; 73 | returnCode = EXIT_FAILURE; 74 | } 75 | 76 | //Check non 188-bytes TS packet demuxer feed 77 | std::cout << "Unit test7 started." << std::endl; 78 | UnitTest7 unitTest7; 79 | if (!unitTest7.runTest()) { 80 | std::cout << "Unit test 7 failed" << std::endl; 81 | returnCode = EXIT_FAILURE; 82 | } 83 | 84 | //Continuity counter tests 85 | std::cout << "Unit test8 started." << std::endl; 86 | UnitTest8 unitTest8; 87 | if (!unitTest8.runTest()) { 88 | std::cout << "Unit test 8 failed" << std::endl; 89 | returnCode = EXIT_FAILURE; 90 | } 91 | 92 | //TS Packet sync 93 | std::cout << "Unit test9 started." << std::endl; 94 | UnitTest9 unitTest9; 95 | if (!unitTest9.runTest()) { 96 | std::cout << "Unit test 9 failed" << std::endl; 97 | returnCode = EXIT_FAILURE; 98 | } 99 | 100 | if (returnCode == EXIT_FAILURE) { 101 | std::cout << "Unit tests failed." << std::endl; 102 | } else { 103 | std::cout << "Unit tests pass." << std::endl; 104 | } 105 | 106 | return returnCode; 107 | } 108 | 109 | -------------------------------------------------------------------------------- /unit_tests/unit_test_1.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | 6 | //This unit test is 7 | 8 | //1. Sending a vector of bytes from 1 byte to TEST_VECTOR_SIZE (4000) bytes 9 | //The vector is a linear counting uint8_t that is multiplexed and demuxed 10 | //The result is compared against the expected length and vector linearity 11 | 12 | //2. Verify the PTS and DTS values (They are set to the packet size and packet number) 13 | 14 | //3. Verify the muxer is spiting out a modulo 188 size packets 15 | 16 | //4. Every tenth frame is set to mRandomAccess. Verify the correctness after muxing demuxing 17 | 18 | //This unit test is sending each TS to the demuxer meaning 188 bytes at a time 19 | 20 | 21 | #include "unit_test_1.h" 22 | 23 | //AAC (ADTS) audio 24 | #define TYPE_AUDIO 0x0f 25 | //H.264 video 26 | #define TYPE_VIDEO 0x1b 27 | //Audio PID 28 | #define AUDIO_PID 257 29 | //Video PID 30 | #define VIDEO_PID 256 31 | //PMT PID 32 | #define PMT_PID 100 33 | 34 | //Test Vector size 35 | #define TEST_VECTOR_SIZE 4000 36 | 37 | void UnitTest1::dmxOutput(const EsFrame& esFrame) { 38 | 39 | //Is the frame correctly marked as mRandomAccess 40 | if ((mFrameCounter%10)?0: 1 != esFrame.mRandomAccess) { 41 | std::cout << "mRandomAccess indication error: " << unsigned(esFrame.mRandomAccess) << " Frame -> " << unsigned(mFrameCounter) << std::endl; 42 | mUnitTestStatus = false; 43 | } 44 | 45 | //verify expected size 46 | if (esFrame.mData->size() != mFrameCounter ) { 47 | std::cout << "Frame size missmatch " << unsigned(esFrame.mData->size()) << std::endl; 48 | mUnitTestStatus = false; 49 | } 50 | 51 | //Make sure PTS and DTS are set as expected 52 | if (esFrame.mPts != mFrameCounter || esFrame.mDts != mFrameCounter) { 53 | std::cout << "PTS - DTS" << std::endl; 54 | mUnitTestStatus = false; 55 | } 56 | 57 | uint8_t referenceVector = 0; 58 | //verify expected vector 59 | for (int lI = 0 ; lI < mFrameCounter ; lI++) { 60 | if (esFrame.mData->data()[lI] != referenceVector++ ) { 61 | std::cout << "Content missmatch for packet size -> " << unsigned(mFrameCounter) << " at byte: " << unsigned(lI); 62 | std::cout << " Expected " << unsigned(referenceVector -1 ) << " got " << (uint8_t)esFrame.mData->data()[lI] + 0 << std::endl; 63 | mUnitTestStatus = false; 64 | } 65 | } 66 | 67 | mFrameInTransit = false; 68 | 69 | } 70 | 71 | void UnitTest1::muxOutput(SimpleBuffer &rTsOutBuffer) { 72 | //Double to fail at non integer data 73 | double packets = (double) rTsOutBuffer.size() / 188.0; 74 | if (packets != (int) packets) { 75 | std::cout << "Payload not X * 188 " << std::endl; 76 | mUnitTestStatus = false; 77 | } 78 | 79 | uint8_t* lpData = rTsOutBuffer.data(); 80 | 81 | for (int lI = 0 ; lI < packets ; lI++) { 82 | SimpleBuffer lIn; 83 | lIn.append(lpData+(lI*188), 188); 84 | mDemuxer.decode(lIn); 85 | } 86 | } 87 | 88 | bool UnitTest1::runTest() { 89 | 90 | mDemuxer.esOutCallback = std::bind(&UnitTest1::dmxOutput, this, std::placeholders::_1); 91 | 92 | uint8_t testVector[TEST_VECTOR_SIZE]; 93 | std::map gStreamPidMap; 94 | gStreamPidMap[TYPE_VIDEO] = VIDEO_PID; 95 | MpegTsMuxer lMuxer(gStreamPidMap, PMT_PID, VIDEO_PID, MpegTsMuxer::MuxType::h222Type); 96 | lMuxer.tsOutCallback = std::bind(&UnitTest1::muxOutput, this, std::placeholders::_1); 97 | 98 | //Make Vector 99 | for (int x = 0; x < TEST_VECTOR_SIZE; x++) { 100 | testVector[x] = x; 101 | } 102 | 103 | //Run trough all sizes 104 | int x = 1; 105 | for (; x < TEST_VECTOR_SIZE+1; x++) { 106 | EsFrame lEsFrame; 107 | lEsFrame.mData = std::make_shared(); 108 | lEsFrame.mData->append((const uint8_t *)&testVector[0], x); 109 | lEsFrame.mPts = x; 110 | lEsFrame.mDts = x; 111 | lEsFrame.mPcr = 0; 112 | lEsFrame.mStreamType = TYPE_VIDEO; 113 | lEsFrame.mStreamId = 224; 114 | lEsFrame.mPid = VIDEO_PID; 115 | lEsFrame.mExpectedPesPacketLength = 0; 116 | lEsFrame.mRandomAccess = (x%10)?0:1; 117 | lEsFrame.mCompleted = true; 118 | 119 | mFrameInTransit = true; 120 | 121 | lMuxer.encode(lEsFrame); 122 | 123 | if (mFrameInTransit) { 124 | std::cout << "Frame " << unsigned(x) << " not muxed/demuxed corectly" << std::endl; 125 | mUnitTestStatus = false; 126 | } 127 | 128 | mFrameCounter++; 129 | } 130 | 131 | if (x != mFrameCounter) { 132 | std::cout << "mux/demux frame count mismatch " << std::endl; 133 | mUnitTestStatus = false; 134 | } 135 | 136 | return mUnitTestStatus; //True = OK 137 | } 138 | -------------------------------------------------------------------------------- /unit_tests/unit_test_1.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | #ifndef MPEGTS_UNIT_TEST_1_H 6 | #define MPEGTS_UNIT_TEST_1_H 7 | 8 | #include 9 | #include "mpegts_demuxer.h" 10 | #include "mpegts_muxer.h" 11 | 12 | using namespace mpegts; 13 | 14 | class UnitTest1 { 15 | public: 16 | bool runTest(); 17 | private: 18 | void muxOutput(SimpleBuffer &rTsOutBuffer); 19 | void dmxOutput(const EsFrame& esFrame); 20 | 21 | MpegTsDemuxer mDemuxer; 22 | int mFrameCounter = 1; 23 | bool mUnitTestStatus = true; 24 | bool mFrameInTransit = false; 25 | }; 26 | 27 | #endif //MPEGTS_UNIT_TEST_1_H 28 | -------------------------------------------------------------------------------- /unit_tests/unit_test_2.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | 6 | //This unit test is 7 | 8 | //1. Sending a vector of bytes from 1 byte to TEST_VECTOR_SIZE (4000) bytes 9 | //The vector is a linear counting uint8_t that is multiplexed and demuxed 10 | //The result is compared against the expected length and vector linearity 11 | 12 | //2. Verify the PTS and DTS values (They are set to the packet size and packet number) 13 | 14 | //3. Verify the muxer is spiting out a modulo 188 size packets 15 | 16 | //4. Every tenth frame is set to mRandomAccess. Verify the correctness after muxing demuxing 17 | 18 | //This unit test is sending a block of TS to the dmuxer 19 | 20 | 21 | #include "unit_test_2.h" 22 | 23 | //AAC (ADTS) audio 24 | #define TYPE_AUDIO 0x0f 25 | //H.264 video 26 | #define TYPE_VIDEO 0x1b 27 | //Audio PID 28 | #define AUDIO_PID 257 29 | //Video PID 30 | #define VIDEO_PID 256 31 | //PMT PID 32 | #define PMT_PID 100 33 | 34 | //Test Vector size 35 | #define TEST_VECTOR_SIZE 4000 36 | 37 | void UnitTest2::dmxOutput(const EsFrame& esFrame){ 38 | 39 | //Is the frame correctly marked as mRandomAccess 40 | if ((mFrameCounter%10)?0: 1 != esFrame.mRandomAccess) { 41 | std::cout << "mRandomAccess indication error: " << unsigned(esFrame.mRandomAccess) << " Frame -> " << unsigned(mFrameCounter) << std::endl; 42 | mUnitTestStatus = false; 43 | } 44 | 45 | //verify expected size 46 | if (esFrame.mData->size() != mFrameCounter ) { 47 | std::cout << "Frame size missmatch " << unsigned(esFrame.mData->size()) << std::endl; 48 | mUnitTestStatus = false; 49 | } 50 | 51 | //Make sure PTS and DTS are set as expected 52 | if (esFrame.mPts != mFrameCounter || esFrame.mDts != mFrameCounter) { 53 | std::cout << "PTS - DTS" << std::endl; 54 | mUnitTestStatus = false; 55 | } 56 | 57 | uint8_t referenceVector = 0; 58 | //verify expected vector 59 | for (int lI = 0 ; lI < mFrameCounter ; lI++) { 60 | if (esFrame.mData->data()[lI] != referenceVector++ ) { 61 | std::cout << "Content missmatch for packet size -> " << unsigned(mFrameCounter) << " at byte: " << unsigned(lI); 62 | std::cout << " Expected " << unsigned(referenceVector -1 ) << " got " << (uint8_t)esFrame.mData->data()[lI] + 0 << std::endl; 63 | mUnitTestStatus = false; 64 | } 65 | } 66 | 67 | mFrameInTransit = false; 68 | } 69 | 70 | void UnitTest2::muxOutput(SimpleBuffer &rTsOutBuffer) { 71 | //Double to fail at non integer data 72 | double packets = (double) rTsOutBuffer.size() / 188.0; 73 | if (packets != (int) packets) { 74 | std::cout << "Payload not X * 188 " << std::endl; 75 | mUnitTestStatus = false; 76 | } 77 | 78 | SimpleBuffer lIn; 79 | lIn.append(rTsOutBuffer.data(), rTsOutBuffer.size()); 80 | mDemuxer.decode(lIn); 81 | 82 | } 83 | 84 | bool UnitTest2::runTest() { 85 | 86 | mDemuxer.esOutCallback = std::bind(&UnitTest2::dmxOutput, this, std::placeholders::_1); 87 | 88 | uint8_t testVector[TEST_VECTOR_SIZE]; 89 | std::map gStreamPidMap; 90 | gStreamPidMap[TYPE_VIDEO] = VIDEO_PID; 91 | MpegTsMuxer lMuxer(gStreamPidMap, PMT_PID, VIDEO_PID, MpegTsMuxer::MuxType::h222Type); 92 | lMuxer.tsOutCallback = std::bind(&UnitTest2::muxOutput, this, std::placeholders::_1); 93 | 94 | //Make Vector 95 | for (int x = 0; x < TEST_VECTOR_SIZE; x++) { 96 | testVector[x] = x; 97 | } 98 | 99 | //Run trough all sizes 100 | int x = 1; 101 | for (; x < TEST_VECTOR_SIZE+1; x++) { 102 | EsFrame lEsFrame; 103 | lEsFrame.mData = std::make_shared(); 104 | lEsFrame.mData->append((const uint8_t *)&testVector[0], x); 105 | lEsFrame.mPts = x; 106 | lEsFrame.mDts = x; 107 | lEsFrame.mPcr = 0; 108 | lEsFrame.mStreamType = TYPE_VIDEO; 109 | lEsFrame.mStreamId = 224; 110 | lEsFrame.mPid = VIDEO_PID; 111 | lEsFrame.mExpectedPesPacketLength = 0; 112 | lEsFrame.mRandomAccess = (x%10)?0:1; 113 | lEsFrame.mCompleted = true; 114 | 115 | mFrameInTransit = true; 116 | 117 | lMuxer.encode(lEsFrame); 118 | 119 | if (mFrameInTransit) { 120 | std::cout << "Frame " << unsigned(x) << " not muxed/demuxed corectly" << std::endl; 121 | mUnitTestStatus = false; 122 | } 123 | 124 | mFrameCounter++; 125 | } 126 | 127 | if (x != mFrameCounter) { 128 | std::cout << "mux/demux frame count mismatch " << std::endl; 129 | mUnitTestStatus = false; 130 | } 131 | 132 | return mUnitTestStatus; //True = OK 133 | } 134 | -------------------------------------------------------------------------------- /unit_tests/unit_test_2.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | #ifndef MPEGTS_UNIT_TEST_2_H 6 | #define MPEGTS_UNIT_TEST_2_H 7 | 8 | #include 9 | #include "mpegts_demuxer.h" 10 | #include "mpegts_muxer.h" 11 | 12 | using namespace mpegts; 13 | 14 | class UnitTest2 { 15 | public: 16 | bool runTest(); 17 | private: 18 | void muxOutput(SimpleBuffer &rTsOutBuffer); 19 | void dmxOutput(const EsFrame& esFrame); 20 | 21 | MpegTsDemuxer mDemuxer; 22 | int mFrameCounter = 1; 23 | bool mFrameInTransit = false; 24 | bool mUnitTestStatus = true; 25 | }; 26 | 27 | #endif //MPEGTS_UNIT_TEST_2_H 28 | -------------------------------------------------------------------------------- /unit_tests/unit_test_3.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | 6 | //This unit test is 7 | 8 | //1. Sending a vector of bytes from 1 byte to TEST_VECTOR_SIZE (4000) bytes 9 | //The vector is a linear counting uint8_t that is multiplexed and demuxed 10 | //The result is compared against the expected length and vector linearity 11 | 12 | //2. Verify the PTS and DTS values PTS == DTS+1 in this test. 13 | 14 | //3. Verify the muxer is spiting out a modulo 188 size packets 15 | 16 | //4. Every tenth frame is set to mRandomAccess. Verify the correctness after muxing demuxing 17 | 18 | //This unit test is sending each TS to the demuxer meaning 188 bytes at a time 19 | 20 | 21 | #include "unit_test_3.h" 22 | 23 | //AAC (ADTS) audio 24 | #define TYPE_AUDIO 0x0f 25 | //H.264 video 26 | #define TYPE_VIDEO 0x1b 27 | //Audio PID 28 | #define AUDIO_PID 257 29 | //Video PID 30 | #define VIDEO_PID 256 31 | //PMT PID 32 | #define PMT_PID 100 33 | //PCR PID 34 | #define PCR_PID 300 35 | 36 | //Test Vector size 37 | #define TEST_VECTOR_SIZE 4000 38 | 39 | void UnitTest3::dmxOutput(const EsFrame &esFrame){ 40 | 41 | //Is the frame correctly marked as mRandomAccess 42 | if ((mFrameCounter%10)?0: 1 != esFrame.mRandomAccess) { 43 | std::cout << "mRandomAccess indication error: " << unsigned(esFrame.mRandomAccess) << " Frame -> " << unsigned(mFrameCounter) << std::endl; 44 | mUnitTestStatus = false; 45 | } 46 | 47 | //verify expected size 48 | if (esFrame.mData->size() != mFrameCounter ) { 49 | std::cout << "Frame size missmatch got: " << unsigned(esFrame.mData->size()) << " Expected: " << unsigned(mFrameCounter) << std::endl; 50 | mUnitTestStatus = false; 51 | } 52 | 53 | //Make sure PTS and DTS are set as expected 54 | if (esFrame.mPts != (mFrameCounter + 1) || esFrame.mDts != mFrameCounter) { 55 | std::cout << "PTS - DTS" << std::endl; 56 | mUnitTestStatus = false; 57 | } 58 | 59 | uint8_t referenceVector = 0; 60 | //verify expected vector 61 | for (int lI = 0 ; lI < mFrameCounter ; lI++) { 62 | if (esFrame.mData->data()[lI] != referenceVector++ ) { 63 | std::cout << "Content missmatch for packet size -> " << unsigned(mFrameCounter) << " at byte: " << unsigned(lI); 64 | std::cout << " Expected " << unsigned(referenceVector -1 ) << " got " << (uint8_t)esFrame.mData->data()[lI] + 0 << std::endl; 65 | mUnitTestStatus = false; 66 | } 67 | } 68 | 69 | mFrameInTransit = false; 70 | 71 | } 72 | 73 | void UnitTest3::muxOutput(SimpleBuffer &rTsOutBuffer) { 74 | //Double to fail at non integer data 75 | double packets = (double) rTsOutBuffer.size() / 188.0; 76 | if (packets != (int) packets) { 77 | std::cout << "Payload not X * 188 " << std::endl; 78 | mUnitTestStatus = false; 79 | } 80 | 81 | uint8_t* lpData = rTsOutBuffer.data(); 82 | 83 | 84 | for (int lI = 0 ; lI < packets ; lI++) { 85 | SimpleBuffer lIn; 86 | lIn.append(lpData+(lI*188), 188); 87 | mDemuxer.decode(lIn); 88 | } 89 | } 90 | 91 | bool UnitTest3::runTest() { 92 | 93 | mDemuxer.esOutCallback = std::bind(&UnitTest3::dmxOutput, this, std::placeholders::_1); 94 | 95 | uint8_t testVector[TEST_VECTOR_SIZE]; 96 | std::map gStreamPidMap; 97 | gStreamPidMap[TYPE_VIDEO] = VIDEO_PID; 98 | MpegTsMuxer lMuxer(gStreamPidMap, PMT_PID, PCR_PID, MpegTsMuxer::MuxType::h222Type); 99 | lMuxer.tsOutCallback = std::bind(&UnitTest3::muxOutput, this, std::placeholders::_1); 100 | 101 | //Make Vector 102 | for (int x = 0; x < TEST_VECTOR_SIZE; x++) { 103 | testVector[x] = x; 104 | } 105 | 106 | //Run trough all sizes 107 | int x = 1; 108 | for (; x < TEST_VECTOR_SIZE+1; x++) { 109 | EsFrame lEsFrame; 110 | lEsFrame.mData = std::make_shared(); 111 | lEsFrame.mData->append((const uint8_t *)&testVector[0], x); 112 | lEsFrame.mPts = x+1; 113 | lEsFrame.mDts = x; 114 | lEsFrame.mPcr = 0; 115 | lEsFrame.mStreamType = TYPE_VIDEO; 116 | lEsFrame.mStreamId = 224; 117 | lEsFrame.mPid = VIDEO_PID; 118 | lEsFrame.mExpectedPesPacketLength = 0; 119 | lEsFrame.mRandomAccess = (x%10)?0:1; 120 | lEsFrame.mCompleted = true; 121 | 122 | mFrameInTransit = true; 123 | 124 | lMuxer.encode(lEsFrame); 125 | 126 | if (mFrameInTransit) { 127 | std::cout << "Frame " << unsigned(x) << " not muxed/demuxed corectly" << std::endl; 128 | mUnitTestStatus = false; 129 | } 130 | 131 | mFrameCounter++; 132 | } 133 | 134 | if (x != mFrameCounter) { 135 | std::cout << "mux/demux frame count mismatch " << std::endl; 136 | mUnitTestStatus = false; 137 | } 138 | 139 | return mUnitTestStatus; //True = OK 140 | } 141 | -------------------------------------------------------------------------------- /unit_tests/unit_test_3.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | #ifndef MPEGTS_UNIT_TEST_3_H 6 | #define MPEGTS_UNIT_TEST_3_H 7 | 8 | #include 9 | #include "mpegts_demuxer.h" 10 | #include "mpegts_muxer.h" 11 | 12 | using namespace mpegts; 13 | 14 | class UnitTest3 { 15 | public: 16 | bool runTest(); 17 | private: 18 | void muxOutput(SimpleBuffer &rTsOutBuffer); 19 | void dmxOutput(const EsFrame &esFrame); 20 | 21 | MpegTsDemuxer mDemuxer; 22 | int mFrameCounter = 1; 23 | bool mUnitTestStatus = true; 24 | bool mFrameInTransit = false; 25 | }; 26 | 27 | #endif //MPEGTS_UNIT_TEST_3_H 28 | -------------------------------------------------------------------------------- /unit_tests/unit_test_4.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | 6 | //This unit test is 7 | 8 | //1. Sending a vector of bytes from 1 byte to TEST_VECTOR_SIZE (4000) bytes 9 | //The vector is a linear counting uint8_t that is multiplexed and demuxed 10 | //The result is compared against the expected length and vector linearity 11 | 12 | //2. Verify the PTS and DTS values (They are set to the packet size and packet number) 13 | 14 | //3. Verify the muxer is spiting out a modulo 188 size packets 15 | 16 | //4. Every tenth frame is set to mRandomAccess. Verify the correctness after muxing demuxing 17 | 18 | //This unit test is sending each TS to the demuxer meaning 188 bytes at a time 19 | 20 | 21 | #include "unit_test_4.h" 22 | 23 | //AAC (ADTS) audio 24 | #define TYPE_AUDIO 0x0f 25 | //H.264 video 26 | #define TYPE_VIDEO 0x1b 27 | //Audio PID 28 | #define AUDIO_PID 257 29 | //Video PID 30 | #define VIDEO_PID 256 31 | //PMT PID 32 | #define PMT_PID 100 33 | //PCR PID 34 | #define PCR_PID 300 35 | 36 | //Test Vector size 37 | #define TEST_VECTOR_SIZE 4000 38 | 39 | void UnitTest4::dmxOutput(const EsFrame& esFrame){ 40 | 41 | //Is the frame correctly marked as mRandomAccess 42 | if ((mFrameCounter%10)?0: 1 != esFrame.mRandomAccess) { 43 | std::cout << "mRandomAccess indication error: " << unsigned(esFrame.mRandomAccess) << " Frame -> " << unsigned(mFrameCounter) << std::endl; 44 | mUnitTestStatus = false; 45 | } 46 | 47 | //verify expected size 48 | if (esFrame.mData->size() != mFrameCounter ) { 49 | std::cout << "Frame size missmatch " << unsigned(esFrame.mData->size()) << std::endl; 50 | mUnitTestStatus = false; 51 | } 52 | 53 | //Make sure PTS and DTS are set as expected 54 | if (esFrame.mPts != mFrameCounter || esFrame.mDts != mFrameCounter) { 55 | std::cout << "PTS - DTS" << std::endl; 56 | mUnitTestStatus = false; 57 | } 58 | 59 | uint8_t referenceVector = 0; 60 | //verify expected vector 61 | for (int lI = 0 ; lI < mFrameCounter ; lI++) { 62 | if (esFrame.mData->data()[lI] != referenceVector++ ) { 63 | std::cout << "Content missmatch for packet size -> " << unsigned(mFrameCounter) << " at byte: " << unsigned(lI); 64 | std::cout << " Expected " << unsigned(referenceVector -1 ) << " got " << (uint8_t)esFrame.mData->data()[lI] + 0 << std::endl; 65 | mUnitTestStatus = false; 66 | } 67 | } 68 | 69 | mFrameInTransit = false; 70 | 71 | } 72 | 73 | void UnitTest4::muxOutput(SimpleBuffer &rTsOutBuffer) { 74 | //Double to fail at non integer data 75 | double packets = (double) rTsOutBuffer.size() / 188.0; 76 | if (packets != (int) packets) { 77 | std::cout << "Payload not X * 188 " << std::endl; 78 | mUnitTestStatus = false; 79 | } 80 | 81 | SimpleBuffer lIn; 82 | lIn.append(rTsOutBuffer.data(), rTsOutBuffer.size()); 83 | mDemuxer.decode(lIn); 84 | } 85 | 86 | bool UnitTest4::runTest() { 87 | 88 | mDemuxer.esOutCallback = std::bind(&UnitTest4::dmxOutput, this, std::placeholders::_1); 89 | 90 | uint8_t testVector[TEST_VECTOR_SIZE]; 91 | std::map gStreamPidMap; 92 | gStreamPidMap[TYPE_VIDEO] = VIDEO_PID; 93 | MpegTsMuxer lMuxer(gStreamPidMap, PMT_PID, PCR_PID, MpegTsMuxer::MuxType::h222Type); 94 | lMuxer.tsOutCallback = std::bind(&UnitTest4::muxOutput, this, std::placeholders::_1); 95 | 96 | //Make Vector 97 | for (int x = 0; x < TEST_VECTOR_SIZE; x++) { 98 | testVector[x] = x; 99 | } 100 | 101 | //Run trough all sizes 102 | int x = 1; 103 | for (; x < TEST_VECTOR_SIZE+1; x++) { 104 | EsFrame lEsFrame; 105 | lEsFrame.mData = std::make_shared(); 106 | lEsFrame.mData->append((const uint8_t *)&testVector[0], x); 107 | lEsFrame.mPts = x; 108 | lEsFrame.mDts = x; 109 | lEsFrame.mPcr = 0; 110 | lEsFrame.mStreamType = TYPE_VIDEO; 111 | lEsFrame.mStreamId = 224; 112 | lEsFrame.mPid = VIDEO_PID; 113 | lEsFrame.mExpectedPesPacketLength = 0; 114 | lEsFrame.mRandomAccess = (x%10)?0:1; 115 | lEsFrame.mCompleted = true; 116 | 117 | mFrameInTransit = true; 118 | 119 | lMuxer.encode(lEsFrame); 120 | 121 | if (mFrameInTransit) { 122 | std::cout << "Frame " << unsigned(x) << " not muxed/demuxed corectly" << std::endl; 123 | mUnitTestStatus = false; 124 | } 125 | 126 | mFrameCounter++; 127 | } 128 | 129 | if (x != mFrameCounter) { 130 | std::cout << "mux/demux frame count mismatch " << std::endl; 131 | mUnitTestStatus = false; 132 | } 133 | 134 | return mUnitTestStatus; //True = OK 135 | } 136 | -------------------------------------------------------------------------------- /unit_tests/unit_test_4.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | #ifndef MPEGTS_UNIT_TEST_4_H 6 | #define MPEGTS_UNIT_TEST_4_H 7 | 8 | #include 9 | #include "mpegts_demuxer.h" 10 | #include "mpegts_muxer.h" 11 | 12 | using namespace mpegts; 13 | 14 | class UnitTest4 { 15 | public: 16 | bool runTest(); 17 | private: 18 | void muxOutput(SimpleBuffer &rTsOutBuffer); 19 | void dmxOutput(const EsFrame &esFrame); 20 | 21 | MpegTsDemuxer mDemuxer; 22 | int mFrameCounter = 1; 23 | bool mUnitTestStatus = true; 24 | bool mFrameInTransit = false; 25 | }; 26 | 27 | #endif //MPEGTS_UNIT_TEST_4_H 28 | -------------------------------------------------------------------------------- /unit_tests/unit_test_5.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-02. 3 | // 4 | 5 | 6 | //This unit test is sending a PTS + PMD + a PES where the last data is exactly filling a TS frame 7 | //meaning 188 - 4 bytes. 8 | // the test is the n checking the data for the correct length and linearity of the test vector. 9 | 10 | #include 11 | 12 | #define TS_PACKET_SIZE 188 13 | 14 | uint8_t gPAT[TS_PACKET_SIZE] = 15 | {0x47, 0x40, 0x00, 0x10, 0x00, 0x00, 0xB0, 0x0D, 0x00, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x01, 0xE0, 16 | 0x64, 0xDE, 0xE0, 0xF3, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 17 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 18 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 19 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 20 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 21 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 22 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 23 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 24 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 25 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 26 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 27 | 28 | uint8_t pPMT[TS_PACKET_SIZE] = 29 | {0x47, 0x40, 0x64, 0x10, 0x00, 0x02, 0xB0, 0x17, 0x00, 0x01, 0xC1, 0x00, 0x00, 0xE1, 0x00, 0xF0, 30 | 0x00, 0x0F, 0xE1, 0x01, 0xF0, 0x00, 0x1B, 0xE1, 0x00, 0xF0, 0x00, 0xF2, 0xD9, 0x15, 0x63, 0xFF, 31 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 32 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 33 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 34 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 35 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 36 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 37 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 38 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 39 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 40 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 41 | 42 | uint8_t tsPacket1[TS_PACKET_SIZE] = 43 | {0x47, 0x41, 0x00, 0x32, 0x07, 0x10, 0x00, 0x00, 0xF9, 0x9C, 0x7E, 0x00, 0x00, 0x00, 0x01, 44 | 0xE0, 0x01, 0x62, 0x81, 0xC0, 0x0A, 0x31, 0x00, 0x09, 0x2C, 0xC1, 0x11, 0x00, 0x07, 0xE6, 45 | 0x71, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 46 | 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 47 | 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 48 | 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 49 | 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 50 | 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 51 | 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 52 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 53 | 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 54 | 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 55 | 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}; 56 | 57 | uint8_t tsPacket2[TS_PACKET_SIZE] = 58 | {0x47, 0x01, 0x00, 0x13, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 59 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 60 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 61 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 62 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 63 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 64 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 65 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 66 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 67 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 68 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 69 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 70 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}; 71 | 72 | 73 | #include "unit_test_5.h" 74 | 75 | void UnitTest5::dmxOutput(const EsFrame &esFrame){ 76 | 77 | if (mPacketLength != esFrame.mData->size()) { 78 | std::cout << "Content length mismatch. Expected:" << unsigned(mPacketLength) << " bytes" << std::endl; 79 | } 80 | 81 | uint8_t referenceVector = 0; 82 | //verify expected vector 83 | for (int lI = 0 ; lI < esFrame.mData->size() ; lI++) { 84 | if (esFrame.mData->data()[lI] != referenceVector++ ) { 85 | std::cout << "Content mismatch. Expected:" << unsigned(lI) << " Got: " << (uint8_t)esFrame.mData->data()[lI] + 0 << std::endl; 86 | mUnitTestStatus = false; 87 | } 88 | } 89 | 90 | mFrameInTransit = false; 91 | 92 | } 93 | 94 | 95 | bool UnitTest5::runTest() { 96 | 97 | MpegTsDemuxer lDemuxer; 98 | lDemuxer.esOutCallback = std::bind(&UnitTest5::dmxOutput, this, std::placeholders::_1); 99 | 100 | //Prepare vector 101 | uint8_t vectorCounter = 0; 102 | for (int x = 31 ; x < TS_PACKET_SIZE ; x++) { 103 | tsPacket1[x]=vectorCounter++; 104 | mPacketLength++; 105 | } 106 | for (int x = 4 ; x < TS_PACKET_SIZE ; x++) { 107 | tsPacket2[x]=vectorCounter++; 108 | mPacketLength++; 109 | } 110 | 111 | SimpleBuffer lIn; 112 | //Append pat 113 | lIn.append(&gPAT[0], 188); 114 | 115 | //Append pmt 116 | lIn.append(&pPMT[0], 188); 117 | 118 | //Append ts packet 1 119 | lIn.append(&tsPacket1[0], 188); 120 | 121 | //Append ts packet 2 122 | lIn.append(&tsPacket2[0], 188); 123 | 124 | mFrameInTransit = true; 125 | lDemuxer.decode(lIn); 126 | 127 | if (mFrameInTransit) { 128 | std::cout << "Frame not muxed/demuxed corectly" << std::endl; 129 | mUnitTestStatus = false; 130 | } 131 | 132 | return mUnitTestStatus; //True = OK 133 | } 134 | -------------------------------------------------------------------------------- /unit_tests/unit_test_5.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | #ifndef MPEGTS_UNIT_TEST_5_H 6 | #define MPEGTS_UNIT_TEST_5_H 7 | 8 | #include 9 | #include "mpegts_demuxer.h" 10 | #include "mpegts_muxer.h" 11 | 12 | using namespace mpegts; 13 | 14 | class UnitTest5 { 15 | public: 16 | bool runTest(); 17 | 18 | void dmxOutput(const EsFrame &esFrame); 19 | bool mFrameInTransit = false; 20 | bool mUnitTestStatus = true; 21 | int mPacketLength = 0; 22 | 23 | }; 24 | 25 | #endif //MPEGTS_UNIT_TEST_5_H 26 | -------------------------------------------------------------------------------- /unit_tests/unit_test_6.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | 6 | //This unit test is testing the PTS TDS and PCR values after mux and demux 7 | 8 | //PTS only 9 | //PTS + DTS 10 | //PTS + DTS + OOB (meaning separate packets) PCR 11 | //PTS + DTS + PCR in the video pid (PES packet) 12 | 13 | #include "unit_test_6.h" 14 | 15 | //AAC (ADTS) audio 16 | #define TYPE_AUDIO 0x0f 17 | //H.264 video 18 | #define TYPE_VIDEO 0x1b 19 | //Audio PID 20 | #define AUDIO_PID 257 21 | //Video PID 22 | #define VIDEO_PID 256 23 | //PMT PID 24 | #define PMT_PID 100 25 | //PCR PID 26 | #define PCR_PID 300 27 | 28 | //Test Vector size 29 | #define TEST_VECTOR_SIZE 100 30 | 31 | #define NUM_FRAMES_LOOP 2000 32 | 33 | void UnitTest6::dmxOutputPcr(uint64_t lPcr) { 34 | if ((mDts + mPts + mPcr) != lPcr) { 35 | std::cout << "PCR mismatch." << std::endl; 36 | mUnitTestStatus = false; 37 | } 38 | } 39 | 40 | void UnitTest6::dmxOutputPtsDts(const EsFrame &esFrame) { 41 | if (mPts != esFrame.mPts) { 42 | std::cout << "PTS mismatch." << std::endl; 43 | mUnitTestStatus = false; 44 | } 45 | 46 | if (mDts != esFrame.mDts) { 47 | std::cout << "DTS mismatch." << std::endl; 48 | mUnitTestStatus = false; 49 | } 50 | 51 | uint8_t referenceVector = 0; 52 | //verify expected vector 53 | for (int lI = 0 ; lI < TEST_VECTOR_SIZE ; lI++) { 54 | if (esFrame.mData->data()[lI] != referenceVector++ ) { 55 | std::cout << "Content missmatch." << std::endl; 56 | mUnitTestStatus = false; 57 | } 58 | } 59 | 60 | mFrameInTransit = false; 61 | } 62 | 63 | void UnitTest6::dmxOutputPts(const EsFrame &esFrame){ 64 | 65 | if (mPts != esFrame.mPts) { 66 | std::cout << "PTS missmatch." << std::endl; 67 | } 68 | 69 | uint8_t referenceVector = 0; 70 | //verify expected vector 71 | for (int lI = 0 ; lI < TEST_VECTOR_SIZE ; lI++) { 72 | if (esFrame.mData->data()[lI] != referenceVector++ ) { 73 | std::cout << "Content missmatch." << std::endl; 74 | mUnitTestStatus = false; 75 | } 76 | } 77 | 78 | mFrameInTransit = false; 79 | 80 | } 81 | 82 | void UnitTest6::muxOutput(SimpleBuffer &rTsOutBuffer) { 83 | //Double to fail at non integer data 84 | double packets = (double) rTsOutBuffer.size() / 188.0; 85 | if (packets != (int) packets) { 86 | std::cout << "Payload not X * 188 " << std::endl; 87 | mUnitTestStatus = false; 88 | } 89 | 90 | uint8_t* lpData = rTsOutBuffer.data(); 91 | 92 | for (int lI = 0 ; lI < packets ; lI++) { 93 | SimpleBuffer lIn; 94 | lIn.append(lpData+(lI*188), 188); 95 | mDemuxer.decode(lIn); 96 | } 97 | } 98 | 99 | bool UnitTest6::runTest() { 100 | 101 | mDemuxer.esOutCallback = std::bind(&UnitTest6::dmxOutputPts, this, std::placeholders::_1); 102 | 103 | uint8_t testVector[TEST_VECTOR_SIZE]; 104 | std::map gStreamPidMap; 105 | gStreamPidMap[TYPE_VIDEO] = VIDEO_PID; 106 | mpMuxer = new MpegTsMuxer(gStreamPidMap, PMT_PID, PCR_PID, MpegTsMuxer::MuxType::h222Type); 107 | mpMuxer->tsOutCallback = std::bind(&UnitTest6::muxOutput, this, std::placeholders::_1); 108 | 109 | //Make Vector 110 | for (int x = 0; x < TEST_VECTOR_SIZE; x++) { 111 | testVector[x] = x; 112 | } 113 | 114 | mPts = 0; 115 | mDts = 0; 116 | mPcr = 0; 117 | 118 | //Send frames only containing PTS 119 | for (int x = 1; x < NUM_FRAMES_LOOP; x++) { 120 | EsFrame lEsFrame; 121 | lEsFrame.mData = std::make_shared(); 122 | lEsFrame.mData->append((const uint8_t *)&testVector[0], TEST_VECTOR_SIZE); 123 | lEsFrame.mPts = mPts; 124 | lEsFrame.mDts = mPts; 125 | lEsFrame.mPcr = 0; 126 | lEsFrame.mStreamType = TYPE_VIDEO; 127 | lEsFrame.mStreamId = 224; 128 | lEsFrame.mPid = VIDEO_PID; 129 | lEsFrame.mExpectedPesPacketLength = 0; 130 | lEsFrame.mRandomAccess = 1; 131 | lEsFrame.mCompleted = true; 132 | mFrameInTransit = true; 133 | mpMuxer->encode(lEsFrame); 134 | if (mFrameInTransit) { 135 | std::cout << "Frame " << unsigned(x) << " not muxed/demuxed corectly" << std::endl; 136 | mUnitTestStatus = false; 137 | } 138 | mPts += 1920; 139 | } 140 | 141 | //Flip the callback to analyze DTS also 142 | mDemuxer.esOutCallback = std::bind(&UnitTest6::dmxOutputPtsDts, this, std::placeholders::_1); 143 | 144 | //Send frames containing PTS / DTS 145 | for (int x = 1; x < NUM_FRAMES_LOOP; x++) { 146 | EsFrame lEsFrame; 147 | lEsFrame.mData = std::make_shared(); 148 | lEsFrame.mData->append((const uint8_t *)&testVector[0], TEST_VECTOR_SIZE); 149 | lEsFrame.mPts = mPts; 150 | lEsFrame.mDts = mDts; 151 | lEsFrame.mPcr = 0; 152 | lEsFrame.mStreamType = TYPE_VIDEO; 153 | lEsFrame.mStreamId = 224; 154 | lEsFrame.mPid = VIDEO_PID; 155 | lEsFrame.mExpectedPesPacketLength = 0; 156 | lEsFrame.mRandomAccess = 1; 157 | lEsFrame.mCompleted = true; 158 | mFrameInTransit = true; 159 | mpMuxer->encode(lEsFrame); 160 | if (mFrameInTransit) { 161 | std::cout << "Frame " << unsigned(x) << " not muxed/demuxed corectly" << std::endl; 162 | mUnitTestStatus = false; 163 | } 164 | mPts += 1111; 165 | mDts += 2920; 166 | } 167 | 168 | 169 | mDemuxer.pcrOutCallback = std::bind(&UnitTest6::dmxOutputPcr, this, std::placeholders::_1); 170 | 171 | //Send frames containing PTS / DTS then also send out of band PCR 172 | for (int x = 1; x < NUM_FRAMES_LOOP; x++) { 173 | EsFrame lEsFrame; 174 | lEsFrame.mData = std::make_shared(); 175 | lEsFrame.mData->append((const uint8_t *)&testVector[0], TEST_VECTOR_SIZE); 176 | lEsFrame.mPts = mPts; 177 | lEsFrame.mDts = mDts; 178 | lEsFrame.mPcr = 0; 179 | lEsFrame.mStreamType = TYPE_VIDEO; 180 | lEsFrame.mStreamId = 224; 181 | lEsFrame.mPid = VIDEO_PID; 182 | lEsFrame.mExpectedPesPacketLength = 0; 183 | lEsFrame.mRandomAccess = 1; 184 | lEsFrame.mCompleted = true; 185 | mFrameInTransit = true; 186 | mpMuxer->encode(lEsFrame); 187 | mpMuxer->createPcr(mPts+mDts+mPcr); 188 | if (mFrameInTransit) { 189 | std::cout << "Frame " << unsigned(x) << " not muxed/demuxed corectly" << std::endl; 190 | mUnitTestStatus = false; 191 | } 192 | mPts += 3920; 193 | mDts += 1923; 194 | mPcr += 1; 195 | } 196 | 197 | //Here we switch the PCR to be embedded into the data.. 198 | 199 | //So we delete the old muxer and create a new one using the video pid as PCR pid 200 | //That means that the PCR will be taken from the Elementary stream data. 201 | delete mpMuxer; 202 | mpMuxer = new MpegTsMuxer(gStreamPidMap, PMT_PID, VIDEO_PID, MpegTsMuxer::MuxType::h222Type); 203 | mpMuxer->tsOutCallback = std::bind(&UnitTest6::muxOutput, this, std::placeholders::_1); 204 | 205 | //Send frames containing PTS / DTS then also send in band PCR 206 | for (int x = 1; x < NUM_FRAMES_LOOP; x++) { 207 | EsFrame lEsFrame; 208 | lEsFrame.mData = std::make_shared(); 209 | lEsFrame.mData->append((const uint8_t *)&testVector[0], TEST_VECTOR_SIZE); 210 | lEsFrame.mPts = mPts; 211 | lEsFrame.mDts = mDts; 212 | lEsFrame.mPcr = mPts+mDts+mPcr; 213 | lEsFrame.mStreamType = TYPE_VIDEO; 214 | lEsFrame.mStreamId = 224; 215 | lEsFrame.mPid = VIDEO_PID; 216 | lEsFrame.mExpectedPesPacketLength = 0; 217 | lEsFrame.mRandomAccess = 1; 218 | lEsFrame.mCompleted = true; 219 | mFrameInTransit = true; 220 | mpMuxer->encode(lEsFrame); 221 | if (mFrameInTransit) { 222 | std::cout << "Frame " << unsigned(x) << " not muxed/demuxed corectly" << std::endl; 223 | mUnitTestStatus = false; 224 | } 225 | mPts += 1234; 226 | mDts += 4321; 227 | mPcr += 1; 228 | } 229 | 230 | delete mpMuxer; 231 | return mUnitTestStatus; //True = OK 232 | } 233 | -------------------------------------------------------------------------------- /unit_tests/unit_test_6.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-01. 3 | // 4 | 5 | #ifndef MPEGTS_UNIT_TEST_6_H 6 | #define MPEGTS_UNIT_TEST_6_H 7 | 8 | #include 9 | #include "mpegts_demuxer.h" 10 | #include "mpegts_muxer.h" 11 | 12 | using namespace mpegts; 13 | 14 | class UnitTest6 { 15 | public: 16 | bool runTest(); 17 | private: 18 | void muxOutput(SimpleBuffer &rTsOutBuffer); 19 | void dmxOutputPts(const EsFrame &esFrame); 20 | void dmxOutputPtsDts(const EsFrame &esFrame); 21 | void dmxOutputPcr(uint64_t lPcr); 22 | 23 | MpegTsDemuxer mDemuxer; 24 | MpegTsMuxer *mpMuxer = nullptr; 25 | int mFrameCounter = 1; 26 | bool mUnitTestStatus = true; 27 | bool mFrameInTransit = false; 28 | uint64_t mPts = 0; 29 | uint64_t mDts = 0; 30 | uint64_t mPcr = 0; 31 | }; 32 | 33 | #endif //MPEGTS_UNIT_TEST_6_H 34 | -------------------------------------------------------------------------------- /unit_tests/unit_test_7.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-30. 3 | // 4 | 5 | 6 | //This unit test is testing the demuxers ability to demux non ts size inputs 7 | 8 | #include "unit_test_7.h" 9 | 10 | void UnitTest7::dmxOutputPcr(uint64_t lPcr) { 11 | } 12 | 13 | void UnitTest7::dmxOutput(const EsFrame &esFrame){ 14 | mFrameCounter ++; 15 | mUnitTestStatus = true; 16 | } 17 | 18 | bool UnitTest7::runTest() { 19 | 20 | std::random_device dev; 21 | std::mt19937 rng(dev()); 22 | std::uniform_int_distribution lRandGenerator(1,300); // distribution in range [1, 300] 23 | 24 | mDemuxer.esOutCallback = std::bind(&UnitTest7::dmxOutput, this, std::placeholders::_1); 25 | mDemuxer.pcrOutCallback = std::bind(&UnitTest7::dmxOutputPcr, this, std::placeholders::_1); 26 | 27 | std::ifstream lFile("bars.ts", std::ios::binary | std::ios::in); 28 | if (!lFile.is_open()) { 29 | std::cout << "Failed to open bars.ts - please create it using generate_bars.sh" << std::endl; 30 | mUnitTestStatus = false; 31 | return mUnitTestStatus; 32 | } 33 | 34 | uint8_t lPacket[300] = {0}; 35 | SimpleBuffer lIn; 36 | 37 | while (!lFile.eof()) { 38 | uint32_t lRandSize = lRandGenerator(rng); 39 | lFile.read((char*)&lPacket[0], lRandSize); 40 | uint32_t lBytesRead = lFile.gcount(); 41 | if (lBytesRead) { 42 | lIn.append(lPacket, lBytesRead); 43 | mDemuxer.decode(lIn); 44 | } 45 | } 46 | lFile.close(); 47 | 48 | if (mFrameCounter != 1337) { 49 | mUnitTestStatus = false; 50 | } 51 | 52 | return mUnitTestStatus; //True = OK 53 | } 54 | -------------------------------------------------------------------------------- /unit_tests/unit_test_7.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2020-07-30. 3 | // 4 | 5 | #ifndef MPEGTS_UNIT_TEST_7_H 6 | #define MPEGTS_UNIT_TEST_7_H 7 | 8 | #include 9 | #include 10 | #include "mpegts_demuxer.h" 11 | 12 | using namespace mpegts; 13 | 14 | class UnitTest7 { 15 | public: 16 | bool runTest(); 17 | private: 18 | void dmxOutput(const EsFrame &esFrame); 19 | void dmxOutputPcr(uint64_t lPcr); 20 | 21 | MpegTsDemuxer mDemuxer; 22 | 23 | int mFrameCounter = 0; 24 | bool mUnitTestStatus = false; 25 | bool mFrameInTransit = false; 26 | 27 | }; 28 | 29 | #endif //MPEGTS_UNIT_TEST_7_H 30 | -------------------------------------------------------------------------------- /unit_tests/unit_test_8.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "unit_test_8.h" 4 | #include "unit_test_8_data.h" 5 | 6 | #include "common.h" 7 | #include "mpegts_demuxer.h" 8 | #include "simple_buffer.h" 9 | #include "ts_packet.h" 10 | 11 | using namespace mpegts; 12 | 13 | bool UnitTest8::runTest() { 14 | bool testStatus = true; 15 | 16 | // Map with the number of cc errors per PID 17 | std::map ccErrors; 18 | 19 | // cc error callback 20 | auto ccErrorCallback = [&](uint16_t pid, uint8_t expectedCC, uint8_t actualCC) { 21 | auto it = ccErrors.find(pid); 22 | if (it == ccErrors.end()) { 23 | ccErrors[pid] = 0; 24 | } 25 | ccErrors[pid]++; 26 | }; 27 | 28 | auto printCcErrors = [&]() { 29 | for (auto it : ccErrors) { 30 | std::cout << "PID: " << int(it.first) << " CC errors: " << int(it.second) << std::endl; 31 | } 32 | }; 33 | 34 | // Decode a sequence without any cc errors 35 | { 36 | MpegTsDemuxer dmx; 37 | dmx.ccErrorCallback = ccErrorCallback; 38 | 39 | SimpleBuffer inputBuffer; 40 | inputBuffer.append(ccTestData, ccTestDataLength); 41 | 42 | dmx.decode(inputBuffer); 43 | 44 | if (ccErrors.size() != 0) { 45 | std::cout << "No CC errors were expected but the following exist: " << std::endl; 46 | printCcErrors(); 47 | testStatus = false; 48 | } 49 | 50 | ccErrors.clear(); 51 | } 52 | 53 | // Decode a sequence with every other video packet removed 54 | { 55 | MpegTsDemuxer dmx; 56 | dmx.ccErrorCallback = ccErrorCallback; 57 | 58 | SimpleBuffer inputBuffer; 59 | 60 | // Add PAT and PMT 61 | inputBuffer.append(ccTestData, 2 * 188); 62 | 63 | uint32_t packetIndex = 2; 64 | while (packetIndex * 188 < ccTestDataLength) { 65 | if (packetIndex & 1) { 66 | inputBuffer.append(ccTestData + packetIndex * 188, 188); 67 | } 68 | packetIndex++; 69 | } 70 | 71 | dmx.decode(inputBuffer); 72 | 73 | if (ccErrors.size() == 0) { 74 | std::cout << "CC errors were expected but none were reported" << std::endl; 75 | testStatus = false; 76 | } else if (ccErrors[256] != 8) { 77 | std::cout << "Expected 8 CC errors on PID 256" << std::endl; 78 | std::cout << "Reported CC errors:" << std::endl; 79 | testStatus = false; 80 | printCcErrors(); 81 | } 82 | 83 | ccErrors.clear(); 84 | } 85 | 86 | 87 | return testStatus; 88 | } 89 | -------------------------------------------------------------------------------- /unit_tests/unit_test_8.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class UnitTest8 { 6 | public: 7 | bool runTest(); 8 | }; 9 | -------------------------------------------------------------------------------- /unit_tests/unit_test_8_data.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 20 TS packets without CC errors 4 | // PID order: 0 (PAT), 4096 (PMT), 256, 256, 256, 256, ..., 256 5 | const uint8_t ccTestData[] = { 6 | 0x47, 0x40, 0x00, 0x10, 0x00, 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 7 | 0x00, 0x00, 0x01, 0xf0, 0x00, 0x2a, 0xb1, 0x04, 0xb2, 0xff, 0xff, 0xff, 8 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 9 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 10 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 11 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 12 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 13 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 14 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 15 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 16 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 17 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 18 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 19 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 20 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 21 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x47, 0x50, 0x00, 0x10, 22 | 0x00, 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 23 | 0x00, 0x1b, 0xe1, 0x00, 0xf0, 0x00, 0x0f, 0xe1, 0x01, 0xf0, 0x00, 0x2f, 24 | 0x44, 0xb9, 0x9b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 25 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 26 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 27 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 28 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 29 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 30 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 31 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 32 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 33 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 34 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 35 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 36 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 37 | 0xff, 0xff, 0xff, 0xff, 0x47, 0x41, 0x00, 0x30, 0x07, 0x50, 0x00, 0x00, 38 | 0x7b, 0x0c, 0x7e, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x80, 0xc0, 39 | 0x0a, 0x31, 0x00, 0x07, 0xf4, 0x81, 0x11, 0x00, 0x07, 0xd8, 0x61, 0x00, 40 | 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 41 | 0x20, 0xac, 0xd9, 0x40, 0x50, 0x05, 0xbb, 0x01, 0x10, 0x00, 0x00, 0x03, 42 | 0x00, 0x10, 0x00, 0x00, 0x06, 0x4e, 0x02, 0x00, 0x0f, 0x42, 0x40, 0x00, 43 | 0x5b, 0x8d, 0xe6, 0xb3, 0x80, 0x78, 0xc1, 0x8c, 0xb0, 0x00, 0x00, 0x00, 44 | 0x01, 0x68, 0xeb, 0xec, 0xb2, 0x2c, 0x00, 0x00, 0x01, 0x06, 0x00, 0x06, 45 | 0x8e, 0xd4, 0xd8, 0x1a, 0x5e, 0xc0, 0x80, 0x00, 0x00, 0x01, 0x06, 0x05, 46 | 0xff, 0xff, 0xf0, 0xdc, 0x45, 0xe9, 0xbd, 0xe6, 0xd9, 0x48, 0xb7, 0x96, 47 | 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef, 0x78, 0x32, 0x36, 0x34, 0x20, 48 | 0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x20, 0x31, 0x36, 0x33, 0x20, 0x72, 49 | 0x33, 0x30, 0x36, 0x30, 0x20, 0x35, 0x64, 0x62, 0x36, 0x61, 0x61, 0x36, 50 | 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34, 0x2f, 0x4d, 0x50, 0x45, 51 | 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20, 0x63, 0x6f, 0x64, 0x65, 52 | 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x6c, 0x65, 0x66, 0x74, 53 | 0x47, 0x01, 0x00, 0x11, 0x20, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x30, 54 | 0x32, 0x31, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 55 | 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e, 56 | 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74, 57 | 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 58 | 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x31, 0x20, 0x72, 0x65, 59 | 0x66, 0x3d, 0x33, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, 60 | 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 61 | 0x65, 0x3d, 0x30, 0x78, 0x33, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x33, 0x20, 62 | 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 63 | 0x3d, 0x37, 0x20, 0x70, 0x73, 0x79, 0x3d, 0x31, 0x20, 0x70, 0x73, 0x79, 64 | 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x3a, 0x30, 0x2e, 0x30, 65 | 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x66, 0x3d, 66 | 0x31, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x31, 67 | 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x6d, 0x65, 0x3d, 68 | 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69, 0x47, 0x01, 0x00, 0x12, 69 | 0x73, 0x3d, 0x31, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x31, 70 | 0x20, 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a, 71 | 0x6f, 0x6e, 0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x66, 0x61, 72 | 0x73, 0x74, 0x5f, 0x70, 0x73, 0x6b, 0x69, 0x70, 0x3d, 0x31, 0x20, 0x63, 73 | 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 74 | 0x73, 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 75 | 0x64, 0x73, 0x3d, 0x32, 0x32, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 76 | 0x65, 0x61, 0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 77 | 0x33, 0x20, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x68, 0x72, 78 | 0x65, 0x61, 0x64, 0x73, 0x3d, 0x30, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, 79 | 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x69, 80 | 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x3d, 0x30, 0x20, 81 | 0x62, 0x6c, 0x75, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 82 | 0x74, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 83 | 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 84 | 0x62, 0x66, 0x72, 0x61, 0x47, 0x01, 0x00, 0x13, 0x6d, 0x65, 0x73, 0x3d, 85 | 0x33, 0x20, 0x62, 0x5f, 0x70, 0x79, 0x72, 0x61, 0x6d, 0x69, 0x64, 0x3d, 86 | 0x32, 0x20, 0x62, 0x5f, 0x61, 0x64, 0x61, 0x70, 0x74, 0x3d, 0x31, 0x20, 87 | 0x62, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x20, 0x64, 0x69, 0x72, 88 | 0x65, 0x63, 0x74, 0x3d, 0x31, 0x20, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 89 | 0x62, 0x3d, 0x31, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x67, 0x6f, 0x70, 90 | 0x3d, 0x30, 0x20, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x70, 0x3d, 0x32, 91 | 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x34, 0x38, 0x20, 0x6b, 92 | 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d, 0x32, 0x35, 93 | 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, 0x30, 0x20, 94 | 0x69, 0x6e, 0x74, 0x72, 0x61, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 95 | 0x68, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 96 | 0x68, 0x65, 0x61, 0x64, 0x3d, 0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x63, 97 | 0x62, 0x72, 0x20, 0x6d, 0x62, 0x74, 0x72, 0x65, 0x65, 0x3d, 0x31, 0x20, 98 | 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x30, 0x30, 0x30, 99 | 0x20, 0x72, 0x61, 0x74, 0x65, 0x74, 0x6f, 0x6c, 0x3d, 0x31, 0x2e, 0x30, 100 | 0x47, 0x01, 0x00, 0x14, 0x20, 0x71, 0x63, 0x6f, 0x6d, 0x70, 0x3d, 0x30, 101 | 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d, 0x30, 0x20, 102 | 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x73, 103 | 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x76, 0x62, 0x76, 0x5f, 0x6d, 0x61, 104 | 0x78, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x30, 0x30, 0x30, 0x20, 0x76, 105 | 0x62, 0x76, 0x5f, 0x62, 0x75, 0x66, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x31, 106 | 0x35, 0x30, 0x30, 0x20, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x72, 0x64, 0x3d, 107 | 0x63, 0x62, 0x72, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x65, 0x72, 0x3d, 0x31, 108 | 0x20, 0x69, 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 109 | 0x34, 0x30, 0x20, 0x61, 0x71, 0x3d, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, 110 | 0x00, 0x80, 0x00, 0x00, 0x01, 0x06, 0x01, 0x03, 0x00, 0x00, 0x48, 0x80, 111 | 0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x00, 0x14, 0xff, 0xfe, 0xf7, 0x88, 112 | 0x1f, 0x32, 0xca, 0xca, 0xc4, 0xeb, 0x5d, 0xc8, 0x47, 0x9e, 0xb4, 0xba, 113 | 0xb4, 0x7d, 0x43, 0xb9, 0x19, 0xc4, 0xff, 0xc0, 0x02, 0x0f, 0x94, 0x0c, 114 | 0x02, 0x9d, 0x40, 0x7c, 0xdd, 0xa1, 0xc4, 0x32, 0x11, 0x46, 0x88, 0xd8, 115 | 0x45, 0xe6, 0x40, 0x00, 0x75, 0xb4, 0xc3, 0xa9, 0x47, 0x01, 0x00, 0x15, 116 | 0xb4, 0x4d, 0x94, 0xc0, 0xa8, 0xcc, 0x98, 0xc3, 0x00, 0x00, 0x74, 0x5f, 117 | 0x91, 0xb1, 0xc0, 0x80, 0xab, 0x45, 0xfd, 0x5a, 0x7f, 0x31, 0x1d, 0x70, 118 | 0x20, 0x4f, 0x5d, 0x78, 0x1c, 0x6d, 0x73, 0x89, 0x0d, 0xc2, 0xc2, 0xa2, 119 | 0xf6, 0xad, 0xa1, 0x45, 0x74, 0x85, 0x11, 0x52, 0x51, 0x3b, 0xd9, 0x5f, 120 | 0x60, 0x4e, 0x77, 0xe8, 0xd6, 0x4c, 0x87, 0x38, 0x38, 0xf7, 0x6d, 0xfc, 121 | 0xba, 0x3f, 0xfb, 0x83, 0x39, 0xf7, 0x76, 0x78, 0x76, 0x28, 0x00, 0x93, 122 | 0x25, 0x82, 0xc2, 0x21, 0x10, 0xd1, 0x70, 0xd4, 0xc6, 0xca, 0xfc, 0x74, 123 | 0xd7, 0x0f, 0xe4, 0xa0, 0x12, 0x0a, 0xf1, 0x7f, 0xc6, 0x41, 0x02, 0x43, 124 | 0xf9, 0x0f, 0xe4, 0x93, 0xbb, 0x87, 0xf2, 0x1f, 0xc9, 0x1a, 0xf5, 0x0f, 125 | 0xe4, 0x3f, 0x92, 0x20, 0xfd, 0xff, 0x17, 0xfc, 0x62, 0xfe, 0xd8, 0x7f, 126 | 0x21, 0xfc, 0x8f, 0xaf, 0x61, 0xfc, 0x87, 0xf2, 0x3b, 0xfb, 0x87, 0xf2, 127 | 0x1f, 0xc8, 0xeb, 0xd2, 0x1f, 0xc8, 0x7f, 0x23, 0x80, 0x48, 0x7f, 0x21, 128 | 0xfc, 0x8e, 0x01, 0x21, 0xfc, 0x87, 0xf2, 0x38, 0x04, 0x87, 0xf2, 0x1f, 129 | 0xc8, 0x28, 0x10, 0xaf, 0x17, 0xfc, 0x5e, 0x2d, 0x70, 0xfe, 0x43, 0xf9, 130 | 0x05, 0x02, 0x15, 0xe2, 0xff, 0x8b, 0xc5, 0xca, 0x1f, 0xc8, 0x7f, 0x20, 131 | 0xa7, 0x42, 0xbc, 0x5f, 0x47, 0x01, 0x00, 0x16, 0xf1, 0x78, 0xdd, 0xbf, 132 | 0xe2, 0xff, 0x8b, 0xc6, 0xcd, 0xff, 0x17, 0xfc, 0x5e, 0x33, 0xef, 0xf8, 133 | 0xbf, 0xe2, 0xf1, 0xbf, 0x7f, 0xc5, 0xff, 0x19, 0x7e, 0xb7, 0xf0, 0x66, 134 | 0xcf, 0x19, 0x8b, 0x32, 0x0c, 0xbd, 0x74, 0xaa, 0xaf, 0x7c, 0x3b, 0x83, 135 | 0x28, 0x14, 0xe1, 0x4d, 0x30, 0x81, 0xc7, 0x66, 0x4b, 0xb6, 0x49, 0xc9, 136 | 0x4c, 0xb7, 0xd2, 0x56, 0x04, 0x31, 0x26, 0xb9, 0xe2, 0xf1, 0xc6, 0x72, 137 | 0x52, 0x7d, 0xb9, 0xb1, 0x9d, 0x64, 0x3a, 0x40, 0x08, 0x37, 0xaa, 0x99, 138 | 0x84, 0xe8, 0x7c, 0xc7, 0x82, 0xd5, 0x4b, 0x2d, 0x26, 0x67, 0x2f, 0xdf, 139 | 0x6b, 0xaa, 0xb4, 0xc2, 0xc8, 0xff, 0x03, 0xe3, 0x87, 0x00, 0x7c, 0x6d, 140 | 0x85, 0x83, 0x5c, 0x25, 0x7b, 0x6c, 0x59, 0x66, 0x0e, 0x7a, 0xcf, 0x58, 141 | 0x09, 0xdc, 0x04, 0x0c, 0xd2, 0xe8, 0x02, 0x02, 0xe4, 0xf2, 0x1a, 0x5c, 142 | 0xa9, 0xc0, 0xfe, 0x8b, 0xda, 0x50, 0x2b, 0x53, 0x74, 0x43, 0x03, 0x95, 143 | 0x62, 0x03, 0x9e, 0x27, 0x4a, 0xe4, 0xa1, 0xaa, 0xfe, 0x69, 0x6a, 0x0c, 144 | 0xcb, 0x04, 0x2e, 0xb4, 0xf2, 0x09, 0x3e, 0x1e, 0xb0, 0x18, 0x44, 0x91, 145 | 0xfa, 0xaa, 0x47, 0x73, 0x64, 0x44, 0xea, 0x0e, 0xfb, 0x59, 0x0f, 0x86, 146 | 0x07, 0xc7, 0x97, 0x9e, 0x0b, 0x4c, 0x31, 0xb2, 0xb0, 0x68, 0x40, 0xb1, 147 | 0x47, 0x01, 0x00, 0x17, 0xf7, 0x92, 0xa1, 0x73, 0xe6, 0x6e, 0x24, 0x02, 148 | 0xe9, 0x3c, 0x7b, 0xae, 0xce, 0x1b, 0xf7, 0x3b, 0x96, 0xc4, 0xa6, 0xfe, 149 | 0xd9, 0x05, 0x00, 0x7c, 0x2d, 0x93, 0xf3, 0x4e, 0x42, 0x9d, 0x4f, 0xab, 150 | 0x9d, 0x93, 0xfd, 0xcf, 0x90, 0x78, 0x4b, 0x84, 0x3b, 0x8a, 0x68, 0xd5, 151 | 0x54, 0x9a, 0x5f, 0xba, 0x30, 0xf4, 0xef, 0xde, 0x34, 0x8d, 0x6d, 0x50, 152 | 0x79, 0x98, 0x5a, 0x73, 0xfb, 0x1a, 0x09, 0xe7, 0xb7, 0xc0, 0x3f, 0x1e, 153 | 0xcf, 0x65, 0x78, 0x89, 0xb9, 0x48, 0xcd, 0xd3, 0xa1, 0x0f, 0xed, 0xbf, 154 | 0x6c, 0xdc, 0xfc, 0xfe, 0xc2, 0xe6, 0xb3, 0xb3, 0xe4, 0xe1, 0x5b, 0xdb, 155 | 0x77, 0x72, 0xae, 0x0d, 0xd7, 0xb1, 0x7d, 0xd2, 0x39, 0x1f, 0x86, 0x3d, 156 | 0x0c, 0xed, 0xc0, 0x77, 0xb4, 0x67, 0x92, 0xe3, 0xe2, 0x1d, 0xd1, 0x39, 157 | 0x3e, 0xc8, 0xf2, 0x81, 0x7d, 0x41, 0xbd, 0x01, 0x33, 0x31, 0x0f, 0x62, 158 | 0xaf, 0x0a, 0x26, 0x5b, 0x13, 0x94, 0xfc, 0x97, 0xc6, 0x5f, 0x19, 0x2d, 159 | 0x74, 0xeb, 0x57, 0x16, 0x52, 0xfd, 0x7d, 0x82, 0xff, 0x58, 0x6d, 0xc3, 160 | 0xad, 0x8c, 0xc2, 0xdb, 0x87, 0x33, 0x28, 0xa7, 0xdc, 0xdb, 0x69, 0xd5, 161 | 0x93, 0x2e, 0xe8, 0x0d, 0xdf, 0x2e, 0x69, 0xf5, 0x2a, 0xfb, 0x51, 0x48, 162 | 0x1f, 0xb0, 0x22, 0x56, 0xaf, 0x6e, 0xad, 0x92, 0x47, 0x01, 0x00, 0x18, 163 | 0x12, 0x2e, 0x53, 0xe4, 0x86, 0xd8, 0x9d, 0x20, 0x0a, 0x34, 0x28, 0x8b, 164 | 0xa6, 0xf2, 0x94, 0x3e, 0xb2, 0xf5, 0x45, 0x80, 0xa9, 0x3f, 0x86, 0xed, 165 | 0x95, 0x4a, 0x39, 0xce, 0xb4, 0x68, 0x38, 0xbd, 0xb8, 0xb8, 0x5a, 0x11, 166 | 0xfe, 0xb9, 0xca, 0xa3, 0x58, 0x69, 0xb8, 0x04, 0x48, 0xf2, 0x53, 0x71, 167 | 0xde, 0xbd, 0xa3, 0x26, 0x09, 0x97, 0x82, 0xea, 0x96, 0x7d, 0xc0, 0x57, 168 | 0x06, 0x51, 0x3b, 0x75, 0xe5, 0x91, 0x3c, 0x81, 0xb7, 0x14, 0x37, 0x0f, 169 | 0xaf, 0xde, 0xc1, 0x10, 0xfa, 0x43, 0xa0, 0x49, 0x59, 0xbf, 0x49, 0x10, 170 | 0x85, 0xa4, 0x7c, 0x5d, 0xdd, 0x18, 0x3e, 0x33, 0xd7, 0x0a, 0x02, 0xfd, 171 | 0x24, 0xc2, 0x34, 0x70, 0x89, 0xa4, 0x1b, 0x11, 0x0f, 0xe6, 0x1a, 0x58, 172 | 0x77, 0xa7, 0x74, 0x06, 0x8c, 0x1e, 0x01, 0x3a, 0xae, 0xdf, 0xb0, 0x5f, 173 | 0xac, 0xed, 0x7c, 0xed, 0x12, 0xed, 0xf1, 0x51, 0xc7, 0xdc, 0x58, 0x59, 174 | 0x0f, 0x1c, 0x81, 0x17, 0x37, 0xd2, 0x7c, 0x0f, 0x40, 0xb8, 0x26, 0x8e, 175 | 0x0c, 0x7b, 0xdf, 0x00, 0x75, 0xe3, 0x56, 0xb2, 0x2b, 0x95, 0xec, 0xb7, 176 | 0x72, 0x0b, 0x9b, 0x97, 0xe3, 0xfc, 0xca, 0x5c, 0x6a, 0x0c, 0x79, 0x36, 177 | 0x66, 0x16, 0xa4, 0xbf, 0x09, 0x27, 0x97, 0xeb, 0x6f, 0x6c, 0xa9, 0x41, 178 | 0xed, 0xa0, 0xb6, 0x94, 0x47, 0x01, 0x00, 0x19, 0x5d, 0x28, 0x3b, 0x10, 179 | 0xfa, 0x1e, 0x34, 0xe0, 0x0f, 0x88, 0x00, 0x0a, 0x29, 0x0f, 0x22, 0x28, 180 | 0xa4, 0x84, 0x47, 0x92, 0x3e, 0x0c, 0x66, 0x9b, 0x6b, 0x08, 0xc3, 0x09, 181 | 0xa9, 0x2d, 0x17, 0xcf, 0x0d, 0x75, 0x42, 0x70, 0x7f, 0x5d, 0x3b, 0x01, 182 | 0xb3, 0x78, 0x0f, 0x4e, 0x10, 0x8f, 0xbe, 0xa4, 0x42, 0x6a, 0xcb, 0x5e, 183 | 0x21, 0xe7, 0x42, 0xe5, 0x92, 0x3e, 0x3b, 0xf5, 0x09, 0x6f, 0x8f, 0xe5, 184 | 0xc6, 0x20, 0x01, 0x90, 0xff, 0xde, 0xf7, 0x33, 0xfc, 0x54, 0xd4, 0x40, 185 | 0x05, 0x46, 0x41, 0x0c, 0x9e, 0x39, 0xac, 0x70, 0x92, 0x3f, 0x7d, 0x8a, 186 | 0xef, 0x6e, 0x31, 0xa2, 0xb2, 0x22, 0xeb, 0x9b, 0x1f, 0x87, 0xf4, 0x56, 187 | 0x72, 0xb6, 0x4d, 0x42, 0xff, 0x1c, 0xe8, 0xa4, 0x04, 0x06, 0x09, 0xce, 188 | 0x56, 0x62, 0x12, 0x6b, 0x32, 0xad, 0xf2, 0x4e, 0xe4, 0x2b, 0x46, 0x11, 189 | 0xb5, 0x70, 0x5a, 0x7c, 0x73, 0xa0, 0x45, 0x3b, 0xf8, 0x13, 0x84, 0x5a, 190 | 0x3f, 0x08, 0x34, 0xfc, 0x50, 0x13, 0x88, 0x76, 0x75, 0x0e, 0x31, 0xe7, 191 | 0xc8, 0xd2, 0xf8, 0x82, 0x87, 0x5f, 0xa2, 0x71, 0x7c, 0xce, 0x45, 0x8d, 192 | 0x53, 0x3f, 0xef, 0xf6, 0xff, 0xef, 0x17, 0x85, 0x4d, 0x28, 0x8b, 0xd9, 193 | 0x9a, 0x9c, 0x62, 0x7e, 0xdb, 0xf0, 0x32, 0x8c, 0xbd, 0x2c, 0xdc, 0x42, 194 | 0x47, 0x01, 0x00, 0x1a, 0x41, 0x9f, 0x99, 0x54, 0x2d, 0xd6, 0x06, 0x12, 195 | 0xdf, 0x00, 0xaf, 0xb4, 0xf6, 0xb0, 0xb4, 0xe7, 0x99, 0xf0, 0x5b, 0x7a, 196 | 0x06, 0xc1, 0x61, 0x07, 0xd2, 0xae, 0x56, 0xad, 0xb3, 0xe8, 0xef, 0x6b, 197 | 0x2e, 0x5f, 0xa9, 0x56, 0xe6, 0x39, 0x19, 0xae, 0x56, 0x88, 0xaa, 0xcc, 198 | 0x36, 0x7a, 0x98, 0x3b, 0xef, 0xa8, 0x1b, 0x75, 0x27, 0x52, 0xdf, 0x3b, 199 | 0x29, 0x6d, 0x95, 0x0e, 0xc2, 0xc8, 0xed, 0x80, 0xb4, 0x1f, 0xb2, 0x19, 200 | 0x9f, 0xde, 0x3f, 0xd8, 0x6d, 0x34, 0x1c, 0x1b, 0xbe, 0x1a, 0x12, 0x5b, 201 | 0x9b, 0xec, 0x60, 0x23, 0x05, 0x63, 0xe3, 0x99, 0x65, 0x2b, 0xf2, 0x50, 202 | 0x1e, 0x77, 0x86, 0x36, 0x22, 0x16, 0x51, 0x19, 0x77, 0xd0, 0x42, 0xc3, 203 | 0xce, 0xf7, 0x69, 0xa5, 0x35, 0x66, 0xb7, 0x44, 0x0f, 0xb0, 0x87, 0xc2, 204 | 0x5d, 0x6c, 0xb3, 0x79, 0x04, 0xb8, 0x9f, 0xee, 0x68, 0x28, 0x04, 0x2a, 205 | 0x09, 0x50, 0x18, 0x28, 0xce, 0x9c, 0x7f, 0x42, 0xfd, 0x96, 0xe0, 0x4d, 206 | 0x78, 0x7b, 0xe9, 0xd1, 0x9e, 0x8d, 0x64, 0x0b, 0xa6, 0x25, 0xc1, 0x95, 207 | 0xa1, 0x64, 0xda, 0x1e, 0xed, 0x15, 0x46, 0x80, 0x52, 0xe6, 0xb6, 0xf4, 208 | 0x45, 0x2d, 0x5b, 0xf3, 0x59, 0x47, 0xe3, 0x20, 0x0c, 0xa9, 0x05, 0xb9, 209 | 0xbe, 0x55, 0x96, 0x3c, 0x9f, 0x72, 0x56, 0x2b, 0x47, 0x01, 0x00, 0x1b, 210 | 0xcd, 0xfb, 0x01, 0xce, 0x7f, 0x15, 0x70, 0xca, 0xd7, 0x29, 0x7f, 0xed, 211 | 0x24, 0x20, 0x66, 0xe8, 0xc0, 0x8d, 0x1f, 0xf7, 0xf0, 0x1d, 0x5c, 0x71, 212 | 0x02, 0xf5, 0x4b, 0xf1, 0x68, 0x87, 0x7e, 0x98, 0x4c, 0xf9, 0x1a, 0xc7, 213 | 0xd2, 0xe2, 0x72, 0x32, 0x71, 0x73, 0x93, 0x4e, 0x23, 0xd4, 0x2e, 0xd7, 214 | 0x5d, 0x7d, 0x1f, 0x5a, 0x05, 0x8d, 0x9f, 0x2e, 0x77, 0x24, 0xa8, 0x1e, 215 | 0x16, 0x33, 0x20, 0x97, 0xf1, 0xc5, 0x95, 0x5a, 0x46, 0xa5, 0xf7, 0x9f, 216 | 0x96, 0x8f, 0x8c, 0x97, 0x06, 0xf6, 0x48, 0xa6, 0xa7, 0x80, 0xa9, 0x55, 217 | 0x96, 0x98, 0x16, 0x98, 0xee, 0xf2, 0x43, 0x11, 0x91, 0x78, 0x0f, 0xc7, 218 | 0x80, 0xf5, 0x13, 0x6e, 0xf6, 0x91, 0x4c, 0x73, 0x2b, 0xe8, 0x23, 0x2c, 219 | 0x84, 0x0a, 0xda, 0xb1, 0xbf, 0x16, 0x71, 0xce, 0x2a, 0x07, 0x40, 0xc8, 220 | 0xc0, 0x4a, 0x1e, 0xbe, 0x55, 0xda, 0xc1, 0x6a, 0x7d, 0x18, 0xa0, 0x8b, 221 | 0x15, 0x23, 0xd0, 0xa8, 0x35, 0xed, 0x20, 0x47, 0x27, 0x90, 0x3d, 0xca, 222 | 0xc2, 0x69, 0xc4, 0xfb, 0xcc, 0x3a, 0x07, 0x7e, 0x66, 0xb5, 0x3a, 0xfd, 223 | 0xae, 0xb0, 0x03, 0x5e, 0xf1, 0xff, 0x04, 0xf1, 0xb4, 0x6f, 0x3a, 0x25, 224 | 0xa0, 0xea, 0xd8, 0xc5, 0xdd, 0x42, 0x2d, 0x4b, 0x80, 0xa6, 0x9d, 0x9f, 225 | 0xeb, 0xf9, 0x7b, 0xce, 0x47, 0x01, 0x00, 0x1c, 0x2e, 0x5a, 0xb7, 0xd7, 226 | 0x37, 0x82, 0x4d, 0x3d, 0xf5, 0x56, 0x7b, 0x2c, 0xf3, 0xf8, 0x02, 0x72, 227 | 0x8a, 0xff, 0xde, 0x81, 0xe6, 0xf0, 0x72, 0x54, 0xf4, 0x47, 0xef, 0x30, 228 | 0xe1, 0x40, 0x86, 0xe0, 0x81, 0xab, 0x87, 0xc4, 0x70, 0x5b, 0x08, 0xaf, 229 | 0xe1, 0x6d, 0xdc, 0x06, 0xbe, 0x13, 0xd2, 0x2a, 0xf2, 0xec, 0x97, 0x8e, 230 | 0xf9, 0x45, 0x62, 0x43, 0x13, 0x84, 0xbe, 0x85, 0x8f, 0x79, 0x80, 0xc5, 231 | 0xb2, 0x4c, 0xf1, 0x1a, 0xca, 0x79, 0x29, 0xd4, 0xcc, 0x3d, 0xc0, 0xba, 232 | 0x45, 0x6a, 0x0b, 0x7f, 0x79, 0x74, 0x66, 0xb5, 0xc9, 0x75, 0x4a, 0x4f, 233 | 0xe7, 0x1c, 0x0a, 0xe7, 0xd0, 0xee, 0x3c, 0xc3, 0x84, 0xa8, 0x2f, 0xcc, 234 | 0x5e, 0x6f, 0xcc, 0xf7, 0xa5, 0x3e, 0x6a, 0x60, 0x83, 0xff, 0x9b, 0x21, 235 | 0x95, 0xb5, 0x8f, 0x93, 0xe5, 0x68, 0x91, 0xa1, 0x8a, 0x12, 0xc3, 0xff, 236 | 0xb5, 0x80, 0x17, 0xe7, 0x8a, 0x07, 0x25, 0x55, 0xe2, 0x75, 0xa0, 0xb3, 237 | 0xd6, 0x03, 0xe7, 0xe5, 0x00, 0x07, 0xed, 0x8c, 0xb9, 0x94, 0xe6, 0x8c, 238 | 0xc4, 0xa6, 0xcb, 0xfd, 0xa7, 0x53, 0x6f, 0xd8, 0x6c, 0x51, 0x8f, 0x14, 239 | 0x0f, 0x47, 0x9c, 0x48, 0x1f, 0xc0, 0xc5, 0xc4, 0x39, 0xfe, 0x2d, 0x40, 240 | 0x02, 0x96, 0x56, 0xca, 0xc6, 0xf1, 0xb7, 0xdc, 0xc3, 0xff, 0xeb, 0x38, 241 | 0x47, 0x01, 0x00, 0x1d, 0x64, 0xda, 0x90, 0xa2, 0xa3, 0xbb, 0xb6, 0xd7, 242 | 0x70, 0x26, 0xf2, 0x60, 0x0c, 0xbc, 0x8f, 0x27, 0x1d, 0x20, 0x11, 0x80, 243 | 0xbd, 0x62, 0xe8, 0xdf, 0x47, 0x32, 0x6c, 0x73, 0xc7, 0x20, 0x8f, 0x60, 244 | 0xc6, 0x84, 0x7c, 0xe4, 0xdb, 0x6b, 0xdd, 0x3a, 0x84, 0x64, 0xb8, 0xf1, 245 | 0x75, 0x46, 0x55, 0xbc, 0x62, 0x05, 0x0f, 0xe0, 0xc6, 0xf7, 0x8d, 0xdd, 246 | 0xe7, 0x07, 0x14, 0x20, 0xaf, 0x2d, 0xc1, 0x2e, 0x20, 0xeb, 0xcf, 0xc9, 247 | 0x31, 0x5f, 0xd2, 0xf7, 0x9a, 0xad, 0x4c, 0x53, 0x3f, 0x2e, 0x42, 0xb0, 248 | 0x04, 0x56, 0x8b, 0xfa, 0x97, 0xe0, 0x50, 0xc5, 0xa1, 0x2c, 0x98, 0x15, 249 | 0x2a, 0x0e, 0x96, 0x36, 0x8b, 0xe9, 0x4c, 0x9a, 0x40, 0x64, 0xde, 0xd1, 250 | 0xe2, 0xf5, 0xf5, 0xc9, 0x2b, 0xff, 0x66, 0xde, 0x04, 0x55, 0x1f, 0x99, 251 | 0x82, 0xd1, 0x7b, 0x38, 0x0c, 0xe0, 0x09, 0x30, 0x12, 0xfa, 0x80, 0x0e, 252 | 0x35, 0x87, 0x9b, 0xd3, 0x35, 0xab, 0xe0, 0x35, 0x17, 0xad, 0x00, 0xa9, 253 | 0x5a, 0x00, 0x2a, 0xe1, 0xb4, 0xf5, 0x1b, 0x5f, 0x7c, 0xf3, 0xd6, 0xdc, 254 | 0xab, 0x5c, 0xbc, 0xdc, 0x4b, 0xae, 0x83, 0x9c, 0x58, 0x93, 0x30, 0x2d, 255 | 0xb5, 0xe4, 0xb3, 0x02, 0xb9, 0x74, 0xfb, 0xa0, 0x5c, 0xb9, 0x52, 0x08, 256 | 0x12, 0xa1, 0x06, 0x1a, 0xce, 0x9b, 0x50, 0xcf, 0x47, 0x01, 0x00, 0x1e, 257 | 0xeb, 0x0e, 0xad, 0xb9, 0x8f, 0xea, 0x31, 0x75, 0x65, 0xf5, 0xe6, 0xed, 258 | 0x4d, 0x1b, 0x4f, 0x4e, 0x44, 0x0e, 0x15, 0xfe, 0x09, 0xc2, 0x59, 0xe8, 259 | 0x18, 0x9f, 0xb0, 0x3b, 0xd1, 0x57, 0x58, 0x21, 0x69, 0xfb, 0x6b, 0xb1, 260 | 0x58, 0x0b, 0x1c, 0xee, 0x92, 0xec, 0x6e, 0x5a, 0xb8, 0x36, 0xb1, 0xb8, 261 | 0x92, 0x9c, 0x90, 0x92, 0xdb, 0x79, 0x6b, 0x16, 0xff, 0xfe, 0xe8, 0xa3, 262 | 0x89, 0x58, 0x11, 0x9a, 0x43, 0x67, 0x54, 0x61, 0x14, 0x8c, 0x1b, 0x3e, 263 | 0x66, 0xef, 0x21, 0x3e, 0x32, 0x2a, 0xef, 0xc8, 0xc1, 0x22, 0xdf, 0x60, 264 | 0x2a, 0x1c, 0x3d, 0xb8, 0x29, 0xe7, 0xbc, 0x30, 0xc2, 0xe7, 0x46, 0xe8, 265 | 0x29, 0x32, 0x73, 0x4d, 0x42, 0xd6, 0x08, 0xa4, 0x8c, 0x53, 0xa5, 0x93, 266 | 0x7b, 0xcf, 0x27, 0x94, 0xf5, 0xb1, 0xe9, 0x6b, 0xe0, 0x5c, 0x2c, 0x07, 267 | 0xef, 0x1a, 0xa1, 0xe5, 0xf9, 0x05, 0x4b, 0x77, 0x51, 0x37, 0x0a, 0x4a, 268 | 0x4b, 0x87, 0x6f, 0x7b, 0x3c, 0x3a, 0x66, 0xac, 0xb5, 0xb0, 0xf1, 0xb8, 269 | 0x2d, 0x4a, 0x19, 0x71, 0x2e, 0x79, 0x34, 0xec, 0x34, 0x14, 0xe5, 0x72, 270 | 0x32, 0xbb, 0xbc, 0x64, 0x8a, 0x81, 0xa1, 0x2a, 0x2f, 0x8c, 0xbd, 0xc8, 271 | 0x14, 0xd8, 0xdc, 0xff, 0x02, 0xd8, 0xe4, 0x4b, 0x87, 0x92, 0x12, 0xda, 272 | 0xe2, 0x58, 0xd5, 0xad, 0x47, 0x01, 0x00, 0x1f, 0xd6, 0x06, 0xd4, 0xcd, 273 | 0xf3, 0xe2, 0x15, 0x5f, 0x68, 0x85, 0x2b, 0x14, 0x61, 0x6d, 0xac, 0x4e, 274 | 0xda, 0x58, 0x17, 0x27, 0xf2, 0xba, 0x8c, 0xcb, 0xed, 0x33, 0x9c, 0xee, 275 | 0x13, 0xb3, 0x4d, 0x3d, 0xb6, 0x7d, 0xac, 0x91, 0xe1, 0xee, 0xbb, 0xd8, 276 | 0x5e, 0x8a, 0x88, 0x10, 0xea, 0xbd, 0x46, 0x77, 0x60, 0xf7, 0x01, 0x0a, 277 | 0xe9, 0x0b, 0xe9, 0xe0, 0xdd, 0xaf, 0x44, 0xf8, 0x9e, 0xb6, 0x16, 0xb6, 278 | 0x4d, 0xde, 0xe6, 0x58, 0x09, 0xe3, 0xb2, 0x8b, 0x1a, 0x04, 0xaf, 0x82, 279 | 0x9b, 0x0e, 0xdf, 0x22, 0x8d, 0x77, 0xd6, 0x87, 0xec, 0x68, 0x7f, 0x81, 280 | 0xa8, 0x02, 0xd1, 0xe8, 0xd0, 0xed, 0xec, 0xfa, 0x51, 0x7b, 0xa4, 0x78, 281 | 0x3d, 0xbc, 0xe8, 0xd9, 0x16, 0x9d, 0x0e, 0x56, 0x41, 0x63, 0xe3, 0x73, 282 | 0xaf, 0x0b, 0x4e, 0x77, 0xad, 0xdf, 0x79, 0xb8, 0x58, 0x32, 0xf0, 0xc8, 283 | 0x26, 0xbd, 0x68, 0xab, 0x34, 0x7b, 0xd6, 0x9e, 0x44, 0x52, 0x6c, 0x21, 284 | 0x8b, 0xad, 0x29, 0xee, 0xd8, 0x7f, 0x44, 0x90, 0xee, 0x4b, 0xa0, 0x68, 285 | 0x75, 0x31, 0x56, 0xd1, 0x5e, 0x8a, 0xec, 0x4b, 0x64, 0x99, 0x58, 0xc0, 286 | 0x7e, 0x1f, 0x6a, 0x5c, 0xeb, 0x1a, 0x88, 0xdb, 0x00, 0x48, 0xaa, 0xfa, 287 | 0x25, 0xfe, 0xe7, 0xe3, 0x32, 0x11, 0xf5, 0xa0, 0x83, 0xcf, 0x79, 0x73, 288 | 0x47, 0x01, 0x00, 0x10, 0x4f, 0xa9, 0x08, 0xac, 0xb0, 0x8e, 0xf7, 0x3c, 289 | 0x4a, 0x4b, 0x6f, 0x51, 0x63, 0xf8, 0x15, 0x95, 0xf4, 0x3c, 0x89, 0x08, 290 | 0x6d, 0xe3, 0x86, 0xa1, 0x89, 0x11, 0xf2, 0x46, 0x9f, 0xfb, 0x25, 0x22, 291 | 0x17, 0x59, 0x58, 0x5d, 0x0b, 0x89, 0xe5, 0xc8, 0xe0, 0xfa, 0x65, 0xc0, 292 | 0xfe, 0x62, 0x0f, 0xa1, 0x98, 0xc2, 0x8d, 0x16, 0x2f, 0xe4, 0xfe, 0xb5, 293 | 0xb0, 0x3c, 0x8c, 0xe8, 0x66, 0x5b, 0x05, 0xd7, 0xf0, 0x88, 0x8a, 0x3b, 294 | 0x65, 0xc0, 0xca, 0xc7, 0xe5, 0x73, 0x9c, 0x37, 0x31, 0x6a, 0x23, 0x05, 295 | 0x3f, 0x8d, 0x5f, 0x00, 0xda, 0xd0, 0x23, 0xe5, 0x3f, 0x83, 0xac, 0xef, 296 | 0x16, 0xad, 0x43, 0xf6, 0x9a, 0xa2, 0xfa, 0x2d, 0x26, 0xe4, 0xe9, 0xc6, 297 | 0xe4, 0x67, 0x16, 0x08, 0xdd, 0x40, 0xca, 0x11, 0x4e, 0xcc, 0x0a, 0x3f, 298 | 0xbe, 0x3e, 0x54, 0xf8, 0xfa, 0x91, 0xc6, 0x86, 0x1c, 0xb4, 0x97, 0x28, 299 | 0x23, 0x6f, 0x5d, 0x43, 0xd9, 0x1c, 0x65, 0xac, 0x2a, 0x45, 0x96, 0x46, 300 | 0x67, 0x89, 0xc2, 0x65, 0xa5, 0x06, 0xcb, 0x11, 0x23, 0x17, 0xc6, 0xfe, 301 | 0xf2, 0xa9, 0x5d, 0xdb, 0xdc, 0xe9, 0x71, 0xed, 0x7c, 0x5b, 0xb4, 0xd4, 302 | 0x10, 0x2a, 0xe1, 0xd0, 0x81, 0x32, 0x15, 0xbf, 0x57, 0xfe, 0xab, 0x9a, 303 | 0x4c, 0x59, 0x74, 0xb9, 0x30, 0x65, 0x28, 0x01, 0x47, 0x01, 0x00, 0x11, 304 | 0xf1, 0x52, 0x68, 0x66, 0x40, 0x56, 0x0f, 0x4b, 0x28, 0xef, 0x5d, 0x01, 305 | 0xfa, 0x6b, 0x4b, 0x57, 0x46, 0x97, 0x83, 0x0a, 0x8b, 0x6f, 0xef, 0x22, 306 | 0x91, 0x20, 0x67, 0x7c, 0x0f, 0xca, 0x2e, 0xfd, 0x21, 0x62, 0x74, 0xaa, 307 | 0xa9, 0xd8, 0xce, 0x1b, 0x1d, 0xc6, 0x25, 0x51, 0xb2, 0x05, 0x3d, 0xa7, 308 | 0xf5, 0x7f, 0x66, 0xb4, 0x3d, 0x00, 0xab, 0xe5, 0xb6, 0x06, 0xec, 0x54, 309 | 0x21, 0x81, 0x15, 0x9e, 0x59, 0x95, 0xd8, 0xd0, 0x2a, 0x73, 0x8a, 0x29, 310 | 0xdc, 0x48, 0x5e, 0xef, 0x7c, 0xce, 0x45, 0xe7, 0x2e, 0x2d, 0x8e, 0xfb, 311 | 0x84, 0x86, 0x5d, 0x52, 0x65, 0xcf, 0x9a, 0x06, 0x21, 0x3b, 0x6e, 0xac, 312 | 0x71, 0xa9, 0x61, 0xf8, 0xf6, 0x6a, 0x03, 0x90, 0xe9, 0x79, 0xc8, 0x69, 313 | 0xb1, 0x73, 0x7c, 0x2c, 0xf6, 0x73, 0x60, 0x8b, 0x24, 0x6e, 0x85, 0x6b, 314 | 0x32, 0xc4, 0x8a, 0x91, 0xb7, 0x86, 0x54, 0x0c, 0xaf, 0x50, 0xf3, 0xf7, 315 | 0x9b, 0x48, 0x8a, 0x59, 0x6a, 0x3d, 0x08, 0x97, 0x3c, 0x8a, 0x22, 0xa6, 316 | 0x7a, 0xb7, 0xa3, 0x15, 0xe4, 0xe6, 0x5f, 0x58, 0xbb, 0x72, 0x76, 0x58, 317 | 0x24, 0x24, 0x9e, 0x78, 0x24, 0x63, 0x56, 0x60, 0xeb, 0x1b, 0x93, 0xaf, 318 | 0x89, 0xb5, 0x52, 0x59, 0x5f, 0x93, 0x74, 0x53, 0x77, 0x40, 0x28, 0xd4, 319 | 0xf1, 0xec, 0x3a, 0xa0 320 | }; 321 | 322 | size_t ccTestDataLength = 3760; 323 | -------------------------------------------------------------------------------- /unit_tests/unit_test_9.cpp: -------------------------------------------------------------------------------- 1 | //This unit test is testing the demuxers ability to demux a broken ts stream and maintain ts packet sync 2 | 3 | #include "unit_test_9.h" 4 | 5 | void UnitTest9::dmxOutput(const EsFrame &esFrame){ 6 | mFrameCounter++; 7 | mUnitTestStatus = true; 8 | } 9 | 10 | bool UnitTest9::runTest() { 11 | mDemuxer.esOutCallback = std::bind(&UnitTest9::dmxOutput, this, std::placeholders::_1); 12 | 13 | std::ifstream lFile("bars-github.ts", std::ios::binary | std::ios::in); 14 | if (!lFile.is_open()) { 15 | std::cout << "Failed to open bars-github.ts - download it using: wget https://github.com/andersc/assets/raw/master/bars.ts -O bars-github.ts" << std::endl; 16 | mUnitTestStatus = false; 17 | return mUnitTestStatus; 18 | } 19 | 20 | uint8_t lPacket[400] = {0}; 21 | SimpleBuffer lIn; 22 | 23 | const std::vector lPacketChunkSize = {150, 200, 250, 300, 188, 276, 400}; 24 | size_t lPacketChunkSizeIndex = 0; 25 | size_t lPacketDestroyIndex = 0; 26 | 27 | while (!lFile.eof()) { 28 | uint32_t lPacketSize = lPacketChunkSize[lPacketChunkSizeIndex++ % lPacketChunkSize.size()]; 29 | 30 | lFile.read(reinterpret_cast(&lPacket[0]), lPacketSize); 31 | uint32_t lBytesRead = lFile.gcount(); 32 | 33 | if (lBytesRead) { 34 | if (lPacketDestroyIndex++ % 5 == 0) { 35 | // Simulate that every fifth packet is corrupt by removing the last 33 bytes (from the size we append below) 36 | lBytesRead -= 33; 37 | } 38 | lIn.append(lPacket, lBytesRead); 39 | // decode call will clear the buffer 40 | mDemuxer.decode(lIn); 41 | } 42 | } 43 | lFile.close(); 44 | 45 | if (mFrameCounter != 1115) { 46 | std::cout << "Frame counter is not as expected: " << mFrameCounter << std::endl; 47 | mUnitTestStatus = false; 48 | } 49 | 50 | return mUnitTestStatus; //True = OK 51 | } 52 | -------------------------------------------------------------------------------- /unit_tests/unit_test_9.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Edgeware AB. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include "mpegts_demuxer.h" 8 | 9 | using namespace mpegts; 10 | 11 | class UnitTest9 { 12 | public: 13 | bool runTest(); 14 | private: 15 | void dmxOutput(const EsFrame &esFrame); 16 | 17 | MpegTsDemuxer mDemuxer; 18 | 19 | int mFrameCounter = 0; 20 | bool mUnitTestStatus = false; 21 | }; 22 | --------------------------------------------------------------------------------