From 3c0aed02e405aba2fd52fd81e1700ec276837dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Wed, 2 Aug 2017 17:14:55 +0200 Subject: [PATCH 01/18] add onffmpeg recording --- include/kerberos/capture/Capture.h | 3 +- include/kerberos/machinery/io/IoVideo.h | 3 + src/kerberos/capture/IPCamera.cpp | 48 +++++----- src/kerberos/machinery/io/IoVideo.cpp | 115 ++++++++++++++++++++++-- 4 files changed, 139 insertions(+), 30 deletions(-) diff --git a/include/kerberos/capture/Capture.h b/include/kerberos/capture/Capture.h index 501d238..cc42821 100755 --- a/include/kerberos/capture/Capture.h +++ b/include/kerberos/capture/Capture.h @@ -46,9 +46,10 @@ namespace kerberos int m_delay; // msec int m_framerate; bool m_onBoardRecording; + bool m_onFFMPEGrecording; bool m_hardwareMJPEGEncoding; - Capture():m_framerate(30),m_onBoardRecording(false),m_hardwareMJPEGEncoding(false){}; + Capture():m_framerate(30),m_onBoardRecording(false),m_onFFMPEGrecording(false),m_hardwareMJPEGEncoding(false){}; virtual ~Capture(){}; virtual void setup(kerberos::StringMap & settings) = 0; void setup(kerberos::StringMap & settings, int width, int height, int angle); diff --git a/include/kerberos/machinery/io/IoVideo.h b/include/kerberos/machinery/io/IoVideo.h index cf1d0f5..dba45ab 100755 --- a/include/kerberos/machinery/io/IoVideo.h +++ b/include/kerberos/machinery/io/IoVideo.h @@ -89,11 +89,14 @@ namespace kerberos pthread_t m_recordThread; pthread_t m_retrieveThread; pthread_t m_recordOnboardThread; + pthread_t m_recordOnFFMPEGThread; pthread_t m_convertThread; double m_timeStartedRecording; void startOnboardRecordThread(); void stopOnboardRecordThread(); + void startFFMPEGRecordThread(); + void stopFFMPEGRecordThread(); void startRecordThread(); void stopRecordThread(); void startRetrieveThread(); diff --git a/src/kerberos/capture/IPCamera.cpp b/src/kerberos/capture/IPCamera.cpp index b2f0dd7..e799a14 100755 --- a/src/kerberos/capture/IPCamera.cpp +++ b/src/kerberos/capture/IPCamera.cpp @@ -9,11 +9,11 @@ namespace kerberos int height = std::atoi(settings.at("captures.IPCamera.frameHeight").c_str()); int angle = std::atoi(settings.at("captures.IPCamera.angle").c_str()); int delay = std::atoi(settings.at("captures.IPCamera.delay").c_str()); - + // Initialize executor (update the usb camera at specific times). tryToUpdateCapture.setAction(this, &IPCamera::update); tryToUpdateCapture.setInterval("thrice in 10 functions calls"); - + // Save width and height in settings Capture::setup(settings, width, height, angle); setImageSize(width, height); @@ -23,8 +23,10 @@ namespace kerberos // Initialize URL to IP Camera setUrl(url); reopen(); + + m_onFFMPEGrecording = true; // should be a variable option. } - + IPCamera::IPCamera(int width, int height) { try @@ -36,7 +38,7 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } }; - + void IPCamera::grab() { try @@ -58,7 +60,7 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - + Image IPCamera::retrieve() { try @@ -85,15 +87,15 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - - + + Image * IPCamera::takeImage() { // ---------------------------------------- // Update camera, call executor's functor. - + tryToUpdateCapture(); - + // ----------- // Take image @@ -111,7 +113,7 @@ namespace kerberos // Delay camera for some time.. usleep(m_delay*1000); - + pthread_mutex_lock(&m_lock); if(m_streamType == "rtsp") { @@ -126,10 +128,10 @@ namespace kerberos // Check if need to rotate the image image->rotate(m_angle); - + pthread_mutex_unlock(&m_lock); } - + return image; } catch(cv::Exception & ex) @@ -139,7 +141,7 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - + void IPCamera::setImageSize(int width, int height) { Capture::setImageSize(width, height); @@ -153,23 +155,23 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - + void IPCamera::setUrl(std::string url) { m_url=url; m_streamType = url.substr(0, 4); } - + void IPCamera::setRotation(int angle) { Capture::setRotation(angle); } - + void IPCamera::setDelay(int msec) { Capture::setDelay(msec); } - + void IPCamera::open(){} void IPCamera::open(const char * url) { @@ -187,20 +189,20 @@ namespace kerberos { m_camera->release(); open(m_url.c_str()); - + if(!m_camera->isOpened()) { m_url += "?"; // retry with ? open(m_url.c_str()); - + if(!m_camera->isOpened()) { throw OpenCVException("can't open url of ip camera"); } } } - + void IPCamera::close() { try @@ -214,11 +216,11 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - + void IPCamera::update(){} - + bool IPCamera::isOpened() { return m_camera->isOpened(); } -} \ No newline at end of file +} diff --git a/src/kerberos/machinery/io/IoVideo.cpp b/src/kerberos/machinery/io/IoVideo.cpp index c56f918..6faea1c 100755 --- a/src/kerberos/machinery/io/IoVideo.cpp +++ b/src/kerberos/machinery/io/IoVideo.cpp @@ -132,7 +132,7 @@ namespace kerberos // ------------------------ // Start conversion thread. - + startConvertThread(); } @@ -259,7 +259,7 @@ namespace kerberos // ------------------ // Check if the camera supports on board recording (camera specific), - // and if you want to user it. If not it will fallback on the video writer + // and if you want to use it. If not it will fallback on the video writer // that ships with OpenCV/FFmpeg. if(m_capture->m_onBoardRecording && m_enableHardwareEncoding) @@ -278,6 +278,22 @@ namespace kerberos m_recording = true; } } + else if(m_capture->m_onFFMPEGrecording) // Use FFMPEG to record. + { + if(!m_recording) + { + // ---------------------------------------- + // The naming convention that will be used + // for the image. + + std::string pathToVideo = getVideoFormat(); + m_fileName = buildPath(pathToVideo, data) + "." + m_extension; + m_path = m_directory + m_fileName; + + startFFMPEGRecordThread(); + m_recording = true; + } + } else // Use built-in OpenCV { if(m_capture && m_writer == 0 && !m_recording) @@ -358,7 +374,82 @@ namespace kerberos return true; } - void * recordOnboad(void * self) + void * recordOnFFMPEG(void * self) + { + IoVideo * video = (IoVideo *) self; + + double cronoPause = (double)cvGetTickCount(); + double cronoTime = (double) (cv::getTickCount() / cv::getTickFrequency()); + double startedRecording = cronoTime; + + BINFO << "IoVideo (FFMPEG): start writing images"; + + pthread_mutex_lock(&video->m_write_lock); + + // Todo write video with FFMPEG.. + // .. (video->m_path); + + BINFO << "IoVideo: locked write thread"; + + pthread_mutex_lock(&video->m_time_lock); + double timeToRecord = video->m_timeStartedRecording + video->m_recordingTimeAfter; + pthread_mutex_unlock(&video->m_time_lock); + + try + { + while(cronoTime < timeToRecord + && cronoTime - startedRecording <= video->m_maxDuration) // lower than max recording time (especially for memory) + { + // update time to record; (locking) + pthread_mutex_lock(&video->m_time_lock); + timeToRecord = video->m_timeStartedRecording + video->m_recordingTimeAfter; + pthread_mutex_unlock(&video->m_time_lock); + + cronoPause = (double) cv::getTickCount(); + cronoTime = cronoPause / cv::getTickFrequency(); + + usleep(1000); // sleep 1s + } + } + catch(cv::Exception & ex) + { + pthread_mutex_unlock(&video->m_lock); + pthread_mutex_unlock(&video->m_time_lock); + LERROR << ex.what(); + } + + BINFO << "IoVideo: end writing images"; + + pthread_mutex_lock(&video->m_release_lock); + + try + { + // Todo stop writing video with FFMPEG.. + // ... + video->m_recording = false; + + if(video->m_createSymbol) + { + std::string link = SYMBOL_DIRECTORY + video->m_fileName; + std::string pathToVideo = video->m_directory + video->m_fileName; + symlink(pathToVideo.c_str(), link.c_str()); + } + } + catch(cv::Exception & ex) + { + LERROR << ex.what(); + } + + + BINFO << "IoVideo: remove videowriter"; + + pthread_mutex_unlock(&video->m_release_lock); + pthread_mutex_unlock(&video->m_write_lock); + + BINFO << "IoVideo: unlocking write thread"; + } + + void * recordOnboard(void * self) { IoVideo * video = (IoVideo *) self; @@ -366,7 +457,7 @@ namespace kerberos double cronoTime = (double) (cv::getTickCount() / cv::getTickFrequency()); double startedRecording = cronoTime; - BINFO << "IoVideo: start writing images"; + BINFO << "IoVideo (OnBoard): start writing images"; pthread_mutex_lock(&video->m_write_lock); @@ -444,7 +535,7 @@ namespace kerberos double timeToSleep = 0; double startedRecording = cronoTime; - BINFO << "IoVideo: start writing images"; + BINFO << "IoVideo (OpenCV): start writing images"; pthread_mutex_lock(&video->m_write_lock); @@ -642,7 +733,7 @@ namespace kerberos void IoVideo::startOnboardRecordThread() { - pthread_create(&m_recordOnboardThread, NULL, recordOnboad, this); + pthread_create(&m_recordOnboardThread, NULL, recordOnboard, this); pthread_detach(m_recordOnboardThread); } @@ -652,6 +743,18 @@ namespace kerberos pthread_join(m_recordOnboardThread, NULL); } + void IoVideo::startFFMPEGRecordThread() + { + pthread_create(&m_recordOnFFMPEGThread, NULL, recordOnFFMPEG, this); + pthread_detach(m_recordOnFFMPEGThread); + } + + void IoVideo::stopFFMPEGRecordThread() + { + pthread_cancel(m_recordOnFFMPEGThread); + pthread_join(m_recordOnFFMPEGThread, NULL); + } + void IoVideo::startRecordThread() { pthread_create(&m_recordThread, NULL, recordContinuously, this); From 1ae392ae98bab819d36820a1df2cd961baf36509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Thu, 3 Aug 2017 13:26:05 +0200 Subject: [PATCH 02/18] disable ffmpeg recording for now --- src/kerberos/machinery/io/IoVideo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kerberos/machinery/io/IoVideo.cpp b/src/kerberos/machinery/io/IoVideo.cpp index 6faea1c..ebb50cb 100755 --- a/src/kerberos/machinery/io/IoVideo.cpp +++ b/src/kerberos/machinery/io/IoVideo.cpp @@ -278,7 +278,7 @@ namespace kerberos m_recording = true; } } - else if(m_capture->m_onFFMPEGrecording) // Use FFMPEG to record. + /*else if(m_capture->m_onFFMPEGrecording) // TODO: Use FFMPEG to record. { if(!m_recording) { @@ -293,7 +293,7 @@ namespace kerberos startFFMPEGRecordThread(); m_recording = true; } - } + }*/ else // Use built-in OpenCV { if(m_capture && m_writer == 0 && !m_recording) From 72fcaf60f3d7005d548004f7bad99012c3b4b534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Sun, 6 Aug 2017 08:44:37 +0200 Subject: [PATCH 03/18] cleanup threads raspicamera --- include/kerberos/capture/RaspiCamera.h | 1 + src/kerberos/capture/RaspiCamera.cpp | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/include/kerberos/capture/RaspiCamera.h b/include/kerberos/capture/RaspiCamera.h index 7ce40de..7422895 100755 --- a/include/kerberos/capture/RaspiCamera.h +++ b/include/kerberos/capture/RaspiCamera.h @@ -59,6 +59,7 @@ namespace kerberos Image * takeImage(); void startRecord(std::string path); void stopRecord(); + void stopThreads(); void open(); void close(); diff --git a/src/kerberos/capture/RaspiCamera.cpp b/src/kerberos/capture/RaspiCamera.cpp index a8cef0e..00186e5 100755 --- a/src/kerberos/capture/RaspiCamera.cpp +++ b/src/kerberos/capture/RaspiCamera.cpp @@ -229,20 +229,39 @@ namespace kerberos state.recording = false; // Start threads - pthread_create( &state.preview_thid, nullptr, &preview_thread, this ); - pthread_create( &state.record_thid, nullptr, &record_thread, this ); + pthread_create(&state.preview_thid, nullptr, &preview_thread, this); + pthread_create(&state.record_thid, nullptr, &record_thread, this); } + void RaspiCamera::stopThreads() + { + // ------------------------- + // Cancel the record thread. + + pthread_join(state.record_thid, nullptr); + + // ------------------------- + // Cancel the preview thread. + + pthread_join(state.preview_thid, nullptr); + } + RaspiCamera::~RaspiCamera() { - delete state.camera; delete state.preview_encode; delete state.record_encode; + delete state.camera; } void RaspiCamera::close() { state.running = false; + stopThreads(); + + state.camera->SetCapturing( false ); + state.camera->SetState(Component::StateIdle); + state.preview_encode->SetState(Component::StateIdle); + state.record_encode->SetState(Component::StateIdle); } void RaspiCamera::update(){} From 607dace43acc6e9acc8f908ccbc174d6b790249d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Mon, 7 Aug 2017 08:38:55 +0200 Subject: [PATCH 04/18] cleanup + add detached thread --- src/kerberos/Kerberos.cpp | 2 + src/kerberos/capture/Capture.cpp | 37 +++++++-------- src/kerberos/capture/RaspiCamera.cpp | 67 +++++++++++++++------------- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/kerberos/Kerberos.cpp b/src/kerberos/Kerberos.cpp index 8ad7aa3..0f370cb 100755 --- a/src/kerberos/Kerberos.cpp +++ b/src/kerberos/Kerberos.cpp @@ -313,6 +313,7 @@ namespace kerberos } pthread_create(&m_streamThread, NULL, streamContinuously, this); + pthread_detach(m_streamThread); } void Kerberos::stopStreamThread() @@ -395,6 +396,7 @@ namespace kerberos // Start a new thread that cheks for detections pthread_create(&m_ioThread, NULL, checkDetectionsContinuously, this); + pthread_detach(m_ioThread); } void Kerberos::stopIOThread() diff --git a/src/kerberos/capture/Capture.cpp b/src/kerberos/capture/Capture.cpp index 60f39a2..2d06e83 100755 --- a/src/kerberos/capture/Capture.cpp +++ b/src/kerberos/capture/Capture.cpp @@ -1,38 +1,38 @@ #include "capture/Capture.h" - + namespace kerberos { void Capture::setup(kerberos::StringMap & settings, int width, int height, int angle) { // -------------------------- // Make width & height global. - + settings["capture.width"] = helper::to_string(width); settings["capture.height"] = helper::to_string(height); settings["capture.angle"] = helper::to_string(angle); - + // ---------------- // Initialize mutex - + pthread_mutex_init(&m_lock, NULL); } - + void Capture::setImageSize(int width, int height) { m_frameWidth = width; m_frameHeight = height; } - + void Capture::setRotation(int angle) { m_angle = angle; } - + void Capture::setDelay(int msec) { - m_delay = msec; + m_delay = msec; } - + ImageVector & Capture::takeImages(int numberOfImages) { m_images.resize(numberOfImages); @@ -68,18 +68,18 @@ namespace kerberos // ------------- // Take images - + for(int i = m_images.size()-numberOfImages; i < m_images.size(); i++) { m_images[i] = takeImage(); } return m_images; } - + // ------------------------------------------- // Function ran in a thread, which continously // grabs frames. - + void * grabContinuously(void * self) { Capture * capture = (Capture *) self; @@ -100,22 +100,23 @@ namespace kerberos usleep(333*100); } } - + void Capture::startGrabThread() { // ------------------------------------------------ // Start a new thread that grabs images continously. // This is needed to clear the buffer of the capture device. - - pthread_create(&m_captureThread, NULL, grabContinuously, this); + + pthread_create(&m_captureThread, NULL, grabContinuously, this); + pthread_detach(m_captureThread); } - + void Capture::stopGrabThread() { // ---------------------------------- // Cancel the existing capture thread, // before deleting the device. - + pthread_cancel(m_captureThread); } -} \ No newline at end of file +} diff --git a/src/kerberos/capture/RaspiCamera.cpp b/src/kerberos/capture/RaspiCamera.cpp index 00186e5..0b9a1a2 100755 --- a/src/kerberos/capture/RaspiCamera.cpp +++ b/src/kerberos/capture/RaspiCamera.cpp @@ -16,46 +16,50 @@ struct State { std::ofstream file; -static int HighPri( const int pri ) +static int HighPri(const int pri) { - struct sched_param sched; - memset( &sched, 0, sizeof(sched) ); + struct sched_param sched; + memset(&sched, 0, sizeof(sched)); - if ( pri > sched_get_priority_max( SCHED_RR ) ) { - sched.sched_priority = sched_get_priority_max( SCHED_RR ); - } else { - sched.sched_priority = pri; - } + if (pri > sched_get_priority_max(SCHED_RR)) + { + sched.sched_priority = sched_get_priority_max( SCHED_RR ); + } + else + { + sched.sched_priority = pri; + } - return sched_setscheduler( 0, SCHED_RR, &sched ); + return sched_setscheduler(0, SCHED_RR, &sched); } -void* preview_thread( void* self ) +void* preview_thread(void* self) { - kerberos::RaspiCamera * capture = (kerberos::RaspiCamera *) self; + kerberos::RaspiCamera * capture = (kerberos::RaspiCamera *) self; - // Retrieve OMX buffer - capture->data_buffer = state.camera->outputPorts()[70].buffer->pBuffer; - capture->mjpeg_data_buffer = state.preview_encode->outputPorts()[201].buffer->pBuffer; + // Retrieve OMX buffer + capture->data_buffer = state.camera->outputPorts()[70].buffer->pBuffer; + capture->mjpeg_data_buffer = state.preview_encode->outputPorts()[201].buffer->pBuffer; - HighPri(99); // Mark the thread as high priority + HighPri(99); // Mark the thread as high priority - // ATTENTION : Each loop must take less time than it takes to the camera to take one frame - // otherwise it will cause underflow which can lead to camera stalling - // a good solution is to implement frame skipping (measure time between to loops, if this time - // is too big, just skip image processing and MJPEG sendout) + // ATTENTION : Each loop must take less time than it takes to the camera to take one frame + // otherwise it will cause underflow which can lead to camera stalling + // a good solution is to implement frame skipping (measure time between to loops, if this time + // is too big, just skip image processing and MJPEG sendout) - while ( state.running ) { - // Get YUV420 image from preview port, this is a blocking call - // If zero-copy is activated, we don't pass any buffer - capture->data_length = state.camera->getOutputData(70, nullptr); - } + while (state.running) + { + // Get YUV420 image from preview port, this is a blocking call + // If zero-copy is activated, we don't pass any buffer + capture->data_length = state.camera->getOutputData(70, nullptr); + } - return nullptr; + return nullptr; } -void* record_thread( void* argp ) +void* record_thread(void* argp) { uint8_t* data = new uint8_t[65536*4]; while ( state.running ) { @@ -230,11 +234,16 @@ namespace kerberos // Start threads pthread_create(&state.preview_thid, nullptr, &preview_thread, this); + pthread_detach(state.preview_thid); + pthread_create(&state.record_thid, nullptr, &record_thread, this); + pthread_detach(state.record_thid); } void RaspiCamera::stopThreads() { + state.running = false; + // ------------------------- // Cancel the record thread. @@ -255,10 +264,8 @@ namespace kerberos void RaspiCamera::close() { - state.running = false; stopThreads(); - - state.camera->SetCapturing( false ); + state.camera->SetCapturing(false); state.camera->SetState(Component::StateIdle); state.preview_encode->SetState(Component::StateIdle); state.record_encode->SetState(Component::StateIdle); @@ -268,7 +275,7 @@ namespace kerberos bool RaspiCamera::isOpened() { - return true; + return state.running; } void RaspiCamera::startRecord(std::string path) From 4f10e031abb76ce01a851b10fff3fe8c0159b547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Wed, 9 Aug 2017 10:40:43 +0200 Subject: [PATCH 05/18] remove newline --- src/kerberos/Kerberos.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/kerberos/Kerberos.cpp b/src/kerberos/Kerberos.cpp index 0f370cb..820fbdb 100755 --- a/src/kerberos/Kerberos.cpp +++ b/src/kerberos/Kerberos.cpp @@ -9,7 +9,6 @@ namespace kerberos setParameters(parameters); - // --------------------- // initialize RestClient From 07832f082a84f760633acb888ef79b3702f19d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Wed, 9 Aug 2017 11:18:10 +0200 Subject: [PATCH 06/18] destroy tunnel when closing capture --- src/kerberos/capture/RaspiCamera.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kerberos/capture/RaspiCamera.cpp b/src/kerberos/capture/RaspiCamera.cpp index 0b9a1a2..772081d 100755 --- a/src/kerberos/capture/RaspiCamera.cpp +++ b/src/kerberos/capture/RaspiCamera.cpp @@ -269,6 +269,7 @@ namespace kerberos state.camera->SetState(Component::StateIdle); state.preview_encode->SetState(Component::StateIdle); state.record_encode->SetState(Component::StateIdle); + Component::DestroyTunnel(state.record_encode); } void RaspiCamera::update(){} From fd690462ecc36837ebfdc3d7b7a8abdc8b96c413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Wed, 9 Aug 2017 14:55:36 +0200 Subject: [PATCH 07/18] add extra logging --- src/kerberos/capture/RaspiCamera.cpp | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/kerberos/capture/RaspiCamera.cpp b/src/kerberos/capture/RaspiCamera.cpp index 772081d..08a64b4 100755 --- a/src/kerberos/capture/RaspiCamera.cpp +++ b/src/kerberos/capture/RaspiCamera.cpp @@ -48,30 +48,39 @@ void* preview_thread(void* self) // a good solution is to implement frame skipping (measure time between to loops, if this time // is too big, just skip image processing and MJPEG sendout) + BINFO << "RaspiCamera: Entering preview thread."; while (state.running) { // Get YUV420 image from preview port, this is a blocking call // If zero-copy is activated, we don't pass any buffer capture->data_length = state.camera->getOutputData(70, nullptr); } + BINFO << "RaspiCamera: Exiting preview thread."; return nullptr; } -void* record_thread(void* argp) +void* record_thread(void* self) { - uint8_t* data = new uint8_t[65536*4]; - while ( state.running ) { - // Consume h264 data, this is a blocking call - int32_t datalen = state.record_encode->getOutputData(data); - if ( datalen > 0 && state.recording ) { - file.write((char*) data, datalen); - file.flush(); + kerberos::RaspiCamera * capture = (kerberos::RaspiCamera *) self; + + uint8_t* data = new uint8_t[65536*4]; + + BINFO << "RaspiCamera: Entering record thread."; + while(state.running) + { + // Consume h264 data, this is a blocking call + int32_t datalen = state.record_encode->getOutputData(data); + if(datalen > 0 && state.recording) + { + file.write((char*) data, datalen); + file.flush(); + } } - } + BINFO << "RaspiCamera: Exiting record thread."; - return nullptr; + return nullptr; } namespace kerberos From 64bf6ca0e80cd26372853b72969881b248454a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Wed, 9 Aug 2017 15:26:37 +0200 Subject: [PATCH 08/18] detach cloud health thread --- src/kerberos/cloud/Cloud.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kerberos/cloud/Cloud.cpp b/src/kerberos/cloud/Cloud.cpp index 52186b0..6eec83c 100755 --- a/src/kerberos/cloud/Cloud.cpp +++ b/src/kerberos/cloud/Cloud.cpp @@ -291,6 +291,7 @@ namespace kerberos { cloudConnection = new RestClient::Connection(CLOUD); pthread_create(&m_healthThread, NULL, deviceHealth, this); + pthread_detach(m_healthThread); } void Cloud::stopHealthThread() From 919effe8f347a005d9836797535eb9a0155de8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Thu, 10 Aug 2017 15:31:02 +0200 Subject: [PATCH 09/18] call the destroy all function --- src/kerberos/capture/RaspiCamera.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/kerberos/capture/RaspiCamera.cpp b/src/kerberos/capture/RaspiCamera.cpp index 08a64b4..c409396 100755 --- a/src/kerberos/capture/RaspiCamera.cpp +++ b/src/kerberos/capture/RaspiCamera.cpp @@ -274,11 +274,7 @@ namespace kerberos void RaspiCamera::close() { stopThreads(); - state.camera->SetCapturing(false); - state.camera->SetState(Component::StateIdle); - state.preview_encode->SetState(Component::StateIdle); - state.record_encode->SetState(Component::StateIdle); - Component::DestroyTunnel(state.record_encode); + Component::DestroyAll(); } void RaspiCamera::update(){} From c258ece36bf6fb4a99f96cd7e227fbafcb92001e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Thu, 10 Aug 2017 15:47:11 +0200 Subject: [PATCH 10/18] move destroyall --- src/kerberos/capture/RaspiCamera.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/kerberos/capture/RaspiCamera.cpp b/src/kerberos/capture/RaspiCamera.cpp index c409396..2144641 100755 --- a/src/kerberos/capture/RaspiCamera.cpp +++ b/src/kerberos/capture/RaspiCamera.cpp @@ -266,15 +266,12 @@ namespace kerberos RaspiCamera::~RaspiCamera() { - delete state.preview_encode; - delete state.record_encode; - delete state.camera; + Component::DestroyAll(); } void RaspiCamera::close() { stopThreads(); - Component::DestroyAll(); } void RaspiCamera::update(){} From e098dc29baa65b15799006be85b38077a2ec7df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Sun, 13 Aug 2017 20:06:30 +0200 Subject: [PATCH 11/18] adding health check, which continuously checks if the capture device is grabbing --- exceptions/KerberosException.h | 35 ++++++++++++++++++------ include/kerberos/capture/Capture.h | 5 ++++ src/kerberos/Kerberos.cpp | 2 ++ src/kerberos/capture/Capture.cpp | 41 ++++++++++++++++++++++++++++ src/kerberos/capture/IPCamera.cpp | 1 + src/kerberos/capture/RaspiCamera.cpp | 2 ++ src/kerberos/capture/USBCamera.cpp | 39 +++++++++++++------------- 7 files changed, 98 insertions(+), 27 deletions(-) diff --git a/exceptions/KerberosException.h b/exceptions/KerberosException.h index 1855897..dd1fbeb 100755 --- a/exceptions/KerberosException.h +++ b/exceptions/KerberosException.h @@ -8,7 +8,7 @@ // // The copyright to the computer program(s) herein // is the property of Verstraeten.io, Belgium. -// The program(s) may be used and/or copied under +// The program(s) may be used and/or copied under // the CC-NC-ND license model. // // https://doc.kerberos.io/license @@ -26,12 +26,16 @@ namespace kerberos class KerberosException : public Exception { public: + KerberosException(const char * msg):message(msg){}; virtual const char* what() const throw() { char * output = new char[300]; - strcpy (output, "Kerberos : A problem occured in the kerberos source."); + strcpy (output, "Kerberos : General : A problem occured : "); + strcat (output, message); return output; }; + private: + const char * message; }; class KerberosFactoryCouldNotCreateException : public Exception @@ -40,15 +44,15 @@ namespace kerberos KerberosFactoryCouldNotCreateException(const char * message):message(message){}; virtual const char * what() const throw() { - char * output = new char[300]; - strcpy (output, "Kerberos : Factory : Could not create an instance of message: "); - strcat (output, message); - return output; + char * output = new char[300]; + strcpy (output, "Kerberos : Factory : Could not create an instance of message: "); + strcat (output, message); + return output; }; private: const char * message; }; - + class KerberosCouldNotOpenCamera : public Exception { public: @@ -62,6 +66,21 @@ namespace kerberos }; private: const char * message; + }; + + class KerberosCouldNotGrabFromCamera : public Exception + { + public: + KerberosCouldNotGrabFromCamera(const char * message):message(message){}; + virtual const char* what() const throw() + { + char * output = new char[300]; + strcpy (output, "Kerberos : Capture : Could not grab from capture: "); + strcat (output, message); + return output; + }; + private: + const char * message; }; } -#endif \ No newline at end of file +#endif diff --git a/include/kerberos/capture/Capture.h b/include/kerberos/capture/Capture.h index cc42821..6188e4b 100755 --- a/include/kerberos/capture/Capture.h +++ b/include/kerberos/capture/Capture.h @@ -28,6 +28,7 @@ #include //inet_addr #include //write #include //for threading , link with lpthread +#include namespace kerberos { @@ -40,6 +41,8 @@ namespace kerberos public: pthread_mutex_t m_lock; pthread_t m_captureThread; + pthread_t m_healthThread; + std::atomic healthCounter; // increment to check if capture is still grabbing images. int m_frameWidth, m_frameHeight; int m_angle; // 90, 180, 270 @@ -75,6 +78,8 @@ namespace kerberos void startGrabThread(); void stopGrabThread(); + void startHealthThread(); + void stopHealthThread(); }; template diff --git a/src/kerberos/Kerberos.cpp b/src/kerberos/Kerberos.cpp index 820fbdb..d134820 100755 --- a/src/kerberos/Kerberos.cpp +++ b/src/kerberos/Kerberos.cpp @@ -212,6 +212,7 @@ namespace kerberos { machinery->disableCapture(); capture->stopGrabThread(); + capture->stopHealthThread(); capture->close(); } delete capture; @@ -225,6 +226,7 @@ namespace kerberos capture = Factory::getInstance()->create(settings.at("capture")); capture->setup(settings); capture->startGrabThread(); + capture->startHealthThread(); // ------------------ // Initialize stream diff --git a/src/kerberos/capture/Capture.cpp b/src/kerberos/capture/Capture.cpp index 2d06e83..b3d2adb 100755 --- a/src/kerberos/capture/Capture.cpp +++ b/src/kerberos/capture/Capture.cpp @@ -119,4 +119,45 @@ namespace kerberos pthread_cancel(m_captureThread); } + + + // ------------------------------------------- + // Function ran in a thread, which continously + // checkes the health of the camera. + + void * healthContinuously(void * self) + { + Capture * capture = (Capture *) self; + + int healthCounter = capture->healthCounter.load(); + for(;;) + { + usleep(30000*1000); // every 30s. + LINFO << "Capture: checking health status of camera."; + + if(healthCounter == capture->healthCounter.load()) + { + LINFO << "Capture: devices is blocking, and not grabbing any more frames."; + throw KerberosCouldNotGrabFromCamera("devices is blocking, and not grabbin any more frames."); + } + } + } + + void Capture::startHealthThread() + { + // ------------------------------------------------ + // Start a new thread that verifies the health of the camera. + + pthread_create(&m_healthThread, NULL, healthContinuously, this); + pthread_detach(m_healthThread); + } + + void Capture::stopHealthThread() + { + // ---------------------------------- + // Cancel the existing health thread, + // before deleting the device. + + pthread_cancel(m_healthThread); + } } diff --git a/src/kerberos/capture/IPCamera.cpp b/src/kerberos/capture/IPCamera.cpp index e799a14..ddaf6e6 100755 --- a/src/kerberos/capture/IPCamera.cpp +++ b/src/kerberos/capture/IPCamera.cpp @@ -45,6 +45,7 @@ namespace kerberos { pthread_mutex_lock(&m_lock); + healthCounter = std::rand() % 10000; if(!m_camera->grab()) { reopen(); diff --git a/src/kerberos/capture/RaspiCamera.cpp b/src/kerberos/capture/RaspiCamera.cpp index 2144641..bcfadf2 100755 --- a/src/kerberos/capture/RaspiCamera.cpp +++ b/src/kerberos/capture/RaspiCamera.cpp @@ -51,6 +51,8 @@ void* preview_thread(void* self) BINFO << "RaspiCamera: Entering preview thread."; while (state.running) { + capture->healthCounter = std::rand() % 10000; + // Get YUV420 image from preview port, this is a blocking call // If zero-copy is activated, we don't pass any buffer capture->data_length = state.camera->getOutputData(70, nullptr); diff --git a/src/kerberos/capture/USBCamera.cpp b/src/kerberos/capture/USBCamera.cpp index 74e5ffe..60330a1 100755 --- a/src/kerberos/capture/USBCamera.cpp +++ b/src/kerberos/capture/USBCamera.cpp @@ -13,18 +13,18 @@ namespace kerberos // Initialize executor (update the usb camera at specific times). tryToUpdateCapture.setAction(this, &USBCamera::update); tryToUpdateCapture.setInterval("thrice in 10 functions calls"); - + // Save width and height in settings Capture::setup(settings, width, height, angle); setImageSize(width, height); setRotation(angle); setDelay(delay); setDeviceNumber(deviceNumber); - + // Initialize USB Camera open(); } - + USBCamera::USBCamera(int width, int height) { try @@ -37,12 +37,13 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } }; - + void USBCamera::grab() { try { pthread_mutex_lock(&m_lock); + healthCounter = std::rand() % 10000; m_camera->grab(); pthread_mutex_unlock(&m_lock); } @@ -53,7 +54,7 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - + Image USBCamera::retrieve() { try @@ -71,29 +72,29 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - + Image * USBCamera::takeImage() { // Update camera, call executor's functor. tryToUpdateCapture(); - + // Take image try { // Delay camera for some time.. usleep(m_delay*1000); - + // Get image from camera Image * image = new Image(); - + pthread_mutex_lock(&m_lock); m_camera->grab(); m_camera->retrieve(image->getImage()); pthread_mutex_unlock(&m_lock); - + // Check if need to rotate the image image->rotate(m_angle); - + return image; } catch(cv::Exception & ex) @@ -102,8 +103,8 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - - + + void USBCamera::setImageSize(int width, int height) { Capture::setImageSize(width, height); @@ -117,7 +118,7 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - + void USBCamera::open() { try @@ -127,7 +128,7 @@ namespace kerberos m_camera->release(); m_camera->open(getDeviceNumber()); setImageSize(m_frameWidth, m_frameHeight); - + if(!isOpened()) { throw OpenCVException("can't open usb camera"); @@ -139,7 +140,7 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - + void USBCamera::close() { try @@ -153,11 +154,11 @@ namespace kerberos throw OpenCVException(ex.msg.c_str()); } } - + void USBCamera::update(){} - + bool USBCamera::isOpened() { return m_camera->isOpened(); } -} \ No newline at end of file +} From b9134bd828d2e17ec767f3c042a58d5efcb87d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Tue, 15 Aug 2017 20:57:30 +0200 Subject: [PATCH 12/18] bug: serious memory leak in streaming class. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the client socket wasn’t properly destroyed, that’s why after a while the library started throwing connection issues and the live stream wasn’t available anymore. --- src/kerberos/capture/Stream.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/kerberos/capture/Stream.cpp b/src/kerberos/capture/Stream.cpp index bfa9013..387f116 100755 --- a/src/kerberos/capture/Stream.cpp +++ b/src/kerberos/capture/Stream.cpp @@ -273,6 +273,10 @@ namespace kerberos LINFO << "Stream: authentication failed."; + FD_CLR(client, &master); + shutdown(client, 2); + close(client); + return false; } } @@ -310,8 +314,10 @@ namespace kerberos if (retval != 0 || error != 0 || socketState == -1) { - shutdown(clients[i], 2); FD_CLR(clients[i],&master); + shutdown(clients[i], 2); + close(clients[i]); + std::vector::iterator position = std::find(clients.begin(), clients.end(), clients[i]); if (position != clients.end()) { From c07dfb70fc2db53210d2121af80e07a321f9e727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Tue, 15 Aug 2017 21:18:57 +0200 Subject: [PATCH 13/18] Update External-Googletest.cmake --- cmake/External-Googletest.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/External-Googletest.cmake b/cmake/External-Googletest.cmake index 926e637..d144d6a 100755 --- a/cmake/External-Googletest.cmake +++ b/cmake/External-Googletest.cmake @@ -2,6 +2,7 @@ message("External project: Googletest") ExternalProject_Add(googletest GIT_REPOSITORY https://github.com/google/googletest + GIT_TAG 673c975a963f356b19fea90cb57b69192253da2a SOURCE_DIR googletest BUILD_IN_SOURCE 1 UPDATE_COMMAND "" @@ -20,4 +21,4 @@ set(GOOGLETEST_LIBRARY_DIR ${CMAKE_BINARY_DIR}/googletest/) include_directories(${GOOGLETEST_INCLUDE_DIR}) link_directories(${GOOGLETEST_LIBRARY_DIR}) -set(GOOGLETEST_LIBRARIES "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock.a" "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock_main.a") \ No newline at end of file +set(GOOGLETEST_LIBRARIES "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock.a" "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock_main.a") From f9e1a05a93c81538848db53ec2dca89efe2c6f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Tue, 15 Aug 2017 21:32:28 +0200 Subject: [PATCH 14/18] revert --- cmake/External-Googletest.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/cmake/External-Googletest.cmake b/cmake/External-Googletest.cmake index d144d6a..c39b5aa 100755 --- a/cmake/External-Googletest.cmake +++ b/cmake/External-Googletest.cmake @@ -2,7 +2,6 @@ message("External project: Googletest") ExternalProject_Add(googletest GIT_REPOSITORY https://github.com/google/googletest - GIT_TAG 673c975a963f356b19fea90cb57b69192253da2a SOURCE_DIR googletest BUILD_IN_SOURCE 1 UPDATE_COMMAND "" From 7b2fbbc84b2920da3c0b027e41af1b9283350ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Tue, 15 Aug 2017 21:36:07 +0200 Subject: [PATCH 15/18] Revert "Update External-Googletest.cmake" This reverts commit c07dfb70fc2db53210d2121af80e07a321f9e727. --- cmake/External-Googletest.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/External-Googletest.cmake b/cmake/External-Googletest.cmake index c39b5aa..926e637 100755 --- a/cmake/External-Googletest.cmake +++ b/cmake/External-Googletest.cmake @@ -20,4 +20,4 @@ set(GOOGLETEST_LIBRARY_DIR ${CMAKE_BINARY_DIR}/googletest/) include_directories(${GOOGLETEST_INCLUDE_DIR}) link_directories(${GOOGLETEST_LIBRARY_DIR}) -set(GOOGLETEST_LIBRARIES "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock.a" "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock_main.a") +set(GOOGLETEST_LIBRARIES "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock.a" "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock_main.a") \ No newline at end of file From 51fbdd730335a1211aca887097ee9e5999529cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Tue, 15 Aug 2017 21:37:19 +0200 Subject: [PATCH 16/18] add tag --- cmake/External-Googletest.cmake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake/External-Googletest.cmake b/cmake/External-Googletest.cmake index 926e637..fd95600 100755 --- a/cmake/External-Googletest.cmake +++ b/cmake/External-Googletest.cmake @@ -1,7 +1,8 @@ message("External project: Googletest") ExternalProject_Add(googletest - GIT_REPOSITORY https://github.com/google/googletest + GIT_REPOSITORY https://github.com/google/googletest + GIT_TAG 673c975a963f356b19fea90cb57b69192253da2a SOURCE_DIR googletest BUILD_IN_SOURCE 1 UPDATE_COMMAND "" @@ -20,4 +21,4 @@ set(GOOGLETEST_LIBRARY_DIR ${CMAKE_BINARY_DIR}/googletest/) include_directories(${GOOGLETEST_INCLUDE_DIR}) link_directories(${GOOGLETEST_LIBRARY_DIR}) -set(GOOGLETEST_LIBRARIES "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock.a" "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock_main.a") \ No newline at end of file +set(GOOGLETEST_LIBRARIES "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock.a" "${GOOGLETEST_LIBRARY_DIR}googlemock/libgmock_main.a") From 0d8a1bfeed95d3a7f5a364f00f10324eefb8c739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Thu, 17 Aug 2017 08:31:50 +0200 Subject: [PATCH 17/18] upgrade version --- include/kerberos/Globals.h | 2 +- src/CMakeLists.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/kerberos/Globals.h b/include/kerberos/Globals.h index 2f15816..1831014 100755 --- a/include/kerberos/Globals.h +++ b/include/kerberos/Globals.h @@ -17,7 +17,7 @@ #ifndef __Version_H_INCLUDED__ // if Version.h hasn't been included yet... #define __Version_H_INCLUDED__ // #define this so the compiler knows it has been included - #define VERSION "2.3.0" + #define VERSION "2.3.1" #define HADES "https://hades.kerberos.io" #define CLOUD "https://cloud.kerberos.io" #define SYMBOL_DIRECTORY "/etc/opt/kerberosio/symbols/" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd074ce..ca00d34 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -147,10 +147,10 @@ SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Cédric Verstraeten") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Kerberos is a low-budget surveillance solution, that uses computer vision algorithms to detect changes, and that can trigger other devices. Kerberos is open source so you, but also other people, can customize the source to your needs and share it with our community. It has a green footprint when deploying on the Raspberry Pi and it's easy to install, you only need to transfer the image to the SD card and you're done.") - set(CPACK_PACKAGE_VERSION "2.3.0") + set(CPACK_PACKAGE_VERSION "2.3.1") set(CPACK_PACKAGE_VERSION_MAJOR "2") set(CPACK_PACKAGE_VERSION_MINOR "3") - set(CPACK_PACKAGE_VERSION_PATCH "0") + set(CPACK_PACKAGE_VERSION_PATCH "1") string(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE) find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") From b7c8cfb5e053fa64fb6045efe9fbe9402b074678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Thu, 17 Aug 2017 08:48:27 +0200 Subject: [PATCH 18/18] add replace all function --- include/kerberos/Helper.h | 1 + src/kerberos/Helper.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/include/kerberos/Helper.h b/include/kerberos/Helper.h index 4ffbdde..9506fbc 100755 --- a/include/kerberos/Helper.h +++ b/include/kerberos/Helper.h @@ -47,6 +47,7 @@ namespace kerberos kerberos::StringMap getSettingsFromXML(const std::string & path); std::string printStringMap(const std::string & prefix, const kerberos::StringMap & map); bool replace(std::string& str, const std::string& from, const std::string& to); + void replaceAll(std::string& str, const std::string& from, const std::string& to); std::string to_string (const int & t); std::string generatePath(const std::string timezone, const std::string & subDirectory = ""); std::string getTimestamp(); diff --git a/src/kerberos/Helper.cpp b/src/kerberos/Helper.cpp index 884f60d..df0fc7b 100755 --- a/src/kerberos/Helper.cpp +++ b/src/kerberos/Helper.cpp @@ -192,6 +192,16 @@ namespace kerberos return true; } + void replaceAll(std::string& str, const std::string& from, const std::string& to) + { + size_t start_pos = 0; + while((start_pos = str.find(from)) != std::string::npos) + { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + } + std::string generatePath(const std::string timezone, const std::string & subDirectory) { srand(int(time(NULL)));