├── .vscode └── settings.json ├── CMakeLists.txt ├── README.md ├── h264.png ├── h265.png ├── main.cpp ├── stream_analyser.cpp └── stream_analyser.h /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "iomanip": "cpp" 4 | } 5 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(stream_analyser VERSION 1.0) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_BUILD_TYPE Debug) 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fPIC -w -fdiagnostics-color=always -pthread") 7 | 8 | add_executable(stream_analyser "main.cpp" "stream_analyser.cpp") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | a lightweight tool to analyse h264 and h265 raw stream, written by native C++, easy to integrate into your code. please refer to `main.cpp` 2 | 3 | ## how to build ## 4 | ``` 5 | mkdir build && cd build 6 | cmake .. 7 | make -j8 8 | ``` 9 | 10 | ## result for h264 ## 11 | ``` 12 | ./stream_analyser video.h264 h264 13 | ``` 14 | ![](./h264.png) 15 | 16 | ## result for h265 ## 17 | ``` 18 | ./stream_analyser video.h265 h265 19 | ``` 20 | ![](./h265.png) -------------------------------------------------------------------------------- /h264.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherlockchou86/stream_analyser/2d2bed88229ea05d154f2745bf60d8a6b8fb2bab/h264.png -------------------------------------------------------------------------------- /h265.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherlockchou86/stream_analyser/2d2bed88229ea05d154f2745bf60d8a6b8fb2bab/h265.png -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "stream_analyser.h" 7 | 8 | /** 9 | * run command usage: 10 | * (1) ./stream_analyser video1.h264 h264 11 | * (2) ./stream_analyser video2.h265 h265 12 | * 13 | * note only work fine for h264 or h265 raw data. 14 | */ 15 | int main(int argc, char* argv[]) { 16 | // process name, file name, video codec type 17 | assert(argc == 3); 18 | std::string filename(argv[1]); 19 | bool is_h264 = std::string(argv[2]) == std::string("h264"); 20 | std::ifstream file(filename, std::ios::binary); 21 | 22 | auto read_len = 1024 * 1024; // read 1MB bytes each time 23 | auto real_read_len = 0; 24 | unsigned char read_bytes[read_len]; 25 | auto total_index = 0; 26 | auto total_bytes = 0; 27 | auto total_times = 0; 28 | std::vector total_nal_units; 29 | 30 | do { 31 | file.read(reinterpret_cast(read_bytes), read_len); 32 | real_read_len = file.gcount(); 33 | total_times++; 34 | if (real_read_len) { 35 | total_bytes += real_read_len; 36 | stream_analyser analyser(read_bytes, real_read_len, !is_h264); 37 | 38 | std::vector nal_units; 39 | auto ret = analyser.analyse(nal_units); 40 | 41 | total_nal_units.insert(total_nal_units.end(), nal_units.begin(), nal_units.end()); 42 | } 43 | } while (real_read_len); 44 | 45 | std::ostringstream oss; 46 | oss << "total read times:" << total_times << std::endl; 47 | oss << "total read bytes:" << total_bytes << std::endl; 48 | 49 | /** 50 | * NAL index relative to the whole video data (start from 0), 51 | * NAL index relative the readed data (start from 0), 52 | * offset for current NAL unit relative to readed data (bytes), 53 | * length of current NAL unit (bytes), 54 | * start bytes & head bytes for current NAL unit, 55 | * NAL type (int), 56 | * NAL type name (string) 57 | */ 58 | oss << std::setw(8) << std::setfill(' ') << "index" 59 | << std::setw(8) << std::setfill(' ') << "i-index" 60 | << std::setw(16) << std::setfill(' ') << "i-offset" 61 | << std::setw(8) << std::setfill(' ') << "length" 62 | << std::setw(24) << std::setfill(' ') << "start-flag" 63 | << std::setw(16) << std::setfill(' ') << "nal-type" 64 | << std::setw(24) << std::setfill(' ') << "nal-type-name" << std::endl; 65 | for (auto& nal: total_nal_units) { 66 | oss << std::setw(8) << std::setfill(' ') << total_index++ 67 | << std::setw(8) << std::setfill(' ') << nal.index 68 | << std::setw(16) << std::setfill(' ') << nal.offset 69 | << std::setw(8) << std::setfill(' ') << nal.nal_length 70 | << std::setw(24) << std::setfill(' ') << stream_analyser::to_hex(nal.start_bytes) << stream_analyser::to_hex(nal.head_bytes, false) 71 | << std::setw(16) << std::setfill(' ') << nal.nal_type 72 | << std::setw(24) << std::setfill(' ') << nal.nal_type_name << std::endl; 73 | } 74 | 75 | std::cout << oss.str(); 76 | 77 | std::ofstream ofile("./analyse.txt", std::ios::out); 78 | ofile.write(oss.str().c_str(), oss.str().length()); 79 | } -------------------------------------------------------------------------------- /stream_analyser.cpp: -------------------------------------------------------------------------------- 1 | #include "stream_analyser.h" 2 | 3 | stream_analyser::stream_analyser(const unsigned char* data, 4 | unsigned long length, 5 | bool is_h265): 6 | m_data(data), 7 | m_length(length), 8 | m_is_h265(is_h265), 9 | m_current_pos(0), 10 | m_nal_index(0) { 11 | } 12 | 13 | unsigned long stream_analyser::find_start_bytes(unsigned long pos) { 14 | for (size_t i = pos; i + 3 < m_length; ++i) { 15 | if (m_data[i] == 0x00 && m_data[i + 1] == 0x00 && m_data[i + 2] == 0x01) { 16 | return i; 17 | } 18 | if (i + 4 < m_length && m_data[i] == 0x00 && m_data[i + 1] == 0x00 && m_data[i + 2] == 0x00 && m_data[i + 3] == 0x01) { 19 | return i; 20 | } 21 | } 22 | return std::string::npos; 23 | } 24 | 25 | std::string stream_analyser::get_h264_nal_type_name(int nal_type) { 26 | switch (nal_type) { 27 | case 1: return "Non-IDR Slice"; 28 | case 5: return "IDR Slice"; 29 | case 6: return "SEI"; 30 | case 7: return "SPS"; 31 | case 8: return "PPS"; 32 | default: return "Other"; 33 | } 34 | } 35 | 36 | std::string stream_analyser::get_h265_nal_type_name(int nal_type) { 37 | switch (nal_type) { 38 | case 32: return "VPS"; 39 | case 33: return "SPS"; 40 | case 34: return "PPS"; 41 | case 39: return "SEI"; 42 | case 19: return "IDR_W_RADL"; 43 | case 20: return "IDR_N_LP"; 44 | default: return "Other"; 45 | } 46 | } 47 | 48 | nal_unit stream_analyser::parse_nal_unit(int index, unsigned long offset, unsigned long length) { 49 | const unsigned char* nal_data = m_data + offset; 50 | bool long_start_code = (nal_data[2] == 0x01) ? false : true; 51 | int nal_type; 52 | std::string nal_type_name; 53 | 54 | if (m_is_h265) { 55 | nal_type = nal_data[long_start_code ? 4 : 3] >> 1 & 0x3F; // type for H.265 56 | nal_type_name = get_h265_nal_type_name(nal_type); 57 | } else { 58 | nal_type = (long_start_code) ? nal_data[4] & 0x1F : nal_data[3] & 0x1F; // type fot H.264 59 | nal_type_name = get_h264_nal_type_name(nal_type); 60 | } 61 | 62 | /* fill properties of NAL Unit. */ 63 | nal_unit nal; 64 | nal.codec_type = m_is_h265 ? 265: 264; 65 | nal.data = m_data; 66 | nal.index = index; 67 | nal.nal_length = length; 68 | nal.nal_type = nal_type; 69 | nal.nal_type_name = nal_type_name; 70 | nal.offset = offset; 71 | if (m_is_h265) { 72 | nal.start_bytes.push_back(nal_data[0]); 73 | nal.start_bytes.push_back(nal_data[1]); 74 | nal.start_bytes.push_back(nal_data[2]); 75 | if (long_start_code) { 76 | nal.start_bytes.push_back(nal_data[3]); 77 | nal.head_bytes.push_back(nal_data[4]); 78 | nal.head_bytes.push_back(nal_data[5]); 79 | } 80 | else { 81 | nal.head_bytes.push_back(nal_data[3]); 82 | nal.head_bytes.push_back(nal_data[4]); 83 | } 84 | } 85 | else { 86 | nal.start_bytes.push_back(nal_data[0]); 87 | nal.start_bytes.push_back(nal_data[1]); 88 | nal.start_bytes.push_back(nal_data[2]); 89 | if (long_start_code) { 90 | nal.start_bytes.push_back(nal_data[3]); 91 | nal.head_bytes.push_back(nal_data[4]); 92 | } 93 | else { 94 | nal.head_bytes.push_back(nal_data[3]); 95 | } 96 | } 97 | 98 | return nal; 99 | } 100 | 101 | bool stream_analyser::analyse(std::vector& nal_units) { 102 | nal_units.clear(); 103 | while (m_current_pos < m_length) { 104 | auto nal_start = find_start_bytes(m_current_pos); 105 | if (nal_start == std::string::npos) { 106 | return false; 107 | } 108 | auto nal_end = find_start_bytes(nal_start + 4); 109 | auto nal_length = (nal_end == std::string::npos) ? m_length - nal_start : nal_end - nal_start; 110 | 111 | auto nal_unit = parse_nal_unit(m_nal_index++, nal_start, nal_length); 112 | nal_units.push_back(nal_unit); 113 | 114 | m_current_pos = (nal_end == std::string::npos) ? m_length : nal_end; 115 | } 116 | m_current_pos = 0; 117 | m_nal_index = 0; 118 | 119 | return true; 120 | } 121 | 122 | std::string stream_analyser::to_hex(const std::vector& bytes, bool add_hex_flag) { 123 | std::ostringstream oss; 124 | if (add_hex_flag) { 125 | oss << "0x"; 126 | } 127 | 128 | for (auto& b: bytes) { 129 | oss << std::hex << std::setw(2) << std::setfill('0') << (int)b; 130 | } 131 | return oss.str(); 132 | } -------------------------------------------------------------------------------- /stream_analyser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * NAL Unit in h264/h265 streams. 8 | * 9 | * h264 with 4 start bytes: 10 | * | 0x00 | 0x00 | 0x00 | 0x01 | 0x65 | .... | 11 | * -------------------------------------------------------- 12 | * | start bytes | head bytes | body data | 13 | * 14 | * h265 with 4 start bytes: 15 | * | 0x00 | 0x00 | 0x00 | 0x01 | 0x65 | 0x48 | .... | 16 | * ------------------------------------------------------- 17 | * | start bytes | head bytes | body data | 18 | * 19 | */ 20 | struct nal_unit { 21 | /** 22 | * the start pointer of packet to be analysed. 23 | */ 24 | const unsigned char* data; 25 | 26 | /** 27 | * the codec type to be analysed, 264 or 265. 28 | */ 29 | int codec_type; 30 | 31 | /** 32 | * the index of NAL Unit in packet, begin from 0. 33 | */ 34 | int index; 35 | 36 | /** 37 | * the offset value of NAL Unit relative to start pointer of packet, begin from 0. 38 | */ 39 | unsigned long offset; 40 | 41 | /** 42 | * the size of whole NAL Unit in bytes, including start bytes and head bytes. 43 | */ 44 | unsigned long nal_length; 45 | 46 | /** 47 | * the type of NAL Unit, differ according to codec type. 48 | */ 49 | int nal_type; 50 | 51 | /** 52 | * the type name(description) of NAL Unit, differ according to codec type. 53 | */ 54 | std::string nal_type_name; 55 | 56 | /** 57 | * the start bytes of NAL Unit, 3 bytes(0x000001) or 4 bytes(0x00000001). 58 | */ 59 | std::vector start_bytes; 60 | 61 | /** 62 | * the head bytes of NAL Unit, only 1 byte for h264, and 2 bytes for h265. 63 | */ 64 | std::vector head_bytes; 65 | }; 66 | 67 | /** 68 | * bit stream analysis for video (only support annexb format with h264 or h265), mainly used to parse out NAL units (index, offset, type, length) from byte packets before decoding or after encoding. 69 | * 70 | * we can determine if ignore packets or not according to the analysis. for example, 71 | * 1. ignore the IDR NAL unit which has no SPS or PPS units before it. 72 | * 2. ignore all non-IDR NAL units when write to file/ send to network unitl a IDR NAL unit appears. 73 | */ 74 | class stream_analyser { 75 | private: 76 | /** 77 | * the start pointer of packet to be analysed. 78 | */ 79 | const unsigned char* m_data; 80 | 81 | /** 82 | * the size of packet to be analysed in bytes. 83 | */ 84 | unsigned long m_length; 85 | 86 | /** 87 | * current position for inner usage. 88 | */ 89 | unsigned long m_current_pos; 90 | 91 | /** 92 | * current index for inner usage. 93 | */ 94 | int m_nal_index; 95 | 96 | /** 97 | * h265 or not. 98 | */ 99 | bool m_is_h265; 100 | 101 | /** 102 | * find start bytes from specific position. 103 | */ 104 | unsigned long find_start_bytes(unsigned long pos); 105 | 106 | /** 107 | * get h264 NAL type name from type id. 108 | */ 109 | std::string get_h264_nal_type_name(int nal_type); 110 | 111 | /** 112 | * get h265 NAL type name from type id. 113 | */ 114 | std::string get_h265_nal_type_name(int nal_type); 115 | 116 | /** 117 | * parse out a NAL Unit using index, offset, length. 118 | * 119 | * @note 120 | * it will always succeed because has checked the data before calling this function. 121 | */ 122 | nal_unit parse_nal_unit(int index, unsigned long offset, unsigned long length); 123 | public: 124 | /** 125 | * create stream_analyser instance using initial parameters. 126 | * 127 | * @param data start pointer of packet to be analysed. 128 | * @param length size of packet in bytes. 129 | * @param is_h265 h265 or not (not by default). 130 | * 131 | * @note 132 | * packet can be any format of byte streams, not just something like `AVPacket->data` returned from `av_read_frame` in FFmpeg. 133 | * we can read batch of bytes manually from h264/h265 file also, and then using `stream_analyser` to analyse it. 134 | */ 135 | stream_analyser(const unsigned char* data, unsigned long length, bool is_h265 = false); 136 | 137 | /** 138 | * analyse packet and parse out NAL Units from packet. 139 | * 140 | * @param nal_units [out]NAL Units parsed out successfully. 141 | * 142 | * @return 143 | * true if succeed: at least 1 NAL Unit parsed out from packet. 144 | * false otherwise: no NAL Unit parsed out from packet at all. 145 | * 146 | * @note 147 | * it works based on start bytes and head bytes of NAL Unit, and no care about its body data. 148 | * so it could not guarantee the NAL Unit which has been parsed out successfully is valid and data complete. 149 | * 150 | * make sure the packet to be analysed is large enough and contains at least 1 NAL Unit. 151 | */ 152 | bool analyse(std::vector& nal_units); 153 | 154 | /** 155 | * format bytes to string of hex. 156 | * 157 | * examples: 158 | * 4 bytes(0,103,75,217) to: 0x00674BD9 159 | * 160 | * @param bytes bytes to be converted. 161 | */ 162 | static std::string to_hex(const std::vector& bytes, bool add_hex_flag = true); 163 | }; --------------------------------------------------------------------------------