added intial async audio/video code, fixed a few bugs, improved thread safety, and made a few other minor adjustments
parent
0a11dbabef
commit
ac2c08927f
2
README
2
README
|
@ -14,7 +14,7 @@ What's the goal of rewriting OBS?
|
|||
necessary for user interface. It also means allowing the use of OpenGL as
|
||||
well as Direct3D.
|
||||
|
||||
- Separate the application from the core, allowing custom appliction of
|
||||
- Separate the application from the core, allowing custom application of
|
||||
the core if desired, and easier extending of the user interface.
|
||||
|
||||
- Simplify complex systems to not only make it easier to use, but easier to
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
uniform float4x4 ViewProj;
|
||||
uniform float4x4 yuv_matrix;
|
||||
texture2d tex;
|
||||
|
||||
sampler_state def_sampler {
|
||||
Filter = Linear;
|
||||
AddressU = Clamp;
|
||||
AddressV = Clamp;
|
||||
};
|
||||
|
||||
struct VertInOut {
|
||||
float4 pos : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
VertInOut VSConvert(VertInOut vert_in)
|
||||
{
|
||||
VertInOut vert_out;
|
||||
vert_out.pos = mul(vert_in.pos, ViewProj);
|
||||
vert_out.uv = vert_in.uv;
|
||||
return vert_out;
|
||||
}
|
||||
|
||||
float4 PSConvert(VertInOut vert_in)
|
||||
{
|
||||
float4 yuv = tex.Sample(def_sampler, vert_in.uv);
|
||||
return saturate(mul(float4(yuv.xyz, 1.0), yuv_matrix));
|
||||
}
|
||||
|
||||
technique ConvertYUV
|
||||
{
|
||||
pass
|
||||
{
|
||||
vertex_shader = VSConvert(vert_in);
|
||||
pixel_shader = PSConvert(vert_in);
|
||||
}
|
||||
}
|
|
@ -17,26 +17,56 @@
|
|||
|
||||
#include "../util/threading.h"
|
||||
#include "../util/darray.h"
|
||||
#include "../util/circlebuf.h"
|
||||
#include "../util/platform.h"
|
||||
|
||||
#include "audio-io.h"
|
||||
|
||||
/* TODO: Incomplete */
|
||||
|
||||
struct audio_output {
|
||||
struct audio_info info;
|
||||
media_t media;
|
||||
media_output_t output;
|
||||
struct audio_line {
|
||||
struct audio_output *audio;
|
||||
struct circlebuf buffer;
|
||||
uint64_t base_timestamp;
|
||||
uint64_t last_timestamp;
|
||||
|
||||
pthread_t thread;
|
||||
pthread_mutex_t data_mutex;
|
||||
event_t stop_event;
|
||||
|
||||
struct darray pending_frames;
|
||||
|
||||
bool initialized;
|
||||
/* states whether this line is still being used. if not, then when the
|
||||
* buffer is depleted, it's destroyed */
|
||||
bool alive;
|
||||
};
|
||||
|
||||
static inline void audio_line_destroy_data(struct audio_line *line)
|
||||
{
|
||||
circlebuf_free(&line->buffer);
|
||||
bfree(line);
|
||||
}
|
||||
|
||||
struct audio_output {
|
||||
struct audio_info info;
|
||||
size_t block_size;
|
||||
media_t media;
|
||||
media_output_t output;
|
||||
|
||||
pthread_t thread;
|
||||
event_t stop_event;
|
||||
|
||||
DARRAY(uint8_t) pending_bytes;
|
||||
|
||||
bool initialized;
|
||||
|
||||
pthread_mutex_t line_mutex;
|
||||
DARRAY(struct audio_line*) lines;
|
||||
};
|
||||
|
||||
static inline void audio_output_removeline(struct audio_output *audio,
|
||||
struct audio_line *line)
|
||||
{
|
||||
pthread_mutex_lock(&audio->line_mutex);
|
||||
da_erase_item(audio->lines, &line);
|
||||
pthread_mutex_unlock(&audio->line_mutex);
|
||||
audio_line_destroy_data(line);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static void *audio_thread(void *param)
|
||||
|
@ -44,7 +74,6 @@ static void *audio_thread(void *param)
|
|||
struct audio_output *audio = param;
|
||||
|
||||
while (event_try(&audio->stop_event) == EAGAIN) {
|
||||
os_sleep_ms(5);
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
|
@ -55,8 +84,8 @@ static void *audio_thread(void *param)
|
|||
|
||||
static inline bool valid_audio_params(struct audio_info *info)
|
||||
{
|
||||
return info->channels > 0 && info->format && info->name &&
|
||||
info->samples_per_sec > 0 && info->speakers > 0;
|
||||
return info->format && info->name && info->samples_per_sec > 0 &&
|
||||
info->speakers > 0;
|
||||
}
|
||||
|
||||
static inline bool ao_add_to_media(audio_t audio)
|
||||
|
@ -85,9 +114,12 @@ int audio_output_open(audio_t *audio, media_t media, struct audio_info *info)
|
|||
memset(out, 0, sizeof(struct audio_output));
|
||||
|
||||
memcpy(&out->info, info, sizeof(struct audio_info));
|
||||
pthread_mutex_init_value(&out->line_mutex);
|
||||
out->media = media;
|
||||
out->block_size = get_audio_channels(info->speakers) *
|
||||
get_audio_bytes_per_channel(info->type);
|
||||
|
||||
if (pthread_mutex_init(&out->data_mutex, NULL) != 0)
|
||||
if (pthread_mutex_init(&out->line_mutex, NULL) != 0)
|
||||
goto fail;
|
||||
if (event_init(&out->stop_event, true) != 0)
|
||||
goto fail;
|
||||
|
@ -105,16 +137,27 @@ fail:
|
|||
return AUDIO_OUTPUT_FAIL;
|
||||
}
|
||||
|
||||
void audio_output_data(audio_t audio, struct audio_data *data)
|
||||
audio_line_t audio_output_createline(audio_t audio)
|
||||
{
|
||||
pthread_mutex_lock(&audio->data_mutex);
|
||||
/* TODO */
|
||||
pthread_mutex_unlock(&audio->data_mutex);
|
||||
struct audio_line *line = bmalloc(sizeof(struct audio_line));
|
||||
memset(line, 0, sizeof(struct audio_line));
|
||||
line->alive = true;
|
||||
|
||||
pthread_mutex_lock(&audio->line_mutex);
|
||||
da_push_back(audio->lines, &line);
|
||||
pthread_mutex_unlock(&audio->line_mutex);
|
||||
return line;
|
||||
}
|
||||
|
||||
const struct audio_info *audio_output_getinfo(audio_t audio)
|
||||
{
|
||||
return &audio->info;
|
||||
}
|
||||
|
||||
void audio_output_close(audio_t audio)
|
||||
{
|
||||
void *thread_ret;
|
||||
size_t i;
|
||||
|
||||
if (!audio)
|
||||
return;
|
||||
|
@ -124,8 +167,50 @@ void audio_output_close(audio_t audio)
|
|||
pthread_join(audio->thread, &thread_ret);
|
||||
}
|
||||
|
||||
for (i = 0; i < audio->lines.num; i++)
|
||||
audio_line_destroy_data(audio->lines.array[i]);
|
||||
|
||||
da_free(audio->lines);
|
||||
media_remove_output(audio->media, audio->output);
|
||||
event_destroy(&audio->stop_event);
|
||||
pthread_mutex_destroy(&audio->data_mutex);
|
||||
pthread_mutex_destroy(&audio->line_mutex);
|
||||
bfree(audio);
|
||||
}
|
||||
|
||||
void audio_line_destroy(struct audio_line *line)
|
||||
{
|
||||
if (line) {
|
||||
if (!line->buffer.size)
|
||||
audio_output_removeline(line->audio, line);
|
||||
else
|
||||
line->alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t audio_output_blocksize(audio_t audio)
|
||||
{
|
||||
return audio->block_size;
|
||||
}
|
||||
|
||||
static inline uint64_t convert_to_sample_offset(audio_t audio, uint64_t offset)
|
||||
{
|
||||
return (uint64_t)((double)offset *
|
||||
(1000000000.0 / (double)audio->info.samples_per_sec));
|
||||
}
|
||||
|
||||
void audio_line_output(audio_line_t line, const struct audio_data *data)
|
||||
{
|
||||
if (!line->buffer.size) {
|
||||
line->base_timestamp = data->timestamp;
|
||||
|
||||
circlebuf_push_back(&line->buffer, data->data,
|
||||
data->frames * line->audio->block_size);
|
||||
} else {
|
||||
uint64_t position = data->timestamp - line->base_timestamp;
|
||||
position = convert_to_sample_offset(line->audio, position);
|
||||
position *= line->audio->block_size;
|
||||
|
||||
circlebuf_place(&line->buffer, position, data->data,
|
||||
data->frames * line->audio->block_size);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,9 @@ extern "C" {
|
|||
*/
|
||||
|
||||
struct audio_output;
|
||||
struct audio_line;
|
||||
typedef struct audio_output *audio_t;
|
||||
typedef struct audio_line *audio_line_t;
|
||||
|
||||
enum audio_type {
|
||||
AUDIO_FORMAT_UNKNOWN,
|
||||
|
@ -56,11 +58,8 @@ enum speaker_setup {
|
|||
};
|
||||
|
||||
struct audio_data {
|
||||
void *data;
|
||||
const void *data;
|
||||
uint32_t frames;
|
||||
uint32_t speakers;
|
||||
uint32_t samples_per_sec;
|
||||
enum audio_type type;
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
|
@ -68,21 +67,65 @@ struct audio_info {
|
|||
const char *name;
|
||||
const char *format;
|
||||
|
||||
uint32_t channels;
|
||||
uint32_t samples_per_sec;
|
||||
enum audio_type type;
|
||||
enum speaker_setup speakers;
|
||||
};
|
||||
|
||||
|
||||
static inline uint32_t get_audio_channels(enum speaker_setup speakers)
|
||||
{
|
||||
switch (speakers) {
|
||||
case SPEAKERS_MONO: return 1;
|
||||
case SPEAKERS_STEREO: return 2;
|
||||
case SPEAKERS_2POINT1: return 3;
|
||||
case SPEAKERS_SURROUND:
|
||||
case SPEAKERS_QUAD: return 4;
|
||||
case SPEAKERS_4POINT1: return 5;
|
||||
case SPEAKERS_5POINT1:
|
||||
case SPEAKERS_5POINT1_SURROUND: return 6;
|
||||
case SPEAKERS_7POINT1: return 8;
|
||||
case SPEAKERS_7POINT1_SURROUND: return 8;
|
||||
default:
|
||||
case SPEAKERS_UNKNOWN: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline size_t get_audio_bytes_per_channel(enum audio_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case AUDIO_FORMAT_8BIT: return 1;
|
||||
case AUDIO_FORMAT_16BIT: return 2;
|
||||
case AUDIO_FORMAT_24BIT: return 3;
|
||||
case AUDIO_FORMAT_FLOAT:
|
||||
case AUDIO_FORMAT_32BIT: return 4;
|
||||
default:
|
||||
case AUDIO_FORMAT_UNKNOWN: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline size_t get_audio_size(enum audio_type type,
|
||||
enum speaker_setup speakers, uint32_t frames)
|
||||
{
|
||||
return get_audio_channels(speakers) *
|
||||
get_audio_bytes_per_channel(type) *
|
||||
frames;
|
||||
}
|
||||
|
||||
#define AUDIO_OUTPUT_SUCCESS 0
|
||||
#define AUDIO_OUTPUT_INVALIDPARAM -1
|
||||
#define AUDIO_OUTPUT_FAIL -2
|
||||
|
||||
EXPORT int audio_output_open(audio_t *audio, media_t media,
|
||||
struct audio_info *info);
|
||||
EXPORT void audio_output_data(audio_t audio, struct audio_data *data);
|
||||
EXPORT audio_line_t audio_output_createline(audio_t audio);
|
||||
EXPORT size_t audio_output_blocksize(audio_t audio);
|
||||
EXPORT const struct audio_info *audio_output_getinfo(audio_t audio);
|
||||
EXPORT void audio_output_close(audio_t audio);
|
||||
|
||||
EXPORT void audio_line_destroy(audio_line_t line);
|
||||
EXPORT void audio_line_output(audio_line_t line, const struct audio_data *data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -32,6 +32,16 @@ extern "C" {
|
|||
struct video_output;
|
||||
typedef struct video_output *video_t;
|
||||
|
||||
enum video_type {
|
||||
VIDEO_FORMAT_UNKNOWN,
|
||||
VIDEO_FORMAT_YUV444,
|
||||
VIDEO_FORMAT_YUV422,
|
||||
VIDEO_FORMAT_YUV420,
|
||||
VIDEO_FORMAT_RGBA,
|
||||
VIDEO_FORMAT_BGRA,
|
||||
VIDEO_FORMAT_BGRX,
|
||||
};
|
||||
|
||||
struct video_frame {
|
||||
const void *data;
|
||||
uint32_t row_size;
|
||||
|
@ -39,13 +49,14 @@ struct video_frame {
|
|||
};
|
||||
|
||||
struct video_info {
|
||||
const char *name;
|
||||
const char *format;
|
||||
const char *name;
|
||||
const char *format;
|
||||
|
||||
uint32_t fps_num; /* numerator */
|
||||
uint32_t fps_den; /* denominator */
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
enum video_type type;
|
||||
uint32_t fps_num; /* numerator */
|
||||
uint32_t fps_den; /* denominator */
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
#define VIDEO_OUTPUT_SUCCESS 0
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
#define MODULE_FILENOTFOUND -2
|
||||
#define MODULE_FUNCTIONNOTFOUND -3
|
||||
|
||||
#define SOURCE_VIDEO (1<<0)
|
||||
#define SOURCE_AUDIO (1<<1)
|
||||
#define SOURCE_ASYNC (1<<2)
|
||||
#define SOURCE_VIDEO (1<<0)
|
||||
#define SOURCE_AUDIO (1<<1)
|
||||
#define SOURCE_ASYNC_VIDEO (1<<2)
|
||||
|
|
|
@ -41,7 +41,7 @@ static void scene_destroy(void *data)
|
|||
|
||||
static uint32_t scene_get_output_flags(void *data)
|
||||
{
|
||||
return SOURCE_VIDEO | SOURCE_AUDIO;
|
||||
return SOURCE_VIDEO;
|
||||
}
|
||||
|
||||
static void scene_video_render(void *data)
|
||||
|
@ -111,6 +111,8 @@ obs_scene_t obs_scene_create(void)
|
|||
struct obs_source *source = bmalloc(sizeof(struct obs_source));
|
||||
struct obs_scene *scene = scene_create(NULL, source);
|
||||
|
||||
memset(source, 0, sizeof(struct obs_source));
|
||||
|
||||
source->data = scene;
|
||||
if (!source->data) {
|
||||
bfree(source);
|
||||
|
@ -118,7 +120,7 @@ obs_scene_t obs_scene_create(void)
|
|||
}
|
||||
|
||||
scene->source = source;
|
||||
obs_source_init(source);
|
||||
obs_source_init(source, NULL, &scene_info);
|
||||
memcpy(&source->callbacks, &scene_info, sizeof(struct source_info));
|
||||
return scene;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include "util/platform.h"
|
||||
|
||||
#include "obs.h"
|
||||
#include "obs-data.h"
|
||||
|
||||
|
@ -77,17 +79,39 @@ static inline const struct source_info *find_source(struct darray *list,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
void obs_source_init(struct obs_source *source)
|
||||
bool obs_source_init(struct obs_source *source, const char *settings,
|
||||
const struct source_info *info)
|
||||
{
|
||||
source->filter_target = NULL;
|
||||
source->rendering_filter = false;
|
||||
uint32_t flags = info->get_output_flags(source->data);
|
||||
|
||||
dstr_init(&source->settings);
|
||||
da_init(source->filters);
|
||||
pthread_mutex_init_value(&source->filter_mutex);
|
||||
pthread_mutex_init_value(&source->video_mutex);
|
||||
pthread_mutex_init_value(&source->audio_mutex);
|
||||
dstr_copy(&source->settings, settings);
|
||||
memcpy(&source->callbacks, info, sizeof(struct source_info));
|
||||
|
||||
if (pthread_mutex_init(&source->filter_mutex, NULL) != 0)
|
||||
return false;
|
||||
if (pthread_mutex_init(&source->audio_mutex, NULL) != 0)
|
||||
return false;
|
||||
if (pthread_mutex_init(&source->video_mutex, NULL) != 0)
|
||||
return false;
|
||||
|
||||
if (flags & SOURCE_AUDIO) {
|
||||
source->audio_line = audio_output_createline(obs->audio);
|
||||
if (!source->audio_line) {
|
||||
blog(LOG_ERROR, "Failed to create audio line for "
|
||||
"source");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
source->valid = true;
|
||||
pthread_mutex_lock(&obs->source_list_mutex);
|
||||
da_push_back(obs->sources, &source);
|
||||
pthread_mutex_unlock(&obs->source_list_mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
obs_source_t obs_source_create(enum obs_source_type type, const char *name,
|
||||
|
@ -113,27 +137,57 @@ obs_source_t obs_source_create(enum obs_source_type type, const char *name,
|
|||
}
|
||||
|
||||
source = bmalloc(sizeof(struct obs_source));
|
||||
source->data = info->create(settings, source);
|
||||
if (!source->data) {
|
||||
bfree(source);
|
||||
return NULL;
|
||||
}
|
||||
memset(source, 0, sizeof(struct obs_source));
|
||||
|
||||
source->data = info->create(settings, source);
|
||||
if (!source->data)
|
||||
goto fail;
|
||||
|
||||
if (!obs_source_init(source, settings, info))
|
||||
goto fail;
|
||||
|
||||
obs_source_init(source);
|
||||
dstr_copy(&source->settings, settings);
|
||||
memcpy(&source->callbacks, info, sizeof(struct source_info));
|
||||
return source;
|
||||
|
||||
fail:
|
||||
blog(LOG_ERROR, "obs_source_create failed");
|
||||
obs_source_destroy(source);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void obs_source_destroy(obs_source_t source)
|
||||
{
|
||||
if (source) {
|
||||
pthread_mutex_lock(&obs->source_list_mutex);
|
||||
da_erase_item(obs->sources, &source);
|
||||
pthread_mutex_unlock(&obs->source_list_mutex);
|
||||
size_t i;
|
||||
if (source->filter_parent)
|
||||
obs_source_filter_remove(source->filter_parent, source);
|
||||
|
||||
for (i = 0; i < source->filters.num; i++)
|
||||
obs_source_destroy(source->filters.array[i]);
|
||||
|
||||
if (source->valid) {
|
||||
pthread_mutex_lock(&obs->source_list_mutex);
|
||||
da_erase_item(obs->sources, &source);
|
||||
pthread_mutex_unlock(&obs->source_list_mutex);
|
||||
}
|
||||
|
||||
for (i = 0; i < source->audio_buffer.num; i++)
|
||||
audiobuf_free(source->audio_buffer.array+i);
|
||||
for (i = 0; i < source->video_frames.num; i++)
|
||||
source_frame_destroy(source->video_frames.array[i]);
|
||||
|
||||
gs_entercontext(obs->graphics);
|
||||
texture_destroy(source->output_texture);
|
||||
gs_leavecontext();
|
||||
|
||||
da_free(source->filters);
|
||||
source->callbacks.destroy(source->data);
|
||||
if (source->data)
|
||||
source->callbacks.destroy(source->data);
|
||||
|
||||
audio_line_destroy(source->audio_line);
|
||||
|
||||
pthread_mutex_destroy(&source->filter_mutex);
|
||||
pthread_mutex_destroy(&source->audio_mutex);
|
||||
pthread_mutex_destroy(&source->video_mutex);
|
||||
dstr_free(&source->settings);
|
||||
bfree(source);
|
||||
}
|
||||
|
@ -173,6 +227,74 @@ void obs_source_video_tick(obs_source_t source, float seconds)
|
|||
source->callbacks.video_tick(source->data, seconds);
|
||||
}
|
||||
|
||||
#define MAX_VARIANCE 2000000000ULL
|
||||
|
||||
static void source_output_audio_line(obs_source_t source,
|
||||
const struct audio_data *data)
|
||||
{
|
||||
struct audio_data in = *data;
|
||||
|
||||
if (!in.timestamp) {
|
||||
in.timestamp = os_gettime_ns();
|
||||
if (!source->timing_set) {
|
||||
source->timing_set = true;
|
||||
source->timing_adjust = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!source->timing_set) {
|
||||
source->timing_set = true;
|
||||
source->timing_adjust = in.timestamp - os_gettime_ns();
|
||||
|
||||
/* detects 'directly' set timestamps as long as they're within
|
||||
* a certain threashold */
|
||||
if ((source->timing_adjust+MAX_VARIANCE) < MAX_VARIANCE*2)
|
||||
source->timing_adjust = 0;
|
||||
}
|
||||
|
||||
in.timestamp += source->timing_adjust;
|
||||
audio_line_output(source->audio_line, &in);
|
||||
}
|
||||
|
||||
static void obs_source_flush_audio_buffer(obs_source_t source)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
pthread_mutex_lock(&source->audio_mutex);
|
||||
source->timing_set = true;
|
||||
|
||||
for (i = 0; i < source->audio_buffer.num; i++) {
|
||||
struct audiobuf *buf = source->audio_buffer.array+i;
|
||||
struct audio_data data;
|
||||
|
||||
data.data = buf->data;
|
||||
data.frames = buf->frames;
|
||||
data.timestamp = buf->timestamp + source->timing_adjust;
|
||||
audio_line_output(source->audio_line, &data);
|
||||
audiobuf_free(buf);
|
||||
}
|
||||
|
||||
da_free(source->audio_buffer);
|
||||
pthread_mutex_unlock(&source->audio_mutex);
|
||||
}
|
||||
|
||||
static void obs_source_render_async_video(obs_source_t source)
|
||||
{
|
||||
struct source_frame *frame = obs_source_getframe(source);
|
||||
if (!frame)
|
||||
return;
|
||||
|
||||
source->timing_adjust = frame->timestamp - os_gettime_ns();
|
||||
if (!source->timing_set && source->audio_buffer.num)
|
||||
obs_source_flush_audio_buffer(source);
|
||||
|
||||
if (!source->output_texture) {
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
obs_source_releaseframe(source, frame);
|
||||
}
|
||||
|
||||
void obs_source_video_render(obs_source_t source)
|
||||
{
|
||||
if (source->callbacks.video_render) {
|
||||
|
@ -183,6 +305,12 @@ void obs_source_video_render(obs_source_t source)
|
|||
} else {
|
||||
source->callbacks.video_render(source->data);
|
||||
}
|
||||
|
||||
} else if (source->filter_target) {
|
||||
obs_source_video_render(source->filter_target);
|
||||
|
||||
} else {
|
||||
obs_source_render_async_video(source);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,6 +359,8 @@ obs_source_t obs_filter_gettarget(obs_source_t filter)
|
|||
|
||||
void obs_source_filter_add(obs_source_t source, obs_source_t filter)
|
||||
{
|
||||
pthread_mutex_lock(&source->filter_mutex);
|
||||
|
||||
if (da_find(source->filters, &filter, 0) != DARRAY_INVALID) {
|
||||
blog(LOG_WARNING, "Tried to add a filter that was already "
|
||||
"present on the source");
|
||||
|
@ -243,12 +373,20 @@ void obs_source_filter_add(obs_source_t source, obs_source_t filter)
|
|||
}
|
||||
|
||||
da_push_back(source->filters, &filter);
|
||||
|
||||
pthread_mutex_unlock(&source->filter_mutex);
|
||||
|
||||
filter->filter_parent = source;
|
||||
filter->filter_target = source;
|
||||
}
|
||||
|
||||
void obs_source_filter_remove(obs_source_t source, obs_source_t filter)
|
||||
{
|
||||
size_t idx = da_find(source->filters, &filter, 0);
|
||||
size_t idx;
|
||||
|
||||
pthread_mutex_lock(&source->filter_mutex);
|
||||
|
||||
idx = da_find(source->filters, &filter, 0);
|
||||
if (idx == DARRAY_INVALID)
|
||||
return;
|
||||
|
||||
|
@ -258,6 +396,10 @@ void obs_source_filter_remove(obs_source_t source, obs_source_t filter)
|
|||
}
|
||||
|
||||
da_erase(source->filters, idx);
|
||||
|
||||
pthread_mutex_unlock(&source->filter_mutex);
|
||||
|
||||
filter->filter_parent = NULL;
|
||||
filter->filter_target = NULL;
|
||||
}
|
||||
|
||||
|
@ -308,12 +450,112 @@ void obs_source_save_settings(obs_source_t source, const char *settings)
|
|||
dstr_copy(&source->settings, settings);
|
||||
}
|
||||
|
||||
void obs_source_output_video(obs_source_t source, struct video_frame *frame)
|
||||
static inline struct filter_frame *filter_async_video(obs_source_t source,
|
||||
struct filter_frame *in)
|
||||
{
|
||||
/* TODO */
|
||||
size_t i;
|
||||
for (i = source->filters.num; i > 0; i--) {
|
||||
struct obs_source *filter = source->filters.array[i-1];
|
||||
if (filter->callbacks.filter_video) {
|
||||
in = filter->callbacks.filter_video(filter->data, in);
|
||||
if (!in)
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
void obs_source_output_audio(obs_source_t source, struct audio_data *audio)
|
||||
static struct filter_frame *process_video(obs_source_t source,
|
||||
const struct source_video *frame)
|
||||
{
|
||||
/* TODO: convert to YUV444 or RGB */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void obs_source_output_video(obs_source_t source,
|
||||
const struct source_video *frame)
|
||||
{
|
||||
struct filter_frame *output;
|
||||
|
||||
output = process_video(source, frame);
|
||||
|
||||
pthread_mutex_lock(&source->filter_mutex);
|
||||
output = filter_async_video(source, output);
|
||||
pthread_mutex_unlock(&source->filter_mutex);
|
||||
|
||||
pthread_mutex_lock(&source->video_mutex);
|
||||
da_push_back(source->video_frames, &output);
|
||||
pthread_mutex_unlock(&source->video_mutex);
|
||||
}
|
||||
|
||||
static inline const struct audio_data *filter_async_audio(obs_source_t source,
|
||||
const struct audio_data *in)
|
||||
{
|
||||
size_t i;
|
||||
for (i = source->filters.num; i > 0; i--) {
|
||||
struct obs_source *filter = source->filters.array[i-1];
|
||||
if (filter->callbacks.filter_audio) {
|
||||
in = filter->callbacks.filter_audio(filter->data, in);
|
||||
if (!in)
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
static struct audio_data *process_audio(obs_source_t source,
|
||||
const struct source_audio *audio)
|
||||
{
|
||||
/* TODO: upmix/downmix/resample */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void obs_source_output_audio(obs_source_t source,
|
||||
const struct source_audio *audio)
|
||||
{
|
||||
uint32_t flags = obs_source_get_output_flags(source);
|
||||
struct audio_data *data;
|
||||
const struct audio_data *output;
|
||||
|
||||
data = process_audio(source, audio);
|
||||
|
||||
pthread_mutex_lock(&source->filter_mutex);
|
||||
output = filter_async_audio(source, data);
|
||||
|
||||
if (output) {
|
||||
pthread_mutex_lock(&source->audio_mutex);
|
||||
|
||||
if (!source->timing_set && flags & SOURCE_ASYNC_VIDEO) {
|
||||
struct audiobuf newbuf;
|
||||
size_t audio_size = audio_output_blocksize(obs->audio) *
|
||||
output->frames;
|
||||
|
||||
newbuf.data = bmalloc(audio_size);
|
||||
newbuf.frames = output->frames;
|
||||
newbuf.timestamp = output->timestamp;
|
||||
memcpy(newbuf.data, output->data, audio_size);
|
||||
|
||||
da_push_back(source->audio_buffer, &newbuf);
|
||||
|
||||
} else {
|
||||
source_output_audio_line(source, output);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&source->audio_mutex);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&source->filter_mutex);
|
||||
}
|
||||
|
||||
struct source_frame *obs_source_getframe(obs_source_t source)
|
||||
{
|
||||
/* TODO */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void obs_source_releaseframe(obs_source_t source, struct source_frame *frame)
|
||||
{
|
||||
/* TODO */
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "util/c99defs.h"
|
||||
#include "util/darray.h"
|
||||
#include "util/dstr.h"
|
||||
#include "util/threading.h"
|
||||
#include "media-io/media-io.h"
|
||||
|
||||
/*
|
||||
|
@ -135,16 +136,20 @@
|
|||
* Return value: true if sources remaining, otherwise false.
|
||||
*
|
||||
* ---------------------------------------------------------
|
||||
* void [name]_filter_video(void *data, struct video_frame *frame);
|
||||
* struct filter_frame *[name]_filter_video(void *data,
|
||||
* struct filter_frame *frame);
|
||||
* Filters audio data. Used with audio filters.
|
||||
*
|
||||
* frame: Video frame data.
|
||||
* returns: New video frame data (or NULL if pending)
|
||||
*
|
||||
* ---------------------------------------------------------
|
||||
* void [name]_filter_audio(void *data, struct audio_data *audio);
|
||||
* const struct audio_data *[name]_filter_audio(void *data,
|
||||
* const struct audio_data *audio);
|
||||
* Filters video data. Used with async video data.
|
||||
*
|
||||
* audio: Audio data.
|
||||
* reutrns New audio data (or NULL if pending)
|
||||
*/
|
||||
|
||||
struct obs_source;
|
||||
|
@ -180,24 +185,53 @@ struct source_info {
|
|||
|
||||
bool (*enum_children)(void *data, size_t idx, obs_source_t *child);
|
||||
|
||||
void (*filter_video)(void *data, struct video_frame *frame);
|
||||
void (*filter_audio)(void *data, struct audio_data *audio);
|
||||
struct filter_frame *(*filter_video)(void *data,
|
||||
struct filter_frame *frame);
|
||||
const struct audio_data *(*filter_audio)(void *data,
|
||||
const struct audio_data *audio);
|
||||
};
|
||||
|
||||
struct obs_source {
|
||||
void *data;
|
||||
struct source_info callbacks;
|
||||
struct dstr settings;
|
||||
bool rendering_filter;
|
||||
struct audiobuf {
|
||||
void *data;
|
||||
uint32_t frames;
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
struct obs_source *filter_target;
|
||||
DARRAY(struct obs_source*) filters;
|
||||
static inline void audiobuf_free(struct audiobuf *buf)
|
||||
{
|
||||
bfree(buf->data);
|
||||
}
|
||||
|
||||
struct obs_source {
|
||||
void *data; /* source-specific data */
|
||||
struct source_info callbacks;
|
||||
struct dstr settings;
|
||||
bool valid;
|
||||
|
||||
/* async video and audio */
|
||||
bool timing_set;
|
||||
uint64_t timing_adjust;
|
||||
texture_t output_texture;
|
||||
|
||||
audio_line_t audio_line;
|
||||
DARRAY(struct audiobuf) audio_buffer;
|
||||
DARRAY(struct source_frame*) video_frames;
|
||||
pthread_mutex_t audio_mutex;
|
||||
pthread_mutex_t video_mutex;
|
||||
|
||||
/* filters */
|
||||
struct obs_source *filter_parent;
|
||||
struct obs_source *filter_target;
|
||||
DARRAY(struct obs_source*) filters;
|
||||
pthread_mutex_t filter_mutex;
|
||||
bool rendering_filter;
|
||||
};
|
||||
|
||||
extern bool get_source_info(void *module, const char *module_name,
|
||||
const char *source_name, struct source_info *info);
|
||||
|
||||
extern void obs_source_init(struct obs_source *source);
|
||||
extern bool obs_source_init(struct obs_source *source, const char *settings,
|
||||
const struct source_info *info);
|
||||
|
||||
extern void obs_source_activate(obs_source_t source);
|
||||
extern void obs_source_deactivate(obs_source_t source);
|
||||
|
|
67
libobs/obs.h
67
libobs/obs.h
|
@ -39,6 +39,8 @@ enum obs_source_type {
|
|||
SOURCE_SCENE
|
||||
};
|
||||
|
||||
/* used for changing the order of items (for example, filters in a source,
|
||||
* or items in a scene */
|
||||
enum order_movement {
|
||||
ORDER_MOVE_UP,
|
||||
ORDER_MOVE_DOWN,
|
||||
|
@ -46,6 +48,49 @@ enum order_movement {
|
|||
ORDER_MOVE_BOTTOM
|
||||
};
|
||||
|
||||
struct source_audio {
|
||||
const void *data;
|
||||
uint32_t frames;
|
||||
|
||||
/* audio will be automatically resampled/upmixed/downmixed */
|
||||
enum speaker_setup speakers;
|
||||
enum audio_type type;
|
||||
uint32_t samples_per_sec;
|
||||
|
||||
/* can be 0 if 'immediate' */
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
struct source_video {
|
||||
const void *data;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t row_bytes;
|
||||
uint64_t timestamp;
|
||||
|
||||
enum video_type type;
|
||||
float yuv_matrix[16];
|
||||
};
|
||||
|
||||
struct source_frame {
|
||||
void *data;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t row_bytes;
|
||||
uint64_t timestamp;
|
||||
|
||||
bool yuv;
|
||||
float yuv_matrix[16];
|
||||
};
|
||||
|
||||
static inline void source_frame_destroy(struct source_frame *frame)
|
||||
{
|
||||
if (frame) {
|
||||
bfree(frame->data);
|
||||
bfree(frame);
|
||||
}
|
||||
}
|
||||
|
||||
/* opaque types */
|
||||
struct obs_display;
|
||||
struct obs_source;
|
||||
|
@ -63,7 +108,7 @@ typedef struct obs_output *obs_output_t;
|
|||
/* OBS context */
|
||||
|
||||
/**
|
||||
* Creates the OBS context.
|
||||
* Starts up and shuts down OBS
|
||||
*
|
||||
* Using the graphics module specified, creates an OBS context and sets the
|
||||
* primary video/audio output information.
|
||||
|
@ -214,9 +259,8 @@ EXPORT size_t obs_source_getparam(obs_source_t source, const char *param,
|
|||
*/
|
||||
EXPORT void obs_source_setparam(obs_source_t source, const char *param,
|
||||
const void *data, size_t size);
|
||||
/**
|
||||
* Enumerates child sources this source has
|
||||
*/
|
||||
|
||||
/** Enumerates child sources this source has */
|
||||
EXPORT bool obs_source_enum_children(obs_source_t source, size_t idx,
|
||||
obs_source_t *child);
|
||||
|
||||
|
@ -243,12 +287,19 @@ EXPORT const char *obs_source_get_settings(obs_source_t source);
|
|||
EXPORT void obs_source_save_settings(obs_source_t source, const char *settings);
|
||||
|
||||
/** Outputs asynchronous video data */
|
||||
EXPORT void obs_source_obs_output_video(obs_source_t source,
|
||||
struct video_frame *frame);
|
||||
EXPORT void obs_source_obs_async_video(obs_source_t source,
|
||||
const struct video_frame *frame);
|
||||
|
||||
/** Outputs audio data (always asynchronous) */
|
||||
EXPORT void obs_source_obs_output_audio(obs_source_t source,
|
||||
struct audio_data *audio);
|
||||
EXPORT void obs_source_obs_async_audio(obs_source_t source,
|
||||
const struct source_audio *audio);
|
||||
|
||||
/** Gets the current async video frame */
|
||||
EXPORT struct source_frame *obs_source_getframe(obs_source_t source);
|
||||
|
||||
/** Relases the current async frame */
|
||||
EXPORT void obs_source_releaseframe(obs_source_t source,
|
||||
struct source_frame *frame);
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
/******************************************************************************
|
||||
Copyright (c) 2013 by Hugh Bailey <obs.jim@gmail.com>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "c99defs.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "bmem.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Dynamic circular buffer */
|
||||
|
||||
struct circlebuf {
|
||||
void *data;
|
||||
size_t size;
|
||||
|
||||
size_t start_pos;
|
||||
size_t end_pos;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
static inline void circlebuf_init(struct circlebuf *cb)
|
||||
{
|
||||
memset(cb, 0, sizeof(struct circlebuf));
|
||||
}
|
||||
|
||||
static inline void circlebuf_free(struct circlebuf *cb)
|
||||
{
|
||||
bfree(cb->data);
|
||||
memset(cb, 0, sizeof(struct circlebuf));
|
||||
}
|
||||
|
||||
static inline void circlebuf_reorder_data(struct circlebuf *cb,
|
||||
size_t new_capacity)
|
||||
{
|
||||
size_t difference;
|
||||
uint8_t *data;
|
||||
|
||||
if (!cb->size || !cb->start_pos || cb->end_pos > cb->start_pos)
|
||||
return;
|
||||
|
||||
difference = new_capacity - cb->capacity;
|
||||
data = (uint8_t*)cb->data + cb->start_pos;
|
||||
memmove(data+difference, data, cb->capacity - cb->start_pos);
|
||||
cb->start_pos += difference;
|
||||
}
|
||||
|
||||
static inline void circlebuf_ensure_capacity(struct circlebuf *cb)
|
||||
{
|
||||
size_t new_capacity;
|
||||
if (cb->size <= cb->capacity)
|
||||
return;
|
||||
|
||||
new_capacity = cb->capacity*2;
|
||||
if (cb->size > new_capacity)
|
||||
new_capacity = cb->size;
|
||||
|
||||
cb->data = brealloc(cb->data, new_capacity);
|
||||
circlebuf_reorder_data(cb, new_capacity);
|
||||
cb->capacity = new_capacity;
|
||||
}
|
||||
|
||||
static inline void circlebuf_reserve(struct circlebuf *cb, size_t capacity)
|
||||
{
|
||||
if (capacity <= cb->capacity)
|
||||
return;
|
||||
|
||||
cb->data = brealloc(cb->data, capacity);
|
||||
circlebuf_reorder_data(cb, capacity);
|
||||
cb->capacity = capacity;
|
||||
}
|
||||
|
||||
static inline void circlebuf_upsize(struct circlebuf *cb, size_t size)
|
||||
{
|
||||
size_t add_size = size - cb->size;
|
||||
size_t new_end_pos = cb->end_pos + add_size;
|
||||
|
||||
if (size <= cb->size)
|
||||
return;
|
||||
|
||||
cb->size = size;
|
||||
circlebuf_ensure_capacity(cb);
|
||||
|
||||
if (new_end_pos > cb->capacity) {
|
||||
size_t back_size = cb->capacity - cb->end_pos;
|
||||
size_t loop_size = add_size - back_size;
|
||||
|
||||
if (back_size)
|
||||
memset((uint8_t*)cb->data + cb->end_pos, 0, back_size);
|
||||
|
||||
memset(cb->data, 0, loop_size);
|
||||
new_end_pos -= cb->capacity;
|
||||
} else {
|
||||
memset((uint8_t*)cb->data + cb->end_pos, 0, add_size);
|
||||
}
|
||||
|
||||
cb->end_pos = new_end_pos;
|
||||
}
|
||||
|
||||
/** Overwrites data at a specific point in the buffer (relative). */
|
||||
static inline void circlebuf_place(struct circlebuf *cb, size_t position,
|
||||
const void *data, size_t size)
|
||||
{
|
||||
size_t end_point = position + size;
|
||||
size_t data_end_pos;
|
||||
|
||||
if (end_point > cb->size)
|
||||
circlebuf_upsize(cb, end_point);
|
||||
|
||||
position += cb->start_pos;
|
||||
if (position >= cb->capacity)
|
||||
position -= cb->capacity;
|
||||
|
||||
data_end_pos = position + size;
|
||||
if (data_end_pos > cb->capacity) {
|
||||
size_t back_size = cb->capacity - data_end_pos;
|
||||
size_t loop_size = size - back_size;
|
||||
|
||||
if (back_size)
|
||||
memcpy((uint8_t*)cb->data + position, data, back_size);
|
||||
memcpy(cb->data, (uint8_t*)data + back_size, loop_size);
|
||||
} else {
|
||||
memcpy((uint8_t*)cb->data + position, data, size);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void circlebuf_push_back(struct circlebuf *cb, const void *data,
|
||||
size_t size)
|
||||
{
|
||||
size_t new_end_pos = cb->end_pos + size;
|
||||
|
||||
cb->size += size;
|
||||
circlebuf_ensure_capacity(cb);
|
||||
|
||||
if (new_end_pos > cb->capacity) {
|
||||
size_t back_size = cb->capacity - cb->end_pos;
|
||||
size_t loop_size = size - back_size;
|
||||
|
||||
if (back_size)
|
||||
memcpy((uint8_t*)cb->data + cb->end_pos, data,
|
||||
back_size);
|
||||
memcpy(cb->data, (uint8_t*)data + back_size, loop_size);
|
||||
|
||||
new_end_pos -= cb->capacity;
|
||||
} else {
|
||||
memcpy((uint8_t*)cb->data + cb->end_pos, data, size);
|
||||
}
|
||||
|
||||
cb->end_pos = new_end_pos;
|
||||
}
|
||||
|
||||
static inline void circlebuf_pop_front(struct circlebuf *cb, void *data,
|
||||
size_t size)
|
||||
{
|
||||
size_t start_size;
|
||||
assert(size <= cb->size);
|
||||
|
||||
start_size = cb->capacity - cb->start_pos;
|
||||
|
||||
if (start_size < size) {
|
||||
memcpy(data, (uint8_t*)cb->data + cb->start_pos, start_size);
|
||||
memcpy((uint8_t*)data + start_size, cb->data,
|
||||
size - start_size);
|
||||
} else {
|
||||
memcpy(data, (uint8_t*)cb->data + cb->start_pos, size);
|
||||
}
|
||||
|
||||
cb->size -= size;
|
||||
cb->start_pos += size;
|
||||
if (cb->start_pos >= cb->capacity)
|
||||
cb->start_pos -= cb->capacity;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
Loading…
Reference in New Issue