feat: remove the Libvlc capture method from the daemons and build

Ffmpeg reaches the same network cameras libvlc did, so carrying a second
streaming library -- with its own threading, its own bugs and its own
build dependency -- buys nothing. The preceding migration moves existing
monitors to Ffmpeg first, so Monitor::Load cannot meet a type it no
longer recognises, which is Fatal and would stop zmc rather than skip
the monitor.

Drops LibvlcCamera, the LIBVLC camera type and its parse and
construction arms, LIBVLC_SRC and Camera::IsLibvlc, the ZM_NO_LIBVLC
option and the libvlc probe, and the HAVE_LIBVLC and HAVE_VLC_VLC_H
defines. Since the camera resolved libvlc through dlopen rather than
linking it, this removes a headers-only build dependency and a runtime
one, and nothing else needed relinking.

kOptionSeparators stays -- the Ffmpeg path still parses the Options
field with it -- but it is no longer a contract shared between two
readers, so its comment and the test that pins its behaviour are
reworded rather than removed.

Built clean from a fresh cmake configure, which no longer lists libVLC
among optional libraries; unit suite passes (147 cases, 12531
assertions); no libvlc strings remain in zmc.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Y6FieTwEXuLhhR4e2yiax
This commit is contained in:
Isaac ConnorandClaude Opus 5 committed 2026-10-10 09:32:17 -04:00
1 parent 7db7430914
commit b0645ae512
10 files changed
+7 -502

No files matched your search

-22
View File
@@ -260,8 +260,6 @@ set(ZM_MYSQL_ENGINE "InnoDB" CACHE STRING
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_LIBVLC "OFF" CACHE BOOL
"Set to ON to skip libvlc checks and force building ZM without libvlc. default: OFF")
set(ZM_NO_LIBVNC "OFF" CACHE BOOL
"Set to ON to skip libvnc checks and force building ZM without libvnc. default: OFF")
set(ZM_NO_CURL "OFF" CACHE BOOL
@@ -630,26 +628,6 @@ if(FFMPEG_EXECUTABLE)
mark_as_advanced(FFMPEG_EXECUTABLE)
endif()
# Do not check for libvlc if ZM_NO_LIBVLC is on
if(NOT ZM_NO_LIBVLC)
# libvlc (using find_library and find_path)
find_library(LIBVLC_LIBRARIES vlc)
if(LIBVLC_LIBRARIES)
set(HAVE_LIBVLC 1)
#list(APPEND ZM_BIN_LIBS "${LIBVLC_LIBRARIES}")
find_path(LIBVLC_INCLUDE_DIR "vlc/vlc.h")
if(LIBVLC_INCLUDE_DIR)
list(APPEND ZM_INCLUDE_DIRS "${LIBVLC_INCLUDE_DIR}")
set(CMAKE_REQUIRED_INCLUDES "${LIBVLC_INCLUDE_DIR}")
endif()
mark_as_advanced(FORCE LIBVLC_LIBRARIES LIBVLC_INCLUDE_DIR)
check_include_file("vlc/vlc.h" HAVE_VLC_VLC_H)
set(optlibsfound "${optlibsfound} libVLC")
else()
set(optlibsnotfound "${optlibsnotfound} libVLC")
endif()
endif()
if(NOT ZM_NO_LIBVNC)
# libvncclient (using find_library and find_path)
find_library(LIBVNC_LIBRARIES vncclient)
-1
View File
@@ -33,7 +33,6 @@ set(ZM_BIN_SRC_FILES
zm_group.cpp
zm_image.cpp
zm_jpeg.cpp
zm_libvlc_camera.cpp
zm_libvnc_camera.cpp
zm_local_camera.cpp
zm_monitor.cpp
+1 -2
View File
@@ -36,7 +36,7 @@ class ZMPacket;
//
class Camera {
protected:
typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC, VNC_SRC } SourceType;
typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, CURL_SRC, VNC_SRC } SourceType;
const Monitor *monitor;
SourceType type;
@@ -95,7 +95,6 @@ class Camera {
bool IsRemote() const { return type == REMOTE_SRC; }
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; }
bool IsVNC() const { return type == VNC_SRC; }
unsigned int Width() const { return width; }
-346
View File
@@ -1,346 +0,0 @@
/*
* 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
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "zm_libvlc_camera.h"
#include "zm_packet.h"
#include "zm_signal.h"
#include <dlfcn.h>
#if HAVE_LIBVLC
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;
}
*(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");
}
// Do all the buffer checking work here to avoid unnecessary locking
void* LibvlcLockBuffer(void* opaque, void** planes) {
LibvlcPrivateData* data = reinterpret_cast<LibvlcPrivateData*>(opaque);
data->mutex.lock();
uint8_t* buffer = data->buffer;
data->buffer = data->prevBuffer;
data->prevBuffer = buffer;
*planes = data->buffer;
return nullptr;
}
void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) {
LibvlcPrivateData* data = reinterpret_cast<LibvlcPrivateData*>(opaque);
bool newFrame = false;
for( size_t i=0; i < data->bufferSize; i++ ) {
if ( data->buffer[i] != data->prevBuffer[i] ) {
newFrame = true;
break;
}
}
data->mutex.unlock();
time_t now;
time(&now);
// Return frames slightly faster than 1fps (if time() supports greater than one second resolution)
if ( newFrame || difftime(now, data->prevTime) >= 0.8 ) {
data->prevTime = now;
{
std::lock_guard<std::mutex> lck(data->newImageMutex);
data->newImage = true;
}
data->newImageCv.notify_all();
}
}
LibvlcCamera::LibvlcCamera(
const Monitor *monitor,
const std::string &p_path,
const std::string &p_user,
const std::string &p_pass,
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(
monitor,
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),
mUser(UriEncode(p_user)),
mPass(UriEncode(p_pass)),
mMethod(p_method),
mOptions(p_options) {
mLibvlcInstance = nullptr;
mLibvlcMedia = nullptr;
mLibvlcMediaPlayer = nullptr;
mLibvlcData.buffer = nullptr;
mLibvlcData.prevBuffer = nullptr;
mOptArgV = nullptr;
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
if ( zm_is_rgb32(pixelFormat) ) {
subpixelorder = ZM_SUBPIX_ORDER_BGRA;
pixelFormat = zm_pixformat_from_colours(colours, subpixelorder);
mTargetChroma = "RV32";
mBpp = 4;
} else if ( zm_is_rgb24(pixelFormat) ) {
subpixelorder = ZM_SUBPIX_ORDER_BGR;
pixelFormat = zm_pixformat_from_colours(colours, subpixelorder);
mTargetChroma = "RV24";
mBpp = 3;
} else if ( pixelFormat == AV_PIX_FMT_GRAY8 ) {
subpixelorder = ZM_SUBPIX_ORDER_NONE;
pixelFormat = zm_pixformat_from_colours(colours, subpixelorder);
mTargetChroma = "GREY";
mBpp = 1;
} else {
mBpp = 0;
Panic("Unexpected colours: %d",colours);
}
if ( capture ) {
Initialise();
}
}
LibvlcCamera::~LibvlcCamera() {
if ( capture ) {
Terminate();
}
mLibvlcData.newImageCv.notify_all(); // to unblock on termination (zm_terminate)
if ( mLibvlcMediaPlayer != nullptr ) {
(*libvlc_media_player_release_f)(mLibvlcMediaPlayer);
mLibvlcMediaPlayer = nullptr;
}
if ( mLibvlcMedia != nullptr ) {
(*libvlc_media_release_f)(mLibvlcMedia);
mLibvlcMedia = nullptr;
}
if ( mLibvlcInstance != nullptr ) {
(*libvlc_release_f)(mLibvlcInstance);
mLibvlcInstance = nullptr;
}
if (libvlc_lib) {
dlclose(libvlc_lib);
libvlc_lib = nullptr;
}
if ( mOptArgV != nullptr ) {
delete[] mOptArgV;
}
}
void LibvlcCamera::Initialise() {
bind_libvlc_symbols();
}
void LibvlcCamera::Terminate() {
if (libvlc_media_player_stop_f && mLibvlcMediaPlayer)
(*libvlc_media_player_stop_f)(mLibvlcMediaPlayer);
if ( mLibvlcData.buffer ) {
zm_freealigned(mLibvlcData.buffer);
mLibvlcData.buffer = nullptr;
}
if ( mLibvlcData.prevBuffer ) {
zm_freealigned(mLibvlcData.prevBuffer);
mLibvlcData.prevBuffer = nullptr;
}
}
int LibvlcCamera::PrimeCapture() {
Debug(1, "Priming capture from %s, libvlc version %s", mPath.c_str(), (*libvlc_get_version_f)());
opVect = Split(Options(), kOptionSeparators);
Debug(1, "Method: '%s'", Method().c_str());
// 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");
opVect.push_back("--no-audio");
if ( opVect.size() > 0 ) {
mOptArgV = new char*[opVect.size()];
Debug(2, "Number of Options: %zu", 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 %zu to '%s'", i, opVect[i].c_str());
}
}
mLibvlcInstance = (*libvlc_new_f)(opVect.size(), (const char* const*)mOptArgV);
if ( mLibvlcInstance == nullptr ) {
Error("Unable to create libvlc instance due to: %s", (*libvlc_errmsg_f)());
return -1;
}
(*libvlc_log_set_f)(mLibvlcInstance, LibvlcCamera::log_callback, nullptr);
// recreate the path with encoded authentication info
if( mUser.length() > 0 ) {
std::string mMaskedPath = remove_authentication(mPath);
std::string protocol = StringToUpper(mPath.substr(0, 4));
if ( protocol == "RTSP" ) {
// build the actual uri string with encoded parameters (from the user and pass fields)
mPath = StringToLower(protocol) + "://" + mUser + ":" + mPass + "@" + mMaskedPath.substr(7, std::string::npos);
Debug(1, "Rebuilt URI with encoded parameters: '%s'", mPath.c_str());
}
}
mLibvlcMedia = (*libvlc_media_new_location_f)(mLibvlcInstance, mPath.c_str());
if ( mLibvlcMedia == nullptr ) {
Error("Unable to open input %s due to: %s", mPath.c_str(), (*libvlc_errmsg_f)());
return -1;
}
mLibvlcMediaPlayer = (*libvlc_media_player_new_from_media_f)(mLibvlcMedia);
if ( mLibvlcMediaPlayer == nullptr ) {
Error("Unable to create player for %s due to: %s", mPath.c_str(), (*libvlc_errmsg_f)());
return -1;
}
(*libvlc_video_set_format_f)(mLibvlcMediaPlayer, mTargetChroma.c_str(), width, height, width * mBpp);
(*libvlc_video_set_callbacks_f)(mLibvlcMediaPlayer, &LibvlcLockBuffer, &LibvlcUnlockBuffer, nullptr, &mLibvlcData);
mLibvlcData.bufferSize = static_cast<size_t>(width) * height * mBpp;
// Libvlc wants 32 byte alignment for images (should in theory do this for all image lines)
mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize);
mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize);
mLibvlcData.newImage = false;
(*libvlc_media_player_play_f)(mLibvlcMediaPlayer);
return 0;
}
int LibvlcCamera::PreCapture() {
return 0;
}
// Should not return -1 as cancels capture. Always wait for image if available.
int LibvlcCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
// newImage is a mutex/condition based flag to tell us when there is an image available
{
std::unique_lock<std::mutex> lck(mLibvlcData.newImageMutex);
mLibvlcData.newImageCv.wait(lck, [&] { return mLibvlcData.newImage || zm_terminate; });
mLibvlcData.newImage = false;
}
if (zm_terminate)
return 0;
mLibvlcData.mutex.lock();
zm_packet->image->Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, mLibvlcData.bufferSize);
zm_packet->packet->stream_index = mVideoStreamId;
zm_packet->stream = mVideoStream;
mLibvlcData.mutex.unlock();
return 1;
}
int LibvlcCamera::PostCapture() {
return 0;
}
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, "%s", logString);
}
}
#endif // HAVE_LIBVLC
-85
View File
@@ -1,85 +0,0 @@
/*
* ZoneMinder Libvlc Camera Class Interface, $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
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef ZM_LIBVLC_CAMERA_H
#define ZM_LIBVLC_CAMERA_H
#include "zm_camera.h"
#include "zm_utils.h"
#include <condition_variable>
#include <mutex>
#if HAVE_LIBVLC
#if HAVE_VLC_VLC_H
#include "vlc/vlc.h"
#endif
// Used by libvlc callbacks
struct LibvlcPrivateData {
uint8_t* buffer;
uint8_t* prevBuffer;
time_t prevTime;
size_t bufferSize;
std::mutex mutex;
bool newImage;
std::mutex newImageMutex;
std::condition_variable newImageCv;
};
class LibvlcCamera : public Camera {
private:
static void log_callback( void *ptr, int level, const libvlc_log_t *ctx, const char *format, va_list vargs );
protected:
std::string mPath;
std::string mUser;
std::string mPass;
std::string mMethod;
std::string mOptions;
StringVector opVect; // mOptArgV will point into opVect so it needs to hang around
char **mOptArgV;
LibvlcPrivateData mLibvlcData;
std::string mTargetChroma;
uint8_t mBpp;
libvlc_instance_t *mLibvlcInstance;
libvlc_media_t *mLibvlcMedia;
libvlc_media_player_t *mLibvlcMediaPlayer;
public:
LibvlcCamera( const Monitor *monitor, const std::string &path, const std::string &user,const std::string &pass, 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 );
~LibvlcCamera();
const std::string &Path() const { return mPath; }
const std::string &Options() const { return mOptions; }
const std::string &Method() const { return mMethod; }
void Initialise();
void Terminate();
int PrimeCapture() override;
int PreCapture() override;
int Capture(std::shared_ptr<ZMPacket> &p) override;
int PostCapture() override;
int Close() override { return 0; };
};
#endif // HAVE_LIBVLC
#endif // ZM_LIBVLC_CAMERA_H
-37
View File
@@ -39,10 +39,6 @@
#include "zm_local_camera.h"
#endif // ZM_HAS_V4L2
#if HAVE_LIBVLC
#include "zm_libvlc_camera.h"
#endif // HAVE_LIBVLC
#if HAVE_LIBVNC
#include "zm_libvnc_camera.h"
#endif // HAVE_LIBVNC
@@ -126,7 +122,6 @@ std::string CameraType_Strings[] = {
"Remote",
"File",
"Ffmpeg",
"LibVLC",
"LibCURL",
"VNC"
};
@@ -392,8 +387,6 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
type = REMOTE;
} else if (!strcmp(dbrow[col], "File")) {
type = FILE;
} else if (!strcmp(dbrow[col], "Libvlc")) {
type = LIBVLC;
} else if (!strcmp(dbrow[col], "VNC")) {
type = VNC;
} else {
@@ -877,36 +870,6 @@ void Monitor::LoadCamera() {
);
break;
}
case LIBVLC: {
// libvlc reads the same network streams ffmpeg does, through a second
// library with its own build dependency, its own threading and its own
// bugs. Ffmpeg is the maintained path and every url that works here works
// there, so this capture method is on its way out.
Warning("Monitor %u (%s): the Libvlc capture method is deprecated and will be removed. "
"Change this monitor to Type 'Ffmpeg', keeping the same Source Path",
id, name.c_str());
#if HAVE_LIBVLC
camera = zm::make_unique<LibvlcCamera>(this,
path.c_str(),
user,
pass,
method,
options,
camera_width,
camera_height,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio
);
#else // HAVE_LIBVLC
Error("You must have vlc libraries installed to use vlc cameras for monitor %d", id);
#endif // HAVE_LIBVLC
break;
}
case VNC: {
#if HAVE_LIBVNC
camera = zm::make_unique<VncCamera>(this,
-1
View File
@@ -130,7 +130,6 @@ class Monitor : public std::enable_shared_from_this<Monitor> {
REMOTE,
FILE,
FFMPEG,
LIBVLC,
LIBCURL,
VNC,
} CameraType;
+2 -3
View File
@@ -99,9 +99,8 @@ std::string ClientAddress(const std::string &remote_addr,
// Separator characters between entries in a monitor's Options field. The ui
// offers a textarea, so an option may be on its own line as well as after a
// comma. Both consumers - av_dict_parse_string() for Ffmpeg and Split() for
// Libvlc - take a set of separator characters and skip empty entries, so a
// blank line, crlf or a trailing newline all just work.
// comma. av_dict_parse_string() takes a set of separator characters and skips
// empty entries, so a blank line, crlf or a trailing newline all just work.
constexpr const char kOptionSeparators[] = ",\r\n";
inline bool StartsWith(const std::string &haystack, const std::string &needle) {
+4 -3
View File
@@ -129,9 +129,10 @@ TEST_CASE("Split (string delimiter)") {
items = Split("a b c", " ", 2);
REQUIRE(items == std::vector<std::string>{"a", "b c"});
// LibvlcCamera splits the monitor Options field this way. The field is a
// textarea, so an option may be on its own line rather than after a comma,
// and runs of separators (a blank line, or crlf) must not become entries.
// A monitor's Options field is a textarea, so an entry may be on its own
// line rather than after a comma, and runs of separators (a blank line, or
// crlf) must not become empty entries. Split() is no longer the reader of
// that field, but this is the contract kOptionSeparators is chosen for.
items = Split("--rate=1\n--no-audio", kOptionSeparators);
REQUIRE(items == std::vector<std::string>{"--rate=1", "--no-audio"});
-2
View File
@@ -38,8 +38,6 @@
#cmakedefine HAVE_MYSQL_OPT_SSL_MODE 1
#cmakedefine HAVE_MYSQL_OPT_SSL_VERIFY_SERVER_CERT 1
#cmakedefine HAVE_LIBAVUTIL_HWCONTEXT_H 1
#cmakedefine HAVE_LIBVLC 1
#cmakedefine HAVE_VLC_VLC_H 1
#cmakedefine HAVE_LIBVNC 1
#cmakedefine HAVE_RFB_RFB_H 1
#cmakedefine HAVE_LIBJWT 1