RTSP轉RTMP同時儲存成mp4檔案
阿新 • • 發佈:2019-01-08
一 背景:
使用者需要通過flash或手機看監控視訊,而目前絕大多數攝像機都是RTSP協議,所以需要做一箇中轉。
參考資料:
http://blog.csdn.net/firehood_/article/details/8813587
http://blog.csdn.net/firehood_/article/details/8783589
這兩篇文章詳細說明了mp4v2儲存檔案和把264檔案通過RTMP直播,對這兩方面感興趣的可以直接看這兩文章。本文只是把這兩文章中讀264轉存轉發改成讀rtsp轉存轉發,多了一點live555的內容,其他基本一樣,只稍調整了一點點介面部分。
二 流程:
1 live555從攝像機讀音視訊,為了便於講解這裡只討論視訊。
2 mp4v2儲存成.mp4
3 librtmp上傳視訊流到nginx
4 web使用者通過flash觀看
三 技術講解:
1 live555讀RTSP視訊,可以參考playCommon.cpp或testRTSPClient.cpp,前者功能全,後者簡潔一些,不過支援多路流同時訪問。如果自己寫RTSP Client,環境不是很複雜,可以參考這份程式碼。
RTSP接收到H264視訊後,不管是讀寫檔案還是傳送流,主要的問題就在SPS和PPS上面。這兩個名字這裡不做解釋,自行google,這裡只講怎麼取到這兩個引數。
讀live的原始碼,注意 playCommon.cpp中有一段:
else if (strcmp(subsession->mediumName(), "video") == 0 &&
(strcmp(subsession->codecName(), "H264") == 0)) {
// For H.264 video stream, we use a special sink that adds 'start codes', and (at the start) the SPS and PPS NAL units:
fileSink = H264VideoFileSink::createNew(*env, outFileName,
subsession->fmtp_spropparametersets(),
fileSinkBufferSize, oneFilePerFrame);
}
這裡呼叫H264VideoFileSink儲存264檔案,播放正常,所以我們需要研究H264VideoFileSink的原始碼。
只需注意H264VideoFileSink.cpp裡面的afterGettingFrame函式,如下:
if (!fHaveWrittenFirstFrame) {
// If we have PPS/SPS NAL units encoded in a "sprop parameter string", prepend these to the file:
unsigned numSPropRecords;
SPropRecord* sPropRecords = parseSPropParameterSets(fSPropParameterSetsStr, numSPropRecords);
for (unsigned i = 0; i < numSPropRecords; ++i) {
addData(start_code, 4, presentationTime);
addData(sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength, presentationTime);
}
delete[] sPropRecords;
fHaveWrittenFirstFrame = True; // for next time
}
// Write the input data to the file, with the start code in front:
addData(start_code, 4, presentationTime);
註釋的很明顯了,這裡再簡單說一下:
for (unsigned i = 0; i < numSPropRecords; ++i) {
addData(start_code, 4, presentationTime);
addData(sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength, presentationTime);
}
這裡的sPropRecords是從外面傳進來的引數:subsession->fmtp_spropparametersets(),從名字上看就是sps和pps合在一起的內容。
debug就可以發現,numSPropRecords==2,for迴圈兩次,第一次是sps,第二次就是pps了。把這兩個記住,儲存264和傳送RTMP時寫在相應的位置,所有的工作基本就完成了。
2 mp4v2的api非常簡單,沒什麼好說,唯一注意一點就是先從sps中讀取視訊的寬和高。可以參考後面程式碼。
3 librtmp,也很簡單,只要注意先發關sps和pps
4 直播伺服器用nginx+nginx_rtmp_module, google一下,文件很多。
四 執行環境: CentOS6.4及Ubuntu 12下執行正常。windows也可以,多平臺時需要型別統一,比如UINT,BYTE,uint32_t, uint8_t ,稍稍改動一下就好。
五 rtsp部分簡單程式碼演示:
1 把testRTSPClient.cpp程式碼複製出來,放在自己的程式碼裡,RTSP接收功能就OK了。只改動一點點,以下是改動部分:
void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString) {
這裡,增加 :
//暫時只處理視訊
if (strcmp(scs.subsession->mediumName(), "video") == 0 &&
(strcmp(scs.subsession->codecName(), "H264") == 0))
{
DummySink* pVideoSink = DummySink::createNew(env, *scs.subsession, rtspClient->url());
pVideoSink->sps = scs.subsession->fmtp_spropparametersets();
scs.subsession->sink = pVideoSink;
}
//這部分程式碼參考playComm.cpp, sps是string變數,為了演示方便,直接宣告成public,這裡把sps pps內容當成字串傳到DummySink。
//1280*760*3
#define DUMMY_SINK_RECEIVE_BUFFER_SIZE 2764800
uint8_t* buff = NULL;
struct timeval start_time;
unsigned numSPropRecords;
SPropRecord* sPropRecords = NULL;
DummySink::DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId)
: MediaSink(env),
fSubsession(subsession) {
fStreamId = strDup(streamId);
fReceiveBuffer = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
buff = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
gettimeofday(&start_time, NULL);
}
DummySink::~DummySink() {
delete[] fReceiveBuffer;
delete[] fStreamId;
delete []buff;
}
void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigned /*durationInMicroseconds*/)
{
struct timeval cur;
gettimeofday(&cur, NULL);
static unsigned char const start_code[4] = {0x00, 0x00, 0x00, 0x01};
if (sPropRecords == NULL)
{
//這幾行程式碼出自H264VideoFileSink.cpp,先儲存成sps.264是考慮儲存檔案和直播的時間不一定在什麼時候開始,先在本地快取一下。
//當然儲存在記憶體裡也可以。
FILE* pfSPS = fopen("sps.264", "wb");
sPropRecords = parseSPropParameterSets(sps.c_str(), numSPropRecords);
//這裡的sps是個string, 就是前面continueAfterSETUP程式碼裡的提到的。
for (unsigned i = 0; i < numSPropRecords; ++i)
{
if (pfSPS)
{
fwrite(start_code, 1, 4, pfSPS );
fwrite(sPropRecords[i].sPropBytes, 1, sPropRecords[i].sPropLength, pfSPS);
}
}
fclose(pfSPS);
gettimeofday(&start_time, NULL);
}
int timestamp = (cur.tv_sec-start_time.tv_sec)*1000 + (cur.tv_usec - start_time.tv_usec)/1000;
memset(buff, '0', DUMMY_SINK_RECEIVE_BUFFER_SIZE);
memcpy(buff, start_code, 4);
memcpy(buff+4, fReceiveBuffer, frameSize);
callback(buff, frameSize+4, timestamp);//這裡自行實現一個回撥函式,把視訊資料回調出去。
continuePlaying();
}
六:mp4v2儲存成.mp4,可以參考這個文章:http://blog.csdn.net/firehood_/article/details/8813587
不過這裡面有幾個引數沒說明怎麼得到,可以自己稍加處理,比如從sps裡讀出寬和高,參考:http://blog.csdn.net/firehood_/article/details/8783589,
同一作者寫的,如果認真看完他的文件,我這裡寫的都不需要看了。
bool h264_decode_sps(BYTE * buf,unsigned int nLen,int &width,int &height)
{
UINT StartBit=0;
int forbidden_zero_bit=u(1,buf,StartBit);
int nal_ref_idc=u(2,buf,StartBit);
int nal_unit_type=u(5,buf,StartBit);
if(nal_unit_type==7)
{
int profile_idc=u(8,buf,StartBit);
int constraint_set0_flag=u(1,buf,StartBit);//(buf[1] & 0x80)>>7;
int constraint_set1_flag=u(1,buf,StartBit);//(buf[1] & 0x40)>>6;
int constraint_set2_flag=u(1,buf,StartBit);//(buf[1] & 0x20)>>5;
int constraint_set3_flag=u(1,buf,StartBit);//(buf[1] & 0x10)>>4;
int reserved_zero_4bits=u(4,buf,StartBit);
int level_idc=u(8,buf,StartBit);
int seq_parameter_set_id=Ue(buf,nLen,StartBit);
if( profile_idc == 100 || profile_idc == 110 ||
profile_idc == 122 || profile_idc == 144 )
{
int chroma_format_idc=Ue(buf,nLen,StartBit);
if( chroma_format_idc == 3 )
int residual_colour_transform_flag=u(1,buf,StartBit);
int bit_depth_luma_minus8=Ue(buf,nLen,StartBit);
int bit_depth_chroma_minus8=Ue(buf,nLen,StartBit);
int qpprime_y_zero_transform_bypass_flag=u(1,buf,StartBit);
int seq_scaling_matrix_present_flag=u(1,buf,StartBit);
int seq_scaling_list_present_flag[8];
if( seq_scaling_matrix_present_flag )
{
for( int i = 0; i < 8; i++ ) {
seq_scaling_list_present_flag[i]=u(1,buf,StartBit);
}
}
}
int log2_max_frame_num_minus4=Ue(buf,nLen,StartBit);
int pic_order_cnt_type=Ue(buf,nLen,StartBit);
if( pic_order_cnt_type == 0 )
int log2_max_pic_order_cnt_lsb_minus4=Ue(buf,nLen,StartBit);
else if( pic_order_cnt_type == 1 )
{
int delta_pic_order_always_zero_flag=u(1,buf,StartBit);
int offset_for_non_ref_pic=Se(buf,nLen,StartBit);
int offset_for_top_to_bottom_field=Se(buf,nLen,StartBit);
int num_ref_frames_in_pic_order_cnt_cycle=Ue(buf,nLen,StartBit);
int *offset_for_ref_frame=new int[num_ref_frames_in_pic_order_cnt_cycle];
for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
offset_for_ref_frame[i]=Se(buf,nLen,StartBit);
delete [] offset_for_ref_frame;
}
int num_ref_frames=Ue(buf,nLen,StartBit);
int gaps_in_frame_num_value_allowed_flag=u(1,buf,StartBit);
int pic_width_in_mbs_minus1=Ue(buf,nLen,StartBit);
int pic_height_in_map_units_minus1=Ue(buf,nLen,StartBit);
width=(pic_width_in_mbs_minus1+1)*16;
height=(pic_height_in_map_units_minus1+1)*16;
return true;
}
else
return false;
}
順便考考大家,知道這個函式的引數BYTE * buf,unsigned int nLen從哪裡來的嗎?
對了,就是剛才儲存的sps.264,fopen,fread,讀出來,MP4Encoder這裡有個函式PraseMetadata,幫著分析sps pps。
大致如下:
MP4ENC_Metadata mp4meta;
PraseMetadata(buf, nLen, mp4meta);
int width = 0,height = 0;
if (!h264_decode_sps(mp4meta.Sps, mp4meta.nSpsLen, width, height))
return -1;
Handle = CreateMP4File(outFile, width, height);
Write264Metadata(&mp4meta);
然後每次回調回來:
WriteH264Data(data, len);
自己加個時間判斷,比如每幾分鐘儲存一個檔案,自己看著處理。回撥的時間戳這裡沒有用上,是傳送RTMP時用的。
七 RTMP傳送
參考上面提到的第二個連結,裡面是讀一個264檔案,然後傳送成RTMP流。這裡稍稍改一下,把實時的資料發出去。其實原來都一樣的,在live儲存檔案的時候
已經說過,檔案開頭儲存了sps和pps,然後依次是0x00, 0x00, 0x00, 0x01+視訊資料,這些直接fwrite到檔案裡就是.264。參考的文章就是傳送這個.264。
注意一下,傳送檔案和傳送實時資料流的唯一區別就是:檔案的頭儲存著sps資訊,而直播流需要讀快取裡的(一般rtsp會每隔一段時間再發,用這個也行,而且比儲存的更準確)。
那接下來就很簡單了,先修改一下SendH264File這個函式:
bool CRTMPStream::SendH264File(const char *pFileName)
{
if(pFileName == NULL)
{
return FALSE;
}
FILE *fp = fopen(pFileName, "rb");
if(!fp)
{
printf("ERROR:open file %s failed!",pFileName);
}
fseek(fp, 0, SEEK_SET);
m_nFileBufSize = fread(m_pFileBuf, sizeof(unsigned char), FILEBUFSIZE, fp);
if(m_nFileBufSize >= FILEBUFSIZE)
{
printf("warning : File size is larger than BUFSIZE\n");
}
fclose(fp);
RTMPMetadata metaData;
memset(&metaData,0,sizeof(RTMPMetadata));
NaluUnit naluUnit;
// 讀取SPS幀
ReadOneNaluFromBuf(naluUnit);
metaData.nSpsLen = naluUnit.size;
memcpy(metaData.Sps,naluUnit.data,naluUnit.size);
// 讀取PPS幀
ReadOneNaluFromBuf(naluUnit);
metaData.nPpsLen = naluUnit.size;
memcpy(metaData.Pps,naluUnit.data,naluUnit.size);
// 解碼SPS,獲取視訊影象寬、高資訊
int width = 0,height = 0;
h264_decode_sps(metaData.Sps,metaData.nSpsLen,width,height);
metaData.nWidth = width;
metaData.nHeight = height;
metaData.nFrameRate = 25;
// 傳送MetaData
return SendMetadata(&metaData);
//以下本文註釋掉,改從直播視訊裡發
/*unsigned int tick = 0;
while(ReadOneNaluFromBuf(naluUnit))
{
bool bKeyframe = (naluUnit.type == 0x05) ? TRUE : FALSE;
使用者需要通過flash或手機看監控視訊,而目前絕大多數攝像機都是RTSP協議,所以需要做一箇中轉。
參考資料:
http://blog.csdn.net/firehood_/article/details/8813587
http://blog.csdn.net/firehood_/article/details/8783589
這兩篇文章詳細說明了mp4v2儲存檔案和把264檔案通過RTMP直播,對這兩方面感興趣的可以直接看這兩文章。本文只是把這兩文章中讀264轉存轉發改成讀rtsp轉存轉發,多了一點live555的內容,其他基本一樣,只稍調整了一點點介面部分。
二 流程:
1 live555從攝像機讀音視訊,為了便於講解這裡只討論視訊。
2 mp4v2儲存成.mp4
3 librtmp上傳視訊流到nginx
4 web使用者通過flash觀看
三 技術講解:
1 live555讀RTSP視訊,可以參考playCommon.cpp或testRTSPClient.cpp,前者功能全,後者簡潔一些,不過支援多路流同時訪問。如果自己寫RTSP Client,環境不是很複雜,可以參考這份程式碼。
RTSP接收到H264視訊後,不管是讀寫檔案還是傳送流,主要的問題就在SPS和PPS上面。這兩個名字這裡不做解釋,自行google,這裡只講怎麼取到這兩個引數。
讀live的原始碼,注意 playCommon.cpp中有一段:
else if (strcmp(subsession->mediumName(), "video") == 0 &&
(strcmp(subsession->codecName(), "H264") == 0)) {
// For H.264 video stream, we use a special sink that adds 'start codes', and (at the start) the SPS and PPS NAL units:
fileSink = H264VideoFileSink::createNew(*env, outFileName,
subsession->fmtp_spropparametersets(),
fileSinkBufferSize, oneFilePerFrame);
}
這裡呼叫H264VideoFileSink儲存264檔案,播放正常,所以我們需要研究H264VideoFileSink的原始碼。
只需注意H264VideoFileSink.cpp裡面的afterGettingFrame函式,如下:
if (!fHaveWrittenFirstFrame) {
// If we have PPS/SPS NAL units encoded in a "sprop parameter string", prepend these to the file:
unsigned numSPropRecords;
SPropRecord* sPropRecords = parseSPropParameterSets(fSPropParameterSetsStr, numSPropRecords);
for (unsigned i = 0; i < numSPropRecords; ++i) {
addData(start_code, 4, presentationTime);
addData(sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength, presentationTime);
}
delete[] sPropRecords;
fHaveWrittenFirstFrame = True; // for next time
}
// Write the input data to the file, with the start code in front:
addData(start_code, 4, presentationTime);
註釋的很明顯了,這裡再簡單說一下:
for (unsigned i = 0; i < numSPropRecords; ++i) {
addData(start_code, 4, presentationTime);
addData(sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength, presentationTime);
}
這裡的sPropRecords是從外面傳進來的引數:subsession->fmtp_spropparametersets(),從名字上看就是sps和pps合在一起的內容。
debug就可以發現,numSPropRecords==2,for迴圈兩次,第一次是sps,第二次就是pps了。把這兩個記住,儲存264和傳送RTMP時寫在相應的位置,所有的工作基本就完成了。
2 mp4v2的api非常簡單,沒什麼好說,唯一注意一點就是先從sps中讀取視訊的寬和高。可以參考後面程式碼。
3 librtmp,也很簡單,只要注意先發關sps和pps
4 直播伺服器用nginx+nginx_rtmp_module, google一下,文件很多。
四 執行環境: CentOS6.4及Ubuntu 12下執行正常。windows也可以,多平臺時需要型別統一,比如UINT,BYTE,uint32_t, uint8_t ,稍稍改動一下就好。
五 rtsp部分簡單程式碼演示:
1 把testRTSPClient.cpp程式碼複製出來,放在自己的程式碼裡,RTSP接收功能就OK了。只改動一點點,以下是改動部分:
void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString) {
這裡,增加 :
//暫時只處理視訊
if (strcmp(scs.subsession->mediumName(), "video") == 0 &&
(strcmp(scs.subsession->codecName(), "H264") == 0))
{
DummySink* pVideoSink = DummySink::createNew(env, *scs.subsession, rtspClient->url());
pVideoSink->sps = scs.subsession->fmtp_spropparametersets();
scs.subsession->sink = pVideoSink;
}
//這部分程式碼參考playComm.cpp, sps是string變數,為了演示方便,直接宣告成public,這裡把sps pps內容當成字串傳到DummySink。
//1280*760*3
#define DUMMY_SINK_RECEIVE_BUFFER_SIZE 2764800
uint8_t* buff = NULL;
struct timeval start_time;
unsigned numSPropRecords;
SPropRecord* sPropRecords = NULL;
DummySink::DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId)
: MediaSink(env),
fSubsession(subsession) {
fStreamId = strDup(streamId);
fReceiveBuffer = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
buff = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
gettimeofday(&start_time, NULL);
}
DummySink::~DummySink() {
delete[] fReceiveBuffer;
delete[] fStreamId;
delete []buff;
}
void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigned /*durationInMicroseconds*/)
{
struct timeval cur;
gettimeofday(&cur, NULL);
static unsigned char const start_code[4] = {0x00, 0x00, 0x00, 0x01};
if (sPropRecords == NULL)
{
//這幾行程式碼出自H264VideoFileSink.cpp,先儲存成sps.264是考慮儲存檔案和直播的時間不一定在什麼時候開始,先在本地快取一下。
//當然儲存在記憶體裡也可以。
FILE* pfSPS = fopen("sps.264", "wb");
sPropRecords = parseSPropParameterSets(sps.c_str(), numSPropRecords);
//這裡的sps是個string, 就是前面continueAfterSETUP程式碼裡的提到的。
for (unsigned i = 0; i < numSPropRecords; ++i)
{
if (pfSPS)
{
fwrite(start_code, 1, 4, pfSPS );
fwrite(sPropRecords[i].sPropBytes, 1, sPropRecords[i].sPropLength, pfSPS);
}
}
fclose(pfSPS);
gettimeofday(&start_time, NULL);
}
int timestamp = (cur.tv_sec-start_time.tv_sec)*1000 + (cur.tv_usec - start_time.tv_usec)/1000;
memset(buff, '0', DUMMY_SINK_RECEIVE_BUFFER_SIZE);
memcpy(buff, start_code, 4);
memcpy(buff+4, fReceiveBuffer, frameSize);
callback(buff, frameSize+4, timestamp);//這裡自行實現一個回撥函式,把視訊資料回調出去。
continuePlaying();
}
六:mp4v2儲存成.mp4,可以參考這個文章:http://blog.csdn.net/firehood_/article/details/8813587
不過這裡面有幾個引數沒說明怎麼得到,可以自己稍加處理,比如從sps裡讀出寬和高,參考:http://blog.csdn.net/firehood_/article/details/8783589,
同一作者寫的,如果認真看完他的文件,我這裡寫的都不需要看了。
bool h264_decode_sps(BYTE * buf,unsigned int nLen,int &width,int &height)
{
UINT StartBit=0;
int forbidden_zero_bit=u(1,buf,StartBit);
int nal_ref_idc=u(2,buf,StartBit);
int nal_unit_type=u(5,buf,StartBit);
if(nal_unit_type==7)
{
int profile_idc=u(8,buf,StartBit);
int constraint_set0_flag=u(1,buf,StartBit);//(buf[1] & 0x80)>>7;
int constraint_set1_flag=u(1,buf,StartBit);//(buf[1] & 0x40)>>6;
int constraint_set2_flag=u(1,buf,StartBit);//(buf[1] & 0x20)>>5;
int constraint_set3_flag=u(1,buf,StartBit);//(buf[1] & 0x10)>>4;
int reserved_zero_4bits=u(4,buf,StartBit);
int level_idc=u(8,buf,StartBit);
int seq_parameter_set_id=Ue(buf,nLen,StartBit);
if( profile_idc == 100 || profile_idc == 110 ||
profile_idc == 122 || profile_idc == 144 )
{
int chroma_format_idc=Ue(buf,nLen,StartBit);
if( chroma_format_idc == 3 )
int residual_colour_transform_flag=u(1,buf,StartBit);
int bit_depth_luma_minus8=Ue(buf,nLen,StartBit);
int bit_depth_chroma_minus8=Ue(buf,nLen,StartBit);
int qpprime_y_zero_transform_bypass_flag=u(1,buf,StartBit);
int seq_scaling_matrix_present_flag=u(1,buf,StartBit);
int seq_scaling_list_present_flag[8];
if( seq_scaling_matrix_present_flag )
{
for( int i = 0; i < 8; i++ ) {
seq_scaling_list_present_flag[i]=u(1,buf,StartBit);
}
}
}
int log2_max_frame_num_minus4=Ue(buf,nLen,StartBit);
int pic_order_cnt_type=Ue(buf,nLen,StartBit);
if( pic_order_cnt_type == 0 )
int log2_max_pic_order_cnt_lsb_minus4=Ue(buf,nLen,StartBit);
else if( pic_order_cnt_type == 1 )
{
int delta_pic_order_always_zero_flag=u(1,buf,StartBit);
int offset_for_non_ref_pic=Se(buf,nLen,StartBit);
int offset_for_top_to_bottom_field=Se(buf,nLen,StartBit);
int num_ref_frames_in_pic_order_cnt_cycle=Ue(buf,nLen,StartBit);
int *offset_for_ref_frame=new int[num_ref_frames_in_pic_order_cnt_cycle];
for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
offset_for_ref_frame[i]=Se(buf,nLen,StartBit);
delete [] offset_for_ref_frame;
}
int num_ref_frames=Ue(buf,nLen,StartBit);
int gaps_in_frame_num_value_allowed_flag=u(1,buf,StartBit);
int pic_width_in_mbs_minus1=Ue(buf,nLen,StartBit);
int pic_height_in_map_units_minus1=Ue(buf,nLen,StartBit);
width=(pic_width_in_mbs_minus1+1)*16;
height=(pic_height_in_map_units_minus1+1)*16;
return true;
}
else
return false;
}
順便考考大家,知道這個函式的引數BYTE * buf,unsigned int nLen從哪裡來的嗎?
對了,就是剛才儲存的sps.264,fopen,fread,讀出來,MP4Encoder這裡有個函式PraseMetadata,幫著分析sps pps。
大致如下:
MP4ENC_Metadata mp4meta;
PraseMetadata(buf, nLen, mp4meta);
int width = 0,height = 0;
if (!h264_decode_sps(mp4meta.Sps, mp4meta.nSpsLen, width, height))
return -1;
Handle = CreateMP4File(outFile, width, height);
Write264Metadata(&mp4meta);
然後每次回調回來:
WriteH264Data(data, len);
自己加個時間判斷,比如每幾分鐘儲存一個檔案,自己看著處理。回撥的時間戳這裡沒有用上,是傳送RTMP時用的。
七 RTMP傳送
參考上面提到的第二個連結,裡面是讀一個264檔案,然後傳送成RTMP流。這裡稍稍改一下,把實時的資料發出去。其實原來都一樣的,在live儲存檔案的時候
已經說過,檔案開頭儲存了sps和pps,然後依次是0x00, 0x00, 0x00, 0x01+視訊資料,這些直接fwrite到檔案裡就是.264。參考的文章就是傳送這個.264。
注意一下,傳送檔案和傳送實時資料流的唯一區別就是:檔案的頭儲存著sps資訊,而直播流需要讀快取裡的(一般rtsp會每隔一段時間再發,用這個也行,而且比儲存的更準確)。
那接下來就很簡單了,先修改一下SendH264File這個函式:
bool CRTMPStream::SendH264File(const char *pFileName)
{
if(pFileName == NULL)
{
return FALSE;
}
FILE *fp = fopen(pFileName, "rb");
if(!fp)
{
printf("ERROR:open file %s failed!",pFileName);
}
fseek(fp, 0, SEEK_SET);
m_nFileBufSize = fread(m_pFileBuf, sizeof(unsigned char), FILEBUFSIZE, fp);
if(m_nFileBufSize >= FILEBUFSIZE)
{
printf("warning : File size is larger than BUFSIZE\n");
}
fclose(fp);
RTMPMetadata metaData;
memset(&metaData,0,sizeof(RTMPMetadata));
NaluUnit naluUnit;
// 讀取SPS幀
ReadOneNaluFromBuf(naluUnit);
metaData.nSpsLen = naluUnit.size;
memcpy(metaData.Sps,naluUnit.data,naluUnit.size);
// 讀取PPS幀
ReadOneNaluFromBuf(naluUnit);
metaData.nPpsLen = naluUnit.size;
memcpy(metaData.Pps,naluUnit.data,naluUnit.size);
// 解碼SPS,獲取視訊影象寬、高資訊
int width = 0,height = 0;
h264_decode_sps(metaData.Sps,metaData.nSpsLen,width,height);
metaData.nWidth = width;
metaData.nHeight = height;
metaData.nFrameRate = 25;
// 傳送MetaData
return SendMetadata(&metaData);
//以下本文註釋掉,改從直播視訊裡發
/*unsigned int tick = 0;
while(ReadOneNaluFromBuf(naluUnit))
{
bool bKeyframe = (naluUnit.type == 0x05) ? TRUE : FALSE;