diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cea30c87..aa37253bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ set(ZM_MYSQL_ENGINE "InnoDB" CACHE STRING "MySQL engine to use with database, de set(ZM_NO_MMAP "OFF" CACHE BOOL "Set to ON to not use mmap shared memory. Shouldn't be enabled unless you experience problems with the shared memory. default: OFF") set(ZM_NO_FFMPEG "OFF" CACHE BOOL "Set to ON to skip ffmpeg checks and force building ZM without ffmpeg. default: OFF") set(ZM_NO_LIBVLC "OFF" CACHE BOOL "Set to ON to skip libvlc checks and force building ZM without libvlc. default: OFF") +set(ZM_NO_CURL "OFF" CACHE BOOL "Set to ON to skip cURL checks and force building ZM without cURL. default: OFF") set(ZM_NO_X10 "OFF" CACHE BOOL "Set to ON to build ZoneMinder without X10 support. default: OFF") set(ZM_PERL_SUBPREFIX "${CMAKE_INSTALL_LIBDIR}/perl5" CACHE PATH "Use a different directory for the zm perl modules. NOTE: This is a subprefix, e.g. lib will be turned into /lib, default: /perl5") set(ZM_PERL_USE_PATH "${CMAKE_INSTALL_PREFIX}/${ZM_PERL_SUBPREFIX}" CACHE PATH "Override the include path for zm perl modules. Useful if you are moving the perl modules without using the ZM_PERL_SUBPREFIX option. default: /") @@ -105,18 +106,21 @@ else(ZLIB_FOUND) set(optlibsnotfound "${optlibsnotfound} zlib") endif(ZLIB_FOUND) -# cURL -find_package(CURL) -if(CURL_FOUND) - set(HAVE_LIBCURL 1) - list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) - include_directories(${CURL_INCLUDE_DIRS}) - set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS}) - check_include_file("curl/curl.h" HAVE_CURL_CURL_H) - set(optlibsfound "${optlibsfound} cURL") -else(CURL_FOUND) - set(optlibsnotfound "${optlibsnotfound} cURL") -endif(CURL_FOUND) +# Do not check for cURL if ZM_NO_CURL is on +if(NOT ZM_NO_CURL) + # cURL + find_package(CURL) + if(CURL_FOUND) + set(HAVE_LIBCURL 1) + list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) + include_directories(${CURL_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS}) + check_include_file("curl/curl.h" HAVE_CURL_CURL_H) + set(optlibsfound "${optlibsfound} cURL") + else(CURL_FOUND) + set(optlibsnotfound "${optlibsnotfound} cURL") + endif(CURL_FOUND) +endif(ZM_NO_CURL) # jpeg find_package(JPEG) diff --git a/configure.ac b/configure.ac index 6e7451426..b32b7c12e 100644 --- a/configure.ac +++ b/configure.ac @@ -278,6 +278,7 @@ AC_CHECK_LIB(swscale,sws_scale,,,-lswscale) AC_CHECK_LIB(vlc,libvlc_new,,AC_MSG_WARN(libvlc.a may be required for streaming)) AC_CHECK_LIB(bz2,BZ2_bzCompress,,AC_MSG_WARN(zm requires libbz2.a for recent versions of ffmpeg)) AC_CHECK_LIB(z,compress,,) +AC_CHECK_LIB(curl,curl_global_init,,) # Checks for header files. AC_FUNC_ALLOCA @@ -315,6 +316,7 @@ AC_CHECK_HEADERS(sys/shm.h,,,) fi AC_CHECK_HEADERS(zlib.h,,,) AC_CHECK_HEADERS(vlc/vlc.h,,,) +AC_CHECK_HEADERS(curl/curl.h,,,) if test "$ZM_SSL_LIB" == "openssl"; then AC_CHECK_DECLS(MD5,,AC_MSG_ERROR([zm requires openssl/md5.h - use ZM_SSL_LIB option to select gnutls instead]),[#include diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index e94a589e5..aea098e2d 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -63,7 +63,7 @@ DROP TABLE IF EXISTS `Controls`; CREATE TABLE `Controls` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', - `Type` enum('Local','Remote','Ffmpeg','Libvlc') NOT NULL default 'Local', + `Type` enum('Local','Remote','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local', `Protocol` varchar(64) default NULL, `CanWake` tinyint(3) unsigned NOT NULL default '0', `CanSleep` tinyint(3) unsigned NOT NULL default '0', @@ -282,7 +282,7 @@ DROP TABLE IF EXISTS `MonitorPresets`; CREATE TABLE `MonitorPresets` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', - `Type` enum('Local','Remote','File','Ffmpeg','Libvlc') NOT NULL default 'Local', + `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local', `Device` tinytext, `Channel` tinytext, `Format` int(10) unsigned default NULL, @@ -313,7 +313,7 @@ DROP TABLE IF EXISTS `Monitors`; CREATE TABLE `Monitors` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', - `Type` enum('Local','Remote','File','Ffmpeg','Libvlc') NOT NULL default 'Local', + `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local', `Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor', `Enabled` tinyint(3) unsigned NOT NULL default '1', `LinkedMonitors` varchar(255) NOT NULL default '', diff --git a/db/zm_update-1.26.6.sql b/db/zm_update-1.26.6.sql index 4b667d8a8..37c77fca4 100644 --- a/db/zm_update-1.26.6.sql +++ b/db/zm_update-1.26.6.sql @@ -3,9 +3,15 @@ -- -- --- Add Libvlc monitor type +-- Add Libvlc and cURL monitor types -- -ALTER TABLE Controls modify column Type enum('Local','Remote','Ffmpeg','Libvlc') NOT NULL default 'Local'; -ALTER TABLE MonitorPresets modify column Type enum('Local','Remote','File','Ffmpeg','Libvlc') NOT NULL default 'Local'; -ALTER TABLE Monitors modify column Type enum('Local','Remote','File','Ffmpeg','Libvlc') NOT NULL default 'Local'; +ALTER TABLE Controls modify column Type enum('Local','Remote','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local'; +ALTER TABLE MonitorPresets modify column Type enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local'; +ALTER TABLE Monitors modify column Type enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local'; + +-- +-- Add required fields for cURL authenication +-- +ALTER TABLE `Monitors` ADD `User` VARCHAR(32) NOT NULL AFTER `SubPath`; +ALTER TABLE `Monitors` ADD `Pass` VARCHAR(32) NOT NULL AFTER `User`; diff --git a/src/Makefile.am b/src/Makefile.am index 64c9163cb..9feaa15d5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,6 +26,7 @@ zm_SOURCES = \ zm_comms.cpp \ zm_config.cpp \ zm_coord.cpp \ + zm_curl_camera.cpp \ zm.cpp \ zm_db.cpp \ zm_logger.cpp \ @@ -76,6 +77,7 @@ noinst_HEADERS = \ zm_config_defines.h \ zm_config.h \ zm_coord.h \ + zm_curl_camera.h \ zm_db.h \ zm_logger.h \ zm_event.h \ diff --git a/src/zm_camera.h b/src/zm_camera.h index d7af79b25..bddb66fc8 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -59,6 +59,7 @@ public: bool IsFile() const { return( type == FILE_SRC ); } bool IsFfmpeg() const { return( type == FFMPEG_SRC ); } bool IsLibvlc() const { return( type == LIBVLC_SRC ); } + bool IscURL() const { return( type == CURL_SRC ); } unsigned int Width() const { return( width ); } unsigned int Height() const { return( height ); } unsigned int Colours() const { return( colours ); } diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index bbdb12502..f8240e793 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -18,26 +18,24 @@ // #include "zm.h" +#include "zm_curl_camera.h" #if HAVE_LIBCURL -static FILE* curldebugfile = NULL; // Remove later - -#include "zm_curl_camera.h" +#define CURL_MAXRETRY 5 +const char* content_length_match = "Content-Length:"; +const char* content_type_match = "Content-Type:"; +size_t content_length_match_len; +size_t content_type_match_len; cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), - mPath( p_path ), mUser( p_user ), mPass ( p_pass ) + mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), mode ( MODE_UNSET ) { - c = NULL; if ( capture ) { Initialise(); - c = curl_easy_init(); - if(c == NULL) { - Fatal("Failed getting easy handle from libcurl"); - } } } @@ -45,57 +43,71 @@ cURLCamera::~cURLCamera() { if ( capture ) { - if(c != NULL) { - curl_easy_cleanup(c); - c = NULL; - } + Terminate(); } } void cURLCamera::Initialise() { - ret = curl_global_init(CURL_GLOBAL_ALL); - if(ret != CURLE_OK) { - Fatal("libcurl initialization failed: ", curl_easy_strerror(ret)); + content_length_match_len = strlen(content_length_match); + content_type_match_len = strlen(content_type_match); + + /* cURL initialization */ + cRet = curl_global_init(CURL_GLOBAL_ALL); + if(cRet != CURLE_OK) { + Fatal("libcurl initialization failed: ", curl_easy_strerror(cRet)); } Debug(2,"libcurl version: %s",curl_version()); - curldebugfile = fopen("/tmp/curl_debug.log","w"); // Remove later + /* Create the shared data mutex */ + nRet = pthread_mutex_init(&shareddata_mutex, NULL); + if(nRet != 0) { + Fatal("Shared data mutex creation failed: %s",strerror(nRet)); + } + /* Create the data available condition variable */ + nRet = pthread_cond_init(&data_available_cond, NULL); + if(nRet != 0) { + Fatal("Data available condition variable creation failed: %s",strerror(nRet)); + } + /* Create the request complete condition variable */ + nRet = pthread_cond_init(&request_complete_cond, NULL); + if(nRet != 0) { + Fatal("Request complete condition variable creation failed: %s",strerror(nRet)); + } + + /* Create the thread */ + nRet = pthread_create(&thread, NULL, thread_func_dispatcher, this); + if(nRet != 0) { + Fatal("Thread creation failed: %s",strerror(nRet)); + } } void cURLCamera::Terminate() { + /* Signal the thread to terminate */ + bTerminate = true; + + /* Wait for thread termination */ + pthread_join(thread, NULL); + + /* Destroy condition variables */ + pthread_cond_destroy(&request_complete_cond); + pthread_cond_destroy(&data_available_cond); + + /* Destroy mutex */ + pthread_mutex_destroy(&shareddata_mutex); + + /* cURL cleanup */ curl_global_cleanup(); - fclose(curldebugfile); // Remove later } int cURLCamera::PrimeCapture() { Info( "Priming capture from %s", mPath.c_str() ); - /* Temporary */ - curl_easy_setopt(c, CURLOPT_VERBOSE, 1); - curl_easy_setopt(c, CURLOPT_STDERR, curldebugfile); - - ret = curl_easy_setopt(c, CURLOPT_URL, mPath.c_str()); - if(ret != CURLE_OK) - Fatal("Failed setting libcurl URL. error %d: ", curl_easy_strerror(ret)); - - ret = curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, &cURLCamera::header_callback); - if(ret != CURLE_OK) - Fatal("Failed setting libcurl header callback function. error %d: ", curl_easy_strerror(ret)); - - ret = curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, &cURLCamera::data_callback); - if(ret != CURLE_OK) - Fatal("Failed setting libcurl header callback function. error %d: ", curl_easy_strerror(ret)); - - ret = curl_easy_setopt(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - if(ret != CURLE_OK) - Warning("Failed setting libcurl acceptable http authenication methods. error %d: ", curl_easy_strerror(ret)); - return 0; } @@ -107,20 +119,165 @@ int cURLCamera::PreCapture() int cURLCamera::Capture( Image &image ) { + bool frameComplete = false; uint8_t* directbuffer; - // bool frameComplete = false; - + + /* MODE_STREAM specific variables */ + bool SubHeadersParsingComplete = false; + unsigned int frame_content_length = 0; + std::string frame_content_type; + bool need_more_data = false; + /* Request a writeable buffer of the target image */ directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); if(directbuffer == NULL) { - Error("Failed requesting writeable buffer for the captured image."); + Error("Failed requesting writeable buffer for the captured image"); return (-1); } - //success = curl_easy_perform(easyhandle); - + while (!frameComplete) { - return (0); + lock(); + + if(mode == MODE_STREAM) { + + /* Subheader parsing */ + while(!SubHeadersParsingComplete && !need_more_data) { + + size_t crlf_start, crlf_end, crlf_size; + std::string subheader; + + /* Find crlf start */ + crlf_start = memcspn(databuffer,"\r\n",databuffer.size()); + if(crlf_start == databuffer.size()) { + /* Not found, wait for more data */ + need_more_data = true; + break; + } + + /* See if we have enough data for determining crlf length */ + if(databuffer.size() < crlf_start+5) { + /* Need more data */ + need_more_data = true; + break; + } + + /* Find crlf end and calculate crlf size */ + crlf_end = memspn(((const char*)databuffer.head())+crlf_start,"\r\n",5); + crlf_size = (crlf_start + crlf_end) - crlf_start; + + /* Is this the end of a previous stream? (This is just before the boundary) */ + if(crlf_start == 0) { + databuffer.consume(crlf_size); + continue; + } + + /* Check for invalid CRLF size */ + if(crlf_size > 4) { + Error("Invalid CRLF length"); + } + + /* Check if the crlf is \n\n or \r\n\r\n (marks end of headers, this is the last header) */ + if( (crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0) || (crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0) ) { + /* This is the last header */ + SubHeadersParsingComplete = true; + } + + /* Copy the subheader, excluding the crlf */ + subheader.assign(databuffer, crlf_start); + + /* Advance the buffer past this one */ + databuffer.consume(crlf_start+crlf_size); + + Debug(7,"Got subheader: %s",subheader.c_str()); + + /* Find where the data in this header starts */ + size_t subheader_data_start = subheader.rfind(' '); + if(subheader_data_start == std::string::npos) { + subheader_data_start = subheader.find(':'); + } + + /* Extract the data into a string */ + std::string subheader_data = subheader.substr(subheader_data_start+1, std::string::npos); + + Debug(8,"Got subheader data: %s",subheader_data.c_str()); + + /* Check the header */ + if(subheader.compare(0,content_length_match_len,content_length_match,content_length_match_len) == 0) { + /* Found the content-length header */ + frame_content_length = atoi(subheader_data.c_str()); + Debug(6,"Got content-length subheader: %d",frame_content_length); + } else if(subheader.compare(0,content_type_match_len,content_type_match,content_type_match_len) == 0) { + /* Found the content-type header */ + frame_content_type = subheader_data; + Debug(6,"Got content-type subheader: %s",frame_content_type.c_str()); + } + + } + + /* Attempt to extract the frame */ + if(!need_more_data) { + if(!SubHeadersParsingComplete) { + /* We haven't parsed all headers yet */ + need_more_data = true; + } else if(frame_content_length <= 0) { + /* Invalid frame */ + Error("Invalid frame: invalid content length"); + } else if(frame_content_type != "image/jpeg") { + /* Unsupported frame type */ + Error("Unsupported frame: %s",frame_content_type.c_str()); + } else if(frame_content_length > databuffer.size()) { + /* Incomplete frame, wait for more data */ + need_more_data = true; + } else { + /* All good. decode the image */ + image.DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); + frameComplete = true; + } + } + + /* Attempt to get more data */ + if(need_more_data) { + nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); + if(nRet != 0) { + Error("Failed waiting for available data condition variable: %s",strerror(nRet)); + return -18; + } + need_more_data = false; + } + + } else if(mode == MODE_SINGLE) { + /* Check if we have anything */ + if (!single_offsets.empty()) { + if( (single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front()) ) { + /* Extract frame */ + image.DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); + single_offsets.pop_front(); + frameComplete = true; + } else { + /* This shouldn't happen */ + Error("Internal error. Attempting recovery"); + databuffer.consume(single_offsets.front()); + single_offsets.pop_front(); + } + } else { + /* Don't have a frame yet, wait for the request complete condition variable */ + nRet = pthread_cond_wait(&request_complete_cond,&shareddata_mutex); + if(nRet != 0) { + Error("Failed waiting for request complete condition variable: %s",strerror(nRet)); + return -19; + } + } + } /* MODE_SINGLE */ + + unlock(); + + } /* frameComplete loop */ + + if(!frameComplete) + return -1; + + return 0; } int cURLCamera::PostCapture() @@ -129,12 +286,228 @@ int cURLCamera::PostCapture() return( 0 ); } -size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { +size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) +{ + lock(); + + /* Append the data we just received to our buffer */ + databuffer.append((const char*)buffer, size*nmemb); + + /* Signal data available */ + nRet = pthread_cond_signal(&data_available_cond); + if(nRet != 0) { + Error("Failed signaling data available condition variable: %s",strerror(nRet)); + return -16; + } + + unlock(); + + /* Return bytes processed */ + return size*nmemb; +} + + + +size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) +{ + std::string header; + header.assign((const char*)buffer, size*nmemb); + + Debug(4,"Got header: %s",header.c_str()); + + /* Check Content-Type header */ + if(strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) { + size_t pos = header.find(' '); + if(pos == std::string::npos) { + pos = header.find(':'); + } + + std::string content_type = header.substr(pos+1, std::string::npos); + Debug(6,"Content-Type is: %s",content_type.c_str()); + + lock(); + + const char* multipart_match = "multipart/x-mixed-replace"; + const char* image_jpeg_match = "image/jpeg"; + if(content_type.compare(0, strlen(multipart_match), multipart_match) == 0) { + Debug(7,"Content type matched as multipart/x-mixed-replace"); + mode = MODE_STREAM; + } else if(content_type.compare(0, strlen(image_jpeg_match), image_jpeg_match) == 0) { + Debug(7,"Content type matched as image/jpeg"); + mode = MODE_SINGLE; + } else { + Fatal("Unknown Content-Type: %s", content_type.c_str()); + } + + unlock(); + } + + /* Return bytes processed */ + return size*nmemb; +} + +void* cURLCamera::thread_func() +{ + int tRet; + double dSize; + + c = curl_easy_init(); + if(c == NULL) { + Fatal("Failed getting easy handle from libcurl"); + } + + /* Set URL */ + cRet = curl_easy_setopt(c, CURLOPT_URL, mPath.c_str()); + if(cRet != CURLE_OK) + Fatal("Failed setting libcurl URL: %s", curl_easy_strerror(cRet)); + + /* Header callback */ + cRet = curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher); + if(cRet != CURLE_OK) + Fatal("Failed setting libcurl header callback function: %s", curl_easy_strerror(cRet)); + cRet = curl_easy_setopt(c, CURLOPT_HEADERDATA, this); + if(cRet != CURLE_OK) + Fatal("Failed setting libcurl header callback object: %s", curl_easy_strerror(cRet)); + + /* Data callback */ + cRet = curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher); + if(cRet != CURLE_OK) + Fatal("Failed setting libcurl data callback function: %s", curl_easy_strerror(cRet)); + cRet = curl_easy_setopt(c, CURLOPT_WRITEDATA, this); + if(cRet != CURLE_OK) + Fatal("Failed setting libcurl data callback object: %s", curl_easy_strerror(cRet)); + + /* Progress callback */ + cRet = curl_easy_setopt(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher); + if(cRet != CURLE_OK) + Fatal("Failed setting libcurl progress callback function: %s", curl_easy_strerror(cRet)); + cRet = curl_easy_setopt(c, CURLOPT_PROGRESSDATA, this); + if(cRet != CURLE_OK) + Fatal("Failed setting libcurl progress callback object: %s", curl_easy_strerror(cRet)); + + /* Set username and password */ + if(!mUser.empty()) { + cRet = curl_easy_setopt(c, CURLOPT_USERNAME, mUser.c_str()); + if(cRet != CURLE_OK) + Error("Failed setting username: %s", curl_easy_strerror(cRet)); + } + if(!mPass.empty()) { + cRet = curl_easy_setopt(c, CURLOPT_PASSWORD, mPass.c_str()); + if(cRet != CURLE_OK) + Error("Failed setting password: %s", curl_easy_strerror(cRet)); + } + + /* Authenication preference */ + cRet = curl_easy_setopt(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + if(cRet != CURLE_OK) + Warning("Failed setting libcurl acceptable http authenication methods: %s", curl_easy_strerror(cRet)); + + + /* Work loop */ + for(unsigned int attempt=0;attempt 0) { + single_offsets.push_back(dSize); + } + /* Signal the request completed condition variable */ + tRet = pthread_cond_signal(&request_complete_cond); + if(tRet != 0) { + Error("Failed signaling request completed condition variable: %s",strerror(tRet)); + } + /* Unlock */ + unlock(); + + } else if (mode == MODE_STREAM) { + break; + } + } + + /* Return value checking */ + if(cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) { + break; + } else if (cRet != CURLE_OK) { + Error("cURL Request failed: %s",curl_easy_strerror(cRet)); + if(attempt<4) { + Error("Retrying.. Attempt %d of %d: %s",attempt+1,CURL_MAXRETRY); + } + tRet = -50; + } + } + + /* Cleanup */ + curl_easy_cleanup(c); + c = NULL; + + return (void*)tRet; +} + +int cURLCamera::lock() { + int nRet; + + /* Lock shared data */ + nRet = pthread_mutex_lock(&shareddata_mutex); + if(nRet != 0) { + Error("Failed locking shared data mutex: %s",strerror(nRet)); + } + return nRet; +} + +int cURLCamera::unlock() { + int nRet; + + /* Unlock shared data */ + nRet = pthread_mutex_unlock(&shareddata_mutex); + if(nRet != 0) { + Error("Failed unlocking shared data mutex: %s",strerror(nRet)); + } + return nRet; +} + +int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) +{ + /* Signal the curl thread to terminate */ + if(bTerminate) + return -10; + return 0; } -size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) { - return 0; +/* These functions call the functions in the class for the correct object */ +size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) +{ + return ((cURLCamera*)userdata)->data_callback(buffer,size,nmemb,userdata); } +size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) +{ + return ((cURLCamera*)userdata)->header_callback(buffer,size,nmemb,userdata); +} + +int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) +{ + return ((cURLCamera*)userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow); +} + +void* thread_func_dispatcher(void* object) { + return ((cURLCamera*)object)->thread_func(); +} + + + #endif // HAVE_LIBCURL diff --git a/src/zm_curl_camera.h b/src/zm_curl_camera.h index 5ddb13b35..3e99dc081 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -25,11 +25,18 @@ #include "zm_buffer.h" #include "zm_regexp.h" #include "zm_utils.h" +#include "zm_signal.h" +#include +#include #if HAVE_CURL_CURL_H #include #endif +#define MODE_UNSET 0 +#define MODE_SINGLE 1 +#define MODE_STREAM 2 + // // Class representing 'remote' cameras, i.e. those which are // accessed over a network connection. @@ -45,6 +52,18 @@ protected: CURL* c; #endif + /* Shared data */ + bool bTerminate; + int mode; + Buffer databuffer; + std::deque single_offsets; + + /* pthread objects */ + pthread_t thread; + pthread_mutex_t shareddata_mutex; + pthread_cond_t data_available_cond; + pthread_cond_t request_complete_cond; + public: cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); ~cURLCamera(); @@ -63,11 +82,22 @@ public: size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata); + int progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow); int debug_callback(CURL* handle, curl_infotype type, char* str, size_t strsize, void* data); + void* thread_func(); + int lock(); + int unlock(); private: - CURLcode ret; + int nRet; + CURLcode cRet; }; +/* Dispatchers */ +size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata); +size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata); +int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow); +void* thread_func_dispatcher(void* object); + #endif // ZM_CURL_CAMERA_H diff --git a/web/skins/classic/views/controlcap.php b/web/skins/classic/views/controlcap.php index f516b42ec..eab3e15ad 100644 --- a/web/skins/classic/views/controlcap.php +++ b/web/skins/classic/views/controlcap.php @@ -345,7 +345,7 @@ switch ( $tab ) ?> $SLANG['Local'], 'Remote'=>$SLANG['Remote'], 'Ffmpeg'=>$SLANG['Ffmpeg'], 'Libvlc'=>$SLANG['Libvlc'] ); + $types = array( 'Local'=>$SLANG['Local'], 'Remote'=>$SLANG['Remote'], 'Ffmpeg'=>$SLANG['Ffmpeg'], 'Libvlc'=>$SLANG['Libvlc'], 'cURL'=>"cURL"); ?> diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 29d9fcc5c..2d12871bb 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -506,10 +506,12 @@ if ( $tab != 'source' || ($newMonitor['Type'] != 'Local' && $newMonitor['Type'] + +