测试样例,记录带以后查阅。
LiveRtspClient关键代码:
void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,struct timeval presentationTime, unsigned /*durationInMicroseconds*/)
{
// We've just received a frame of data. (Optionally) print out information about it:
#ifdef DEBUG_PRINT_EACH_RECEIVED_FRAME
if (fStreamId != NULL)
envir() << "Stream \"" << fStreamId << "\"; ";
envir() << fSubsession.mediumName() << "/" << fSubsession.codecName() << ":\tReceived " << frameSize << " bytes";
if (numTruncatedBytes > 0)
envir() << " (with " << numTruncatedBytes << " bytes truncated)";
char uSecsStr[6+1]; // used to output the 'microseconds' part of the presentation time
sprintf(uSecsStr, "%06u", (unsigned)presentationTime.tv_usec);
envir() << ".\tPresentation time: " << (int)presentationTime.tv_sec << "." << uSecsStr<<"\n";
envir() << "Width:"<<(int)fSubsession.videoWidth()<<" High:"<<(int)fSubsession.videoHeight()<<"\n";
envir() << "Scale:"<<(int)fSubsession.scale()<<" Speed:"<<(int)fSubsession.speed()<<"\n";
envir() << "FPS:"<<(int)fSubsession.videoFPS()<<" Channels:"<<(int)fSubsession.numChannels()<<"\n";
if (fSubsession.rtpSource() != NULL && !fSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP())
{
envir() << "!"; // mark the debugging output to indicate that this presentation time is not RTCP-synchronized
}
#ifdef DEBUG_PRINT_NPT
envir() << "\tNPT: " << fSubsession.getNormalPlayTime(presentationTime);
#endif
envir() << "\n";
#endif
CliveRtsp* pThis = ((CliveRtsp*)pUser);
long long nTimeStamp = presentationTime.tv_sec*1000+presentationTime.tv_usec/1000;
if(strcmp(fSubsession.mediumName(), "video") == 0 &&(strcmp(fSubsession.codecName(), "H264") == 0))
{
if(firstFrame)
{
unsigned int num;
SPropRecord *sps = parseSPropParameterSets(fSubsession.fmtp_spropparametersets(), num);
CMP4.CreatMP4File("../test.mp4");
CMP4.WriteH264_SPSandPPS(sps[0].sPropBytes,sps[0].sPropLength,sps[1].sPropBytes,sps[1].sPropLength);
firstFrame = false;
}
CMP4.WriteH264_NALU(fReceiveBuffer,frameSize,nTimeStamp);
}
if(strcmp(fSubsession.mediumName(), "audio") == 0)
{
envir() << "Get Audio "<
Gpac封装了个类,其中也实现了SPS信息头的解析用于获取视频宽高,不知道为什么有时候LiveRtspClient中获取到的视频宽高为0,这样Mp4文件的创建就会有问题,所以干脆自己解决了。
头文件:
#ifndef _CMP4ENCODE_H_
#define _CMP4ENCODE_H_
#include "gpac/isomedia.h"
typedef unsigned char UINT8;
//只保存视频数据
class CMP4Encode
{
public:
CMP4Encode(void);
~CMP4Encode(void);
public:
bool CreatMP4File(char*filename);
bool WriteH264_SPSandPPS(unsigned char*sps,int spslen,unsigned char*pps,int ppslen);
void WriteH264_NALU(unsigned char*pData,int len,long nTimeStamp);
void CloseFile();
private:
bool WriteH264Frame(unsigned char*data,int len,bool keyframe,long nTimeStamp);
void ParserSps(unsigned char*pData); //解Sps获取宽 视频高
int get_bit_at_position(UINT8*buf,UINT8 &bytePosition,UINT8 &bitPosition); //取当前字节 当前位 的值
int get_u_code_num(UINT8 *buf, UINT8 &bytePosition, UINT8 &bitPosition,UINT8 bitCount);//获取 从左往右 n个bit组成的值
int get_ue_code_num(UINT8 *buf, UINT8 &bytePosition, UINT8 &bitPosition); //ue(v)无符号指数哥伦布编码
int get_se_code_num(UINT8 *buf, UINT8 &bytePosition, UINT8 &bitPosition); //se(v)有符号指数哥伦布编码
private:
GF_ISOFile *p_file;//MP4文件
GF_AVCConfig *p_config;//MP4配置
GF_ISOSample *p_videosample;//MP4帧
unsigned int i_videodescidx;
long m_videostartimestamp;//视频时间戳
int m_videtrackid; //视频ID
int m_wight; //视频宽
int m_hight; //视频高
int m_Fps; //视频fps
long long Total_NALUSize;//总计写入NALU大小
};
#endif
cpp文件:
#include "CMP4Encode.h"
#include
CMP4Encode::CMP4Encode(void)
{
m_videostartimestamp = -1;
p_file = NULL;
p_config = NULL;
p_videosample = NULL;
m_wight = 0;
m_hight = 0;
m_Fps = 0;
Total_NALUSize = 0;
}
CMP4Encode::~CMP4Encode(void)
{
CloseFile();
}
bool CMP4Encode::CreatMP4File(char*filename)
{
if(filename==NULL)
return false;//打开文件
p_file=gf_isom_open(filename,GF_ISOM_OPEN_WRITE,NULL);//打开文件
if (p_file==NULL)
return false;
gf_isom_set_brand_info(p_file,GF_ISOM_BRAND_MP42,0); //设置视频类型 mp42
return true;
}
//写入 sps pps
bool CMP4Encode::WriteH264_SPSandPPS(unsigned char*sps,int spslen,unsigned char*pps,int ppslen)
{
if(p_file == NULL)
return false;
ParserSps(sps);//解析sps 求视频宽高
if(m_wight==0||m_hight==0)
return false;
m_videtrackid = gf_isom_new_track(p_file,0,GF_ISOM_MEDIA_VISUAL,1000);
if(m_videtrackid == NULL)
return false;
gf_isom_set_track_enabled(p_file,m_videtrackid,1);
p_videosample = gf_isom_sample_new(); //创建一个新的样本
p_videosample->data=(char*)malloc(1024*1024); //分配内存
p_config = gf_odf_avc_cfg_new();//MP4配置 构造函数
//创建新的avc配置函数
gf_isom_avc_config_new(p_file,m_videtrackid,p_config,NULL,NULL,&i_videodescidx);
//为其指定宽高
gf_isom_set_visual_info(p_file,m_videtrackid,i_videodescidx,m_wight,m_hight);
GF_AVCConfigSlot m_slotsps={0};
GF_AVCConfigSlot m_slotpps={0};
//配置sps信息
p_config->configurationVersion = 1;
p_config->AVCProfileIndication = sps[1];
p_config->profile_compatibility = sps[2];
p_config->AVCLevelIndication = sps[3];
m_slotsps.size=spslen;
m_slotsps.data=(char*)malloc(spslen);
memcpy(m_slotsps.data,sps,spslen);
gf_list_add(p_config->sequenceParameterSets,&m_slotsps);
//配置pps信息
m_slotpps.size=ppslen;
m_slotpps.data=(char*)malloc(ppslen);
memcpy(m_slotpps.data,pps,ppslen);
gf_list_add(p_config->pictureParameterSets,&m_slotpps);
//更新 AVC 配置
gf_isom_avc_config_update(p_file,m_videtrackid,1,p_config);
free(m_slotsps.data);
free(m_slotpps.data);
return true;
}
/*
解析NAL第一个字符的标志位 确定其类型 只判断 profile_idc(5 bit)
sps -- 7
pps -- 8
IDR帧 -- 5
非IDR帧 -- 1
SEI -- 6 补充增强信息单元
*/
void CMP4Encode::WriteH264_NALU(unsigned char*pData,int len,long nTimeStamp)
{
if((pData == NULL)||(len == 0))
return;
unsigned char* RealData = new unsigned char[len+4];//填充长度 高在前
RealData[0] = (len>>24)&0xFF;
RealData[1] = (len>>16)&0xFF;
RealData[2] = (len>>8)&0xFF;
RealData[3] = len&0xFF;
memcpy(RealData+4,pData,len);
//写入数据到MP4文件
unsigned int Type = pData[0]&0x1f;
switch(Type)
{
case 1:
WriteH264Frame(RealData,len+4,false,nTimeStamp);
break;
case 5:
WriteH264Frame(RealData,len+4,true,nTimeStamp);
break;
case 6:
WriteH264Frame(RealData,len+4,false,nTimeStamp);
break;
case 7:
WriteH264Frame(RealData,len+4,false,nTimeStamp);
break;
case 8:
WriteH264Frame(RealData,len+4,false,nTimeStamp);
break;
default:break;
}
delete []RealData;
return ;
}
//写入一帧,前四字节为该帧NAL长度
bool CMP4Encode::WriteH264Frame(unsigned char*data,int len,bool keyframe, long nTimeStamp)
{
if (!p_videosample)
return false;
////获取系统时间
//win_time_val_t wintv;
// win_gettimeofday(&wintv);
// long long timestamp = wintv.sec * 1000 + wintv.msec / 1000;
if (m_videostartimestamp == -1) //判断IDR帧 easyplayer中应该是一整帧
{
m_videostartimestamp = nTimeStamp;
}
if (m_videostartimestamp!=-1)
{
p_videosample->IsRAP = keyframe;
p_videosample->dataLength = len;
p_videosample->data = (char*)data;
memcpy(p_videosample->data,data,len);
p_videosample->DTS= nTimeStamp - m_videostartimestamp;
p_videosample->CTS_Offset = 0;
GF_Err gferr=gf_isom_add_sample(p_file,m_videtrackid,i_videodescidx,p_videosample);
if (gferr == -1)
{
p_videosample->DTS = nTimeStamp - m_videostartimestamp + 15;
gf_isom_add_sample(p_file,m_videtrackid,i_videodescidx,p_videosample);
}
}
Total_NALUSize += len;
return true;
}
void CMP4Encode::CloseFile()
{
m_videostartimestamp = -1;
if (p_file) //清理文件
{
gf_isom_close(p_file);
p_file=NULL;
}
if(p_config) //清理文件配置
{
p_config->pictureParameterSets=NULL;
p_config->sequenceParameterSets=NULL;
gf_odf_avc_cfg_del(p_config);
p_config=NULL;
}
if(p_videosample) //清理数据帧
{
if(p_videosample->data)
{
free(p_videosample->data);
p_videosample->data=NULL;
}
gf_isom_sample_del(&p_videosample);
p_videosample = NULL;
}
//清理视频参数
m_wight = 0;
m_hight = 0;
m_Fps = 0;
Total_NALUSize = 0;
return;
}
//解Sps获取宽 视频高
void CMP4Encode::ParserSps(unsigned char*strArray)
{
UINT8 bytePosition = 0, bitPosition = 0;
//NALU 头
int forbidden_bit = get_u_code_num(strArray,bytePosition,bitPosition,1);
int nal_ref_idc = get_u_code_num(strArray,bytePosition,bitPosition,2);
int nal_unit_type = get_u_code_num(strArray,bytePosition,bitPosition,5); //0x07
//这里可能包含防竞争码 需做处理海康视频流中并未见此字段
//sps信息
int profile_idc = get_u_code_num(strArray,bytePosition,bitPosition,8);
int constraint_set0_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
int constraint_set1_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
int constraint_set2_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
int constraint_set3_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
int constraint_set4_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
int constraint_set5_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
int reserve_zero_2bit = get_u_code_num(strArray,bytePosition,bitPosition,2);
int level_idc = get_u_code_num(strArray,bytePosition,bitPosition,8);
int seq_parameter_set_id = get_ue_code_num(strArray,bytePosition,bitPosition);
int chroma_format_idc = 0;
if (profile_idc == 100 || profile_idc == 110 ||profile_idc == 122 || profile_idc == 244 ||
profile_idc == 44 || profile_idc == 83 ||profile_idc == 86 || profile_idc == 118 ||profile_idc == 128||
profile_idc == 138 || profile_idc == 139 || profile_idc == 134 || profile_idc == 135)
{
chroma_format_idc = get_ue_code_num(strArray,bytePosition,bitPosition);
if(chroma_format_idc == 3)
{
int separte_colour_plane_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
}
int bit_depth_luma_minus8 = get_ue_code_num(strArray,bytePosition,bitPosition);
int bit_depth_chroma_minus8 = get_ue_code_num(strArray,bytePosition,bitPosition);
int qpprime_y_zero_transfrom_bypass_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
int seq_scaling_matrix_preasent_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
if(seq_scaling_matrix_preasent_flag)
{
int seq_scaling_list_present_flag[12] = {0};
for(int i = 0;i<((chroma_format_idc!=3)?8:12);i++)
{
seq_scaling_list_present_flag[i] = get_u_code_num(strArray,bytePosition,bitPosition,1);
//余下不解析
}
}
}
int log2_max_frame_num_minus4 = get_ue_code_num(strArray,bytePosition,bitPosition);
int pic_order_cnt_type = get_ue_code_num(strArray,bytePosition,bitPosition);
if(pic_order_cnt_type == 0)
{
int log2_max_pic_order_cnt_lsb_minus4 = get_ue_code_num(strArray,bytePosition,bitPosition);
}
else if(pic_order_cnt_type == 1)
{
int delta_pic_order_always_zero_flag = get_u_code_num(strArray,bytePosition,bitPosition,1);
int offset_for_non_ref_pic = get_se_code_num(strArray,bytePosition,bitPosition);
int offset_for_top_to_bottom_field = get_se_code_num(strArray,bytePosition,bitPosition);
int num_ref_frames_in_pic_order_cnt_cycle = get_ue_code_num(strArray,bytePosition,bitPosition);
int* offset_for_ref_frame = new int[num_ref_frames_in_pic_order_cnt_cycle];
for(int i = 0;i7)
{
bytePosition++;
bitPosition = 0;
}
return val;
}
//获取 从左往右 n个bit组成的值
int CMP4Encode::get_u_code_num(UINT8 *buf, UINT8 &bytePosition, UINT8 &bitPosition,UINT8 bitCount)
{
UINT8 bitVal = 0;
int code_num = 0;
for (int bit_pos = bitCount-1; bit_pos >-1; bit_pos--)
{
bitVal = get_bit_at_position(buf, bytePosition, bitPosition);
code_num += (1 << bit_pos)*bitVal;
}
return code_num;
}
//ue(v)无符号指数哥伦布编码
int CMP4Encode::get_ue_code_num(UINT8 *buf, UINT8 &bytePosition, UINT8 &bitPosition)
{
if(bitPosition > 0x08)
return 0;
UINT8 bitVal = 0;
int codeNum = 0,prefix = 0, surfix = 0,leadingZeroBits = 0;
//获取前导0的个数
while(true)
{
bitVal = get_bit_at_position(buf, bytePosition, bitPosition);
if (0 == bitVal)
{
leadingZeroBits++; //前导 0 的统计
}
else
{
break;
}
}
//计算前缀
prefix = (1 << leadingZeroBits) - 1;
//计算后缀
for (int bit_pos = leadingZeroBits-1; bit_pos >-1; bit_pos--)
{
bitVal = get_bit_at_position(buf, bytePosition, bitPosition);
surfix += (1 << bit_pos)*bitVal;
}
codeNum = prefix + surfix;
return codeNum;
}
//se(v)有符号指数哥伦布编码
int CMP4Encode::get_se_code_num(UINT8 *buf, UINT8 &bytePosition, UINT8 &bitPosition)
{
if(bitPosition > 0x08)
return 0;
UINT8 bitVal = 0;
int SeNum = 0,codeNum = 0,prefix = 0, surfix = 0,leadingZeroBits = 0;
//获取前导0的个数
while(true)
{
bitVal = get_bit_at_position(buf, bytePosition, bitPosition);
if (0 == bitVal)
{
leadingZeroBits++; //前导 0 的统计
}
else
{
break;
}
}
/*如 0 0 0 1 0 1 1
前缀prefix 为 2^(leadingZeroBits = 3) -1 = 7
后缀surfix 0 1 1 = 0*2^2 + 1*2^1 +1*2^0 = 3
codeNum = prefix + surfix = 10
*/
prefix = (1 << leadingZeroBits) - 1;
for (int bit_pos = leadingZeroBits-1; bit_pos >-1; bit_pos--)
{
bitVal = get_bit_at_position(buf, bytePosition, bitPosition);
surfix += (1 << bit_pos)*bitVal;
}
codeNum = prefix + surfix;
//计算Se 公式 (−1)codeNum+1 Ceil(codeNum÷2 )
SeNum = ceil(codeNum/2.0);
if((codeNum+1)%2) //奇
return -SeNum;
return SeNum;
}
粗糙的demo代码,细节部分可自行处理设计。
资源库链接:https://download.csdn.net/my