admin 管理员组

文章数量: 1184232

H264码流是由一系列的NALU单元构成,因此读取H264码流就转变为读取一系列的NALU单元。

1、NALU单元的读取
(1)NALU单元的开始标志是0x00000001。因此读取NALU单元时,先从文件读取4字节数据,判断是否是NALU的开始标志,只有是NALU的开始标准时,才进行NALU数据的读取。
(2)读取NALU数据时,从开始标志位后开始读取,每次读取1字节,直到碰到下个NALU开始标志;当碰到下个NALU开始标志时,文件标志向前回退4字节,指向下个NALU开始标志,NALU单元的长度回退4,退出循环完成1NALU的读取。

// 从文件中获取一个NALU(网络抽象层单元),传参时已为NALU开辟空间
int get_nalu(FILE* pin_file,unsigned char* pnalu){   
    int pos =0;                            // NALU单元长度
    if(!pin_file)return -1;
    
    // 读取NALU的前4字节
    if(fread(pnalu,1,4,pin_file)<=0)return -1;
    // 如果不是NALU的起始标志
    if(pnalu[0]!=0|| pnalu[1]!=0|| pnalu[2]!=0|| pnalu[3]!=1)return -1;
    pos =4;                                // pos指向NALU数据处
    while(1){
        if(feof(pin_file))break;
        pnalu[pos]= fgetc(pin_file);       // 从H264码流中读取1字节
        if(pnalu[pos - 3]==0&& pnalu[pos - 2]==0&& pnalu[pos - 1]==0&& pnalu[pos]==1)    //读到下个NALU开始标志处
        {
            fseek(pin_file,-4,SEEK_CUR);    // 文件指针回退4字节
            pos -=4;break;                          // 1NALU读取完成
        }
        pos++;}return pos + 1;                         //返回1NALU长度
}

2、将H264数据封装为mp4文件
(1)打开H264文件,创建MP4文件,为MP4文件添加时间戳;
(2)读取单个NALU单元,NALU中第5字节代表NALU属性,判断NALU属性,SPS单元代表一个GOP的开始;第一次读取到SPS时,为MP4文件添加一个视频轨道,并且设置编码规范与级别;MP4AddH264SequenceParameterSet将SPS写入MP4文件中;
(3)若NALU单元属性是PPS,调用MP4AddH264SequenceParameterSet将PPS写入mp4文件中;
(4)否则读到的NALU单元就是代表NALU数据,将pbuf中前4为写为NALU数据长度,将NALU数据写入mp4文件。
(5)重复上述步骤,直到读到H264文件末端。

// 将H.264数据封装成MP4文件
int pack_h264_mp4(const char* pin_file,const char* pout_file){
    FILE* pin = NULL;                           // H264文件句柄
    unsigned char* pnalu = malloc(1024*1024);   // 为1NALU分配空间
    unsigned char* pnalu_data = NULL;           // NALUz数据指针
    unsigned char  nalu_type;                   // NALU属性
    int nalu_len;                               // 读取到NALU长度
    MP4FileHandle pMP4_Handle = NULL;           // MP4文件句柄
    MP4TrackId video_Id;                        // 视频轨道ID
    int width =640;                            // 设置视频宽度
    int height =480;                           // 设置视频高度
    int frame_rate =25;                        // 设置视频帧率
    int time_scale =90000;                     // 设置视频时间刻度
    int add_stream =1;                         // 是否添加视频流的标志
    if(!(pin = fopen(pin_file,"rb")))return -1;if((pMP4_Handle = MP4Create(pout_file,0))== MP4_INVALID_FILE_HANDLE){
        printf("ERROR:Create mp4 handle failed.\n");return -1;                              // 文件创建失败
    }
    MP4SetTimeScale(pMP4_Handle,time_scale);                          // 为MP4文件设置时间刻度,加时间戳
    while(1){if((nalu_len = get_nalu(pin,pnalu))<=0)break;
        if(pnalu[0]!=0|| pnalu[1]!=0|| pnalu[2]!=0|| pnalu[3]!=1)continue;
        nalu_len -=4;                          // NALU数据长度
        pnalu_data = pnalu + 4;                 // 找到nalu中数据的起始地址
        nalu_type = pnalu_data[0]& 0x1F;       // 判断NALU类型
        switch(nalu_type){case 0x7F:                          //SPS(视频序列相关参数)
                printf("----------SPS(%d)----------\n",nalu_len);
                if(add_stream){
                    // 为MP4文件添加视频轨道
                    if((video_Id = MP4AddH264VideoTrack(
                        pMP4_Handle,
                        time_scale,
                        time_scale/frame_rate,
                        width,
                        height,
                        pnalu_data[1],
                        pnalu_data[2],
                        pnalu_data[3],3))== MP4_INVALID_TRACK_ID){
                        printf("ERROR:Can't add track.\n");return -1;}
                    MP4SetVideoProfileLevel(pMP4_Handle,0x7F);      // 为视频轨道设置编码规范与级别
                    add_stream =0;                                 // 只用添加1次视频轨道
                }
                MP4AddH264PictureParameterSet(pMP4_Handle,video_Id,pnalu_data,nalu_len);    // 将SPS写入MP4文件
                break;case 0x08:
                printf("------------PPS(%d)------------\n",nalu_len);
                MP4AddH264PictureParameterSet(pMP4_Handle,video_Id,pnalu_data,nalu_len);    // 将PPS数据写入MP4文件
                break;
            
            default:
                printf("------------NALU_DATA(%d)------------\n",nalu_len);
                pnalu[0]=(nalu_len >>24)& 0xFF;
                pnalu[1]=(nalu_len >>16)& 0xFF;
                pnalu[2]=(nalu_len >>8)& 0xFF;
                pnalu[3]=(nalu_len >>0)& 0xFF;
                MP4WriteSample(pMP4_Handle,video_Id,pnalu,nalu_len + 4,MP4_INVALID_DURATION,0,1);    // 将PPS数据写入MP4文件
                break;}}
    free(pnalu_data);
    fclose(pin);
    MP4Close(pMP4_Handle,0);    // 关闭MP4文件
    return0;}

完整代码如下:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include "mp4v2/mp4v2.h"
int get_nalu(FILE* pin_file,unsigned char* pnalu){   
    int pos =0;                            // NALU单元长度
    if(!pin_file)return -1;
    
    // 读取NALU的前4字节
    if(fread(pnalu,1,4,pin_file)<=0)return -1;
    // 如果不是NALU的起始标志
    if(pnalu[0]!=0|| pnalu[1]!=0|| pnalu[2]!=0|| pnalu[3]!=1)return -1;
    pos =4;                                // pos指向NALU数据处
    while(1){
        if(feof(pin_file))break;
        pnalu[pos]= fgetc(pin_file);       // 从H264码流中读取1字节
        if(pnalu[pos - 3]==0&& pnalu[pos - 2]==0&& pnalu[pos - 1]==0&& pnalu[pos]==1)    //读到下个NALU开始标志处
        {
            fseek(pin_file,-4,SEEK_CUR);    // 文件指针回退4字节
            pos -=4;break;                          // 1NALU读取完成
        }
        pos++;}return pos + 1;                         //返回1NALU长度
}
int pack_h264_mp4(const char* pin_file,const char* pout_file){
    FILE* pin = NULL;                           // H264文件句柄
    unsigned char* pnalu = malloc(1024*1024);   // 为1NALU分配空间
    unsigned char* pnalu_data = NULL;           // NALUz数据指针
    unsigned char  nalu_type;                   // NALU属性
    int nalu_len;                               // 读取到NALU长度
    MP4FileHandle pMP4_Handle = NULL;           // MP4文件句柄
    MP4TrackId video_Id;                        // 视频轨道ID
    int width =640;                            // 设置视频宽度
    int height =480;                           // 设置视频高度
    int frame_rate =25;                        // 设置视频帧率
    int time_scale =90000;                     // 设置视频时间刻度
    int add_stream =1;                         // 是否添加视频流的标志
    if(!(pin = fopen(pin_file,"rb")))return -1;if((pMP4_Handle = MP4Create(pout_file,0))== MP4_INVALID_FILE_HANDLE){
        printf("ERROR:Create mp4 handle failed.\n");return -1;                              // 文件创建失败
    }
    MP4SetTimeScale(pMP4_Handle,time_scale);                          // 为MP4文件设置时间刻度,加时间戳
    while(1){if((nalu_len = get_nalu(pin,pnalu))<=0)break;
        if(pnalu[0]!=0|| pnalu[1]!=0|| pnalu[2]!=0|| pnalu[3]!=1)continue;
        nalu_len -=4;                          // NALU数据长度
        pnalu_data = pnalu + 4;                 // 找到nalu中数据的起始地址
        nalu_type = pnalu_data[0]& 0x1F;       // 判断NALU类型
        switch(nalu_type){case 0x7F:                          //SPS(视频序列相关参数)
                printf("----------SPS(%d)----------\n",nalu_len);
                if(add_stream){
                    // 为MP4文件添加视频轨道
                    if((video_Id = MP4AddH264VideoTrack(
                        pMP4_Handle,
                        time_scale,
                        time_scale/frame_rate,
                        width,
                        height,
                        pnalu_data[1],
                        pnalu_data[2],
                        pnalu_data[3],3))== MP4_INVALID_TRACK_ID){
                        printf("ERROR:Can't add track.\n");return -1;}
                    MP4SetVideoProfileLevel(pMP4_Handle,0x7F);      // 为视频轨道设置编码规范与级别
                    add_stream =0;                                 // 只用添加1次视频轨道
                }
                MP4AddH264PictureParameterSet(pMP4_Handle,video_Id,pnalu_data,nalu_len);    // 将SPS写入MP4文件
                break;case 0x08:
                printf("------------PPS(%d)------------\n",nalu_len);
                MP4AddH264PictureParameterSet(pMP4_Handle,video_Id,pnalu_data,nalu_len);    // 将PPS数据写入MP4文件
                break;
            
            default:
                printf("------------NALU_DATA(%d)------------\n",nalu_len);
                pnalu[0]=(nalu_len >>24)& 0xFF;
                pnalu[1]=(nalu_len >>16)& 0xFF;
                pnalu[2]=(nalu_len >>8)& 0xFF;
                pnalu[3]=(nalu_len >>0)& 0xFF;
                MP4WriteSample(pMP4_Handle,video_Id,pnalu,nalu_len + 4,MP4_INVALID_DURATION,0,1);    // 将PPS数据写入MP4文件
                break;}}
    free(pnalu_data);
    fclose(pin);
    MP4Close(pMP4_Handle,0);    // 关闭MP4文件
    return0;}
int main(){
    if(pack_h264_mp4("venc.h264","test.mp4")){
        printf("Error:Packet to mp4 fail.\n");return -1;}return0;}

本文标签: 文件 字节 编程