最近本人在學習大華相機的二次開發時遇到一個問題:大華SDK開發文檔並沒有說明如何獲取相機H264裸碼流及其如何封裝成mp4文件。在研究了海康的相關代碼之後,才明白如何操作。
main.cpp
#include <iostream>
#include "dhnetsdk.h"
#include "MP4Encoder.h"
using namespace std;
LLONG g_lRealHandle;
MP4Encoder* pMP4;
void CALLBACK RealDataCallBackEx2(LLONG lRealHandle, DWORD dwDataType, BYTE*pBuffer, DWORD dwBufSize, tagVideoFrameParam param, LDWORD dwUser)
{
// 若多個實時監視使用相同的數據回調函數,則用戶可通過 lRealHandle 進行一一對應
if (lRealHandle == g_lRealHandle)
{
int type = param.frametype;
switch (type)
{
case 0: // I 幀
pMP4->getConvertMP4File(pBuffer, dwBufSize, type);
break;
case 1: // P 幀
pMP4->getConvertMP4File(pBuffer, dwBufSize, type);
break;
default:
break;
}
}
}
int main()
{
BOOL g_bNetSDKInitFlag = FALSE;
// 初始化 SDK
g_bNetSDKInitFlag = CLIENT_Init(NULL, 0);
if (FALSE == g_bNetSDKInitFlag)
{
cout << "Initialize client SDK fail;" << endl;
return 0;
}
else
cout << "Initialize client SDK done; " << endl;
NET_DEVICEINFO_Ex deviceInfo = { 0 };
int nError = 0;
LLONG login_handle = CLIENT_LoginEx2("192.168.1.64", 37777, "admin", "admin", EM_LOGIN_SPEC_CAP_TCP, nullptr, &deviceInfo, &nError);
/*登錄失敗*/
if (login_handle != 0)
{
cout << "登錄成功" << endl;
//開啓實時監視
int nChannelID = 0; // 預覽通道號
DH_RealPlayType emRealPlayType = DH_RType_Realplay; // 實時監視
HWND hWnd = GetConsoleWindow();
g_lRealHandle = CLIENT_RealPlayEx(login_handle, nChannelID, hWnd, emRealPlayType);
pMP4 = new MP4Encoder;
if (0 == g_lRealHandle)
{
cout << "CLIENT_RealPlayEx: failed! Error code:" << CLIENT_GetLastError() << endl;
system("pause");
return 0;
}
else
{
//DWORD dwFlag = 0x00000001;
if (FALSE == CLIENT_SetRealDataCallBackEx2(g_lRealHandle, &RealDataCallBackEx2, NULL, REALDATA_FLAG_DATA_WITH_FRAME_INFO))
{
cout << "CLIENT_SetRealDataCallBackEx: failed! Error code: " << CLIENT_GetLastError() << endl;
return 0;
}
Sleep(10000);
}
}
pMP4->getMP4FileClose();
delete pMP4;
//關閉預覽
if (CLIENT_StopRealPlayEx(g_lRealHandle))
{
g_lRealHandle = 0;
};
// 退出設備
if (0 != login_handle)
{
if (FALSE == CLIENT_Logout(login_handle))
{
cout << "CLIENT_Logout Failed!Last Error \n" << CLIENT_GetLastError() << endl;
}
else
{
login_handle = 0;
}
}
// 清理初始化資源
if (TRUE == g_bNetSDKInitFlag)
{
CLIENT_Cleanup();
g_bNetSDKInitFlag = FALSE;
}
return 0;
}
MP4Encoder.cpp,通過libmp4V2實現MP4寫封裝生成MP4視頻文件
#include "MP4Encoder.h"
void MP4Encoder::getMP4FileClose()
{
MP4Close(mFile);
}
bool MP4Encoder::getConvertMP4File(BYTE *pBuffer, DWORD dwBufSize, int dwDataType)
{
CreateMP4File();
WriteH264Data(mFile, pBuffer, dwBufSize, dwDataType);
return true;
}
MP4Encoder::MP4Encoder()
{
mFile = MP4_INVALID_FILE_HANDLE;
mVideoId = MP4_INVALID_TRACK_ID;
}
MP4Encoder::~MP4Encoder()
{
}
string MP4Encoder::GetSystemTime()
{
SYSTEMTIME m_time;
GetLocalTime(&m_time);
char szDateTime[100] = { 0 };
sprintf_s(szDateTime, "%02d%02d%02d%02d%02d%02d", m_time.wYear, m_time.wMonth,
m_time.wDay, m_time.wHour, m_time.wMinute, m_time.wSecond);
string time(szDateTime);
return time;
}
MP4FileHandle MP4Encoder::CreateMP4File()
{
if (mFile == NULL)
{
string s = GetSystemTime() + ".mp4";
const char* FileName = s.c_str();
mFile = MP4Create(FileName);
if (mFile == MP4_INVALID_FILE_HANDLE)
{
cout << "Open file failed!\n";
return 0;
}
if (mFile == NULL)
{
printf("ERROR:Create file failed!");
return false;
}
// 設置時間片
MP4SetTimeScale(mFile, VIDEO_TIME_SCALE);
}
return mFile;
}
int MP4Encoder::ReadOneNaluFromBuf(const unsigned char *pBuffer, unsigned int nBufferSize, unsigned int offSet, MP4ENC_NaluUnit &nalu)
{
unsigned int i = offSet;
int j = 0;
while (i<nBufferSize)
{//尋找第一個00 00 00 01
if (pBuffer[i] == 0x00 &&pBuffer[i+1] == 0x00 &&pBuffer[i+2] == 0x00 &&pBuffer[i+3] == 0x01)
{
i = i + 4;
unsigned int pos = i;
while (pos<nBufferSize)
{//尋找最後一個00 00 00 01
if (pBuffer[pos] == 0x00 &&pBuffer[pos+1] == 0x00 &&pBuffer[pos+2] == 0x00 &&pBuffer[pos+3] == 0x01)
break;
pos++;
}
pos = pos + 4;
if (pos == nBufferSize)
nalu.size = pos - i;
else
nalu.size = (pos - 4) - i;
nalu.type = pBuffer[i] & 0x1f;
nalu.data = (unsigned char*)&pBuffer[i];
return (nalu.size + i - offSet);
}
i++;
}
return 0;
}
int MP4Encoder::WriteH264Data(MP4FileHandle MP4File, const unsigned char* pData, int size,int dwDataType)
{
if (MP4File == NULL)
{
return -1;
}
if (pData == NULL)
{
return -1;
}
MP4ENC_NaluUnit nalu;
int pos = 0;
int len = 0;
while (len = ReadOneNaluFromBuf(pData, size, pos, nalu))
{
if (nalu.type == 0x07) // sps
{
if (mVideoId == MP4_INVALID_TRACK_ID)
{ // 添加h264 track
mVideoId = MP4AddH264VideoTrack(
MP4File,
VIDEO_TIME_SCALE, // 視頻每秒的ticks數(如90000)
VIDEO_TIME_SCALE / 13, // 視頻的固定的視頻幀的顯示時間,公式爲timeScale(90000)/fps(碼率例如20f)
VIDEO_WIDTH, // 視頻的寬度
VIDEO_HEIGHT, // 視頻的高度
nalu.data[1], // sps[1] AVCProfileIndication
nalu.data[2], // sps[2] profile_compat
nalu.data[3], // sps[3] AVCLevelIndication
3 // 4 bytes length before each NAL unit
);
if (mVideoId == MP4_INVALID_TRACK_ID)
{
printf("add video track failed.\n");
system("pause");
return 0;
}
MP4SetVideoProfileLevel(MP4File, 0x01); // Simple Profile @ Level 3
MP4AddH264SequenceParameterSet(MP4File, mVideoId, nalu.data, nalu.size);
}
}
else if (nalu.type == 0x08) // pps
{
MP4AddH264PictureParameterSet(MP4File, mVideoId, nalu.data, nalu.size);
}
else
{
int datalen = nalu.size + 4;
unsigned char *data = new unsigned char[datalen];
// MP4 Nalu前四個字節表示Nalu長度
data[0] = nalu.size >> 24;
data[1] = nalu.size >> 16;
data[2] = nalu.size >> 8;
data[3] = nalu.size & 0xff;
memcpy(data + 4, nalu.data, nalu.size);
if (dwDataType == 0)
{
MP4WriteSample(MP4File, mVideoId, data, datalen, MP4_INVALID_DURATION, 0, 1);
}
if (dwDataType == 1)
{
MP4WriteSample(MP4File, mVideoId, data, datalen, MP4_INVALID_DURATION, 0, 0);
}
delete[] data;
}
pos += len;
}
return pos;
}
整個項目(VS2015-X64)下載鏈接:https://download.csdn.net/download/qq_35135771/12526926