New upstream version 21.0.2+dfsg1

This commit is contained in:
Sebastian Ramacher 2018-02-19 20:54:37 +01:00
parent 1f1bbb3518
commit baafb6325b
706 changed files with 49633 additions and 5044 deletions

View file

@ -12,18 +12,32 @@ if (NOT "${FFMPEG_AVCODEC_LIBRARIES}" STREQUAL "")
endif()
if(UNIX)
if (NOT APPLE)
find_package(PulseAudio)
if (NOT "${PULSEAUDIO_LIBRARY}" STREQUAL "")
message(STATUS "Found PulseAudio - Audio Monitor enabled")
set(HAVE_PULSEAUDIO "1")
else()
set(HAVE_PULSEAUDIO "0")
endif()
else()
set(HAVE_PULSEAUDIO "0")
endif()
find_package(DBus QUIET)
if (NOT APPLE)
find_package(X11_XCB REQUIRED)
endif()
else()
set(HAVE_DBUS "0")
set(HAVE_PULSEAUDIO "0")
endif()
find_package(ImageMagick QUIET COMPONENTS MagickCore)
if(NOT ImageMagick_MagickCore_FOUND AND NOT FFMPEG_AVCODEC_FOUND)
message(FATAL_ERROR "Either MagickCore or Libavcodec is required, but both were not found")
message(FATAL_ERROR "Either MagickCore or Libavcodec is required, but neither were found.")
elseif(NOT ImageMagick_MagickCore_FOUND AND LIBOBS_PREFER_IMAGEMAGICK)
message(FATAL_ERROR "ImageMagick support was requested, but was not found.")
endif()
option(LIBOBS_PREFER_IMAGEMAGICK "Prefer ImageMagick over ffmpeg for image loading" OFF)
@ -31,6 +45,12 @@ option(LIBOBS_PREFER_IMAGEMAGICK "Prefer ImageMagick over ffmpeg for image loadi
if(NOT FFMPEG_AVCODEC_FOUND OR (ImageMagick_MagickCore_FOUND AND LIBOBS_PREFER_IMAGEMAGICK))
message(STATUS "Using ImageMagick for image loading in libobs")
if(${ImageMagick_VERSION_STRING} LESS 7)
set(LIBOBS_IMAGEMAGICK_DIR_STYLE LIBOBS_IMAGEMAGICK_DIR_STYLE_6L)
elseif(${ImageMagick_VERSION_STRING} GREATER_EQUAL 7)
set(LIBOBS_IMAGEMAGICK_DIR_STYLE LIBOBS_IMAGEMAGICK_DIR_STYLE_7GE)
endif()
set(libobs_image_loading_SOURCES
graphics/graphics-magick.c)
set(libobs_image_loading_LIBRARIES
@ -63,6 +83,7 @@ if(WIN32)
util/platform-windows.c)
set(libobs_PLATFORM_HEADERS
util/threading-windows.h
util/windows/win-registry.h
util/windows/win-version.h
util/windows/ComPtr.hpp
util/windows/CoTaskMemPtr.hpp
@ -75,7 +96,7 @@ if(WIN32)
set(libobs_audio_monitoring_HEADERS
audio-monitoring/win32/wasapi-output.h
)
set(libobs_PLATFORM_DEPS winmm)
set(libobs_PLATFORM_DEPS winmm psapi)
if(MSVC)
set(libobs_PLATFORM_DEPS
${libobs_PLATFORM_DEPS}
@ -145,12 +166,22 @@ elseif(UNIX)
util/threading-posix.c
util/pipe-posix.c
util/platform-nix.c)
set(libobs_PLATFORM_HEADERS
util/threading-posix.h)
set(libobs_audio_monitoring_SOURCES
audio-monitoring/null/null-audio-monitoring.c
)
if(HAVE_PULSEAUDIO)
set(libobs_audio_monitoring_HEADERS
audio-monitoring/pulse/pulseaudio-wrapper.h)
set(libobs_audio_monitoring_SOURCES
audio-monitoring/pulse/pulseaudio-wrapper.c
audio-monitoring/pulse/pulseaudio-enum-devices.c
audio-monitoring/pulse/pulseaudio-output.c)
else()
set(libobs_audio_monitoring_SOURCES
audio-monitoring/null/null-audio-monitoring.c)
endif()
if(DBUS_FOUND)
set(libobs_PLATFORM_SOURCES ${libobs_PLATFORM_SOURCES}
util/platform-nix-dbus.c)
@ -168,6 +199,12 @@ elseif(UNIX)
${libobs_PLATFORM_DEPS}
${X11_XCB_LIBRARIES})
if(HAVE_PULSEAUDIO)
set(libobs_PLATFORM_DEPS
${libobs_PLATFORM_DEPS}
${PULSEAUDIO_LIBRARY})
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
# use the sysinfo compatibility library on bsd
find_package(Libsysinfo REQUIRED)
@ -358,7 +395,9 @@ set(libobs_SOURCES
${libobs_graphics_SOURCES}
${libobs_mediaio_SOURCES}
${libobs_util_SOURCES}
${libobs_libobs_SOURCES})
${libobs_libobs_SOURCES}
${libobs_audio_monitoring_SOURCES}
)
set(libobs_HEADERS
${libobs_config_HEADERS}
@ -367,7 +406,6 @@ set(libobs_HEADERS
${libobs_mediaio_HEADERS}
${libobs_util_HEADERS}
${libobs_libobs_HEADERS}
${libobs_audio_monitoring_SOURCES}
${libobs_audio_monitoring_HEADERS}
)

View file

@ -1,4 +1,4 @@
#include "../../obs-internal.h"
#include <obs-internal.h>
void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data)
{

View file

@ -0,0 +1,33 @@
#include <obs-internal.h>
#include "pulseaudio-wrapper.h"
static void pulseaudio_output_info(pa_context *c, const pa_source_info *i,
int eol, void *userdata)
{
UNUSED_PARAMETER(c);
if (eol != 0 || i->monitor_of_sink == PA_INVALID_INDEX)
goto skip;
struct enum_cb *ecb = (struct enum_cb *) userdata;
if (ecb->cont)
ecb->cont = ecb->cb(ecb->data, i->description, i->name);
skip:
pulseaudio_signal(0);
}
void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb,
void *data)
{
struct enum_cb *ecb = bzalloc(sizeof(struct enum_cb));
ecb->cb = cb;
ecb->data = data;
ecb->cont = 1;
pulseaudio_init();
pa_source_info_cb_t pa_cb = pulseaudio_output_info;
pulseaudio_get_source_info_list(pa_cb, (void *) ecb);
pulseaudio_unref();
bfree(ecb);
}

View file

@ -0,0 +1,552 @@
#include "obs-internal.h"
#include "pulseaudio-wrapper.h"
#define PULSE_DATA(voidptr) struct audio_monitor *data = voidptr;
#define blog(level, msg, ...) blog(level, "pulse-am: " msg, ##__VA_ARGS__)
struct audio_monitor {
obs_source_t *source;
pa_stream *stream;
char *device;
pa_buffer_attr attr;
enum speaker_layout speakers;
pa_sample_format_t format;
uint_fast32_t samples_per_sec;
uint_fast32_t bytes_per_frame;
uint_fast8_t channels;
uint_fast32_t packets;
uint_fast64_t frames;
struct circlebuf new_data;
audio_resampler_t *resampler;
size_t buffer_size;
size_t bytesRemaining;
size_t bytes_per_channel;
bool ignore;
pthread_mutex_t playback_mutex;
};
static enum speaker_layout pulseaudio_channels_to_obs_speakers(
uint_fast32_t channels)
{
switch (channels) {
case 0: return SPEAKERS_UNKNOWN;
case 1: return SPEAKERS_MONO;
case 2: return SPEAKERS_STEREO;
case 3: return SPEAKERS_2POINT1;
case 4: return SPEAKERS_4POINT0;
case 5: return SPEAKERS_4POINT1;
case 6: return SPEAKERS_5POINT1;
case 8: return SPEAKERS_7POINT1;
default: return SPEAKERS_UNKNOWN;
}
}
static enum audio_format pulseaudio_to_obs_audio_format(
pa_sample_format_t format)
{
switch (format) {
case PA_SAMPLE_U8:
return AUDIO_FORMAT_U8BIT;
case PA_SAMPLE_S16LE:
return AUDIO_FORMAT_16BIT;
case PA_SAMPLE_S32LE:
return AUDIO_FORMAT_32BIT;
case PA_SAMPLE_FLOAT32LE:
return AUDIO_FORMAT_FLOAT;
default:
return AUDIO_FORMAT_UNKNOWN;
}
}
static pa_channel_map pulseaudio_channel_map(enum speaker_layout layout)
{
pa_channel_map ret;
ret.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
ret.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
ret.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
ret.map[3] = PA_CHANNEL_POSITION_LFE;
ret.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
ret.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
ret.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
ret.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
switch (layout) {
case SPEAKERS_MONO:
ret.channels = 1;
ret.map[0] = PA_CHANNEL_POSITION_MONO;
break;
case SPEAKERS_STEREO:
ret.channels = 2;
break;
case SPEAKERS_2POINT1:
ret.channels = 3;
ret.map[2] = PA_CHANNEL_POSITION_LFE;
break;
case SPEAKERS_4POINT0:
ret.channels = 4;
ret.map[3] = PA_CHANNEL_POSITION_REAR_CENTER;
break;
case SPEAKERS_4POINT1:
ret.channels = 5;
ret.map[4] = PA_CHANNEL_POSITION_REAR_CENTER;
break;
case SPEAKERS_5POINT1:
ret.channels = 6;
break;
case SPEAKERS_7POINT1:
ret.channels = 8;
break;
case SPEAKERS_UNKNOWN:
default:
ret.channels = 0;
break;
}
return ret;
}
static void process_byte(void *p, size_t frames, size_t channels, float vol)
{
register char *cur = (char *) p;
register char *end = cur + frames * channels;
while (cur < end)
*(cur++) *= vol;
}
static void process_short(void *p, size_t frames, size_t channels, float vol)
{
register short *cur = (short *) p;
register short *end = cur + frames * channels;
while (cur < end)
*(cur++) *= vol;
}
static void process_float(void *p, size_t frames, size_t channels, float vol)
{
register float *cur = (float *) p;
register float *end = cur + frames * channels;
while (cur < end)
*(cur++) *= vol;
}
void process_volume(const struct audio_monitor *monitor, float vol,
uint8_t *const *resample_data, uint32_t resample_frames)
{
switch (monitor->bytes_per_channel) {
case 1:
process_byte(resample_data[0], resample_frames,
monitor->channels, vol);
break;
case 2:
process_short(resample_data[0], resample_frames,
monitor->channels, vol);
break;
default:
process_float(resample_data[0], resample_frames,
monitor->channels, vol);
break;
}
}
static void do_stream_write(void *param)
{
PULSE_DATA(param);
uint8_t *buffer = NULL;
while (data->new_data.size >= data->buffer_size &&
data->bytesRemaining > 0) {
size_t bytesToFill = data->buffer_size;
if (bytesToFill > data->bytesRemaining)
bytesToFill = data->bytesRemaining;
pa_stream_begin_write(data->stream, (void **) &buffer,
&bytesToFill);
circlebuf_pop_front(&data->new_data, buffer, bytesToFill);
pulseaudio_lock();
pa_stream_write(data->stream, buffer, bytesToFill, NULL,
0LL, PA_SEEK_RELATIVE);
pulseaudio_unlock();
data->bytesRemaining -= bytesToFill;
}
}
static void on_audio_playback(void *param, obs_source_t *source,
const struct audio_data *audio_data, bool muted)
{
struct audio_monitor *monitor = param;
float vol = source->user_volume;
size_t bytes;
uint8_t *resample_data[MAX_AV_PLANES];
uint32_t resample_frames;
uint64_t ts_offset;
bool success;
if (pthread_mutex_trylock(&monitor->playback_mutex) != 0)
return;
if (os_atomic_load_long(&source->activate_refs) == 0)
goto unlock;
success = audio_resampler_resample(monitor->resampler, resample_data,
&resample_frames, &ts_offset,
(const uint8_t *const *) audio_data->data,
(uint32_t) audio_data->frames);
if (!success)
goto unlock;
bytes = monitor->bytes_per_frame * resample_frames;
if (muted) {
memset(resample_data[0], 0, bytes);
} else {
if (!close_float(vol, 1.0f, EPSILON)) {
process_volume(monitor, vol, resample_data,
resample_frames);
}
}
circlebuf_push_back(&monitor->new_data, resample_data[0], bytes);
monitor->packets++;
monitor->frames += resample_frames;
unlock:
pthread_mutex_unlock(&monitor->playback_mutex);
do_stream_write(param);
}
static void pulseaudio_stream_write(pa_stream *p, size_t nbytes, void *userdata)
{
UNUSED_PARAMETER(p);
PULSE_DATA(userdata);
pthread_mutex_lock(&data->playback_mutex);
data->bytesRemaining += nbytes;
pthread_mutex_unlock(&data->playback_mutex);
pulseaudio_signal(0);
}
static void pulseaudio_underflow(pa_stream *p, void *userdata)
{
UNUSED_PARAMETER(p);
PULSE_DATA(userdata);
pthread_mutex_lock(&data->playback_mutex);
if (obs_source_active(data->source))
data->attr.tlength = (data->attr.tlength * 3) / 2;
pa_stream_set_buffer_attr(data->stream, &data->attr, NULL, NULL);
pthread_mutex_unlock(&data->playback_mutex);
pulseaudio_signal(0);
}
static void pulseaudio_server_info(pa_context *c, const pa_server_info *i,
void *userdata)
{
UNUSED_PARAMETER(c);
UNUSED_PARAMETER(userdata);
blog(LOG_INFO, "Server name: '%s %s'", i->server_name,
i->server_version);
pulseaudio_signal(0);
}
static void pulseaudio_source_info(pa_context *c, const pa_source_info *i,
int eol, void *userdata)
{
UNUSED_PARAMETER(c);
PULSE_DATA(userdata);
// An error occured
if (eol < 0) {
data->format = PA_SAMPLE_INVALID;
goto skip;
}
// Terminating call for multi instance callbacks
if (eol > 0)
goto skip;
blog(LOG_INFO, "Audio format: %s, %"PRIu32" Hz, %"PRIu8" channels",
pa_sample_format_to_string(i->sample_spec.format),
i->sample_spec.rate, i->sample_spec.channels);
pa_sample_format_t format = i->sample_spec.format;
if (pulseaudio_to_obs_audio_format(format) == AUDIO_FORMAT_UNKNOWN) {
format = PA_SAMPLE_FLOAT32LE;
blog(LOG_INFO, "Sample format %s not supported by OBS,"
"using %s instead for recording",
pa_sample_format_to_string(
i->sample_spec.format),
pa_sample_format_to_string(format));
}
uint8_t channels = i->sample_spec.channels;
if (pulseaudio_channels_to_obs_speakers(channels) == SPEAKERS_UNKNOWN) {
channels = 2;
blog(LOG_INFO, "%c channels not supported by OBS,"
"using %c instead for recording",
i->sample_spec.channels,
channels);
}
data->format = format;
data->samples_per_sec = i->sample_spec.rate;
data->channels = channels;
skip:
pulseaudio_signal(0);
}
static void pulseaudio_stop_playback(struct audio_monitor *monitor)
{
if (monitor->stream) {
pa_stream_disconnect(monitor->stream);
pa_stream_unref(monitor->stream);
monitor->stream = NULL;
}
blog(LOG_INFO, "Stopped Monitoring in '%s'", monitor->device);
blog(LOG_INFO, "Got %"PRIuFAST32" packets with %"PRIuFAST64" frames",
monitor->packets, monitor->frames);
monitor->packets = 0;
monitor->frames = 0;
}
static bool audio_monitor_init(struct audio_monitor *monitor,
obs_source_t *source)
{
pthread_mutex_init_value(&monitor->playback_mutex);
monitor->source = source;
const char *id = obs->audio.monitoring_device_id;
if (!id)
return false;
if (source->info.output_flags & OBS_SOURCE_DO_NOT_SELF_MONITOR) {
obs_data_t *s = obs_source_get_settings(source);
const char *s_dev_id = obs_data_get_string(s, "device_id");
bool match = devices_match(s_dev_id, id);
obs_data_release(s);
if (match) {
monitor->ignore = true;
blog(LOG_INFO, "Prevented feedback-loop in '%s'",
s_dev_id);
return true;
}
}
pulseaudio_init();
if (strcmp(id, "default") == 0)
get_default_id(&monitor->device);
else
monitor->device = bstrdup(id);
if (!monitor->device)
return false;
if (pulseaudio_get_server_info(pulseaudio_server_info,
(void *) monitor) < 0) {
blog(LOG_ERROR, "Unable to get server info !");
return false;
}
if (pulseaudio_get_source_info(pulseaudio_source_info, monitor->device,
(void *) monitor) < 0) {
blog(LOG_ERROR, "Unable to get source info !");
return false;
}
if (monitor->format == PA_SAMPLE_INVALID) {
blog(LOG_ERROR,
"An error occurred while getting the source info!");
return false;
}
pa_sample_spec spec;
spec.format = monitor->format;
spec.rate = (uint32_t) monitor->samples_per_sec;
spec.channels = monitor->channels;
if (!pa_sample_spec_valid(&spec)) {
blog(LOG_ERROR, "Sample spec is not valid");
return false;
}
const struct audio_output_info *info = audio_output_get_info(
obs->audio.audio);
struct resample_info from = {
.samples_per_sec = info->samples_per_sec,
.speakers = info->speakers,
.format = AUDIO_FORMAT_FLOAT_PLANAR
};
struct resample_info to = {
.samples_per_sec = (uint32_t) monitor->samples_per_sec,
.speakers = pulseaudio_channels_to_obs_speakers(
monitor->channels),
.format = pulseaudio_to_obs_audio_format
(monitor->format)
};
monitor->resampler = audio_resampler_create(&to, &from);
if (!monitor->resampler) {
blog(LOG_WARNING, "%s: %s", __FUNCTION__,
"Failed to create resampler");
return false;
}
monitor->bytes_per_channel = get_audio_bytes_per_channel(
pulseaudio_to_obs_audio_format(monitor->format));
monitor->speakers = pulseaudio_channels_to_obs_speakers(spec.channels);
monitor->bytes_per_frame = pa_frame_size(&spec);
pa_channel_map channel_map = pulseaudio_channel_map(monitor->speakers);
monitor->stream = pulseaudio_stream_new(
obs_source_get_name(monitor->source), &spec, &channel_map);
if (!monitor->stream) {
blog(LOG_ERROR, "Unable to create stream");
return false;
}
monitor->attr.fragsize = (uint32_t) -1;
monitor->attr.maxlength = (uint32_t) -1;
monitor->attr.minreq = (uint32_t) -1;
monitor->attr.prebuf = (uint32_t) -1;
monitor->attr.tlength = pa_usec_to_bytes(25000, &spec);
monitor->buffer_size = monitor->bytes_per_frame *
pa_usec_to_bytes(5000, &spec);
pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING |
PA_STREAM_AUTO_TIMING_UPDATE;
if (pthread_mutex_init(&monitor->playback_mutex, NULL) != 0) {
blog(LOG_WARNING, "%s: %s", __FUNCTION__,
"Failed to init mutex");
return false;
}
int_fast32_t ret = pulseaudio_connect_playback(monitor->stream,
monitor->device, &monitor->attr, flags);
if (ret < 0) {
pulseaudio_stop_playback(monitor);
blog(LOG_ERROR, "Unable to connect to stream");
return false;
}
blog(LOG_INFO, "Started Monitoring in '%s'", monitor->device);
return true;
}
static void audio_monitor_init_final(struct audio_monitor *monitor)
{
if (monitor->ignore)
return;
obs_source_add_audio_capture_callback(monitor->source,
on_audio_playback, monitor);
pulseaudio_write_callback(monitor->stream, pulseaudio_stream_write,
(void *) monitor);
pulseaudio_set_underflow_callback(monitor->stream, pulseaudio_underflow,
(void *) monitor);
}
static inline void audio_monitor_free(struct audio_monitor *monitor)
{
if (monitor->ignore)
return;
if (monitor->source)
obs_source_remove_audio_capture_callback(monitor->source,
on_audio_playback, monitor);
audio_resampler_destroy(monitor->resampler);
circlebuf_free(&monitor->new_data);
if (monitor->stream)
pulseaudio_stop_playback(monitor);
pulseaudio_unref();
bfree(monitor->device);
}
struct audio_monitor *audio_monitor_create(obs_source_t *source)
{
struct audio_monitor monitor = {0};
struct audio_monitor *out;
if (!audio_monitor_init(&monitor, source))
goto fail;
out = bmemdup(&monitor, sizeof(monitor));
pthread_mutex_lock(&obs->audio.monitoring_mutex);
da_push_back(obs->audio.monitors, &out);
pthread_mutex_unlock(&obs->audio.monitoring_mutex);
audio_monitor_init_final(out);
return out;
fail:
audio_monitor_free(&monitor);
return NULL;
}
void audio_monitor_reset(struct audio_monitor *monitor)
{
struct audio_monitor new_monitor = {0};
bool success;
audio_monitor_free(monitor);
pthread_mutex_lock(&monitor->playback_mutex);
success = audio_monitor_init(&new_monitor, monitor->source);
pthread_mutex_unlock(&monitor->playback_mutex);
if (success) {
*monitor = new_monitor;
audio_monitor_init_final(monitor);
} else {
audio_monitor_free(&new_monitor);
}
}
void audio_monitor_destroy(struct audio_monitor *monitor)
{
if (monitor) {
audio_monitor_free(monitor);
pthread_mutex_lock(&obs->audio.monitoring_mutex);
da_erase_item(obs->audio.monitors, &monitor);
pthread_mutex_unlock(&obs->audio.monitoring_mutex);
bfree(monitor);
}
}

View file

@ -0,0 +1,341 @@
/*
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
Copyright (C) 2017 by Fabio Madia <admshao@gmail.com>
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, see <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <pulse/thread-mainloop.h>
#include <util/base.h>
#include <obs.h>
#include "pulseaudio-wrapper.h"
/* global data */
static uint_fast32_t pulseaudio_refs = 0;
static pthread_mutex_t pulseaudio_mutex = PTHREAD_MUTEX_INITIALIZER;
static pa_threaded_mainloop *pulseaudio_mainloop = NULL;
static pa_context *pulseaudio_context = NULL;
static void pulseaudio_default_devices(pa_context *c, const pa_server_info *i,
void *userdata)
{
UNUSED_PARAMETER(c);
struct pulseaudio_default_output *d =
(struct pulseaudio_default_output *) userdata;
d->default_sink_name = bstrdup(i->default_sink_name);
pulseaudio_signal(0);
}
void get_default_id(char **id)
{
pulseaudio_init();
struct pulseaudio_default_output *pdo = bzalloc(
sizeof(struct pulseaudio_default_output));
pulseaudio_get_server_info(
(pa_server_info_cb_t) pulseaudio_default_devices,
(void *) pdo);
*id = bzalloc(strlen(pdo->default_sink_name) + 9);
strcat(*id, pdo->default_sink_name);
strcat(*id, ".monitor");
bfree(pdo->default_sink_name);
bfree(pdo);
pulseaudio_unref();
}
bool devices_match(const char *id1, const char *id2)
{
bool match;
char *name1 = NULL;
char *name2 = NULL;
if (!id1 || !id2)
return false;
if (strcmp(id1, "default") == 0) {
get_default_id(&name1);
id1 = name1;
}
if (strcmp(id2, "default") == 0) {
get_default_id(&name2);
id2 = name2;
}
match = strcmp(id1, id2) == 0;
bfree(name1);
bfree(name2);
return match;
}
/**
* context status change callback
*
* @todo this is currently a noop, we want to reconnect here if the connection
* is lost ...
*/
static void pulseaudio_context_state_changed(pa_context *c, void *userdata)
{
UNUSED_PARAMETER(userdata);
UNUSED_PARAMETER(c);
pulseaudio_signal(0);
}
/**
* get the default properties
*/
static pa_proplist *pulseaudio_properties()
{
pa_proplist *p = pa_proplist_new();
pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, "OBS");
pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, "obs");
pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, "production");
return p;
}
/**
* Initialize the pulse audio context with properties and callback
*/
static void pulseaudio_init_context()
{
pulseaudio_lock();
pa_proplist *p = pulseaudio_properties();
pulseaudio_context = pa_context_new_with_proplist(
pa_threaded_mainloop_get_api(pulseaudio_mainloop),
"OBS-Monitor", p);
pa_context_set_state_callback(pulseaudio_context,
pulseaudio_context_state_changed, NULL);
pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOAUTOSPAWN,
NULL);
pa_proplist_free(p);
pulseaudio_unlock();
}
/**
* wait for context to be ready
*/
static int_fast32_t pulseaudio_context_ready()
{
pulseaudio_lock();
if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(pulseaudio_context))) {
pulseaudio_unlock();
return -1;
}
while (pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY)
pulseaudio_wait();
pulseaudio_unlock();
return 0;
}
int_fast32_t pulseaudio_init()
{
pthread_mutex_lock(&pulseaudio_mutex);
if (pulseaudio_refs == 0) {
pulseaudio_mainloop = pa_threaded_mainloop_new();
pa_threaded_mainloop_start(pulseaudio_mainloop);
pulseaudio_init_context();
}
pulseaudio_refs++;
pthread_mutex_unlock(&pulseaudio_mutex);
return 0;
}
void pulseaudio_unref()
{
pthread_mutex_lock(&pulseaudio_mutex);
if (--pulseaudio_refs == 0) {
pulseaudio_lock();
if (pulseaudio_context != NULL) {
pa_context_disconnect(pulseaudio_context);
pa_context_unref(pulseaudio_context);
pulseaudio_context = NULL;
}
pulseaudio_unlock();
if (pulseaudio_mainloop != NULL) {
pa_threaded_mainloop_stop(pulseaudio_mainloop);
pa_threaded_mainloop_free(pulseaudio_mainloop);
pulseaudio_mainloop = NULL;
}
}
pthread_mutex_unlock(&pulseaudio_mutex);
}
void pulseaudio_lock()
{
pa_threaded_mainloop_lock(pulseaudio_mainloop);
}
void pulseaudio_unlock()
{
pa_threaded_mainloop_unlock(pulseaudio_mainloop);
}
void pulseaudio_wait()
{
pa_threaded_mainloop_wait(pulseaudio_mainloop);
}
void pulseaudio_signal(int wait_for_accept)
{
pa_threaded_mainloop_signal(pulseaudio_mainloop, wait_for_accept);
}
void pulseaudio_accept()
{
pa_threaded_mainloop_accept(pulseaudio_mainloop);
}
int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb,
void *userdata)
{
if (pulseaudio_context_ready() < 0)
return -1;
pulseaudio_lock();
pa_operation *op = pa_context_get_source_info_list(
pulseaudio_context, cb, userdata);
if (!op) {
pulseaudio_unlock();
return -1;
}
while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
pulseaudio_wait();
pa_operation_unref(op);
pulseaudio_unlock();
return 0;
}
int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb,
const char *name, void *userdata)
{
if (pulseaudio_context_ready() < 0)
return -1;
pulseaudio_lock();
pa_operation *op = pa_context_get_source_info_by_name(
pulseaudio_context, name, cb, userdata);
if (!op) {
pulseaudio_unlock();
return -1;
}
while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
pulseaudio_wait();
pa_operation_unref(op);
pulseaudio_unlock();
return 0;
}
int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata)
{
if (pulseaudio_context_ready() < 0)
return -1;
pulseaudio_lock();
pa_operation *op = pa_context_get_server_info(
pulseaudio_context, cb, userdata);
if (!op) {
pulseaudio_unlock();
return -1;
}
while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
pulseaudio_wait();
pa_operation_unref(op);
pulseaudio_unlock();
return 0;
}
pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss,
const pa_channel_map *map)
{
if (pulseaudio_context_ready() < 0)
return NULL;
pulseaudio_lock();
pa_proplist *p = pulseaudio_properties();
pa_stream *s = pa_stream_new_with_proplist(
pulseaudio_context, name, ss, map, p);
pa_proplist_free(p);
pulseaudio_unlock();
return s;
}
int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name,
const pa_buffer_attr *attr, pa_stream_flags_t flags)
{
if (pulseaudio_context_ready() < 0)
return -1;
size_t dev_len = strlen(name) - 8;
char device[dev_len];
memcpy(device, name, dev_len);
device[dev_len] = '\0';
pulseaudio_lock();
int_fast32_t ret = pa_stream_connect_playback(s, device, attr, flags,
NULL, NULL);
pulseaudio_unlock();
return ret;
}
void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb,
void *userdata)
{
if (pulseaudio_context_ready() < 0)
return;
pulseaudio_lock();
pa_stream_set_write_callback(p, cb, userdata);
pulseaudio_unlock();
}
void pulseaudio_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb,
void *userdata)
{
if (pulseaudio_context_ready() < 0)
return;
pulseaudio_lock();
pa_stream_set_underflow_callback(p, cb, userdata);
pulseaudio_unlock();
}

View file

@ -0,0 +1,185 @@
/*
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
Copyright (C) 2017 by Fabio Madia <admshao@gmail.com>
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, see <http://www.gnu.org/licenses/>.
*/
#include <inttypes.h>
#include <pulse/stream.h>
#include <pulse/context.h>
#include <pulse/introspect.h>
#pragma once
struct pulseaudio_default_output {
char *default_sink_name;
};
struct enum_cb {
obs_enum_audio_device_cb cb;
void *data;
int cont;
};
void get_default_id(char **id);
bool devices_match(const char *id1, const char *id2);
/**
* Initialize the pulseaudio mainloop and increase the reference count
*/
int_fast32_t pulseaudio_init();
/**
* Unreference the pulseaudio mainloop, when the reference count reaches
* zero the mainloop will automatically be destroyed
*/
void pulseaudio_unref();
/**
* Lock the mainloop
*
* In order to allow for multiple threads to use the same mainloop pulseaudio
* provides it's own locking mechanism. This function should be called before
* using any pulseaudio function that is in any way related to the mainloop or
* context.
*
* @note use of this function may cause deadlocks
*
* @warning do not use with pulseaudio_ wrapper functions
*/
void pulseaudio_lock();
/**
* Unlock the mainloop
*
* @see pulseaudio_lock()
*/
void pulseaudio_unlock();
/**
* Wait for events to happen
*
* This function should be called when waiting for an event to happen.
*/
void pulseaudio_wait();
/**
* Wait for accept signal from calling thread
*
* This function tells the pulseaudio mainloop wheter the data provided to
* the callback should be retained until the calling thread executes
* pulseaudio_accept()
*
* If wait_for_accept is 0 the function returns and the data is freed.
*/
void pulseaudio_signal(int wait_for_accept);
/**
* Signal the waiting callback to return
*
* This function is used in conjunction with pulseaudio_signal()
*/
void pulseaudio_accept();
/**
* Request source information
*
* The function will block until the operation was executed and the mainloop
* called the provided callback function.
*
* @return negative on error
*
* @note The function will block until the server context is ready.
*
* @warning call without active locks
*/
int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb,
void *userdata);
/**
* Request source information from a specific source
*
* The function will block until the operation was executed and the mainloop
* called the provided callback function.
*
* @param cb pointer to the callback function
* @param name the source name to get information for
* @param userdata pointer to userdata the callback will be called with
*
* @return negative on error
*
* @note The function will block until the server context is ready.
*
* @warning call without active locks
*/
int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb,
const char *name, void *userdata);
/**
* Request server information
*
* The function will block until the operation was executed and the mainloop
* called the provided callback function.
*
* @return negative on error
*
* @note The function will block until the server context is ready.
*
* @warning call without active locks
*/
int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata);
/**
* Create a new stream with the default properties
*
* @note The function will block until the server context is ready.
*
* @warning call without active locks
*/
pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss,
const pa_channel_map *map);
/**
* Connect to a pulseaudio playback stream
*
* @param s pa_stream to connect to. NULL for default
* @param attr pa_buffer_attr
* @param name Device name. NULL for default device
* @param flags pa_stream_flags_t
* @return negative on error
*/
int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name,
const pa_buffer_attr *attr, pa_stream_flags_t flags);
/**
* Sets a callback function for when data can be written to the stream
*
* @param p pa_stream to connect to. NULL for default
* @param cb pa_stream_request_cb_t
* @param userdata pointer to userdata the callback will be called with
*/
void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb,
void *userdata);
/**
* Sets a callback function for when an underflow happen
*
* @param p pa_stream to connect to. NULL for default
* @param cb pa_stream_notify_cb_t
* @param userdata pointer to userdata the callback will be called with
*/
void pulseaudio_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb,
void *userdata);

View file

@ -165,7 +165,10 @@ static void on_audio_playback(void *param, obs_source_t *source,
UINT32 pad = 0;
monitor->client->lpVtbl->GetCurrentPadding(monitor->client, &pad);
if (monitor->source_has_video) {
bool decouple_audio =
source->async_unbuffered && source->async_decoupled;
if (monitor->source_has_video && !decouple_audio) {
uint64_t ts = audio_data->timestamp - ts_offset;
if (!process_audio_delay(monitor, (float**)(&resample_data[0]),
@ -226,14 +229,11 @@ static inline void audio_monitor_free(struct audio_monitor *monitor)
static enum speaker_layout convert_speaker_layout(DWORD layout, WORD channels)
{
switch (layout) {
case KSAUDIO_SPEAKER_QUAD: return SPEAKERS_QUAD;
case KSAUDIO_SPEAKER_2POINT1: return SPEAKERS_2POINT1;
case KSAUDIO_SPEAKER_SURROUND: return SPEAKERS_4POINT0;
case KSAUDIO_SPEAKER_4POINT1: return SPEAKERS_4POINT1;
case KSAUDIO_SPEAKER_SURROUND: return SPEAKERS_SURROUND;
case KSAUDIO_SPEAKER_5POINT1: return SPEAKERS_5POINT1;
case KSAUDIO_SPEAKER_5POINT1_SURROUND: return SPEAKERS_5POINT1_SURROUND;
case KSAUDIO_SPEAKER_7POINT1: return SPEAKERS_7POINT1;
case KSAUDIO_SPEAKER_7POINT1_SURROUND: return SPEAKERS_7POINT1_SURROUND;
}
return (enum speaker_layout)channels;

View file

@ -2,8 +2,11 @@
#include <mmdeviceapi.h>
#include <audioclient.h>
#define KSAUDIO_SPEAKER_4POINT1 (KSAUDIO_SPEAKER_QUAD|SPEAKER_LOW_FREQUENCY)
#define KSAUDIO_SPEAKER_2POINT1 (KSAUDIO_SPEAKER_STEREO|SPEAKER_LOW_FREQUENCY)
#define KSAUDIO_SPEAKER_SURROUND_AVUTIL \
(KSAUDIO_SPEAKER_STEREO|SPEAKER_FRONT_CENTER)
#define KSAUDIO_SPEAKER_4POINT1 (KSAUDIO_SPEAKER_SURROUND|SPEAKER_LOW_FREQUENCY)
#define safe_release(ptr) \
do { \

View file

@ -88,6 +88,17 @@ static inline void calldata_clear(struct calldata *data)
}
}
static inline calldata_t *calldata_create(void)
{
return (calldata_t*)bzalloc(sizeof(struct calldata));
}
static inline void calldata_destroy(calldata_t *cd)
{
calldata_free(cd);
bfree(cd);
}
/* ------------------------------------------------------------------------- */
/* NOTE: 'get' functions return true only if parameter exists, and is the
* same type. They return false otherwise. */

View file

@ -86,9 +86,19 @@ static inline size_t signal_get_callback_idx(struct signal_info *si,
return DARRAY_INVALID;
}
struct global_callback_info {
global_signal_callback_t callback;
void *data;
long signaling;
bool remove;
};
struct signal_handler {
struct signal_info *first;
pthread_mutex_t mutex;
DARRAY(struct global_callback_info) global_callbacks;
pthread_mutex_t global_callbacks_mutex;
};
static struct signal_info *getsignal(signal_handler_t *handler,
@ -114,11 +124,24 @@ static struct signal_info *getsignal(signal_handler_t *handler,
signal_handler_t *signal_handler_create(void)
{
struct signal_handler *handler = bmalloc(sizeof(struct signal_handler));
struct signal_handler *handler = bzalloc(sizeof(struct signal_handler));
handler->first = NULL;
pthread_mutexattr_t attr;
if (pthread_mutexattr_init(&attr) != 0)
return NULL;
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
return NULL;
if (pthread_mutex_init(&handler->mutex, NULL) != 0) {
blog(LOG_ERROR, "Couldn't create signal handler!");
blog(LOG_ERROR, "Couldn't create signal handler mutex!");
bfree(handler);
return NULL;
}
if (pthread_mutex_init(&handler->global_callbacks_mutex, &attr) != 0) {
blog(LOG_ERROR, "Couldn't create signal handler global "
"callbacks mutex!");
pthread_mutex_destroy(&handler->mutex);
bfree(handler);
return NULL;
}
@ -136,6 +159,8 @@ void signal_handler_destroy(signal_handler_t *handler)
sig = next;
}
da_free(handler->global_callbacks);
pthread_mutex_destroy(&handler->global_callbacks_mutex);
pthread_mutex_destroy(&handler->mutex);
bfree(handler);
}
@ -199,7 +224,7 @@ void signal_handler_connect(signal_handler_t *handler, const char *signal,
idx = signal_get_callback_idx(sig, callback, data);
if (idx == DARRAY_INVALID)
da_push_back(sig->callbacks, &cb_data);
pthread_mutex_unlock(&sig->mutex);
}
@ -236,10 +261,21 @@ void signal_handler_disconnect(signal_handler_t *handler, const char *signal,
else
da_erase(sig->callbacks, idx);
}
pthread_mutex_unlock(&sig->mutex);
}
static THREAD_LOCAL struct signal_callback *current_signal_cb = NULL;
static THREAD_LOCAL struct global_callback_info *current_global_cb = NULL;
void signal_handler_remove_current(void)
{
if (current_signal_cb)
current_signal_cb->remove = true;
else if (current_global_cb)
current_global_cb->remove = true;
}
void signal_handler_signal(signal_handler_t *handler, const char *signal,
calldata_t *params)
{
@ -253,8 +289,11 @@ void signal_handler_signal(signal_handler_t *handler, const char *signal,
for (size_t i = 0; i < sig->callbacks.num; i++) {
struct signal_callback *cb = sig->callbacks.array+i;
if (!cb->remove)
if (!cb->remove) {
current_signal_cb = cb;
cb->callback(cb->data, params);
current_signal_cb = NULL;
}
}
for (size_t i = sig->callbacks.num; i > 0; i--) {
@ -265,4 +304,74 @@ void signal_handler_signal(signal_handler_t *handler, const char *signal,
sig->signalling = false;
pthread_mutex_unlock(&sig->mutex);
pthread_mutex_lock(&handler->global_callbacks_mutex);
if (handler->global_callbacks.num) {
for (size_t i = 0; i < handler->global_callbacks.num; i++) {
struct global_callback_info *cb =
handler->global_callbacks.array + i;
if (!cb->remove) {
cb->signaling++;
current_global_cb = cb;
cb->callback(cb->data, signal, params);
current_global_cb = NULL;
cb->signaling--;
}
}
for (size_t i = handler->global_callbacks.num; i > 0; i--) {
struct global_callback_info *cb =
handler->global_callbacks.array + (i - 1);
if (cb->remove && !cb->signaling)
da_erase(handler->global_callbacks, i - 1);
}
}
pthread_mutex_unlock(&handler->global_callbacks_mutex);
}
void signal_handler_connect_global(signal_handler_t *handler,
global_signal_callback_t callback, void *data)
{
struct global_callback_info cb_data = {callback, data, 0, false};
size_t idx;
if (!handler || !callback)
return;
pthread_mutex_lock(&handler->global_callbacks_mutex);
idx = da_find(handler->global_callbacks, &cb_data, 0);
if (idx == DARRAY_INVALID)
da_push_back(handler->global_callbacks, &cb_data);
pthread_mutex_unlock(&handler->global_callbacks_mutex);
}
void signal_handler_disconnect_global(signal_handler_t *handler,
global_signal_callback_t callback, void *data)
{
struct global_callback_info cb_data = {callback, data, false};
size_t idx;
if (!handler || !callback)
return;
pthread_mutex_lock(&handler->global_callbacks_mutex);
idx = da_find(handler->global_callbacks, &cb_data, 0);
if (idx != DARRAY_INVALID) {
struct global_callback_info *cb =
handler->global_callbacks.array + idx;
if (cb->signaling)
cb->remove = true;
else
da_erase(handler->global_callbacks, idx);
}
pthread_mutex_unlock(&handler->global_callbacks_mutex);
}

View file

@ -33,6 +33,7 @@ extern "C" {
struct signal_handler;
typedef struct signal_handler signal_handler_t;
typedef void (*global_signal_callback_t)(void*, const char*, calldata_t*);
typedef void (*signal_callback_t)(void*, calldata_t*);
EXPORT signal_handler_t *signal_handler_create(void);
@ -60,6 +61,13 @@ EXPORT void signal_handler_connect(signal_handler_t *handler,
EXPORT void signal_handler_disconnect(signal_handler_t *handler,
const char *signal, signal_callback_t callback, void *data);
EXPORT void signal_handler_connect_global(signal_handler_t *handler,
global_signal_callback_t callback, void *data);
EXPORT void signal_handler_disconnect_global(signal_handler_t *handler,
global_signal_callback_t callback, void *data);
EXPORT void signal_handler_remove_current(void);
EXPORT void signal_handler_signal(signal_handler_t *handler, const char *signal,
calldata_t *params);

View file

@ -172,10 +172,25 @@ float4 PSPlanar420(VertInOut vert_in) : TARGET
ch_u += width_i;
ch_v += height_i;
/* set up coordinates for next chroma line, in case
* (width / 2) % 4 == 2, i.e. the current set of 4 pixels is split
* between the current and the next chroma line; do note that the next
* chroma line is two source lines below the current source line */
float ch_u_n = 0. + width_i;
float ch_v_n = ch_v + height_i * 3;
sample_pos[0] = float2(ch_u, ch_v);
sample_pos[1] = float2(ch_u += width_i2, ch_v);
sample_pos[2] = float2(ch_u += width_i2, ch_v);
sample_pos[3] = float2(ch_u + width_i2, ch_v);
ch_u += width_i2;
// check if ch_u overflowed the current source and chroma line
if (ch_u > 1.0) {
sample_pos[2] = float2(ch_u_n, ch_v_n);
sample_pos[2] = float2(ch_u_n + width_i2, ch_v_n);
} else {
sample_pos[2] = float2(ch_u, ch_v);
sample_pos[3] = float2(ch_u + width_i2, ch_v);
}
}
float4x4 out_val = float4x4(

View file

@ -368,6 +368,13 @@ void gs_effect_set_vec4(gs_eparam_t *param, const struct vec4 *val)
effect_setval_inline(param, val, sizeof(struct vec4));
}
void gs_effect_set_color(gs_eparam_t *param, uint32_t argb)
{
struct vec4 v_color;
vec4_from_bgra(&v_color, argb);
effect_setval_inline(param, &v_color, sizeof(struct vec4));
}
void gs_effect_set_texture(gs_eparam_t *param, gs_texture_t *val)
{
effect_setval_inline(param, &val, sizeof(gs_texture_t*));

View file

@ -164,8 +164,19 @@ static bool ffmpeg_image_decode(struct ffmpeg_image *info, uint8_t *out,
}
while (!got_frame) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101)
ret = avcodec_send_packet(info->decoder_ctx, &packet);
if (ret == 0)
ret = avcodec_receive_frame(info->decoder_ctx, frame);
got_frame = (ret == 0);
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
ret = 0;
#else
ret = avcodec_decode_video2(info->decoder_ctx, frame,
&got_frame, &packet);
#endif
if (ret < 0) {
blog(LOG_WARNING, "Failed to decode frame for '%s': %s",
info->file, av_err2str(ret));
@ -176,7 +187,7 @@ static bool ffmpeg_image_decode(struct ffmpeg_image *info, uint8_t *out,
success = ffmpeg_image_reformat_frame(info, frame, out, linesize);
fail:
av_free_packet(&packet);
av_packet_unref(&packet);
av_frame_free(&frame);
return success;
}

View file

@ -141,10 +141,12 @@ bool load_graphics_imports(struct gs_exports *exports, void *module,
GRAPHICS_IMPORT(gs_vertexbuffer_destroy);
GRAPHICS_IMPORT(gs_vertexbuffer_flush);
GRAPHICS_IMPORT(gs_vertexbuffer_flush_direct);
GRAPHICS_IMPORT(gs_vertexbuffer_get_data);
GRAPHICS_IMPORT(gs_indexbuffer_destroy);
GRAPHICS_IMPORT(gs_indexbuffer_flush);
GRAPHICS_IMPORT(gs_indexbuffer_flush_direct);
GRAPHICS_IMPORT(gs_indexbuffer_get_data);
GRAPHICS_IMPORT(gs_indexbuffer_get_num_indices);
GRAPHICS_IMPORT(gs_indexbuffer_get_type);

View file

@ -189,11 +189,15 @@ struct gs_exports {
void (*gs_vertexbuffer_destroy)(gs_vertbuffer_t *vertbuffer);
void (*gs_vertexbuffer_flush)(gs_vertbuffer_t *vertbuffer);
void (*gs_vertexbuffer_flush_direct)(gs_vertbuffer_t *vertbuffer,
const struct gs_vb_data *data);
struct gs_vb_data *(*gs_vertexbuffer_get_data)(
const gs_vertbuffer_t *vertbuffer);
void (*gs_indexbuffer_destroy)(gs_indexbuffer_t *indexbuffer);
void (*gs_indexbuffer_flush)(gs_indexbuffer_t *indexbuffer);
void (*gs_indexbuffer_flush_direct)(gs_indexbuffer_t *indexbuffer,
const void *data);
void *(*gs_indexbuffer_get_data)(const gs_indexbuffer_t *indexbuffer);
size_t (*gs_indexbuffer_get_num_indices)(
const gs_indexbuffer_t *indexbuffer);

View file

@ -1,8 +1,14 @@
#include "graphics.h"
#include "obsconfig.h"
#define MAGICKCORE_QUANTUM_DEPTH 16
#define MAGICKCORE_HDRI_ENABLE 0
#if LIBOBS_IMAGEMAGICK_DIR_STYLE == LIBOBS_IMAGEMAGICK_DIR_STYLE_6L
#include <magick/MagickCore.h>
#elif LIBOBS_IMAGEMAGICK_DIR_STYLE == LIBOBS_IMAGEMAGICK_DIR_STYLE_7GE
#include <MagickCore/MagickCore.h>
#endif
void gs_init_image_deps()
{

View file

@ -28,11 +28,7 @@
#include "effect-parser.h"
#include "effect.h"
#ifdef _MSC_VER
static __declspec(thread) graphics_t *thread_graphics = NULL;
#else /* assume GCC or that other compiler we dare not mention */
static __thread graphics_t *thread_graphics = NULL;
#endif
static THREAD_LOCAL graphics_t *thread_graphics = NULL;
static inline bool gs_obj_valid(const void *obj, const char *f,
const char *name)
@ -1468,6 +1464,46 @@ gs_vertbuffer_t *gs_vertexbuffer_create(struct gs_vb_data *data,
if (!gs_valid("gs_vertexbuffer_create"))
return NULL;
if (data && data->num && (flags & GS_DUP_BUFFER) != 0) {
struct gs_vb_data *new_data = gs_vbdata_create();
new_data->num = data->num;
#define DUP_VAL(val) \
do { \
if (data->val) \
new_data->val = bmemdup(data->val, \
sizeof(*data->val) * \
data->num); \
} while (false)
DUP_VAL(points);
DUP_VAL(normals);
DUP_VAL(tangents);
DUP_VAL(colors);
#undef DUP_VAL
if (data->tvarray && data->num_tex) {
new_data->num_tex = data->num_tex;
new_data->tvarray = bzalloc(
sizeof(struct gs_tvertarray) *
data->num_tex);
for (size_t i = 0; i < data->num_tex; i++) {
struct gs_tvertarray *tv = &data->tvarray[i];
struct gs_tvertarray *new_tv =
&new_data->tvarray[i];
size_t size = tv->width * sizeof(float);
new_tv->width = tv->width;
new_tv->array = bmemdup(tv->array,
size * data->num);
}
}
data = new_data;
}
return graphics->exports.device_vertexbuffer_create(graphics->device,
data, flags);
}
@ -1480,6 +1516,13 @@ gs_indexbuffer_t *gs_indexbuffer_create(enum gs_index_type type,
if (!gs_valid("gs_indexbuffer_create"))
return NULL;
if (indices && num && (flags & GS_DUP_BUFFER) != 0) {
size_t size = type == GS_UNSIGNED_SHORT
? sizeof(unsigned short)
: sizeof(unsigned long);
indices = bmemdup(indices, size * num);
}
return graphics->exports.device_indexbuffer_create(graphics->device,
type, indices, num, flags);
}
@ -2430,6 +2473,16 @@ void gs_vertexbuffer_flush(gs_vertbuffer_t *vertbuffer)
thread_graphics->exports.gs_vertexbuffer_flush(vertbuffer);
}
void gs_vertexbuffer_flush_direct(gs_vertbuffer_t *vertbuffer,
const struct gs_vb_data *data)
{
if (!gs_valid_p2("gs_vertexbuffer_flush_direct", vertbuffer, data))
return;
thread_graphics->exports.gs_vertexbuffer_flush_direct(vertbuffer,
data);
}
struct gs_vb_data *gs_vertexbuffer_get_data(const gs_vertbuffer_t *vertbuffer)
{
if (!gs_valid_p("gs_vertexbuffer_get_data", vertbuffer))
@ -2458,6 +2511,15 @@ void gs_indexbuffer_flush(gs_indexbuffer_t *indexbuffer)
thread_graphics->exports.gs_indexbuffer_flush(indexbuffer);
}
void gs_indexbuffer_flush_direct(gs_indexbuffer_t *indexbuffer,
const void *data)
{
if (!gs_valid_p2("gs_indexbuffer_flush_direct", indexbuffer, data))
return;
thread_graphics->exports.gs_indexbuffer_flush_direct(indexbuffer, data);
}
void *gs_indexbuffer_get_data(const gs_indexbuffer_t *indexbuffer)
{
if (!gs_valid_p("gs_indexbuffer_get_data", indexbuffer))

View file

@ -291,6 +291,7 @@ enum gs_shader_param_type {
GS_SHADER_PARAM_TEXTURE,
};
#ifndef SWIG
struct gs_shader_param_info {
enum gs_shader_param_type type;
const char *name;
@ -327,6 +328,7 @@ EXPORT void gs_shader_set_val(gs_sparam_t *param, const void *val, size_t size);
EXPORT void gs_shader_set_default(gs_sparam_t *param);
EXPORT void gs_shader_set_next_sampler(gs_sparam_t *param,
gs_samplerstate_t *sampler);
#endif
/* ---------------------------------------------------
* effect functions
@ -340,6 +342,7 @@ EXPORT void gs_shader_set_next_sampler(gs_sparam_t *param,
GS_EFFECT_TEXTURE
};*/
#ifndef SWIG
struct gs_effect_param_info {
const char *name;
enum gs_shader_param_type type;
@ -349,6 +352,7 @@ struct gs_effect_param_info {
float min, max, inc, mul; */
};
#endif
EXPORT void gs_effect_destroy(gs_effect_t *effect);
@ -382,8 +386,11 @@ EXPORT void gs_effect_update_params(gs_effect_t *effect);
EXPORT gs_eparam_t *gs_effect_get_viewproj_matrix(const gs_effect_t *effect);
EXPORT gs_eparam_t *gs_effect_get_world_matrix(const gs_effect_t *effect);
#ifndef SWIG
EXPORT void gs_effect_get_param_info(const gs_eparam_t *param,
struct gs_effect_param_info *info);
#endif
EXPORT void gs_effect_set_bool(gs_eparam_t *param, bool val);
EXPORT void gs_effect_set_float(gs_eparam_t *param, float val);
EXPORT void gs_effect_set_int(gs_eparam_t *param, int val);
@ -398,6 +405,8 @@ EXPORT void gs_effect_set_default(gs_eparam_t *param);
EXPORT void gs_effect_set_next_sampler(gs_eparam_t *param,
gs_samplerstate_t *sampler);
EXPORT void gs_effect_set_color(gs_eparam_t *param, uint32_t argb);
/* ---------------------------------------------------
* texture render helper functions
* --------------------------------------------------- */
@ -419,6 +428,8 @@ EXPORT gs_texture_t *gs_texrender_get_texture(const gs_texrender_t *texrender);
#define GS_DYNAMIC (1<<1)
#define GS_RENDER_TARGET (1<<2)
#define GS_GL_DUMMYTEX (1<<3) /**<< texture with no allocated texture data */
#define GS_DUP_BUFFER (1<<4) /**<< do not pass buffer ownership when
* creating a vertex/index buffer */
/* ---------------- */
/* global functions */
@ -716,11 +727,15 @@ EXPORT void gs_samplerstate_destroy(gs_samplerstate_t *samplerstate);
EXPORT void gs_vertexbuffer_destroy(gs_vertbuffer_t *vertbuffer);
EXPORT void gs_vertexbuffer_flush(gs_vertbuffer_t *vertbuffer);
EXPORT void gs_vertexbuffer_flush_direct(gs_vertbuffer_t *vertbuffer,
const struct gs_vb_data *data);
EXPORT struct gs_vb_data *gs_vertexbuffer_get_data(
const gs_vertbuffer_t *vertbuffer);
EXPORT void gs_indexbuffer_destroy(gs_indexbuffer_t *indexbuffer);
EXPORT void gs_indexbuffer_flush(gs_indexbuffer_t *indexbuffer);
EXPORT void gs_indexbuffer_flush_direct(gs_indexbuffer_t *indexbuffer,
const void *data);
EXPORT void *gs_indexbuffer_get_data(const gs_indexbuffer_t *indexbuffer);
EXPORT size_t gs_indexbuffer_get_num_indices(
const gs_indexbuffer_t *indexbuffer);

View file

@ -64,7 +64,7 @@ static bool init_animated_gif(gs_image_file_t *image, const char *path)
bool is_animated_gif = true;
gif_result result;
uint64_t max_size;
size_t size;
size_t size, size_read;
FILE *file;
image->bitmap_callbacks.bitmap_create = bi_def_bitmap_create;
@ -87,7 +87,11 @@ static bool init_animated_gif(gs_image_file_t *image, const char *path)
fseek(file, 0, SEEK_SET);
image->gif_data = bmalloc(size);
fread(image->gif_data, 1, size, file);
size_read = fread(image->gif_data, 1, size, file);
if (size_read != size) {
blog(LOG_WARNING, "Failed to fully read gif file '%s'.", path);
goto fail;
}
do {
result = gif_initialise(&image->gif, size, image->gif_data);

View file

@ -26,9 +26,13 @@ extern "C" {
#endif
#define MAX_AUDIO_MIXES 6
#define MAX_AUDIO_CHANNELS 2
#define MAX_AUDIO_CHANNELS 8
#define AUDIO_OUTPUT_FRAMES 1024
#define TOTAL_AUDIO_SIZE \
(MAX_AUDIO_MIXES * MAX_AUDIO_CHANNELS * \
AUDIO_OUTPUT_FRAMES * sizeof(float))
/*
* Base audio output component. Use this to create an audio output track
* for the media.
@ -51,18 +55,24 @@ enum audio_format {
AUDIO_FORMAT_FLOAT_PLANAR,
};
/**
* The speaker layout describes where the speakers are located in the room.
* For OBS it dictates:
* * how many channels are available and
* * which channels are used for which speakers.
*
* Standard channel layouts where retrieved from ffmpeg documentation at:
* https://trac.ffmpeg.org/wiki/AudioChannelManipulation
*/
enum speaker_layout {
SPEAKERS_UNKNOWN,
SPEAKERS_MONO,
SPEAKERS_STEREO,
SPEAKERS_2POINT1,
SPEAKERS_QUAD,
SPEAKERS_4POINT1,
SPEAKERS_5POINT1,
SPEAKERS_5POINT1_SURROUND,
SPEAKERS_7POINT1,
SPEAKERS_7POINT1_SURROUND,
SPEAKERS_SURROUND,
SPEAKERS_UNKNOWN, /**< Unknown setting, fallback is stereo. */
SPEAKERS_MONO, /**< Channels: MONO */
SPEAKERS_STEREO, /**< Channels: FL, FR */
SPEAKERS_2POINT1, /**< Channels: FL, FR, LFE */
SPEAKERS_4POINT0, /**< Channels: FL, FR, FC, RC */
SPEAKERS_4POINT1, /**< Channels: FL, FR, FC, LFE, RC */
SPEAKERS_5POINT1, /**< Channels: FL, FR, FC, LFE, RL, RR */
SPEAKERS_7POINT1=8, /**< Channels: FL, FR, FC, LFE, RL, RR, SL, SR */
};
struct audio_data {
@ -102,13 +112,10 @@ static inline uint32_t get_audio_channels(enum speaker_layout speakers)
case SPEAKERS_MONO: return 1;
case SPEAKERS_STEREO: return 2;
case SPEAKERS_2POINT1: return 3;
case SPEAKERS_SURROUND:
case SPEAKERS_QUAD: return 4;
case SPEAKERS_4POINT0: return 4;
case SPEAKERS_4POINT1: return 5;
case SPEAKERS_5POINT1:
case SPEAKERS_5POINT1_SURROUND: return 6;
case SPEAKERS_5POINT1: return 6;
case SPEAKERS_7POINT1: return 8;
case SPEAKERS_7POINT1_SURROUND: return 8;
case SPEAKERS_UNKNOWN: return 0;
}

View file

@ -63,14 +63,11 @@ static inline uint64_t convert_speaker_layout(enum speaker_layout layout)
case SPEAKERS_UNKNOWN: return 0;
case SPEAKERS_MONO: return AV_CH_LAYOUT_MONO;
case SPEAKERS_STEREO: return AV_CH_LAYOUT_STEREO;
case SPEAKERS_2POINT1: return AV_CH_LAYOUT_2_1;
case SPEAKERS_QUAD: return AV_CH_LAYOUT_QUAD;
case SPEAKERS_2POINT1: return AV_CH_LAYOUT_SURROUND;
case SPEAKERS_4POINT0: return AV_CH_LAYOUT_4POINT0;
case SPEAKERS_4POINT1: return AV_CH_LAYOUT_4POINT1;
case SPEAKERS_5POINT1: return AV_CH_LAYOUT_5POINT1;
case SPEAKERS_5POINT1_SURROUND: return AV_CH_LAYOUT_5POINT1_BACK;
case SPEAKERS_5POINT1: return AV_CH_LAYOUT_5POINT1_BACK;
case SPEAKERS_7POINT1: return AV_CH_LAYOUT_7POINT1;
case SPEAKERS_7POINT1_SURROUND: return AV_CH_LAYOUT_7POINT1_WIDE_BACK;
case SPEAKERS_SURROUND: return AV_CH_LAYOUT_SURROUND;
}
/* shouldn't get here */

View file

@ -208,7 +208,7 @@ void decompress_420(
uint8_t *output, uint32_t out_linesize)
{
uint32_t start_y_d2 = start_y/2;
uint32_t width_d2 = min_uint32(in_linesize[0], out_linesize)/2;
uint32_t width_d2 = in_linesize[0]/2;
uint32_t height_d2 = end_y/2;
uint32_t y;
@ -221,18 +221,18 @@ void decompress_420(
lum0 = input[0] + y * 2 * in_linesize[0];
lum1 = lum0 + in_linesize[0];
output0 = (uint32_t*)(output + y * 2 * in_linesize[0]);
output1 = (uint32_t*)((uint8_t*)output0 + in_linesize[0]);
output0 = (uint32_t*)(output + y * 2 * out_linesize);
output1 = (uint32_t*)((uint8_t*)output0 + out_linesize);
for (x = 0; x < width_d2; x++) {
uint32_t out;
out = (*(chroma0++) << 8) | (*(chroma1++) << 16);
out = (*(chroma0++) << 8) | *(chroma1++);
*(output0++) = *(lum0++) | out;
*(output0++) = *(lum0++) | out;
*(output0++) = (*(lum0++) << 16) | out;
*(output0++) = (*(lum0++) << 16) | out;
*(output1++) = *(lum1++) | out;
*(output1++) = *(lum1++) | out;
*(output1++) = (*(lum1++) << 16) | out;
*(output1++) = (*(lum1++) << 16) | out;
}
}
}

View file

@ -18,7 +18,3 @@
#pragma once
#define MAX_AV_PLANES 8
/* time threshold in nanoseconds to ensure audio timing is as seamless as
* possible */
#define TS_SMOOTHING_THRESHOLD 70000000ULL

View file

@ -26,6 +26,12 @@
#include <sys/types.h>
#include <sys/stat.h>
#if LIBAVCODEC_VERSION_MAJOR >= 58
#define CODEC_FLAG_GLOBAL_H AV_CODEC_FLAG_GLOBAL_HEADER
#else
#define CODEC_FLAG_GLOBAL_H CODEC_FLAG_GLOBAL_HEADER
#endif
struct media_remux_job {
int64_t in_size;
AVFormatContext *ifmt_ctx, *ofmt_ctx;
@ -86,7 +92,17 @@ static inline bool init_output(media_remux_job_t job, const char *out_filename)
return false;
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
AVCodecParameters *par = avcodec_parameters_alloc();
ret = avcodec_parameters_from_context(par, in_stream->codec);
if (ret == 0)
ret = avcodec_parameters_to_context(out_stream->codec,
par);
avcodec_parameters_free(&par);
#else
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
#endif
if (ret < 0) {
blog(LOG_ERROR, "media_remux: Failed to copy context");
return false;
@ -95,7 +111,7 @@ static inline bool init_output(media_remux_job_t job, const char *out_filename)
out_stream->codec->codec_tag = 0;
if (job->ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_H;
}
#ifndef _NDEBUG
@ -188,7 +204,7 @@ static inline int process_packets(media_remux_job_t job,
job->ofmt_ctx->streams[pkt.stream_index]);
ret = av_interleaved_write_frame(job->ofmt_ctx, &pkt);
av_free_packet(&pkt);
av_packet_unref(&pkt);
if (ret < 0) {
blog(LOG_ERROR, "media_remux: Error muxing packet: %s",

View file

@ -302,6 +302,8 @@ static inline bool video_input_init(struct video_input *input,
.format = video->info.format,
.width = video->info.width,
.height = video->info.height,
.range = video->info.range,
.colorspace = video->info.colorspace
};
int ret = video_scaler_create(&input->scaler,

View file

@ -32,6 +32,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma warning(disable : 4756)
#endif
#define CLAMP(x, min, max) ((x) < min ? min : ((x) > max ? max : (x)))
typedef float (*obs_fader_conversion_t)(const float val);
struct fader_cb {
@ -61,8 +63,6 @@ struct meter_cb {
struct obs_volmeter {
pthread_mutex_t mutex;
obs_fader_conversion_t pos_to_db;
obs_fader_conversion_t db_to_pos;
obs_source_t *source;
enum obs_fader_type type;
float cur_db;
@ -70,20 +70,10 @@ struct obs_volmeter {
pthread_mutex_t callback_mutex;
DARRAY(struct meter_cb)callbacks;
unsigned int channels;
unsigned int update_ms;
unsigned int update_frames;
unsigned int peakhold_ms;
unsigned int peakhold_frames;
unsigned int peakhold_count;
unsigned int ival_frames;
float ival_sum;
float ival_max;
float vol_peak;
float vol_mag;
float vol_max;
float vol_magnitude[MAX_AUDIO_CHANNELS];
float vol_peak[MAX_AUDIO_CHANNELS];
};
static float cubic_def_to_db(const float def)
@ -205,13 +195,14 @@ static void signal_volume_changed(struct obs_fader *fader, const float db)
}
static void signal_levels_updated(struct obs_volmeter *volmeter,
const float level, const float magnitude, const float peak,
bool muted)
const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS],
const float input_peak[MAX_AUDIO_CHANNELS])
{
pthread_mutex_lock(&volmeter->callback_mutex);
for (size_t i = volmeter->callbacks.num; i > 0; i--) {
struct meter_cb cb = volmeter->callbacks.array[i - 1];
cb.callback(cb.param, level, magnitude, peak, muted);
cb.callback(cb.param, magnitude, peak, input_peak);
}
pthread_mutex_unlock(&volmeter->callback_mutex);
}
@ -265,145 +256,84 @@ static void volmeter_source_destroyed(void *vptr, calldata_t *calldata)
obs_volmeter_detach_source(volmeter);
}
/* TODO: Separate for individual channels */
static void volmeter_sum_and_max(float *data[MAX_AV_PLANES], size_t frames,
float *sum, float *max)
{
float s = *sum;
float m = *max;
for (size_t plane = 0; plane < MAX_AV_PLANES; plane++) {
if (!data[plane])
break;
for (float *c = data[plane]; c < data[plane] + frames; ++c) {
const float pow = *c * *c;
s += pow;
m = (m > pow) ? m : pow;
}
}
*sum = s;
*max = m;
}
/**
* @todo The IIR low pass filter has a different behavior depending on the
* update interval and sample rate, it should be replaced with something
* that is independent from both.
*/
static void volmeter_calc_ival_levels(obs_volmeter_t *volmeter)
{
const unsigned int samples = volmeter->ival_frames * volmeter->channels;
const float alpha = 0.15f;
const float ival_max = sqrtf(volmeter->ival_max);
const float ival_rms = sqrtf(volmeter->ival_sum / (float)samples);
if (ival_max > volmeter->vol_max) {
volmeter->vol_max = ival_max;
} else {
volmeter->vol_max = alpha * volmeter->vol_max +
(1.0f - alpha) * ival_max;
}
if (volmeter->vol_max > volmeter->vol_peak ||
volmeter->peakhold_count > volmeter->peakhold_frames) {
volmeter->vol_peak = volmeter->vol_max;
volmeter->peakhold_count = 0;
} else {
volmeter->peakhold_count += volmeter->ival_frames;
}
volmeter->vol_mag = alpha * ival_rms +
volmeter->vol_mag * (1.0f - alpha);
/* reset interval data */
volmeter->ival_frames = 0;
volmeter->ival_sum = 0.0f;
volmeter->ival_max = 0.0f;
}
static bool volmeter_process_audio_data(obs_volmeter_t *volmeter,
static void volmeter_process_audio_data(obs_volmeter_t *volmeter,
const struct audio_data *data)
{
bool updated = false;
size_t frames = 0;
size_t left = data->frames;
float *adata[MAX_AV_PLANES];
int nr_samples = data->frames;
int channel_nr = 0;
for (size_t i = 0; i < MAX_AV_PLANES; i++)
adata[i] = (float*)data->data[i];
while (left) {
frames = (volmeter->ival_frames + left >
volmeter->update_frames)
? volmeter->update_frames - volmeter->ival_frames
: left;
volmeter_sum_and_max(adata, frames, &volmeter->ival_sum,
&volmeter->ival_max);
volmeter->ival_frames += (unsigned int)frames;
left -= frames;
for (size_t i = 0; i < MAX_AV_PLANES; i++) {
if (!adata[i])
break;
adata[i] += frames;
for (size_t plane_nr = 0; plane_nr < MAX_AV_PLANES; plane_nr++) {
float *samples = (float *)data->data[plane_nr];
if (!samples) {
// This plane does not contain data.
continue;
}
/* break if we did not reach the end of the interval */
if (volmeter->ival_frames != volmeter->update_frames)
break;
// For each plane calculate:
// * peak = the maximum-absolute of the sample values.
// * magnitude = root-mean-square of the sample values.
// A VU meter needs to integrate over 300ms, but this will
// be handled by the ballistics of the meter itself,
// reality. Which makes this calculation independent of
// sample rate or update rate.
float peak = 0.0;
float sum_of_squares = 0.0;
for (int sample_nr = 0; sample_nr < nr_samples; sample_nr++) {
float sample = samples[sample_nr];
volmeter_calc_ival_levels(volmeter);
updated = true;
peak = fmaxf(peak, fabsf(sample));
sum_of_squares += (sample * sample);
}
volmeter->vol_magnitude[channel_nr] = sqrtf(sum_of_squares /
nr_samples);
volmeter->vol_peak[channel_nr] = peak;
channel_nr++;
}
return updated;
// Clear audio channels that are not in use.
for (; channel_nr < MAX_AUDIO_CHANNELS; channel_nr++) {
volmeter->vol_magnitude[channel_nr] = 0.0;
volmeter->vol_peak[channel_nr] = 0.0;
}
}
static void volmeter_source_data_received(void *vptr, obs_source_t *source,
const struct audio_data *data, bool muted)
{
struct obs_volmeter *volmeter = (struct obs_volmeter *) vptr;
bool updated = false;
float mul, level, mag, peak;
float mul;
float magnitude[MAX_AUDIO_CHANNELS];
float peak[MAX_AUDIO_CHANNELS];
float input_peak[MAX_AUDIO_CHANNELS];
pthread_mutex_lock(&volmeter->mutex);
updated = volmeter_process_audio_data(volmeter, data);
volmeter_process_audio_data(volmeter, data);
if (updated) {
mul = db_to_mul(volmeter->cur_db);
level = volmeter->db_to_pos(mul_to_db(volmeter->vol_max * mul));
mag = volmeter->db_to_pos(mul_to_db(volmeter->vol_mag * mul));
peak = volmeter->db_to_pos(
mul_to_db(volmeter->vol_peak * mul));
// Adjust magnitude/peak based on the volume level set by the user.
// And convert to dB.
mul = muted ? 0.0f : db_to_mul(volmeter->cur_db);
for (int channel_nr = 0; channel_nr < MAX_AUDIO_CHANNELS;
channel_nr++) {
magnitude[channel_nr] = mul_to_db(
volmeter->vol_magnitude[channel_nr] * mul);
peak[channel_nr] = mul_to_db(
volmeter->vol_peak[channel_nr] * mul);
input_peak[channel_nr] = mul_to_db(
volmeter->vol_peak[channel_nr]);
}
// The input-peak is NOT adjusted with volume, so that the user
// can check the input-gain.
pthread_mutex_unlock(&volmeter->mutex);
if (updated)
signal_levels_updated(volmeter, level, mag, peak, muted);
signal_levels_updated(volmeter, magnitude, peak, input_peak);
UNUSED_PARAMETER(source);
}
static void volmeter_update_audio_settings(obs_volmeter_t *volmeter)
{
audio_t *audio = obs_get_audio();
const unsigned int sr = audio_output_get_sample_rate(audio);
uint32_t channels = (uint32_t)audio_output_get_channels(audio);
pthread_mutex_lock(&volmeter->mutex);
volmeter->channels = channels;
volmeter->update_frames = volmeter->update_ms * sr / 1000;
volmeter->peakhold_frames = volmeter->peakhold_ms * sr / 1000;
pthread_mutex_unlock(&volmeter->mutex);
}
obs_fader_t *obs_fader_create(enum obs_fader_type type)
{
struct obs_fader *fader = bzalloc(sizeof(struct obs_fader));
@ -634,28 +564,9 @@ obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type)
if (pthread_mutex_init(&volmeter->callback_mutex, NULL) != 0)
goto fail;
/* set conversion functions */
switch(type) {
case OBS_FADER_CUBIC:
volmeter->pos_to_db = cubic_def_to_db;
volmeter->db_to_pos = cubic_db_to_def;
break;
case OBS_FADER_IEC:
volmeter->pos_to_db = iec_def_to_db;
volmeter->db_to_pos = iec_db_to_def;
break;
case OBS_FADER_LOG:
volmeter->pos_to_db = log_def_to_db;
volmeter->db_to_pos = log_db_to_def;
break;
default:
goto fail;
break;
}
volmeter->type = type;
obs_volmeter_set_update_interval(volmeter, 50);
obs_volmeter_set_peak_hold(volmeter, 1500);
return volmeter;
fail:
@ -739,8 +650,6 @@ void obs_volmeter_set_update_interval(obs_volmeter_t *volmeter,
pthread_mutex_lock(&volmeter->mutex);
volmeter->update_ms = ms;
pthread_mutex_unlock(&volmeter->mutex);
volmeter_update_audio_settings(volmeter);
}
unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter)
@ -755,28 +664,26 @@ unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter)
return interval;
}
void obs_volmeter_set_peak_hold(obs_volmeter_t *volmeter, const unsigned int ms)
int obs_volmeter_get_nr_channels(obs_volmeter_t *volmeter)
{
if (!volmeter)
return;
int source_nr_audio_channels;
int obs_nr_audio_channels;
pthread_mutex_lock(&volmeter->mutex);
volmeter->peakhold_ms = ms;
pthread_mutex_unlock(&volmeter->mutex);
if (volmeter->source) {
source_nr_audio_channels = get_audio_channels(
volmeter->source->sample_info.speakers);
} else {
source_nr_audio_channels = 1;
}
volmeter_update_audio_settings(volmeter);
}
struct obs_audio_info audio_info;
if (obs_get_audio_info(&audio_info)) {
obs_nr_audio_channels = get_audio_channels(audio_info.speakers);
} else {
obs_nr_audio_channels = 2;
}
unsigned int obs_volmeter_get_peak_hold(obs_volmeter_t *volmeter)
{
if (!volmeter)
return 0;
pthread_mutex_lock(&volmeter->mutex);
const unsigned int peakhold = volmeter->peakhold_ms;
pthread_mutex_unlock(&volmeter->mutex);
return peakhold;
return CLAMP(source_nr_audio_channels, 1, obs_nr_audio_channels);
}
void obs_volmeter_add_callback(obs_volmeter_t *volmeter,
@ -804,3 +711,4 @@ void obs_volmeter_remove_callback(obs_volmeter_t *volmeter,
da_erase_item(volmeter->callbacks, &cb);
pthread_mutex_unlock(&volmeter->callback_mutex);
}

View file

@ -229,22 +229,15 @@ EXPORT void obs_volmeter_set_update_interval(obs_volmeter_t *volmeter,
EXPORT unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter);
/**
* @brief Set the peak hold time for the volume meter
* @brief Get the number of channels which are configured for this source.
* @param volmeter pointer to the volume meter object
* @param ms peak hold time in ms
*/
EXPORT void obs_volmeter_set_peak_hold(obs_volmeter_t *volmeter,
const unsigned int ms);
EXPORT int obs_volmeter_get_nr_channels(obs_volmeter_t *volmeter);
/**
* @brief Get the peak hold time for the volume meter
* @param volmeter pointer to the volume meter object
* @return the peak hold time in ms
*/
EXPORT unsigned int obs_volmeter_get_peak_hold(obs_volmeter_t *volmeter);
typedef void (*obs_volmeter_updated_t)(void *param, float level,
float magnitude, float peak, float muted);
typedef void (*obs_volmeter_updated_t)(void *param,
const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS],
const float input_peak[MAX_AUDIO_CHANNELS]);
EXPORT void obs_volmeter_add_callback(obs_volmeter_t *volmeter,
obs_volmeter_updated_t callback, void *param);

View file

@ -27,7 +27,7 @@
/*
* Increment if major breaking API changes
*/
#define LIBOBS_API_MAJOR_VER 19
#define LIBOBS_API_MAJOR_VER 21
/*
* Increment if backward-compatible additions
@ -41,7 +41,7 @@
*
* Reset to zero each major or minor version
*/
#define LIBOBS_API_PATCH_VER 3
#define LIBOBS_API_PATCH_VER 2
#define MAKE_SEMANTIC_VERSION(major, minor, patch) \
((major << 24) | \

View file

@ -854,7 +854,7 @@ static inline void set_item_def(struct obs_data *data, obs_data_item_t **item,
item = &actual_item;
}
if (item && *item && (*item)->type == type)
if (item && *item && (*item)->type != type)
return;
set_item_data(data, item, name, ptr, size, type, true, false);

View file

@ -611,7 +611,7 @@ uint32_t obs_encoder_get_height(const obs_encoder_t *encoder)
if (!encoder->media)
return 0;
return encoder->scaled_width != 0 ?
return encoder->scaled_height != 0 ?
encoder->scaled_height :
video_output_get_height(encoder->media);
}
@ -952,7 +952,6 @@ static bool buffer_audio(struct obs_encoder *encoder, struct audio_data *data)
/* use currently buffered audio instead */
if (v_start_ts < data->timestamp) {
start_from_buffer(encoder, v_start_ts);
goto skip_push;
}
} else if (!encoder->start_ts && !encoder->paired_encoder) {
@ -962,7 +961,6 @@ static bool buffer_audio(struct obs_encoder *encoder, struct audio_data *data)
fail:
push_back_audio(encoder, data, size, offset_size);
skip_push:
profile_end(buffer_audio_name);
return success;
}

View file

@ -20,3 +20,12 @@
# define av_frame_free avcodec_free_frame
#endif
#if LIBAVCODEC_VERSION_MAJOR >= 58
#define CODEC_CAP_TRUNC AV_CODEC_CAP_TRUNCATED
#define CODEC_FLAG_TRUNC AV_CODEC_FLAG_TRUNCATED
#define INPUT_BUFFER_PADDING_SIZE AV_INPUT_BUFFER_PADDING_SIZE
#else
#define CODEC_CAP_TRUNC CODEC_CAP_TRUNCATED
#define CODEC_FLAG_TRUNC CODEC_FLAG_TRUNCATED
#define INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE
#endif

View file

@ -206,7 +206,7 @@ obs_hotkey_id obs_hotkey_register_service(obs_service_t *service,
obs_hotkey_id obs_hotkey_register_source(obs_source_t *source, const char *name,
const char *description, obs_hotkey_func func, void *data)
{
if (!source || !lock())
if (!source || source->context.private || !lock())
return OBS_INVALID_HOTKEY_ID;
obs_hotkey_id id = obs_hotkey_register_internal(

View file

@ -22,9 +22,15 @@ extern "C" {
#endif
typedef size_t obs_hotkey_id;
#define OBS_INVALID_HOTKEY_ID (~(obs_hotkey_id)0)
typedef size_t obs_hotkey_pair_id;
#ifndef SWIG
#define OBS_INVALID_HOTKEY_ID (~(obs_hotkey_id)0)
#define OBS_INVALID_HOTKEY_PAIR_ID (~(obs_hotkey_pair_id)0)
#else
const size_t OBS_INVALID_HOTKEY_ID = (size_t)-1;
const size_t OBS_INVALID_HOTKEY_PAIR_ID = (size_t)-1;
#endif
enum obs_key {
#define OBS_HOTKEY(x) x,
@ -68,6 +74,7 @@ EXPORT obs_hotkey_id obs_hotkey_binding_get_hotkey_id(
EXPORT obs_hotkey_t *obs_hotkey_binding_get_hotkey(
obs_hotkey_binding_t *binding);
#ifndef SWIG
struct obs_hotkeys_translations {
const char *insert;
const char *del;
@ -115,6 +122,7 @@ struct obs_hotkeys_translations {
* the default English translations for that specific operating system. */
EXPORT void obs_hotkeys_set_translations_s(
struct obs_hotkeys_translations *translations, size_t size);
#endif
#define obs_hotkeys_set_translations(translations) \
obs_hotkeys_set_translations_s(translations, \
@ -277,7 +285,7 @@ EXPORT int obs_key_to_virtual_key(obs_key_t key);
EXPORT const char *obs_key_to_name(obs_key_t key);
EXPORT obs_key_t obs_key_from_name(const char *name);
inline bool obs_key_combination_is_empty(obs_key_combination_t combo)
static inline bool obs_key_combination_is_empty(obs_key_combination_t combo)
{
return !combo.modifiers && combo.key == OBS_KEY_NONE;
}

View file

@ -475,3 +475,5 @@ OBS_MOUSE_BUTTON(OBS_KEY_MOUSE29)
#undef OBS_MOUSE_BUTTON
#undef OBS_MOUSE_BUTTON_DEFAULT
#endif
OBS_HOTKEY(OBS_KEY_BACKSLASH_RT102)

View file

@ -44,6 +44,11 @@ static inline int64_t packet_dts_usec(struct encoder_packet *packet)
return packet->dts * MICROSECOND_DEN / packet->timebase_den;
}
struct tick_callback {
void (*tick)(void *param, float seconds);
void *param;
};
struct draw_callback {
void (*draw)(void *param, uint32_t cx, uint32_t cy);
void *param;
@ -82,6 +87,7 @@ struct obs_module {
bool (*load)(void);
void (*unload)(void);
void (*post_load)(void);
void (*set_locale)(const char *locale);
void (*free_locale)(void);
uint32_t (*ver)(void);
@ -318,6 +324,7 @@ struct obs_core_data {
pthread_mutex_t audio_sources_mutex;
pthread_mutex_t draw_callbacks_mutex;
DARRAY(struct draw_callback) draw_callbacks;
DARRAY(struct tick_callback) tick_callbacks;
struct obs_view main_view;
@ -393,7 +400,7 @@ struct obs_core {
extern struct obs_core *obs;
extern void *obs_video_thread(void *param);
extern void *obs_graphics_thread(void *param);
extern gs_effect_t *obs_load_effect(gs_effect_t **effect, const char *file);
@ -609,6 +616,7 @@ struct obs_source {
bool async_active;
bool async_update_texture;
bool async_unbuffered;
bool async_decoupled;
struct obs_source_frame *async_preload_frame;
DARRAY(struct async_frame) async_cache;
DARRAY(struct obs_source_frame*)async_frames;
@ -679,9 +687,11 @@ struct obs_source {
struct audio_monitor *monitor;
enum obs_monitoring_type monitoring_type;
obs_data_t *private_settings;
};
extern const struct obs_source_info *get_source_info(const char *id);
extern struct obs_source_info *get_source_info(const char *id);
extern bool obs_source_init_context(struct obs_source *source,
obs_data_t *settings, const char *name,
obs_data_t *hotkey_data, bool private);

View file

@ -48,6 +48,7 @@ static int load_module_exports(struct obs_module *mod, const char *path)
/* optional exports */
mod->unload = os_dlsym(mod->module, "obs_module_unload");
mod->post_load = os_dlsym(mod->module, "obs_module_post_load");
mod->set_locale = os_dlsym(mod->module, "obs_module_set_locale");
mod->free_locale = os_dlsym(mod->module, "obs_module_free_locale");
mod->name = os_dlsym(mod->module, "obs_module_name");
@ -88,7 +89,7 @@ int obs_open_module(obs_module_t **module, const char *path,
mod.module = os_dlopen(path);
if (!mod.module) {
blog(LOG_WARNING, "Module '%s' not found", path);
blog(LOG_WARNING, "Module '%s' not loaded", path);
return MODULE_FILE_NOT_FOUND;
}
@ -254,6 +255,13 @@ void obs_load_all_modules(void)
profile_end(obs_load_all_modules_name);
}
void obs_post_load_modules(void)
{
for (obs_module_t *mod = obs->first_module; !!mod; mod = mod->next)
if (mod->post_load)
mod->post_load();
}
static inline void make_data_dir(struct dstr *parsed_data_dir,
const char *data_dir, const char *name)
{

View file

@ -97,6 +97,9 @@ MODULE_EXPORT bool obs_module_load(void);
/** Optional: Called when the module is unloaded. */
MODULE_EXPORT void obs_module_unload(void);
/** Optional: Called when all modules have finished loading */
MODULE_EXPORT void obs_module_post_load(void);
/** Called to set the current locale data for the module. */
MODULE_EXPORT void obs_module_set_locale(const char *locale);

View file

@ -16,6 +16,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#if defined(__FreeBSD__)
#define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
@ -87,55 +90,105 @@ char *find_libobs_data_file(const char *file)
static void log_processor_cores(void)
{
blog(LOG_INFO, "Processor: %lu logical cores",
sysconf(_SC_NPROCESSORS_ONLN));
blog(LOG_INFO, "Physical Cores: %d, Logical Cores: %d",
os_get_physical_cores(), os_get_logical_cores());
}
#if defined(__linux__)
static void log_processor_info(void)
{
FILE *fp;
int physical_id = -1;
int last_physical_id = -1;
char *line = NULL;
size_t linecap = 0;
struct dstr processor;
FILE *fp;
struct dstr proc_name;
struct dstr proc_speed;
fp = fopen("/proc/cpuinfo", "r");
if (!fp)
return;
dstr_init(&processor);
dstr_init(&proc_name);
dstr_init(&proc_speed);
while (getline(&line, &linecap, fp) != -1) {
if (!strncmp(line, "model name", 10)) {
char *start = strchr(line, ':');
if (!start || *(++start) == '\0')
continue;
dstr_copy(&processor, start);
dstr_resize(&processor, processor.len - 1);
dstr_depad(&processor);
dstr_copy(&proc_name, start);
dstr_resize(&proc_name, proc_name.len - 1);
dstr_depad(&proc_name);
}
if (!strncmp(line, "physical id", 11)) {
char *start = strchr(line, ':');
if (!start || *(++start) == '\0')
continue;
physical_id = atoi(start);
}
if (!strncmp(line, "cpu MHz", 7)) {
char *start = strchr(line, ':');
if (!start || *(++start) == '\0')
continue;
dstr_copy(&proc_speed, start);
dstr_resize(&proc_speed, proc_speed.len - 1);
dstr_depad(&proc_speed);
}
if (*line == '\n' && physical_id != last_physical_id) {
last_physical_id = physical_id;
blog(LOG_INFO, "Processor: %s", processor.array);
blog(LOG_INFO, "CPU Name: %s", proc_name.array);
blog(LOG_INFO, "CPU Speed: %sMHz", proc_speed.array);
}
}
fclose(fp);
dstr_free(&processor);
dstr_free(&proc_name);
dstr_free(&proc_speed);
free(line);
}
#elif defined(__FreeBSD__)
static void log_processor_info(void)
static void log_processor_speed(void)
{
char *line = NULL;
size_t linecap = 0;
FILE *fp;
struct dstr proc_speed;
fp = fopen("/var/run/dmesg.boot", "r");
if (!fp) {
blog(LOG_INFO, "CPU: Missing /var/run/dmesg.boot !");
return;
}
dstr_init(&proc_speed);
while (getline(&line, &linecap, fp) != -1) {
if (!strncmp(line, "CPU: ", 5)) {
char *start = strrchr(line, '(');
if (!start || *(++start) == '\0')
continue;
size_t len = strcspn(start, "-");
dstr_ncopy(&proc_speed, start, len);
}
}
blog(LOG_INFO, "CPU Speed: %sMHz", proc_speed.array);
fclose(fp);
dstr_free(&proc_speed);
free(line);
}
static void log_processor_name(void)
{
int mib[2];
size_t len;
@ -150,10 +203,16 @@ static void log_processor_info(void)
return;
sysctl(mib, 2, proc, &len, NULL, 0);
blog(LOG_INFO, "Processor: %s", proc);
blog(LOG_INFO, "CPU Name: %s", proc);
bfree(proc);
}
static void log_processor_info(void)
{
log_processor_name();
log_processor_speed();
}
#endif
static void log_memory_info(void)
@ -162,8 +221,10 @@ static void log_memory_info(void)
if (sysinfo(&info) < 0)
return;
blog(LOG_INFO, "Physical Memory: %"PRIu64"MB Total",
(uint64_t)info.totalram * info.mem_unit / 1024 / 1024);
blog(LOG_INFO, "Physical Memory: %"PRIu64"MB Total, %"PRIu64"MB Free",
(uint64_t)info.totalram * info.mem_unit / 1024 / 1024,
((uint64_t)info.freeram + (uint64_t)info.bufferram) *
info.mem_unit / 1024 / 1024);
}
static void log_kernel_version(void)
@ -222,10 +283,10 @@ static void log_distribution_info(void)
void log_system_info(void)
{
log_processor_cores();
#if defined(__linux__) || defined(__FreeBSD__)
log_processor_info();
#endif
log_processor_cores();
log_memory_info();
log_kernel_version();
#if defined(__linux__)
@ -594,7 +655,7 @@ static inline bool fill_keycodes(struct obs_core_hotkeys *hotkeys)
context->min_keycode = setup->min_keycode;
cookie = xcb_get_keyboard_mapping(connection,
mincode, maxcode - mincode - 1);
mincode, maxcode - mincode + 1);
reply = xcb_get_keyboard_mapping_reply(connection, cookie, &error);
@ -606,7 +667,7 @@ static inline bool fill_keycodes(struct obs_core_hotkeys *hotkeys)
const xcb_keysym_t *keysyms = xcb_get_keyboard_mapping_keysyms(reply);
int syms_per_code = (int)reply->keysyms_per_keycode;
context->num_keysyms = (maxcode - mincode) * syms_per_code;
context->num_keysyms = (maxcode - mincode + 1) * syms_per_code;
context->syms_per_code = syms_per_code;
context->keysyms = bmemdup(keysyms,
sizeof(xcb_keysym_t) * context->num_keysyms);

View file

@ -283,28 +283,36 @@ static void log_frame_info(struct obs_output *output)
{
struct obs_core_video *video = &obs->video;
uint32_t video_frames = video_output_get_total_frames(output->video);
uint32_t total = video_frames - output->starting_frame_count;
uint32_t drawn = video->total_frames - output->starting_drawn_count;
uint32_t lagged = video->lagged_frames - output->starting_lagged_count;
int dropped = obs_output_get_frames_dropped(output);
int total = output->total_frames;
double percentage_lagged = 0.0f;
double percentage_dropped = 0.0f;
if (total)
percentage_dropped = (double)dropped / (double)total * 100.0;
if (drawn)
percentage_lagged = (double)lagged / (double)drawn * 100.0;
if (dropped)
percentage_dropped = (double)dropped / (double)total * 100.0;
blog(LOG_INFO, "Output '%s': stopping", output->context.name);
blog(LOG_INFO, "Output '%s': Total encoded frames: %"PRIu32,
output->context.name, total);
blog(LOG_INFO, "Output '%s': Total drawn frames: %"PRIu32,
output->context.name, drawn);
if (!dropped || !total)
blog(LOG_INFO, "Output '%s': Total frames output: %d",
output->context.name, total);
else
blog(LOG_INFO, "Output '%s': Total frames output: %d"
" (%d attempted)",
output->context.name, total - dropped, total);
if (!lagged || !drawn)
blog(LOG_INFO, "Output '%s': Total drawn frames: %"PRIu32,
output->context.name, drawn);
else
blog(LOG_INFO, "Output '%s': Total drawn frames: %"PRIu32
" (%"PRIu32" attempted)",
output->context.name, drawn - lagged, drawn);
if (drawn && lagged)
blog(LOG_INFO, "Output '%s': Number of lagged frames due "
@ -1293,7 +1301,7 @@ static bool initialize_interleaved_packets(struct obs_output *output)
}
/* get new offsets */
output->video_offset = video->dts;
output->video_offset = video->pts;
for (size_t i = 0; i < audio_mixes; i++)
output->audio_offsets[i] = audio[i]->dts;
@ -1329,8 +1337,12 @@ static inline void insert_interleaved_packet(struct obs_output *output,
struct encoder_packet *cur_packet;
cur_packet = output->interleaved_packets.array + idx;
if (out->dts_usec < cur_packet->dts_usec)
if (out->dts_usec == cur_packet->dts_usec &&
out->type == OBS_ENCODER_VIDEO) {
break;
} else if (out->dts_usec < cur_packet->dts_usec) {
break;
}
}
da_insert(output->interleaved_packets, idx, out);
@ -2155,3 +2167,15 @@ bool obs_output_reconnecting(const obs_output_t *output)
return reconnecting(output);
}
const char *obs_output_get_supported_video_codecs(const obs_output_t *output)
{
return obs_output_valid(output, __FUNCTION__) ?
output->info.encoded_video_codecs : NULL;
}
const char *obs_output_get_supported_audio_codecs(const obs_output_t *output)
{
return obs_output_valid(output, __FUNCTION__) ?
output->info.encoded_audio_codecs : NULL;
}

View file

@ -67,6 +67,10 @@ struct obs_output_info {
float (*get_congestion)(void *data);
int (*get_connect_time_ms)(void *data);
/* only used with encoded outputs, separated with semicolon */
const char *encoded_video_codecs;
const char *encoded_audio_codecs;
};
EXPORT void obs_register_output_s(const struct obs_output_info *info,

View file

@ -143,9 +143,10 @@ static inline void frame_rate_data_free(struct frame_rate_data *data)
struct obs_properties;
struct obs_property {
const char *name;
const char *desc;
const char *long_desc;
char *name;
char *desc;
char *long_desc;
void *priv;
enum obs_property_type type;
bool visible;
bool enabled;
@ -153,6 +154,7 @@ struct obs_property {
struct obs_properties *parent;
obs_property_modified_t modified;
obs_property_modified2_t modified2;
struct obs_property *next;
};
@ -222,6 +224,9 @@ static void obs_property_destroy(struct obs_property *property)
else if (property->type == OBS_PROPERTY_FRAME_RATE)
frame_rate_data_free(get_property_data(property));
bfree(property->name);
bfree(property->desc);
bfree(property->long_desc);
bfree(property);
}
@ -277,6 +282,8 @@ void obs_properties_apply_settings(obs_properties_t *props, obs_data_t *settings
while (p) {
if (p->modified)
p->modified(props, p, settings);
else if (p->modified2)
p->modified2(p->priv, props, p, settings);
p = p->next;
}
}
@ -323,8 +330,8 @@ static inline struct obs_property *new_prop(struct obs_properties *props,
p->enabled = true;
p->visible = true;
p->type = type;
p->name = name;
p->desc = desc;
p->name = bstrdup(name);
p->desc = bstrdup(desc);
propertes_add(props, p);
return p;
@ -495,6 +502,20 @@ obs_property_t *obs_properties_add_button(obs_properties_t *props,
return p;
}
obs_property_t *obs_properties_add_button2(obs_properties_t *props,
const char *name, const char *text,
obs_property_clicked_t callback, void *priv)
{
if (!props || has_prop(props, name)) return NULL;
struct obs_property *p = new_prop(props, name, text,
OBS_PROPERTY_BUTTON);
struct button_data *data = get_property_data(p);
data->callback = callback;
p->priv = priv;
return p;
}
obs_property_t *obs_properties_add_font(obs_properties_t *props,
const char *name, const char *desc)
{
@ -571,10 +592,24 @@ void obs_property_set_modified_callback(obs_property_t *p,
if (p) p->modified = modified;
}
void obs_property_set_modified_callback2(obs_property_t *p,
obs_property_modified2_t modified2, void *priv)
{
if (p) {
p->modified2 = modified2;
p->priv = priv;
}
}
bool obs_property_modified(obs_property_t *p, obs_data_t *settings)
{
if (p && p->modified)
return p->modified(p->parent, p, settings);
if (p) {
if (p->modified) {
return p->modified(p->parent, p, settings);
} else if (p->modified2) {
return p->modified2(p->priv, p->parent, p, settings);
}
}
return false;
}
@ -584,9 +619,12 @@ bool obs_property_button_clicked(obs_property_t *p, void *obj)
if (p) {
struct button_data *data = get_type_data(p,
OBS_PROPERTY_BUTTON);
if (data && data->callback)
if (data && data->callback) {
if (p->priv)
return data->callback(p->parent, p, p->priv);
return data->callback(p->parent, p,
(context ? context->data : NULL));
}
}
return false;
@ -604,12 +642,22 @@ void obs_property_set_enabled(obs_property_t *p, bool enabled)
void obs_property_set_description(obs_property_t *p, const char *description)
{
if (p) p->desc = description;
if (p) {
bfree(p->desc);
p->desc = description && *description
? bstrdup(description)
: NULL;
}
}
void obs_property_set_long_description(obs_property_t *p, const char *long_desc)
{
if (p) p->long_desc = long_desc;
if (p) {
bfree(p->long_desc);
p->long_desc = long_desc && *long_desc
? bstrdup(long_desc)
: NULL;
}
}
const char *obs_property_name(obs_property_t *p)

View file

@ -194,6 +194,10 @@ EXPORT obs_property_t *obs_properties_add_button(obs_properties_t *props,
const char *name, const char *text,
obs_property_clicked_t callback);
EXPORT obs_property_t *obs_properties_add_button2(obs_properties_t *props,
const char *name, const char *text,
obs_property_clicked_t callback, void *priv);
/**
* Adds a font selection property.
*
@ -223,9 +227,13 @@ EXPORT obs_property_t *obs_properties_add_frame_rate(obs_properties_t *props,
*/
typedef bool (*obs_property_modified_t)(obs_properties_t *props,
obs_property_t *property, obs_data_t *settings);
typedef bool (*obs_property_modified2_t)(void *priv, obs_properties_t *props,
obs_property_t *property, obs_data_t *settings);
EXPORT void obs_property_set_modified_callback(obs_property_t *p,
obs_property_modified_t modified);
EXPORT void obs_property_set_modified_callback2(obs_property_t *p,
obs_property_modified2_t modified, void *priv);
EXPORT bool obs_property_modified(obs_property_t *p, obs_data_t *settings);
EXPORT bool obs_property_button_clicked(obs_property_t *p, void *obj);

View file

@ -486,7 +486,10 @@ static inline void render_item(struct obs_scene_item *item)
-(float)item->crop.top,
0.0f);
gs_blend_state_push();
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
obs_source_video_render(item->source);
gs_blend_state_pop();
gs_texrender_end(item->item_render);
}
}
@ -590,6 +593,7 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data)
const char *scale_filter_str;
struct obs_scene_item *item;
bool visible;
bool lock;
if (!source) {
blog(LOG_WARNING, "[scene_load_item] Source %s not found!",
@ -616,10 +620,18 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data)
item->rot = (float)obs_data_get_double(item_data, "rot");
item->align = (uint32_t)obs_data_get_int(item_data, "align");
visible = obs_data_get_bool(item_data, "visible");
lock = obs_data_get_bool(item_data, "locked");
obs_data_get_vec2(item_data, "pos", &item->pos);
obs_data_get_vec2(item_data, "scale", &item->scale);
obs_data_release(item->private_settings);
item->private_settings =
obs_data_get_obj(item_data, "private_settings");
if (!item->private_settings)
item->private_settings = obs_data_create();
set_visibility(item, visible);
obs_sceneitem_set_locked(item, lock);
item->bounds_type =
(enum obs_bounds_type)obs_data_get_int(item_data,
@ -697,6 +709,7 @@ static void scene_save_item(obs_data_array_t *array,
obs_data_set_string(item_data, "name", name);
obs_data_set_bool (item_data, "visible", item->user_visible);
obs_data_set_bool (item_data, "locked", item->locked);
obs_data_set_double(item_data, "rot", item->rot);
obs_data_set_vec2 (item_data, "pos", &item->pos);
obs_data_set_vec2 (item_data, "scale", &item->scale);
@ -723,6 +736,9 @@ static void scene_save_item(obs_data_array_t *array,
obs_data_set_string(item_data, "scale_filter", scale_filter);
obs_data_set_obj(item_data, "private_settings",
item->private_settings);
obs_data_array_push_back(array, item_data);
obs_data_release(item_data);
}
@ -891,7 +907,7 @@ static bool scene_audio_render(void *data, uint64_t *ts_out,
item = scene->first_item;
while (item) {
if (!obs_source_audio_pending(item->source)) {
if (!obs_source_audio_pending(item->source) && item->visible) {
uint64_t source_ts =
obs_source_get_audio_timestamp(item->source);
@ -1079,6 +1095,11 @@ obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name,
new_scene = make_private ?
obs_scene_create_private(name) : obs_scene_create(name);
obs_source_copy_filters(new_scene->source, scene->source);
obs_data_apply(new_scene->source->private_settings,
scene->source->private_settings);
for (size_t i = 0; i < items.num; i++) {
item = items.array[i];
source = make_unique ?
@ -1112,8 +1133,19 @@ obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name,
new_item->bounds_align = item->bounds_align;
new_item->bounds = item->bounds;
new_item->toggle_visibility =
OBS_INVALID_HOTKEY_PAIR_ID;
obs_sceneitem_set_crop(new_item, &item->crop);
if (!new_item->item_render &&
item_texture_enabled(new_item)) {
obs_enter_graphics();
new_item->item_render = gs_texrender_create(
GS_RGBA, GS_ZS_NONE);
obs_leave_graphics();
}
obs_source_release(source);
}
}
@ -1347,6 +1379,8 @@ obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source)
item->align = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
item->actions_mutex = mutex;
item->user_visible = true;
item->locked = false;
item->private_settings = obs_data_create();
os_atomic_set_long(&item->active_refs, 1);
vec2_set(&item->scale, 1.0f, 1.0f);
matrix4_identity(&item->draw_transform);
@ -1402,6 +1436,7 @@ static void obs_sceneitem_destroy(obs_sceneitem_t *item)
gs_texrender_destroy(item->item_render);
obs_leave_graphics();
}
obs_data_release(item->private_settings);
obs_hotkey_pair_unregister(item->toggle_visibility);
pthread_mutex_destroy(&item->actions_mutex);
if (item->source)
@ -1773,6 +1808,27 @@ bool obs_sceneitem_set_visible(obs_sceneitem_t *item, bool visible)
return true;
}
bool obs_sceneitem_locked(const obs_sceneitem_t *item)
{
return item ? item->locked : false;
}
bool obs_sceneitem_set_locked(obs_sceneitem_t *item, bool lock)
{
if (!item)
return false;
if (item->locked == lock)
return false;
if (!item->parent)
return false;
item->locked = lock;
return true;
}
static bool sceneitems_match(obs_scene_t *scene, obs_sceneitem_t * const *items,
size_t size, bool *order_matches)
{
@ -1962,3 +2018,12 @@ int64_t obs_sceneitem_get_id(const obs_sceneitem_t *item)
return item->id;
}
obs_data_t *obs_sceneitem_get_private_settings(obs_sceneitem_t *item)
{
if (!obs_ptr_valid(item, "obs_sceneitem_get_private_settings"))
return NULL;
obs_data_addref(item->private_settings);
return item->private_settings;
}

View file

@ -41,6 +41,7 @@ struct obs_scene_item {
bool user_visible;
bool visible;
bool selected;
bool locked;
gs_texrender_t *item_render;
struct obs_sceneitem_crop crop;
@ -67,6 +68,8 @@ struct obs_scene_item {
obs_hotkey_pair_id toggle_visibility;
obs_data_t *private_settings;
pthread_mutex_t actions_mutex;
DARRAY(struct item_action) audio_actions;

View file

@ -393,3 +393,13 @@ const char *obs_service_get_id(const obs_service_t *service)
return obs_service_valid(service, "obs_service_get_id")
? service->info.id : NULL;
}
const char *obs_service_get_output_type(const obs_service_t *service)
{
if (!obs_service_valid(service, "obs_service_get_output_type"))
return NULL;
if (service->info.get_output_type)
return service->info.get_output_type(service->context.data);
return NULL;
}

View file

@ -72,6 +72,8 @@ struct obs_service_info {
void *type_data;
void (*free_type_data)(void *type_data);
const char *(*get_output_type)(void *data);
/* TODO: more stuff later */
};

View file

@ -365,6 +365,9 @@ bool obs_transition_start(obs_source_t *transition,
if (same_as_source && !active)
return false;
if (transition->info.transition_start)
transition->info.transition_start(transition->context.data);
if (transition->transition_use_fixed_duration)
duration_ms = transition->transition_fixed_duration;
@ -376,6 +379,10 @@ bool obs_transition_start(obs_source_t *transition,
set_source(transition, OBS_TRANSITION_SOURCE_B, dest,
activate_transition);
if (dest == NULL && same_as_dest && !same_as_source) {
transition->transitioning_video = true;
transition->transitioning_audio = true;
}
obs_source_dosignal(transition, "source_transition_start",
"transition_start");
@ -442,6 +449,11 @@ static inline float get_video_time(obs_source_t *transition)
return calc_time(transition, ts);
}
float obs_transition_get_time(obs_source_t *transition)
{
return get_video_time(transition);
}
static inline gs_texture_t *get_texture(obs_source_t *transition,
enum obs_transition_target target)
{
@ -645,6 +657,14 @@ static void obs_transition_stop(obs_source_t *transition)
transition->transition_sources[1] = NULL;
}
static inline void handle_stop(obs_source_t *transition)
{
if (transition->info.transition_stop)
transition->info.transition_stop(transition->context.data);
obs_source_dosignal(transition, "source_transition_stop",
"transition_stop");
}
void obs_transition_video_render(obs_source_t *transition,
obs_transition_video_render_callback_t callback)
{
@ -728,8 +748,61 @@ void obs_transition_video_render(obs_source_t *transition,
obs_source_dosignal(transition, "source_transition_video_stop",
"transition_video_stop");
if (stopped)
obs_source_dosignal(transition, "source_transition_stop",
"transition_stop");
handle_stop(transition);
}
bool obs_transition_video_render_direct(obs_source_t *transition,
enum obs_transition_target target)
{
struct transition_state state;
struct matrix4 matrices[2];
bool stopped = false;
bool video_stopped = false;
bool render_b = target == OBS_TRANSITION_SOURCE_B;
bool transitioning;
float t;
if (!transition_valid(transition, "obs_transition_video_render"))
return false;
t = get_video_time(transition);
lock_transition(transition);
if (t >= 1.0f && transition->transitioning_video) {
transition->transitioning_video = false;
video_stopped = true;
if (!transition->transitioning_audio) {
obs_transition_stop(transition);
stopped = true;
}
}
copy_transition_state(transition, &state);
transitioning = state.transitioning_audio || state.transitioning_video;
matrices[0] = transition->transition_matrices[0];
matrices[1] = transition->transition_matrices[1];
unlock_transition(transition);
int idx = (transitioning && render_b) ? 1 : 0;
if (state.s[idx]) {
gs_matrix_push();
gs_matrix_mul(&matrices[idx]);
obs_source_video_render(state.s[idx]);
gs_matrix_pop();
}
obs_source_release(state.s[0]);
obs_source_release(state.s[1]);
if (video_stopped)
obs_source_dosignal(transition, "source_transition_video_stop",
"transition_video_stop");
if (stopped)
handle_stop(transition);
return transitioning;
}
static inline float get_sample_time(obs_source_t *transition,
@ -805,10 +878,6 @@ static inline uint64_t calc_min_ts(obs_source_t *sources[2])
return min_ts;
}
#define TOTAL_AUDIO_SIZE \
(MAX_AUDIO_MIXES * MAX_AUDIO_CHANNELS * \
AUDIO_OUTPUT_FRAMES * sizeof(float))
static inline bool stop_audio(obs_source_t *transition)
{
transition->transitioning_audio = false;
@ -882,8 +951,7 @@ bool obs_transition_audio_render(obs_source_t *transition,
}
if (stopped)
obs_source_dosignal(transition, "source_transition_stop",
"transition_stop");
handle_stop(transition);
*ts_out = min_ts;
return !!min_ts;

View file

@ -39,7 +39,7 @@ static inline bool deinterlacing_enabled(const struct obs_source *source)
return source->deinterlace_mode != OBS_DEINTERLACE_MODE_DISABLE;
}
const struct obs_source_info *get_source_info(const char *id)
struct obs_source_info *get_source_info(const char *id)
{
for (size_t i = 0; i < obs->source_types.num; i++) {
struct obs_source_info *info = &obs->source_types.array[i];
@ -190,6 +190,8 @@ bool obs_source_init(struct obs_source *source)
pthread_mutex_unlock(&obs->data.audio_sources_mutex);
}
source->private_settings = obs_data_create();
obs_context_data_insert(&source->context,
&obs->data.sources_mutex,
&obs->data.first_source);
@ -321,8 +323,13 @@ static obs_source_t *obs_source_create_internal(const char *id,
private))
goto fail;
if (info && info->get_defaults)
info->get_defaults(source->context.settings);
if (info) {
if (info->get_defaults2)
info->get_defaults2(info->type_data,
source->context.settings);
else if (info->get_defaults)
info->get_defaults(source->context.settings);
}
if (!obs_source_init(source))
goto fail;
@ -403,9 +410,11 @@ static void duplicate_filters(obs_source_t *dst, obs_source_t *src,
obs_source_t *src_filter = filters.array[i - 1];
char *new_name = get_new_filter_name(dst,
src_filter->context.name);
bool enabled = obs_source_enabled(src_filter);
obs_source_t *dst_filter = obs_source_duplicate(src_filter,
new_name, private);
obs_source_set_enabled(dst_filter, enabled);
bfree(new_name);
obs_source_filter_add(dst, dst_filter);
@ -418,11 +427,7 @@ static void duplicate_filters(obs_source_t *dst, obs_source_t *src,
void obs_source_copy_filters(obs_source_t *dst, obs_source_t *src)
{
duplicate_filters(dst, src, dst->context.private ?
OBS_SCENE_DUP_PRIVATE_COPY :
OBS_SCENE_DUP_COPY);
obs_source_release(src);
duplicate_filters(dst, src, dst->context.private);
}
obs_source_t *obs_source_duplicate(obs_source_t *source,
@ -445,7 +450,6 @@ obs_source_t *obs_source_duplicate(obs_source_t *source,
create_private ? OBS_SCENE_DUP_PRIVATE_COPY :
OBS_SCENE_DUP_COPY);
obs_source_t *new_source = obs_scene_get_source(new_scene);
duplicate_filters(new_source, source, create_private);
return new_source;
}
@ -464,6 +468,8 @@ obs_source_t *obs_source_duplicate(obs_source_t *source,
new_source->muted = source->muted;
new_source->flags = source->flags;
obs_data_apply(new_source->private_settings, source->private_settings);
if (source->info.type != OBS_SOURCE_TYPE_FILTER)
duplicate_filters(new_source, source, create_private);
@ -582,6 +588,7 @@ void obs_source_destroy(struct obs_source *source)
pthread_mutex_destroy(&source->audio_cb_mutex);
pthread_mutex_destroy(&source->audio_mutex);
pthread_mutex_destroy(&source->async_mutex);
obs_data_release(source->private_settings);
obs_context_data_free(&source->context);
if (source->owns_info_id)
@ -688,7 +695,9 @@ bool obs_source_removed(const obs_source_t *source)
static inline obs_data_t *get_defaults(const struct obs_source_info *info)
{
obs_data_t *settings = obs_data_create();
if (info->get_defaults)
if (info->get_defaults2)
info->get_defaults2(info->type_data, settings);
else if (info->get_defaults)
info->get_defaults(settings);
return settings;
}
@ -708,14 +717,18 @@ obs_data_t *obs_get_source_defaults(const char *id)
obs_properties_t *obs_get_source_properties(const char *id)
{
const struct obs_source_info *info = get_source_info(id);
if (info && info->get_properties) {
if (info && (info->get_properties || info->get_properties2)) {
obs_data_t *defaults = get_defaults(info);
obs_properties_t *properties;
obs_properties_t *props;
properties = info->get_properties(NULL);
obs_properties_apply_settings(properties, defaults);
if (info->get_properties2)
props = info->get_properties2(NULL, info->type_data);
else
props = info->get_properties(NULL);
obs_properties_apply_settings(props, defaults);
obs_data_release(defaults);
return properties;
return props;
}
return NULL;
}
@ -723,13 +736,13 @@ obs_properties_t *obs_get_source_properties(const char *id)
bool obs_is_source_configurable(const char *id)
{
const struct obs_source_info *info = get_source_info(id);
return info && info->get_properties;
return info && (info->get_properties || info->get_properties2);
}
bool obs_source_configurable(const obs_source_t *source)
{
return data_valid(source, "obs_source_configurable") &&
source->info.get_properties;
(source->info.get_properties || source->info.get_properties2);
}
obs_properties_t *obs_source_properties(const obs_source_t *source)
@ -737,7 +750,14 @@ obs_properties_t *obs_source_properties(const obs_source_t *source)
if (!data_valid(source, "obs_source_properties"))
return NULL;
if (source->info.get_properties) {
if (source->info.get_properties2) {
obs_properties_t *props;
props = source->info.get_properties2(source->context.data,
source->info.type_data);
obs_properties_apply_settings(props, source->context.settings);
return props;
} else if (source->info.get_properties) {
obs_properties_t *props;
props = source->info.get_properties(source->context.data);
obs_properties_apply_settings(props, source->context.settings);
@ -1044,6 +1064,9 @@ void obs_source_video_tick(obs_source_t *source, float seconds)
static inline uint64_t conv_frames_to_time(const size_t sample_rate,
const size_t frames)
{
if (!sample_rate)
return 0;
return (uint64_t)frames * 1000000000ULL / (uint64_t)sample_rate;
}
@ -1056,6 +1079,10 @@ static inline size_t conv_time_to_frames(const size_t sample_rate,
/* maximum buffer size */
#define MAX_BUF_SIZE (1000 * AUDIO_OUTPUT_FRAMES * sizeof(float))
/* time threshold in nanoseconds to ensure audio timing is as seamless as
* possible */
#define TS_SMOOTHING_THRESHOLD 70000000ULL
static inline void reset_audio_timing(obs_source_t *source, uint64_t timestamp,
uint64_t os_time)
{
@ -1507,7 +1534,6 @@ static bool update_async_texrender(struct obs_source *source,
uint32_t cy = source->async_height;
float convert_width = (float)source->async_convert_width;
float convert_height = (float)source->async_convert_height;
gs_effect_t *conv = obs->video.conversion_effect;
gs_technique_t *tech = gs_effect_get_technique(conv,
@ -1666,9 +1692,13 @@ static void obs_source_update_async_video(obs_source_t *source)
source->async_rendered = true;
if (frame) {
source->timing_adjust =
os_gettime_ns() - frame->timestamp;
source->timing_set = true;
if (!source->async_decoupled ||
!source->async_unbuffered) {
source->timing_adjust =
obs->video.video_time -
frame->timestamp;
source->timing_set = true;
}
if (source->async_update_texture) {
update_async_texture(source, frame,
@ -1731,8 +1761,11 @@ static bool ready_async_frame(obs_source_t *source, uint64_t sys_time);
static inline void render_video(obs_source_t *source)
{
if (source->info.type != OBS_SOURCE_TYPE_FILTER &&
(source->info.output_flags & OBS_SOURCE_VIDEO) == 0)
(source->info.output_flags & OBS_SOURCE_VIDEO) == 0) {
if (source->filter_parent)
obs_source_skip_video_filter(source);
return;
}
if (source->info.type == OBS_SOURCE_TYPE_INPUT &&
(source->info.output_flags & OBS_SOURCE_ASYNC) != 0 &&
@ -1776,7 +1809,7 @@ void obs_source_video_render(obs_source_t *source)
static uint32_t get_base_width(const obs_source_t *source)
{
bool is_filter = (source->info.type == OBS_SOURCE_TYPE_FILTER);
bool is_filter = !!source->filter_parent;
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) {
return source->enabled ? source->transition_actual_cx : 0;
@ -1784,7 +1817,7 @@ static uint32_t get_base_width(const obs_source_t *source)
} else if (source->info.get_width && (!is_filter || source->enabled)) {
return source->info.get_width(source->context.data);
} else if (source->info.type == OBS_SOURCE_TYPE_FILTER) {
} else if (is_filter) {
return get_base_width(source->filter_target);
}
@ -1793,7 +1826,7 @@ static uint32_t get_base_width(const obs_source_t *source)
static uint32_t get_base_height(const obs_source_t *source)
{
bool is_filter = (source->info.type == OBS_SOURCE_TYPE_FILTER);
bool is_filter = !!source->filter_parent;
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) {
return source->enabled ? source->transition_actual_cy : 0;
@ -3857,13 +3890,17 @@ static void custom_audio_render(obs_source_t *source, uint32_t mixers,
uint64_t ts;
for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
for (size_t ch = 0; ch < channels; ch++)
for (size_t ch = 0; ch < channels; ch++) {
audio_data.output[mix].data[ch] =
source->audio_output_buf[mix][ch];
}
}
memset(audio_data.output[0].data[0], 0, AUDIO_OUTPUT_FRAMES *
MAX_AUDIO_MIXES * channels * sizeof(float));
if ((source->audio_mixers & mixers & (1 << mix)) != 0) {
memset(source->audio_output_buf[mix][0], 0,
sizeof(float) * AUDIO_OUTPUT_FRAMES *
channels);
}
}
success = source->info.audio_render(source->context.data, &ts,
&audio_data, mixers, channels, sample_rate);
@ -3874,11 +3911,15 @@ static void custom_audio_render(obs_source_t *source, uint32_t mixers,
return;
for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
if ((source->audio_mixers & (1 << mix)) == 0) {
uint32_t mix_bit = 1 << mix;
if ((mixers & mix_bit) == 0)
continue;
if ((source->audio_mixers & mix_bit) == 0) {
memset(source->audio_output_buf[mix][0], 0,
sizeof(float) * AUDIO_OUTPUT_FRAMES *
channels);
continue;
}
}
@ -4051,3 +4092,45 @@ bool obs_source_async_unbuffered(const obs_source_t *source)
return obs_source_valid(source, "obs_source_async_unbuffered") ?
source->async_unbuffered : false;
}
obs_data_t *obs_source_get_private_settings(obs_source_t *source)
{
if (!obs_ptr_valid(source, "obs_source_get_private_settings"))
return NULL;
obs_data_addref(source->private_settings);
return source->private_settings;
}
void obs_source_set_async_decoupled(obs_source_t *source, bool decouple)
{
if (!obs_ptr_valid(source, "obs_source_set_async_decoupled"))
return;
source->async_decoupled = decouple;
if (decouple) {
pthread_mutex_lock(&source->audio_buf_mutex);
source->timing_set = false;
reset_audio_data(source, 0);
pthread_mutex_unlock(&source->audio_buf_mutex);
}
}
bool obs_source_async_decoupled(const obs_source_t *source)
{
return obs_source_valid(source, "obs_source_async_decoupled") ?
source->async_decoupled : false;
}
/* hidden/undocumented export to allow source type redefinition for scripts */
EXPORT void obs_enable_source_type(const char *name, bool enable)
{
struct obs_source_info *info = get_source_info(name);
if (!info)
return;
if (enable)
info->output_flags &= ~OBS_SOURCE_CAP_DISABLED;
else
info->output_flags |= OBS_SOURCE_CAP_DISABLED;
}

View file

@ -130,6 +130,11 @@ enum obs_source_type {
*/
#define OBS_SOURCE_DO_NOT_SELF_MONITOR (1<<9)
/**
* Source type is currently disabled and should not be shown to the user
*/
#define OBS_SOURCE_CAP_DISABLED (1<<10)
/** @} */
typedef void (*obs_source_enum_proc_t)(obs_source_t *parent,
@ -201,6 +206,7 @@ struct obs_source_info {
* Gets the default settings for this source
*
* @param[out] settings Data to assign default settings to
* @deprecated Use get_defaults2 if type_data is needed
*/
void (*get_defaults)(obs_data_t *settings);
@ -208,6 +214,7 @@ struct obs_source_info {
* Gets the property information of this source
*
* @return The properties data
* @deprecated Use get_properties2 if type_data is needed
*/
obs_properties_t *(*get_properties)(void *data);
@ -425,6 +432,26 @@ struct obs_source_info {
void (*enum_all_sources)(void *data,
obs_source_enum_proc_t enum_callback,
void *param);
void (*transition_start)(void *data);
void (*transition_stop)(void *data);
/**
* Gets the default settings for this source
*
* @param type_data The type_data variable of this structure
* @param[out] settings Data to assign default settings to
*/
void (*get_defaults2)(void *type_data, obs_data_t *settings);
/**
* Gets the property information of this source
*
* @param data Source data
* @param type_data The type_data variable of this structure
* @return The properties data
*/
obs_properties_t *(*get_properties2)(void *data, void *type_data);
};
EXPORT void obs_register_source_s(const struct obs_source_info *info,

View file

@ -15,6 +15,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <time.h>
#include <stdlib.h>
#include "obs.h"
#include "obs-internal.h"
#include "graphics/vec4.h"
@ -35,9 +38,24 @@ static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time)
delta_time = cur_time - last_time;
seconds = (float)((double)delta_time / 1000000000.0);
/* ------------------------------------- */
/* call tick callbacks */
pthread_mutex_lock(&obs->data.draw_callbacks_mutex);
for (size_t i = obs->data.tick_callbacks.num; i > 0; i--) {
struct tick_callback *callback;
callback = obs->data.tick_callbacks.array + (i - 1);
callback->tick(callback->param, seconds);
}
pthread_mutex_unlock(&obs->data.draw_callbacks_mutex);
/* ------------------------------------- */
/* call the tick function of each source */
pthread_mutex_lock(&data->sources_mutex);
/* call the tick function of each source */
source = data->first_source;
while (source) {
obs_source_video_tick(source, seconds);
@ -108,9 +126,9 @@ static inline void render_main_texture(struct obs_core_video *video,
pthread_mutex_lock(&obs->data.draw_callbacks_mutex);
for (size_t i = 0; i < obs->data.draw_callbacks.num; i++) {
for (size_t i = obs->data.draw_callbacks.num; i > 0; i--) {
struct draw_callback *callback;
callback = obs->data.draw_callbacks.array+i;
callback = obs->data.draw_callbacks.array + (i - 1);
callback->draw(callback->param,
video->base_width, video->base_height);
@ -300,7 +318,7 @@ static inline void stage_output_texture(struct obs_core_video *video,
texture_ready = video->textures_converted[prev_texture];
} else {
texture = video->output_textures[prev_texture];
texture_ready = video->output_textures[prev_texture];
texture_ready = video->textures_output[prev_texture];
}
unmap_last_surface(video);
@ -582,7 +600,7 @@ static inline void output_frame(void)
static const char *tick_sources_name = "tick_sources";
static const char *render_displays_name = "render_displays";
static const char *output_frame_name = "output_frame";
void *obs_video_thread(void *param)
void *obs_graphics_thread(void *param)
{
uint64_t last_time = 0;
uint64_t interval = video_output_get_frame_time(obs->video.video);
@ -596,9 +614,11 @@ void *obs_video_thread(void *param)
const char *video_thread_name =
profile_store_name(obs_get_profiler_name_store(),
"obs_video_thread(%g"NBSP"ms)", interval / 1000000.);
"obs_graphics_thread(%g"NBSP"ms)", interval / 1000000.);
profile_register_root(video_thread_name, interval);
srand((unsigned int)time(NULL));
while (!video_output_stopped(obs->video.video)) {
uint64_t frame_start = os_gettime_ns();
uint64_t frame_time_ns;
@ -609,14 +629,14 @@ void *obs_video_thread(void *param)
last_time = tick_sources(obs->video.video_time, last_time);
profile_end(tick_sources_name);
profile_start(render_displays_name);
render_displays();
profile_end(render_displays_name);
profile_start(output_frame_name);
output_frame();
profile_end(output_frame_name);
profile_start(render_displays_name);
render_displays();
profile_end(render_displays_name);
frame_time_ns = os_gettime_ns() - frame_start;
profile_end(video_thread_name);

View file

@ -15,6 +15,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "util/windows/win-registry.h"
#include "util/windows/win-version.h"
#include "util/platform.h"
#include "util/dstr.h"
@ -22,6 +23,8 @@
#include "obs-internal.h"
#include <windows.h>
#include <wscapi.h>
#include <iwscapi.h>
static uint32_t win_ver = 0;
@ -189,6 +192,187 @@ static void log_aero(void)
aeroMessage);
}
#define WIN10_GAME_BAR_REG_KEY \
L"Software\\Microsoft\\Windows\\CurrentVersion\\GameDVR"
#define WIN10_GAME_DVR_POLICY_REG_KEY \
L"SOFTWARE\\Policies\\Microsoft\\Windows\\GameDVR"
#define WIN10_GAME_DVR_REG_KEY L"System\\GameConfigStore"
#define WIN10_GAME_MODE_REG_KEY L"Software\\Microsoft\\GameBar"
static void log_gaming_features(void)
{
if (win_ver < 0xA00)
return;
struct reg_dword game_bar_enabled;
struct reg_dword game_dvr_allowed;
struct reg_dword game_dvr_enabled;
struct reg_dword game_dvr_bg_recording;
struct reg_dword game_mode_enabled;
get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_BAR_REG_KEY,
L"AppCaptureEnabled", &game_bar_enabled);
get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_DVR_POLICY_REG_KEY,
L"AllowGameDVR", &game_dvr_allowed);
get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_DVR_REG_KEY,
L"GameDVR_Enabled", &game_dvr_enabled);
get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_BAR_REG_KEY,
L"HistoricalCaptureEnabled", &game_dvr_bg_recording);
get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_MODE_REG_KEY,
L"AllowAutoGameMode", &game_mode_enabled);
blog(LOG_INFO, "Windows 10 Gaming Features:");
if (game_bar_enabled.status == ERROR_SUCCESS) {
blog(LOG_INFO, "\tGame Bar: %s",
(bool)game_bar_enabled.return_value ? "On" : "Off");
}
if (game_dvr_allowed.status == ERROR_SUCCESS) {
blog(LOG_INFO, "\tGame DVR Allowed: %s",
(bool)game_dvr_allowed.return_value ? "Yes" : "No");
}
if (game_dvr_enabled.status == ERROR_SUCCESS) {
blog(LOG_INFO, "\tGame DVR: %s",
(bool)game_dvr_enabled.return_value ? "On" : "Off");
}
if (game_dvr_bg_recording.status == ERROR_SUCCESS) {
blog(LOG_INFO, "\tGame DVR Background Recording: %s",
(bool)game_dvr_bg_recording.return_value ? "On" :
"Off");
}
if (game_mode_enabled.status == ERROR_SUCCESS) {
blog(LOG_INFO, "\tGame Mode: %s",
(bool)game_mode_enabled.return_value ? "On" : "Off");
}
}
static const char *get_str_for_state(int state)
{
switch (state) {
case WSC_SECURITY_PRODUCT_STATE_ON:
return "enabled";
case WSC_SECURITY_PRODUCT_STATE_OFF:
return "disabled";
case WSC_SECURITY_PRODUCT_STATE_SNOOZED:
return "temporarily disabled";
case WSC_SECURITY_PRODUCT_STATE_EXPIRED:
return "expired";
default:
return "unknown";
}
}
static const char *get_str_for_type(int type)
{
switch (type) {
case WSC_SECURITY_PROVIDER_ANTIVIRUS:
return "AV";
case WSC_SECURITY_PROVIDER_FIREWALL:
return "FW";
case WSC_SECURITY_PROVIDER_ANTISPYWARE:
return "ASW";
default:
return "unknown";
}
}
static void log_security_products_by_type(IWSCProductList *prod_list, int type)
{
HRESULT hr;
LONG count = 0;
IWscProduct *prod;
BSTR name;
WSC_SECURITY_PRODUCT_STATE prod_state;
hr = prod_list->lpVtbl->Initialize(prod_list, type);
if (FAILED(hr))
return;
hr = prod_list->lpVtbl->get_Count(prod_list, &count);
if (FAILED(hr)) {
prod_list->lpVtbl->Release(prod_list);
return;
}
for (int i = 0; i < count; i++) {
hr = prod_list->lpVtbl->get_Item(prod_list, i, &prod);
if (FAILED(hr))
continue;
hr = prod->lpVtbl->get_ProductName(prod, &name);
if (FAILED(hr))
continue;
hr = prod->lpVtbl->get_ProductState(prod, &prod_state);
if (FAILED(hr)) {
SysFreeString(name);
continue;
}
blog(LOG_INFO, "\t%S: %s (%s)", name,
get_str_for_state(prod_state),
get_str_for_type(type));
SysFreeString(name);
prod->lpVtbl->Release(prod);
}
prod_list->lpVtbl->Release(prod_list);
}
static void log_security_products(void)
{
IWSCProductList *prod_list = NULL;
HMODULE h_wsc;
HRESULT hr;
/* We load the DLL rather than import wcsapi.lib because the clsid /
* iid only exists on Windows 8 or higher. */
h_wsc = LoadLibraryW(L"wscapi.dll");
if (!h_wsc)
return;
const CLSID *prod_list_clsid =
(const CLSID *)GetProcAddress(h_wsc, "CLSID_WSCProductList");
const IID *prod_list_iid =
(const IID *)GetProcAddress(h_wsc, "IID_IWSCProductList");
if (prod_list_clsid && prod_list_iid) {
blog(LOG_INFO, "Sec. Software Status:");
hr = CoCreateInstance(prod_list_clsid, NULL,
CLSCTX_INPROC_SERVER, prod_list_iid,
&prod_list);
if (!FAILED(hr)) {
log_security_products_by_type(prod_list,
WSC_SECURITY_PROVIDER_ANTIVIRUS);
}
hr = CoCreateInstance(prod_list_clsid, NULL,
CLSCTX_INPROC_SERVER, prod_list_iid,
&prod_list);
if (!FAILED(hr)) {
log_security_products_by_type(prod_list,
WSC_SECURITY_PROVIDER_FIREWALL);
}
hr = CoCreateInstance(prod_list_clsid, NULL,
CLSCTX_INPROC_SERVER, prod_list_iid,
&prod_list);
if (!FAILED(hr)) {
log_security_products_by_type(prod_list,
WSC_SECURITY_PROVIDER_ANTISPYWARE);
}
}
FreeLibrary(h_wsc);
}
void log_system_info(void)
{
struct win_version_info ver;
@ -202,6 +386,8 @@ void log_system_info(void)
log_windows_version();
log_admin_status();
log_aero();
log_gaming_features();
log_security_products();
}
@ -328,12 +514,16 @@ static int get_virtual_key(obs_key_t key)
case OBS_KEY_BRACKETRIGHT: return VK_OEM_6;
case OBS_KEY_ASCIITILDE: return VK_OEM_3;
case OBS_KEY_HENKAN: return VK_CONVERT;
case OBS_KEY_MUHENKAN: return VK_NONCONVERT;
case OBS_KEY_KANJI: return VK_KANJI;
case OBS_KEY_TOUROKU: return VK_OEM_FJ_TOUROKU;
case OBS_KEY_MASSYO: return VK_OEM_FJ_MASSHOU;
case OBS_KEY_HANGUL: return VK_HANGUL;
case OBS_KEY_BACKSLASH_RT102: return VK_OEM_102;
case OBS_KEY_MOUSE1: return VK_LBUTTON;
case OBS_KEY_MOUSE2: return VK_RBUTTON;
case OBS_KEY_MOUSE3: return VK_MBUTTON;

View file

@ -385,7 +385,7 @@ static int obs_init_video(struct obs_video_info *ovi)
gs_leave_context();
errorcode = pthread_create(&video->video_thread, NULL,
obs_video_thread, obs);
obs_graphics_thread, obs);
if (errorcode != 0)
return OBS_VIDEO_FAIL;
@ -560,7 +560,7 @@ static bool obs_init_data(void)
goto fail;
if (pthread_mutex_init(&data->services_mutex, &attr) != 0)
goto fail;
if (pthread_mutex_init(&obs->data.draw_callbacks_mutex, NULL) != 0)
if (pthread_mutex_init(&obs->data.draw_callbacks_mutex, &attr) != 0)
goto fail;
if (!obs_view_init(&data->main_view))
goto fail;
@ -619,6 +619,7 @@ static void obs_free_data(void)
pthread_mutex_destroy(&data->services_mutex);
pthread_mutex_destroy(&data->draw_callbacks_mutex);
da_free(data->draw_callbacks);
da_free(data->tick_callbacks);
}
static const char *obs_signals[] = {
@ -809,6 +810,7 @@ bool obs_startup(const char *locale, const char *module_config_path,
void obs_shutdown(void)
{
struct obs_module *module;
struct obs_core *core;
if (!obs)
return;
@ -849,25 +851,27 @@ void obs_shutdown(void)
obs->procs = NULL;
obs->signals = NULL;
module = obs->first_module;
core = obs;
obs = NULL;
module = core->first_module;
while (module) {
struct obs_module *next = module->next;
free_module(module);
module = next;
}
obs->first_module = NULL;
core->first_module = NULL;
for (size_t i = 0; i < obs->module_paths.num; i++)
free_module_path(obs->module_paths.array+i);
da_free(obs->module_paths);
for (size_t i = 0; i < core->module_paths.num; i++)
free_module_path(core->module_paths.array+i);
da_free(core->module_paths);
if (obs->name_store_owned)
profiler_name_store_free(obs->name_store);
if (core->name_store_owned)
profiler_name_store_free(core->name_store);
bfree(obs->module_config_path);
bfree(obs->locale);
bfree(obs);
obs = NULL;
bfree(core->module_config_path);
bfree(core->locale);
bfree(core);
#ifdef _WIN32
uninitialize_com();
@ -884,6 +888,11 @@ uint32_t obs_get_version(void)
return LIBOBS_API_VER;
}
const char *obs_get_version_string(void)
{
return OBS_VERSION;
}
void obs_set_locale(const char *locale)
{
struct obs_module *module;
@ -1425,6 +1434,32 @@ void obs_render_main_view(void)
obs_view_render(&obs->data.main_view);
}
void obs_render_main_texture(void)
{
struct obs_core_video *video = &obs->video;
gs_texture_t *tex;
gs_effect_t *effect;
gs_eparam_t *param;
int last_tex;
if (!obs) return;
last_tex = video->cur_texture == 0
? NUM_TEXTURES - 1
: video->cur_texture - 1;
if (!video->textures_rendered[last_tex])
return;
tex = video->render_textures[last_tex];
effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
param = gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture(param, tex);
while (gs_effect_loop(effect, "Draw"))
gs_draw_sprite(tex, 0, 0, 0);
}
void obs_set_master_volume(float volume)
{
struct calldata data = {0};
@ -1514,6 +1549,12 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data)
obs_source_set_monitoring_type(source,
(enum obs_monitoring_type)monitoring_type);
obs_data_release(source->private_settings);
source->private_settings =
obs_data_get_obj(source_data, "private_settings");
if (!source->private_settings)
source->private_settings = obs_data_create();
if (filters) {
size_t count = obs_data_array_count(filters);
@ -1642,6 +1683,9 @@ obs_data_t *obs_save_source(obs_source_t *source)
obs_data_set_int (source_data, "deinterlace_field_order", di_order);
obs_data_set_int (source_data, "monitoring_type", m_type);
obs_data_set_obj(source_data, "private_settings",
source->private_settings);
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION)
obs_transition_save(source, source_data);
@ -1898,7 +1942,7 @@ bool obs_set_audio_monitoring_device(const char *name, const char *id)
if (!obs || !name || !id || !*name || !*id)
return false;
#ifdef _WIN32
#if defined(_WIN32) || HAVE_PULSEAUDIO
pthread_mutex_lock(&obs->audio.monitoring_mutex);
if (strcmp(id, obs->audio.monitoring_device_id) == 0) {
@ -1937,6 +1981,34 @@ void obs_get_audio_monitoring_device(const char **name, const char **id)
*id = obs->audio.monitoring_device_id;
}
void obs_add_tick_callback(
void (*tick)(void *param, float seconds),
void *param)
{
if (!obs)
return;
struct tick_callback data = {tick, param};
pthread_mutex_lock(&obs->data.draw_callbacks_mutex);
da_insert(obs->data.tick_callbacks, 0, &data);
pthread_mutex_unlock(&obs->data.draw_callbacks_mutex);
}
void obs_remove_tick_callback(
void (*tick)(void *param, float seconds),
void *param)
{
if (!obs)
return;
struct tick_callback data = {tick, param};
pthread_mutex_lock(&obs->data.draw_callbacks_mutex);
da_erase_item(obs->data.tick_callbacks, &data);
pthread_mutex_unlock(&obs->data.draw_callbacks_mutex);
}
void obs_add_main_render_callback(
void (*draw)(void *param, uint32_t cx, uint32_t cy),
void *param)
@ -1947,7 +2019,7 @@ void obs_add_main_render_callback(
struct draw_callback data = {draw, param};
pthread_mutex_lock(&obs->data.draw_callbacks_mutex);
da_push_back(obs->data.draw_callbacks, &data);
da_insert(obs->data.draw_callbacks, 0, &data);
pthread_mutex_unlock(&obs->data.draw_callbacks_mutex);
}

View file

@ -145,6 +145,7 @@ struct obs_transform_info {
struct vec2 bounds;
};
#ifndef SWIG
/**
* Video initialization structure
*/
@ -175,6 +176,7 @@ struct obs_video_info {
enum obs_scale_type scale_type; /**< How to scale if scaling */
};
#endif
/**
* Audio initialization structure
@ -260,6 +262,9 @@ EXPORT bool obs_initialized(void);
/** @return The current core version */
EXPORT uint32_t obs_get_version(void);
/** @return The current core version string */
EXPORT const char *obs_get_version_string(void);
/**
* Sets a new locale to use for modules. This will call obs_module_set_locale
* for each module with the new locale.
@ -278,6 +283,7 @@ EXPORT const char *obs_get_locale(void);
*/
EXPORT profiler_name_store_t *obs_get_profiler_name_store(void);
#ifndef SWIG
/**
* Sets base video output base resolution/fps/format.
*
@ -295,6 +301,7 @@ EXPORT profiler_name_store_t *obs_get_profiler_name_store(void);
* OBS_VIDEO_FAIL for generic failure
*/
EXPORT int obs_reset_video(struct obs_video_info *ovi);
#endif
/**
* Sets base audio output format/channels/samples/etc
@ -303,8 +310,10 @@ EXPORT int obs_reset_video(struct obs_video_info *ovi);
*/
EXPORT bool obs_reset_audio(const struct obs_audio_info *oai);
#ifndef SWIG
/** Gets the current video settings, returns false if no video */
EXPORT bool obs_get_video_info(struct obs_video_info *ovi);
#endif
/** Gets the current audio settings, returns false if no audio */
EXPORT bool obs_get_audio_info(struct obs_audio_info *oai);
@ -374,6 +383,11 @@ EXPORT void obs_add_module_path(const char *bin, const char *data);
/** Automatically loads all modules from module paths (convenience function) */
EXPORT void obs_load_all_modules(void);
/** Notifies modules that all modules have been loaded. This function should
* be called after all modules have been loaded. */
EXPORT void obs_post_load_modules(void);
#ifndef SWIG
struct obs_module_info {
const char *bin_path;
const char *data_path;
@ -384,6 +398,7 @@ typedef void (*obs_find_module_callback_t)(void *param,
/** Finds all modules within the search paths added by obs_add_module_path. */
EXPORT void obs_find_modules(obs_find_module_callback_t callback, void *param);
#endif
typedef void (*obs_enum_module_callback_t)(void *param, obs_module_t *module);
@ -532,9 +547,11 @@ enum obs_base_effect {
/** Returns a commonly used base effect */
EXPORT gs_effect_t *obs_get_base_effect(enum obs_base_effect effect);
#ifndef SWIG
/* DEPRECATED: gets texture_rect default effect */
DEPRECATED
EXPORT gs_effect_t *obs_get_default_rect_effect(void);
#endif
/** Returns the primary obs signal handler */
EXPORT signal_handler_t *obs_get_signal_handler(void);
@ -542,8 +559,14 @@ EXPORT signal_handler_t *obs_get_signal_handler(void);
/** Returns the primary obs procedure handler */
EXPORT proc_handler_t *obs_get_proc_handler(void);
#ifndef SWIG
/** Renders the main view */
DEPRECATED
EXPORT void obs_render_main_view(void);
#endif
/** Renders the last main output texture */
EXPORT void obs_render_main_texture(void);
/** Sets the master user volume */
EXPORT void obs_set_master_volume(float volume);
@ -591,6 +614,13 @@ EXPORT void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb,
EXPORT bool obs_set_audio_monitoring_device(const char *name, const char *id);
EXPORT void obs_get_audio_monitoring_device(const char **name, const char **id);
EXPORT void obs_add_tick_callback(
void (*tick)(void *param, float seconds),
void *param);
EXPORT void obs_remove_tick_callback(
void (*tick)(void *param, float seconds),
void *param);
EXPORT void obs_add_main_render_callback(
void (*draw)(void *param, uint32_t cx, uint32_t cy),
void *param);
@ -950,6 +980,10 @@ EXPORT void obs_source_set_monitoring_type(obs_source_t *source,
EXPORT enum obs_monitoring_type obs_source_get_monitoring_type(
const obs_source_t *source);
/** Gets private front-end settings data. This data is saved/loaded
* automatically. Returns an incremented reference. */
EXPORT obs_data_t *obs_source_get_private_settings(obs_source_t *item);
/* ------------------------------------------------------------------------- */
/* Functions used by sources */
@ -1107,6 +1141,12 @@ EXPORT void obs_source_set_async_unbuffered(obs_source_t *source,
bool unbuffered);
EXPORT bool obs_source_async_unbuffered(const obs_source_t *source);
/** Used to decouple audio from video so that audio doesn't attempt to sync up
* with video. I.E. Audio acts independently. Only works when in unbuffered
* mode. */
EXPORT void obs_source_set_async_decoupled(obs_source_t *source, bool decouple);
EXPORT bool obs_source_async_decoupled(const obs_source_t *source);
/* ------------------------------------------------------------------------- */
/* Transition-specific functions */
enum obs_transition_target {
@ -1165,9 +1205,16 @@ typedef void (*obs_transition_video_render_callback_t)(void *data,
uint32_t cx, uint32_t cy);
typedef float (*obs_transition_audio_mix_callback_t)(void *data, float t);
EXPORT float obs_transition_get_time(obs_source_t *transition);
EXPORT void obs_transition_video_render(obs_source_t *transition,
obs_transition_video_render_callback_t callback);
/** Directly renders its sub-source instead of to texture. Returns false if no
* longer transitioning */
EXPORT bool obs_transition_video_render_direct(obs_source_t *transition,
enum obs_transition_target target);
EXPORT bool obs_transition_audio_render(obs_source_t *transition,
uint64_t *ts_out, struct obs_source_audio_mix *audio,
uint32_t mixers, size_t channels, size_t sample_rate,
@ -1251,8 +1298,12 @@ EXPORT obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item);
/** Gets the source of a scene item. */
EXPORT obs_source_t *obs_sceneitem_get_source(const obs_sceneitem_t *item);
/* FIXME: The following functions should be deprecated and replaced with a way
* to specify savable private user data. -Jim */
EXPORT void obs_sceneitem_select(obs_sceneitem_t *item, bool select);
EXPORT bool obs_sceneitem_selected(const obs_sceneitem_t *item);
EXPORT bool obs_sceneitem_locked(const obs_sceneitem_t *item);
EXPORT bool obs_sceneitem_set_locked(obs_sceneitem_t *item, bool lock);
/* Functions for getting/setting specific orientation of a scene item */
EXPORT void obs_sceneitem_set_pos(obs_sceneitem_t *item, const struct vec2 *pos);
@ -1320,6 +1371,10 @@ EXPORT enum obs_scale_type obs_sceneitem_get_scale_filter(
EXPORT void obs_sceneitem_defer_update_begin(obs_sceneitem_t *item);
EXPORT void obs_sceneitem_defer_update_end(obs_sceneitem_t *item);
/** Gets private front-end settings data. This data is saved/loaded
* automatically. Returns an incremented reference. */
EXPORT obs_data_t *obs_sceneitem_get_private_settings(obs_sceneitem_t *item);
/* ------------------------------------------------------------------------- */
/* Outputs */
@ -1519,6 +1574,12 @@ EXPORT bool obs_output_reconnecting(const obs_output_t *output);
/** Pass a string of the last output error, for UI use */
EXPORT void obs_output_set_last_error(obs_output_t *output,
const char *message);
EXPORT const char *obs_output_get_last_error(obs_output_t *output);
EXPORT const char *obs_output_get_supported_video_codecs(
const obs_output_t *output);
EXPORT const char *obs_output_get_supported_audio_codecs(
const obs_output_t *output);
/* ------------------------------------------------------------------------- */
/* Functions used by outputs */
@ -1710,6 +1771,7 @@ EXPORT const char *obs_encoder_get_id(const obs_encoder_t *encoder);
EXPORT uint32_t obs_get_encoder_caps(const char *encoder_id);
#ifndef SWIG
/** Duplicates an encoder packet */
DEPRECATED
EXPORT void obs_duplicate_encoder_packet(struct encoder_packet *dst,
@ -1717,6 +1779,7 @@ EXPORT void obs_duplicate_encoder_packet(struct encoder_packet *dst,
DEPRECATED
EXPORT void obs_free_encoder_packet(struct encoder_packet *packet);
#endif
EXPORT void obs_encoder_packet_ref(struct encoder_packet *dst,
struct encoder_packet *src);
@ -1800,6 +1863,10 @@ EXPORT void *obs_service_get_type_data(obs_service_t *service);
EXPORT const char *obs_service_get_id(const obs_service_t *service);
/* NOTE: This function is temporary and should be removed/replaced at a later
* date. */
EXPORT const char *obs_service_get_output_type(const obs_service_t *service);
/* ------------------------------------------------------------------------- */
/* Source frame allocation functions */

View file

@ -17,3 +17,7 @@
#define OBS_UNIX_STRUCTURE @OBS_UNIX_STRUCTURE@
#define BUILD_CAPTIONS @BUILD_CAPTIONS@
#define HAVE_DBUS @HAVE_DBUS@
#define HAVE_PULSEAUDIO @HAVE_PULSEAUDIO@
#define LIBOBS_IMAGEMAGICK_DIR_STYLE_6L 6
#define LIBOBS_IMAGEMAGICK_DIR_STYLE_7GE 7
#define LIBOBS_IMAGEMAGICK_DIR_STYLE @LIBOBS_IMAGEMAGICK_DIR_STYLE@

View file

@ -76,7 +76,7 @@ EXPORT void base_set_crash_handler(
EXPORT void blogva(int log_level, const char *format, va_list args);
#ifndef _MSC_VER
#if !defined(_MSC_VER) && !defined(SWIG)
#define PRINTFATTR(f, a) __attribute__((__format__(__printf__, f, a)))
#else
#define PRINTFATTR(f, a)

View file

@ -189,6 +189,48 @@ static inline void circlebuf_push_front(struct circlebuf *cb, const void *data,
}
}
static inline void circlebuf_push_back_zero(struct circlebuf *cb, size_t size)
{
size_t new_end_pos = cb->end_pos + size;
cb->size += size;
circlebuf_ensure_capacity(cb);
if (new_end_pos > cb->capacity) {
size_t back_size = cb->capacity - cb->end_pos;
size_t loop_size = size - back_size;
if (back_size)
memset((uint8_t*)cb->data + cb->end_pos, 0, back_size);
memset(cb->data, 0, loop_size);
new_end_pos -= cb->capacity;
} else {
memset((uint8_t*)cb->data + cb->end_pos, 0, size);
}
cb->end_pos = new_end_pos;
}
static inline void circlebuf_push_front_zero(struct circlebuf *cb, size_t size)
{
cb->size += size;
circlebuf_ensure_capacity(cb);
if (cb->start_pos < size) {
size_t back_size = size - cb->start_pos;
if (cb->start_pos)
memset(cb->data, 0, cb->start_pos);
cb->start_pos = cb->capacity - back_size;
memset((uint8_t*)cb->data + cb->start_pos, 0, back_size);
} else {
cb->start_pos -= size;
memset((uint8_t*)cb->data + cb->start_pos, 0, size);
}
}
static inline void circlebuf_peek_front(struct circlebuf *cb, void *data,
size_t size)
{
@ -237,6 +279,11 @@ static inline void circlebuf_pop_front(struct circlebuf *cb, void *data,
circlebuf_peek_front(cb, data, size);
cb->size -= size;
if (!cb->size) {
cb->start_pos = cb->end_pos = 0;
return;
}
cb->start_pos += size;
if (cb->start_pos >= cb->capacity)
cb->start_pos -= cb->capacity;
@ -248,6 +295,11 @@ static inline void circlebuf_pop_back(struct circlebuf *cb, void *data,
circlebuf_peek_front(cb, data, size);
cb->size -= size;
if (!cb->size) {
cb->start_pos = cb->end_pos = 0;
return;
}
if (cb->end_pos <= size)
cb->end_pos = cb->capacity - (size - cb->end_pos);
else

View file

@ -262,49 +262,78 @@ wchar_t *wcsdepad(wchar_t *str)
char **strlist_split(const char *str, char split_ch, bool include_empty)
{
const char *cur_str = str;
const char *next_str;
const char *new_str;
DARRAY(char*) list;
da_init(list);
const char *cur_str = str;
const char *next_str;
char * out = NULL;
size_t count = 0;
size_t total_size = 0;
if (str) {
char **table;
char *offset;
size_t cur_idx = 0;
size_t cur_pos = 0;
next_str = strchr(str, split_ch);
while (next_str) {
size_t size = next_str - cur_str;
if (size || include_empty) {
new_str = bstrdup_n(cur_str, size);
da_push_back(list, &new_str);
++count;
total_size += size + 1;
}
cur_str = next_str+1;
cur_str = next_str + 1;
next_str = strchr(cur_str, split_ch);
}
if (*cur_str || include_empty) {
new_str = bstrdup(cur_str);
da_push_back(list, &new_str);
++count;
total_size += strlen(cur_str) + 1;
}
/* ------------------ */
cur_pos = (count + 1) * sizeof(char *);
total_size += cur_pos;
out = bmalloc(total_size);
offset = out + cur_pos;
table = (char **)out;
/* ------------------ */
next_str = strchr(str, split_ch);
cur_str = str;
while (next_str) {
size_t size = next_str - cur_str;
if (size || include_empty) {
table[cur_idx++] = offset;
strncpy(offset, cur_str, size);
offset[size] = 0;
offset += size + 1;
}
cur_str = next_str + 1;
next_str = strchr(cur_str, split_ch);
}
if (*cur_str || include_empty) {
table[cur_idx++] = offset;
strcpy(offset, cur_str);
}
table[cur_idx] = NULL;
}
new_str = NULL;
da_push_back(list, &new_str);
return list.array;
return (char**)out;
}
void strlist_free(char **strlist)
{
if (strlist) {
char **temp = strlist;
while (*temp)
bfree(*(temp++));
bfree(strlist);
}
bfree(strlist);
}
void dstr_init_copy_strref(struct dstr *dst, const struct strref *src)

View file

@ -352,3 +352,68 @@ int os_get_logical_cores(void)
os_get_cores_internal();
return logical_cores;
}
static inline bool os_get_sys_memory_usage_internal(vm_statistics_t vmstat)
{
mach_msg_type_number_t out_count = HOST_VM_INFO_COUNT;
if (host_statistics(mach_host_self(), HOST_VM_INFO,
(host_info_t)vmstat, &out_count) != KERN_SUCCESS)
return false;
return true;
}
uint64_t os_get_sys_free_size(void)
{
vm_statistics_data_t vmstat = {};
if (!os_get_sys_memory_usage_internal(&vmstat))
return 0;
return vmstat.free_count * vm_page_size;
}
#ifndef MACH_TASK_BASIC_INFO
typedef task_basic_info_data_t mach_task_basic_info_data_t;
#endif
static inline bool os_get_proc_memory_usage_internal(
mach_task_basic_info_data_t *taskinfo)
{
#ifdef MACH_TASK_BASIC_INFO
const task_flavor_t flavor = MACH_TASK_BASIC_INFO;
mach_msg_type_number_t out_count = MACH_TASK_BASIC_INFO_COUNT;
#else
const task_flavor_t flavor = TASK_BASIC_INFO;
mach_msg_type_number_t out_count = TASK_BASIC_INFO_COUNT;
#endif
if (task_info(mach_task_self(), flavor,
(task_info_t)taskinfo, &out_count) != KERN_SUCCESS)
return false;
return true;
}
bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage)
{
mach_task_basic_info_data_t taskinfo = {};
if (!os_get_proc_memory_usage_internal(&taskinfo))
return false;
usage->resident_size = taskinfo.resident_size;
usage->virtual_size = taskinfo.virtual_size;
return true;
}
uint64_t os_get_proc_resident_size(void)
{
mach_task_basic_info_data_t taskinfo = {};
if (!os_get_proc_memory_usage_internal(&taskinfo))
return 0;
return taskinfo.resident_size;
}
uint64_t os_get_proc_virtual_size(void)
{
mach_task_basic_info_data_t taskinfo = {};
if (!os_get_proc_memory_usage_internal(&taskinfo))
return 0;
return taskinfo.virtual_size;
}

View file

@ -33,6 +33,16 @@
#if !defined(__APPLE__)
#include <sys/times.h>
#include <sys/wait.h>
#ifdef __FreeBSD__
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/user.h>
#include <unistd.h>
#include <libprocstat.h>
#else
#include <sys/resource.h>
#endif
#include <spawn.h>
#endif
@ -628,8 +638,6 @@ static int physical_cores = 0;
static int logical_cores = 0;
static bool core_count_initialized = false;
/* return sysconf(_SC_NPROCESSORS_ONLN); */
static void os_get_cores_internal(void)
{
if (core_count_initialized)
@ -639,29 +647,120 @@ static void os_get_cores_internal(void)
logical_cores = sysconf(_SC_NPROCESSORS_ONLN);
#ifndef __linux__
physical_cores = logical_cores;
#else
char *text = os_quick_read_utf8_file("/proc/cpuinfo");
char *core_id = text;
#if defined(__linux__)
int physical_id = -1;
int last_physical_id = -1;
int core_count = 0;
char *line = NULL;
size_t linecap = 0;
FILE *fp;
struct dstr proc_phys_id;
struct dstr proc_phys_ids;
fp = fopen("/proc/cpuinfo", "r");
if (!fp)
return;
dstr_init(&proc_phys_id);
dstr_init(&proc_phys_ids);
while (getline(&line, &linecap, fp) != -1) {
if (!strncmp(line, "physical id", 11)) {
char *start = strchr(line, ':');
if (!start || *(++start) == '\0')
continue;
physical_id = atoi(start);
dstr_free(&proc_phys_id);
dstr_init(&proc_phys_id);
dstr_catf(&proc_phys_id, "%d", physical_id);
}
if (!strncmp(line, "cpu cores", 9)) {
char *start = strchr(line, ':');
if (!start || *(++start) == '\0')
continue;
if (dstr_is_empty(&proc_phys_ids) ||
(!dstr_is_empty(&proc_phys_ids) &&
!dstr_find(&proc_phys_ids, proc_phys_id.array))) {
dstr_cat_dstr(&proc_phys_ids, &proc_phys_id);
dstr_cat(&proc_phys_ids, " ");
core_count += atoi(start);
}
}
if (*line == '\n' && physical_id != last_physical_id) {
last_physical_id = physical_id;
}
}
if (core_count == 0)
physical_cores = logical_cores;
else
physical_cores = core_count;
fclose(fp);
dstr_free(&proc_phys_ids);
dstr_free(&proc_phys_id);
free(line);
#elif defined(__FreeBSD__)
char *text = os_quick_read_utf8_file("/var/run/dmesg.boot");
char *core_count = text;
int packages = 0;
int cores = 0;
struct dstr proc_packages;
struct dstr proc_cores;
dstr_init(&proc_packages);
dstr_init(&proc_cores);
if (!text || !*text) {
physical_cores = logical_cores;
return;
}
for (;;) {
core_id = strstr(core_id, "\ncore id");
if (!core_id)
break;
physical_cores++;
core_id++;
}
core_count = strstr(core_count, "\nFreeBSD/SMP: ");
if (!core_count)
goto FreeBSD_cores_cleanup;
if (physical_cores == 0)
core_count++;
core_count = strstr(core_count, "\nFreeBSD/SMP: ");
if (!core_count)
goto FreeBSD_cores_cleanup;
core_count = strstr(core_count, ": ");
core_count += 2;
size_t len = strcspn(core_count, " ");
dstr_ncopy(&proc_packages, core_count, len);
core_count = strstr(core_count, "package(s) x ");
if (!core_count)
goto FreeBSD_cores_cleanup;
core_count += 13;
len = strcspn(core_count, " ");
dstr_ncopy(&proc_cores, core_count, len);
FreeBSD_cores_cleanup:
if (!dstr_is_empty(&proc_packages))
packages = atoi(proc_packages.array);
if (!dstr_is_empty(&proc_cores))
cores = atoi(proc_cores.array);
if (packages == 0)
physical_cores = logical_cores;
else if (cores == 0)
physical_cores = packages;
else
physical_cores = packages * cores;
dstr_free(&proc_cores);
dstr_free(&proc_packages);
bfree(text);
#else
physical_cores = logical_cores;
#endif
}
@ -678,6 +777,120 @@ int os_get_logical_cores(void)
os_get_cores_internal();
return logical_cores;
}
#ifdef __FreeBSD__
uint64_t os_get_sys_free_size(void)
{
uint64_t mem_free = 0;
size_t length = sizeof(mem_free);
if (sysctlbyname("vm.stats.vm.v_free_count", &mem_free, &length,
NULL, 0) < 0)
return 0;
return mem_free;
}
static inline bool os_get_proc_memory_usage_internal(struct kinfo_proc *kinfo)
{
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
size_t length = sizeof(*kinfo);
if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), kinfo, &length,
NULL, 0) < 0)
return false;
return true;
}
bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage)
{
struct kinfo_proc kinfo;
if (!os_get_proc_memory_usage_internal(&kinfo))
return false;
usage->resident_size =
(uint64_t)kinfo.ki_rssize * sysconf(_SC_PAGESIZE);
usage->virtual_size = (uint64_t)kinfo.ki_size;
return true;
}
uint64_t os_get_proc_resident_size(void)
{
struct kinfo_proc kinfo;
if (!os_get_proc_memory_usage_internal(&kinfo))
return 0;
return (uint64_t)kinfo.ki_rssize * sysconf(_SC_PAGESIZE);
}
uint64_t os_get_proc_virtual_size(void)
{
struct kinfo_proc kinfo;
if (!os_get_proc_memory_usage_internal(&kinfo))
return 0;
return (uint64_t)kinfo.ki_size;
}
#else
uint64_t os_get_sys_free_size(void) {return 0;}
typedef struct
{
unsigned long virtual_size;
unsigned long resident_size;
unsigned long share_pages;
unsigned long text;
unsigned long library;
unsigned long data;
unsigned long dirty_pages;
} statm_t;
static inline bool os_get_proc_memory_usage_internal(statm_t *statm)
{
const char *statm_path = "/proc/self/statm";
FILE *f = fopen(statm_path, "r");
if (!f)
return false;
if (fscanf(f, "%ld %ld %ld %ld %ld %ld %ld",
&statm->virtual_size,
&statm->resident_size,
&statm->share_pages,
&statm->text,
&statm->library,
&statm->data,
&statm->dirty_pages) != 7) {
fclose(f);
return false;
}
fclose(f);
return true;
}
bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage)
{
statm_t statm = {};
if (!os_get_proc_memory_usage_internal(&statm))
return false;
usage->resident_size = statm.resident_size;
usage->virtual_size = statm.virtual_size;
return true;
}
uint64_t os_get_proc_resident_size(void)
{
statm_t statm = {};
if (!os_get_proc_memory_usage_internal(&statm))
return 0;
return (uint64_t)statm.resident_size;
}
uint64_t os_get_proc_virtual_size(void)
{
statm_t statm = {};
if (!os_get_proc_memory_usage_internal(&statm))
return 0;
return (uint64_t)statm.virtual_size;
}
#endif
#endif
uint64_t os_get_free_disk_space(const char *dir)

View file

@ -14,16 +14,19 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define PSAPI_VERSION 1
#include <windows.h>
#include <mmsystem.h>
#include <shellapi.h>
#include <shlobj.h>
#include <intrin.h>
#include <psapi.h>
#include "base.h"
#include "platform.h"
#include "darray.h"
#include "dstr.h"
#include "windows/win-registry.h"
#include "windows/win-version.h"
#include "../../deps/w32-pthreads/pthread.h"
@ -47,7 +50,7 @@ static inline uint32_t get_winver(void)
winver = (ver.major << 16) | ver.minor;
}
return winver;
return winver;
}
void *os_dlopen(const char *path)
@ -798,6 +801,32 @@ bool is_64_bit_windows(void)
#endif
}
void get_reg_dword(HKEY hkey, LPCWSTR sub_key, LPCWSTR value_name,
struct reg_dword *info)
{
struct reg_dword reg = {0};
HKEY key;
LSTATUS status;
status = RegOpenKeyEx(hkey, sub_key, 0, KEY_READ, &key);
if (status != ERROR_SUCCESS) {
info->status = status;
info->size = 0;
info->return_value = 0;
return;
}
reg.size = sizeof(reg.return_value);
reg.status = RegQueryValueExW(key, value_name, NULL, NULL,
(LPBYTE)&reg.return_value, &reg.size);
RegCloseKey(key);
*info = reg;
}
#define WINVER_REG_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
void get_win_ver(struct win_version_info *info)
@ -812,7 +841,7 @@ void get_win_ver(struct win_version_info *info)
get_dll_ver(L"kernel32", &ver);
got_version = true;
if (ver.major == 10 && ver.revis == 0) {
if (ver.major == 10) {
HKEY key;
DWORD size, win10_revision;
LSTATUS status;
@ -827,7 +856,8 @@ void get_win_ver(struct win_version_info *info)
status = RegQueryValueExW(key, L"UBR", NULL, NULL,
(LPBYTE)&win10_revision, &size);
if (status == ERROR_SUCCESS)
ver.revis = (int)win10_revision;
ver.revis = (int)win10_revision > ver.revis ?
(int)win10_revision : ver.revis;
RegCloseKey(key);
}
@ -947,6 +977,55 @@ int os_get_logical_cores(void)
return logical_cores;
}
static inline bool os_get_sys_memory_usage_internal(MEMORYSTATUSEX *msex)
{
if (!GlobalMemoryStatusEx(msex))
return false;
return true;
}
uint64_t os_get_sys_free_size(void)
{
MEMORYSTATUSEX msex = {sizeof(MEMORYSTATUSEX)};
if (!os_get_sys_memory_usage_internal(&msex))
return 0;
return msex.ullAvailPhys;
}
static inline bool os_get_proc_memory_usage_internal(PROCESS_MEMORY_COUNTERS *pmc)
{
if (!GetProcessMemoryInfo(GetCurrentProcess(), pmc, sizeof(*pmc)))
return false;
return true;
}
bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage)
{
PROCESS_MEMORY_COUNTERS pmc = {sizeof(PROCESS_MEMORY_COUNTERS)};
if (!os_get_proc_memory_usage_internal(&pmc))
return false;
usage->resident_size = pmc.WorkingSetSize;
usage->virtual_size = pmc.PagefileUsage;
return true;
}
uint64_t os_get_proc_resident_size(void)
{
PROCESS_MEMORY_COUNTERS pmc = {sizeof(PROCESS_MEMORY_COUNTERS)};
if (!os_get_proc_memory_usage_internal(&pmc))
return 0;
return pmc.WorkingSetSize;
}
uint64_t os_get_proc_virtual_size(void)
{
PROCESS_MEMORY_COUNTERS pmc = {sizeof(PROCESS_MEMORY_COUNTERS)};
if (!os_get_proc_memory_usage_internal(&pmc))
return 0;
return pmc.PagefileUsage;
}
uint64_t os_get_free_disk_space(const char *dir)
{
wchar_t *wdir = NULL;

View file

@ -183,7 +183,8 @@ size_t os_fread_utf8(FILE *file, char **pstr)
/* remove the ghastly BOM if present */
fseek(file, 0, SEEK_SET);
fread(bom, 1, 3, file);
size_t size_read = fread(bom, 1, 3, file);
(void)size_read;
offset = (astrcmp_n(bom, "\xEF\xBB\xBF", 3) == 0) ? 3 : 0;

View file

@ -181,6 +181,18 @@ EXPORT void os_breakpoint(void);
EXPORT int os_get_physical_cores(void);
EXPORT int os_get_logical_cores(void);
EXPORT uint64_t os_get_sys_free_size(void);
struct os_proc_memory_usage {
uint64_t resident_size;
uint64_t virtual_size;
};
typedef struct os_proc_memory_usage os_proc_memory_usage_t;
EXPORT bool os_get_proc_memory_usage(os_proc_memory_usage_t *usage);
EXPORT uint64_t os_get_proc_resident_size(void);
EXPORT uint64_t os_get_proc_virtual_size(void);
#ifdef _MSC_VER
#define strtoll _strtoi64
#if _MSC_VER < 1900

View file

@ -260,13 +260,8 @@ static bool enabled = false;
static pthread_mutex_t root_mutex = PTHREAD_MUTEX_INITIALIZER;
static DARRAY(profile_root_entry) root_entries;
#ifdef _MSC_VER
static __declspec(thread) profile_call *thread_context = NULL;
static __declspec(thread) bool thread_enabled = true;
#else
static __thread profile_call *thread_context = NULL;
static __thread bool thread_enabled = true;
#endif
static THREAD_LOCAL profile_call *thread_context = NULL;
static THREAD_LOCAL bool thread_enabled = true;
void profiler_start(void)
{

View file

@ -78,6 +78,12 @@ EXPORT int os_sem_wait(os_sem_t *sem);
EXPORT void os_set_thread_name(const char *name);
#ifdef _MSC_VER
#define THREAD_LOCAL __declspec(thread)
#else
#define THREAD_LOCAL __thread
#endif
#ifdef __cplusplus
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2015 Hugh Bailey <obs.jim@gmail.com>
* Copyright (c) 2017 Ryan Foster <RytoEX@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <windows.h>
#include "../c99defs.h"
#ifdef __cplusplus
extern "C" {
#endif
struct reg_dword {
LSTATUS status;
DWORD size;
DWORD return_value;
};
EXPORT void get_reg_dword(HKEY hkey, LPCWSTR sub_key, LPCWSTR value_name,
struct reg_dword *info);
#ifdef __cplusplus
}
#endif