将H264码流封装为mp4文件

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文件

    return 0;
}

完整代码如下:

#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文件

    return 0;
}

int main()
{
    if(pack_h264_mp4("venc.h264","test.mp4"))
    {
        printf("Error:Packet to mp4 fail.\n");
        return -1;
    }
    return 0;
}

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

￴ㅤ￴￴ㅤ9527超级帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值