vlc-video: Add VLC video source

Adds the ability to add video playlists via libvlc instead of via the
media source.  This is mostly just being added as a secondary option to
the media source to reduce maintenance costs and save time.  Currently
libff cannot pause/unpause/seek, and isn't programmed to handle
playlists yet.

If VLC is installed on the computer (with the same architecture) it will
allow video playback via libVLC.  In the future, users should be able to
optionally download VLC libraries via the installer as well if they
don't want to necessarily install VLC to get the plugin working.

This plugin performs runtime linking instead of compile-time linking;
compiling VLC is not required, only its headers are required.  To
compile, clone the VLC repository and set the VLCPath cmake variable to
point to the VLC repository directory.
This commit is contained in:
jp9000
2016-06-02 00:31:43 -07:00
parent fb1ff173f9
commit d89299fc1a
7 changed files with 1104 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ if(WIN32)
add_subdirectory(decklink/win)
add_subdirectory(win-mf)
add_subdirectory(obs-qsv11)
add_subdirectory(vlc-video)
elseif(APPLE)
add_subdirectory(coreaudio-encoder)
add_subdirectory(mac-avcapture)
@@ -17,6 +18,7 @@ elseif(APPLE)
add_subdirectory(mac-vth264)
add_subdirectory(mac-syphon)
add_subdirectory(decklink/mac)
add_subdirectory(vlc-video)
elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
add_subdirectory(linux-capture)
add_subdirectory(linux-pulseaudio)

View File

@@ -0,0 +1,41 @@
project(vlc-video)
if(DISABLE_VLC)
message(STATUS "VLC video plugin disabled")
return()
endif()
find_package(LibVLC QUIET)
if(NOT LIBVLC_INCLUDES_FOUND AND ENABLE_VLC)
message(FATAL_ERROR "LibVLC includes not found but set as enabled")
elseif(NOT LIBVLC_INCLUDES_FOUND)
message(STATUS "LibVLC includes not found, VLC video plugin disabled")
return()
endif()
include_directories(${LIBVLC_INCLUDE_DIRS})
add_definitions(${LIBVLC_DEFINITIONS})
if(MSVC)
set(vlc-video_PLATFORM_DEPS
w32-pthreads)
endif()
set(vlc-video_HEADERS
vlc-video-plugin.h
)
set(vlc-video_SOURCES
vlc-video-plugin.c
vlc-video-source.c
)
add_library(vlc-video MODULE
${vlc-video_SOURCES}
${vlc-video_HEADERS})
target_link_libraries(vlc-video
libobs
${vlc-video_PLATFORM_DEPS})
install_obs_plugin_with_data(vlc-video data)

View File

@@ -0,0 +1,7 @@
VLCSource="VLC Video Source"
Playlist="Playlist"
LoopPlaylist="Loop Playlist"
PlaybackBehavior="Visibility behavior"
PlaybackBehavior.StopRestart="Stop when not visible, restart when visible"
PlaybackBehavior.PauseUnpause="Pause when not visible, unpause when visible"
PlaybackBehavior.AlwaysPlay="Always play even when not visible"

View File

@@ -0,0 +1,201 @@
#ifdef _WIN32
#include <windows.h>
#endif
#include <util/platform.h>
#include "vlc-video-plugin.h"
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("vlc-video", "en-US")
/* libvlc core */
LIBVLC_NEW libvlc_new_;
LIBVLC_RELEASE libvlc_release_;
LIBVLC_CLOCK libvlc_clock_;
LIBVLC_EVENT_ATTACH libvlc_event_attach_;
/* libvlc media */
LIBVLC_MEDIA_NEW_PATH libvlc_media_new_path_;
LIBVLC_MEDIA_RELEASE libvlc_media_release_;
LIBVLC_MEDIA_RELEASE libvlc_media_retain_;
/* libvlc media player */
LIBVLC_MEDIA_PLAYER_NEW libvlc_media_player_new_;
LIBVLC_MEDIA_PLAYER_NEW_FROM_MEDIA libvlc_media_player_new_from_media_;
LIBVLC_MEDIA_PLAYER_RELEASE libvlc_media_player_release_;
LIBVLC_VIDEO_SET_CALLBACKS libvlc_video_set_callbacks_;
LIBVLC_VIDEO_SET_FORMAT_CALLBACKS libvlc_video_set_format_callbacks_;
LIBVLC_AUDIO_SET_CALLBACKS libvlc_audio_set_callbacks_;
LIBVLC_AUDIO_SET_FORMAT_CALLBACKS libvlc_audio_set_format_callbacks_;
LIBVLC_MEDIA_PLAYER_PLAY libvlc_media_player_play_;
LIBVLC_MEDIA_PLAYER_STOP libvlc_media_player_stop_;
LIBVLC_MEDIA_PLAYER_GET_TIME libvlc_media_player_get_time_;
LIBVLC_VIDEO_GET_SIZE libvlc_video_get_size_;
LIBVLC_MEDIA_PLAYER_EVENT_MANAGER libvlc_media_player_event_manager_;
/* libvlc media list */
LIBVLC_MEDIA_LIST_NEW libvlc_media_list_new_;
LIBVLC_MEDIA_LIST_RELEASE libvlc_media_list_release_;
LIBVLC_MEDIA_LIST_ADD_MEDIA libvlc_media_list_add_media_;
LIBVLC_MEDIA_LIST_LOCK libvlc_media_list_lock_;
LIBVLC_MEDIA_LIST_UNLOCK libvlc_media_list_unlock_;
LIBVLC_MEDIA_LIST_EVENT_MANAGER libvlc_media_list_event_manager_;
/* libvlc media list player */
LIBVLC_MEDIA_LIST_PLAYER_NEW libvlc_media_list_player_new_;
LIBVLC_MEDIA_LIST_PLAYER_RELEASE libvlc_media_list_player_release_;
LIBVLC_MEDIA_LIST_PLAYER_PLAY libvlc_media_list_player_play_;
LIBVLC_MEDIA_LIST_PLAYER_PAUSE libvlc_media_list_player_pause_;
LIBVLC_MEDIA_LIST_PLAYER_STOP libvlc_media_list_player_stop_;
LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_PLAYER libvlc_media_list_player_set_media_player_;
LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_LIST libvlc_media_list_player_set_media_list_;
LIBVLC_MEDIA_LIST_PLAYER_EVENT_MANAGER libvlc_media_list_player_event_manager_;
LIBVLC_MEDIA_LIST_PLAYER_SET_PLAYBACK_MODE libvlc_media_list_player_set_playback_mode_;
void *libvlc_module = NULL;
libvlc_instance_t *libvlc = NULL;
uint64_t time_start = 0;
static bool load_vlc_funcs(void)
{
#define LOAD_VLC_FUNC(func) \
do { \
func ## _ = os_dlsym(libvlc_module, #func); \
if (!func ## _) { \
blog(LOG_WARNING, "Could not func VLC function %s, " \
"VLC loading failed", #func); \
return false; \
} \
} while (false)
/* libvlc core */
LOAD_VLC_FUNC(libvlc_new);
LOAD_VLC_FUNC(libvlc_release);
LOAD_VLC_FUNC(libvlc_clock);
LOAD_VLC_FUNC(libvlc_event_attach);
/* libvlc media */
LOAD_VLC_FUNC(libvlc_media_new_path);
LOAD_VLC_FUNC(libvlc_media_release);
LOAD_VLC_FUNC(libvlc_media_retain);
/* libvlc media player */
LOAD_VLC_FUNC(libvlc_media_player_new);
LOAD_VLC_FUNC(libvlc_media_player_new_from_media);
LOAD_VLC_FUNC(libvlc_media_player_release);
LOAD_VLC_FUNC(libvlc_video_set_callbacks);
LOAD_VLC_FUNC(libvlc_video_set_format_callbacks);
LOAD_VLC_FUNC(libvlc_audio_set_callbacks);
LOAD_VLC_FUNC(libvlc_audio_set_format_callbacks);
LOAD_VLC_FUNC(libvlc_media_player_play);
LOAD_VLC_FUNC(libvlc_media_player_stop);
LOAD_VLC_FUNC(libvlc_media_player_get_time);
LOAD_VLC_FUNC(libvlc_video_get_size);
LOAD_VLC_FUNC(libvlc_media_player_event_manager);
/* libvlc media list */
LOAD_VLC_FUNC(libvlc_media_list_new);
LOAD_VLC_FUNC(libvlc_media_list_release);
LOAD_VLC_FUNC(libvlc_media_list_add_media);
LOAD_VLC_FUNC(libvlc_media_list_lock);
LOAD_VLC_FUNC(libvlc_media_list_unlock);
LOAD_VLC_FUNC(libvlc_media_list_event_manager);
/* libvlc media list player */
LOAD_VLC_FUNC(libvlc_media_list_player_new);
LOAD_VLC_FUNC(libvlc_media_list_player_release);
LOAD_VLC_FUNC(libvlc_media_list_player_play);
LOAD_VLC_FUNC(libvlc_media_list_player_pause);
LOAD_VLC_FUNC(libvlc_media_list_player_stop);
LOAD_VLC_FUNC(libvlc_media_list_player_set_media_player);
LOAD_VLC_FUNC(libvlc_media_list_player_set_media_list);
LOAD_VLC_FUNC(libvlc_media_list_player_event_manager);
LOAD_VLC_FUNC(libvlc_media_list_player_set_playback_mode);
return true;
}
static bool load_libvlc_module(void)
{
#ifdef _WIN32
char *path_utf8 = NULL;
wchar_t path[1024];
LSTATUS status;
DWORD size;
HKEY key;
memset(path, 0, 1024 * sizeof(wchar_t));
status = RegOpenKeyW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\VideoLAN\\VLC",
&key);
if (status != ERROR_SUCCESS)
return false;
size = 1024;
status = RegQueryValueExW(key, L"InstallDir", NULL, NULL,
(LPBYTE)path, &size);
if (status == ERROR_SUCCESS) {
wcscat(path, L"\\libvlc.dll");
os_wcs_to_utf8_ptr(path, 0, &path_utf8);
libvlc_module = os_dlopen(path_utf8);
bfree(path_utf8);
}
RegCloseKey(key);
#else
#ifdef __APPLE__
#define LIBVLC_DIR "/Applications/VLC.app/Contents/MacOS/"
#define LIBVLC_FILE LIBVLC_DIR "lib/libvlc.5.dylib"
setenv("VLC_PLUGIN_PATH", LIBVLC_DIR "plugins", false);
#else
#define LIBVLC_FILE "libvlc.5.so"
#endif
libvlc_module = os_dlopen(LIBVLC_FILE);
#endif
return libvlc_module != NULL;
}
extern struct obs_source_info vlc_source_info;
bool load_libvlc(void)
{
if (libvlc)
return true;
libvlc = libvlc_new_(0, 0);
if (!libvlc) {
blog(LOG_INFO, "Couldn't create libvlc instance");
return false;
}
time_start = (uint64_t)libvlc_clock_() * 1000ULL;
return true;
}
bool obs_module_load(void)
{
if (!load_libvlc_module()) {
blog(LOG_INFO, "Couldn't find VLC installation, VLC video "
"source disabled");
return true;
}
if (!load_vlc_funcs())
return true;
blog(LOG_INFO, "VLC found, VLC video source enabled");
obs_register_source(&vlc_source_info);
return true;
}
void obs_module_unload(void)
{
if (libvlc)
libvlc_release_(libvlc);
if (libvlc_module)
os_dlclose(libvlc_module);
}

View File

@@ -0,0 +1,156 @@
#include <obs-module.h>
#include <libvlc.h>
#ifdef _MSC_VER
#include <basetsd.h>
typedef SSIZE_T ssize_t;
#endif
#include <libvlc_media.h>
#include <libvlc_events.h>
#include <libvlc_media_list.h>
#include <libvlc_media_player.h>
#include <libvlc_media_list_player.h>
extern libvlc_instance_t *libvlc;
extern uint64_t time_start;
extern bool load_libvlc(void);
/* libvlc core */
typedef libvlc_instance_t *(*LIBVLC_NEW)(int argc, const char *const *argv);
typedef void (*LIBVLC_RELEASE)(libvlc_instance_t *p_instance);
typedef int64_t (*LIBVLC_CLOCK)(void);
typedef int (*LIBVLC_EVENT_ATTACH)(libvlc_event_manager_t *p_event_manager,
libvlc_event_type_t i_event_type,
libvlc_callback_t f_callback,
void *user_data);
/* libvlc media */
typedef libvlc_media_t *(*LIBVLC_MEDIA_NEW_PATH)(
libvlc_instance_t *p_instance, const char *path);
typedef void (*LIBVLC_MEDIA_RETAIN)(libvlc_media_t *p_md);
typedef void (*LIBVLC_MEDIA_RELEASE)(libvlc_media_t *p_md);
/* libvlc media player */
typedef libvlc_media_player_t *(*LIBVLC_MEDIA_PLAYER_NEW)(
libvlc_instance_t *p_libvlc);
typedef libvlc_media_player_t *(*LIBVLC_MEDIA_PLAYER_NEW_FROM_MEDIA)(
libvlc_media_t *p_md);
typedef void (*LIBVLC_MEDIA_PLAYER_RELEASE)(
libvlc_media_player_t *p_mi);
typedef void (*LIBVLC_VIDEO_SET_CALLBACKS)(
libvlc_media_player_t *mp,
libvlc_video_lock_cb lock,
libvlc_video_unlock_cb unlock,
libvlc_video_display_cb display,
void *opaque);
typedef void (*LIBVLC_VIDEO_SET_FORMAT_CALLBACKS)(
libvlc_media_player_t *mp,
libvlc_video_format_cb setup,
libvlc_video_cleanup_cb cleanup);
typedef void (*LIBVLC_AUDIO_SET_CALLBACKS)(
libvlc_media_player_t *mp,
libvlc_audio_play_cb play,
libvlc_audio_pause_cb pause,
libvlc_audio_resume_cb resume,
libvlc_audio_flush_cb flush,
libvlc_audio_drain_cb drain,
void *opaque);
typedef void (*LIBVLC_AUDIO_SET_FORMAT_CALLBACKS)(
libvlc_media_player_t *mp,
libvlc_audio_setup_cb setup,
libvlc_audio_cleanup_cb cleanup);
typedef int (*LIBVLC_MEDIA_PLAYER_PLAY)(
libvlc_media_player_t *p_mi);
typedef void (*LIBVLC_MEDIA_PLAYER_STOP)(
libvlc_media_player_t *p_mi);
typedef libvlc_time_t (*LIBVLC_MEDIA_PLAYER_GET_TIME)(
libvlc_media_player_t *p_mi);
typedef int (*LIBVLC_VIDEO_GET_SIZE)(
libvlc_media_player_t *p_mi,
unsigned num,
unsigned *px,
unsigned *py);
typedef libvlc_event_manager_t *(*LIBVLC_MEDIA_PLAYER_EVENT_MANAGER)(
libvlc_media_player_t *p_mp);
/* libvlc media list */
typedef libvlc_media_list_t *(*LIBVLC_MEDIA_LIST_NEW)(
libvlc_instance_t *p_instance);
typedef void (*LIBVLC_MEDIA_LIST_RELEASE)(libvlc_media_list_t *p_ml);
typedef int (*LIBVLC_MEDIA_LIST_ADD_MEDIA)(libvlc_media_list_t *p_ml,
libvlc_media_t *p_md);
typedef void (*LIBVLC_MEDIA_LIST_LOCK)(libvlc_media_list_t *p_ml);
typedef void (*LIBVLC_MEDIA_LIST_UNLOCK)(libvlc_media_list_t *p_ml);
typedef libvlc_event_manager_t *(*LIBVLC_MEDIA_LIST_EVENT_MANAGER)(
libvlc_media_list_t *p_ml);
/* libvlc media list player */
typedef libvlc_media_list_player_t *(*LIBVLC_MEDIA_LIST_PLAYER_NEW)(
libvlc_instance_t * p_instance);
typedef void (*LIBVLC_MEDIA_LIST_PLAYER_RELEASE)(
libvlc_media_list_player_t *p_mlp);
typedef void (*LIBVLC_MEDIA_LIST_PLAYER_PLAY)(
libvlc_media_list_player_t *p_mlp);
typedef void (*LIBVLC_MEDIA_LIST_PLAYER_PAUSE)(
libvlc_media_list_player_t *p_mlp);
typedef void (*LIBVLC_MEDIA_LIST_PLAYER_STOP)(
libvlc_media_list_player_t *p_mlp);
typedef void (*LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_PLAYER)(
libvlc_media_list_player_t *p_mlp,
libvlc_media_player_t *p_mp);
typedef void (*LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_LIST)(
libvlc_media_list_player_t *p_mlp,
libvlc_media_list_t *p_mlist);
typedef libvlc_event_manager_t *(*LIBVLC_MEDIA_LIST_PLAYER_EVENT_MANAGER)(
libvlc_media_list_player_t *p_mlp);
typedef void (*LIBVLC_MEDIA_LIST_PLAYER_SET_PLAYBACK_MODE)(
libvlc_media_list_player_t *p_mlp,
libvlc_playback_mode_t e_mode);
/* -------------------------------------------------------------------- */
/* libvlc core */
extern LIBVLC_NEW libvlc_new_;
extern LIBVLC_RELEASE libvlc_release_;
extern LIBVLC_CLOCK libvlc_clock_;
extern LIBVLC_EVENT_ATTACH libvlc_event_attach_;
/* libvlc media */
extern LIBVLC_MEDIA_NEW_PATH libvlc_media_new_path_;
extern LIBVLC_MEDIA_RELEASE libvlc_media_release_;
extern LIBVLC_MEDIA_RETAIN libvlc_media_retain_;
/* libvlc media player */
extern LIBVLC_MEDIA_PLAYER_NEW libvlc_media_player_new_;
extern LIBVLC_MEDIA_PLAYER_NEW_FROM_MEDIA libvlc_media_player_new_from_media_;
extern LIBVLC_MEDIA_PLAYER_RELEASE libvlc_media_player_release_;
extern LIBVLC_VIDEO_SET_CALLBACKS libvlc_video_set_callbacks_;
extern LIBVLC_VIDEO_SET_FORMAT_CALLBACKS libvlc_video_set_format_callbacks_;
extern LIBVLC_AUDIO_SET_CALLBACKS libvlc_audio_set_callbacks_;
extern LIBVLC_AUDIO_SET_FORMAT_CALLBACKS libvlc_audio_set_format_callbacks_;
extern LIBVLC_MEDIA_PLAYER_PLAY libvlc_media_player_play_;
extern LIBVLC_MEDIA_PLAYER_STOP libvlc_media_player_stop_;
extern LIBVLC_MEDIA_PLAYER_GET_TIME libvlc_media_player_get_time_;
extern LIBVLC_VIDEO_GET_SIZE libvlc_video_get_size_;
extern LIBVLC_MEDIA_PLAYER_EVENT_MANAGER libvlc_media_player_event_manager_;
/* libvlc media list */
extern LIBVLC_MEDIA_LIST_NEW libvlc_media_list_new_;
extern LIBVLC_MEDIA_LIST_RELEASE libvlc_media_list_release_;
extern LIBVLC_MEDIA_LIST_ADD_MEDIA libvlc_media_list_add_media_;
extern LIBVLC_MEDIA_LIST_LOCK libvlc_media_list_lock_;
extern LIBVLC_MEDIA_LIST_UNLOCK libvlc_media_list_unlock_;
extern LIBVLC_MEDIA_LIST_EVENT_MANAGER libvlc_media_list_event_manager_;
/* libvlc media list player */
extern LIBVLC_MEDIA_LIST_PLAYER_NEW libvlc_media_list_player_new_;
extern LIBVLC_MEDIA_LIST_PLAYER_RELEASE libvlc_media_list_player_release_;
extern LIBVLC_MEDIA_LIST_PLAYER_PLAY libvlc_media_list_player_play_;
extern LIBVLC_MEDIA_LIST_PLAYER_PAUSE libvlc_media_list_player_pause_;
extern LIBVLC_MEDIA_LIST_PLAYER_STOP libvlc_media_list_player_stop_;
extern LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_PLAYER libvlc_media_list_player_set_media_player_;
extern LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_LIST libvlc_media_list_player_set_media_list_;
extern LIBVLC_MEDIA_LIST_PLAYER_EVENT_MANAGER libvlc_media_list_player_event_manager_;
extern LIBVLC_MEDIA_LIST_PLAYER_SET_PLAYBACK_MODE libvlc_media_list_player_set_playback_mode_;

View File

@@ -0,0 +1,625 @@
#include "vlc-video-plugin.h"
#include <media-io/video-frame.h>
#include <util/threading.h>
#include <util/dstr.h>
#define do_log(level, format, ...) \
blog(level, "[vlc_source: '%s'] " format, \
obs_source_get_name(ss->source), ##__VA_ARGS__)
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
#define S_PLAYLIST "playlist"
#define S_LOOP "loop"
#define S_BEHAVIOR "playback_behavior"
#define S_BEHAVIOR_STOP_RESTART "stop_restart"
#define S_BEHAVIOR_PAUSE_UNPAUSE "pause_unpause"
#define S_BEHAVIOR_ALWAYS_PLAY "always_play"
#define T_(text) obs_module_text(text)
#define T_PLAYLIST T_("Playlist")
#define T_LOOP T_("LoopPlaylist")
#define T_BEHAVIOR T_("PlaybackBehavior")
#define T_BEHAVIOR_STOP_RESTART T_("PlaybackBehavior.StopRestart")
#define T_BEHAVIOR_PAUSE_UNPAUSE T_("PlaybackBehavior.PauseUnpause")
#define T_BEHAVIOR_ALWAYS_PLAY T_("PlaybackBehavior.AlwaysPlay")
/* ------------------------------------------------------------------------- */
struct media_file_data {
char *path;
libvlc_media_t *media;
};
enum behavior {
BEHAVIOR_STOP_RESTART,
BEHAVIOR_PAUSE_UNPAUSE,
BEHAVIOR_ALWAYS_PLAY,
};
struct vlc_source {
obs_source_t *source;
libvlc_media_player_t *media_player;
libvlc_media_list_player_t *media_list_player;
struct obs_source_frame frame;
struct obs_source_audio audio;
size_t audio_capacity;
pthread_mutex_t mutex;
DARRAY(struct media_file_data) files;
enum behavior behavior;
bool loop;
};
static libvlc_media_t *get_media(struct darray *array, const char *path)
{
DARRAY(struct media_file_data) files;
libvlc_media_t *media = NULL;
files.da = *array;
for (size_t i = 0; i < files.num; i++) {
const char *cur_path = files.array[i].path;
if (strcmp(path, cur_path) == 0) {
media = files.array[i].media;
libvlc_media_retain_(media);
break;
}
}
return media;
}
static inline libvlc_media_t *create_media_from_file(const char *file)
{
return libvlc_media_new_path_(libvlc, file);
}
static void free_files(struct darray *array)
{
DARRAY(struct media_file_data) files;
files.da = *array;
for (size_t i = 0; i < files.num; i++) {
bfree(files.array[i].path);
libvlc_media_release_(files.array[i].media);
}
da_free(files);
}
static inline bool chroma_is(const char *chroma, const char *val)
{
return *(uint32_t*)chroma == *(uint32_t*)val;
}
static enum video_format convert_vlc_video_format(char *chroma, bool *full)
{
*full = false;
#define CHROMA_TEST(val, ret) \
if (chroma_is(chroma, val)) return ret
#define CHROMA_CONV(val, new_val, ret) \
do { \
if (chroma_is(chroma, val)) { \
*(uint32_t*)chroma = *(uint32_t*)new_val; \
return ret; \
} \
} while (false)
#define CHROMA_CONV_FULL(val, new_val, ret) \
do { \
*full = true; \
CHROMA_CONV(val, new_val, ret); \
} while (false)
CHROMA_TEST("RGBA", VIDEO_FORMAT_RGBA);
CHROMA_TEST("BGRA", VIDEO_FORMAT_BGRA);
/* 4:2:0 formats */
CHROMA_TEST("NV12", VIDEO_FORMAT_NV12);
CHROMA_TEST("I420", VIDEO_FORMAT_I420);
CHROMA_TEST("IYUV", VIDEO_FORMAT_I420);
CHROMA_CONV("NV21", "NV12", VIDEO_FORMAT_NV12);
CHROMA_CONV("I422", "NV12", VIDEO_FORMAT_NV12);
CHROMA_CONV("Y42B", "NV12", VIDEO_FORMAT_NV12);
CHROMA_CONV("YV12", "NV12", VIDEO_FORMAT_NV12);
CHROMA_CONV("yv12", "NV12", VIDEO_FORMAT_NV12);
CHROMA_CONV_FULL("J420", "J420", VIDEO_FORMAT_I420);
/* 4:2:2 formats */
CHROMA_TEST("UYVY", VIDEO_FORMAT_UYVY);
CHROMA_TEST("UYNV", VIDEO_FORMAT_UYVY);
CHROMA_TEST("UYNY", VIDEO_FORMAT_UYVY);
CHROMA_TEST("Y422", VIDEO_FORMAT_UYVY);
CHROMA_TEST("HDYC", VIDEO_FORMAT_UYVY);
CHROMA_TEST("AVUI", VIDEO_FORMAT_UYVY);
CHROMA_TEST("uyv1", VIDEO_FORMAT_UYVY);
CHROMA_TEST("2vuy", VIDEO_FORMAT_UYVY);
CHROMA_TEST("2Vuy", VIDEO_FORMAT_UYVY);
CHROMA_TEST("2Vu1", VIDEO_FORMAT_UYVY);
CHROMA_TEST("YUY2", VIDEO_FORMAT_YUY2);
CHROMA_TEST("YUYV", VIDEO_FORMAT_YUY2);
CHROMA_TEST("YUNV", VIDEO_FORMAT_YUY2);
CHROMA_TEST("V422", VIDEO_FORMAT_YUY2);
CHROMA_TEST("YVYU", VIDEO_FORMAT_YVYU);
CHROMA_CONV("v210", "UYVY", VIDEO_FORMAT_UYVY);
CHROMA_CONV("cyuv", "UYVY", VIDEO_FORMAT_UYVY);
CHROMA_CONV("CYUV", "UYVY", VIDEO_FORMAT_UYVY);
CHROMA_CONV("VYUY", "UYVY", VIDEO_FORMAT_UYVY);
CHROMA_CONV("NV16", "UYVY", VIDEO_FORMAT_UYVY);
CHROMA_CONV("NV61", "UYVY", VIDEO_FORMAT_UYVY);
CHROMA_CONV("I410", "UYVY", VIDEO_FORMAT_UYVY);
CHROMA_CONV("I422", "UYVY", VIDEO_FORMAT_UYVY);
CHROMA_CONV("Y42B", "UYVY", VIDEO_FORMAT_UYVY);
CHROMA_CONV("J422", "UYVY", VIDEO_FORMAT_UYVY);
/* 4:4:4 formats */
CHROMA_TEST("I444", VIDEO_FORMAT_I444);
CHROMA_CONV_FULL("J444", "J444", VIDEO_FORMAT_I444);
CHROMA_CONV("YUVA", "RGBA", VIDEO_FORMAT_RGBA);
/* 4:4:0 formats */
CHROMA_CONV("I440", "I444", VIDEO_FORMAT_I444);
CHROMA_CONV("J440", "I444", VIDEO_FORMAT_I444);
/* 4:1:0 formats */
CHROMA_CONV("YVU9", "NV12", VIDEO_FORMAT_UYVY);
CHROMA_CONV("I410", "NV12", VIDEO_FORMAT_UYVY);
/* 4:1:1 formats */
CHROMA_CONV("I411", "NV12", VIDEO_FORMAT_UYVY);
CHROMA_CONV("Y41B", "NV12", VIDEO_FORMAT_UYVY);
/* greyscale formats */
CHROMA_TEST("GREY", VIDEO_FORMAT_Y800);
CHROMA_TEST("Y800", VIDEO_FORMAT_Y800);
CHROMA_TEST("Y8 ", VIDEO_FORMAT_Y800);
#undef CHROMA_CONV_FULL
#undef CHROMA_CONV
#undef CHROMA_TEST
*(uint32_t*)chroma = *(uint32_t*)"BGRA";
return VIDEO_FORMAT_BGRA;
}
static inline unsigned get_format_lines(enum video_format format,
unsigned height, size_t plane)
{
switch (format) {
case VIDEO_FORMAT_I420:
case VIDEO_FORMAT_NV12:
return (plane == 0) ? height : height / 2;
case VIDEO_FORMAT_YVYU:
case VIDEO_FORMAT_YUY2:
case VIDEO_FORMAT_UYVY:
case VIDEO_FORMAT_I444:
case VIDEO_FORMAT_RGBA:
case VIDEO_FORMAT_BGRA:
case VIDEO_FORMAT_BGRX:
case VIDEO_FORMAT_Y800:
return height;
case VIDEO_FORMAT_NONE:;
}
return 0;
}
static enum audio_format convert_vlc_audio_format(char *format)
{
#define AUDIO_TEST(val, ret) \
if (chroma_is(format, val)) return ret
#define AUDIO_CONV(val, new_val, ret) \
do { \
if (chroma_is(format, val)) { \
*(uint32_t*)format = *(uint32_t*)new_val; \
return ret; \
} \
} while (false)
AUDIO_TEST("S16N", AUDIO_FORMAT_16BIT);
AUDIO_TEST("S32N", AUDIO_FORMAT_32BIT);
AUDIO_TEST("FL32", AUDIO_FORMAT_FLOAT);
AUDIO_CONV("U16N", "S16N", AUDIO_FORMAT_16BIT);
AUDIO_CONV("U32N", "S32N", AUDIO_FORMAT_32BIT);
AUDIO_CONV("S24N", "S32N", AUDIO_FORMAT_32BIT);
AUDIO_CONV("U24N", "S32N", AUDIO_FORMAT_32BIT);
AUDIO_CONV("FL64", "FL32", AUDIO_FORMAT_FLOAT);
AUDIO_CONV("S16I", "S16N", AUDIO_FORMAT_16BIT);
AUDIO_CONV("U16I", "S16N", AUDIO_FORMAT_16BIT);
AUDIO_CONV("S24I", "S32N", AUDIO_FORMAT_32BIT);
AUDIO_CONV("U24I", "S32N", AUDIO_FORMAT_32BIT);
AUDIO_CONV("S32I", "S32N", AUDIO_FORMAT_32BIT);
AUDIO_CONV("U32I", "S32N", AUDIO_FORMAT_32BIT);
#undef AUDIO_CONV
#undef AUDIO_TEST
*(uint32_t*)format = *(uint32_t*)"FL32";
return AUDIO_FORMAT_FLOAT;
}
/* ------------------------------------------------------------------------- */
static const char *vlcs_get_name(void *unused)
{
UNUSED_PARAMETER(unused);
return obs_module_text("VLCSource");
}
static void vlcs_destroy(void *data)
{
struct vlc_source *c = data;
if (c->media_list_player) {
libvlc_media_list_player_stop_(c->media_list_player);
libvlc_media_list_player_release_(c->media_list_player);
}
if (c->media_player) {
libvlc_media_player_release_(c->media_player);
}
bfree((void*)c->audio.data[0]);
obs_source_frame_free(&c->frame);
free_files(&c->files.da);
pthread_mutex_destroy(&c->mutex);
bfree(c);
}
static void *vlcs_video_lock(void *data, void **planes)
{
struct vlc_source *c = data;
for (size_t i = 0; i < MAX_AV_PLANES && c->frame.data[i] != NULL; i++)
planes[i] = c->frame.data[i];
return NULL;
}
static void vlcs_video_display(void *data, void *picture)
{
struct vlc_source *c = data;
c->frame.timestamp = (uint64_t)libvlc_clock_() * 1000ULL - time_start;
obs_source_output_video(c->source, &c->frame);
UNUSED_PARAMETER(picture);
}
static unsigned vlcs_video_format(void **p_data, char *chroma, unsigned *width,
unsigned *height, unsigned *pitches, unsigned *lines)
{
struct vlc_source *c = *p_data;
enum video_format new_format;
enum video_range_type range;
bool new_range;
size_t i = 0;
new_format = convert_vlc_video_format(chroma, &new_range);
libvlc_video_get_size_(c->media_player, 0, width, height);
/* don't allocate a new frame if format/width/height hasn't changed */
if (c->frame.format != new_format ||
c->frame.width != *width ||
c->frame.height != *height) {
obs_source_frame_free(&c->frame);
obs_source_frame_init(&c->frame, new_format, *width, *height);
c->frame.format = new_format;
c->frame.full_range = new_range;
range = c->frame.full_range ?
VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL;
video_format_get_parameters(VIDEO_CS_DEFAULT, range,
c->frame.color_matrix,
c->frame.color_range_min,
c->frame.color_range_max);
}
while (c->frame.data[i]) {
pitches[i] = (unsigned)c->frame.linesize[i];
lines[i] = get_format_lines(c->frame.format, *height, i);
i++;
}
return 1;
}
static void vlcs_audio_play(void *data, const void *samples, unsigned count,
int64_t pts)
{
struct vlc_source *c = data;
size_t size = get_audio_size(c->audio.format, c->audio.speakers, count);
if (c->audio_capacity < count) {
c->audio.data[0] = brealloc((void*)c->audio.data[0], size);
c->audio_capacity = count;
}
memcpy((void*)c->audio.data[0], samples, size);
c->audio.timestamp = (uint64_t)pts * 1000ULL - time_start;
c->audio.frames = count;
obs_source_output_audio(c->source, &c->audio);
}
static int vlcs_audio_setup(void **p_data, char *format, unsigned *rate,
unsigned *channels)
{
struct vlc_source *c = *p_data;
enum audio_format new_audio_format;
new_audio_format = convert_vlc_audio_format(format);
if (*channels == 7 || *channels > 8)
*channels = 2;
/* don't free audio data if the data is the same format */
if (c->audio.format == new_audio_format &&
c->audio.samples_per_sec == *rate &&
c->audio.speakers == (enum speaker_layout)*channels)
return 0;
c->audio_capacity = 0;
bfree((void*)c->audio.data[0]);
memset(&c->audio, 0, sizeof(c->audio));
c->audio.speakers = (enum speaker_layout)*channels;
c->audio.samples_per_sec = *rate;
c->audio.format = new_audio_format;
return 0;
}
static void vlcs_update(void *data, obs_data_t *settings)
{
DARRAY(struct media_file_data) new_files;
DARRAY(struct media_file_data) old_files;
libvlc_media_list_t *media_list;
struct vlc_source *c = data;
obs_data_array_t *array;
const char *behavior;
size_t count;
da_init(new_files);
da_init(old_files);
array = obs_data_get_array(settings, S_PLAYLIST);
count = obs_data_array_count(array);
c->loop = obs_data_get_bool(settings, S_LOOP);
behavior = obs_data_get_string(settings, S_BEHAVIOR);
if (astrcmpi(behavior, S_BEHAVIOR_PAUSE_UNPAUSE) == 0) {
c->behavior = BEHAVIOR_PAUSE_UNPAUSE;
} else if (astrcmpi(behavior, S_BEHAVIOR_ALWAYS_PLAY) == 0) {
c->behavior = BEHAVIOR_ALWAYS_PLAY;
} else { /* S_BEHAVIOR_STOP_RESTART */
c->behavior = BEHAVIOR_STOP_RESTART;
}
/* ------------------------------------- */
/* create new list of sources */
for (size_t i = 0; i < count; i++) {
obs_data_t *item = obs_data_array_item(array, i);
const char *path = obs_data_get_string(item, "value");
struct media_file_data data;
struct dstr new_path = {0};
libvlc_media_t *new_media;
dstr_copy(&new_path, path);
#ifdef _WIN32
dstr_replace(&new_path, "/", "\\");
#endif
path = new_path.array;
new_media = get_media(&c->files.da, path);
if (!new_media)
new_media = get_media(&new_files.da, path);
if (!new_media)
new_media = create_media_from_file(path);
if (new_media) {
data.path = new_path.array;
data.media = new_media;
da_push_back(new_files, &data);
} else {
dstr_free(&new_path);
}
obs_data_release(item);
}
/* ------------------------------------- */
/* update settings data */
libvlc_media_list_player_stop_(c->media_list_player);
pthread_mutex_lock(&c->mutex);
old_files.da = c->files.da;
c->files.da = new_files.da;
pthread_mutex_unlock(&c->mutex);
/* ------------------------------------- */
/* clean up and restart playback */
free_files(&old_files.da);
media_list = libvlc_media_list_new_(libvlc);
libvlc_media_list_lock_(media_list);
for (size_t i = 0; i < c->files.num; i++)
libvlc_media_list_add_media_(media_list,
c->files.array[i].media);
libvlc_media_list_unlock_(media_list);
libvlc_media_list_player_set_media_list_(c->media_list_player,
media_list);
libvlc_media_list_release_(media_list);
libvlc_media_list_player_set_playback_mode_(c->media_list_player,
c->loop ? libvlc_playback_mode_loop :
libvlc_playback_mode_default);
if (c->files.num &&
(c->behavior == BEHAVIOR_ALWAYS_PLAY || obs_source_active(c->source)))
libvlc_media_list_player_play_(c->media_list_player);
else
obs_source_output_video(c->source, NULL);
obs_data_array_release(array);
}
static void vlcs_stopped(const struct libvlc_event_t *event, void *data)
{
struct vlc_source *c = data;
if (!c->loop)
obs_source_output_video(c->source, NULL);
UNUSED_PARAMETER(event);
}
static void *vlcs_create(obs_data_t *settings, obs_source_t *source)
{
struct vlc_source *c = bzalloc(sizeof(*c));
c->source = source;
pthread_mutex_init_value(&c->mutex);
if (pthread_mutex_init(&c->mutex, NULL) != 0)
goto error;
if (!load_libvlc())
goto error;
c->media_list_player = libvlc_media_list_player_new_(libvlc);
if (!c->media_list_player)
goto error;
c->media_player = libvlc_media_player_new_(libvlc);
if (!c->media_player)
goto error;
libvlc_media_list_player_set_media_player_(c->media_list_player,
c->media_player);
libvlc_video_set_callbacks_(c->media_player,
vlcs_video_lock, NULL, vlcs_video_display,
c);
libvlc_video_set_format_callbacks_(c->media_player,
vlcs_video_format, NULL);
libvlc_audio_set_callbacks_(c->media_player,
vlcs_audio_play, NULL, NULL, NULL, NULL, c);
libvlc_audio_set_format_callbacks_(c->media_player,
vlcs_audio_setup, NULL);
libvlc_event_manager_t *event_manager;
event_manager = libvlc_media_player_event_manager_(c->media_player);
libvlc_event_attach_(event_manager, libvlc_MediaPlayerEndReached,
vlcs_stopped, c);
obs_source_update(source, NULL);
UNUSED_PARAMETER(settings);
return c;
error:
vlcs_destroy(c);
return NULL;
}
static void vlcs_activate(void *data)
{
struct vlc_source *c = data;
if (c->behavior == BEHAVIOR_STOP_RESTART) {
libvlc_media_list_player_play_(c->media_list_player);
} else if (c->behavior == BEHAVIOR_PAUSE_UNPAUSE) {
libvlc_media_list_player_play_(c->media_list_player);
}
}
static void vlcs_deactivate(void *data)
{
struct vlc_source *c = data;
if (c->behavior == BEHAVIOR_STOP_RESTART) {
libvlc_media_list_player_stop_(c->media_list_player);
obs_source_output_video(c->source, NULL);
} else if (c->behavior == BEHAVIOR_PAUSE_UNPAUSE) {
libvlc_media_list_player_pause_(c->media_list_player);
}
}
static void vlcs_defaults(obs_data_t *settings)
{
obs_data_set_default_bool(settings, S_LOOP, true);
obs_data_set_default_string(settings, S_BEHAVIOR,
S_BEHAVIOR_STOP_RESTART);
}
static const char *file_filter =
"Media files (*.mp4 *.ts *.mov *.flv *.mkv *.avi "
"*.mp3 *.ogg *.aac *.wav *.webm)";
static obs_properties_t *vlcs_properties(void *data)
{
obs_properties_t *ppts = obs_properties_create();
struct vlc_source *c = data;
struct dstr path = {0};
obs_property_t *p;
obs_properties_add_bool(ppts, S_LOOP, T_LOOP);
if (c) {
pthread_mutex_lock(&c->mutex);
if (c->files.num) {
struct media_file_data *last = da_end(c->files);
const char *slash;
dstr_copy(&path, last->path);
dstr_replace(&path, "\\", "/");
slash = strrchr(path.array, '/');
if (slash)
dstr_resize(&path, slash - path.array + 1);
}
pthread_mutex_unlock(&c->mutex);
}
p = obs_properties_add_list(ppts, S_BEHAVIOR, T_BEHAVIOR,
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
obs_property_list_add_string(p, T_BEHAVIOR_STOP_RESTART,
S_BEHAVIOR_STOP_RESTART);
obs_property_list_add_string(p, T_BEHAVIOR_PAUSE_UNPAUSE,
S_BEHAVIOR_PAUSE_UNPAUSE);
obs_property_list_add_string(p, T_BEHAVIOR_ALWAYS_PLAY,
S_BEHAVIOR_ALWAYS_PLAY);
obs_properties_add_editable_list(ppts, S_PLAYLIST, T_PLAYLIST,
OBS_EDITABLE_LIST_TYPE_FILES, file_filter, path.array);
dstr_free(&path);
return ppts;
}
struct obs_source_info vlc_source_info = {
.id = "vlc_source",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO,
.get_name = vlcs_get_name,
.create = vlcs_create,
.destroy = vlcs_destroy,
.update = vlcs_update,
.get_defaults = vlcs_defaults,
.get_properties = vlcs_properties,
.activate = vlcs_activate,
.deactivate = vlcs_deactivate
};