New upstream version 21.0.2+dfsg1
This commit is contained in:
parent
1f1bbb3518
commit
baafb6325b
706 changed files with 49633 additions and 5044 deletions
|
|
@ -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}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
33
libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c
Normal file
33
libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c
Normal 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);
|
||||
}
|
||||
552
libobs/audio-monitoring/pulse/pulseaudio-output.c
Normal file
552
libobs/audio-monitoring/pulse/pulseaudio-output.c
Normal 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);
|
||||
}
|
||||
}
|
||||
341
libobs/audio-monitoring/pulse/pulseaudio-wrapper.c
Normal file
341
libobs/audio-monitoring/pulse/pulseaudio-wrapper.c
Normal 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();
|
||||
}
|
||||
185
libobs/audio-monitoring/pulse/pulseaudio-wrapper.h
Normal file
185
libobs/audio-monitoring/pulse/pulseaudio-wrapper.h
Normal 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);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 { \
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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*));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) | \
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
102
libobs/obs.c
102
libobs/obs.c
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
67
libobs/obs.h
67
libobs/obs.h
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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@
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)®.return_value, ®.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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
38
libobs/util/windows/win-registry.h
Normal file
38
libobs/util/windows/win-registry.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue