|
@@ -74,6 +74,8 @@ const char *obs_output_get_display_name(const char *id)
|
|
|
static const char *output_signals[] = {
|
|
|
"void start(ptr output)",
|
|
|
"void stop(ptr output, int code)",
|
|
|
+ "void pause(ptr output)",
|
|
|
+ "void unpause(ptr output)",
|
|
|
"void starting(ptr output)",
|
|
|
"void stopping(ptr output)",
|
|
|
"void activate(ptr output)",
|
|
@@ -105,6 +107,7 @@ obs_output_t *obs_output_create(const char *id, const char *name,
|
|
|
pthread_mutex_init_value(&output->interleaved_mutex);
|
|
|
pthread_mutex_init_value(&output->delay_mutex);
|
|
|
pthread_mutex_init_value(&output->caption_mutex);
|
|
|
+ pthread_mutex_init_value(&output->pause.mutex);
|
|
|
|
|
|
if (pthread_mutex_init(&output->interleaved_mutex, NULL) != 0)
|
|
|
goto fail;
|
|
@@ -112,6 +115,8 @@ obs_output_t *obs_output_create(const char *id, const char *name,
|
|
|
goto fail;
|
|
|
if (pthread_mutex_init(&output->caption_mutex, NULL) != 0)
|
|
|
goto fail;
|
|
|
+ if (pthread_mutex_init(&output->pause.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))
|
|
@@ -214,6 +219,7 @@ void obs_output_destroy(obs_output_t *output)
|
|
|
clear_audio_buffers(output);
|
|
|
|
|
|
os_event_destroy(output->stopping_event);
|
|
|
+ pthread_mutex_destroy(&output->pause.mutex);
|
|
|
pthread_mutex_destroy(&output->caption_mutex);
|
|
|
pthread_mutex_destroy(&output->interleaved_mutex);
|
|
|
pthread_mutex_destroy(&output->delay_mutex);
|
|
@@ -353,6 +359,9 @@ void obs_output_actual_stop(obs_output_t *output, bool force, uint64_t ts)
|
|
|
|
|
|
if (stopping(output) && !force)
|
|
|
return;
|
|
|
+
|
|
|
+ obs_output_pause(output, false);
|
|
|
+
|
|
|
os_event_reset(output->stopping_event);
|
|
|
|
|
|
was_reconnecting = reconnecting(output) && !delay_active(output);
|
|
@@ -517,17 +526,148 @@ obs_data_t *obs_output_get_settings(const obs_output_t *output)
|
|
|
bool obs_output_can_pause(const obs_output_t *output)
|
|
|
{
|
|
|
return obs_output_valid(output, "obs_output_can_pause")
|
|
|
- ? (output->info.pause != NULL)
|
|
|
+ ? !!(output->info.flags & OBS_OUTPUT_CAN_PAUSE)
|
|
|
: false;
|
|
|
}
|
|
|
|
|
|
-void obs_output_pause(obs_output_t *output)
|
|
|
+static inline void end_pause(struct pause_data *pause, uint64_t ts)
|
|
|
+{
|
|
|
+ if (!pause->ts_end) {
|
|
|
+ pause->ts_end = ts;
|
|
|
+ pause->ts_offset += pause->ts_end - pause->ts_start;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static inline uint64_t get_closest_v_ts(struct pause_data *pause)
|
|
|
+{
|
|
|
+ uint64_t interval = obs->video.video_frame_interval_ns;
|
|
|
+ uint64_t ts = os_gettime_ns();
|
|
|
+
|
|
|
+ return pause->last_video_ts +
|
|
|
+ ((ts - pause->last_video_ts + interval) / interval) * interval;
|
|
|
+}
|
|
|
+
|
|
|
+static bool obs_encoded_output_pause(obs_output_t *output, bool pause)
|
|
|
{
|
|
|
+ obs_encoder_t *venc;
|
|
|
+ obs_encoder_t *aenc[MAX_AUDIO_MIXES];
|
|
|
+ uint64_t closest_v_ts;
|
|
|
+
|
|
|
+ venc = output->video_encoder;
|
|
|
+ for (size_t i = 0; i < MAX_AUDIO_MIXES; i++)
|
|
|
+ aenc[i] = output->audio_encoders[i];
|
|
|
+
|
|
|
+ pthread_mutex_lock(&venc->pause.mutex);
|
|
|
+ for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
|
|
|
+ if (aenc[i]) {
|
|
|
+ pthread_mutex_lock(&aenc[i]->pause.mutex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ---------------------------- */
|
|
|
+
|
|
|
+ closest_v_ts = get_closest_v_ts(&venc->pause);
|
|
|
+
|
|
|
+ if (pause) {
|
|
|
+ os_atomic_set_bool(&venc->paused, true);
|
|
|
+ venc->pause.ts_start = closest_v_ts;
|
|
|
+
|
|
|
+ for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
|
|
|
+ if (aenc[i]) {
|
|
|
+ os_atomic_set_bool(&aenc[i]->paused, true);
|
|
|
+ aenc[i]->pause.ts_start = closest_v_ts;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ os_atomic_set_bool(&venc->paused, false);
|
|
|
+ end_pause(&venc->pause, closest_v_ts);
|
|
|
+
|
|
|
+ for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
|
|
|
+ if (aenc[i]) {
|
|
|
+ os_atomic_set_bool(&aenc[i]->paused, false);
|
|
|
+ end_pause(&aenc[i]->pause, closest_v_ts);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ---------------------------- */
|
|
|
+
|
|
|
+ for (size_t i = MAX_AUDIO_MIXES; i > 0; i--) {
|
|
|
+ if (aenc[i - 1]) {
|
|
|
+ pthread_mutex_unlock(&aenc[i - 1]->pause.mutex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ pthread_mutex_unlock(&venc->pause.mutex);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static bool obs_raw_output_pause(obs_output_t *output, bool pause)
|
|
|
+{
|
|
|
+ bool success;
|
|
|
+ uint64_t closest_v_ts;
|
|
|
+
|
|
|
+ pthread_mutex_lock(&output->pause.mutex);
|
|
|
+ closest_v_ts = get_closest_v_ts(&output->pause);
|
|
|
+ if (pause) {
|
|
|
+ success = !output->pause.ts_start;
|
|
|
+ if (success)
|
|
|
+ output->pause.ts_start = closest_v_ts;
|
|
|
+ } else {
|
|
|
+ success = !!output->pause.ts_start;
|
|
|
+ if (success)
|
|
|
+ end_pause(&output->pause, closest_v_ts);
|
|
|
+ }
|
|
|
+ pthread_mutex_unlock(&output->pause.mutex);
|
|
|
+
|
|
|
+ return success;
|
|
|
+}
|
|
|
+
|
|
|
+bool obs_output_pause(obs_output_t *output, bool pause)
|
|
|
+{
|
|
|
+ bool success;
|
|
|
+
|
|
|
if (!obs_output_valid(output, "obs_output_pause"))
|
|
|
- return;
|
|
|
+ return false;
|
|
|
+ if ((output->info.flags & OBS_OUTPUT_CAN_PAUSE) == 0)
|
|
|
+ return false;
|
|
|
+ if (!os_atomic_load_bool(&output->active))
|
|
|
+ return false;
|
|
|
+ if (os_atomic_load_bool(&output->paused) == pause)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ success = ((output->info.flags & OBS_OUTPUT_ENCODED) != 0)
|
|
|
+ ? obs_encoded_output_pause(output, pause)
|
|
|
+ : obs_raw_output_pause(output, pause);
|
|
|
+ if (success) {
|
|
|
+ os_atomic_set_bool(&output->paused, pause);
|
|
|
+ do_output_signal(output, pause ? "pause" : "unpause");
|
|
|
+
|
|
|
+ blog(LOG_INFO, "output %s %spaused", output->context.name,
|
|
|
+ pause ? "" : "un");
|
|
|
+ }
|
|
|
+ return success;
|
|
|
+}
|
|
|
+
|
|
|
+bool obs_output_paused(const obs_output_t *output)
|
|
|
+{
|
|
|
+ return obs_output_valid(output, "obs_output_paused")
|
|
|
+ ? os_atomic_load_bool(&output->paused)
|
|
|
+ : false;
|
|
|
+}
|
|
|
+
|
|
|
+uint64_t obs_output_get_pause_offset(obs_output_t *output)
|
|
|
+{
|
|
|
+ uint64_t offset;
|
|
|
|
|
|
- if (output->info.pause)
|
|
|
- output->info.pause(output->context.data);
|
|
|
+ if (!obs_output_valid(output, "obs_output_get_pause_offset"))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ pthread_mutex_lock(&output->pause.mutex);
|
|
|
+ offset = output->pause.ts_offset;
|
|
|
+ pthread_mutex_unlock(&output->pause.mutex);
|
|
|
+
|
|
|
+ return offset;
|
|
|
}
|
|
|
|
|
|
signal_handler_t *obs_output_get_signal_handler(const obs_output_t *output)
|
|
@@ -1532,18 +1672,29 @@ static void default_encoded_callback(void *param, struct encoder_packet *packet)
|
|
|
static void default_raw_video_callback(void *param, struct video_data *frame)
|
|
|
{
|
|
|
struct obs_output *output = param;
|
|
|
+
|
|
|
+ if (video_pause_check(&output->pause, frame->timestamp))
|
|
|
+ return;
|
|
|
+
|
|
|
if (data_active(output))
|
|
|
output->info.raw_video(output->context.data, frame);
|
|
|
output->total_frames++;
|
|
|
-
|
|
|
- if (!output->video_start_ts) {
|
|
|
- output->video_start_ts = frame->timestamp;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
static bool prepare_audio(struct obs_output *output,
|
|
|
const struct audio_data *old, struct audio_data *new)
|
|
|
{
|
|
|
+ if (!output->video_start_ts) {
|
|
|
+ pthread_mutex_lock(&output->pause.mutex);
|
|
|
+ output->video_start_ts = output->pause.last_video_ts;
|
|
|
+ pthread_mutex_unlock(&output->pause.mutex);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!output->video_start_ts)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ /* ------------------ */
|
|
|
+
|
|
|
*new = *old;
|
|
|
|
|
|
if (old->timestamp < output->video_start_ts) {
|
|
@@ -1580,10 +1731,10 @@ static void default_raw_audio_callback(void *param, size_t mix_idx,
|
|
|
|
|
|
/* -------------- */
|
|
|
|
|
|
- if (!output->video_start_ts)
|
|
|
- return;
|
|
|
if (!prepare_audio(output, in, &out))
|
|
|
return;
|
|
|
+ if (audio_pause_check(&output->pause, &out, output->sample_rate))
|
|
|
+ return;
|
|
|
if (!output->audio_start_ts) {
|
|
|
output->audio_start_ts = out.timestamp;
|
|
|
}
|
|
@@ -1610,6 +1761,10 @@ static void default_raw_audio_callback(void *param, size_t mix_idx,
|
|
|
audio_frames_to_ns(output->sample_rate,
|
|
|
output->total_audio_frames);
|
|
|
|
|
|
+ pthread_mutex_lock(&output->pause.mutex);
|
|
|
+ out.timestamp += output->pause.ts_offset;
|
|
|
+ pthread_mutex_unlock(&output->pause.mutex);
|
|
|
+
|
|
|
output->total_audio_frames += AUDIO_OUTPUT_FRAMES;
|
|
|
|
|
|
if (output->info.raw_audio2)
|
|
@@ -1906,6 +2061,8 @@ static void reset_raw_output(obs_output_t *output)
|
|
|
output->planes = get_audio_planes(info.format, info.speakers);
|
|
|
output->total_audio_frames = 0;
|
|
|
output->audio_size = get_audio_size(info.format, info.speakers, 1);
|
|
|
+
|
|
|
+ pause_reset(&output->pause);
|
|
|
}
|
|
|
|
|
|
bool obs_output_begin_data_capture(obs_output_t *output, uint32_t flags)
|