2013-12-13 01:45:29 +08:00
|
|
|
/*
|
|
|
|
* ZoneMinder Libvlc Camera Class Implementation, $Date$, $Revision$
|
|
|
|
* 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-12-13 01:45:29 +08:00
|
|
|
*/
|
|
|
|
|
2020-04-14 01:11:57 +08:00
|
|
|
#include <dlfcn.h>
|
2013-12-13 01:45:29 +08:00
|
|
|
#include "zm.h"
|
2018-05-07 22:27:26 +08:00
|
|
|
#include "zm_signal.h"
|
2013-12-13 01:45:29 +08:00
|
|
|
#include "zm_libvlc_camera.h"
|
|
|
|
|
|
|
|
#if HAVE_LIBVLC
|
2020-04-14 01:11:57 +08:00
|
|
|
static void *libvlc_lib = nullptr;
|
|
|
|
static void (*libvlc_media_player_release_f)(libvlc_media_player_t* ) = nullptr;
|
|
|
|
static void (*libvlc_media_release_f)(libvlc_media_t* ) = nullptr;
|
|
|
|
static void (*libvlc_release_f)(libvlc_instance_t* ) = nullptr;
|
|
|
|
static void (*libvlc_media_player_stop_f)(libvlc_media_player_t* ) = nullptr;
|
|
|
|
static libvlc_instance_t* (*libvlc_new_f)(int, const char* const *) = nullptr;
|
|
|
|
static void (*libvlc_log_set_f)(libvlc_instance_t*, libvlc_log_cb, void *) = nullptr;
|
|
|
|
static libvlc_media_t* (*libvlc_media_new_location_f)(libvlc_instance_t*, const char*) = nullptr;
|
|
|
|
static libvlc_media_player_t* (*libvlc_media_player_new_from_media_f)(libvlc_media_t*) = nullptr;
|
|
|
|
static void (*libvlc_video_set_format_f)(libvlc_media_player_t*, const char*, unsigned, unsigned, unsigned) = nullptr;
|
|
|
|
static void (*libvlc_video_set_callbacks_f)(libvlc_media_player_t*, libvlc_video_lock_cb, libvlc_video_unlock_cb, libvlc_video_display_cb, void*) = nullptr;
|
|
|
|
static int (*libvlc_media_player_play_f)(libvlc_media_player_t *) = nullptr;
|
|
|
|
static const char* (*libvlc_errmsg_f)(void) = nullptr;
|
|
|
|
static const char* (*libvlc_get_version_f)(void) = nullptr;
|
|
|
|
|
|
|
|
void bind_libvlc_symbols() {
|
|
|
|
if(libvlc_lib != nullptr) // Safe-check
|
|
|
|
return;
|
|
|
|
|
|
|
|
libvlc_lib = dlopen("libvlc.so", RTLD_LAZY | RTLD_GLOBAL);
|
|
|
|
if(!libvlc_lib){
|
|
|
|
Error("Error loading libvlc: %s", dlerror());
|
|
|
|
return;
|
|
|
|
}
|
2013-12-13 01:45:29 +08:00
|
|
|
|
2020-04-14 01:11:57 +08:00
|
|
|
*(void**) (&libvlc_media_player_release_f) = dlsym(libvlc_lib, "libvlc_media_player_release");
|
|
|
|
*(void**) (&libvlc_media_release_f) = dlsym(libvlc_lib, "libvlc_media_release");
|
|
|
|
*(void**) (&libvlc_release_f) = dlsym(libvlc_lib, "libvlc_release");
|
|
|
|
*(void**) (&libvlc_media_player_stop_f) = dlsym(libvlc_lib, "libvlc_media_player_stop");
|
|
|
|
*(void**) (&libvlc_new_f) = dlsym(libvlc_lib, "libvlc_new");
|
|
|
|
*(void**) (&libvlc_log_set_f) = dlsym(libvlc_lib, "libvlc_log_set");
|
|
|
|
*(void**) (&libvlc_media_new_location_f) = dlsym(libvlc_lib, "libvlc_media_new_location");
|
|
|
|
*(void**) (&libvlc_media_player_new_from_media_f) = dlsym(libvlc_lib, "libvlc_media_player_new_from_media");
|
|
|
|
*(void**) (&libvlc_video_set_format_f) = dlsym(libvlc_lib, "libvlc_video_set_format");
|
|
|
|
*(void**) (&libvlc_video_set_callbacks_f) = dlsym(libvlc_lib, "libvlc_video_set_callbacks");
|
|
|
|
*(void**) (&libvlc_media_player_play_f) = dlsym(libvlc_lib, "libvlc_media_player_play");
|
|
|
|
*(void**) (&libvlc_errmsg_f) = dlsym(libvlc_lib, "libvlc_errmsg");
|
|
|
|
*(void**) (&libvlc_get_version_f) = dlsym(libvlc_lib, "libvlc_get_version");
|
|
|
|
}
|
2013-12-20 07:02:21 +08:00
|
|
|
// Do all the buffer checking work here to avoid unnecessary locking
|
2017-11-04 01:49:42 +08:00
|
|
|
void* LibvlcLockBuffer(void* opaque, void** planes) {
|
2017-11-19 05:00:10 +08:00
|
|
|
LibvlcPrivateData* data = reinterpret_cast<LibvlcPrivateData*>(opaque);
|
2016-06-22 00:21:18 +08:00
|
|
|
data->mutex.lock();
|
|
|
|
|
|
|
|
uint8_t* buffer = data->buffer;
|
|
|
|
data->buffer = data->prevBuffer;
|
|
|
|
data->prevBuffer = buffer;
|
|
|
|
|
|
|
|
*planes = data->buffer;
|
2020-08-26 07:45:48 +08:00
|
|
|
return nullptr;
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) {
|
2017-11-19 05:00:10 +08:00
|
|
|
LibvlcPrivateData* data = reinterpret_cast<LibvlcPrivateData*>(opaque);
|
2016-06-22 00:21:18 +08:00
|
|
|
|
|
|
|
bool newFrame = false;
|
2018-05-07 22:27:26 +08:00
|
|
|
for( unsigned int i=0; i < data->bufferSize; i++ ) {
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( data->buffer[i] != data->prevBuffer[i] ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
newFrame = true;
|
|
|
|
break;
|
2013-12-20 07:02:21 +08:00
|
|
|
}
|
2016-06-22 00:21:18 +08:00
|
|
|
}
|
|
|
|
data->mutex.unlock();
|
|
|
|
|
|
|
|
time_t now;
|
|
|
|
time(&now);
|
|
|
|
// Return frames slightly faster than 1fps (if time() supports greater than one second resolution)
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( newFrame || difftime(now, data->prevTime) >= 0.8 ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
data->prevTime = now;
|
|
|
|
data->newImage.updateValueSignal(true);
|
|
|
|
}
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
2018-05-07 22:27:26 +08:00
|
|
|
LibvlcCamera::LibvlcCamera(
|
|
|
|
int p_id,
|
|
|
|
const std::string &p_path,
|
|
|
|
const std::string &p_method,
|
|
|
|
const std::string &p_options,
|
|
|
|
int p_width,
|
|
|
|
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
|
|
|
|
) :
|
|
|
|
Camera(
|
|
|
|
p_id,
|
|
|
|
LIBVLC_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
|
|
|
|
),
|
|
|
|
mPath(p_path),
|
|
|
|
mMethod(p_method),
|
|
|
|
mOptions(p_options)
|
2016-06-22 00:21:18 +08:00
|
|
|
{
|
2020-08-26 07:45:48 +08:00
|
|
|
mLibvlcInstance = nullptr;
|
|
|
|
mLibvlcMedia = nullptr;
|
|
|
|
mLibvlcMediaPlayer = nullptr;
|
|
|
|
mLibvlcData.buffer = nullptr;
|
|
|
|
mLibvlcData.prevBuffer = nullptr;
|
|
|
|
mOptArgV = nullptr;
|
2016-06-22 00:21:18 +08:00
|
|
|
|
|
|
|
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
2018-05-07 22:27:26 +08:00
|
|
|
if ( colours == ZM_COLOUR_RGB32 ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
subpixelorder = ZM_SUBPIX_ORDER_BGRA;
|
|
|
|
mTargetChroma = "RV32";
|
|
|
|
mBpp = 4;
|
2018-05-07 22:27:26 +08:00
|
|
|
} else if ( colours == ZM_COLOUR_RGB24 ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
subpixelorder = ZM_SUBPIX_ORDER_BGR;
|
|
|
|
mTargetChroma = "RV24";
|
|
|
|
mBpp = 3;
|
2018-05-07 22:27:26 +08:00
|
|
|
} else if ( colours == ZM_COLOUR_GRAY8 ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
subpixelorder = ZM_SUBPIX_ORDER_NONE;
|
|
|
|
mTargetChroma = "GREY";
|
|
|
|
mBpp = 1;
|
|
|
|
} else {
|
2017-12-13 01:37:36 +08:00
|
|
|
mBpp = 0;
|
2016-06-22 00:21:18 +08:00
|
|
|
Panic("Unexpected colours: %d",colours);
|
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( capture ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
Initialise();
|
|
|
|
}
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
LibvlcCamera::~LibvlcCamera() {
|
|
|
|
if ( capture ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
Terminate();
|
|
|
|
}
|
2020-08-26 07:45:48 +08:00
|
|
|
if ( mLibvlcMediaPlayer != nullptr ) {
|
2020-04-14 01:11:57 +08:00
|
|
|
(*libvlc_media_player_release_f)(mLibvlcMediaPlayer);
|
2020-08-26 07:45:48 +08:00
|
|
|
mLibvlcMediaPlayer = nullptr;
|
2016-06-22 00:21:18 +08:00
|
|
|
}
|
2020-08-26 07:45:48 +08:00
|
|
|
if ( mLibvlcMedia != nullptr ) {
|
2020-04-14 01:11:57 +08:00
|
|
|
(*libvlc_media_release_f)(mLibvlcMedia);
|
2020-08-26 07:45:48 +08:00
|
|
|
mLibvlcMedia = nullptr;
|
2016-06-22 00:21:18 +08:00
|
|
|
}
|
2020-08-26 07:45:48 +08:00
|
|
|
if ( mLibvlcInstance != nullptr ) {
|
2020-04-14 01:11:57 +08:00
|
|
|
(*libvlc_release_f)(mLibvlcInstance);
|
2020-08-26 07:45:48 +08:00
|
|
|
mLibvlcInstance = nullptr;
|
2016-06-22 00:21:18 +08:00
|
|
|
}
|
2020-08-26 07:45:48 +08:00
|
|
|
if ( mOptArgV != nullptr ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
delete[] mOptArgV;
|
|
|
|
}
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
void LibvlcCamera::Initialise() {
|
2020-04-14 01:11:57 +08:00
|
|
|
bind_libvlc_symbols();
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
void LibvlcCamera::Terminate() {
|
2020-04-14 01:11:57 +08:00
|
|
|
(*libvlc_media_player_stop_f)(mLibvlcMediaPlayer);
|
2018-05-07 22:27:26 +08:00
|
|
|
if ( mLibvlcData.buffer ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
zm_freealigned(mLibvlcData.buffer);
|
2020-08-26 07:45:48 +08:00
|
|
|
mLibvlcData.buffer = nullptr;
|
2016-06-22 00:21:18 +08:00
|
|
|
}
|
2020-04-14 01:11:57 +08:00
|
|
|
|
2018-05-07 22:27:26 +08:00
|
|
|
if ( mLibvlcData.prevBuffer ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
zm_freealigned(mLibvlcData.prevBuffer);
|
2020-08-26 07:45:48 +08:00
|
|
|
mLibvlcData.prevBuffer = nullptr;
|
2016-06-22 00:21:18 +08:00
|
|
|
}
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
int LibvlcCamera::PrimeCapture() {
|
2020-07-14 03:03:04 +08:00
|
|
|
Debug(1, "Priming capture from %s, libvlc version %s", mPath.c_str(), (*libvlc_get_version_f)());
|
2016-06-22 00:21:18 +08:00
|
|
|
|
|
|
|
StringVector opVect = split(Options(), ",");
|
|
|
|
|
|
|
|
// Set transport method as specified by method field, rtpUni is default
|
|
|
|
if ( Method() == "rtpMulti" )
|
|
|
|
opVect.push_back("--rtsp-mcast");
|
|
|
|
else if ( Method() == "rtpRtsp" )
|
|
|
|
opVect.push_back("--rtsp-tcp");
|
|
|
|
else if ( Method() == "rtpRtspHttp" )
|
|
|
|
opVect.push_back("--rtsp-http");
|
|
|
|
|
2018-05-07 22:27:26 +08:00
|
|
|
opVect.push_back("--no-audio");
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
if ( opVect.size() > 0 ) {
|
2016-06-22 00:21:18 +08:00
|
|
|
mOptArgV = new char*[opVect.size()];
|
|
|
|
Debug(2, "Number of Options: %d",opVect.size());
|
|
|
|
for (size_t i=0; i< opVect.size(); i++) {
|
|
|
|
opVect[i] = trimSpaces(opVect[i]);
|
|
|
|
mOptArgV[i] = (char *)opVect[i].c_str();
|
|
|
|
Debug(2, "set option %d to '%s'", i, opVect[i].c_str());
|
2014-05-05 19:29:12 +08:00
|
|
|
}
|
2016-06-22 00:21:18 +08:00
|
|
|
}
|
|
|
|
|
2020-04-14 01:11:57 +08:00
|
|
|
mLibvlcInstance = (*libvlc_new_f)(opVect.size(), (const char* const*)mOptArgV);
|
2020-08-26 07:45:48 +08:00
|
|
|
if ( mLibvlcInstance == nullptr ) {
|
2020-04-14 01:11:57 +08:00
|
|
|
Error("Unable to create libvlc instance due to: %s", (*libvlc_errmsg_f)());
|
2018-05-07 22:27:26 +08:00
|
|
|
return -1;
|
|
|
|
}
|
2020-08-26 07:45:48 +08:00
|
|
|
(*libvlc_log_set_f)(mLibvlcInstance, LibvlcCamera::log_callback, nullptr);
|
2019-01-09 02:06:19 +08:00
|
|
|
|
2016-06-22 00:21:18 +08:00
|
|
|
|
2020-04-14 01:11:57 +08:00
|
|
|
mLibvlcMedia = (*libvlc_media_new_location_f)(mLibvlcInstance, mPath.c_str());
|
2020-08-26 07:45:48 +08:00
|
|
|
if ( mLibvlcMedia == nullptr ) {
|
2020-04-14 01:11:57 +08:00
|
|
|
Error("Unable to open input %s due to: %s", mPath.c_str(), (*libvlc_errmsg_f)());
|
2018-05-07 22:27:26 +08:00
|
|
|
return -1;
|
|
|
|
}
|
2014-05-05 19:29:12 +08:00
|
|
|
|
2020-04-14 01:11:57 +08:00
|
|
|
mLibvlcMediaPlayer = (*libvlc_media_player_new_from_media_f)(mLibvlcMedia);
|
2020-08-26 07:45:48 +08:00
|
|
|
if ( mLibvlcMediaPlayer == nullptr ) {
|
2020-04-14 01:11:57 +08:00
|
|
|
Error("Unable to create player for %s due to: %s", mPath.c_str(), (*libvlc_errmsg_f)());
|
2018-05-07 22:27:26 +08:00
|
|
|
return -1;
|
|
|
|
}
|
2016-06-22 00:21:18 +08:00
|
|
|
|
2020-04-14 01:11:57 +08:00
|
|
|
(*libvlc_video_set_format_f)(mLibvlcMediaPlayer, mTargetChroma.c_str(), width, height, width * mBpp);
|
2020-08-26 07:45:48 +08:00
|
|
|
(*libvlc_video_set_callbacks_f)(mLibvlcMediaPlayer, &LibvlcLockBuffer, &LibvlcUnlockBuffer, nullptr, &mLibvlcData);
|
2016-06-22 00:21:18 +08:00
|
|
|
|
|
|
|
mLibvlcData.bufferSize = width * height * mBpp;
|
|
|
|
// Libvlc wants 32 byte alignment for images (should in theory do this for all image lines)
|
2017-04-16 15:57:37 +08:00
|
|
|
mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize);
|
|
|
|
mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize);
|
2016-04-04 22:11:48 +08:00
|
|
|
|
2016-06-22 00:21:18 +08:00
|
|
|
mLibvlcData.newImage.setValueImmediate(false);
|
|
|
|
|
2020-04-14 01:11:57 +08:00
|
|
|
(*libvlc_media_player_play_f)(mLibvlcMediaPlayer);
|
2016-06-22 00:21:18 +08:00
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
return 0;
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
2019-01-09 02:06:19 +08:00
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
int LibvlcCamera::PreCapture() {
|
2018-05-07 22:27:26 +08:00
|
|
|
return 0;
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Should not return -1 as cancels capture. Always wait for image if available.
|
2018-05-07 22:27:26 +08:00
|
|
|
int LibvlcCamera::Capture(Image &image) {
|
2018-05-07 23:08:06 +08:00
|
|
|
|
|
|
|
// newImage is a mutex/condition based flag to tell us when there is an image available
|
2018-05-07 22:27:26 +08:00
|
|
|
while( !mLibvlcData.newImage.getValueImmediate() ) {
|
|
|
|
if (zm_terminate)
|
|
|
|
return 0;
|
2016-06-22 00:21:18 +08:00
|
|
|
mLibvlcData.newImage.getUpdatedValue(1);
|
2018-05-07 22:27:26 +08:00
|
|
|
}
|
2016-06-22 00:21:18 +08:00
|
|
|
|
|
|
|
mLibvlcData.mutex.lock();
|
|
|
|
image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp);
|
|
|
|
mLibvlcData.newImage.setValueImmediate(false);
|
|
|
|
mLibvlcData.mutex.unlock();
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
return 1;
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) {
|
2018-05-07 22:27:26 +08:00
|
|
|
return 0;
|
2014-10-23 16:56:35 +08:00
|
|
|
}
|
|
|
|
|
2017-11-04 01:49:42 +08:00
|
|
|
int LibvlcCamera::PostCapture() {
|
2018-05-07 22:27:26 +08:00
|
|
|
return 0;
|
2013-12-13 01:45:29 +08:00
|
|
|
}
|
|
|
|
|
2019-01-09 02:06:19 +08:00
|
|
|
void LibvlcCamera::log_callback(void *ptr, int level, const libvlc_log_t *ctx, const char *fmt, va_list vargs) {
|
|
|
|
Logger *log = Logger::fetch();
|
|
|
|
int log_level = Logger::NOLOG;
|
|
|
|
if ( level == LIBVLC_ERROR ) {
|
|
|
|
log_level = Logger::WARNING; // ffmpeg outputs a lot of errors that don't really affect anything.
|
|
|
|
//log_level = Logger::ERROR;
|
|
|
|
} else if ( level == LIBVLC_WARNING ) {
|
|
|
|
log_level = Logger::INFO;
|
|
|
|
//log_level = Logger::WARNING;
|
|
|
|
} else if ( level == LIBVLC_NOTICE ) {
|
|
|
|
log_level = Logger::DEBUG1;
|
|
|
|
//log_level = Logger::INFO;
|
|
|
|
} else if ( level == LIBVLC_DEBUG ) {
|
|
|
|
log_level = Logger::DEBUG3;
|
|
|
|
} else {
|
|
|
|
Error("Unknown log level %d", level);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( log ) {
|
|
|
|
char logString[8192];
|
|
|
|
vsnprintf(logString, sizeof(logString)-1, fmt, vargs);
|
|
|
|
log->logPrint(false, __FILE__, __LINE__, log_level, logString);
|
|
|
|
}
|
|
|
|
}
|
2013-12-13 01:45:29 +08:00
|
|
|
#endif // HAVE_LIBVLC
|