2013-11-04 22:52:21 +08:00
|
|
|
//
|
|
|
|
// ZoneMinder cURL Camera Class Implementation, $Date: 2009-01-16 12:18:50 +0000 (Fri, 16 Jan 2009) $, $Revision: 2713 $
|
|
|
|
// Copyright (C) 2001-2008 Philip Coombes
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License
|
|
|
|
// as published by the Free Software Foundation; either version 2
|
|
|
|
// of the License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
2016-12-26 23:23:16 +08:00
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2013-11-04 22:52:21 +08:00
|
|
|
//
|
|
|
|
|
|
|
|
#include "zm.h"
|
2017-05-17 00:04:56 +08:00
|
|
|
|
2013-12-28 00:02:32 +08:00
|
|
|
#include "zm_curl_camera.h"
|
2013-11-04 22:52:21 +08:00
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
#include "zm_packetqueue.h"
|
|
|
|
|
2013-11-04 22:52:21 +08:00
|
|
|
#if HAVE_LIBCURL
|
|
|
|
|
2013-12-28 00:02:32 +08:00
|
|
|
#define CURL_MAXRETRY 5
|
2013-12-31 21:50:06 +08:00
|
|
|
#define CURL_BUFFER_INITIAL_SIZE 65536
|
|
|
|
|
2013-12-28 00:02:32 +08:00
|
|
|
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;
|
2013-11-04 22:52:21 +08:00
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
|
Feature h264 videostorage (#1882)
* Moved writing of configure options from Controller to Model. Fixes #191.
* Initial commit for saving events as videos :)
* Add zm_video.cpp to autotools
* Add zm_video.h to autotools
* Search for MP4V2 header file 3 times: mp4v2/mp4v2.h, mp4v2.h, mp4.h
* Fix serve memory leak
* Few minor code improvements
* Added the ability to override preset, tune, profile and few other improvements
* Correctly write SPS & PPS from x264 encoder headers
* Remove unnessecary SPS & PPS writing code
* Imported missing files from master to feature-h264-videostorage
* Audio support including fixes for dts/pts, split on keyframe and update to mkv extension to prevent ffmpeg problems writing rtsp audio to mp4 containter (header problem)
* Updates to make gcc happy
* Add html5 video control to timeline and event to support mkv playback
* Add zm_videostore.cpp to CMakeLists.txt
* Remove Modern Branch for now
* Fix minor bug
* Option handled added in master, removing duplicate declaration
* Add CaptureandRecord from zm_camera.h
* Putting placeholder in for CaptureAndRecord function
* Removed duplicate code and brackets
* add digest auth file for cmake
Conflicts:
src/CMakeLists.txt
* Add web dir back into Makefile.am
Revert "Removed web from SUBDIRS in Makefile.am"
This reverts commit d9bbcdf3a94cba4d8955fcd03bd965ed2772f34d.
* Add CaptureAndRecord to vlc, still need to make it record
* Resolve SegFault on videostore
* Swap to mp4 container
* mp4 changes
* spaces to tabs, hide video stuff if video writer is turned off
* Make timeline open event.mp4 instead of mkv
* Missed mkv in timeline.js
* Fix some issues from the merge conflict
* Resolve post merge build issues with braces
* Fix whitespace
* Update Jpeg and Video options for passthrough options
* Whitespace fix zm_camera.h
* Fix array mkssing comma
* Add support for Jpeg save options for h264 branch snapshot. Might remove altogether if snapshots not needed
* Update VideoStoreData memory size comment
* Change from config.use_mkv_storage to per monitor option VideoWriter from video branch
* Fix bracket issues post merge
* Clean up comments and add av_free_packet
* Convert from event_directory to event file as per Video branch
* Testing videojs for video playback
* Fixed a missing bracket post merge and also SQL_values now used for EventID and Monitors
* bring recent improvements in ffmpeg capture function into captureandrecord
* Remove pict from writeAudioFramePacket as not used
* Add translate options for h264 Storage options in Monitor and update en_gb file
* Cherry-Pick from iconnor - make it compile on ubuntu 15.04. Which is libav 56.1.0
Conflicts:
src/zm_ffmpeg.cpp
src/zm_remote_camera_rtsp.cpp
Conflicts:
distros/ubuntu1204/changelog
* Clean up videostore code and remove lots of unused code
* proof of concept for dynamic/automatic video rotation using video-js plugin zoomrotate
Conflicts:
web/skins/classic/views/event.php
* removed redundant field in sql query
Conflicts:
web/skins/classic/views/event.php
* local storage of video js plugin
* Beautify!
Make the code somewhat readable.
* added missing videojs.zoomrotate.js file
added missing videojs.zoomrotate.js file
* Typo
added missing "
* Added missing brackets
* fix to display thumbnails when only storing snapshot.jpg
* added control for video playback rate
Conflicts:
web/skins/classic/views/event.php
* dynamically create jpegs from video file for viewing in browser
* fix timeline view for SaveJPEGs monitors (without enabled VideoWriter)
* only expose monitor info which are being used in client
* fix segmentation fault in zma with ubuntu 14.04 and ffmpeg 2.5.8 (gcc 4.8)
when libx264 is not installed
* better way of detecting showing image or video in timeline and event view
instead of Monitor.VideoWriter, Event.DefaultVideo is used, so even if
VideoWriter/SaveJPEG option is changed, a valid image or video will always be
displayed for historical events in both timeline and event view
this also fixes loading videos in timeline view
* Fixes problem of crashing zmc when bad packet arrives causing av_interleaved_write_frame() to return non-zero (-22). Prefilters common packet issues. Add metadata title to generated video file
* Remove syslog.h
* fixed SaveJPEGs are not working
which is caused in errors introduced when merging with master
* Update README.md
* Fix build warnings specific to h264 branch, unused FrameImg, unused ret and int64_t snprintf issues
* Fix PRId64 issue in travis, builds locally fine, but I can see a gcc version issue here
* Fix PRId64 issue in travis, another try
* Try "STDC_FORMAT_MACROS" to see if that helps Travis on gcc 4.6.3
* Revert space removal around PRId64
* video branch ffmpeg 2.9 fixes
ffmpeg 2.9 patched removed SSE2 CPU
* Add FFMPEGInit back
* use webvvt to overlay timestamp (honoring Monitor.LabelFormat) to videos in timeline and event
also fixed bug which prevented seeking in timeline video preview
* ffmpeg 3.0 API build failure fixes
* Update README.md
* merge all the commits from the messed up iconnor_video branch
* fix whitespace
* revert
* whitespace fixes
* spelling fix
* put back some text
* add these back
* fix spelling mistake
* Steal some packet dumping routines from ffmpeg. Convert them to use our logging routines
* add a test and error message if the codec is not h264
* these have been removed in master
* add a view to check auth and just send the video
* add some comments, and dump filename and AVFormatContext on failure to write header
* add the toggle for RecordAudio so that the checkbox works to turn off Audio
* Must init videoStore in constuctor
* more debug and comments, return checking
* Fix dropped part of sql query.
* fix extra else and some whitespace
* Fix missing } from merge that was preventing building.
* fix tabs
* get rid of use of separator, just use \n
* Restore lost fixes for deprecation
* Why are these failing
* Respect record_audio flag when setting up video file so dont try and initiliase mp4 with unsupported audio
* Forgot that I was trying to solve case of stream is true and record_audio
is false.
* Pass swscale_ctx back in to getCachedContext or it will create new
context every frame and leak memory like a mofo.
* Add libx264-dev and libmp4v2-dev to build requires to save hassle of
ensuring they are installed before build.
* Merge my Rotation/Orientation work and fixes for bad h264 streams
* need arpa/inet for reverse lookups
* pull in the new byte range code for viewing videos
* Move our recording flag deeper into closeevent
* add braces and only call closeEvent if there is an event
* deprecate the z_frame_rate stuff which is deprecated in ffmpeg
* remark out some debugging
* fix for video on stream 1
* fix audio_stream to audio_st
* Ignore bad decodes
* fix problems with content-length causing viewing to not work in chrome/android
* change logic of sending file contents to handle an off by one and be more readable
* Some fixes pointed out by Maxim Romanov. Also simply the loading of events to not join the Monitors table
* fix to sql for timeline
* added RecordAudio to sql in README
* Use sub queries instead of joins to fix errors when using new mysql defaults.
* fix sql queries
* Dockerfile to build feature-h264-videostorage
* Must cast codec
* add php-acpu as a dependency
* require php5-acpu
* fix typo
* remove extra /
* Add a line for out-of-tree builds to do api/lib/Cake/bootstrap.php
* delete merge conflict files
* delete merge conflict files
2017-05-16 10:02:48 +08:00
|
|
|
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, p_record_audio ),
|
2016-04-04 22:11:48 +08:00
|
|
|
mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET )
|
2013-11-04 22:52:21 +08:00
|
|
|
{
|
2013-11-04 23:10:07 +08:00
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
if ( capture ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
Initialise();
|
|
|
|
}
|
2013-11-04 22:52:21 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
cURLCamera::~cURLCamera() {
|
|
|
|
if ( capture ) {
|
2013-12-28 00:02:32 +08:00
|
|
|
|
2016-04-04 22:11:48 +08:00
|
|
|
Terminate();
|
|
|
|
}
|
2013-11-04 22:52:21 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
void cURLCamera::Initialise() {
|
2016-04-04 22:11:48 +08:00
|
|
|
content_length_match_len = strlen(content_length_match);
|
|
|
|
content_type_match_len = strlen(content_type_match);
|
|
|
|
|
|
|
|
databuffer.expand(CURL_BUFFER_INITIAL_SIZE);
|
|
|
|
|
|
|
|
/* cURL initialization */
|
2017-12-13 01:35:54 +08:00
|
|
|
CURLcode cRet = curl_global_init(CURL_GLOBAL_ALL);
|
2016-04-04 22:11:48 +08:00
|
|
|
if(cRet != CURLE_OK) {
|
|
|
|
Fatal("libcurl initialization failed: ", curl_easy_strerror(cRet));
|
|
|
|
}
|
|
|
|
|
|
|
|
Debug(2,"libcurl version: %s",curl_version());
|
|
|
|
|
|
|
|
/* Create the shared data mutex */
|
2017-12-13 01:35:54 +08:00
|
|
|
int nRet = pthread_mutex_init(&shareddata_mutex, NULL);
|
2016-04-04 22:11:48 +08:00
|
|
|
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));
|
|
|
|
}
|
2013-11-04 22:52:21 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
void cURLCamera::Terminate() {
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Signal the thread to terminate */
|
|
|
|
bTerminate = true;
|
2013-12-28 00:02:32 +08:00
|
|
|
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Wait for thread termination */
|
|
|
|
pthread_join(thread, NULL);
|
2013-12-28 00:02:32 +08:00
|
|
|
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Destroy condition variables */
|
|
|
|
pthread_cond_destroy(&request_complete_cond);
|
|
|
|
pthread_cond_destroy(&data_available_cond);
|
2013-12-28 00:02:32 +08:00
|
|
|
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Destroy mutex */
|
|
|
|
pthread_mutex_destroy(&shareddata_mutex);
|
2013-12-28 00:02:32 +08:00
|
|
|
|
2016-04-04 22:11:48 +08:00
|
|
|
/* cURL cleanup */
|
|
|
|
curl_global_cleanup();
|
2013-11-04 22:52:21 +08:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
int cURLCamera::PrimeCapture() {
|
2016-04-04 22:11:48 +08:00
|
|
|
//Info( "Priming capture from %s", mPath.c_str() );
|
|
|
|
return 0;
|
2013-11-04 22:52:21 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
int cURLCamera::PreCapture() {
|
|
|
|
// Nothing to do here
|
|
|
|
return( 0 );
|
2013-11-04 22:52:21 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
int cURLCamera::Capture( Image &image ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
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;
|
2017-12-13 01:35:54 +08:00
|
|
|
int nRet;
|
2016-04-04 22:11:48 +08:00
|
|
|
|
|
|
|
/* Grab the mutex to ensure exclusive access to the shared data */
|
|
|
|
lock();
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
while ( !frameComplete ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
|
|
|
|
/* If the work thread did a reset, reset our local variables */
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( bReset ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
SubHeadersParsingComplete = false;
|
|
|
|
frame_content_length = 0;
|
|
|
|
frame_content_type.clear();
|
|
|
|
need_more_data = false;
|
|
|
|
bReset = false;
|
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( mode == MODE_UNSET ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Don't have a mode yet. Sleep while waiting for data */
|
|
|
|
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( nRet != 0 ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
Error("Failed waiting for available data condition variable: %s",strerror(nRet));
|
|
|
|
return -20;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( mode == MODE_STREAM ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
|
|
|
|
/* Subheader parsing */
|
2017-11-04 01:49:42 +08:00
|
|
|
while( !SubHeadersParsingComplete && !need_more_data ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
|
|
|
|
size_t crlf_start, crlf_end, crlf_size;
|
|
|
|
std::string subheader;
|
|
|
|
|
|
|
|
/* Check if the buffer contains something */
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( databuffer.empty() ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Empty buffer, wait for data */
|
|
|
|
need_more_data = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find crlf start */
|
|
|
|
crlf_start = memcspn(databuffer,"\r\n",databuffer.size());
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( crlf_start == databuffer.size() ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Not found, wait for more data */
|
|
|
|
need_more_data = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* See if we have enough data for determining crlf length */
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( databuffer.size() < crlf_start+5 ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
/* 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) */
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( crlf_start == 0 ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
databuffer.consume(crlf_size);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check for invalid CRLF size */
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( crlf_size > 4 ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
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(' ');
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( subheader_data_start == std::string::npos ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
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(strncasecmp(subheader.c_str(),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(strncasecmp(subheader.c_str(),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;
|
2017-11-19 05:00:10 +08:00
|
|
|
} else if ( ! frame_content_length ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Invalid frame */
|
|
|
|
Error("Invalid frame: invalid content length");
|
2017-11-19 05:00:10 +08:00
|
|
|
} else if ( frame_content_type != "image/jpeg" ) {
|
2016-04-04 22:11:48 +08:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Failed to match content-type */
|
|
|
|
Fatal("Unable to match Content-Type. Check URL, username and password");
|
|
|
|
} /* mode */
|
|
|
|
|
|
|
|
} /* frameComplete loop */
|
|
|
|
|
|
|
|
/* Release the mutex */
|
|
|
|
unlock();
|
|
|
|
|
|
|
|
if(!frameComplete)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
2013-11-04 22:52:21 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
int cURLCamera::PostCapture() {
|
Feature h264 videostorage (#1882)
* Moved writing of configure options from Controller to Model. Fixes #191.
* Initial commit for saving events as videos :)
* Add zm_video.cpp to autotools
* Add zm_video.h to autotools
* Search for MP4V2 header file 3 times: mp4v2/mp4v2.h, mp4v2.h, mp4.h
* Fix serve memory leak
* Few minor code improvements
* Added the ability to override preset, tune, profile and few other improvements
* Correctly write SPS & PPS from x264 encoder headers
* Remove unnessecary SPS & PPS writing code
* Imported missing files from master to feature-h264-videostorage
* Audio support including fixes for dts/pts, split on keyframe and update to mkv extension to prevent ffmpeg problems writing rtsp audio to mp4 containter (header problem)
* Updates to make gcc happy
* Add html5 video control to timeline and event to support mkv playback
* Add zm_videostore.cpp to CMakeLists.txt
* Remove Modern Branch for now
* Fix minor bug
* Option handled added in master, removing duplicate declaration
* Add CaptureandRecord from zm_camera.h
* Putting placeholder in for CaptureAndRecord function
* Removed duplicate code and brackets
* add digest auth file for cmake
Conflicts:
src/CMakeLists.txt
* Add web dir back into Makefile.am
Revert "Removed web from SUBDIRS in Makefile.am"
This reverts commit d9bbcdf3a94cba4d8955fcd03bd965ed2772f34d.
* Add CaptureAndRecord to vlc, still need to make it record
* Resolve SegFault on videostore
* Swap to mp4 container
* mp4 changes
* spaces to tabs, hide video stuff if video writer is turned off
* Make timeline open event.mp4 instead of mkv
* Missed mkv in timeline.js
* Fix some issues from the merge conflict
* Resolve post merge build issues with braces
* Fix whitespace
* Update Jpeg and Video options for passthrough options
* Whitespace fix zm_camera.h
* Fix array mkssing comma
* Add support for Jpeg save options for h264 branch snapshot. Might remove altogether if snapshots not needed
* Update VideoStoreData memory size comment
* Change from config.use_mkv_storage to per monitor option VideoWriter from video branch
* Fix bracket issues post merge
* Clean up comments and add av_free_packet
* Convert from event_directory to event file as per Video branch
* Testing videojs for video playback
* Fixed a missing bracket post merge and also SQL_values now used for EventID and Monitors
* bring recent improvements in ffmpeg capture function into captureandrecord
* Remove pict from writeAudioFramePacket as not used
* Add translate options for h264 Storage options in Monitor and update en_gb file
* Cherry-Pick from iconnor - make it compile on ubuntu 15.04. Which is libav 56.1.0
Conflicts:
src/zm_ffmpeg.cpp
src/zm_remote_camera_rtsp.cpp
Conflicts:
distros/ubuntu1204/changelog
* Clean up videostore code and remove lots of unused code
* proof of concept for dynamic/automatic video rotation using video-js plugin zoomrotate
Conflicts:
web/skins/classic/views/event.php
* removed redundant field in sql query
Conflicts:
web/skins/classic/views/event.php
* local storage of video js plugin
* Beautify!
Make the code somewhat readable.
* added missing videojs.zoomrotate.js file
added missing videojs.zoomrotate.js file
* Typo
added missing "
* Added missing brackets
* fix to display thumbnails when only storing snapshot.jpg
* added control for video playback rate
Conflicts:
web/skins/classic/views/event.php
* dynamically create jpegs from video file for viewing in browser
* fix timeline view for SaveJPEGs monitors (without enabled VideoWriter)
* only expose monitor info which are being used in client
* fix segmentation fault in zma with ubuntu 14.04 and ffmpeg 2.5.8 (gcc 4.8)
when libx264 is not installed
* better way of detecting showing image or video in timeline and event view
instead of Monitor.VideoWriter, Event.DefaultVideo is used, so even if
VideoWriter/SaveJPEG option is changed, a valid image or video will always be
displayed for historical events in both timeline and event view
this also fixes loading videos in timeline view
* Fixes problem of crashing zmc when bad packet arrives causing av_interleaved_write_frame() to return non-zero (-22). Prefilters common packet issues. Add metadata title to generated video file
* Remove syslog.h
* fixed SaveJPEGs are not working
which is caused in errors introduced when merging with master
* Update README.md
* Fix build warnings specific to h264 branch, unused FrameImg, unused ret and int64_t snprintf issues
* Fix PRId64 issue in travis, builds locally fine, but I can see a gcc version issue here
* Fix PRId64 issue in travis, another try
* Try "STDC_FORMAT_MACROS" to see if that helps Travis on gcc 4.6.3
* Revert space removal around PRId64
* video branch ffmpeg 2.9 fixes
ffmpeg 2.9 patched removed SSE2 CPU
* Add FFMPEGInit back
* use webvvt to overlay timestamp (honoring Monitor.LabelFormat) to videos in timeline and event
also fixed bug which prevented seeking in timeline video preview
* ffmpeg 3.0 API build failure fixes
* Update README.md
* merge all the commits from the messed up iconnor_video branch
* fix whitespace
* revert
* whitespace fixes
* spelling fix
* put back some text
* add these back
* fix spelling mistake
* Steal some packet dumping routines from ffmpeg. Convert them to use our logging routines
* add a test and error message if the codec is not h264
* these have been removed in master
* add a view to check auth and just send the video
* add some comments, and dump filename and AVFormatContext on failure to write header
* add the toggle for RecordAudio so that the checkbox works to turn off Audio
* Must init videoStore in constuctor
* more debug and comments, return checking
* Fix dropped part of sql query.
* fix extra else and some whitespace
* Fix missing } from merge that was preventing building.
* fix tabs
* get rid of use of separator, just use \n
* Restore lost fixes for deprecation
* Why are these failing
* Respect record_audio flag when setting up video file so dont try and initiliase mp4 with unsupported audio
* Forgot that I was trying to solve case of stream is true and record_audio
is false.
* Pass swscale_ctx back in to getCachedContext or it will create new
context every frame and leak memory like a mofo.
* Add libx264-dev and libmp4v2-dev to build requires to save hassle of
ensuring they are installed before build.
* Merge my Rotation/Orientation work and fixes for bad h264 streams
* need arpa/inet for reverse lookups
* pull in the new byte range code for viewing videos
* Move our recording flag deeper into closeevent
* add braces and only call closeEvent if there is an event
* deprecate the z_frame_rate stuff which is deprecated in ffmpeg
* remark out some debugging
* fix for video on stream 1
* fix audio_stream to audio_st
* Ignore bad decodes
* fix problems with content-length causing viewing to not work in chrome/android
* change logic of sending file contents to handle an off by one and be more readable
* Some fixes pointed out by Maxim Romanov. Also simply the loading of events to not join the Monitors table
* fix to sql for timeline
* added RecordAudio to sql in README
* Use sub queries instead of joins to fix errors when using new mysql defaults.
* fix sql queries
* Dockerfile to build feature-h264-videostorage
* Must cast codec
* add php-acpu as a dependency
* require php5-acpu
* fix typo
* remove extra /
* Add a line for out-of-tree builds to do api/lib/Cake/bootstrap.php
* delete merge conflict files
* delete merge conflict files
2017-05-16 10:02:48 +08:00
|
|
|
// Nothing to do here
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
int cURLCamera::CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ) {
|
|
|
|
Error("Capture and Record not implemented for the cURL camera type");
|
|
|
|
// Nothing to do here
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) {
|
2016-04-04 22:11:48 +08:00
|
|
|
lock();
|
2013-12-28 00:02:32 +08:00
|
|
|
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Append the data we just received to our buffer */
|
|
|
|
databuffer.append((const char*)buffer, size*nmemb);
|
2013-12-28 00:02:32 +08:00
|
|
|
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Signal data available */
|
2017-12-13 01:35:54 +08:00
|
|
|
int nRet = pthread_cond_signal(&data_available_cond);
|
2016-04-04 22:11:48 +08:00
|
|
|
if(nRet != 0) {
|
|
|
|
Error("Failed signaling data available condition variable: %s",strerror(nRet));
|
|
|
|
return -16;
|
|
|
|
}
|
2013-12-28 00:02:32 +08:00
|
|
|
|
2016-04-04 22:11:48 +08:00
|
|
|
unlock();
|
2013-12-28 00:02:32 +08:00
|
|
|
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Return bytes processed */
|
|
|
|
return size*nmemb;
|
2013-12-28 00:02:32 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) {
|
2016-04-04 22:11:48 +08:00
|
|
|
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) {
|
|
|
|
header.erase(pos, std::string::npos);
|
|
|
|
}
|
|
|
|
|
|
|
|
pos = header.rfind(' ');
|
|
|
|
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(strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) {
|
|
|
|
Debug(7,"Content type matched as multipart/x-mixed-replace");
|
|
|
|
mode = MODE_STREAM;
|
|
|
|
} else if(strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) {
|
|
|
|
Debug(7,"Content type matched as image/jpeg");
|
|
|
|
mode = MODE_SINGLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return bytes processed */
|
|
|
|
return size*nmemb;
|
2013-12-28 00:02:32 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
void* cURLCamera::thread_func() {
|
2016-04-04 22:11:48 +08:00
|
|
|
long tRet;
|
|
|
|
double dSize;
|
|
|
|
|
|
|
|
c = curl_easy_init();
|
|
|
|
if(c == NULL) {
|
|
|
|
Fatal("Failed getting easy handle from libcurl");
|
|
|
|
}
|
|
|
|
|
2017-12-13 01:35:54 +08:00
|
|
|
CURLcode cRet;
|
2016-04-04 22:11:48 +08:00
|
|
|
/* 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_NOPROGRESS, 0);
|
|
|
|
if(cRet != CURLE_OK)
|
|
|
|
Fatal("Failed enabling libcurl progress callback function: %s", curl_easy_strerror(cRet));
|
|
|
|
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(int attempt=1;attempt<=CURL_MAXRETRY;attempt++) {
|
|
|
|
tRet = 0;
|
|
|
|
while(!bTerminate) {
|
|
|
|
/* Do the work */
|
|
|
|
cRet = curl_easy_perform(c);
|
|
|
|
|
|
|
|
if(mode == MODE_SINGLE) {
|
|
|
|
if(cRet != CURLE_OK) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* Attempt to get the size of the file */
|
|
|
|
cRet = curl_easy_getinfo(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize);
|
|
|
|
if(cRet != CURLE_OK) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* We need to lock for the offsets array and the condition variable */
|
|
|
|
lock();
|
|
|
|
/* Push the size into our offsets array */
|
|
|
|
if(dSize > 0) {
|
|
|
|
single_offsets.push_back(dSize);
|
|
|
|
} else {
|
|
|
|
Fatal("Unable to get the size of the image");
|
|
|
|
}
|
|
|
|
/* Signal the request complete 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) {
|
|
|
|
/* Aborted */
|
|
|
|
break;
|
|
|
|
} else if (cRet != CURLE_OK) {
|
|
|
|
/* Some error */
|
|
|
|
Error("cURL Request failed: %s",curl_easy_strerror(cRet));
|
|
|
|
if(attempt < CURL_MAXRETRY) {
|
|
|
|
Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY);
|
|
|
|
/* Do a reset */
|
|
|
|
lock();
|
|
|
|
databuffer.clear();
|
|
|
|
single_offsets.clear();
|
|
|
|
mode = MODE_UNSET;
|
|
|
|
bReset = true;
|
|
|
|
unlock();
|
|
|
|
}
|
|
|
|
tRet = -50;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Cleanup */
|
|
|
|
curl_easy_cleanup(c);
|
|
|
|
c = NULL;
|
|
|
|
|
|
|
|
return (void*)tRet;
|
2013-11-04 23:10:07 +08:00
|
|
|
}
|
|
|
|
|
2013-12-28 00:02:32 +08:00
|
|
|
int cURLCamera::lock() {
|
2016-04-04 22:11:48 +08:00
|
|
|
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;
|
2013-12-28 00:02:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int cURLCamera::unlock() {
|
2016-04-04 22:11:48 +08:00
|
|
|
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;
|
2013-12-28 00:02:32 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) {
|
2016-04-04 22:11:48 +08:00
|
|
|
/* Signal the curl thread to terminate */
|
|
|
|
if(bTerminate)
|
|
|
|
return -10;
|
|
|
|
|
|
|
|
return 0;
|
2013-11-04 23:10:07 +08:00
|
|
|
}
|
2013-11-04 22:52:21 +08:00
|
|
|
|
2013-12-28 00:02:32 +08:00
|
|
|
/* These functions call the functions in the class for the correct object */
|
2017-05-17 00:04:56 +08:00
|
|
|
size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) {
|
2017-11-17 20:52:26 +08:00
|
|
|
return reinterpret_cast<cURLCamera*>(userdata)->data_callback(buffer,size,nmemb,userdata);
|
2013-12-28 00:02:32 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) {
|
2017-11-17 20:52:26 +08:00
|
|
|
return reinterpret_cast<cURLCamera*>(userdata)->header_callback(buffer,size,nmemb,userdata);
|
2013-12-28 00:02:32 +08:00
|
|
|
}
|
|
|
|
|
2017-05-17 00:04:56 +08:00
|
|
|
int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) {
|
2017-11-17 20:52:26 +08:00
|
|
|
return reinterpret_cast<cURLCamera*>(userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow);
|
2013-12-28 00:02:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void* thread_func_dispatcher(void* object) {
|
2017-11-17 20:52:26 +08:00
|
|
|
return reinterpret_cast<cURLCamera*>(object)->thread_func();
|
2013-12-28 00:02:32 +08:00
|
|
|
}
|
|
|
|
|
2013-11-04 22:52:21 +08:00
|
|
|
#endif // HAVE_LIBCURL
|