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;
}