New upstream version 18.0.1+dfsg1
This commit is contained in:
parent
6efda2859e
commit
f2cf6cce50
1337 changed files with 41178 additions and 84670 deletions
|
|
@ -68,6 +68,13 @@ if(WIN32)
|
|||
util/windows/CoTaskMemPtr.hpp
|
||||
util/windows/HRError.hpp
|
||||
util/windows/WinHandle.hpp)
|
||||
set(libobs_audio_monitoring_SOURCES
|
||||
audio-monitoring/win32/wasapi-output.c
|
||||
audio-monitoring/win32/wasapi-enum-devices.c
|
||||
)
|
||||
set(libobs_audio_monitoring_HEADERS
|
||||
audio-monitoring/win32/wasapi-output.h
|
||||
)
|
||||
set(libobs_PLATFORM_DEPS winmm)
|
||||
if(MSVC)
|
||||
set(libobs_PLATFORM_DEPS
|
||||
|
|
@ -83,6 +90,13 @@ elseif(APPLE)
|
|||
util/platform-cocoa.m)
|
||||
set(libobs_PLATFORM_HEADERS
|
||||
util/threading-posix.h)
|
||||
set(libobs_audio_monitoring_SOURCES
|
||||
audio-monitoring/osx/coreaudio-enum-devices.c
|
||||
audio-monitoring/osx/coreaudio-output.c
|
||||
)
|
||||
set(libobs_audio_monitoring_HEADERS
|
||||
audio-monitoring/osx/mac-helpers.h
|
||||
)
|
||||
|
||||
set_source_files_properties(${libobs_PLATFORM_SOURCES}
|
||||
PROPERTIES
|
||||
|
|
@ -93,6 +107,18 @@ elseif(APPLE)
|
|||
mark_as_advanced(COCOA)
|
||||
include_directories(${COCOA})
|
||||
|
||||
find_library(COREAUDIO CoreAudio)
|
||||
mark_as_advanced(COREAUDIO)
|
||||
include_directories(${COREAUDIO})
|
||||
|
||||
find_library(AUDIOTOOLBOX AudioToolbox)
|
||||
mark_as_advanced(AUDIOTOOLBOX)
|
||||
include_directories(${AUDIOTOOLBOX})
|
||||
|
||||
find_library(AUDIOUNIT AudioUnit)
|
||||
mark_as_advanced(AUDIOUNIT)
|
||||
include_directories(${AUDIOUNIT})
|
||||
|
||||
find_library(APPKIT AppKit)
|
||||
mark_as_advanced(APPKIT)
|
||||
include_directories(${APPKIT})
|
||||
|
|
@ -107,6 +133,9 @@ elseif(APPLE)
|
|||
|
||||
set(libobs_PLATFORM_DEPS
|
||||
${COCOA}
|
||||
${COREAUDIO}
|
||||
${AUDIOUNIT}
|
||||
${AUDIOTOOLBOX}
|
||||
${APPKIT}
|
||||
${IOKIT}
|
||||
${CARBON})
|
||||
|
|
@ -118,6 +147,9 @@ elseif(UNIX)
|
|||
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(DBUS_FOUND)
|
||||
set(libobs_PLATFORM_SOURCES ${libobs_PLATFORM_SOURCES}
|
||||
|
|
@ -334,7 +366,10 @@ set(libobs_HEADERS
|
|||
${libobs_graphics_HEADERS}
|
||||
${libobs_mediaio_HEADERS}
|
||||
${libobs_util_HEADERS}
|
||||
${libobs_libobs_HEADERS})
|
||||
${libobs_libobs_HEADERS}
|
||||
${libobs_audio_monitoring_SOURCES}
|
||||
${libobs_audio_monitoring_HEADERS}
|
||||
)
|
||||
|
||||
source_group("callback\\Source Files" FILES ${libobs_callback_SOURCES})
|
||||
source_group("callback\\Header Files" FILES ${libobs_callback_HEADERS})
|
||||
|
|
@ -346,7 +381,15 @@ source_group("media-io\\Source Files" FILES ${libobs_mediaio_SOURCES})
|
|||
source_group("media-io\\Header Files" FILES ${libobs_mediaio_HEADERS})
|
||||
source_group("util\\Source Files" FILES ${libobs_util_SOURCES})
|
||||
source_group("util\\Header Files" FILES ${libobs_util_HEADERS})
|
||||
source_group("audio-monitoring\\Source Files" FILES ${libobs_audio_monitoring_SOURCES})
|
||||
source_group("audio-monitoring\\Header Files" FILES ${libobs_audio_monitoring_HEADERS})
|
||||
|
||||
if(BUILD_CAPTIONS)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/libcaption)
|
||||
set(libobs_PLATFORM_DEPS
|
||||
${libobs_PLATFORM_DEPS}
|
||||
caption)
|
||||
endif()
|
||||
|
||||
add_library(libobs SHARED ${libobs_SOURCES} ${libobs_HEADERS})
|
||||
|
||||
|
|
|
|||
23
libobs/audio-monitoring/null/null-audio-monitoring.c
Normal file
23
libobs/audio-monitoring/null/null-audio-monitoring.c
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#include "../../obs-internal.h"
|
||||
|
||||
void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data)
|
||||
{
|
||||
UNUSED_PARAMETER(cb);
|
||||
UNUSED_PARAMETER(data);
|
||||
}
|
||||
|
||||
struct audio_monitor *audio_monitor_create(obs_source_t *source)
|
||||
{
|
||||
UNUSED_PARAMETER(source);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void audio_monitor_reset(struct audio_monitor *monitor)
|
||||
{
|
||||
UNUSED_PARAMETER(monitor);
|
||||
}
|
||||
|
||||
void audio_monitor_destroy(struct audio_monitor *monitor)
|
||||
{
|
||||
UNUSED_PARAMETER(monitor);
|
||||
}
|
||||
96
libobs/audio-monitoring/osx/coreaudio-enum-devices.c
Normal file
96
libobs/audio-monitoring/osx/coreaudio-enum-devices.c
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include <CoreFoundation/CFString.h>
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
|
||||
#include "../../obs-internal.h"
|
||||
#include "../../util/dstr.h"
|
||||
|
||||
#include "mac-helpers.h"
|
||||
|
||||
static inline bool cf_to_cstr(CFStringRef ref, char *buf, size_t size)
|
||||
{
|
||||
if (!ref) return false;
|
||||
return (bool)CFStringGetCString(ref, buf, size, kCFStringEncodingUTF8);
|
||||
}
|
||||
|
||||
static void obs_enum_audio_monitoring_device(obs_enum_audio_device_cb cb,
|
||||
void *data, AudioDeviceID id)
|
||||
{
|
||||
UInt32 size = 0;
|
||||
CFStringRef cf_name = NULL;
|
||||
CFStringRef cf_uid = NULL;
|
||||
char name[1024];
|
||||
char uid[1024];
|
||||
OSStatus stat;
|
||||
|
||||
AudioObjectPropertyAddress addr = {
|
||||
kAudioDevicePropertyStreams,
|
||||
kAudioDevicePropertyScopeInput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
/* check to see if it's a mac input device */
|
||||
AudioObjectGetPropertyDataSize(id, &addr, 0, NULL, &size);
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
size = sizeof(CFStringRef);
|
||||
|
||||
addr.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
stat = AudioObjectGetPropertyData(id, &addr, 0, NULL, &size, &cf_uid);
|
||||
if (!success(stat, "get audio device UID"))
|
||||
return;
|
||||
|
||||
addr.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
stat = AudioObjectGetPropertyData(id, &addr, 0, NULL, &size, &cf_name);
|
||||
if (!success(stat, "get audio device name"))
|
||||
goto fail;
|
||||
|
||||
if (!cf_to_cstr(cf_name, name, sizeof(name))) {
|
||||
blog(LOG_WARNING, "%s: failed to convert name", __FUNCTION__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!cf_to_cstr(cf_uid, uid, sizeof(uid))) {
|
||||
blog(LOG_WARNING, "%s: failed to convert uid", __FUNCTION__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cb(data, name, uid);
|
||||
|
||||
fail:
|
||||
if (cf_name)
|
||||
CFRelease(cf_name);
|
||||
if (cf_uid)
|
||||
CFRelease(cf_uid);
|
||||
}
|
||||
|
||||
void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data)
|
||||
{
|
||||
AudioObjectPropertyAddress addr = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 size = 0;
|
||||
UInt32 count;
|
||||
OSStatus stat;
|
||||
AudioDeviceID *ids;
|
||||
|
||||
stat = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
|
||||
0, NULL, &size);
|
||||
if (!success(stat, "get data size"))
|
||||
return;
|
||||
|
||||
ids = malloc(size);
|
||||
count = size / sizeof(AudioDeviceID);
|
||||
|
||||
stat = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
|
||||
0, NULL, &size, ids);
|
||||
if (success(stat, "get data")) {
|
||||
for (UInt32 i = 0; i < count; i++)
|
||||
obs_enum_audio_monitoring_device(cb, data, ids[i]);
|
||||
}
|
||||
|
||||
free(ids);
|
||||
}
|
||||
322
libobs/audio-monitoring/osx/coreaudio-output.c
Normal file
322
libobs/audio-monitoring/osx/coreaudio-output.c
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolBox/AudioQueue.h>
|
||||
#include <CoreFoundation/CFString.h>
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
|
||||
#include "../../media-io/audio-resampler.h"
|
||||
#include "../../util/circlebuf.h"
|
||||
#include "../../util/threading.h"
|
||||
#include "../../util/platform.h"
|
||||
#include "../../obs-internal.h"
|
||||
#include "../../util/darray.h"
|
||||
|
||||
#include "mac-helpers.h"
|
||||
|
||||
struct audio_monitor {
|
||||
obs_source_t *source;
|
||||
AudioQueueRef queue;
|
||||
AudioQueueBufferRef buffers[3];
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
struct circlebuf empty_buffers;
|
||||
struct circlebuf new_data;
|
||||
audio_resampler_t *resampler;
|
||||
size_t buffer_size;
|
||||
size_t wait_size;
|
||||
uint32_t channels;
|
||||
|
||||
volatile bool active;
|
||||
bool paused;
|
||||
};
|
||||
|
||||
static inline bool fill_buffer(struct audio_monitor *monitor)
|
||||
{
|
||||
AudioQueueBufferRef buf;
|
||||
OSStatus stat;
|
||||
|
||||
if (monitor->new_data.size < monitor->buffer_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
circlebuf_pop_front(&monitor->empty_buffers, &buf, sizeof(buf));
|
||||
circlebuf_pop_front(&monitor->new_data, buf->mAudioData,
|
||||
monitor->buffer_size);
|
||||
|
||||
buf->mAudioDataByteSize = monitor->buffer_size;
|
||||
|
||||
stat = AudioQueueEnqueueBuffer(monitor->queue, buf, 0, NULL);
|
||||
if (!success(stat, "AudioQueueEnqueueBuffer")) {
|
||||
blog(LOG_WARNING, "%s: %s", __FUNCTION__,
|
||||
"Failed to enqueue buffer");
|
||||
AudioQueueStop(monitor->queue, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
uint32_t bytes;
|
||||
|
||||
UNUSED_PARAMETER(source);
|
||||
|
||||
if (!os_atomic_load_bool(&monitor->active)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *resample_data[MAX_AV_PLANES];
|
||||
uint32_t resample_frames;
|
||||
uint64_t ts_offset;
|
||||
bool success;
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
bytes = sizeof(float) * monitor->channels * resample_frames;
|
||||
|
||||
if (muted) {
|
||||
memset(resample_data[0], 0, bytes);
|
||||
} else {
|
||||
/* apply volume */
|
||||
if (!close_float(vol, 1.0f, EPSILON)) {
|
||||
register float *cur = (float*)resample_data[0];
|
||||
register float *end = cur +
|
||||
resample_frames * monitor->channels;
|
||||
|
||||
while (cur < end)
|
||||
*(cur++) *= vol;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&monitor->mutex);
|
||||
circlebuf_push_back(&monitor->new_data, resample_data[0], bytes);
|
||||
|
||||
if (monitor->new_data.size >= monitor->wait_size) {
|
||||
monitor->wait_size = 0;
|
||||
|
||||
while (monitor->empty_buffers.size > 0) {
|
||||
if (!fill_buffer(monitor)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (monitor->paused) {
|
||||
AudioQueueStart(monitor->queue, NULL);
|
||||
monitor->paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&monitor->mutex);
|
||||
}
|
||||
|
||||
static void buffer_audio(void *data, AudioQueueRef aq, AudioQueueBufferRef buf)
|
||||
{
|
||||
struct audio_monitor *monitor = data;
|
||||
|
||||
pthread_mutex_lock(&monitor->mutex);
|
||||
circlebuf_push_back(&monitor->empty_buffers, &buf, sizeof(buf));
|
||||
while (monitor->empty_buffers.size > 0) {
|
||||
if (!fill_buffer(monitor)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (monitor->empty_buffers.size == sizeof(buf) * 3) {
|
||||
monitor->paused = true;
|
||||
monitor->wait_size = monitor->buffer_size * 3;
|
||||
AudioQueuePause(monitor->queue);
|
||||
}
|
||||
pthread_mutex_unlock(&monitor->mutex);
|
||||
|
||||
UNUSED_PARAMETER(aq);
|
||||
}
|
||||
|
||||
static bool audio_monitor_init(struct audio_monitor *monitor)
|
||||
{
|
||||
const struct audio_output_info *info = audio_output_get_info(
|
||||
obs->audio.audio);
|
||||
uint32_t channels = get_audio_channels(info->speakers);
|
||||
OSStatus stat;
|
||||
|
||||
AudioStreamBasicDescription desc = {
|
||||
.mSampleRate = (Float64)info->samples_per_sec,
|
||||
.mFormatID = kAudioFormatLinearPCM,
|
||||
.mFormatFlags = kAudioFormatFlagIsFloat |
|
||||
kAudioFormatFlagIsPacked,
|
||||
.mBytesPerPacket = sizeof(float) * channels,
|
||||
.mFramesPerPacket = 1,
|
||||
.mBytesPerFrame = sizeof(float) * channels,
|
||||
.mChannelsPerFrame = channels,
|
||||
.mBitsPerChannel = sizeof(float) * 8
|
||||
};
|
||||
|
||||
monitor->channels = channels;
|
||||
monitor->buffer_size =
|
||||
channels * sizeof(float) * info->samples_per_sec / 100 * 3;
|
||||
monitor->wait_size = monitor->buffer_size * 3;
|
||||
|
||||
pthread_mutex_init_value(&monitor->mutex);
|
||||
|
||||
stat = AudioQueueNewOutput(&desc, buffer_audio, monitor, NULL, NULL, 0,
|
||||
&monitor->queue);
|
||||
if (!success(stat, "AudioStreamBasicDescription")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *uid = obs->audio.monitoring_device_id;
|
||||
if (!uid || !*uid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strcmp(uid, "default") != 0) {
|
||||
CFStringRef cf_uid = CFStringCreateWithBytesNoCopy(NULL,
|
||||
(const UInt8*)uid, strlen(uid),
|
||||
kCFStringEncodingUTF8,
|
||||
false, NULL);
|
||||
|
||||
stat = AudioQueueSetProperty(monitor->queue,
|
||||
kAudioQueueProperty_CurrentDevice,
|
||||
cf_uid, sizeof(cf_uid));
|
||||
CFRelease(cf_uid);
|
||||
|
||||
if (!success(stat, "set current device")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
stat = AudioQueueSetParameter(monitor->queue,
|
||||
kAudioQueueParam_Volume, 1.0);
|
||||
if (!success(stat, "set volume")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
stat = AudioQueueAllocateBuffer(monitor->queue,
|
||||
monitor->buffer_size, &monitor->buffers[i]);
|
||||
if (!success(stat, "allocation of buffer")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
circlebuf_push_back(&monitor->empty_buffers,
|
||||
&monitor->buffers[i],
|
||||
sizeof(monitor->buffers[i]));
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&monitor->mutex, NULL) != 0) {
|
||||
blog(LOG_WARNING, "%s: %s", __FUNCTION__,
|
||||
"Failed to init mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
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 = info->samples_per_sec,
|
||||
.speakers = info->speakers,
|
||||
.format = AUDIO_FORMAT_FLOAT
|
||||
};
|
||||
|
||||
monitor->resampler = audio_resampler_create(&to, &from);
|
||||
if (!monitor->resampler) {
|
||||
blog(LOG_WARNING, "%s: %s", __FUNCTION__,
|
||||
"Failed to create resampler");
|
||||
return false;
|
||||
}
|
||||
|
||||
stat = AudioQueueStart(monitor->queue, NULL);
|
||||
if (!success(stat, "start")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
monitor->active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void audio_monitor_free(struct audio_monitor *monitor)
|
||||
{
|
||||
if (monitor->source) {
|
||||
obs_source_remove_audio_capture_callback(
|
||||
monitor->source, on_audio_playback, monitor);
|
||||
}
|
||||
if (monitor->active) {
|
||||
AudioQueueStop(monitor->queue, true);
|
||||
}
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
if (monitor->buffers[i]) {
|
||||
AudioQueueFreeBuffer(monitor->queue,
|
||||
monitor->buffers[i]);
|
||||
}
|
||||
}
|
||||
if (monitor->queue) {
|
||||
AudioQueueDispose(monitor->queue, true);
|
||||
}
|
||||
|
||||
audio_resampler_destroy(monitor->resampler);
|
||||
circlebuf_free(&monitor->empty_buffers);
|
||||
circlebuf_free(&monitor->new_data);
|
||||
pthread_mutex_destroy(&monitor->mutex);
|
||||
}
|
||||
|
||||
static void audio_monitor_init_final(struct audio_monitor *monitor,
|
||||
obs_source_t *source)
|
||||
{
|
||||
monitor->source = source;
|
||||
obs_source_add_audio_capture_callback(source, on_audio_playback,
|
||||
monitor);
|
||||
}
|
||||
|
||||
struct audio_monitor *audio_monitor_create(obs_source_t *source)
|
||||
{
|
||||
struct audio_monitor *monitor = bzalloc(sizeof(*monitor));
|
||||
|
||||
if (!audio_monitor_init(monitor)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&obs->audio.monitoring_mutex);
|
||||
da_push_back(obs->audio.monitors, &monitor);
|
||||
pthread_mutex_unlock(&obs->audio.monitoring_mutex);
|
||||
|
||||
audio_monitor_init_final(monitor, source);
|
||||
return monitor;
|
||||
|
||||
fail:
|
||||
audio_monitor_free(monitor);
|
||||
bfree(monitor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void audio_monitor_reset(struct audio_monitor *monitor)
|
||||
{
|
||||
bool success;
|
||||
|
||||
obs_source_t *source = monitor->source;
|
||||
audio_monitor_free(monitor);
|
||||
memset(monitor, 0, sizeof(*monitor));
|
||||
|
||||
success = audio_monitor_init(monitor);
|
||||
if (success)
|
||||
audio_monitor_init_final(monitor, source);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
15
libobs/audio-monitoring/osx/mac-helpers.h
Normal file
15
libobs/audio-monitoring/osx/mac-helpers.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
static bool success_(OSStatus stat, const char *func, const char *call)
|
||||
{
|
||||
if (stat != noErr) {
|
||||
blog(LOG_WARNING, "%s: %s failed: %d",
|
||||
func, call, (int)stat);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define success(stat, call) \
|
||||
success_(stat, __FUNCTION__, call)
|
||||
105
libobs/audio-monitoring/win32/wasapi-enum-devices.c
Normal file
105
libobs/audio-monitoring/win32/wasapi-enum-devices.c
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#include "../../obs-internal.h"
|
||||
|
||||
#include "wasapi-output.h"
|
||||
|
||||
#include <propsys.h>
|
||||
|
||||
#ifdef __MINGW32__
|
||||
|
||||
#ifdef DEFINE_PROPERTYKEY
|
||||
#undef DEFINE_PROPERTYKEY
|
||||
#endif
|
||||
#define DEFINE_PROPERTYKEY(id, a, b, c, d, e, f, g, h, i, j, k, l) \
|
||||
const PROPERTYKEY id = { { a,b,c, { d,e,f,g,h,i,j,k, } }, l };
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, \
|
||||
0xa45c254e, 0xdf1c, 0x4efd, 0x80, \
|
||||
0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
|
||||
|
||||
#else
|
||||
|
||||
#include <functiondiscoverykeys_devpkey.h>
|
||||
|
||||
#endif
|
||||
|
||||
static bool get_device_info(obs_enum_audio_device_cb cb, void *data,
|
||||
IMMDeviceCollection *collection, UINT idx)
|
||||
{
|
||||
IPropertyStore *store = NULL;
|
||||
IMMDevice *device = NULL;
|
||||
PROPVARIANT name_var;
|
||||
char utf8_name[512];
|
||||
WCHAR *w_id = NULL;
|
||||
char utf8_id[512];
|
||||
bool cont = true;
|
||||
HRESULT hr;
|
||||
|
||||
hr = collection->lpVtbl->Item(collection, idx, &device);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = device->lpVtbl->GetId(device, &w_id);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = device->lpVtbl->OpenPropertyStore(device, STGM_READ, &store);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
PropVariantInit(&name_var);
|
||||
hr = store->lpVtbl->GetValue(store, &PKEY_Device_FriendlyName,
|
||||
&name_var);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
os_wcs_to_utf8(w_id, 0, utf8_id, 512);
|
||||
os_wcs_to_utf8(name_var.pwszVal, 0, utf8_name, 512);
|
||||
|
||||
cont = cb(data, utf8_name, utf8_id);
|
||||
|
||||
fail:
|
||||
safe_release(store);
|
||||
safe_release(device);
|
||||
if (w_id)
|
||||
CoTaskMemFree(w_id);
|
||||
return cont;
|
||||
}
|
||||
|
||||
void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb,
|
||||
void *data)
|
||||
{
|
||||
IMMDeviceEnumerator *enumerator = NULL;
|
||||
IMMDeviceCollection *collection = NULL;
|
||||
UINT count;
|
||||
HRESULT hr;
|
||||
|
||||
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator, &enumerator);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = enumerator->lpVtbl->EnumAudioEndpoints(enumerator, eRender,
|
||||
DEVICE_STATE_ACTIVE, &collection);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = collection->lpVtbl->GetCount(collection, &count);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (UINT i = 0; i < count; i++) {
|
||||
if (!get_device_info(cb, data, collection, i)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
safe_release(enumerator);
|
||||
safe_release(collection);
|
||||
}
|
||||
418
libobs/audio-monitoring/win32/wasapi-output.c
Normal file
418
libobs/audio-monitoring/win32/wasapi-output.c
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
#include "../../media-io/audio-resampler.h"
|
||||
#include "../../util/circlebuf.h"
|
||||
#include "../../util/platform.h"
|
||||
#include "../../util/darray.h"
|
||||
#include "../../obs-internal.h"
|
||||
|
||||
#include "wasapi-output.h"
|
||||
|
||||
#define ACTUALLY_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
EXTERN_C const GUID DECLSPEC_SELECTANY name \
|
||||
= { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
||||
|
||||
ACTUALLY_DEFINE_GUID(CLSID_MMDeviceEnumerator,
|
||||
0xBCDE0395, 0xE52F, 0x467C,
|
||||
0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
|
||||
ACTUALLY_DEFINE_GUID(IID_IMMDeviceEnumerator,
|
||||
0xA95664D2, 0x9614, 0x4F35,
|
||||
0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6);
|
||||
ACTUALLY_DEFINE_GUID(IID_IAudioClient,
|
||||
0x1CB9AD4C, 0xDBFA, 0x4C32,
|
||||
0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2);
|
||||
ACTUALLY_DEFINE_GUID(IID_IAudioRenderClient,
|
||||
0xF294ACFC, 0x3146, 0x4483,
|
||||
0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2);
|
||||
|
||||
struct audio_monitor {
|
||||
obs_source_t *source;
|
||||
IMMDevice *device;
|
||||
IAudioClient *client;
|
||||
IAudioRenderClient *render;
|
||||
|
||||
uint64_t last_recv_time;
|
||||
uint64_t prev_video_ts;
|
||||
uint64_t time_since_prev;
|
||||
audio_resampler_t *resampler;
|
||||
uint32_t sample_rate;
|
||||
uint32_t channels;
|
||||
bool source_has_video : 1;
|
||||
|
||||
int64_t lowest_audio_offset;
|
||||
struct circlebuf delay_buffer;
|
||||
uint32_t delay_size;
|
||||
|
||||
DARRAY(float) buf;
|
||||
pthread_mutex_t playback_mutex;
|
||||
};
|
||||
|
||||
/* #define DEBUG_AUDIO */
|
||||
|
||||
static bool process_audio_delay(struct audio_monitor *monitor,
|
||||
float **data, uint32_t *frames, uint64_t ts, uint32_t pad)
|
||||
{
|
||||
obs_source_t *s = monitor->source;
|
||||
uint64_t last_frame_ts = s->last_frame_ts;
|
||||
uint64_t cur_time = os_gettime_ns();
|
||||
uint64_t front_ts;
|
||||
uint64_t cur_ts;
|
||||
int64_t diff;
|
||||
uint32_t blocksize = monitor->channels * sizeof(float);
|
||||
|
||||
/* cut off audio if long-since leftover audio in delay buffer */
|
||||
if (cur_time - monitor->last_recv_time > 1000000000)
|
||||
circlebuf_free(&monitor->delay_buffer);
|
||||
monitor->last_recv_time = cur_time;
|
||||
|
||||
ts += monitor->source->sync_offset;
|
||||
|
||||
circlebuf_push_back(&monitor->delay_buffer, &ts, sizeof(ts));
|
||||
circlebuf_push_back(&monitor->delay_buffer, frames, sizeof(*frames));
|
||||
circlebuf_push_back(&monitor->delay_buffer, *data,
|
||||
*frames * blocksize);
|
||||
|
||||
if (!monitor->prev_video_ts) {
|
||||
monitor->prev_video_ts = last_frame_ts;
|
||||
|
||||
} else if (monitor->prev_video_ts == last_frame_ts) {
|
||||
monitor->time_since_prev += (uint64_t)*frames *
|
||||
1000000000ULL / (uint64_t)monitor->sample_rate;
|
||||
} else {
|
||||
monitor->time_since_prev = 0;
|
||||
}
|
||||
|
||||
while (monitor->delay_buffer.size != 0) {
|
||||
size_t size;
|
||||
bool bad_diff;
|
||||
|
||||
circlebuf_peek_front(&monitor->delay_buffer, &cur_ts,
|
||||
sizeof(ts));
|
||||
front_ts = cur_ts -
|
||||
((uint64_t)pad * 1000000000ULL /
|
||||
(uint64_t)monitor->sample_rate);
|
||||
diff = (int64_t)front_ts - (int64_t)last_frame_ts;
|
||||
bad_diff = !last_frame_ts ||
|
||||
llabs(diff) > 5000000000 ||
|
||||
monitor->time_since_prev > 100000000ULL;
|
||||
|
||||
/* delay audio if rushing */
|
||||
if (!bad_diff && diff > 75000000) {
|
||||
#ifdef DEBUG_AUDIO
|
||||
blog(LOG_INFO, "audio rushing, cutting audio, "
|
||||
"diff: %lld, delay buffer size: %lu, "
|
||||
"v: %llu: a: %llu",
|
||||
diff, (int)monitor->delay_buffer.size,
|
||||
last_frame_ts, front_ts);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
circlebuf_pop_front(&monitor->delay_buffer, NULL, sizeof(ts));
|
||||
circlebuf_pop_front(&monitor->delay_buffer, frames,
|
||||
sizeof(*frames));
|
||||
|
||||
size = *frames * blocksize;
|
||||
da_resize(monitor->buf, size);
|
||||
circlebuf_pop_front(&monitor->delay_buffer,
|
||||
monitor->buf.array, size);
|
||||
|
||||
/* cut audio if dragging */
|
||||
if (!bad_diff && diff < -75000000 && monitor->delay_buffer.size > 0) {
|
||||
#ifdef DEBUG_AUDIO
|
||||
blog(LOG_INFO, "audio dragging, cutting audio, "
|
||||
"diff: %lld, delay buffer size: %lu, "
|
||||
"v: %llu: a: %llu",
|
||||
diff, (int)monitor->delay_buffer.size,
|
||||
last_frame_ts, front_ts);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
*data = monitor->buf.array;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void on_audio_playback(void *param, obs_source_t *source,
|
||||
const struct audio_data *audio_data, bool muted)
|
||||
{
|
||||
struct audio_monitor *monitor = param;
|
||||
IAudioRenderClient *render = monitor->render;
|
||||
uint8_t *resample_data[MAX_AV_PLANES];
|
||||
float vol = source->user_volume;
|
||||
uint32_t resample_frames;
|
||||
uint64_t ts_offset;
|
||||
bool success;
|
||||
BYTE *output;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
UINT32 pad = 0;
|
||||
monitor->client->lpVtbl->GetCurrentPadding(monitor->client, &pad);
|
||||
|
||||
if (monitor->source_has_video) {
|
||||
uint64_t ts = audio_data->timestamp - ts_offset;
|
||||
|
||||
if (!process_audio_delay(monitor, (float**)(&resample_data[0]),
|
||||
&resample_frames, ts, pad)) {
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT hr = render->lpVtbl->GetBuffer(render, resample_frames,
|
||||
&output);
|
||||
if (FAILED(hr)) {
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (!muted) {
|
||||
/* apply volume */
|
||||
if (!close_float(vol, 1.0f, EPSILON)) {
|
||||
register float *cur = (float*)resample_data[0];
|
||||
register float *end = cur +
|
||||
resample_frames * monitor->channels;
|
||||
|
||||
while (cur < end)
|
||||
*(cur++) *= vol;
|
||||
}
|
||||
memcpy(output, resample_data[0],
|
||||
resample_frames * monitor->channels *
|
||||
sizeof(float));
|
||||
}
|
||||
|
||||
render->lpVtbl->ReleaseBuffer(render, resample_frames,
|
||||
muted ? AUDCLNT_BUFFERFLAGS_SILENT : 0);
|
||||
|
||||
unlock:
|
||||
pthread_mutex_unlock(&monitor->playback_mutex);
|
||||
}
|
||||
|
||||
static inline void audio_monitor_free(struct audio_monitor *monitor)
|
||||
{
|
||||
if (monitor->source) {
|
||||
obs_source_remove_audio_capture_callback(
|
||||
monitor->source, on_audio_playback, monitor);
|
||||
}
|
||||
|
||||
if (monitor->client)
|
||||
monitor->client->lpVtbl->Stop(monitor->client);
|
||||
|
||||
safe_release(monitor->device);
|
||||
safe_release(monitor->client);
|
||||
safe_release(monitor->render);
|
||||
audio_resampler_destroy(monitor->resampler);
|
||||
circlebuf_free(&monitor->delay_buffer);
|
||||
da_free(monitor->buf);
|
||||
}
|
||||
|
||||
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_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;
|
||||
}
|
||||
|
||||
static bool audio_monitor_init(struct audio_monitor *monitor)
|
||||
{
|
||||
IMMDeviceEnumerator *immde = NULL;
|
||||
WAVEFORMATEX *wfex = NULL;
|
||||
bool success = false;
|
||||
UINT32 frames;
|
||||
HRESULT hr;
|
||||
|
||||
const char *id = obs->audio.monitoring_device_id;
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_mutex_init_value(&monitor->playback_mutex);
|
||||
|
||||
/* ------------------------------------------ *
|
||||
* Init device */
|
||||
|
||||
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator, (void**)&immde);
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strcmp(id, "default") == 0) {
|
||||
hr = immde->lpVtbl->GetDefaultAudioEndpoint(immde,
|
||||
eRender, eConsole, &monitor->device);
|
||||
} else {
|
||||
wchar_t w_id[512];
|
||||
os_utf8_to_wcs(id, 0, w_id, 512);
|
||||
|
||||
hr = immde->lpVtbl->GetDevice(immde, w_id, &monitor->device);
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* ------------------------------------------ *
|
||||
* Init client */
|
||||
|
||||
hr = monitor->device->lpVtbl->Activate(monitor->device,
|
||||
&IID_IAudioClient, CLSCTX_ALL, NULL,
|
||||
(void**)&monitor->client);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = monitor->client->lpVtbl->GetMixFormat(monitor->client, &wfex);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = monitor->client->lpVtbl->Initialize(monitor->client,
|
||||
AUDCLNT_SHAREMODE_SHARED, 0,
|
||||
10000000, 0, wfex, NULL);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* ------------------------------------------ *
|
||||
* Init resampler */
|
||||
|
||||
const struct audio_output_info *info = audio_output_get_info(
|
||||
obs->audio.audio);
|
||||
WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE*)wfex;
|
||||
struct resample_info from;
|
||||
struct resample_info to;
|
||||
|
||||
from.samples_per_sec = info->samples_per_sec;
|
||||
from.speakers = info->speakers;
|
||||
from.format = AUDIO_FORMAT_FLOAT_PLANAR;
|
||||
|
||||
to.samples_per_sec = (uint32_t)wfex->nSamplesPerSec;
|
||||
to.speakers = convert_speaker_layout(ext->dwChannelMask,
|
||||
wfex->nChannels);
|
||||
to.format = AUDIO_FORMAT_FLOAT;
|
||||
|
||||
monitor->sample_rate = (uint32_t)wfex->nSamplesPerSec;
|
||||
monitor->channels = wfex->nChannels;
|
||||
monitor->resampler = audio_resampler_create(&to, &from);
|
||||
if (!monitor->resampler) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* ------------------------------------------ *
|
||||
* Init client */
|
||||
|
||||
hr = monitor->client->lpVtbl->GetBufferSize(monitor->client, &frames);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = monitor->client->lpVtbl->GetService(monitor->client,
|
||||
&IID_IAudioRenderClient, (void**)&monitor->render);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&monitor->playback_mutex, NULL) != 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hr = monitor->client->lpVtbl->Start(monitor->client);
|
||||
if (FAILED(hr)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
||||
fail:
|
||||
safe_release(immde);
|
||||
if (wfex)
|
||||
CoTaskMemFree(wfex);
|
||||
return success;
|
||||
}
|
||||
|
||||
static void audio_monitor_init_final(struct audio_monitor *monitor,
|
||||
obs_source_t *source)
|
||||
{
|
||||
monitor->source = source;
|
||||
monitor->source_has_video =
|
||||
(source->info.output_flags & OBS_SOURCE_VIDEO) != 0;
|
||||
obs_source_add_audio_capture_callback(source, on_audio_playback,
|
||||
monitor);
|
||||
}
|
||||
|
||||
struct audio_monitor *audio_monitor_create(obs_source_t *source)
|
||||
{
|
||||
struct audio_monitor monitor = {0};
|
||||
struct audio_monitor *out;
|
||||
|
||||
if (!audio_monitor_init(&monitor)) {
|
||||
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, source);
|
||||
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;
|
||||
|
||||
pthread_mutex_lock(&monitor->playback_mutex);
|
||||
success = audio_monitor_init(&new_monitor);
|
||||
pthread_mutex_unlock(&monitor->playback_mutex);
|
||||
|
||||
if (success) {
|
||||
obs_source_t *source = monitor->source;
|
||||
audio_monitor_free(monitor);
|
||||
*monitor = new_monitor;
|
||||
audio_monitor_init_final(monitor, source);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
13
libobs/audio-monitoring/win32/wasapi-output.h
Normal file
13
libobs/audio-monitoring/win32/wasapi-output.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#include <windows.h>
|
||||
#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 safe_release(ptr) \
|
||||
do { \
|
||||
if (ptr) { \
|
||||
ptr->lpVtbl->Release(ptr); \
|
||||
} \
|
||||
} while (false)
|
||||
|
|
@ -31,7 +31,7 @@ struct decl_param {
|
|||
|
||||
static inline void decl_param_free(struct decl_param *param)
|
||||
{
|
||||
if (param)
|
||||
if (param->name)
|
||||
bfree(param->name);
|
||||
memset(param, 0, sizeof(struct decl_param));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ uniform float4x4 color_matrix;
|
|||
uniform float3 color_range_min = {0.0, 0.0, 0.0};
|
||||
uniform float3 color_range_max = {1.0, 1.0, 1.0};
|
||||
uniform float2 base_dimension_i;
|
||||
uniform float undistort_factor = 1.0;
|
||||
|
||||
sampler_state textureSampler {
|
||||
Filter = Linear;
|
||||
|
|
@ -63,21 +64,41 @@ float4 weight4(float x)
|
|||
weight(x + 1.0));
|
||||
}
|
||||
|
||||
float4 pixel(float xpos, float ypos)
|
||||
float AspectUndistortX(float x, float a)
|
||||
{
|
||||
return image.Sample(textureSampler, float2(xpos, ypos));
|
||||
// The higher the power, the longer the linear part will be.
|
||||
return (1.0 - a) * (x * x * x * x * x) + a * x;
|
||||
}
|
||||
|
||||
float4 get_line(float ypos, float4 xpos, float4 linetaps)
|
||||
float AspectUndistortU(float u)
|
||||
{
|
||||
// Normalize texture coord to -1.0 to 1.0 range, and back.
|
||||
return AspectUndistortX((u - 0.5) * 2.0, undistort_factor) * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
float2 pixel_coord(float xpos, float ypos)
|
||||
{
|
||||
return float2(AspectUndistortU(xpos), ypos);
|
||||
}
|
||||
|
||||
float4 pixel(float xpos, float ypos, bool undistort)
|
||||
{
|
||||
if (undistort)
|
||||
return image.Sample(textureSampler, pixel_coord(xpos, ypos));
|
||||
else
|
||||
return image.Sample(textureSampler, float2(xpos, ypos));
|
||||
}
|
||||
|
||||
float4 get_line(float ypos, float4 xpos, float4 linetaps, bool undistort)
|
||||
{
|
||||
return
|
||||
pixel(xpos.r, ypos) * linetaps.r +
|
||||
pixel(xpos.g, ypos) * linetaps.g +
|
||||
pixel(xpos.b, ypos) * linetaps.b +
|
||||
pixel(xpos.a, ypos) * linetaps.a;
|
||||
pixel(xpos.r, ypos, undistort) * linetaps.r +
|
||||
pixel(xpos.g, ypos, undistort) * linetaps.g +
|
||||
pixel(xpos.b, ypos, undistort) * linetaps.b +
|
||||
pixel(xpos.a, ypos, undistort) * linetaps.a;
|
||||
}
|
||||
|
||||
float4 DrawBicubic(VertData v_in)
|
||||
float4 DrawBicubic(VertData v_in, bool undistort)
|
||||
{
|
||||
float2 stepxy = base_dimension_i;
|
||||
float2 pos = v_in.uv + stepxy * 0.5;
|
||||
|
|
@ -100,20 +121,20 @@ float4 DrawBicubic(VertData v_in)
|
|||
);
|
||||
|
||||
return
|
||||
get_line(xystart.y , xpos, rowtaps) * coltaps.r +
|
||||
get_line(xystart.y + stepxy.y , xpos, rowtaps) * coltaps.g +
|
||||
get_line(xystart.y + stepxy.y * 2.0, xpos, rowtaps) * coltaps.b +
|
||||
get_line(xystart.y + stepxy.y * 3.0, xpos, rowtaps) * coltaps.a;
|
||||
get_line(xystart.y , xpos, rowtaps, undistort) * coltaps.r +
|
||||
get_line(xystart.y + stepxy.y , xpos, rowtaps, undistort) * coltaps.g +
|
||||
get_line(xystart.y + stepxy.y * 2.0, xpos, rowtaps, undistort) * coltaps.b +
|
||||
get_line(xystart.y + stepxy.y * 3.0, xpos, rowtaps, undistort) * coltaps.a;
|
||||
}
|
||||
|
||||
float4 PSDrawBicubicRGBA(VertData v_in) : TARGET
|
||||
float4 PSDrawBicubicRGBA(VertData v_in, bool undistort) : TARGET
|
||||
{
|
||||
return DrawBicubic(v_in);
|
||||
return DrawBicubic(v_in, undistort);
|
||||
}
|
||||
|
||||
float4 PSDrawBicubicMatrix(VertData v_in) : TARGET
|
||||
{
|
||||
float4 rgba = DrawBicubic(v_in);
|
||||
float4 rgba = DrawBicubic(v_in, false);
|
||||
float4 yuv;
|
||||
|
||||
yuv.xyz = clamp(rgba.xyz, color_range_min, color_range_max);
|
||||
|
|
@ -125,7 +146,16 @@ technique Draw
|
|||
pass
|
||||
{
|
||||
vertex_shader = VSDefault(v_in);
|
||||
pixel_shader = PSDrawBicubicRGBA(v_in);
|
||||
pixel_shader = PSDrawBicubicRGBA(v_in, false);
|
||||
}
|
||||
}
|
||||
|
||||
technique DrawUndistort
|
||||
{
|
||||
pass
|
||||
{
|
||||
vertex_shader = VSDefault(v_in);
|
||||
pixel_shader = PSDrawBicubicRGBA(v_in, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ uniform float4x4 color_matrix;
|
|||
uniform float3 color_range_min = {0.0, 0.0, 0.0};
|
||||
uniform float3 color_range_max = {1.0, 1.0, 1.0};
|
||||
uniform float2 base_dimension_i;
|
||||
uniform float undistort_factor = 1.0;
|
||||
|
||||
sampler_state textureSampler
|
||||
{
|
||||
|
|
@ -64,24 +65,44 @@ float3 weight3(float x, float scale)
|
|||
weight((x * 2.0 + 2.0 * 2.0 - 3.0) * scale, 3.0));
|
||||
}
|
||||
|
||||
float4 pixel(float xpos, float ypos)
|
||||
float AspectUndistortX(float x, float a)
|
||||
{
|
||||
return image.Sample(textureSampler, float2(xpos, ypos));
|
||||
// The higher the power, the longer the linear part will be.
|
||||
return (1.0 - a) * (x * x * x * x * x) + a * x;
|
||||
}
|
||||
|
||||
float AspectUndistortU(float u)
|
||||
{
|
||||
// Normalize texture coord to -1.0 to 1.0 range, and back.
|
||||
return AspectUndistortX((u - 0.5) * 2.0, undistort_factor) * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
float2 pixel_coord(float xpos, float ypos)
|
||||
{
|
||||
return float2(AspectUndistortU(xpos), ypos);
|
||||
}
|
||||
|
||||
float4 pixel(float xpos, float ypos, bool undistort)
|
||||
{
|
||||
if (undistort)
|
||||
return image.Sample(textureSampler, pixel_coord(xpos, ypos));
|
||||
else
|
||||
return image.Sample(textureSampler, float2(xpos, ypos));
|
||||
}
|
||||
|
||||
float4 get_line(float ypos, float3 xpos1, float3 xpos2, float3 rowtap1,
|
||||
float3 rowtap2)
|
||||
float3 rowtap2, bool undistort)
|
||||
{
|
||||
return
|
||||
pixel(xpos1.r, ypos) * rowtap1.r +
|
||||
pixel(xpos1.g, ypos) * rowtap2.r +
|
||||
pixel(xpos1.b, ypos) * rowtap1.g +
|
||||
pixel(xpos2.r, ypos) * rowtap2.g +
|
||||
pixel(xpos2.g, ypos) * rowtap1.b +
|
||||
pixel(xpos2.b, ypos) * rowtap2.b;
|
||||
pixel(xpos1.r, ypos, undistort) * rowtap1.r +
|
||||
pixel(xpos1.g, ypos, undistort) * rowtap2.r +
|
||||
pixel(xpos1.b, ypos, undistort) * rowtap1.g +
|
||||
pixel(xpos2.r, ypos, undistort) * rowtap2.g +
|
||||
pixel(xpos2.g, ypos, undistort) * rowtap1.b +
|
||||
pixel(xpos2.b, ypos, undistort) * rowtap2.b;
|
||||
}
|
||||
|
||||
float4 DrawLanczos(FragData v_in)
|
||||
float4 DrawLanczos(FragData v_in, bool undistort)
|
||||
{
|
||||
float2 stepxy = base_dimension_i;
|
||||
float2 pos = v_in.uv + stepxy * 0.5;
|
||||
|
|
@ -106,22 +127,22 @@ float4 DrawLanczos(FragData v_in)
|
|||
float3 xpos2 = float3(xystart.x + stepxy.x * 3.0, xystart.x + stepxy.x * 4.0, xystart.x + stepxy.x * 5.0);
|
||||
|
||||
return
|
||||
get_line(xystart.y , xpos1, xpos2, rowtap1, rowtap2) * coltap1.r +
|
||||
get_line(xystart.y + stepxy.y , xpos1, xpos2, rowtap1, rowtap2) * coltap2.r +
|
||||
get_line(xystart.y + stepxy.y * 2.0, xpos1, xpos2, rowtap1, rowtap2) * coltap1.g +
|
||||
get_line(xystart.y + stepxy.y * 3.0, xpos1, xpos2, rowtap1, rowtap2) * coltap2.g +
|
||||
get_line(xystart.y + stepxy.y * 4.0, xpos1, xpos2, rowtap1, rowtap2) * coltap1.b +
|
||||
get_line(xystart.y + stepxy.y * 5.0, xpos1, xpos2, rowtap1, rowtap2) * coltap2.b;
|
||||
get_line(xystart.y , xpos1, xpos2, rowtap1, rowtap2, undistort) * coltap1.r +
|
||||
get_line(xystart.y + stepxy.y , xpos1, xpos2, rowtap1, rowtap2, undistort) * coltap2.r +
|
||||
get_line(xystart.y + stepxy.y * 2.0, xpos1, xpos2, rowtap1, rowtap2, undistort) * coltap1.g +
|
||||
get_line(xystart.y + stepxy.y * 3.0, xpos1, xpos2, rowtap1, rowtap2, undistort) * coltap2.g +
|
||||
get_line(xystart.y + stepxy.y * 4.0, xpos1, xpos2, rowtap1, rowtap2, undistort) * coltap1.b +
|
||||
get_line(xystart.y + stepxy.y * 5.0, xpos1, xpos2, rowtap1, rowtap2, undistort) * coltap2.b;
|
||||
}
|
||||
|
||||
float4 PSDrawLanczosRGBA(FragData v_in) : TARGET
|
||||
float4 PSDrawLanczosRGBA(FragData v_in, bool undistort) : TARGET
|
||||
{
|
||||
return DrawLanczos(v_in);
|
||||
return DrawLanczos(v_in, undistort);
|
||||
}
|
||||
|
||||
float4 PSDrawLanczosMatrix(FragData v_in) : TARGET
|
||||
{
|
||||
float4 rgba = DrawLanczos(v_in);
|
||||
float4 rgba = DrawLanczos(v_in, false);
|
||||
float4 yuv;
|
||||
|
||||
yuv.xyz = clamp(rgba.xyz, color_range_min, color_range_max);
|
||||
|
|
@ -133,7 +154,16 @@ technique Draw
|
|||
pass
|
||||
{
|
||||
vertex_shader = VSDefault(v_in);
|
||||
pixel_shader = PSDrawLanczosRGBA(v_in);
|
||||
pixel_shader = PSDrawLanczosRGBA(v_in, false);
|
||||
}
|
||||
}
|
||||
|
||||
technique DrawUndistort
|
||||
{
|
||||
pass
|
||||
{
|
||||
vertex_shader = VSDefault(v_in);
|
||||
pixel_shader = PSDrawLanczosRGBA(v_in, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ static inline void vec2_neg(struct vec2 *dst, const struct vec2 *v)
|
|||
|
||||
static inline float vec2_dot(const struct vec2 *v1, const struct vec2 *v2)
|
||||
{
|
||||
return (v1->x+v2->x) * (v1->y+v2->y);
|
||||
return v1->x*v2->x + v1->y*v2->y;
|
||||
}
|
||||
|
||||
static inline float vec2_len(const struct vec2 *v)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MAX_AUDIO_MIXES 4
|
||||
#define MAX_AUDIO_MIXES 6
|
||||
#define MAX_AUDIO_CHANNELS 2
|
||||
#define AUDIO_OUTPUT_FRAMES 1024
|
||||
|
||||
|
|
|
|||
|
|
@ -132,15 +132,17 @@ void obs_parse_avc_packet(struct encoder_packet *avc_packet,
|
|||
{
|
||||
struct array_output_data output;
|
||||
struct serializer s;
|
||||
long ref = 1;
|
||||
|
||||
array_output_serializer_init(&s, &output);
|
||||
*avc_packet = *src;
|
||||
|
||||
serialize(&s, &ref, sizeof(ref));
|
||||
serialize_avc_data(&s, src->data, src->size, &avc_packet->keyframe,
|
||||
&avc_packet->priority);
|
||||
|
||||
avc_packet->data = output.bytes.array;
|
||||
avc_packet->size = output.bytes.num;
|
||||
avc_packet->data = output.bytes.array + sizeof(ref);
|
||||
avc_packet->size = output.bytes.num - sizeof(ref);
|
||||
avc_packet->drop_priority = get_drop_priority(avc_packet->priority);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,21 +27,21 @@
|
|||
/*
|
||||
* Increment if major breaking API changes
|
||||
*/
|
||||
#define LIBOBS_API_MAJOR_VER 0 /* 0 means development, anything can break */
|
||||
#define LIBOBS_API_MAJOR_VER 18
|
||||
|
||||
/*
|
||||
* Increment if backward-compatible additions
|
||||
*
|
||||
* Reset to zero each major version
|
||||
*/
|
||||
#define LIBOBS_API_MINOR_VER 16
|
||||
#define LIBOBS_API_MINOR_VER 0
|
||||
|
||||
/*
|
||||
* Increment if backward-compatible bug fix
|
||||
*
|
||||
* Reset to zero each major or minor version
|
||||
*/
|
||||
#define LIBOBS_API_PATCH_VER 2
|
||||
#define LIBOBS_API_PATCH_VER 1
|
||||
|
||||
#define MAKE_SEMANTIC_VERSION(major, minor, patch) \
|
||||
((major << 24) | \
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ bool obs_display_init(struct obs_display *display,
|
|||
const struct gs_init_data *graphics_data)
|
||||
{
|
||||
pthread_mutex_init_value(&display->draw_callbacks_mutex);
|
||||
pthread_mutex_init_value(&display->draw_info_mutex);
|
||||
|
||||
if (graphics_data) {
|
||||
display->swap = gs_swapchain_create(graphics_data);
|
||||
|
|
@ -40,6 +41,10 @@ bool obs_display_init(struct obs_display *display,
|
|||
blog(LOG_ERROR, "obs_display_init: Failed to create mutex");
|
||||
return false;
|
||||
}
|
||||
if (pthread_mutex_init(&display->draw_info_mutex, NULL) != 0) {
|
||||
blog(LOG_ERROR, "obs_display_init: Failed to create mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
display->background_color = 0x4C4C4C;
|
||||
display->enabled = true;
|
||||
|
|
@ -73,6 +78,7 @@ obs_display_t *obs_display_create(const struct gs_init_data *graphics_data)
|
|||
void obs_display_free(obs_display_t *display)
|
||||
{
|
||||
pthread_mutex_destroy(&display->draw_callbacks_mutex);
|
||||
pthread_mutex_destroy(&display->draw_info_mutex);
|
||||
da_free(display->draw_callbacks);
|
||||
|
||||
if (display->swap) {
|
||||
|
|
@ -103,13 +109,13 @@ void obs_display_resize(obs_display_t *display, uint32_t cx, uint32_t cy)
|
|||
{
|
||||
if (!display) return;
|
||||
|
||||
pthread_mutex_lock(&display->draw_callbacks_mutex);
|
||||
pthread_mutex_lock(&display->draw_info_mutex);
|
||||
|
||||
display->cx = cx;
|
||||
display->cy = cy;
|
||||
display->size_changed = true;
|
||||
|
||||
pthread_mutex_unlock(&display->draw_callbacks_mutex);
|
||||
pthread_mutex_unlock(&display->draw_info_mutex);
|
||||
}
|
||||
|
||||
void obs_display_add_draw_callback(obs_display_t *display,
|
||||
|
|
@ -138,16 +144,15 @@ void obs_display_remove_draw_callback(obs_display_t *display,
|
|||
pthread_mutex_unlock(&display->draw_callbacks_mutex);
|
||||
}
|
||||
|
||||
static inline void render_display_begin(struct obs_display *display)
|
||||
static inline void render_display_begin(struct obs_display *display,
|
||||
uint32_t cx, uint32_t cy, bool size_changed)
|
||||
{
|
||||
struct vec4 clear_color;
|
||||
|
||||
gs_load_swapchain(display ? display->swap : NULL);
|
||||
gs_load_swapchain(display->swap);
|
||||
|
||||
if (display->size_changed) {
|
||||
gs_resize(display->cx, display->cy);
|
||||
display->size_changed = false;
|
||||
}
|
||||
if (size_changed)
|
||||
gs_resize(cx, cy);
|
||||
|
||||
gs_begin_scene();
|
||||
|
||||
|
|
@ -161,9 +166,8 @@ static inline void render_display_begin(struct obs_display *display)
|
|||
/* gs_enable_blending(false); */
|
||||
gs_set_cull_mode(GS_NEITHER);
|
||||
|
||||
gs_ortho(0.0f, (float)display->cx,
|
||||
0.0f, (float)display->cy, -100.0f, 100.0f);
|
||||
gs_set_viewport(0, 0, display->cx, display->cy);
|
||||
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
|
||||
gs_set_viewport(0, 0, cx, cy);
|
||||
}
|
||||
|
||||
static inline void render_display_end()
|
||||
|
|
@ -174,9 +178,27 @@ static inline void render_display_end()
|
|||
|
||||
void render_display(struct obs_display *display)
|
||||
{
|
||||
uint32_t cx, cy;
|
||||
bool size_changed;
|
||||
|
||||
if (!display || !display->enabled) return;
|
||||
|
||||
render_display_begin(display);
|
||||
/* -------------------------------------------- */
|
||||
|
||||
pthread_mutex_lock(&display->draw_info_mutex);
|
||||
|
||||
cx = display->cx;
|
||||
cy = display->cy;
|
||||
size_changed = display->size_changed;
|
||||
|
||||
if (size_changed)
|
||||
display->size_changed = false;
|
||||
|
||||
pthread_mutex_unlock(&display->draw_info_mutex);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
render_display_begin(display, cx, cy, size_changed);
|
||||
|
||||
pthread_mutex_lock(&display->draw_callbacks_mutex);
|
||||
|
||||
|
|
@ -184,7 +206,7 @@ void render_display(struct obs_display *display)
|
|||
struct draw_callback *callback;
|
||||
callback = display->draw_callbacks.array+i;
|
||||
|
||||
callback->draw(callback->param, display->cx, display->cy);
|
||||
callback->draw(callback->param, cx, cy);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&display->draw_callbacks_mutex);
|
||||
|
|
|
|||
|
|
@ -796,7 +796,7 @@ static inline void do_encode(struct obs_encoder *encoder,
|
|||
full_stop(encoder);
|
||||
blog(LOG_ERROR, "Error encoding with encoder '%s'",
|
||||
encoder->context.name);
|
||||
return;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (received) {
|
||||
|
|
@ -822,6 +822,7 @@ static inline void do_encode(struct obs_encoder *encoder,
|
|||
pthread_mutex_unlock(&encoder->callbacks_mutex);
|
||||
}
|
||||
|
||||
error:
|
||||
profile_end(do_encode_name);
|
||||
}
|
||||
|
||||
|
|
@ -1034,17 +1035,55 @@ void obs_encoder_remove_output(struct obs_encoder *encoder,
|
|||
pthread_mutex_unlock(&encoder->outputs_mutex);
|
||||
}
|
||||
|
||||
void obs_encoder_packet_create_instance(struct encoder_packet *dst,
|
||||
const struct encoder_packet *src)
|
||||
{
|
||||
long *p_refs;
|
||||
|
||||
*dst = *src;
|
||||
p_refs = bmalloc(src->size + sizeof(long));
|
||||
dst->data = (void*)(p_refs + 1);
|
||||
*p_refs = 1;
|
||||
memcpy(dst->data, src->data, src->size);
|
||||
}
|
||||
|
||||
void obs_duplicate_encoder_packet(struct encoder_packet *dst,
|
||||
const struct encoder_packet *src)
|
||||
{
|
||||
*dst = *src;
|
||||
dst->data = bmemdup(src->data, src->size);
|
||||
obs_encoder_packet_create_instance(dst, src);
|
||||
}
|
||||
|
||||
void obs_free_encoder_packet(struct encoder_packet *packet)
|
||||
{
|
||||
bfree(packet->data);
|
||||
memset(packet, 0, sizeof(struct encoder_packet));
|
||||
obs_encoder_packet_release(packet);
|
||||
}
|
||||
|
||||
void obs_encoder_packet_ref(struct encoder_packet *dst,
|
||||
struct encoder_packet *src)
|
||||
{
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
if (src->data) {
|
||||
long *p_refs = ((long*)src->data) - 1;
|
||||
os_atomic_inc_long(p_refs);
|
||||
}
|
||||
|
||||
*dst = *src;
|
||||
}
|
||||
|
||||
void obs_encoder_packet_release(struct encoder_packet *pkt)
|
||||
{
|
||||
if (!pkt)
|
||||
return;
|
||||
|
||||
if (pkt->data) {
|
||||
long *p_refs = ((long*)pkt->data) - 1;
|
||||
if (os_atomic_dec_long(p_refs) == 0)
|
||||
bfree(p_refs);
|
||||
}
|
||||
|
||||
memset(pkt, 0, sizeof(struct encoder_packet));
|
||||
}
|
||||
|
||||
void obs_encoder_set_preferred_video_format(obs_encoder_t *encoder,
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ struct obs_display {
|
|||
uint32_t background_color;
|
||||
gs_swapchain_t *swap;
|
||||
pthread_mutex_t draw_callbacks_mutex;
|
||||
pthread_mutex_t draw_info_mutex;
|
||||
DARRAY(struct draw_callback) draw_callbacks;
|
||||
|
||||
struct obs_display *next;
|
||||
|
|
@ -276,8 +277,9 @@ struct obs_core_video {
|
|||
gs_effect_t *deinterlace_yadif_2x_effect;
|
||||
};
|
||||
|
||||
struct audio_monitor;
|
||||
|
||||
struct obs_core_audio {
|
||||
/* TODO: sound output subsystem */
|
||||
audio_t *audio;
|
||||
|
||||
DARRAY(struct obs_source*) render_order;
|
||||
|
|
@ -289,6 +291,11 @@ struct obs_core_audio {
|
|||
int total_buffering_ticks;
|
||||
|
||||
float user_volume;
|
||||
|
||||
pthread_mutex_t monitoring_mutex;
|
||||
DARRAY(struct audio_monitor*) monitors;
|
||||
char *monitoring_device_name;
|
||||
char *monitoring_device_id;
|
||||
};
|
||||
|
||||
/* user sources, output channels, and displays */
|
||||
|
|
@ -545,6 +552,7 @@ struct obs_source {
|
|||
volatile bool timing_set;
|
||||
volatile uint64_t timing_adjust;
|
||||
uint64_t resample_offset;
|
||||
uint64_t last_audio_ts;
|
||||
uint64_t next_audio_ts_min;
|
||||
uint64_t next_audio_sys_ts_min;
|
||||
uint64_t last_frame_ts;
|
||||
|
|
@ -593,6 +601,7 @@ struct obs_source {
|
|||
int async_plane_offset[2];
|
||||
bool async_flip;
|
||||
bool async_active;
|
||||
bool async_update_texture;
|
||||
DARRAY(struct async_frame) async_cache;
|
||||
DARRAY(struct obs_source_frame*)async_frames;
|
||||
pthread_mutex_t async_mutex;
|
||||
|
|
@ -659,6 +668,9 @@ struct obs_source {
|
|||
enum obs_transition_mode transition_mode;
|
||||
enum obs_transition_scale_type transition_scale_type;
|
||||
struct matrix4 transition_matrices[2];
|
||||
|
||||
struct audio_monitor *monitor;
|
||||
enum obs_monitoring_type monitoring_type;
|
||||
};
|
||||
|
||||
extern const struct obs_source_info *get_source_info(const char *id);
|
||||
|
|
@ -677,6 +689,10 @@ extern void obs_transition_enum_sources(obs_source_t *transition,
|
|||
extern void obs_transition_save(obs_source_t *source, obs_data_t *data);
|
||||
extern void obs_transition_load(obs_source_t *source, obs_data_t *data);
|
||||
|
||||
struct audio_monitor *audio_monitor_create(obs_source_t *source);
|
||||
void audio_monitor_reset(struct audio_monitor *monitor);
|
||||
extern void audio_monitor_destroy(struct audio_monitor *monitor);
|
||||
|
||||
extern void obs_source_destroy(struct obs_source *source);
|
||||
|
||||
enum view_type {
|
||||
|
|
@ -717,8 +733,6 @@ static inline enum gs_color_format convert_video_format(
|
|||
return GS_RGBA;
|
||||
else if (format == VIDEO_FORMAT_BGRA)
|
||||
return GS_BGRA;
|
||||
else if (format == VIDEO_FORMAT_Y800)
|
||||
return GS_R8;
|
||||
|
||||
return GS_BGRX;
|
||||
}
|
||||
|
|
@ -773,6 +787,13 @@ struct obs_weak_output {
|
|||
struct obs_output *output;
|
||||
};
|
||||
|
||||
#define CAPTION_LINE_CHARS (32)
|
||||
#define CAPTION_LINE_BYTES (4*CAPTION_LINE_CHARS)
|
||||
struct caption_text {
|
||||
char text[CAPTION_LINE_BYTES+1];
|
||||
struct caption_text *next;
|
||||
};
|
||||
|
||||
struct obs_output {
|
||||
struct obs_context_data context;
|
||||
struct obs_output_info info;
|
||||
|
|
@ -827,6 +848,11 @@ struct obs_output {
|
|||
struct video_scale_info video_conversion;
|
||||
struct audio_convert_info audio_conversion;
|
||||
|
||||
pthread_mutex_t caption_mutex;
|
||||
double caption_timestamp;
|
||||
struct caption_text *caption_head;
|
||||
struct caption_text *caption_tail;
|
||||
|
||||
bool valid;
|
||||
|
||||
uint64_t active_delay_ns;
|
||||
|
|
@ -863,6 +889,8 @@ extern const struct obs_output_info *find_output(const char *id);
|
|||
extern void obs_output_remove_encoder(struct obs_output *output,
|
||||
struct obs_encoder *encoder);
|
||||
|
||||
extern void obs_encoder_packet_create_instance(struct encoder_packet *dst,
|
||||
const struct encoder_packet *src);
|
||||
void obs_output_destroy(obs_output_t *output);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ static inline void push_packet(struct obs_output *output,
|
|||
|
||||
dd.msg = DELAY_MSG_PACKET;
|
||||
dd.ts = t;
|
||||
obs_duplicate_encoder_packet(&dd.packet, packet);
|
||||
obs_encoder_packet_create_instance(&dd.packet, packet);
|
||||
|
||||
pthread_mutex_lock(&output->delay_mutex);
|
||||
circlebuf_push_back(&output->delay_data, &dd, sizeof(dd));
|
||||
|
|
@ -48,7 +48,7 @@ static inline void process_delay_data(struct obs_output *output,
|
|||
switch (dd->msg) {
|
||||
case DELAY_MSG_PACKET:
|
||||
if (!delay_active(output) || !delay_capturing(output))
|
||||
obs_free_encoder_packet(&dd->packet);
|
||||
obs_encoder_packet_release(&dd->packet);
|
||||
else
|
||||
output->delay_callback(output, &dd->packet);
|
||||
break;
|
||||
|
|
@ -68,7 +68,7 @@ void obs_output_cleanup_delay(obs_output_t *output)
|
|||
while (output->delay_data.size) {
|
||||
circlebuf_pop_front(&output->delay_data, &dd, sizeof(dd));
|
||||
if (dd.msg == DELAY_MSG_PACKET) {
|
||||
obs_free_encoder_packet(&dd.packet);
|
||||
obs_encoder_packet_release(&dd.packet);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@
|
|||
#include "obs.h"
|
||||
#include "obs-internal.h"
|
||||
|
||||
#if BUILD_CAPTIONS
|
||||
#include <caption/caption.h>
|
||||
#include <caption/avc.h>
|
||||
#endif
|
||||
|
||||
static inline bool active(const struct obs_output *output)
|
||||
{
|
||||
return os_atomic_load_bool(&output->active);
|
||||
|
|
@ -99,11 +104,14 @@ obs_output_t *obs_output_create(const char *id, const char *name,
|
|||
output = bzalloc(sizeof(struct obs_output));
|
||||
pthread_mutex_init_value(&output->interleaved_mutex);
|
||||
pthread_mutex_init_value(&output->delay_mutex);
|
||||
pthread_mutex_init_value(&output->caption_mutex);
|
||||
|
||||
if (pthread_mutex_init(&output->interleaved_mutex, NULL) != 0)
|
||||
goto fail;
|
||||
if (pthread_mutex_init(&output->delay_mutex, NULL) != 0)
|
||||
goto fail;
|
||||
if (pthread_mutex_init(&output->caption_mutex, NULL) != 0)
|
||||
goto fail;
|
||||
if (os_event_init(&output->stopping_event, OS_EVENT_TYPE_MANUAL) != 0)
|
||||
goto fail;
|
||||
if (!init_output_handlers(output, name, settings, hotkey_data))
|
||||
|
|
@ -129,12 +137,6 @@ obs_output_t *obs_output_create(const char *id, const char *name,
|
|||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
if (info)
|
||||
output->context.data = info->create(output->context.settings,
|
||||
output);
|
||||
if (!output->context.data)
|
||||
blog(LOG_ERROR, "Failed to create output '%s'!", name);
|
||||
|
||||
output->reconnect_retry_sec = 2;
|
||||
output->reconnect_retry_max = 20;
|
||||
output->valid = true;
|
||||
|
|
@ -146,6 +148,12 @@ obs_output_t *obs_output_create(const char *id, const char *name,
|
|||
&obs->data.outputs_mutex,
|
||||
&obs->data.first_output);
|
||||
|
||||
if (info)
|
||||
output->context.data = info->create(output->context.settings,
|
||||
output);
|
||||
if (!output->context.data)
|
||||
blog(LOG_ERROR, "Failed to create output '%s'!", name);
|
||||
|
||||
blog(LOG_DEBUG, "output '%s' (%s) created", name, id);
|
||||
return output;
|
||||
|
||||
|
|
@ -157,7 +165,7 @@ fail:
|
|||
static inline void free_packets(struct obs_output *output)
|
||||
{
|
||||
for (size_t i = 0; i < output->interleaved_packets.num; i++)
|
||||
obs_free_encoder_packet(output->interleaved_packets.array+i);
|
||||
obs_encoder_packet_release(output->interleaved_packets.array+i);
|
||||
da_free(output->interleaved_packets);
|
||||
}
|
||||
|
||||
|
|
@ -196,6 +204,7 @@ void obs_output_destroy(obs_output_t *output)
|
|||
}
|
||||
|
||||
os_event_destroy(output->stopping_event);
|
||||
pthread_mutex_destroy(&output->caption_mutex);
|
||||
pthread_mutex_destroy(&output->interleaved_mutex);
|
||||
pthread_mutex_destroy(&output->delay_mutex);
|
||||
os_event_destroy(output->reconnect_stop_event);
|
||||
|
|
@ -235,6 +244,7 @@ bool obs_output_actual_start(obs_output_t *output)
|
|||
if (os_atomic_load_long(&output->delay_restart_refs))
|
||||
os_atomic_dec_long(&output->delay_restart_refs);
|
||||
|
||||
output->caption_timestamp = 0;
|
||||
return success;
|
||||
}
|
||||
|
||||
|
|
@ -356,6 +366,12 @@ void obs_output_actual_stop(obs_output_t *output, bool force, uint64_t ts)
|
|||
signal_stop(output);
|
||||
os_event_signal(output->stopping_event);
|
||||
}
|
||||
|
||||
while (output->caption_head) {
|
||||
output->caption_tail = output->caption_head->next;
|
||||
bfree(output->caption_head);
|
||||
output->caption_head = output->caption_tail;
|
||||
}
|
||||
}
|
||||
|
||||
void obs_output_stop(obs_output_t *output)
|
||||
|
|
@ -942,6 +958,56 @@ static inline bool has_higher_opposing_ts(struct obs_output *output,
|
|||
return output->highest_video_ts > packet->dts_usec;
|
||||
}
|
||||
|
||||
#if BUILD_CAPTIONS
|
||||
static const uint8_t nal_start[4] = {0, 0, 0, 1};
|
||||
|
||||
static bool add_caption(struct obs_output *output, struct encoder_packet *out)
|
||||
{
|
||||
struct encoder_packet backup = *out;
|
||||
caption_frame_t cf;
|
||||
sei_t sei;
|
||||
uint8_t *data;
|
||||
size_t size;
|
||||
long ref = 1;
|
||||
|
||||
DARRAY(uint8_t) out_data;
|
||||
|
||||
if (out->priority > 1)
|
||||
return false;
|
||||
|
||||
sei_init(&sei);
|
||||
|
||||
da_init(out_data);
|
||||
da_push_back_array(out_data, &ref, sizeof(ref));
|
||||
da_push_back_array(out_data, out->data, out->size);
|
||||
|
||||
caption_frame_init(&cf);
|
||||
caption_frame_from_text(&cf, &output->caption_head->text[0]);
|
||||
|
||||
sei_from_caption_frame(&sei, &cf);
|
||||
|
||||
data = malloc(sei_render_size(&sei));
|
||||
size = sei_render(&sei, data);
|
||||
/* TODO SEI should come after AUD/SPS/PPS, but before any VCL */
|
||||
da_push_back_array(out_data, nal_start, 4);
|
||||
da_push_back_array(out_data, data, size);
|
||||
free(data);
|
||||
|
||||
obs_encoder_packet_release(out);
|
||||
|
||||
*out = backup;
|
||||
out->data = (uint8_t*)out_data.array + sizeof(ref);
|
||||
out->size = out_data.num - sizeof(ref);
|
||||
|
||||
sei_free(&sei);
|
||||
|
||||
struct caption_text *next = output->caption_head->next;
|
||||
bfree(output->caption_head);
|
||||
output->caption_head = next;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void send_interleaved(struct obs_output *output)
|
||||
{
|
||||
struct encoder_packet out = output->interleaved_packets.array[0];
|
||||
|
|
@ -952,12 +1018,37 @@ static inline void send_interleaved(struct obs_output *output)
|
|||
if (!has_higher_opposing_ts(output, &out))
|
||||
return;
|
||||
|
||||
if (out.type == OBS_ENCODER_VIDEO)
|
||||
da_erase(output->interleaved_packets, 0);
|
||||
|
||||
if (out.type == OBS_ENCODER_VIDEO) {
|
||||
output->total_frames++;
|
||||
|
||||
da_erase(output->interleaved_packets, 0);
|
||||
#if BUILD_CAPTIONS
|
||||
pthread_mutex_lock(&output->caption_mutex);
|
||||
|
||||
double frame_timestamp = (out.pts * out.timebase_num) /
|
||||
(double)out.timebase_den;
|
||||
|
||||
/* TODO if output->caption_timestamp is more than 5 seconds
|
||||
* old, send empty frame */
|
||||
if (output->caption_head &&
|
||||
output->caption_timestamp <= frame_timestamp) {
|
||||
blog(LOG_INFO,"Sending caption: %f \"%s\"",
|
||||
frame_timestamp,
|
||||
&output->caption_head->text[0]);
|
||||
|
||||
if (add_caption(output, &out)) {
|
||||
output->caption_timestamp =
|
||||
frame_timestamp + 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&output->caption_mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
output->info.encoded_packet(output->context.data, &out);
|
||||
obs_free_encoder_packet(&out);
|
||||
obs_encoder_packet_release(&out);
|
||||
}
|
||||
|
||||
static inline void set_higher_ts(struct obs_output *output,
|
||||
|
|
@ -1056,7 +1147,7 @@ static void discard_to_idx(struct obs_output *output, size_t idx)
|
|||
for (size_t i = 0; i < idx; i++) {
|
||||
struct encoder_packet *packet =
|
||||
&output->interleaved_packets.array[i];
|
||||
obs_free_encoder_packet(packet);
|
||||
obs_encoder_packet_release(packet);
|
||||
}
|
||||
|
||||
da_erase_range(output->interleaved_packets, 0, idx);
|
||||
|
|
@ -1304,7 +1395,7 @@ static void interleave_packets(void *data, struct encoder_packet *packet)
|
|||
pthread_mutex_unlock(&output->interleaved_mutex);
|
||||
|
||||
if (output->active_delay_ns)
|
||||
obs_free_encoder_packet(packet);
|
||||
obs_encoder_packet_release(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1313,7 +1404,7 @@ static void interleave_packets(void *data, struct encoder_packet *packet)
|
|||
if (output->active_delay_ns)
|
||||
out = *packet;
|
||||
else
|
||||
obs_duplicate_encoder_packet(&out, packet);
|
||||
obs_encoder_packet_create_instance(&out, packet);
|
||||
|
||||
if (was_started)
|
||||
apply_interleaved_packet_offset(output, &out);
|
||||
|
|
@ -1356,7 +1447,7 @@ static void default_encoded_callback(void *param, struct encoder_packet *packet)
|
|||
}
|
||||
|
||||
if (output->active_delay_ns)
|
||||
obs_free_encoder_packet(packet);
|
||||
obs_encoder_packet_release(packet);
|
||||
}
|
||||
|
||||
static void default_raw_video_callback(void *param, struct video_data *frame)
|
||||
|
|
@ -1954,3 +2045,75 @@ const char *obs_output_get_id(const obs_output_t *output)
|
|||
return obs_output_valid(output, "obs_output_get_id")
|
||||
? output->info.id : NULL;
|
||||
}
|
||||
|
||||
#if BUILD_CAPTIONS
|
||||
static struct caption_text *caption_text_new(const char *text, size_t bytes,
|
||||
struct caption_text *tail, struct caption_text **head)
|
||||
{
|
||||
struct caption_text *next = bzalloc(sizeof(struct caption_text));
|
||||
snprintf(&next->text[0], CAPTION_LINE_BYTES + 1, "%.*s", bytes, text);
|
||||
|
||||
if (!*head) {
|
||||
*head = next;
|
||||
} else {
|
||||
tail->next = next;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
void obs_output_output_caption_text1(obs_output_t *output, const char *text)
|
||||
{
|
||||
if (!obs_output_valid(output, "obs_output_output_caption_text1"))
|
||||
return;
|
||||
if (!active(output))
|
||||
return;
|
||||
|
||||
// split text into 32 charcter strings
|
||||
int size = (int)strlen(text);
|
||||
int r;
|
||||
size_t char_count;
|
||||
size_t line_length = 0;
|
||||
size_t trimmed_length = 0;
|
||||
|
||||
blog(LOG_DEBUG, "Caption text: %s", text);
|
||||
|
||||
pthread_mutex_lock(&output->caption_mutex);
|
||||
|
||||
for (r = 0 ; 0 < size && CAPTION_LINE_CHARS > r; ++r) {
|
||||
line_length = utf8_line_length(text);
|
||||
trimmed_length = utf8_trimmed_length(text, line_length);
|
||||
char_count = utf8_char_count(text, trimmed_length);
|
||||
|
||||
if (SCREEN_COLS < char_count) {
|
||||
char_count = utf8_wrap_length(text, CAPTION_LINE_CHARS);
|
||||
line_length = utf8_string_length(text, char_count + 1);
|
||||
}
|
||||
|
||||
output->caption_tail = caption_text_new(
|
||||
text,
|
||||
line_length,
|
||||
output->caption_tail,
|
||||
&output->caption_head);
|
||||
|
||||
text += line_length;
|
||||
size -= (int)line_length;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&output->caption_mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
float obs_output_get_congestion(obs_output_t *output)
|
||||
{
|
||||
if (!obs_output_valid(output, "obs_output_get_congestion"))
|
||||
return 0;
|
||||
|
||||
if (output->info.get_congestion) {
|
||||
float val = output->info.get_congestion(output->context.data);
|
||||
if (val < 0.0f) val = 0.0f;
|
||||
else if (val > 1.0f) val = 1.0f;
|
||||
return val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ struct obs_output_info {
|
|||
|
||||
void *type_data;
|
||||
void (*free_type_data)(void *type_data);
|
||||
|
||||
float (*get_congestion)(void *data);
|
||||
};
|
||||
|
||||
EXPORT void obs_register_output_s(const struct obs_output_info *info,
|
||||
|
|
|
|||
|
|
@ -551,7 +551,7 @@ static inline struct list_data *get_list_fmt_data(struct obs_property *p,
|
|||
enum obs_combo_format format)
|
||||
{
|
||||
struct list_data *data = get_list_data(p);
|
||||
return (data->format == format) ? data : NULL;
|
||||
return (data && data->format == format) ? data : NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ static void scene_destroy(void *data)
|
|||
|
||||
static void scene_enum_sources(void *data,
|
||||
obs_source_enum_proc_t enum_callback,
|
||||
void *param)
|
||||
void *param, bool active)
|
||||
{
|
||||
struct obs_scene *scene = data;
|
||||
struct obs_scene_item *item;
|
||||
|
|
@ -175,7 +175,7 @@ static void scene_enum_sources(void *data,
|
|||
next = item->next;
|
||||
|
||||
obs_sceneitem_addref(item);
|
||||
if (os_atomic_load_long(&item->active_refs) > 0)
|
||||
if (!active || os_atomic_load_long(&item->active_refs) > 0)
|
||||
enum_callback(scene->source, item->source, param);
|
||||
obs_sceneitem_release(item);
|
||||
|
||||
|
|
@ -185,6 +185,20 @@ static void scene_enum_sources(void *data,
|
|||
full_unlock(scene);
|
||||
}
|
||||
|
||||
static void scene_enum_active_sources(void *data,
|
||||
obs_source_enum_proc_t enum_callback,
|
||||
void *param)
|
||||
{
|
||||
scene_enum_sources(data, enum_callback, param, true);
|
||||
}
|
||||
|
||||
static void scene_enum_all_sources(void *data,
|
||||
obs_source_enum_proc_t enum_callback,
|
||||
void *param)
|
||||
{
|
||||
scene_enum_sources(data, enum_callback, param, false);
|
||||
}
|
||||
|
||||
static inline void detach_sceneitem(struct obs_scene_item *item)
|
||||
{
|
||||
if (item->prev)
|
||||
|
|
@ -741,11 +755,13 @@ static void apply_scene_item_audio_actions(struct obs_scene_item *item,
|
|||
bool cur_visible = item->visible;
|
||||
uint64_t frame_num = 0;
|
||||
size_t deref_count = 0;
|
||||
float *buf;
|
||||
float *buf = NULL;
|
||||
|
||||
if (!*p_buf)
|
||||
*p_buf = malloc(AUDIO_OUTPUT_FRAMES * sizeof(float));
|
||||
buf = *p_buf;
|
||||
if (p_buf) {
|
||||
if (!*p_buf)
|
||||
*p_buf = malloc(AUDIO_OUTPUT_FRAMES * sizeof(float));
|
||||
buf = *p_buf;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&item->actions_mutex);
|
||||
|
||||
|
|
@ -760,7 +776,7 @@ static void apply_scene_item_audio_actions(struct obs_scene_item *item,
|
|||
new_frame_num = (timestamp - ts) * (uint64_t)sample_rate /
|
||||
1000000000ULL;
|
||||
|
||||
if (new_frame_num >= AUDIO_OUTPUT_FRAMES)
|
||||
if (ts && new_frame_num >= AUDIO_OUTPUT_FRAMES)
|
||||
break;
|
||||
|
||||
da_erase(item->audio_actions, i--);
|
||||
|
|
@ -769,7 +785,7 @@ static void apply_scene_item_audio_actions(struct obs_scene_item *item,
|
|||
if (!item->visible)
|
||||
deref_count++;
|
||||
|
||||
if (new_frame_num > frame_num) {
|
||||
if (buf && new_frame_num > frame_num) {
|
||||
for (; frame_num < new_frame_num; frame_num++)
|
||||
buf[frame_num] = cur_visible ? 1.0f : 0.0f;
|
||||
}
|
||||
|
|
@ -777,8 +793,10 @@ static void apply_scene_item_audio_actions(struct obs_scene_item *item,
|
|||
cur_visible = item->visible;
|
||||
}
|
||||
|
||||
for (; frame_num < AUDIO_OUTPUT_FRAMES; frame_num++)
|
||||
buf[frame_num] = cur_visible ? 1.0f : 0.0f;
|
||||
if (buf) {
|
||||
for (; frame_num < AUDIO_OUTPUT_FRAMES; frame_num++)
|
||||
buf[frame_num] = cur_visible ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&item->actions_mutex);
|
||||
|
||||
|
|
@ -790,7 +808,7 @@ static void apply_scene_item_audio_actions(struct obs_scene_item *item,
|
|||
}
|
||||
}
|
||||
|
||||
static inline bool apply_scene_item_volume(struct obs_scene_item *item,
|
||||
static bool apply_scene_item_volume(struct obs_scene_item *item,
|
||||
float **buf, uint64_t ts, size_t sample_rate)
|
||||
{
|
||||
bool actions_pending;
|
||||
|
|
@ -808,7 +826,7 @@ static inline bool apply_scene_item_volume(struct obs_scene_item *item,
|
|||
uint64_t duration = (uint64_t)AUDIO_OUTPUT_FRAMES *
|
||||
1000000000ULL / (uint64_t)sample_rate;
|
||||
|
||||
if (action.timestamp < (ts + duration)) {
|
||||
if (!ts || action.timestamp < (ts + duration)) {
|
||||
apply_scene_item_audio_actions(item, buf, ts,
|
||||
sample_rate);
|
||||
return true;
|
||||
|
|
@ -818,6 +836,12 @@ static inline bool apply_scene_item_volume(struct obs_scene_item *item,
|
|||
return false;
|
||||
}
|
||||
|
||||
static void process_all_audio_actions(struct obs_scene_item *item,
|
||||
size_t sample_rate)
|
||||
{
|
||||
while (apply_scene_item_volume(item, NULL, 0, sample_rate));
|
||||
}
|
||||
|
||||
static void mix_audio_with_buf(float *p_out, float *p_in, float *buf_in,
|
||||
size_t pos, size_t count)
|
||||
{
|
||||
|
|
@ -859,7 +883,7 @@ static bool scene_audio_render(void *data, uint64_t *ts_out,
|
|||
uint64_t source_ts =
|
||||
obs_source_get_audio_timestamp(item->source);
|
||||
|
||||
if (!timestamp || source_ts < timestamp)
|
||||
if (source_ts && (!timestamp || source_ts < timestamp))
|
||||
timestamp = source_ts;
|
||||
}
|
||||
|
||||
|
|
@ -867,6 +891,14 @@ static bool scene_audio_render(void *data, uint64_t *ts_out,
|
|||
}
|
||||
|
||||
if (!timestamp) {
|
||||
/* just process all pending audio actions if no audio playing,
|
||||
* otherwise audio actions will just never be processed */
|
||||
item = scene->first_item;
|
||||
while (item) {
|
||||
process_all_audio_actions(item, sample_rate);
|
||||
item = item->next;
|
||||
}
|
||||
|
||||
audio_unlock(scene);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -886,6 +918,11 @@ static bool scene_audio_render(void *data, uint64_t *ts_out,
|
|||
}
|
||||
|
||||
source_ts = obs_source_get_audio_timestamp(item->source);
|
||||
if (!source_ts) {
|
||||
item = item->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
pos = (size_t)ns_to_audio_frames(sample_rate,
|
||||
source_ts - timestamp);
|
||||
count = AUDIO_OUTPUT_FRAMES - pos;
|
||||
|
|
@ -939,7 +976,8 @@ const struct obs_source_info scene_info =
|
|||
.get_height = scene_getheight,
|
||||
.load = scene_load,
|
||||
.save = scene_save,
|
||||
.enum_active_sources = scene_enum_sources
|
||||
.enum_active_sources = scene_enum_active_sources,
|
||||
.enum_all_sources = scene_enum_all_sources
|
||||
};
|
||||
|
||||
obs_scene_t *obs_scene_create(const char *name)
|
||||
|
|
@ -1054,6 +1092,8 @@ obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name,
|
|||
new_item->align = item->align;
|
||||
new_item->last_width = item->last_width;
|
||||
new_item->last_height = item->last_height;
|
||||
new_item->output_scale = item->output_scale;
|
||||
new_item->scale_filter = item->scale_filter;
|
||||
new_item->box_transform = item->box_transform;
|
||||
new_item->draw_transform = item->draw_transform;
|
||||
new_item->bounds_type = item->bounds_type;
|
||||
|
|
@ -1360,8 +1400,7 @@ void obs_sceneitem_remove(obs_sceneitem_t *item)
|
|||
|
||||
scene = item->parent;
|
||||
|
||||
if (scene)
|
||||
full_lock(scene);
|
||||
full_lock(scene);
|
||||
|
||||
if (item->removed) {
|
||||
if (scene)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ static bool ready_deinterlace_frames(obs_source_t *source, uint64_t sys_time)
|
|||
if (source->async_frames.num == 2)
|
||||
source->async_frames.array[0]->prev_frame = true;
|
||||
source->deinterlace_offset = 0;
|
||||
source->last_frame_ts = next_frame->timestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ bool obs_source_init(struct obs_source *source)
|
|||
source->control = bzalloc(sizeof(obs_weak_source_t));
|
||||
source->deinterlace_top_first = true;
|
||||
source->control->source = source;
|
||||
source->audio_mixers = 0xF;
|
||||
source->audio_mixers = 0xFF;
|
||||
|
||||
if (is_audio_source(source)) {
|
||||
pthread_mutex_lock(&obs->data.audio_sources_mutex);
|
||||
|
|
@ -410,7 +410,9 @@ obs_source_t *obs_source_duplicate(obs_source_t *source,
|
|||
obs_scene_t *new_scene = obs_scene_duplicate(scene, new_name,
|
||||
create_private ? OBS_SCENE_DUP_PRIVATE_COPY :
|
||||
OBS_SCENE_DUP_COPY);
|
||||
return obs_scene_get_source(new_scene);
|
||||
obs_source_t *new_source = obs_scene_get_source(new_scene);
|
||||
duplicate_filters(new_source, source, create_private);
|
||||
return new_source;
|
||||
}
|
||||
|
||||
settings = obs_data_create();
|
||||
|
|
@ -501,6 +503,8 @@ void obs_source_destroy(struct obs_source *source)
|
|||
source->context.data = NULL;
|
||||
}
|
||||
|
||||
audio_monitor_destroy(source->monitor);
|
||||
|
||||
obs_hotkey_unregister(source->push_to_talk_key);
|
||||
obs_hotkey_unregister(source->push_to_mute_key);
|
||||
obs_hotkey_pair_unregister(source->mute_unmute_key);
|
||||
|
|
@ -919,6 +923,35 @@ void obs_source_deactivate(obs_source_t *source, enum view_type type)
|
|||
|
||||
static inline struct obs_source_frame *get_closest_frame(obs_source_t *source,
|
||||
uint64_t sys_time);
|
||||
bool set_async_texture_size(struct obs_source *source,
|
||||
const struct obs_source_frame *frame);
|
||||
|
||||
static void async_tick(obs_source_t *source)
|
||||
{
|
||||
uint64_t sys_time = obs->video.video_time;
|
||||
|
||||
pthread_mutex_lock(&source->async_mutex);
|
||||
|
||||
if (deinterlacing_enabled(source)) {
|
||||
deinterlace_process_last_frame(source, sys_time);
|
||||
} else {
|
||||
if (source->cur_async_frame) {
|
||||
remove_async_frame(source,
|
||||
source->cur_async_frame);
|
||||
source->cur_async_frame = NULL;
|
||||
}
|
||||
|
||||
source->cur_async_frame = get_closest_frame(source,
|
||||
sys_time);
|
||||
}
|
||||
|
||||
source->last_sys_timestamp = sys_time;
|
||||
pthread_mutex_unlock(&source->async_mutex);
|
||||
|
||||
if (source->cur_async_frame)
|
||||
source->async_update_texture = set_async_texture_size(source,
|
||||
source->cur_async_frame);
|
||||
}
|
||||
|
||||
void obs_source_video_tick(obs_source_t *source, float seconds)
|
||||
{
|
||||
|
|
@ -930,27 +963,8 @@ void obs_source_video_tick(obs_source_t *source, float seconds)
|
|||
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION)
|
||||
obs_transition_tick(source);
|
||||
|
||||
if ((source->info.output_flags & OBS_SOURCE_ASYNC) != 0) {
|
||||
uint64_t sys_time = obs->video.video_time;
|
||||
|
||||
pthread_mutex_lock(&source->async_mutex);
|
||||
|
||||
if (deinterlacing_enabled(source)) {
|
||||
deinterlace_process_last_frame(source, sys_time);
|
||||
} else {
|
||||
if (source->cur_async_frame) {
|
||||
remove_async_frame(source,
|
||||
source->cur_async_frame);
|
||||
source->cur_async_frame = NULL;
|
||||
}
|
||||
|
||||
source->cur_async_frame = get_closest_frame(source,
|
||||
sys_time);
|
||||
}
|
||||
|
||||
source->last_sys_timestamp = sys_time;
|
||||
pthread_mutex_unlock(&source->async_mutex);
|
||||
}
|
||||
if ((source->info.output_flags & OBS_SOURCE_ASYNC) != 0)
|
||||
async_tick(source);
|
||||
|
||||
if (source->defer_update)
|
||||
obs_source_deferred_update(source);
|
||||
|
|
@ -1038,7 +1052,7 @@ static void handle_ts_jump(obs_source_t *source, uint64_t expected,
|
|||
}
|
||||
|
||||
static void source_signal_audio_data(obs_source_t *source,
|
||||
struct audio_data *in, bool muted)
|
||||
const struct audio_data *in, bool muted)
|
||||
{
|
||||
pthread_mutex_lock(&source->audio_cb_mutex);
|
||||
|
||||
|
|
@ -1172,6 +1186,7 @@ static void source_output_audio_data(obs_source_t *source,
|
|||
in.timestamp = source->next_audio_ts_min;
|
||||
}
|
||||
|
||||
source->last_audio_ts = in.timestamp;
|
||||
source->next_audio_ts_min = in.timestamp +
|
||||
conv_frames_to_time(sample_rate, in.frames);
|
||||
|
||||
|
|
@ -1214,14 +1229,16 @@ static void source_output_audio_data(obs_source_t *source,
|
|||
source->last_sync_offset = sync_offset;
|
||||
}
|
||||
|
||||
if (push_back && source->audio_ts)
|
||||
source_output_audio_push_back(source, &in);
|
||||
else
|
||||
source_output_audio_place(source, &in);
|
||||
if (source->monitoring_type != OBS_MONITORING_TYPE_MONITOR_ONLY) {
|
||||
if (push_back && source->audio_ts)
|
||||
source_output_audio_push_back(source, &in);
|
||||
else
|
||||
source_output_audio_place(source, &in);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&source->audio_buf_mutex);
|
||||
|
||||
source_signal_audio_data(source, &in, source_muted(source, os_time));
|
||||
source_signal_audio_data(source, data, source_muted(source, os_time));
|
||||
}
|
||||
|
||||
enum convert_type {
|
||||
|
|
@ -1331,6 +1348,8 @@ bool set_async_texture_size(struct obs_source *source,
|
|||
source->async_height = frame->height;
|
||||
source->async_format = frame->format;
|
||||
|
||||
gs_enter_context(obs->video.graphics);
|
||||
|
||||
gs_texture_destroy(source->async_texture);
|
||||
gs_texture_destroy(source->async_prev_texture);
|
||||
gs_texrender_destroy(source->async_texrender);
|
||||
|
|
@ -1365,6 +1384,8 @@ bool set_async_texture_size(struct obs_source *source,
|
|||
if (deinterlacing_enabled(source))
|
||||
set_deinterlace_texture_size(source);
|
||||
|
||||
gs_leave_context();
|
||||
|
||||
return !!source->async_texture;
|
||||
}
|
||||
|
||||
|
|
@ -1612,10 +1633,11 @@ static void obs_source_update_async_video(obs_source_t *source)
|
|||
os_gettime_ns() - frame->timestamp;
|
||||
source->timing_set = true;
|
||||
|
||||
if (set_async_texture_size(source, frame)) {
|
||||
if (source->async_update_texture) {
|
||||
update_async_texture(source, frame,
|
||||
source->async_texture,
|
||||
source->async_texrender);
|
||||
source->async_update_texture = false;
|
||||
}
|
||||
|
||||
obs_source_release_frame(source, frame);
|
||||
|
|
@ -1862,10 +1884,9 @@ void obs_source_filter_add(obs_source_t *source, obs_source_t *filter)
|
|||
|
||||
signal_handler_signal(source->context.signals, "filter_add", &cd);
|
||||
|
||||
if (source && filter)
|
||||
blog(LOG_DEBUG, "- filter '%s' (%s) added to source '%s'",
|
||||
filter->context.name, filter->info.id,
|
||||
source->context.name);
|
||||
blog(LOG_DEBUG, "- filter '%s' (%s) added to source '%s'",
|
||||
filter->context.name, filter->info.id,
|
||||
source->context.name);
|
||||
}
|
||||
|
||||
static bool obs_source_filter_remove_refless(obs_source_t *source,
|
||||
|
|
@ -1898,10 +1919,9 @@ static bool obs_source_filter_remove_refless(obs_source_t *source,
|
|||
|
||||
signal_handler_signal(source->context.signals, "filter_remove", &cd);
|
||||
|
||||
if (source && filter)
|
||||
blog(LOG_DEBUG, "- filter '%s' (%s) removed from source '%s'",
|
||||
filter->context.name, filter->info.id,
|
||||
source->context.name);
|
||||
blog(LOG_DEBUG, "- filter '%s' (%s) removed from source '%s'",
|
||||
filter->context.name, filter->info.id,
|
||||
source->context.name);
|
||||
|
||||
if (filter->info.filter_remove)
|
||||
filter->info.filter_remove(filter->context.data,
|
||||
|
|
@ -2081,6 +2101,41 @@ static inline void copy_frame_data_plane(struct obs_source_frame *dst,
|
|||
dst->linesize[plane] * lines);
|
||||
}
|
||||
|
||||
static void copy_frame_data_line_y800(uint32_t *dst, uint8_t *src, uint8_t *end)
|
||||
{
|
||||
while (src < end) {
|
||||
register uint32_t val = *(src++);
|
||||
val |= (val << 8);
|
||||
val |= (val << 16);
|
||||
*(dst++) = val;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void copy_frame_data_y800(struct obs_source_frame *dst,
|
||||
const struct obs_source_frame *src)
|
||||
{
|
||||
uint32_t *ptr_dst;
|
||||
uint8_t *ptr_src;
|
||||
uint8_t *src_end;
|
||||
|
||||
if ((src->linesize[0] * 4) != dst->linesize[0]) {
|
||||
for (uint32_t cy = 0; cy < src->height; cy++) {
|
||||
ptr_dst = (uint32_t*)
|
||||
(dst->data[0] + cy * dst->linesize[0]);
|
||||
ptr_src = (src->data[0] + cy * src->linesize[0]);
|
||||
src_end = ptr_src + src->width;
|
||||
|
||||
copy_frame_data_line_y800(ptr_dst, ptr_src, src_end);
|
||||
}
|
||||
} else {
|
||||
ptr_dst = (uint32_t*)dst->data[0];
|
||||
ptr_src = (uint8_t *)src->data[0];
|
||||
src_end = ptr_src + src->height * src->linesize[0];
|
||||
|
||||
copy_frame_data_line_y800(ptr_dst, ptr_src, src_end);
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_frame_data(struct obs_source_frame *dst,
|
||||
const struct obs_source_frame *src)
|
||||
{
|
||||
|
|
@ -2094,7 +2149,7 @@ static void copy_frame_data(struct obs_source_frame *dst,
|
|||
memcpy(dst->color_range_max, src->color_range_max, size);
|
||||
}
|
||||
|
||||
switch (dst->format) {
|
||||
switch (src->format) {
|
||||
case VIDEO_FORMAT_I420:
|
||||
copy_frame_data_plane(dst, src, 0, dst->height);
|
||||
copy_frame_data_plane(dst, src, 1, dst->height/2);
|
||||
|
|
@ -2115,12 +2170,16 @@ static void copy_frame_data(struct obs_source_frame *dst,
|
|||
case VIDEO_FORMAT_YVYU:
|
||||
case VIDEO_FORMAT_YUY2:
|
||||
case VIDEO_FORMAT_UYVY:
|
||||
case VIDEO_FORMAT_Y800:
|
||||
case VIDEO_FORMAT_NONE:
|
||||
case VIDEO_FORMAT_RGBA:
|
||||
case VIDEO_FORMAT_BGRA:
|
||||
case VIDEO_FORMAT_BGRX:
|
||||
copy_frame_data_plane(dst, src, 0, dst->height);
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_Y800:
|
||||
copy_frame_data_y800(dst, src);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2201,8 +2260,12 @@ static inline struct obs_source_frame *cache_video(struct obs_source *source,
|
|||
|
||||
if (!new_frame) {
|
||||
struct async_frame new_af;
|
||||
enum video_format format = frame->format;
|
||||
|
||||
new_frame = obs_source_frame_create(frame->format,
|
||||
if (format == VIDEO_FORMAT_Y800)
|
||||
format = VIDEO_FORMAT_BGRX;
|
||||
|
||||
new_frame = obs_source_frame_create(format,
|
||||
frame->width, frame->height);
|
||||
new_af.frame = new_frame;
|
||||
new_af.used = true;
|
||||
|
|
@ -2453,6 +2516,7 @@ static bool ready_async_frame(obs_source_t *source, uint64_t sys_time)
|
|||
next_frame = source->async_frames.array[0];
|
||||
}
|
||||
|
||||
source->last_frame_ts = next_frame->timestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2908,19 +2972,19 @@ struct source_enum_data {
|
|||
void *param;
|
||||
};
|
||||
|
||||
static void enum_source_tree_callback(obs_source_t *parent, obs_source_t *child,
|
||||
void *param)
|
||||
static void enum_source_active_tree_callback(obs_source_t *parent,
|
||||
obs_source_t *child, void *param)
|
||||
{
|
||||
struct source_enum_data *data = param;
|
||||
bool is_transition = child->info.type == OBS_SOURCE_TYPE_TRANSITION;
|
||||
|
||||
if (is_transition)
|
||||
obs_transition_enum_sources(child,
|
||||
enum_source_tree_callback, param);
|
||||
enum_source_active_tree_callback, param);
|
||||
if (child->info.enum_active_sources) {
|
||||
if (child->context.data) {
|
||||
child->info.enum_active_sources(child->context.data,
|
||||
enum_source_tree_callback, data);
|
||||
enum_source_active_tree_callback, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2967,11 +3031,67 @@ void obs_source_enum_active_tree(obs_source_t *source,
|
|||
obs_source_addref(source);
|
||||
|
||||
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION)
|
||||
obs_transition_enum_sources(source, enum_source_tree_callback,
|
||||
&data);
|
||||
obs_transition_enum_sources(source,
|
||||
enum_source_active_tree_callback, &data);
|
||||
if (source->info.enum_active_sources)
|
||||
source->info.enum_active_sources(source->context.data,
|
||||
enum_source_tree_callback, &data);
|
||||
enum_source_active_tree_callback, &data);
|
||||
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
static void enum_source_full_tree_callback(obs_source_t *parent,
|
||||
obs_source_t *child, void *param)
|
||||
{
|
||||
struct source_enum_data *data = param;
|
||||
bool is_transition = child->info.type == OBS_SOURCE_TYPE_TRANSITION;
|
||||
|
||||
if (is_transition)
|
||||
obs_transition_enum_sources(child,
|
||||
enum_source_full_tree_callback, param);
|
||||
if (child->info.enum_all_sources) {
|
||||
if (child->context.data) {
|
||||
child->info.enum_active_sources(child->context.data,
|
||||
enum_source_full_tree_callback, data);
|
||||
}
|
||||
} else if (child->info.enum_active_sources) {
|
||||
if (child->context.data) {
|
||||
child->info.enum_active_sources(child->context.data,
|
||||
enum_source_full_tree_callback, data);
|
||||
}
|
||||
}
|
||||
|
||||
data->enum_callback(parent, child, data->param);
|
||||
}
|
||||
|
||||
static void obs_source_enum_full_tree(obs_source_t *source,
|
||||
obs_source_enum_proc_t enum_callback,
|
||||
void *param)
|
||||
{
|
||||
struct source_enum_data data = {enum_callback, param};
|
||||
bool is_transition;
|
||||
|
||||
if (!data_valid(source, "obs_source_enum_active_tree"))
|
||||
return;
|
||||
|
||||
is_transition = source->info.type == OBS_SOURCE_TYPE_TRANSITION;
|
||||
if (!is_transition && !source->info.enum_active_sources)
|
||||
return;
|
||||
|
||||
obs_source_addref(source);
|
||||
|
||||
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION)
|
||||
obs_transition_enum_sources(source,
|
||||
enum_source_full_tree_callback, &data);
|
||||
|
||||
if (source->info.enum_all_sources) {
|
||||
source->info.enum_all_sources(source->context.data,
|
||||
enum_source_full_tree_callback, &data);
|
||||
|
||||
} else if (source->info.enum_active_sources) {
|
||||
source->info.enum_active_sources(source->context.data,
|
||||
enum_source_full_tree_callback, &data);
|
||||
}
|
||||
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
|
@ -3003,7 +3123,7 @@ bool obs_source_add_active_child(obs_source_t *parent, obs_source_t *child)
|
|||
return false;
|
||||
}
|
||||
|
||||
obs_source_enum_active_tree(child, check_descendant, &info);
|
||||
obs_source_enum_full_tree(child, check_descendant, &info);
|
||||
if (info.exists)
|
||||
return false;
|
||||
|
||||
|
|
@ -3774,3 +3894,38 @@ void obs_source_remove_audio_capture_callback(obs_source_t *source,
|
|||
da_erase_item(source->audio_cb_list, &info);
|
||||
pthread_mutex_unlock(&source->audio_cb_mutex);
|
||||
}
|
||||
|
||||
void obs_source_set_monitoring_type(obs_source_t *source,
|
||||
enum obs_monitoring_type type)
|
||||
{
|
||||
bool was_on;
|
||||
bool now_on;
|
||||
|
||||
if (!obs_source_valid(source, "obs_source_set_monitoring_type"))
|
||||
return;
|
||||
if (source->info.output_flags & OBS_SOURCE_DO_NOT_MONITOR)
|
||||
return;
|
||||
if (source->monitoring_type == type)
|
||||
return;
|
||||
|
||||
was_on = source->monitoring_type != OBS_MONITORING_TYPE_NONE;
|
||||
now_on = type != OBS_MONITORING_TYPE_NONE;
|
||||
|
||||
if (was_on != now_on) {
|
||||
if (!was_on) {
|
||||
source->monitor = audio_monitor_create(source);
|
||||
} else {
|
||||
audio_monitor_destroy(source->monitor);
|
||||
source->monitor = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
source->monitoring_type = type;
|
||||
}
|
||||
|
||||
enum obs_monitoring_type obs_source_get_monitoring_type(
|
||||
const obs_source_t *source)
|
||||
{
|
||||
return obs_source_valid(source, "obs_source_get_monitoring_type") ?
|
||||
source->monitoring_type : OBS_MONITORING_TYPE_NONE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,14 @@ enum obs_source_type {
|
|||
*/
|
||||
#define OBS_SOURCE_DEPRECATED (1<<8)
|
||||
|
||||
/**
|
||||
* Source cannot have its audio monitored
|
||||
*
|
||||
* Specifies that this source may cause a feedback loop if audio is monitored.
|
||||
* This is used primarily with desktop audio capture sources.
|
||||
*/
|
||||
#define OBS_SOURCE_DO_NOT_MONITOR (1<<9)
|
||||
|
||||
/** @} */
|
||||
|
||||
typedef void (*obs_source_enum_proc_t)(obs_source_t *parent,
|
||||
|
|
@ -400,6 +408,21 @@ struct obs_source_info {
|
|||
bool (*audio_render)(void *data, uint64_t *ts_out,
|
||||
struct obs_source_audio_mix *audio_output,
|
||||
uint32_t mixers, size_t channels, size_t sample_rate);
|
||||
|
||||
/**
|
||||
* Called to enumerate all active and inactive sources being used
|
||||
* within this source. If this callback isn't implemented,
|
||||
* enum_active_sources will be called instead.
|
||||
*
|
||||
* This is typically used if a source can have inactive child sources.
|
||||
*
|
||||
* @param data Filter data
|
||||
* @param enum_callback Enumeration callback
|
||||
* @param param User data to pass to callback
|
||||
*/
|
||||
void (*enum_all_sources)(void *data,
|
||||
obs_source_enum_proc_t enum_callback,
|
||||
void *param);
|
||||
};
|
||||
|
||||
EXPORT void obs_register_source_s(const struct obs_source_info *info,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include <windows.h>
|
||||
#include <time.h>
|
||||
#include <dbghelp.h>
|
||||
#include <shellapi.h>
|
||||
#include <tlhelp32.h>
|
||||
|
|
@ -248,16 +249,26 @@ static inline void init_module_info(struct exception_handler_data *data)
|
|||
|
||||
static inline void write_header(struct exception_handler_data *data)
|
||||
{
|
||||
char date_time[80];
|
||||
time_t now = time(0);
|
||||
struct tm ts;
|
||||
ts = *localtime(&now);
|
||||
strftime(date_time, sizeof(date_time), "%Y-%m-%d, %X", &ts);
|
||||
|
||||
dstr_catf(&data->str, "Unhandled exception: %x\r\n"
|
||||
"Date/Time: %s\r\n"
|
||||
"Fault address: %"PRIX64" (%s)\r\n"
|
||||
"libobs version: "OBS_VERSION"\r\n"
|
||||
"Windows version: %d.%d build %d (revision %d)\r\n"
|
||||
"Windows version: %d.%d build %d (revision: %d; "
|
||||
"%s-bit)\r\n"
|
||||
"CPU: %s\r\n\r\n",
|
||||
data->exception->ExceptionRecord->ExceptionCode,
|
||||
date_time,
|
||||
data->main_trace.instruction_ptr,
|
||||
data->module_name.array,
|
||||
data->win_version.major, data->win_version.minor,
|
||||
data->win_version.build, data->win_version.revis,
|
||||
is_64_bit_windows() ? "64" : "32",
|
||||
data->cpu_info.array);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -157,31 +157,23 @@ static void log_processor_cores(void)
|
|||
|
||||
static void log_available_memory(void)
|
||||
{
|
||||
MEMORYSTATUS ms;
|
||||
GlobalMemoryStatus(&ms);
|
||||
MEMORYSTATUSEX ms;
|
||||
ms.dwLength = sizeof(ms);
|
||||
|
||||
GlobalMemoryStatusEx(&ms);
|
||||
|
||||
#ifdef _WIN64
|
||||
const char *note = "";
|
||||
#else
|
||||
const char *note = " (NOTE: 2 or 4 gigs max is normal for 32bit programs)";
|
||||
const char *note = " (NOTE: 32bit programs cannot use more than 3gb)";
|
||||
#endif
|
||||
|
||||
blog(LOG_INFO, "Physical Memory: %luMB Total, %luMB Free%s",
|
||||
(DWORD)(ms.dwTotalPhys / 1048576),
|
||||
(DWORD)(ms.dwAvailPhys / 1048576),
|
||||
(DWORD)(ms.ullTotalPhys / 1048576),
|
||||
(DWORD)(ms.ullAvailPhys / 1048576),
|
||||
note);
|
||||
}
|
||||
|
||||
static bool is_64_bit_windows(void)
|
||||
{
|
||||
#if defined(_WIN64)
|
||||
return true;
|
||||
#elif defined(_WIN32)
|
||||
BOOL b64 = false;
|
||||
return IsWow64Process(GetCurrentProcess(), &b64) && b64;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void log_windows_version(void)
|
||||
{
|
||||
struct win_version_info ver;
|
||||
|
|
|
|||
77
libobs/obs.c
77
libobs/obs.c
|
|
@ -488,10 +488,22 @@ static bool obs_init_audio(struct audio_output_info *ai)
|
|||
struct obs_core_audio *audio = &obs->audio;
|
||||
int errorcode;
|
||||
|
||||
/* TODO: sound subsystem */
|
||||
pthread_mutexattr_t attr;
|
||||
|
||||
pthread_mutex_init_value(&audio->monitoring_mutex);
|
||||
|
||||
if (pthread_mutexattr_init(&attr) != 0)
|
||||
return false;
|
||||
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
|
||||
return false;
|
||||
if (pthread_mutex_init(&audio->monitoring_mutex, &attr) != 0)
|
||||
return false;
|
||||
|
||||
audio->user_volume = 1.0f;
|
||||
|
||||
audio->monitoring_device_name = bstrdup("Default");
|
||||
audio->monitoring_device_id = bstrdup("default");
|
||||
|
||||
errorcode = audio_output_open(&audio->audio, ai);
|
||||
if (errorcode == AUDIO_OUTPUT_SUCCESS)
|
||||
return true;
|
||||
|
|
@ -513,6 +525,11 @@ static void obs_free_audio(void)
|
|||
da_free(audio->render_order);
|
||||
da_free(audio->root_nodes);
|
||||
|
||||
da_free(audio->monitors);
|
||||
bfree(audio->monitoring_device_name);
|
||||
bfree(audio->monitoring_device_id);
|
||||
pthread_mutex_destroy(&audio->monitoring_mutex);
|
||||
|
||||
memset(audio, 0, sizeof(struct obs_core_audio));
|
||||
}
|
||||
|
||||
|
|
@ -725,6 +742,8 @@ static bool obs_init(const char *locale, const char *module_config_path,
|
|||
{
|
||||
obs = bzalloc(sizeof(struct obs_core));
|
||||
|
||||
pthread_mutex_init_value(&obs->audio.monitoring_mutex);
|
||||
|
||||
obs->name_store_owned = !store;
|
||||
obs->name_store = store ? store : profiler_name_store_create();
|
||||
if (!obs->name_store) {
|
||||
|
|
@ -908,11 +927,6 @@ int obs_reset_video(struct obs_video_info *ovi)
|
|||
stop_video();
|
||||
obs_free_video();
|
||||
|
||||
if (!ovi) {
|
||||
obs_free_graphics();
|
||||
return OBS_VIDEO_SUCCESS;
|
||||
}
|
||||
|
||||
/* align to multiple-of-two and SSE alignment sizes */
|
||||
ovi->output_width &= 0xFFFFFFFC;
|
||||
ovi->output_height &= 0xFFFFFFFE;
|
||||
|
|
@ -1454,6 +1468,7 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data)
|
|||
uint32_t mixers;
|
||||
int di_order;
|
||||
int di_mode;
|
||||
int monitoring_type;
|
||||
|
||||
source = obs_source_create(id, name, settings, hotkeys);
|
||||
|
||||
|
|
@ -1505,6 +1520,10 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data)
|
|||
obs_source_set_deinterlace_field_order(source,
|
||||
(enum obs_deinterlace_field_order)di_order);
|
||||
|
||||
monitoring_type = (int)obs_data_get_int(source_data, "monitoring_type");
|
||||
obs_source_set_monitoring_type(source,
|
||||
(enum obs_monitoring_type)monitoring_type);
|
||||
|
||||
if (filters) {
|
||||
size_t count = obs_data_array_count(filters);
|
||||
|
||||
|
|
@ -1601,6 +1620,7 @@ obs_data_t *obs_save_source(obs_source_t *source)
|
|||
uint64_t ptm_delay = obs_source_get_push_to_mute_delay(source);
|
||||
bool push_to_talk= obs_source_push_to_talk_enabled(source);
|
||||
uint64_t ptt_delay = obs_source_get_push_to_talk_delay(source);
|
||||
int m_type = (int)obs_source_get_monitoring_type(source);
|
||||
int di_mode = (int)obs_source_get_deinterlace_mode(source);
|
||||
int di_order =
|
||||
(int)obs_source_get_deinterlace_field_order(source);
|
||||
|
|
@ -1630,6 +1650,7 @@ obs_data_t *obs_save_source(obs_source_t *source)
|
|||
obs_data_set_obj (source_data, "hotkeys", hotkey_data);
|
||||
obs_data_set_int (source_data, "deinterlace_mode", di_mode);
|
||||
obs_data_set_int (source_data, "deinterlace_field_order", di_order);
|
||||
obs_data_set_int (source_data, "monitoring_type", m_type);
|
||||
|
||||
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION)
|
||||
obs_transition_save(source, source_data);
|
||||
|
|
@ -1876,3 +1897,47 @@ bool obs_obj_invalid(void *obj)
|
|||
|
||||
return !context->data;
|
||||
}
|
||||
|
||||
bool obs_set_audio_monitoring_device(const char *name, const char *id)
|
||||
{
|
||||
if (!obs || !name || !id || !*name || !*id)
|
||||
return false;
|
||||
|
||||
#ifdef _WIN32
|
||||
pthread_mutex_lock(&obs->audio.monitoring_mutex);
|
||||
|
||||
if (strcmp(id, obs->audio.monitoring_device_id) == 0) {
|
||||
pthread_mutex_unlock(&obs->audio.monitoring_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obs->audio.monitoring_device_name)
|
||||
bfree(obs->audio.monitoring_device_name);
|
||||
if (obs->audio.monitoring_device_id)
|
||||
bfree(obs->audio.monitoring_device_id);
|
||||
|
||||
obs->audio.monitoring_device_name = bstrdup(name);
|
||||
obs->audio.monitoring_device_id = bstrdup(id);
|
||||
|
||||
for (size_t i = 0; i < obs->audio.monitors.num; i++) {
|
||||
struct audio_monitor *monitor = obs->audio.monitors.array[i];
|
||||
audio_monitor_reset(monitor);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&obs->audio.monitoring_mutex);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void obs_get_audio_monitoring_device(const char **name, const char **id)
|
||||
{
|
||||
if (!obs)
|
||||
return;
|
||||
|
||||
if (name)
|
||||
*name = obs->audio.monitoring_device_name;
|
||||
if (id)
|
||||
*id = obs->audio.monitoring_device_id;
|
||||
}
|
||||
|
|
|
|||
37
libobs/obs.h
37
libobs/obs.h
|
|
@ -533,8 +533,8 @@ enum obs_base_effect {
|
|||
EXPORT gs_effect_t *obs_get_base_effect(enum obs_base_effect effect);
|
||||
|
||||
/* DEPRECATED: gets texture_rect default effect */
|
||||
DEPRECATED_START EXPORT gs_effect_t *obs_get_default_rect_effect(void)
|
||||
DEPRECATED_END;
|
||||
DEPRECATED
|
||||
EXPORT gs_effect_t *obs_get_default_rect_effect(void);
|
||||
|
||||
/** Returns the primary obs signal handler */
|
||||
EXPORT signal_handler_t *obs_get_signal_handler(void);
|
||||
|
|
@ -582,6 +582,15 @@ EXPORT enum obs_obj_type obs_obj_get_type(void *obj);
|
|||
EXPORT const char *obs_obj_get_id(void *obj);
|
||||
EXPORT bool obs_obj_invalid(void *obj);
|
||||
|
||||
typedef bool (*obs_enum_audio_device_cb)(void *data, const char *name,
|
||||
const char *id);
|
||||
|
||||
EXPORT void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb,
|
||||
void *data);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* View context */
|
||||
|
|
@ -914,6 +923,17 @@ EXPORT void obs_source_set_deinterlace_field_order(obs_source_t *source,
|
|||
EXPORT enum obs_deinterlace_field_order obs_source_get_deinterlace_field_order(
|
||||
const obs_source_t *source);
|
||||
|
||||
enum obs_monitoring_type {
|
||||
OBS_MONITORING_TYPE_NONE,
|
||||
OBS_MONITORING_TYPE_MONITOR_ONLY,
|
||||
OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT
|
||||
};
|
||||
|
||||
EXPORT void obs_source_set_monitoring_type(obs_source_t *source,
|
||||
enum obs_monitoring_type type);
|
||||
EXPORT enum obs_monitoring_type obs_source_get_monitoring_type(
|
||||
const obs_source_t *source);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Functions used by sources */
|
||||
|
||||
|
|
@ -1460,6 +1480,13 @@ EXPORT uint32_t obs_output_get_height(const obs_output_t *output);
|
|||
|
||||
EXPORT const char *obs_output_get_id(const obs_output_t *output);
|
||||
|
||||
#if BUILD_CAPTIONS
|
||||
EXPORT void obs_output_output_caption_text1(obs_output_t *output,
|
||||
const char *text);
|
||||
#endif
|
||||
|
||||
EXPORT float obs_output_get_congestion(obs_output_t *output);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Functions used by outputs */
|
||||
|
||||
|
|
@ -1651,11 +1678,17 @@ EXPORT const char *obs_encoder_get_id(const obs_encoder_t *encoder);
|
|||
EXPORT uint32_t obs_get_encoder_caps(const char *encoder_id);
|
||||
|
||||
/** Duplicates an encoder packet */
|
||||
DEPRECATED
|
||||
EXPORT void obs_duplicate_encoder_packet(struct encoder_packet *dst,
|
||||
const struct encoder_packet *src);
|
||||
|
||||
DEPRECATED
|
||||
EXPORT void obs_free_encoder_packet(struct encoder_packet *packet);
|
||||
|
||||
EXPORT void obs_encoder_packet_ref(struct encoder_packet *dst,
|
||||
struct encoder_packet *src);
|
||||
EXPORT void obs_encoder_packet_release(struct encoder_packet *packet);
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Stream Services */
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#ifndef ON
|
||||
#define ON 1
|
||||
#endif
|
||||
|
||||
#ifndef OFF
|
||||
#define OFF 0
|
||||
#endif
|
||||
|
||||
#define OBS_VERSION "@OBS_VERSION@"
|
||||
#define OBS_DATA_PATH "@OBS_DATA_PATH@"
|
||||
#define OBS_INSTALL_PREFIX "@OBS_INSTALL_PREFIX@"
|
||||
#define OBS_PLUGIN_DESTINATION "@OBS_PLUGIN_DESTINATION@"
|
||||
#define OBS_RELATIVE_PREFIX "@OBS_RELATIVE_PREFIX@"
|
||||
#define OBS_UNIX_STRUCTURE @OBS_UNIX_STRUCTURE@
|
||||
#define BUILD_CAPTIONS @BUILD_CAPTIONS@
|
||||
#define HAVE_DBUS @HAVE_DBUS@
|
||||
|
|
|
|||
|
|
@ -24,19 +24,15 @@
|
|||
#define UNUSED_PARAMETER(param) (void)param
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define DEPRECATED_START __declspec(deprecated)
|
||||
#define DEPRECATED_END
|
||||
#define DEPRECATED __declspec(deprecated)
|
||||
#define FORCE_INLINE __forceinline
|
||||
#else
|
||||
#define DEPRECATED_START
|
||||
#define DEPRECATED_END __attribute__ ((deprecated))
|
||||
#define DEPRECATED __attribute__ ((deprecated))
|
||||
#define FORCE_INLINE inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#pragma warning (disable : 4996)
|
||||
|
||||
/* Microsoft is one of the most inept companies on the face of the planet.
|
||||
* The fact that even visual studio 2013 doesn't support the standard 'inline'
|
||||
* keyword is so incredibly stupid that I just can't imagine what sort of
|
||||
|
|
|
|||
|
|
@ -254,6 +254,20 @@ static inline void circlebuf_pop_back(struct circlebuf *cb, void *data,
|
|||
cb->end_pos -= size;
|
||||
}
|
||||
|
||||
static inline void *circlebuf_data(struct circlebuf *cb, size_t idx)
|
||||
{
|
||||
uint8_t *ptr = (uint8_t*)cb->data;
|
||||
size_t offset = cb->start_pos + idx;
|
||||
|
||||
if (idx > cb->size)
|
||||
return NULL;
|
||||
|
||||
if (offset >= cb->capacity)
|
||||
offset -= cb->capacity;
|
||||
|
||||
return ptr + offset;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include <stdio.h>
|
||||
#include <wchar.h>
|
||||
#include "config-file.h"
|
||||
#include "threading.h"
|
||||
#include "platform.h"
|
||||
#include "base.h"
|
||||
#include "bmem.h"
|
||||
|
|
@ -57,8 +58,19 @@ struct config_data {
|
|||
char *file;
|
||||
struct darray sections; /* struct config_section */
|
||||
struct darray defaults; /* struct config_section */
|
||||
pthread_mutex_t mutex;
|
||||
};
|
||||
|
||||
static inline bool init_mutex(config_t *config)
|
||||
{
|
||||
pthread_mutexattr_t attr;
|
||||
if (pthread_mutexattr_init(&attr) != 0)
|
||||
return false;
|
||||
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
|
||||
return false;
|
||||
return pthread_mutex_init(&config->mutex, &attr) == 0;
|
||||
}
|
||||
|
||||
config_t *config_create(const char *file)
|
||||
{
|
||||
struct config_data *config;
|
||||
|
|
@ -70,6 +82,12 @@ config_t *config_create(const char *file)
|
|||
fclose(f);
|
||||
|
||||
config = bzalloc(sizeof(struct config_data));
|
||||
|
||||
if (!init_mutex(config)) {
|
||||
bfree(config);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
config->file = bstrdup(file);
|
||||
return config;
|
||||
}
|
||||
|
|
@ -278,6 +296,11 @@ int config_open(config_t **config, const char *file,
|
|||
if (!*config)
|
||||
return CONFIG_ERROR;
|
||||
|
||||
if (!init_mutex(*config)) {
|
||||
bfree(*config);
|
||||
return CONFIG_ERROR;
|
||||
}
|
||||
|
||||
(*config)->file = bstrdup(file);
|
||||
|
||||
errorcode = config_parse_file(&(*config)->sections, file, always_open);
|
||||
|
|
@ -301,6 +324,11 @@ int config_open_string(config_t **config, const char *str)
|
|||
if (!*config)
|
||||
return CONFIG_ERROR;
|
||||
|
||||
if (!init_mutex(*config)) {
|
||||
bfree(*config);
|
||||
return CONFIG_ERROR;
|
||||
}
|
||||
|
||||
(*config)->file = NULL;
|
||||
|
||||
lexer_init(&lex);
|
||||
|
|
@ -333,9 +361,13 @@ int config_save(config_t *config)
|
|||
dstr_init(&str);
|
||||
dstr_init(&tmp);
|
||||
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
|
||||
f = os_fopen(config->file, "wb");
|
||||
if (!f)
|
||||
if (!f) {
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
return CONFIG_FILENOTFOUND;
|
||||
}
|
||||
|
||||
for (i = 0; i < config->sections.num; i++) {
|
||||
struct config_section *section = darray_item(
|
||||
|
|
@ -371,6 +403,8 @@ int config_save(config_t *config)
|
|||
fwrite(str.array, 1, str.len, f);
|
||||
fclose(f);
|
||||
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
|
||||
dstr_free(&tmp);
|
||||
dstr_free(&str);
|
||||
|
||||
|
|
@ -391,6 +425,8 @@ int config_save_safe(config_t *config, const char *temp_ext,
|
|||
return CONFIG_ERROR;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
|
||||
dstr_copy(&temp_file, config->file);
|
||||
if (*temp_ext != '.')
|
||||
dstr_cat(&temp_file, ".");
|
||||
|
|
@ -419,6 +455,7 @@ int config_save_safe(config_t *config, const char *temp_ext,
|
|||
os_rename(temp_file.array, file);
|
||||
|
||||
cleanup:
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
dstr_free(&temp_file);
|
||||
dstr_free(&backup_file);
|
||||
return ret;
|
||||
|
|
@ -442,6 +479,7 @@ void config_close(config_t *config)
|
|||
darray_free(&config->defaults);
|
||||
darray_free(&config->sections);
|
||||
bfree(config->file);
|
||||
pthread_mutex_destroy(&config->mutex);
|
||||
bfree(config);
|
||||
}
|
||||
|
||||
|
|
@ -453,14 +491,20 @@ size_t config_num_sections(config_t *config)
|
|||
const char *config_get_section(config_t *config, size_t idx)
|
||||
{
|
||||
struct config_section *section;
|
||||
const char *name = NULL;
|
||||
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
|
||||
if (idx >= config->sections.num)
|
||||
return NULL;
|
||||
goto unlock;
|
||||
|
||||
section = darray_item(sizeof(struct config_section), &config->sections,
|
||||
idx);
|
||||
name = section->name;
|
||||
|
||||
return section->name;
|
||||
unlock:
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
return name;
|
||||
}
|
||||
|
||||
static const struct config_item *config_find_item(const struct darray *sections,
|
||||
|
|
@ -487,14 +531,16 @@ static const struct config_item *config_find_item(const struct darray *sections,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static void config_set_item(struct darray *sections, const char *section,
|
||||
const char *name, char *value)
|
||||
static void config_set_item(config_t *config, struct darray *sections,
|
||||
const char *section, const char *name, char *value)
|
||||
{
|
||||
struct config_section *sec = NULL;
|
||||
struct config_section *array = sections->array;
|
||||
struct config_item *item;
|
||||
size_t i, j;
|
||||
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
|
||||
for (i = 0; i < sections->num; i++) {
|
||||
struct config_section *cur_sec = array+i;
|
||||
struct config_item *items = cur_sec->items.array;
|
||||
|
|
@ -506,7 +552,7 @@ static void config_set_item(struct darray *sections, const char *section,
|
|||
if (astrcmpi(item->name, name) == 0) {
|
||||
bfree(item->value);
|
||||
item->value = value;
|
||||
return;
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -524,6 +570,9 @@ static void config_set_item(struct darray *sections, const char *section,
|
|||
item = darray_push_back_new(sizeof(struct config_item), &sec->items);
|
||||
item->name = bstrdup(name);
|
||||
item->value = value;
|
||||
|
||||
unlock:
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
}
|
||||
|
||||
void config_set_string(config_t *config, const char *section,
|
||||
|
|
@ -531,7 +580,8 @@ void config_set_string(config_t *config, const char *section,
|
|||
{
|
||||
if (!value)
|
||||
value = "";
|
||||
config_set_item(&config->sections, section, name, bstrdup(value));
|
||||
config_set_item(config, &config->sections, section, name,
|
||||
bstrdup(value));
|
||||
}
|
||||
|
||||
void config_set_int(config_t *config, const char *section,
|
||||
|
|
@ -540,7 +590,7 @@ void config_set_int(config_t *config, const char *section,
|
|||
struct dstr str;
|
||||
dstr_init(&str);
|
||||
dstr_printf(&str, "%"PRId64, value);
|
||||
config_set_item(&config->sections, section, name, str.array);
|
||||
config_set_item(config, &config->sections, section, name, str.array);
|
||||
}
|
||||
|
||||
void config_set_uint(config_t *config, const char *section,
|
||||
|
|
@ -549,14 +599,14 @@ void config_set_uint(config_t *config, const char *section,
|
|||
struct dstr str;
|
||||
dstr_init(&str);
|
||||
dstr_printf(&str, "%"PRIu64, value);
|
||||
config_set_item(&config->sections, section, name, str.array);
|
||||
config_set_item(config, &config->sections, section, name, str.array);
|
||||
}
|
||||
|
||||
void config_set_bool(config_t *config, const char *section,
|
||||
const char *name, bool value)
|
||||
{
|
||||
char *str = bstrdup(value ? "true" : "false");
|
||||
config_set_item(&config->sections, section, name, str);
|
||||
config_set_item(config, &config->sections, section, name, str);
|
||||
}
|
||||
|
||||
void config_set_double(config_t *config, const char *section,
|
||||
|
|
@ -564,7 +614,7 @@ void config_set_double(config_t *config, const char *section,
|
|||
{
|
||||
char *str = bzalloc(64);
|
||||
os_dtostr(value, str, 64);
|
||||
config_set_item(&config->sections, section, name, str);
|
||||
config_set_item(config, &config->sections, section, name, str);
|
||||
}
|
||||
|
||||
void config_set_default_string(config_t *config, const char *section,
|
||||
|
|
@ -572,7 +622,8 @@ void config_set_default_string(config_t *config, const char *section,
|
|||
{
|
||||
if (!value)
|
||||
value = "";
|
||||
config_set_item(&config->defaults, section, name, bstrdup(value));
|
||||
config_set_item(config, &config->defaults, section, name,
|
||||
bstrdup(value));
|
||||
}
|
||||
|
||||
void config_set_default_int(config_t *config, const char *section,
|
||||
|
|
@ -581,7 +632,7 @@ void config_set_default_int(config_t *config, const char *section,
|
|||
struct dstr str;
|
||||
dstr_init(&str);
|
||||
dstr_printf(&str, "%"PRId64, value);
|
||||
config_set_item(&config->defaults, section, name, str.array);
|
||||
config_set_item(config, &config->defaults, section, name, str.array);
|
||||
}
|
||||
|
||||
void config_set_default_uint(config_t *config, const char *section,
|
||||
|
|
@ -590,14 +641,14 @@ void config_set_default_uint(config_t *config, const char *section,
|
|||
struct dstr str;
|
||||
dstr_init(&str);
|
||||
dstr_printf(&str, "%"PRIu64, value);
|
||||
config_set_item(&config->defaults, section, name, str.array);
|
||||
config_set_item(config, &config->defaults, section, name, str.array);
|
||||
}
|
||||
|
||||
void config_set_default_bool(config_t *config, const char *section,
|
||||
const char *name, bool value)
|
||||
{
|
||||
char *str = bstrdup(value ? "true" : "false");
|
||||
config_set_item(&config->defaults, section, name, str);
|
||||
config_set_item(config, &config->defaults, section, name, str);
|
||||
}
|
||||
|
||||
void config_set_default_double(config_t *config, const char *section,
|
||||
|
|
@ -606,20 +657,25 @@ void config_set_default_double(config_t *config, const char *section,
|
|||
struct dstr str;
|
||||
dstr_init(&str);
|
||||
dstr_printf(&str, "%g", value);
|
||||
config_set_item(&config->defaults, section, name, str.array);
|
||||
config_set_item(config, &config->defaults, section, name, str.array);
|
||||
}
|
||||
|
||||
const char *config_get_string(const config_t *config, const char *section,
|
||||
const char *config_get_string(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
const struct config_item *item = config_find_item(&config->sections,
|
||||
section, name);
|
||||
const struct config_item *item;
|
||||
const char *value = NULL;
|
||||
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
|
||||
item = config_find_item(&config->sections, section, name);
|
||||
if (!item)
|
||||
item = config_find_item(&config->defaults, section, name);
|
||||
if (!item)
|
||||
return NULL;
|
||||
if (item)
|
||||
value = item->value;
|
||||
|
||||
return item->value;
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline int64_t str_to_int64(const char *str)
|
||||
|
|
@ -644,7 +700,7 @@ static inline uint64_t str_to_uint64(const char *str)
|
|||
return strtoull(str, NULL, 10);
|
||||
}
|
||||
|
||||
int64_t config_get_int(const config_t *config, const char *section,
|
||||
int64_t config_get_int(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
const char *value = config_get_string(config, section, name);
|
||||
|
|
@ -654,7 +710,7 @@ int64_t config_get_int(const config_t *config, const char *section,
|
|||
return 0;
|
||||
}
|
||||
|
||||
uint64_t config_get_uint(const config_t *config, const char *section,
|
||||
uint64_t config_get_uint(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
const char *value = config_get_string(config, section, name);
|
||||
|
|
@ -664,7 +720,7 @@ uint64_t config_get_uint(const config_t *config, const char *section,
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool config_get_bool(const config_t *config, const char *section,
|
||||
bool config_get_bool(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
const char *value = config_get_string(config, section, name);
|
||||
|
|
@ -675,7 +731,7 @@ bool config_get_bool(const config_t *config, const char *section,
|
|||
return false;
|
||||
}
|
||||
|
||||
double config_get_double(const config_t *config, const char *section,
|
||||
double config_get_double(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
const char *value = config_get_string(config, section, name);
|
||||
|
|
@ -689,6 +745,9 @@ bool config_remove_value(config_t *config, const char *section,
|
|||
const char *name)
|
||||
{
|
||||
struct darray *sections = &config->sections;
|
||||
bool success = false;
|
||||
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
|
||||
for (size_t i = 0; i < sections->num; i++) {
|
||||
struct config_section *sec = darray_item(
|
||||
|
|
@ -706,27 +765,34 @@ bool config_remove_value(config_t *config, const char *section,
|
|||
config_item_free(item);
|
||||
darray_erase(sizeof(struct config_item),
|
||||
&sec->items, j);
|
||||
return true;
|
||||
success = true;
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
unlock:
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
return success;
|
||||
}
|
||||
|
||||
const char *config_get_default_string(const config_t *config,
|
||||
const char *config_get_default_string(config_t *config,
|
||||
const char *section, const char *name)
|
||||
{
|
||||
const struct config_item *item;
|
||||
const char *value = NULL;
|
||||
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
|
||||
item = config_find_item(&config->defaults, section, name);
|
||||
if (!item)
|
||||
return NULL;
|
||||
if (item)
|
||||
value = item->value;
|
||||
|
||||
return item->value;
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
return value;
|
||||
}
|
||||
|
||||
int64_t config_get_default_int(const config_t *config, const char *section,
|
||||
int64_t config_get_default_int(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
const char *value = config_get_default_string(config, section, name);
|
||||
|
|
@ -736,7 +802,7 @@ int64_t config_get_default_int(const config_t *config, const char *section,
|
|||
return 0;
|
||||
}
|
||||
|
||||
uint64_t config_get_default_uint(const config_t *config, const char *section,
|
||||
uint64_t config_get_default_uint(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
const char *value = config_get_default_string(config, section, name);
|
||||
|
|
@ -746,7 +812,7 @@ uint64_t config_get_default_uint(const config_t *config, const char *section,
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool config_get_default_bool(const config_t *config, const char *section,
|
||||
bool config_get_default_bool(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
const char *value = config_get_default_string(config, section, name);
|
||||
|
|
@ -757,7 +823,7 @@ bool config_get_default_bool(const config_t *config, const char *section,
|
|||
return false;
|
||||
}
|
||||
|
||||
double config_get_default_double(const config_t *config, const char *section,
|
||||
double config_get_default_double(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
const char *value = config_get_default_string(config, section, name);
|
||||
|
|
@ -767,15 +833,23 @@ double config_get_default_double(const config_t *config, const char *section,
|
|||
return 0.0;
|
||||
}
|
||||
|
||||
bool config_has_user_value(const config_t *config, const char *section,
|
||||
bool config_has_user_value(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
return config_find_item(&config->sections, section, name) != NULL;
|
||||
bool success;
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
success = config_find_item(&config->sections, section, name) != NULL;
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool config_has_default_value(const config_t *config, const char *section,
|
||||
bool config_has_default_value(config_t *config, const char *section,
|
||||
const char *name)
|
||||
{
|
||||
return config_find_item(&config->defaults, section, name) != NULL;
|
||||
bool success;
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
success = config_find_item(&config->defaults, section, name) != NULL;
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
return success;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,15 +64,15 @@ EXPORT void config_set_bool(config_t *config, const char *section,
|
|||
EXPORT void config_set_double(config_t *config, const char *section,
|
||||
const char *name, double value);
|
||||
|
||||
EXPORT const char *config_get_string(const config_t *config,
|
||||
EXPORT const char *config_get_string(config_t *config,
|
||||
const char *section, const char *name);
|
||||
EXPORT int64_t config_get_int(const config_t *config, const char *section,
|
||||
EXPORT int64_t config_get_int(config_t *config, const char *section,
|
||||
const char *name);
|
||||
EXPORT uint64_t config_get_uint(const config_t *config, const char *section,
|
||||
EXPORT uint64_t config_get_uint(config_t *config, const char *section,
|
||||
const char *name);
|
||||
EXPORT bool config_get_bool(const config_t *config, const char *section,
|
||||
EXPORT bool config_get_bool(config_t *config, const char *section,
|
||||
const char *name);
|
||||
EXPORT double config_get_double(const config_t *config, const char *section,
|
||||
EXPORT double config_get_double(config_t *config, const char *section,
|
||||
const char *name);
|
||||
|
||||
EXPORT bool config_remove_value(config_t *config, const char *section,
|
||||
|
|
@ -107,20 +107,20 @@ EXPORT void config_set_default_double(config_t *config, const char *section,
|
|||
|
||||
/* These functions allow you to get the current default values rather than get
|
||||
* the actual values. Probably almost never really needed */
|
||||
EXPORT const char *config_get_default_string(const config_t *config,
|
||||
EXPORT const char *config_get_default_string(config_t *config,
|
||||
const char *section, const char *name);
|
||||
EXPORT int64_t config_get_default_int(const config_t *config,
|
||||
EXPORT int64_t config_get_default_int(config_t *config,
|
||||
const char *section, const char *name);
|
||||
EXPORT uint64_t config_get_default_uint(const config_t *config,
|
||||
EXPORT uint64_t config_get_default_uint(config_t *config,
|
||||
const char *section, const char *name);
|
||||
EXPORT bool config_get_default_bool(const config_t *config,
|
||||
EXPORT bool config_get_default_bool(config_t *config,
|
||||
const char *section, const char *name);
|
||||
EXPORT double config_get_default_double(const config_t *config,
|
||||
EXPORT double config_get_default_double(config_t *config,
|
||||
const char *section, const char *name);
|
||||
|
||||
EXPORT bool config_has_user_value(const config_t *config,
|
||||
EXPORT bool config_has_user_value(config_t *config,
|
||||
const char *section, const char *name);
|
||||
EXPORT bool config_has_default_value(const config_t *config,
|
||||
EXPORT bool config_has_default_value(config_t *config,
|
||||
const char *section, const char *name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
|||
|
|
@ -719,28 +719,31 @@ bool get_dll_ver(const wchar_t *lib, struct win_version_info *ver_info)
|
|||
BOOL success;
|
||||
LPVOID data;
|
||||
DWORD size;
|
||||
char utf8_lib[512];
|
||||
|
||||
if (!ver_initialized && !initialize_version_functions())
|
||||
return false;
|
||||
if (!ver_initialize_success)
|
||||
return false;
|
||||
|
||||
os_wcs_to_utf8(lib, 0, utf8_lib, sizeof(utf8_lib));
|
||||
|
||||
size = get_file_version_info_size(lib, NULL);
|
||||
if (!size) {
|
||||
blog(LOG_ERROR, "Failed to get windows version info size");
|
||||
blog(LOG_ERROR, "Failed to get %s version info size", utf8_lib);
|
||||
return false;
|
||||
}
|
||||
|
||||
data = bmalloc(size);
|
||||
if (!get_file_version_info(lib, 0, size, data)) {
|
||||
blog(LOG_ERROR, "Failed to get windows version info");
|
||||
blog(LOG_ERROR, "Failed to get %s version info", utf8_lib);
|
||||
bfree(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
success = ver_query_value(data, L"\\", (LPVOID*)&info, &len);
|
||||
if (!success || !info || !len) {
|
||||
blog(LOG_ERROR, "Failed to get windows version info value");
|
||||
blog(LOG_ERROR, "Failed to get %s version info value", utf8_lib);
|
||||
bfree(data);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -754,6 +757,16 @@ bool get_dll_ver(const wchar_t *lib, struct win_version_info *ver_info)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool is_64_bit_windows(void)
|
||||
{
|
||||
#if defined(_WIN64)
|
||||
return true;
|
||||
#elif defined(_WIN32)
|
||||
BOOL b64 = false;
|
||||
return IsWow64Process(GetCurrentProcess(), &b64) && b64;
|
||||
#endif
|
||||
}
|
||||
|
||||
#define WINVER_REG_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
|
||||
|
||||
void get_win_ver(struct win_version_info *info)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
|
|
@ -657,3 +658,123 @@ const char *os_get_path_extension(const char *path)
|
|||
|
||||
return path + pos;
|
||||
}
|
||||
|
||||
static inline bool valid_string(const char *str)
|
||||
{
|
||||
while (str && *str) {
|
||||
if (*(str++) != ' ')
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void replace_text(struct dstr *str, size_t pos, size_t len,
|
||||
const char *new_text)
|
||||
{
|
||||
struct dstr front = {0};
|
||||
struct dstr back = {0};
|
||||
|
||||
dstr_left(&front, str, pos);
|
||||
dstr_right(&back, str, pos + len);
|
||||
dstr_copy_dstr(str, &front);
|
||||
dstr_cat(str, new_text);
|
||||
dstr_cat_dstr(str, &back);
|
||||
dstr_free(&front);
|
||||
dstr_free(&back);
|
||||
}
|
||||
|
||||
static void erase_ch(struct dstr *str, size_t pos)
|
||||
{
|
||||
struct dstr new_str = {0};
|
||||
dstr_left(&new_str, str, pos);
|
||||
dstr_cat(&new_str, str->array + pos + 1);
|
||||
dstr_free(str);
|
||||
*str = new_str;
|
||||
}
|
||||
|
||||
char *os_generate_formatted_filename(const char *extension, bool space,
|
||||
const char *format)
|
||||
{
|
||||
time_t now = time(0);
|
||||
struct tm *cur_time;
|
||||
cur_time = localtime(&now);
|
||||
|
||||
const size_t spec_count = 23;
|
||||
static const char *spec[][2] = {
|
||||
{"%CCYY", "%Y"},
|
||||
{"%YY", "%y"},
|
||||
{"%MM", "%m"},
|
||||
{"%DD", "%d"},
|
||||
{"%hh", "%H"},
|
||||
{"%mm", "%M"},
|
||||
{"%ss", "%S"},
|
||||
{"%%", "%%"},
|
||||
|
||||
{"%a", ""},
|
||||
{"%A", ""},
|
||||
{"%b", ""},
|
||||
{"%B", ""},
|
||||
{"%d", ""},
|
||||
{"%H", ""},
|
||||
{"%I", ""},
|
||||
{"%m", ""},
|
||||
{"%M", ""},
|
||||
{"%p", ""},
|
||||
{"%S", ""},
|
||||
{"%y", ""},
|
||||
{"%Y", ""},
|
||||
{"%z", ""},
|
||||
{"%Z", ""},
|
||||
};
|
||||
|
||||
char convert[128] = {0};
|
||||
struct dstr sf;
|
||||
struct dstr c = {0};
|
||||
size_t pos = 0;
|
||||
|
||||
dstr_init_copy(&sf, format);
|
||||
|
||||
while (pos < sf.len) {
|
||||
for (size_t i = 0; i < spec_count && !convert[0]; i++) {
|
||||
size_t len = strlen(spec[i][0]);
|
||||
|
||||
const char *cmp = sf.array + pos;
|
||||
|
||||
if (astrcmp_n(cmp, spec[i][0], len) == 0) {
|
||||
if (strlen(spec[i][1]))
|
||||
strftime(convert, sizeof(convert),
|
||||
spec[i][1], cur_time);
|
||||
else
|
||||
strftime(convert, sizeof(convert),
|
||||
spec[i][0], cur_time);
|
||||
|
||||
|
||||
dstr_copy(&c, convert);
|
||||
if (c.len && valid_string(c.array))
|
||||
replace_text(&sf, pos, len, convert);
|
||||
}
|
||||
}
|
||||
|
||||
if (convert[0]) {
|
||||
pos += strlen(convert);
|
||||
convert[0] = 0;
|
||||
} else if (!convert[0] && sf.array[pos] == '%') {
|
||||
erase_ch(&sf, pos);
|
||||
} else {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!space)
|
||||
dstr_replace(&sf, " ", "_");
|
||||
|
||||
dstr_cat_ch(&sf, '.');
|
||||
dstr_cat(&sf, extension);
|
||||
dstr_free(&c);
|
||||
|
||||
if (sf.len > 255)
|
||||
dstr_mid(&sf, &sf, 0, 255);
|
||||
|
||||
return sf.array;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,9 @@ EXPORT int os_mkdirs(const char *path);
|
|||
EXPORT int os_rename(const char *old_path, const char *new_path);
|
||||
EXPORT int os_copyfile(const char *file_in, const char *file_out);
|
||||
|
||||
EXPORT char *os_generate_formatted_filename(const char *extension, bool space,
|
||||
const char *format);
|
||||
|
||||
struct os_inhibit_info;
|
||||
typedef struct os_inhibit_info os_inhibit_t;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ public:
|
|||
inline operator T*() const {return ptr;}
|
||||
inline T *operator->() const {return ptr;}
|
||||
|
||||
inline const T *Get() const {return ptr;}
|
||||
|
||||
inline CoTaskMemPtr& operator=(T* val)
|
||||
{
|
||||
Clear();
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ struct win_version_info {
|
|||
int revis;
|
||||
};
|
||||
|
||||
EXPORT bool is_64_bit_windows(void);
|
||||
EXPORT bool get_dll_ver(const wchar_t *lib, struct win_version_info *info);
|
||||
EXPORT void get_win_ver(struct win_version_info *info);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue