Professional Documents
Culture Documents
Table of Contents
1 Sample Program for Saving H.264 Video Data .......................................................................... 4 1.1 1.2 1.3 1.4 1.5 Creating a Project .................................................................................................... 6 Creating a Camera Communication Class .............................................................. 8 Creating a NAL Data Extraction Class................................................................... 14 Creating a File Save Class .................................................................................... 26 Combining the Classes .......................................................................................... 29
WvCameraSession Class It acquires H.264 video data in the MP4 fragment format from the camera. Then, it writes the acquired data into an MP4 buffer for passing the data to the WvH264DataExtract class.
WvH264DataExtract Class It extracts the H.264 video data from the MP4 buffer and removes the MP4 header. Then, it writes the data without the MP4 header into a H.264 buffer for passing the data to the WvFileSave class.
WvFileSave Class It extracts the data without the MP4 header from the H.264 buffer, and then saves it to a PC.
The specification for the sample program is as follows: Win32 command-line application (using MFC) Execution method
SimpleH264DataExtract.exe -o {File to save to} -d {Image transmission duration (sec.)} When a file to save to is not specified, the save location is C:\h264.dat. When the duration is not specified, 0 is specified, and the processing continues until the program ends.
The settings of H.264 video data follow the present settings of the camera. When the press of the Ctrl and C buttons or the end of command prompt is detected, the end processing is performed.
In a sample code, the cameras temporary IP address is 192.168.100.1. For actual use, replace the temporary IP address with an actual one. The image data acquired from the camera is referred to as MP4 data and the data with the MP4 header removed from the image data as H.264 data. The sample program is written in Visual C++, and the operation has been confirmed in Visual C++ 2008 Professional Edition.
Check the MFC checkbox under Add common header files for in Application Settings of the Win32 Application Wizard, and then press the Finish button.
Enter WvCameraSession in the Class Name field, and then press the Finish button.
The following explains what this class performs. The class generates a thread in which the following is performed: executing the video.cgi command (sessionless), acquiring H.264 video data in MP4 fragment format from the camera and then writing the data into the MP4 data buffer. The thread starts with the StartGetVideo function and stops with the StopGetVideo function. The main processing for the WvCameraSession class is the GetVideo function. The following explains the flow of the function (WvCameraSession.cpp). 1. It starts an HTTP communication with the camera using WinInet. It transmits the video.cgi command to the camera, and ends the processing when the HTTP status code is a value other than 200. (Line number 32 - 76)
2.
It acquires a response from the camera, and writes the response into the MP4 data buffer. It continues the processing until the stop flag becomes true due to calling the StopGetVideo function or until the image transmission from the camera ends. When a failure in the communication with the camera server such as network disconnection occurs, it displays a message and ends the processing. (Line number 78 - 106)
3.
It deletes each object, and performs the end processing of the HTTP communication. (Line number 109 - 113)
10
WvCameraSession::WvCameraSession(CString hostname, int duration) { this->stop = false; this->mp4Buffer = NULL; this->keyMP4 = NULL; this->thread = NULL; this->hostname = hostname; this->duration = duration; } WvCameraSession::~WvCameraSession(void) { this->mp4Buffer = NULL; this->keyMP4 = NULL; this->thread = NULL; } //Acquire H.264 video data from the camera UINT WvCameraSession::GetVideo() { DWORD dwStatus; BYTE buffer[1024]; UINT readByte; CString command; UINT ret = 0; command.Format(_T("%s%d"), _T("/-wvhttp-01-/video.cgi?v=h264&duration="), duration); CInternetSession inet = CInternetSession(_T("SimpleH264DataExtract")); CHttpConnection *pHttpCon = inet.GetHttpConnection(hostname, CHttpConnection::HTTP_VERB_GET, 80, NULL, NULL); CHttpFile *pFile = pHttpCon->OpenRequest(CHttpConnection::HTTP_VERB_GET, command, NULL, 1, NULL, _T("HTTP/1.1"), NULL); try { pFile->SendRequest(); }
11
catch (CInternetException * e) { CString err; TCHAR buf[512]; e->GetErrorMessage(buf, 512); err.Format(_T("Error occurred : %d / %s\n"), e->m_dwError, buf); ::AfxMessageBox(err, MB_OK | MB_ICONEXCLAMATION); ret = -1; goto EXIT; } pFile->QueryInfoStatusCode(dwStatus); if (dwStatus != 200) { CString err; err.Format(_T("HTTP error : %d \n"), dwStatus); ::AfxMessageBox(err, MB_OK | MB_ICONEXCLAMATION); ret = -1; goto EXIT; } while (1) { try { readByte = pFile->Read(buffer, 1024); keyMP4->Lock(); mp4Buffer->insert(mp4Buffer->end(), buffer, buffer + 1024); keyMP4->Unlock(); if ((readByte < 1024) || (stop == true)) { break; } } catch (CInternetException * e) { CString err; TCHAR buf[512]; e->GetErrorMessage(buf, 512); err.Format(_T("Error occurred : %d / %s\n"), e->m_dwError, buf); ::AfxMessageBox(err, MB_OK | MB_ICONEXCLAMATION); ret = -1; goto EXIT; } ::Sleep(1); }
12
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
EXIT: pFile->Close(); pHttpCon->Close(); inet.Close(); delete pFile; delete pHttpCon; return ret; } //Start the thread UINT WvCameraSession::ThreadProc(LPVOID pParam) { WvCameraSession * session = (WvCameraSession*)pParam; return session->GetVideo(); } //Start the H.264 video data acquition processing void WvCameraSession::StartGetVideo(std::deque<BYTE> * mp4Buffer, CCriticalSection * keyMP4) { this->stop = false; this->mp4Buffer = mp4Buffer; this->keyMP4 = keyMP4; thread = AfxBeginThread(ThreadProc, this); } //End the H.264 video data acquition processing void WvCameraSession::StopGetVideo() { stop = true; } void WvCameraSession::WaitThreadProc() { WaitForSingleObject(thread->m_hThread, INFINITE); }
13
ftyp (File Type Box) It is a description about a file type. Only one box of this kind is included in the head of a stream from the camera. moov (Movie Box) It includes initial settings items required for video playback such as image size, frame rate and time information. Only one box of this kind is included in a stream from the camera. moof (Movie Fragment Box) It includes a sequence No., frame type and others. mdat (Media Data Box) Main body of media data The H.264 video data is divided in processing units called NAL (Network Abstraction Layer) units, and encrypted. The NAL units include the following information. SPS (Sequence Parameter Set) header Information required for decoding a stream PPS (Picture Parameter Set) header Information required for decoding each picture Picture data H.264 image data (hereafter, referred to as NAL data)
14
The SPS/PPS headers are stored in the avcC (AVC Configuration) box inside the moov box.
The number of NAL data in the mdat box differs depending on the settings of the camera. The default value is 1. The following explains what this class performs. The class generates a thread in which the following is performed: analyzing the H.264 video data acquired in the camera communication class, extracting the NAL data, and then writing the extracted NAL data into the H.264 video data buffer. The thread starts with the StartExtractData function and stops with the StopExtractData function. The main processing for the WvH264DataExtract class is the ExtractData function. The following explains the flow of the function (WvH264DataExtract.cpp). 1. It extracts 1024-byte data from the H.264 data buffer where the H.264 video data in the MP4 fragment format is stored. (Line number 255 - 268) 2. It searches the avcC box for the SPS and PPS headers only once after the processing starts. When it finds the headers, it writes the header information into the H.264 data buffer. Otherwise, it ends the processing. (Line number 270 - 289) 3. It searches the mdat box. When it does not find the mdat box, it returns to the processing 1. (Line number 291 - 310)
15
4.
It extracts the data as much as the size of the mdat box from the MP4 data buffer. (Line number 312 - 342)
5.
It writes into the H.264 data buffer the NAL structure data taken out of the extracted data. (Line number 344 - 348)
6.
The processing continues until the stop flag becomes true. When the camera communication class thread ends, the stop flag becomes true.
16
17
WvH264DataExtract.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include "StdAfx.h" #include "WvH264DataExtract.h" #define SEARCH_SPSPPS 0 #define WRITE_SPS 1 #define WRITE_PPS 2 const BYTE hex_avcC[4] = {0x61, 0x76, 0x63, 0x43}; const BYTE hex_mdat[4] = {0x6D, 0x64, 0x61, 0x74}; const BYTE hex_SCP[4] = {0x00, 0x00, 0x00, 0x01}; WvH264DataExtract::WvH264DataExtract(void) { this->stop = false; this->mp4Buffer = NULL; this->h264Buffer = NULL; this->keyMP4 = NULL; this->keyH264 = NULL; this->thread = NULL; } WvH264DataExtract::~WvH264DataExtract(void) { this->mp4Buffer = NULL; this->h264Buffer = NULL; this->keyMP4 = NULL; this->keyH264 = NULL; this->thread = NULL; } static size_t hex2dec(char * hex) { size_t num = 0; int buf; int i; for (i = 0; ; i++) { if (hex[i] >= '0' && hex[i] <= '9') { buf = hex[i] - '0'; } else if (hex[i] >= 'a' && hex[i] <= 'f') { buf = hex[i] - 'a' + 10; } else if (hex[i] >= 'A' && hex[i] <= 'F') { buf = hex[i] - 'A' + 10; } else if (hex[i] == '\0') { break; } else {
18
num = 0xFFFFFFFF; break; } num *= 16; num += buf; if (i >= 8) { //Overflow num = 0xFFFFFFFF; break; } } return(num); } static int GetSize(BYTE * buf, int size) { int i; int data_size = 0; BYTE size_hex_buf[4]; char size_hex[10]; char format[20] = ""; if (size > 4) { return 0; } for (i = 0; i < size; i++) { size_hex_buf[i] = buf[i]; strcat(format, "%02x"); } sprintf(size_hex, format, size_hex_buf[0], size_hex_buf[1], size_hex_buf[2], size_hex_buf[3]); data_size = hex2dec(size_hex); return data_size; } //Write the 1-byte data into the buffer void WvH264DataExtract::WriteBuffer(BYTE buf) { this->keyH264->Lock(); this->h264Buffer->push_back(buf); this->keyH264->Unlock(); } ///Write the data as much as the specified size into the buffer void WvH264DataExtract::WriteBuffers(BYTE * buf, int size) { if (size < 0) {
19
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
return; } this->keyH264->Lock(); this->h264Buffer->insert(h264Buffer->end(), buf, buf + size); this->keyH264->Unlock(); } //Read the data as much as the specified size from the buffer int WvH264DataExtract::GetBuffers(BYTE * buf, int size) { int i; if (size < 0) { return 0; } while (stop == false) { if (mp4Buffer->empty() == false && (int)mp4Buffer->size() > size) { keyMP4->Lock(); for (i = 0; i < size; i++) { buf[i] = (BYTE)mp4Buffer->front(); mp4Buffer->pop_front(); } keyMP4->Unlock(); return size; } else { ::Sleep(1); continue; } } return 0; } //Extract the SPS and PPS headers from the avcC box int WvH264DataExtract::SPSPPS_extract(BYTE * buf, int index) { bool SPSflg = false; bool PPSflg = false; BYTE dat; int i; int cnt = 0; int condition = SEARCH_SPSPPS; int avcC_size = 0; int SPSPPS_size = 0; avcC_size = GetSize(buf + index, 4); for (i = 4; i < avcC_size; i++) {
20
162 163 164 165 166 167 168 169 170 2); 171 172 173 174 175 176 177 178 2); 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
dat = buf[index + i]; switch (condition) { case SEARCH_SPSPPS: if (dat == 0x67) { WriteBuffers((BYTE*)hex_SCP, 4); SPSPPS_size = GetSize(buf + (index + i - 2), condition = WRITE_SPS; cnt = 0; i--; } else if (dat == 0x68) { WriteBuffers((BYTE*)hex_SCP, 4); SPSPPS_size = GetSize(buf + (index + i - 2), condition = WRITE_PPS; cnt = 0; i--; } break; case WRITE_SPS: WriteBuffer(dat); cnt++; if (cnt >= SPSPPS_size) { SPSflg = true; condition = SEARCH_SPSPPS; cnt = 0; } break; case WRITE_PPS: WriteBuffer(dat); cnt++; if (cnt >= SPSPPS_size) { PPSflg = true; condition = SEARCH_SPSPPS; cnt = 0; } break; default: printf("error : writing SPS or PPS\n"); return -1; } } if(!SPSflg | !PPSflg) { printf("error : not found SPS or PPS\n"); return -1;
21
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
} return index + i; } //Extract the NAL data from the mdat box mdat int WvH264DataExtract::NAL_extract(BYTE * buf, int index, int mdat_size) { int nal_size = 0; int mdat_size_count = 8; while (mdat_size_count < mdat_size) { WriteBuffers((BYTE*)hex_SCP, 4); nal_size = GetSize(buf + index, 4); WriteBuffers(buf + index + 4, nal_size); mdat_size_count += nal_size + 4; index += nal_size + 4; } return index; } //Extract the NAL data from the H.264 video data acquired from the camera UINT WvH264DataExtract::ExtractData() { int i; int read_size = 0; int buf_size = 0; int bufBack_size = 0; BYTE * buf = NULL; BYTE * bufBack = NULL; int mdat_size = 0; bool SPSPPSfound = false; UINT ret = 0; printf("H.264 data extracting."); while (stop == false) { read_size = 0; buf_size = 1024; buf = (BYTE*)malloc(buf_size); if (bufBack_size > 0) { memcpy(buf, bufBack, bufBack_size); free(bufBack); bufBack = NULL; } if (GetBuffers(buf + bufBack_size, buf_size - bufBack_size) == 0) { break; }
22
268 269 270 271 272 273 274 275 276 277 278 279 280 == -1) 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
bufBack_size = 0; if (SPSPPSfound == false) { //Extract the SPS and PPS hearders from the avcC box for (i = 0; i <= buf_size - 4; i++) { if (buf[i] == hex_avcC[0] && buf[i + 1] == hex_avcC[1] && buf[i + 2] == hex_avcC[2] && buf[i + 3] == hex_avcC[3]) { if ((read_size = SPSPPS_extract(buf, i - 4)) { ret = -1; goto EXIT; } SPSPPSfound = true; break; } } } //Search the mdat box and acquire the data size for (i = read_size; i <= buf_size - 4; i++) { if (buf[i] == hex_mdat[0] && buf[i + 1] == hex_mdat[1] && buf[i + 2] == hex_mdat[2] && buf[i + 3] == hex_mdat[3]) { mdat_size = GetSize(buf + i - 4, 4); read_size = i + 4; break; } } if (i > buf_size - 4) { bufBack_size = 4; bufBack = (BYTE*)malloc(bufBack_size); memcpy(bufBack, buf + (buf_size - 4), bufBack_size); continue; } //Acquire the data as much as the mdat box size from the buffer if (buf_size - read_size >= mdat_size) { if (buf_size - read_size == mdat_size) { bufBack_size = 0; } else {
23
321 322 323 bufBack_size); 324 325 326 327 328 329 330 331 332 333 334 335 336 0) 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 { } } else {
bufBack_size = buf_size - (read_size + mdat_size); bufBack = (BYTE*)malloc(bufBack_size); memcpy(bufBack, buf + read_size + mdat_size,
bufBack_size = buf_size - read_size; memmove(buf, buf + read_size, bufBack_size); buf_size = mdat_size; bufBack = (BYTE*)realloc(buf, buf_size); buf = bufBack; bufBack = NULL; if (GetBuffers(buf + bufBack_size, buf_size - bufBack_size) ==
break; } read_size = 0; bufBack_size = 0; } //Extract the NAL data from the mdat box read_size = NAL_extract(buf, read_size, mdat_size); free(buf); buf = NULL; printf("."); } printf("\n"); printf("H.264 data extracted. \n"); EXIT: if (buf) { free(buf); buf = NULL; } if (bufBack) { free(bufBack); bufBack = NULL; } return ret; } //Start the thread UINT WvH264DataExtract::ThreadProc(LPVOID pParam)
24
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
{ WvH264DataExtract * extract = (WvH264DataExtract*)pParam; return extract->ExtractData(); } //Start the NAL data extraction processing void WvH264DataExtract::StartExtractData(std::deque<BYTE> * mp4Buffer, std::deque<BYTE> * h264Buffer, CCriticalSection * keyMP4, CCriticalSection * keyH264) { this->stop = false; this->mp4Buffer = mp4Buffer; this->h264Buffer = h264Buffer; this->keyMP4 = keyMP4; this->keyH264 = keyH264; thread = AfxBeginThread(ThreadProc, this); } //End the NAL data extraction processing void WvH264DataExtract::StopExtractData() { this->stop = true; } void WvH264DataExtract::WaitThreadProc() { WaitForSingleObject(thread->m_hThread, INFINITE); }
25
26
WvSaveFile.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include "StdAfx.h" #include "WvSaveFile.h" WvSaveFile::WvSaveFile(CString filepath) { this->stop = false; this->h264Buffer = NULL; this->keyH264 = NULL; this->thread = NULL; this->filepath = filepath; } WvSaveFile::~WvSaveFile(void) { this->h264Buffer = NULL; this->keyH264 = NULL; this->thread = NULL; } //Save the extracted NAL data into the file UINT WvSaveFile::SaveFile() { int i; char tmp; //File open try { out = new CFile(this->filepath.GetBuffer(), CFile::modeCreate | CFile::modeWrite | CFile::typeBinary); } catch(CFileException * e) { CString err; TCHAR buf[512]; e->GetErrorMessage(buf,512); err.Format(_T("Error occurred : %d / %s\n"), e->m_cause, buf); ::AfxMessageBox(err, MB_OK | MB_ICONEXCLAMATION); e->Delete(); return -1; } //Write the data in the buffer into the file while (stop == false) { if (h264Buffer->empty() == false) { keyH264->Lock(); for (i = 0; i < (int)h264Buffer->size(); i++)
27
{ tmp = (char)h264Buffer->front(); out->Write(&tmp, 1); h264Buffer->pop_front(); } keyH264->Unlock(); } } //Write the data remained in the buffer into the file for (i = 0; i < (int)h264Buffer->size(); i++) { tmp = (char)h264Buffer->front(); out->Write(&tmp, 1); h264Buffer->pop_front(); } delete out; return 0; } //Start the thread UINT WvSaveFile::ThreadProc(LPVOID pParam) { WvSaveFile * savefile = (WvSaveFile*)pParam; return savefile->SaveFile(); } //Start the NAL data saving processing void WvSaveFile::StartSaveFile(std::deque<BYTE> * h264Buffer, CCriticalSection * keyH264) { this->stop = false; this->h264Buffer = h264Buffer; this->keyH264 = keyH264; thread = AfxBeginThread(ThreadProc, this); } //End the NAL data saving processing void WvSaveFile::StopSaveFile() { this->stop = true; } void WvSaveFile::WaitThreadProc() { WaitForSingleObject(thread->m_hThread, INFINITE); }
28
29
std::deque<BYTE> mp4Buffer; std::deque<BYTE> h264Buffer; WvCameraSession * session; WvH264DataExtract * extract; WvSaveFile * savefile; CCriticalSection keyMP4; CCriticalSection keyH264;
SimpleH264DataExtract.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // SimpleH264DataExtract.cpp : Define the entry point of the console application // #include "stdafx.h" #include "SimpleH264DataExtract.h" #include "Windows.h" #ifdef _DEBUG #define new DEBUG_NEW #endif
//The one and only application object CWinApp theApp; using namespace std; //Console event handler BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType) { switch (dwCtrlType) { case CTRL_C_EVENT: //Press the Ctrl and C case CTRL_CLOSE_EVENT: //Console end session->StopGetVideo(); return TRUE; } return FALSE; }
30
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; // initialize MFC and print and error on failure. if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) { // TODO: Change error code to suit your needs. _tprintf(_T("Fatal error: MFC initialization failed \n")); nRetCode = 1; } else { int duration = 0; CString output = _T("C: \\h264.dat"); if (argc > 1) { for (int i = 1; i < argc; i++) { if(argv[i][0] == '-') { switch(argv[i][1]){ case 'o': if (i + 1 < argc) { output = argv[i + 1]; } break; case 'd': if (i + 1 < argc) { duration = _ttoi(argv[i + 1]); } break; } } } } //Add the console event handler BOOL ret = SetConsoleCtrlHandler(ConsoleCtrlHandler, true); if (ret == false) { return -1; } //Create the class object session = new WvCameraSession(_T("192.168.100.1"), duration); extract = new WvH264DataExtract(); savefile = new WvSaveFile(output);
31
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
//Initialize the buffer mp4Buffer.clear(); h264Buffer.clear(); //Start the thread session->StartGetVideo(&mp4Buffer, &keyMP4); extract->StartExtractData(&mp4Buffer, &h264Buffer, &keyMP4, &keyH264); savefile->StartSaveFile(&h264Buffer, &keyH264); //Monitor the camera communication thread session->WaitThreadProc(); //Stop the thread extract->StopExtractData(); extract->WaitThreadProc(); savefile->StopSaveFile(); savefile->WaitThreadProc(); //Discard the class object delete session; delete extract; delete savefile; } return nRetCode; }
32