最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C/C++實(shí)現(xiàn)H264文件解析

 更新時(shí)間:2024年10月28日 10:13:43   作者:靜止了 所有的花開  
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)C++實(shí)現(xiàn)H264文件以及一段H264碼流解析,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

H264視頻編碼格式簡(jiǎn)介

H264是視頻壓縮編碼標(biāo)準(zhǔn)。視頻?件的傳輸是?個(gè)極?的問(wèn)題:?段分辨率為19201080的視頻,每個(gè)像素點(diǎn)為RGB占?3個(gè)字節(jié),幀率是25的視頻,對(duì)于傳輸帶寬的要求是:192010803*25/1024/1024=148.315MB/s,換成bps則意味著視頻每秒帶寬為1186.523Mbps,這樣的速率對(duì)于?絡(luò)存儲(chǔ)是不可接受的。因此視頻壓縮和編碼技術(shù)應(yīng)運(yùn)??。

H264碼流分析

通常我們使用H264碼流分析工具時(shí),我們會(huì)發(fā)現(xiàn)H264碼流是由連續(xù)的幀組成的,一組連續(xù)的幀成為1個(gè)GOP即1組,對(duì)于1個(gè)GOP來(lái)說(shuō),通常包括SPS(序列參數(shù)集,編碼視頻序列的全局參數(shù))PPS(圖像參數(shù)集,?個(gè)序列中某?幅圖像或者某?幅圖像的參數(shù)),IDR幀(第一個(gè)出現(xiàn)的I幀成為IDR幀,1個(gè)GOP中通常只包含1個(gè)IDR幀),P幀(GOP中剩余幀基本都是P幀);GOP描述如下:

H264碼流結(jié)構(gòu)如圖:

C/C++ H264文件解析

C++實(shí)現(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 從文件中讀取一個(gè)nalu,包含起始碼
     * @param buf 存放nalu的緩沖區(qū)
     * @param size 緩沖區(qū)大小
     * @param len nalu的長(zhǎng)度
     * @param n 每次讀取多少個(gè)字節(jié)
     * @return  -1 失敗    0 已到文件末尾     1 成功獲取到一個(gè)nalu
     */
    int read_nalu(uint8_t *buf, uint32_t size, uint32_t &len, uint32_t n);
 
    void close_file();
 
    // 獲取起始碼長(zhǎng)度
    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的長(zhǎng)度
     * @param record Pointer用于記錄狀態(tài),第一次分割時(shí)把 *record 賦值為NULL
     * @return  -1 失敗    0 已分割完     1 成功獲取到一個(gè)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; // 無(wú)效的起始碼
}
 
// 讀取起始碼,并返回其長(zhǎng)度
int H264Parse::read_start_code(uint8_t *buf)
{
    // 讀取前4個(gè)字節(jié)來(lái)判斷起始碼長(zhǎng)度
    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;
}
 
// 調(diào)整文件指針位置
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(); // 達(dá)到文件末尾了要先清除 eof 標(biāo)志
        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é)的長(zhǎng)度
 
    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; // 當(dāng)前記錄位置超出緩沖區(qū)
    }
 
    int scLen = get_startCode_len(current);
    if (scLen == -1 || (current + scLen) > (stream + streamLen))
    {
        return -1; // 無(wú)效的起始碼或起始碼長(zhǎng)度超出緩沖區(qū)
    }
 
    // 查找下一個(gè)起始碼的位置
    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; // 成功獲取到一個(gè) NALU
    }
    else
    {
        // 最后一個(gè) NALU
        *nalu = current;
        naluLen = streamLen - offset;
        *record = NULL; // 重置記錄指針
        return 0;       // 分割完畢
    }
}

測(cè)試:

#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()
{
    // 構(gòu)造一個(gè)模擬的 H.264 碼流緩沖區(qū),包含多個(gè) NALU
    // 起始碼格式:0x000001 (3 字節(jié)) 和 0x00000001 (4 字節(jié))
    // NALU 內(nèi)容:隨機(jī)填充的字節(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ù) (測(cè)試末尾)
    std::vector<uint8_t> nalu4 = {0x00, 0x00, 0x01, 0x07, 0xAD, 0xBE};
    buffer.insert(buffer.end(), nalu4.begin(), nalu4.end());
 
    // 輸出構(gòu)建的緩沖區(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; // 初始時(shí)為 NULL
 
    int ret;
    int nalu_index = 1;
 
    // 循環(huán)分割并打印每個(gè) 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++實(shí)現(xiàn)H264文件解析的詳細(xì)內(nèi)容,更多關(guān)于C++ H264文件解析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C/C++實(shí)現(xiàn)的游戲角色名稱名字隨機(jī)生成代碼

    C/C++實(shí)現(xiàn)的游戲角色名稱名字隨機(jī)生成代碼

    這篇文章主要介紹了C/C++實(shí)現(xiàn)的游戲角色名稱名字隨機(jī)生成代碼,本文特別針對(duì)一些古典游戲的角色名稱進(jìn)行隨機(jī)生成,需要的朋友可以參考下
    2015-05-05
  • C++中cin.getline()和getline()函數(shù)的區(qū)別小結(jié)

    C++中cin.getline()和getline()函數(shù)的區(qū)別小結(jié)

    這篇文章主要介紹了C++中cin.getline()和getline()函數(shù)區(qū)別的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • QT實(shí)現(xiàn)自定義Http客戶端的示例代碼

    QT實(shí)現(xiàn)自定義Http客戶端的示例代碼

    這篇文章主要為大家詳細(xì)介紹了QT如何實(shí)現(xiàn)自定義Http客戶端的,可以實(shí)現(xiàn)支持get,post請(qǐng)求方式;支持連接超時(shí)處理;支持網(wǎng)絡(luò)錯(cuò)誤,嘗試重連等功能,感興趣的小伙伴可以學(xué)習(xí)一下
    2022-11-11
  • C語(yǔ)言保留字與標(biāo)識(shí)符規(guī)則的詳細(xì)指南

    C語(yǔ)言保留字與標(biāo)識(shí)符規(guī)則的詳細(xì)指南

    C語(yǔ)言作為一種高效、靈活的編程語(yǔ)言,其核心構(gòu)建塊——保留字和標(biāo)識(shí)符——構(gòu)成了代碼的骨架,本文將深入探討這些概念,輔以代碼示例、圖表和外部資源鏈接,助你全面掌握,需要的朋友可以參考下
    2026-03-03
  • C語(yǔ)言實(shí)現(xiàn)旅游景點(diǎn)咨詢系統(tǒng)

    C語(yǔ)言實(shí)現(xiàn)旅游景點(diǎn)咨詢系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)旅游景點(diǎn)咨詢系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • QT實(shí)現(xiàn)簡(jiǎn)單打地鼠游戲

    QT實(shí)現(xiàn)簡(jiǎn)單打地鼠游戲

    這篇文章主要為大家詳細(xì)介紹了QT實(shí)現(xiàn)簡(jiǎn)單打地鼠游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • C++類繼承時(shí)的構(gòu)造函數(shù)

    C++類繼承時(shí)的構(gòu)造函數(shù)

    這篇文章主要介紹了C++類繼承時(shí)的構(gòu)造函數(shù),C++中,子類繼承父類除去構(gòu)造函數(shù)和析構(gòu)函數(shù)以外的所有成員。因此,子類需要編寫自己的構(gòu)造函數(shù)和析構(gòu)函數(shù)。更多相關(guān)詳情需要的小伙伴可以參考下面文章介紹
    2022-03-03
  • 數(shù)組指針、指針數(shù)組以及二位數(shù)組的深入解析

    數(shù)組指針、指針數(shù)組以及二位數(shù)組的深入解析

    下面來(lái)講講多維數(shù)組與指針的關(guān)系。與普通數(shù)組一樣,使用多維數(shù)組時(shí),實(shí)際上將其自動(dòng)轉(zhuǎn)換為指向該數(shù)組第一個(gè)元素的指針
    2013-09-09
  • C語(yǔ)言循環(huán)控制入門介紹

    C語(yǔ)言循環(huán)控制入門介紹

    大家好,本篇文章主要講的是C語(yǔ)言循環(huán)控制入門介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • C++中?‘=default?’及‘?=delete?’的使用

    C++中?‘=default?’及‘?=delete?’的使用

    這篇文章主要介紹了C++中?=default?及?=delete?使用,使用=default和=delete可以控制編譯器默認(rèn)函數(shù)體的使用,下面我們就來(lái)看看具體的室友方法吧,需要的朋友也可以參考一下
    2021-12-12

最新評(píng)論

阿合奇县| 安龙县| 惠水县| 应城市| 耒阳市| 丰宁| 麦盖提县| 香格里拉县| 连州市| 繁峙县| 柘荣县| 霍山县| 正阳县| 什邡市| 行唐县| 凤城市| 漳平市| 霍山县| 科技| 浦北县| 宁河县| 离岛区| 克什克腾旗| 鄯善县| 金平| 怀安县| 磐安县| 乌拉特中旗| 汪清县| 九龙城区| 鸡泽县| 密云县| 小金县| 上饶市| 阿尔山市| 依兰县| 紫云| 宁晋县| 富裕县| 双桥区| 苍南县|