libobs: Add texture-based encoding support

Allows the ability to encode by passing NV12 textures.  This uses a
separate thread for texture-based encoders with a small queue of
textures.  An output texture with a keyed mutex shared texture is locked
between OBS and each encoder.  A new encoder callback and capability
flag is used to encode with textures.
This commit is contained in:
jp9000 2019-02-05 17:37:40 -08:00
parent cd1607ca6e
commit 93ba6e7128
8 changed files with 499 additions and 21 deletions

View file

@ -384,6 +384,7 @@ set(libobs_libobs_SOURCES
obs-view.c
obs-scene.c
obs-audio.c
obs-video-gpu-encode.c
obs-video.c)
set(libobs_libobs_HEADERS
${libobs_PLATFORM_HEADERS}

View file

@ -179,6 +179,12 @@ static inline bool has_scaling(const struct obs_encoder *encoder)
video_height != encoder->scaled_height);
}
static inline bool gpu_encode_available(const struct obs_encoder *encoder)
{
return (encoder->info.caps & OBS_ENCODER_CAP_PASS_TEXTURE) != 0 &&
obs->video.using_nv12_tex;
}
static void add_connection(struct obs_encoder *encoder)
{
if (encoder->info.type == OBS_ENCODER_AUDIO) {
@ -191,7 +197,12 @@ static void add_connection(struct obs_encoder *encoder)
struct video_scale_info info = {0};
get_video_info(encoder, &info);
start_raw_video(encoder->media, &info, receive_video, encoder);
if (gpu_encode_available(encoder)) {
start_gpu_encode(encoder);
} else {
start_raw_video(encoder->media, &info, receive_video,
encoder);
}
}
set_encoder_active(encoder, true);
@ -199,11 +210,16 @@ static void add_connection(struct obs_encoder *encoder)
static void remove_connection(struct obs_encoder *encoder)
{
if (encoder->info.type == OBS_ENCODER_AUDIO)
if (encoder->info.type == OBS_ENCODER_AUDIO) {
audio_output_disconnect(encoder->media, encoder->mixer_idx,
receive_audio, encoder);
else
stop_raw_video(encoder->media, receive_video, encoder);
} else {
if (gpu_encode_available(encoder)) {
stop_gpu_encode(encoder);
} else {
stop_raw_video(encoder->media, receive_video, encoder);
}
}
obs_encoder_shutdown(encoder);
set_encoder_active(encoder, false);
@ -813,7 +829,7 @@ static inline void send_packet(struct obs_encoder *encoder,
cb->new_packet(cb->param, packet);
}
static void full_stop(struct obs_encoder *encoder)
void full_stop(struct obs_encoder *encoder)
{
if (encoder) {
pthread_mutex_lock(&encoder->callbacks_mutex);

View file

@ -30,6 +30,7 @@ extern "C" {
#endif
#define OBS_ENCODER_CAP_DEPRECATED (1<<0)
#define OBS_ENCODER_CAP_PASS_TEXTURE (1<<1)
/** Specifies the encoder type */
enum obs_encoder_type {
@ -251,6 +252,10 @@ struct obs_encoder_info {
* @return The properties data
*/
obs_properties_t *(*get_properties2)(void *data, void *type_data);
bool (*encode_texture)(void *data, uint32_t handle, int64_t pts,
uint64_t lock_key, uint64_t *next_key,
struct encoder_packet *packet, bool *received_packet);
};
EXPORT void obs_register_encoder_s(const struct obs_encoder_info *info,

View file

@ -38,6 +38,8 @@
#define NUM_TEXTURES 2
#define MICROSECOND_DEN 1000000
#define NUM_ENCODE_TEXTURES 3
#define NUM_ENCODE_TEXTURE_FRAMES_TO_WAIT 1
static inline int64_t packet_dts_usec(struct encoder_packet *packet)
{
@ -225,6 +227,16 @@ struct obs_vframe_info {
int count;
};
struct obs_tex_frame {
gs_texture_t *tex;
gs_texture_t *tex_uv;
uint32_t handle;
uint64_t timestamp;
uint64_t lock_key;
int count;
bool released;
};
struct obs_core_video {
graphics_t *graphics;
gs_stagesurf_t *copy_surfaces[NUM_TEXTURES];
@ -238,6 +250,7 @@ struct obs_core_video {
bool textures_converted[NUM_TEXTURES];
bool using_nv12_tex;
struct circlebuf vframe_info_buffer;
struct circlebuf vframe_info_buffer_gpu;
gs_effect_t *default_effect;
gs_effect_t *default_rect_effect;
gs_effect_t *opaque_effect;
@ -251,6 +264,15 @@ struct obs_core_video {
gs_stagesurf_t *mapped_surface;
int cur_texture;
long raw_active;
long gpu_encoder_active;
pthread_mutex_t gpu_encoder_mutex;
struct circlebuf gpu_encoder_queue;
struct circlebuf gpu_encoder_avail_queue;
DARRAY(obs_encoder_t *) gpu_encoders;
os_sem_t *gpu_encode_semaphore;
pthread_t gpu_encode_thread;
bool gpu_encode_thread_initialized;
volatile bool gpu_encode_stop;
uint64_t video_time;
uint64_t video_avg_frame_time_ns;
@ -1015,6 +1037,9 @@ extern void obs_encoder_add_output(struct obs_encoder *encoder,
extern void obs_encoder_remove_output(struct obs_encoder *encoder,
struct obs_output *output);
extern bool start_gpu_encode(obs_encoder_t *encoder);
extern void stop_gpu_encode(obs_encoder_t *encoder);
extern void do_encode(struct obs_encoder *encoder, struct encoder_frame *frame);
extern void send_off_encoder_packet(obs_encoder_t *encoder, bool success,
bool received, struct encoder_packet *pkt);

View file

@ -707,7 +707,11 @@ void obs_register_encoder_s(const struct obs_encoder_info *info, size_t size)
CHECK_REQUIRED_VAL_(info, get_name, obs_register_encoder);
CHECK_REQUIRED_VAL_(info, create, obs_register_encoder);
CHECK_REQUIRED_VAL_(info, destroy, obs_register_encoder);
CHECK_REQUIRED_VAL_(info, encode, obs_register_encoder);
if ((info->caps & OBS_ENCODER_CAP_PASS_TEXTURE) != 0)
CHECK_REQUIRED_VAL_(info, encode_texture, obs_register_encoder);
else
CHECK_REQUIRED_VAL_(info, encode, obs_register_encoder);
if (info->type == OBS_ENCODER_AUDIO)
CHECK_REQUIRED_VAL_(info, get_frame_size, obs_register_encoder);

View file

@ -0,0 +1,214 @@
/******************************************************************************
Copyright (C) 2018 by Hugh Bailey <obs.jim@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "obs-internal.h"
static void *gpu_encode_thread(void *unused)
{
struct obs_core_video *video = &obs->video;
uint64_t interval = video_output_get_frame_time(obs->video.video);
DARRAY(obs_encoder_t *) encoders;
int wait_frames = NUM_ENCODE_TEXTURE_FRAMES_TO_WAIT;
UNUSED_PARAMETER(unused);
da_init(encoders);
os_set_thread_name("obs gpu encode thread");
while (os_sem_wait(video->gpu_encode_semaphore) == 0) {
struct obs_tex_frame tf;
uint64_t timestamp;
uint64_t lock_key;
uint64_t next_key;
int lock_count = 0;
if (os_atomic_load_bool(&video->gpu_encode_stop))
break;
if (wait_frames) {
wait_frames--;
continue;
}
/* -------------- */
pthread_mutex_lock(&video->gpu_encoder_mutex);
circlebuf_pop_front(&video->gpu_encoder_queue, &tf, sizeof(tf));
timestamp = tf.timestamp;
lock_key = tf.lock_key;
next_key = tf.lock_key;
video_output_inc_texture_frames(video->video);
for (size_t i = 0; i < video->gpu_encoders.num; i++) {
obs_encoder_t *encoder = video->gpu_encoders.array[i];
da_push_back(encoders, &encoder);
obs_encoder_addref(encoder);
}
pthread_mutex_unlock(&video->gpu_encoder_mutex);
/* -------------- */
for (size_t i = 0; i < encoders.num; i++) {
struct encoder_packet pkt = {0};
bool received = false;
bool success;
obs_encoder_t *encoder = encoders.array[i];
struct obs_encoder *pair = encoder->paired_encoder;
pkt.timebase_num = encoder->timebase_num;
pkt.timebase_den = encoder->timebase_den;
pkt.encoder = encoder;
if (!encoder->first_received && pair) {
if (!pair->first_received ||
pair->first_raw_ts > timestamp) {
continue;
}
}
if (!encoder->start_ts)
encoder->start_ts = timestamp;
if (++lock_count == encoders.num)
next_key = 0;
else
next_key++;
success = encoder->info.encode_texture(
encoder->context.data, tf.handle,
encoder->cur_pts, lock_key, &next_key,
&pkt, &received);
send_off_encoder_packet(encoder, success, received,
&pkt);
lock_key = next_key;
encoder->cur_pts += encoder->timebase_num;
}
for (size_t i = 0; i < encoders.num; i++)
obs_encoder_release(encoders.array[i]);
da_resize(encoders, 0);
/* -------------- */
pthread_mutex_lock(&video->gpu_encoder_mutex);
tf.lock_key = next_key;
if (--tf.count) {
tf.timestamp += interval;
circlebuf_push_front(&video->gpu_encoder_queue,
&tf, sizeof(tf));
video_output_inc_texture_skipped_frames(video->video);
} else {
circlebuf_push_back(
&video->gpu_encoder_avail_queue,
&tf, sizeof(tf));
}
pthread_mutex_unlock(&video->gpu_encoder_mutex);
}
da_free(encoders);
return NULL;
}
bool init_gpu_encoding(struct obs_core_video *video)
{
#ifdef _WIN32
struct obs_video_info *ovi = &video->ovi;
video->gpu_encode_stop = false;
circlebuf_reserve(&video->gpu_encoder_avail_queue, NUM_ENCODE_TEXTURES);
for (size_t i = 0; i < NUM_ENCODE_TEXTURES; i++) {
gs_texture_t *tex;
gs_texture_t *tex_uv;
gs_texture_create_nv12(
&tex, &tex_uv,
ovi->output_width, ovi->output_height,
GS_RENDER_TARGET | GS_SHARED_KM_TEX);
if (!tex) {
return false;
}
uint32_t handle = gs_texture_get_shared_handle(tex);
struct obs_tex_frame frame = {
.tex = tex,
.tex_uv = tex_uv,
.handle = handle
};
circlebuf_push_back(&video->gpu_encoder_avail_queue, &frame,
sizeof(frame));
}
if (os_sem_init(&video->gpu_encode_semaphore, 0) != 0)
return false;
if (pthread_create(&video->gpu_encode_thread, NULL,
gpu_encode_thread, NULL) != 0)
return false;
video->gpu_encode_thread_initialized = true;
return true;
#else
UNUSED_PARAMETER(video);
return false;
#endif
}
void stop_gpu_encoding_thread(struct obs_core_video *video)
{
if (video->gpu_encode_thread_initialized) {
os_atomic_set_bool(&video->gpu_encode_stop, true);
os_sem_post(video->gpu_encode_semaphore);
pthread_join(video->gpu_encode_thread, NULL);
video->gpu_encode_thread_initialized = false;
}
}
void free_gpu_encoding(struct obs_core_video *video)
{
if (video->gpu_encode_semaphore) {
os_sem_destroy(video->gpu_encode_semaphore);
video->gpu_encode_semaphore = NULL;
}
#define free_circlebuf(x) \
do { \
while (x.size) { \
struct obs_tex_frame frame; \
circlebuf_pop_front(&x, &frame, sizeof(frame)); \
gs_texture_destroy(frame.tex); \
gs_texture_destroy(frame.tex_uv); \
} \
circlebuf_free(&x); \
} while (false)
free_circlebuf(video->gpu_encoder_queue);
free_circlebuf(video->gpu_encoder_avail_queue);
#undef free_circlebuf
}

View file

@ -390,7 +390,98 @@ end:
profile_end(stage_output_texture_name);
}
static inline void render_video(struct obs_core_video *video, bool raw_active,
#ifdef _WIN32
static inline bool queue_frame(struct obs_core_video *video, bool raw_active,
struct obs_vframe_info *vframe_info, int prev_texture)
{
bool duplicate = !video->gpu_encoder_avail_queue.size ||
(video->gpu_encoder_queue.size && vframe_info->count > 1);
if (duplicate) {
struct obs_tex_frame *tf = circlebuf_data(
&video->gpu_encoder_queue,
video->gpu_encoder_queue.size - sizeof(*tf));
/* texture-based encoding is stopping */
if (!tf) {
return false;
}
tf->count++;
os_sem_post(video->gpu_encode_semaphore);
goto finish;
}
struct obs_tex_frame tf;
circlebuf_pop_front(&video->gpu_encoder_avail_queue, &tf, sizeof(tf));
if (tf.released) {
gs_texture_acquire_sync(tf.tex, tf.lock_key, GS_WAIT_INFINITE);
tf.released = false;
}
/* the vframe_info->count > 1 case causing a copy can only happen if by
* some chance the very first frame has to be duplicated for whatever
* reason. otherwise, it goes to the 'duplicate' case above, which
* will ensure better performance. */
if (raw_active || vframe_info->count > 1) {
gs_copy_texture(tf.tex, video->convert_textures[prev_texture]);
} else {
gs_texture_t *tex = video->convert_textures[prev_texture];
gs_texture_t *tex_uv = video->convert_uv_textures[prev_texture];
video->convert_textures[prev_texture] = tf.tex;
video->convert_uv_textures[prev_texture] = tf.tex_uv;
tf.tex = tex;
tf.tex_uv = tex_uv;
tf.handle = gs_texture_get_shared_handle(tex);
}
tf.count = 1;
tf.timestamp = vframe_info->timestamp;
tf.released = true;
gs_texture_release_sync(tf.tex, ++tf.lock_key);
circlebuf_push_back(&video->gpu_encoder_queue, &tf, sizeof(tf));
os_sem_post(video->gpu_encode_semaphore);
finish:
return --vframe_info->count;
}
extern void full_stop(struct obs_encoder *encoder);
static inline void encode_gpu(struct obs_core_video *video, bool raw_active,
struct obs_vframe_info *vframe_info, int prev_texture)
{
while (queue_frame(video, raw_active, vframe_info, prev_texture));
}
static const char *output_gpu_encoders_name = "output_gpu_encoders";
static void output_gpu_encoders(struct obs_core_video *video, bool raw_active,
int prev_texture)
{
profile_start(output_gpu_encoders_name);
if (!video->textures_converted[prev_texture])
goto end;
struct obs_vframe_info vframe_info;
circlebuf_pop_front(&video->vframe_info_buffer_gpu, &vframe_info,
sizeof(vframe_info));
pthread_mutex_lock(&video->gpu_encoder_mutex);
encode_gpu(video, raw_active, &vframe_info, prev_texture);
pthread_mutex_unlock(&video->gpu_encoder_mutex);
end:
profile_end(output_gpu_encoders_name);
}
#endif
static inline void render_video(struct obs_core_video *video,
bool raw_active, const bool gpu_active,
int cur_texture, int prev_texture)
{
gs_begin_scene();
@ -400,9 +491,17 @@ static inline void render_video(struct obs_core_video *video, bool raw_active,
render_main_texture(video, cur_texture);
if (raw_active) {
if (raw_active || gpu_active) {
render_output_texture(video, cur_texture, prev_texture);
#ifdef _WIN32
if (gpu_active) {
gs_flush();
}
#endif
}
if (raw_active || gpu_active) {
if (video->gpu_conversion) {
if (video->using_nv12_tex)
render_convert_texture_nv12(video,
@ -412,7 +511,14 @@ static inline void render_video(struct obs_core_video *video, bool raw_active,
cur_texture, prev_texture);
}
stage_output_texture(video, cur_texture, prev_texture);
#ifdef _WIN32
if (gpu_active) {
gs_flush();
output_gpu_encoders(video, raw_active, prev_texture);
}
#endif
if (raw_active)
stage_output_texture(video, cur_texture, prev_texture);
}
gs_set_render_target(NULL, NULL);
@ -609,7 +715,8 @@ static inline void output_video_data(struct obs_core_video *video,
}
}
static inline void video_sleep(struct obs_core_video *video, bool active,
static inline void video_sleep(struct obs_core_video *video,
bool raw_active, const bool gpu_active,
uint64_t *p_time, uint64_t interval_ns)
{
struct obs_vframe_info vframe_info;
@ -630,9 +737,13 @@ static inline void video_sleep(struct obs_core_video *video, bool active,
vframe_info.timestamp = cur_time;
vframe_info.count = count;
if (active)
if (raw_active)
circlebuf_push_back(&video->vframe_info_buffer, &vframe_info,
sizeof(vframe_info));
if (gpu_active)
circlebuf_push_back(&video->vframe_info_buffer_gpu,
&vframe_info, sizeof(vframe_info));
}
static const char *output_frame_gs_context_name = "gs_context(video->graphics)";
@ -640,12 +751,13 @@ static const char *output_frame_render_video_name = "render_video";
static const char *output_frame_download_frame_name = "download_frame";
static const char *output_frame_gs_flush_name = "gs_flush";
static const char *output_frame_output_video_data_name = "output_video_data";
static inline void output_frame(bool raw_active)
static inline void output_frame(bool raw_active, const bool gpu_active)
{
struct obs_core_video *video = &obs->video;
int cur_texture = video->cur_texture;
int prev_texture = cur_texture == 0 ? NUM_TEXTURES-1 : cur_texture-1;
struct video_data frame;
bool active = raw_active || gpu_active;
bool frame_ready;
memset(&frame, 0, sizeof(struct video_data));
@ -654,7 +766,7 @@ static inline void output_frame(bool raw_active)
gs_enter_context(video->graphics);
profile_start(output_frame_render_video_name);
render_video(video, raw_active, cur_texture, prev_texture);
render_video(video, raw_active, gpu_active, cur_texture, prev_texture);
profile_end(output_frame_render_video_name);
if (raw_active) {
@ -687,17 +799,31 @@ static inline void output_frame(bool raw_active)
#define NBSP "\xC2\xA0"
static void clear_frame_data(void)
static void clear_base_frame_data(void)
{
struct obs_core_video *video = &obs->video;
memset(video->textures_rendered, 0, sizeof(video->textures_rendered));
memset(video->textures_output, 0, sizeof(video->textures_output));
memset(video->textures_copied, 0, sizeof(video->textures_copied));
memset(video->textures_converted, 0, sizeof(video->textures_converted));
circlebuf_free(&video->vframe_info_buffer);
video->cur_texture = 0;
}
static void clear_raw_frame_data(void)
{
struct obs_core_video *video = &obs->video;
memset(video->textures_copied, 0, sizeof(video->textures_copied));
memset(video->textures_converted, 0, sizeof(video->textures_converted));
circlebuf_free(&video->vframe_info_buffer);
}
#ifdef _WIN32
static void clear_gpu_frame_data(void)
{
struct obs_core_video *video = &obs->video;
circlebuf_free(&video->vframe_info_buffer_gpu);
}
#endif
static const char *tick_sources_name = "tick_sources";
static const char *render_displays_name = "render_displays";
static const char *output_frame_name = "output_frame";
@ -708,7 +834,9 @@ void *obs_graphics_thread(void *param)
uint64_t frame_time_total_ns = 0;
uint64_t fps_total_ns = 0;
uint32_t fps_total_frames = 0;
bool gpu_was_active = false;
bool raw_was_active = false;
bool was_active = false;
obs->video.video_time = os_gettime_ns();
@ -725,10 +853,24 @@ void *obs_graphics_thread(void *param)
uint64_t frame_start = os_gettime_ns();
uint64_t frame_time_ns;
bool raw_active = obs->video.raw_active > 0;
#ifdef _WIN32
bool gpu_active = obs->video.gpu_encoder_active > 0;
#else
const bool gpu_active = 0;
#endif
bool active = raw_active || gpu_active;
if (!was_active && active)
clear_base_frame_data();
if (!raw_was_active && raw_active)
clear_frame_data();
clear_raw_frame_data();
#ifdef _WIN32
if (!gpu_was_active && gpu_active)
clear_gpu_frame_data();
#endif
raw_was_active = raw_active;
gpu_was_active = gpu_active;
was_active = active;
profile_start(video_thread_name);
@ -737,7 +879,7 @@ void *obs_graphics_thread(void *param)
profile_end(tick_sources_name);
profile_start(output_frame_name);
output_frame(raw_active);
output_frame(raw_active, gpu_active);
profile_end(output_frame_name);
profile_start(render_displays_name);
@ -750,8 +892,8 @@ void *obs_graphics_thread(void *param)
profile_reenable_thread();
video_sleep(&obs->video, raw_active, &obs->video.video_time,
interval);
video_sleep(&obs->video, raw_active, gpu_active,
&obs->video.video_time, interval);
frame_time_total_ns += frame_time_ns;
fps_total_ns += (obs->video.video_time - last_time);

View file

@ -388,6 +388,7 @@ static int obs_init_video(struct obs_video_info *ovi)
{
struct obs_core_video *video = &obs->video;
struct video_output_info vi;
pthread_mutexattr_t attr;
int errorcode;
make_video_info(&vi, ovi);
@ -421,6 +422,13 @@ static int obs_init_video(struct obs_video_info *ovi)
gs_leave_context();
if (pthread_mutexattr_init(&attr) != 0)
return OBS_VIDEO_FAIL;
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
return OBS_VIDEO_FAIL;
if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0)
return OBS_VIDEO_FAIL;
errorcode = pthread_create(&video->video_thread, NULL,
obs_graphics_thread, obs);
if (errorcode != 0)
@ -481,6 +489,7 @@ static void obs_free_video(void)
gs_leave_context();
circlebuf_free(&video->vframe_info_buffer);
circlebuf_free(&video->vframe_info_buffer_gpu);
memset(&video->textures_rendered, 0,
sizeof(video->textures_rendered));
@ -491,6 +500,11 @@ static void obs_free_video(void)
memset(&video->textures_converted, 0,
sizeof(video->textures_converted));
pthread_mutex_destroy(&video->gpu_encoder_mutex);
pthread_mutex_init_value(&video->gpu_encoder_mutex);
da_free(video->gpu_encoders);
video->gpu_encoder_active = 0;
video->cur_texture = 0;
}
}
@ -792,6 +806,7 @@ 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);
pthread_mutex_init_value(&obs->video.gpu_encoder_mutex);
obs->name_store_owned = !store;
obs->name_store = store ? store : profiler_name_store_create();
@ -2298,13 +2313,69 @@ obs_data_t *obs_get_private_data(void)
return private_data;
}
extern bool init_gpu_encoding(struct obs_core_video *video);
extern void stop_gpu_encoding_thread(struct obs_core_video *video);
extern void free_gpu_encoding(struct obs_core_video *video);
bool start_gpu_encode(obs_encoder_t *encoder)
{
struct obs_core_video *video = &obs->video;
bool success = true;
obs_enter_graphics();
pthread_mutex_lock(&video->gpu_encoder_mutex);
if (!video->gpu_encoders.num)
success = init_gpu_encoding(video);
if (success)
da_push_back(video->gpu_encoders, &encoder);
else
free_gpu_encoding(video);
pthread_mutex_unlock(&video->gpu_encoder_mutex);
obs_leave_graphics();
if (success) {
os_atomic_inc_long(&video->gpu_encoder_active);
video_output_inc_texture_encoders(video->video);
}
return success;
}
void stop_gpu_encode(obs_encoder_t *encoder)
{
struct obs_core_video *video = &obs->video;
bool call_free = false;
os_atomic_dec_long(&video->gpu_encoder_active);
video_output_dec_texture_encoders(video->video);
pthread_mutex_lock(&video->gpu_encoder_mutex);
da_erase_item(video->gpu_encoders, &encoder);
if (!video->gpu_encoders.num)
call_free = true;
pthread_mutex_unlock(&video->gpu_encoder_mutex);
if (call_free) {
stop_gpu_encoding_thread(video);
obs_enter_graphics();
pthread_mutex_lock(&video->gpu_encoder_mutex);
free_gpu_encoding(video);
pthread_mutex_unlock(&video->gpu_encoder_mutex);
obs_leave_graphics();
}
}
bool obs_video_active(void)
{
struct obs_core_video *video = &obs->video;
if (!obs)
return false;
return os_atomic_load_long(&video->raw_active) > 0;
return os_atomic_load_long(&video->raw_active) > 0 ||
os_atomic_load_long(&video->gpu_encoder_active) > 0;
}
bool obs_nv12_tex_active(void)