From d89299fc1a6145701625bcd5095748d1eb6e9a1e Mon Sep 17 00:00:00 2001 From: jp9000 Date: Thu, 2 Jun 2016 00:31:43 -0700 Subject: [PATCH] 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. --- cmake/Modules/FindLibVLC.cmake | 72 +++ plugins/CMakeLists.txt | 2 + plugins/vlc-video/CMakeLists.txt | 41 ++ plugins/vlc-video/data/locale/en-US.ini | 7 + plugins/vlc-video/vlc-video-plugin.c | 201 ++++++++ plugins/vlc-video/vlc-video-plugin.h | 156 ++++++ plugins/vlc-video/vlc-video-source.c | 625 ++++++++++++++++++++++++ 7 files changed, 1104 insertions(+) create mode 100644 cmake/Modules/FindLibVLC.cmake create mode 100644 plugins/vlc-video/CMakeLists.txt create mode 100644 plugins/vlc-video/data/locale/en-US.ini create mode 100644 plugins/vlc-video/vlc-video-plugin.c create mode 100644 plugins/vlc-video/vlc-video-plugin.h create mode 100644 plugins/vlc-video/vlc-video-source.c diff --git a/cmake/Modules/FindLibVLC.cmake b/cmake/Modules/FindLibVLC.cmake new file mode 100644 index 000000000..3f6888555 --- /dev/null +++ b/cmake/Modules/FindLibVLC.cmake @@ -0,0 +1,72 @@ +# Once done these will be defined: +# +# LIBVLC_FOUND +# LIBVLC_INCLUDE_DIRS +# LIBVLC_LIBRARIES +# +# For use in OBS: +# +# VLC_INCLUDE_DIR + +find_package(PkgConfig QUIET) +if (PKG_CONFIG_FOUND) + pkg_check_modules(_VLC QUIET VLC) +endif() + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_lib_suffix 64) +else() + set(_lib_suffix 32) +endif() + +find_path(VLC_INCLUDE_DIR + NAMES libvlc.h + HINTS + ENV VLCPath${_lib_suffix} + ENV VLCPath + ENV DepsPath${_lib_suffix} + ENV DepsPath + ${VLCPath${_lib_suffix}} + ${VLCPath} + ${DepsPath${_lib_suffix}} + ${DepsPath} + ${_VLC_INCLUDE_DIRS} + PATHS + /usr/include /usr/local/include /opt/local/include /sw/include + PATH_SUFFIXES + vlc include/vlc include) + +find_library(VLC_LIB + NAMES ${_VLC_LIBRARIES} VLC libVLC + HINTS + ENV VLCPath${_lib_suffix} + ENV VLCPath + ENV DepsPath${_lib_suffix} + ENV DepsPath + ${VLCPath${_lib_suffix}} + ${VLCPath} + ${DepsPath${_lib_suffix}} + ${DepsPath} + ${_VLC_LIBRARY_DIRS} + PATHS + /usr/lib /usr/local/lib /opt/local/lib /sw/lib + PATH_SUFFIXES + lib${_lib_suffix} lib + libs${_lib_suffix} libs + bin${_lib_suffix} bin + ../lib${_lib_suffix} ../lib + ../libs${_lib_suffix} ../libs + ../bin${_lib_suffix} ../bin) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibVLC_INCLUDES DEFAULT_MSG VLC_INCLUDE_DIR) +find_package_handle_standard_args(LibVLC DEFAULT_MSG VLC_LIB VLC_INCLUDE_DIR) +mark_as_advanced(VLC_INCLUDE_DIR VLC_LIB) + +if(LIBVLC_INCLUDES_FOUND) + set(LIBVLC_INCLUDE_DIRS ${VLC_INCLUDE_DIR}) +endif() + +if(LIBVLC_FOUND) + set(LIBVLC_LIBRARIES ${VLC_LIB}) +endif() diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 879118349..1ecb062b8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -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) diff --git a/plugins/vlc-video/CMakeLists.txt b/plugins/vlc-video/CMakeLists.txt new file mode 100644 index 000000000..bcc95fdef --- /dev/null +++ b/plugins/vlc-video/CMakeLists.txt @@ -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) diff --git a/plugins/vlc-video/data/locale/en-US.ini b/plugins/vlc-video/data/locale/en-US.ini new file mode 100644 index 000000000..cd5a7682a --- /dev/null +++ b/plugins/vlc-video/data/locale/en-US.ini @@ -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" diff --git a/plugins/vlc-video/vlc-video-plugin.c b/plugins/vlc-video/vlc-video-plugin.c new file mode 100644 index 000000000..1f8e45bdb --- /dev/null +++ b/plugins/vlc-video/vlc-video-plugin.c @@ -0,0 +1,201 @@ +#ifdef _WIN32 +#include +#endif + +#include +#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); +} diff --git a/plugins/vlc-video/vlc-video-plugin.h b/plugins/vlc-video/vlc-video-plugin.h new file mode 100644 index 000000000..d564480e2 --- /dev/null +++ b/plugins/vlc-video/vlc-video-plugin.h @@ -0,0 +1,156 @@ +#include +#include + +#ifdef _MSC_VER +#include +typedef SSIZE_T ssize_t; +#endif + +#include +#include +#include +#include +#include + +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_; diff --git a/plugins/vlc-video/vlc-video-source.c b/plugins/vlc-video/vlc-video-source.c new file mode 100644 index 000000000..8be1ecaf4 --- /dev/null +++ b/plugins/vlc-video/vlc-video-source.c @@ -0,0 +1,625 @@ +#include "vlc-video-plugin.h" +#include +#include +#include + +#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 +};