New upstream version 18.0.1+dfsg1

This commit is contained in:
Sebastian Ramacher 2017-04-19 21:54:15 +02:00
parent 6efda2859e
commit f2cf6cce50
1337 changed files with 41178 additions and 84670 deletions

View file

@ -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})

View 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);
}

View 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);
}

View 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);
}
}

View 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)

View 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);
}

View 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);
}
}

View 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)

View file

@ -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));
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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)

View file

@ -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

View file

@ -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);
}

View file

@ -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) | \

View file

@ -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);

View file

@ -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,

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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,

View file

@ -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;
}
/* ------------------------------------------------------------------------- */

View file

@ -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)

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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,

View file

@ -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);
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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 */

View file

@ -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@

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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)

View file

@ -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;
}

View file

@ -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;

View file

@ -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();

View file

@ -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);