C/C++實現(xiàn)H264文件解析
H264視頻編碼格式簡介
H264是視頻壓縮編碼標準。視頻?件的傳輸是?個極?的問題:?段分辨率為19201080的視頻,每個像素點為RGB占?3個字節(jié),幀率是25的視頻,對于傳輸帶寬的要求是:192010803*25/1024/1024=148.315MB/s,換成bps則意味著視頻每秒帶寬為1186.523Mbps,這樣的速率對于?絡存儲是不可接受的。因此視頻壓縮和編碼技術應運??。
H264碼流分析
通常我們使用H264碼流分析工具時,我們會發(fā)現(xiàn)H264碼流是由連續(xù)的幀組成的,一組連續(xù)的幀成為1個GOP即1組,對于1個GOP來說,通常包括SPS(序列參數(shù)集,編碼視頻序列的全局參數(shù))PPS(圖像參數(shù)集,?個序列中某?幅圖像或者某?幅圖像的參數(shù)),IDR幀(第一個出現(xiàn)的I幀成為IDR幀,1個GOP中通常只包含1個IDR幀),P幀(GOP中剩余幀基本都是P幀);GOP描述如下:
H264碼流結構如圖:
C/C++ H264文件解析
C++實現(xiàn)H264文件以及一段H264碼流解析,源碼如下:
h264Parse.h:
#ifndef _H264PARSE_H_ #define _H264PARSE_H_ #include <fstream> class H264Parse { public: int open_file(const std::string &filename); /** * @brief 從文件中讀取一個nalu,包含起始碼 * @param buf 存放nalu的緩沖區(qū) * @param size 緩沖區(qū)大小 * @param len nalu的長度 * @param n 每次讀取多少個字節(jié) * @return -1 失敗 0 已到文件末尾 1 成功獲取到一個nalu */ int read_nalu(uint8_t *buf, uint32_t size, uint32_t &len, uint32_t n); void close_file(); // 獲取起始碼長度 static int get_startCode_len(const uint8_t *ptr); static const uint8_t *find_startCode_pos(const uint8_t *buf, uint32_t len); /** * @brief 從一段h264碼流中分割nalu,包含起始碼 * @param stream h264碼流 * @param streamLen 碼流大小 * @param nalu Pointer to the extracted nalu * @param naluLen nalu的長度 * @param record Pointer用于記錄狀態(tài),第一次分割時把 *record 賦值為NULL * @return -1 失敗 0 已分割完 1 成功獲取到一個nalu */ static int nalu_tok(const uint8_t *stream, uint32_t streamLen, const uint8_t **nalu, uint32_t &naluLen, const uint8_t **record); private: std::fstream h264File; int read_start_code(uint8_t *buf); int adjust_filePointer_pos(uint32_t totalRead, uint32_t naluLen); }; #endif // _H264PARSE_H_
h264Parse.cpp:
#include "h264Parse.h" #include <iostream> #include <cstring> int H264Parse::open_file(const std::string &filename) { h264File.open(filename, std::ios::in | std::ios::binary); if (!h264File.is_open()) { std::cout << "Failed to open the H.264 file." << std::endl; return -1; } return 0; } int H264Parse::get_startCode_len(const uint8_t *ptr) { if (ptr[0] == 0x00 && ptr[1] == 0x00) { if (ptr[2] == 0x01) return 3; else if (ptr[2] == 0x00 && ptr[3] == 0x01) return 4; } return -1; // 無效的起始碼 } // 讀取起始碼,并返回其長度 int H264Parse::read_start_code(uint8_t *buf) { // 讀取前4個字節(jié)來判斷起始碼長度 h264File.read(reinterpret_cast<char *>(buf), 4); if (h264File.gcount() < 4) { return -1; } return get_startCode_len(buf); } // 尋找NALU的起始碼位置 const uint8_t *H264Parse::find_startCode_pos(const uint8_t *buf, uint32_t len) { const uint8_t *p = buf; if (len < 3) return NULL; for (uint32_t i = 0; i < len - 3; ++i) { if ((p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01) || (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x00 && p[3] == 0x01)) { return p; } p++; } // 檢查最后3字節(jié)是不是起始碼 if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01) return p; return NULL; } // 調整文件指針位置 int H264Parse::adjust_filePointer_pos(uint32_t totalRead, uint32_t naluLen) { int offset = -(totalRead - naluLen); if (!h264File.eof()) { h264File.seekg(offset, std::ios::cur); } else { h264File.clear(); // 達到文件末尾了要先清除 eof 標志 h264File.seekg(offset, std::ios::end); } if (h264File.fail()) { std::cout << "seekg failed!" << std::endl; return -1; } return 0; } int H264Parse::read_nalu(uint8_t *buf, uint32_t size, uint32_t &len, uint32_t n) { uint32_t totalRead = 0; int startCodeLength = read_start_code(buf); if (startCodeLength == -1) { printf("read_start_code failed.\n"); return -1; } totalRead += 4; // 已經(jīng)讀取了4字節(jié)的長度 while (true) { if (size < totalRead + n) { std::cout << "Buffer size is too small: size=" << size << ", needed=" << totalRead + n << std::endl; return -1; } h264File.read(reinterpret_cast<char *>(buf + totalRead), n); std::streamsize bytesRead = h264File.gcount(); if (bytesRead <= 0) { std::cout << "Failed to read from file!" << std::endl; return -1; } uint32_t searchStart = (totalRead > 4) ? totalRead - 3 : startCodeLength; const uint8_t *naluEnd = find_startCode_pos(buf + searchStart, bytesRead + (totalRead > 4 ? 3 : 0)); totalRead += bytesRead; if (naluEnd != nullptr) { len = naluEnd - buf; if (adjust_filePointer_pos(totalRead, len) < 0) return -1; break; } // 是否讀取到文件末尾 if (h264File.peek() == std::char_traits<char>::eof()) { len = totalRead; return 0; // NALU完整讀取 } } memset(buf + len, 0, size - len); // 清空剩余部分 return 1; // 成功讀取 } void H264Parse::close_file() { h264File.close(); } int H264Parse::nalu_tok(const uint8_t *stream, uint32_t streamLen, const uint8_t **nalu, uint32_t &naluLen, const uint8_t **record) { const uint8_t *current = (record && *record) ? *record : stream; uint32_t offset = static_cast<uint32_t>(current - stream); if (offset >= streamLen) { return -1; // 當前記錄位置超出緩沖區(qū) } int scLen = get_startCode_len(current); if (scLen == -1 || (current + scLen) > (stream + streamLen)) { return -1; // 無效的起始碼或起始碼長度超出緩沖區(qū) } // 查找下一個起始碼的位置 const uint8_t *next_start = find_startCode_pos(current + scLen, streamLen - offset - scLen); if (next_start) { *nalu = current; naluLen = static_cast<uint32_t>(next_start - current); *record = next_start; return 1; // 成功獲取到一個 NALU } else { // 最后一個 NALU *nalu = current; naluLen = streamLen - offset; *record = NULL; // 重置記錄指針 return 0; // 分割完畢 } }
測試:
#include <iostream> #include <vector> #include "h264Parse.h" void test1() { int ret; int number = 0; H264Parse h264; uint8_t buf[1024 * 1024]; uint32_t len = 0; h264.open_file("/home/tl/work/app/res/output.h264"); while ((ret = h264.read_nalu(buf, sizeof(buf), len, 1024 * 2)) != -1) { printf("number: %d nalu len: %u\n", number, len - h264.get_startCode_len(buf)); number++; if (ret == 0) break; } if (ret == -1) { std::cout << "read_nalu failed." << std::endl; } h264.close_file(); } // 輔助函數(shù):打印 NALU 信息 void print_nalu(const uint8_t *nalu, uint32_t len, int index) { std::cout << "NALU " << index << ": Length = " << len << " bytes, Data = "; for (uint32_t i = 0; i < len; ++i) { printf("%02X ", nalu[i]); } std::cout << std::endl; } void test2() { // 構造一個模擬的 H.264 碼流緩沖區(qū),包含多個 NALU // 起始碼格式:0x000001 (3 字節(jié)) 和 0x00000001 (4 字節(jié)) // NALU 內(nèi)容:隨機填充的字節(jié)數(shù)據(jù) std::vector<uint8_t> buffer; // NALU 1: 3 字節(jié)起始碼 + 5 字節(jié)數(shù)據(jù) std::vector<uint8_t> nalu1 = {0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x21, 0xA0}; buffer.insert(buffer.end(), nalu1.begin(), nalu1.end()); // NALU 2: 4 字節(jié)起始碼 + 6 字節(jié)數(shù)據(jù) std::vector<uint8_t> nalu2 = {0x00, 0x00, 0x00, 0x01, 0x41, 0x9A, 0x5C, 0xD4, 0x00, 0x11}; buffer.insert(buffer.end(), nalu2.begin(), nalu2.end()); // NALU 3: 3 字節(jié)起始碼 + 4 字節(jié)數(shù)據(jù) std::vector<uint8_t> nalu3 = {0x00, 0x00, 0x01, 0x06, 0x05, 0xFF, 0xEE}; buffer.insert(buffer.end(), nalu3.begin(), nalu3.end()); // NALU 4: 3 字節(jié)起始碼 + 3 字節(jié)數(shù)據(jù) (測試末尾) std::vector<uint8_t> nalu4 = {0x00, 0x00, 0x01, 0x07, 0xAD, 0xBE}; buffer.insert(buffer.end(), nalu4.begin(), nalu4.end()); // 輸出構建的緩沖區(qū)(可選) std::cout << "Constructed H.264 Buffer: "; for (size_t i = 0; i < buffer.size(); ++i) { printf("%02X ", buffer[i]); } std::cout << "\n\n"; const uint8_t *pnalu = nullptr; uint32_t nale_len = 0; const uint8_t *pRecord = NULL; // 初始時為 NULL int ret; int nalu_index = 1; // 循環(huán)分割并打印每個 NALU while ((ret = H264Parse::nalu_tok(buffer.data(), buffer.size(), &pnalu, nale_len, &pRecord)) != -1) { print_nalu(pnalu, nale_len, nalu_index); nalu_index++; if (ret == 0) break; } if (ret == -1) { std::cout << "Error occurred during NALU tokenization." << std::endl; } } // 主函數(shù) int main() { test1(); // test2(); return 0; }
以上就是C/C++實現(xiàn)H264文件解析的詳細內(nèi)容,更多關于C++ H264文件解析的資料請關注腳本之家其它相關文章!
相關文章
c++如何在主函數(shù)文件中調用其他函數(shù)文件
這篇文章主要介紹了c++如何在主函數(shù)文件中調用其他函數(shù)文件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08C++基于reactor的服務器百萬并發(fā)實現(xiàn)與講解
這篇文章主要介紹了C++基于reactor的服務器百萬并發(fā)實現(xiàn)與講解,本文通過實例圖文相結合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07C++一個函數(shù)如何調用其他.cpp文件中的函數(shù)
這篇文章主要介紹了C++一個函數(shù)如何調用其他.cpp文件中的函數(shù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02