libobs: Implement pausing of outputs
This implements pausing of outputs. To accomplish this, raw audio/video data is halted to the encoders or raw output. Pausing is as precisely timed as possible according to the timing of the obs_output_pause call, and audio data will be spliced down to the exact audio sample in accordance to that timing at the start/end marks. Outputs that support this (outputs used for recording) can set the OBS_OUTPUT_CAN_PAUSE capability flag.master
parent
3b581a3727
commit
153fa6337f
|
@ -66,6 +66,14 @@ Output Definition Structure (obs_output_info)
|
||||||
When this capability flag is used, specifies that this output
|
When this capability flag is used, specifies that this output
|
||||||
supports multiple encoded audio tracks simultaneously.
|
supports multiple encoded audio tracks simultaneously.
|
||||||
|
|
||||||
|
- **OBS_OUTPUT_CAN_PAUSE** - Output supports pausing.
|
||||||
|
|
||||||
|
When this capability flag is used, the output supports pausing.
|
||||||
|
When an output is paused, raw or encoded audio/video data will be
|
||||||
|
halted when paused down to the exact point to the closest video
|
||||||
|
frame. Audio data will be correctly truncated down to the exact
|
||||||
|
audio sample according to that video frame timing.
|
||||||
|
|
||||||
.. member:: const char *(*obs_output_info.get_name)(void *type_data)
|
.. member:: const char *(*obs_output_info.get_name)(void *type_data)
|
||||||
|
|
||||||
Get the translated name of the output type.
|
Get the translated name of the output type.
|
||||||
|
@ -170,13 +178,9 @@ Output Definition Structure (obs_output_info)
|
||||||
|
|
||||||
:return: The properties of the output
|
:return: The properties of the output
|
||||||
|
|
||||||
.. member:: void (*obs_output_info.pause)(void *data)
|
.. member:: void (*obs_output_info.unused1)(void *data)
|
||||||
|
|
||||||
Pauses the output (if the output supports pausing).
|
This callback is no longer used.
|
||||||
|
|
||||||
(Author's note: This is currently unimplemented)
|
|
||||||
|
|
||||||
(Optional)
|
|
||||||
|
|
||||||
.. member:: uint64_t (*obs_output_info.get_total_bytes)(void *data)
|
.. member:: uint64_t (*obs_output_info.get_total_bytes)(void *data)
|
||||||
|
|
||||||
|
@ -257,6 +261,14 @@ Output Signals
|
||||||
| OBS_OUTPUT_NO_SPACE - Ran out of disk space
|
| OBS_OUTPUT_NO_SPACE - Ran out of disk space
|
||||||
| OBS_OUTPUT_ENCODE_ERROR - Encoder error
|
| OBS_OUTPUT_ENCODE_ERROR - Encoder error
|
||||||
|
|
||||||
|
**pause** (ptr output)
|
||||||
|
|
||||||
|
Called when the output has been paused.
|
||||||
|
|
||||||
|
**unpause** (ptr output)
|
||||||
|
|
||||||
|
Called when the output has been unpaused.
|
||||||
|
|
||||||
**starting** (ptr output)
|
**starting** (ptr output)
|
||||||
|
|
||||||
Called when the output is starting.
|
Called when the output is starting.
|
||||||
|
@ -444,11 +456,18 @@ General Output Functions
|
||||||
|
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
.. function:: void obs_output_pause(obs_output_t *output)
|
.. function:: bool obs_output_pause(obs_output_t *output, bool pause)
|
||||||
|
|
||||||
Pause an output (if supported by the output).
|
Pause an output (if supported by the output).
|
||||||
|
|
||||||
(Author's Note: Not yet implemented)
|
:return: *true* if the output was paused successfuly, *false*
|
||||||
|
otherwise
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. function:: bool obs_output_paused(const obs_output_t *output)
|
||||||
|
|
||||||
|
:return: *true* if the output is paused, *false* otherwise
|
||||||
|
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
@ -808,6 +827,14 @@ Functions used by outputs
|
||||||
| OBS_OUTPUT_UNSUPPORTED - The settings, video/audio format, or codecs are unsupported by this output
|
| OBS_OUTPUT_UNSUPPORTED - The settings, video/audio format, or codecs are unsupported by this output
|
||||||
| OBS_OUTPUT_NO_SPACE - Ran out of disk space
|
| OBS_OUTPUT_NO_SPACE - Ran out of disk space
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. function:: uint64_t obs_output_get_pause_offset(obs_output_t *output)
|
||||||
|
|
||||||
|
Returns the current pause offset of the output. Used with raw
|
||||||
|
outputs to calculate system timestamps when using calculated
|
||||||
|
timestamps (see FFmpeg output for an example).
|
||||||
|
|
||||||
.. ---------------------------------------------------------------------------
|
.. ---------------------------------------------------------------------------
|
||||||
|
|
||||||
.. _libobs/obs-output.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-output.h
|
.. _libobs/obs-output.h: https://github.com/jp9000/obs-studio/blob/master/libobs/obs-output.h
|
||||||
|
|
|
@ -48,6 +48,7 @@ static bool init_encoder(struct obs_encoder *encoder, const char *name,
|
||||||
pthread_mutex_init_value(&encoder->init_mutex);
|
pthread_mutex_init_value(&encoder->init_mutex);
|
||||||
pthread_mutex_init_value(&encoder->callbacks_mutex);
|
pthread_mutex_init_value(&encoder->callbacks_mutex);
|
||||||
pthread_mutex_init_value(&encoder->outputs_mutex);
|
pthread_mutex_init_value(&encoder->outputs_mutex);
|
||||||
|
pthread_mutex_init_value(&encoder->pause.mutex);
|
||||||
|
|
||||||
if (pthread_mutexattr_init(&attr) != 0)
|
if (pthread_mutexattr_init(&attr) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
@ -62,6 +63,8 @@ static bool init_encoder(struct obs_encoder *encoder, const char *name,
|
||||||
return false;
|
return false;
|
||||||
if (pthread_mutex_init(&encoder->outputs_mutex, NULL) != 0)
|
if (pthread_mutex_init(&encoder->outputs_mutex, NULL) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
if (pthread_mutex_init(&encoder->pause.mutex, NULL) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (encoder->orig_info.get_defaults)
|
if (encoder->orig_info.get_defaults)
|
||||||
encoder->orig_info.get_defaults(encoder->context.settings);
|
encoder->orig_info.get_defaults(encoder->context.settings);
|
||||||
|
@ -264,6 +267,7 @@ static void obs_encoder_actually_destroy(obs_encoder_t *encoder)
|
||||||
pthread_mutex_destroy(&encoder->init_mutex);
|
pthread_mutex_destroy(&encoder->init_mutex);
|
||||||
pthread_mutex_destroy(&encoder->callbacks_mutex);
|
pthread_mutex_destroy(&encoder->callbacks_mutex);
|
||||||
pthread_mutex_destroy(&encoder->outputs_mutex);
|
pthread_mutex_destroy(&encoder->outputs_mutex);
|
||||||
|
pthread_mutex_destroy(&encoder->pause.mutex);
|
||||||
obs_context_data_free(&encoder->context);
|
obs_context_data_free(&encoder->context);
|
||||||
if (encoder->owns_info_id)
|
if (encoder->owns_info_id)
|
||||||
bfree((void *)encoder->info.id);
|
bfree((void *)encoder->info.id);
|
||||||
|
@ -529,6 +533,16 @@ get_callback_idx(const struct obs_encoder *encoder,
|
||||||
return DARRAY_INVALID;
|
return DARRAY_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pause_reset(struct pause_data *pause)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&pause->mutex);
|
||||||
|
pause->last_video_ts = 0;
|
||||||
|
pause->ts_start = 0;
|
||||||
|
pause->ts_end = 0;
|
||||||
|
pause->ts_offset = 0;
|
||||||
|
pthread_mutex_unlock(&pause->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
static inline void obs_encoder_start_internal(
|
static inline void obs_encoder_start_internal(
|
||||||
obs_encoder_t *encoder,
|
obs_encoder_t *encoder,
|
||||||
void (*new_packet)(void *param, struct encoder_packet *packet),
|
void (*new_packet)(void *param, struct encoder_packet *packet),
|
||||||
|
@ -551,6 +565,9 @@ static inline void obs_encoder_start_internal(
|
||||||
pthread_mutex_unlock(&encoder->callbacks_mutex);
|
pthread_mutex_unlock(&encoder->callbacks_mutex);
|
||||||
|
|
||||||
if (first) {
|
if (first) {
|
||||||
|
os_atomic_set_bool(&encoder->paused, false);
|
||||||
|
pause_reset(&encoder->pause);
|
||||||
|
|
||||||
encoder->cur_pts = 0;
|
encoder->cur_pts = 0;
|
||||||
add_connection(encoder);
|
add_connection(encoder);
|
||||||
}
|
}
|
||||||
|
@ -906,6 +923,10 @@ void send_off_encoder_packet(obs_encoder_t *encoder, bool success,
|
||||||
packet_dts_usec(pkt) - encoder->offset_usec;
|
packet_dts_usec(pkt) - encoder->offset_usec;
|
||||||
pkt->sys_dts_usec = pkt->dts_usec;
|
pkt->sys_dts_usec = pkt->dts_usec;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&encoder->pause.mutex);
|
||||||
|
pkt->sys_dts_usec += encoder->pause.ts_offset / 1000;
|
||||||
|
pthread_mutex_unlock(&encoder->pause.mutex);
|
||||||
|
|
||||||
pthread_mutex_lock(&encoder->callbacks_mutex);
|
pthread_mutex_lock(&encoder->callbacks_mutex);
|
||||||
|
|
||||||
for (size_t i = encoder->callbacks.num; i > 0; i--) {
|
for (size_t i = encoder->callbacks.num; i > 0; i--) {
|
||||||
|
@ -946,6 +967,39 @@ bool do_encode(struct obs_encoder *encoder, struct encoder_frame *frame)
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool video_pause_check_internal(struct pause_data *pause,
|
||||||
|
uint64_t ts)
|
||||||
|
{
|
||||||
|
pause->last_video_ts = ts;
|
||||||
|
if (!pause->ts_start) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ts == pause->ts_start) {
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else if (ts == pause->ts_end) {
|
||||||
|
pause->ts_start = 0;
|
||||||
|
pause->ts_end = 0;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool video_pause_check(struct pause_data *pause, uint64_t timestamp)
|
||||||
|
{
|
||||||
|
bool ignore_frame;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&pause->mutex);
|
||||||
|
ignore_frame = video_pause_check_internal(pause, timestamp);
|
||||||
|
pthread_mutex_unlock(&pause->mutex);
|
||||||
|
|
||||||
|
return ignore_frame;
|
||||||
|
}
|
||||||
|
|
||||||
static const char *receive_video_name = "receive_video";
|
static const char *receive_video_name = "receive_video";
|
||||||
static void receive_video(void *param, struct video_data *frame)
|
static void receive_video(void *param, struct video_data *frame)
|
||||||
{
|
{
|
||||||
|
@ -962,6 +1016,9 @@ static void receive_video(void *param, struct video_data *frame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (video_pause_check(&encoder->pause, frame->timestamp))
|
||||||
|
goto wait_for_audio;
|
||||||
|
|
||||||
memset(&enc_frame, 0, sizeof(struct encoder_frame));
|
memset(&enc_frame, 0, sizeof(struct encoder_frame));
|
||||||
|
|
||||||
for (size_t i = 0; i < MAX_AV_PLANES; i++) {
|
for (size_t i = 0; i < MAX_AV_PLANES; i++) {
|
||||||
|
@ -1110,20 +1167,90 @@ static bool send_audio_data(struct obs_encoder *encoder)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void pause_audio(struct pause_data *pause, struct audio_data *data,
|
||||||
|
size_t sample_rate)
|
||||||
|
{
|
||||||
|
uint64_t cutoff_frames = pause->ts_start - data->timestamp;
|
||||||
|
cutoff_frames = ns_to_audio_frames(sample_rate, cutoff_frames);
|
||||||
|
|
||||||
|
data->frames = (uint32_t)cutoff_frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unpause_audio(struct pause_data *pause, struct audio_data *data,
|
||||||
|
size_t sample_rate)
|
||||||
|
{
|
||||||
|
uint64_t cutoff_frames = pause->ts_end - data->timestamp;
|
||||||
|
cutoff_frames = ns_to_audio_frames(sample_rate, cutoff_frames);
|
||||||
|
|
||||||
|
data->timestamp = pause->ts_start;
|
||||||
|
data->frames = data->frames - (uint32_t)cutoff_frames;
|
||||||
|
pause->ts_start = 0;
|
||||||
|
pause->ts_end = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool audio_pause_check_internal(struct pause_data *pause,
|
||||||
|
struct audio_data *data,
|
||||||
|
size_t sample_rate)
|
||||||
|
{
|
||||||
|
uint64_t end_ts;
|
||||||
|
|
||||||
|
if (!pause->ts_start) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_ts =
|
||||||
|
data->timestamp + audio_frames_to_ns(sample_rate, data->frames);
|
||||||
|
|
||||||
|
if (pause->ts_start >= data->timestamp) {
|
||||||
|
if (pause->ts_start <= end_ts) {
|
||||||
|
pause_audio(pause, data, sample_rate);
|
||||||
|
return !data->frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (pause->ts_end >= data->timestamp &&
|
||||||
|
pause->ts_end <= end_ts) {
|
||||||
|
unpause_audio(pause, data, sample_rate);
|
||||||
|
return !data->frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool audio_pause_check(struct pause_data *pause, struct audio_data *data,
|
||||||
|
size_t sample_rate)
|
||||||
|
{
|
||||||
|
bool ignore_audio;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&pause->mutex);
|
||||||
|
ignore_audio = audio_pause_check_internal(pause, data, sample_rate);
|
||||||
|
data->timestamp -= pause->ts_offset;
|
||||||
|
pthread_mutex_unlock(&pause->mutex);
|
||||||
|
|
||||||
|
return ignore_audio;
|
||||||
|
}
|
||||||
|
|
||||||
static const char *receive_audio_name = "receive_audio";
|
static const char *receive_audio_name = "receive_audio";
|
||||||
static void receive_audio(void *param, size_t mix_idx, struct audio_data *data)
|
static void receive_audio(void *param, size_t mix_idx, struct audio_data *in)
|
||||||
{
|
{
|
||||||
profile_start(receive_audio_name);
|
profile_start(receive_audio_name);
|
||||||
|
|
||||||
struct obs_encoder *encoder = param;
|
struct obs_encoder *encoder = param;
|
||||||
|
struct audio_data audio = *in;
|
||||||
|
|
||||||
if (!encoder->first_received) {
|
if (!encoder->first_received) {
|
||||||
encoder->first_raw_ts = data->timestamp;
|
encoder->first_raw_ts = audio.timestamp;
|
||||||
encoder->first_received = true;
|
encoder->first_received = true;
|
||||||
clear_audio(encoder);
|
clear_audio(encoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!buffer_audio(encoder, data))
|
if (audio_pause_check(&encoder->pause, &audio, encoder->samplerate))
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
if (!buffer_audio(encoder, &audio))
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
while (encoder->audio_input_buffer[0].size >=
|
while (encoder->audio_input_buffer[0].size >=
|
||||||
|
@ -1331,3 +1458,10 @@ uint32_t obs_encoder_get_caps(const obs_encoder_t *encoder)
|
||||||
? encoder->orig_info.caps
|
? encoder->orig_info.caps
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool obs_encoder_paused(const obs_encoder_t *encoder)
|
||||||
|
{
|
||||||
|
return obs_encoder_valid(encoder, "obs_encoder_paused")
|
||||||
|
? os_atomic_load_bool(&encoder->paused)
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
|
@ -840,6 +840,19 @@ struct caption_text {
|
||||||
struct caption_text *next;
|
struct caption_text *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct pause_data {
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
uint64_t last_video_ts;
|
||||||
|
uint64_t ts_start;
|
||||||
|
uint64_t ts_end;
|
||||||
|
uint64_t ts_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern bool video_pause_check(struct pause_data *pause, uint64_t timestamp);
|
||||||
|
extern bool audio_pause_check(struct pause_data *pause, struct audio_data *data,
|
||||||
|
size_t sample_rate);
|
||||||
|
extern void pause_reset(struct pause_data *pause);
|
||||||
|
|
||||||
struct obs_output {
|
struct obs_output {
|
||||||
struct obs_context_data context;
|
struct obs_context_data context;
|
||||||
struct obs_output_info info;
|
struct obs_output_info info;
|
||||||
|
@ -878,6 +891,7 @@ struct obs_output {
|
||||||
int total_frames;
|
int total_frames;
|
||||||
|
|
||||||
volatile bool active;
|
volatile bool active;
|
||||||
|
volatile bool paused;
|
||||||
video_t *video;
|
video_t *video;
|
||||||
audio_t *audio;
|
audio_t *audio;
|
||||||
obs_encoder_t *video_encoder;
|
obs_encoder_t *video_encoder;
|
||||||
|
@ -885,6 +899,8 @@ struct obs_output {
|
||||||
obs_service_t *service;
|
obs_service_t *service;
|
||||||
size_t mixer_mask;
|
size_t mixer_mask;
|
||||||
|
|
||||||
|
struct pause_data pause;
|
||||||
|
|
||||||
struct circlebuf audio_buffer[MAX_AUDIO_MIXES][MAX_AV_PLANES];
|
struct circlebuf audio_buffer[MAX_AUDIO_MIXES][MAX_AV_PLANES];
|
||||||
uint64_t audio_start_ts;
|
uint64_t audio_start_ts;
|
||||||
uint64_t video_start_ts;
|
uint64_t video_start_ts;
|
||||||
|
@ -988,6 +1004,7 @@ struct obs_encoder {
|
||||||
enum video_format preferred_format;
|
enum video_format preferred_format;
|
||||||
|
|
||||||
volatile bool active;
|
volatile bool active;
|
||||||
|
volatile bool paused;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
|
|
||||||
/* indicates ownership of the info.id buffer */
|
/* indicates ownership of the info.id buffer */
|
||||||
|
@ -1023,6 +1040,8 @@ struct obs_encoder {
|
||||||
pthread_mutex_t callbacks_mutex;
|
pthread_mutex_t callbacks_mutex;
|
||||||
DARRAY(struct encoder_callback) callbacks;
|
DARRAY(struct encoder_callback) callbacks;
|
||||||
|
|
||||||
|
struct pause_data pause;
|
||||||
|
|
||||||
const char *profile_encoder_encode_name;
|
const char *profile_encoder_encode_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,8 @@ const char *obs_output_get_display_name(const char *id)
|
||||||
static const char *output_signals[] = {
|
static const char *output_signals[] = {
|
||||||
"void start(ptr output)",
|
"void start(ptr output)",
|
||||||
"void stop(ptr output, int code)",
|
"void stop(ptr output, int code)",
|
||||||
|
"void pause(ptr output)",
|
||||||
|
"void unpause(ptr output)",
|
||||||
"void starting(ptr output)",
|
"void starting(ptr output)",
|
||||||
"void stopping(ptr output)",
|
"void stopping(ptr output)",
|
||||||
"void activate(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->interleaved_mutex);
|
||||||
pthread_mutex_init_value(&output->delay_mutex);
|
pthread_mutex_init_value(&output->delay_mutex);
|
||||||
pthread_mutex_init_value(&output->caption_mutex);
|
pthread_mutex_init_value(&output->caption_mutex);
|
||||||
|
pthread_mutex_init_value(&output->pause.mutex);
|
||||||
|
|
||||||
if (pthread_mutex_init(&output->interleaved_mutex, NULL) != 0)
|
if (pthread_mutex_init(&output->interleaved_mutex, NULL) != 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -112,6 +115,8 @@ obs_output_t *obs_output_create(const char *id, const char *name,
|
||||||
goto fail;
|
goto fail;
|
||||||
if (pthread_mutex_init(&output->caption_mutex, NULL) != 0)
|
if (pthread_mutex_init(&output->caption_mutex, NULL) != 0)
|
||||||
goto fail;
|
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)
|
if (os_event_init(&output->stopping_event, OS_EVENT_TYPE_MANUAL) != 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
if (!init_output_handlers(output, name, settings, hotkey_data))
|
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);
|
clear_audio_buffers(output);
|
||||||
|
|
||||||
os_event_destroy(output->stopping_event);
|
os_event_destroy(output->stopping_event);
|
||||||
|
pthread_mutex_destroy(&output->pause.mutex);
|
||||||
pthread_mutex_destroy(&output->caption_mutex);
|
pthread_mutex_destroy(&output->caption_mutex);
|
||||||
pthread_mutex_destroy(&output->interleaved_mutex);
|
pthread_mutex_destroy(&output->interleaved_mutex);
|
||||||
pthread_mutex_destroy(&output->delay_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)
|
if (stopping(output) && !force)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
obs_output_pause(output, false);
|
||||||
|
|
||||||
os_event_reset(output->stopping_event);
|
os_event_reset(output->stopping_event);
|
||||||
|
|
||||||
was_reconnecting = reconnecting(output) && !delay_active(output);
|
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)
|
bool obs_output_can_pause(const obs_output_t *output)
|
||||||
{
|
{
|
||||||
return obs_output_valid(output, "obs_output_can_pause")
|
return obs_output_valid(output, "obs_output_can_pause")
|
||||||
? (output->info.pause != NULL)
|
? !!(output->info.flags & OBS_OUTPUT_CAN_PAUSE)
|
||||||
: false;
|
: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void obs_output_pause(obs_output_t *output)
|
static inline void end_pause(struct pause_data *pause, uint64_t ts)
|
||||||
{
|
{
|
||||||
if (!obs_output_valid(output, "obs_output_pause"))
|
if (!pause->ts_end) {
|
||||||
return;
|
pause->ts_end = ts;
|
||||||
|
pause->ts_offset += pause->ts_end - pause->ts_start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (output->info.pause)
|
static inline uint64_t get_closest_v_ts(struct pause_data *pause)
|
||||||
output->info.pause(output->context.data);
|
{
|
||||||
|
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 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 (!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)
|
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)
|
static void default_raw_video_callback(void *param, struct video_data *frame)
|
||||||
{
|
{
|
||||||
struct obs_output *output = param;
|
struct obs_output *output = param;
|
||||||
|
|
||||||
|
if (video_pause_check(&output->pause, frame->timestamp))
|
||||||
|
return;
|
||||||
|
|
||||||
if (data_active(output))
|
if (data_active(output))
|
||||||
output->info.raw_video(output->context.data, frame);
|
output->info.raw_video(output->context.data, frame);
|
||||||
output->total_frames++;
|
output->total_frames++;
|
||||||
|
|
||||||
if (!output->video_start_ts) {
|
|
||||||
output->video_start_ts = frame->timestamp;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool prepare_audio(struct obs_output *output,
|
static bool prepare_audio(struct obs_output *output,
|
||||||
const struct audio_data *old, struct audio_data *new)
|
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;
|
*new = *old;
|
||||||
|
|
||||||
if (old->timestamp < output->video_start_ts) {
|
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))
|
if (!prepare_audio(output, in, &out))
|
||||||
return;
|
return;
|
||||||
|
if (audio_pause_check(&output->pause, &out, output->sample_rate))
|
||||||
|
return;
|
||||||
if (!output->audio_start_ts) {
|
if (!output->audio_start_ts) {
|
||||||
output->audio_start_ts = out.timestamp;
|
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,
|
audio_frames_to_ns(output->sample_rate,
|
||||||
output->total_audio_frames);
|
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;
|
output->total_audio_frames += AUDIO_OUTPUT_FRAMES;
|
||||||
|
|
||||||
if (output->info.raw_audio2)
|
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->planes = get_audio_planes(info.format, info.speakers);
|
||||||
output->total_audio_frames = 0;
|
output->total_audio_frames = 0;
|
||||||
output->audio_size = get_audio_size(info.format, info.speakers, 1);
|
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)
|
bool obs_output_begin_data_capture(obs_output_t *output, uint32_t flags)
|
||||||
|
|
|
@ -27,6 +27,7 @@ extern "C" {
|
||||||
#define OBS_OUTPUT_ENCODED (1 << 2)
|
#define OBS_OUTPUT_ENCODED (1 << 2)
|
||||||
#define OBS_OUTPUT_SERVICE (1 << 3)
|
#define OBS_OUTPUT_SERVICE (1 << 3)
|
||||||
#define OBS_OUTPUT_MULTI_TRACK (1 << 4)
|
#define OBS_OUTPUT_MULTI_TRACK (1 << 4)
|
||||||
|
#define OBS_OUTPUT_CAN_PAUSE (1 << 5)
|
||||||
|
|
||||||
struct encoder_packet;
|
struct encoder_packet;
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ struct obs_output_info {
|
||||||
|
|
||||||
obs_properties_t *(*get_properties)(void *data);
|
obs_properties_t *(*get_properties)(void *data);
|
||||||
|
|
||||||
void (*pause)(void *data);
|
void (*unused1)(void *data);
|
||||||
|
|
||||||
uint64_t (*get_total_bytes)(void *data);
|
uint64_t (*get_total_bytes)(void *data);
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,9 @@ static void *gpu_encode_thread(void *unused)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (video_pause_check(&encoder->pause, timestamp))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!encoder->start_ts)
|
if (!encoder->start_ts)
|
||||||
encoder->start_ts = timestamp;
|
encoder->start_ts = timestamp;
|
||||||
|
|
||||||
|
|
10
libobs/obs.h
10
libobs/obs.h
|
@ -1692,7 +1692,10 @@ EXPORT void obs_output_update(obs_output_t *output, obs_data_t *settings);
|
||||||
EXPORT bool obs_output_can_pause(const obs_output_t *output);
|
EXPORT bool obs_output_can_pause(const obs_output_t *output);
|
||||||
|
|
||||||
/** Pauses the output (if the functionality is allowed by the output */
|
/** Pauses the output (if the functionality is allowed by the output */
|
||||||
EXPORT void obs_output_pause(obs_output_t *output);
|
EXPORT bool obs_output_pause(obs_output_t *output, bool pause);
|
||||||
|
|
||||||
|
/** Returns whether output is paused */
|
||||||
|
EXPORT bool obs_output_paused(const obs_output_t *output);
|
||||||
|
|
||||||
/* Gets the current output settings string */
|
/* Gets the current output settings string */
|
||||||
EXPORT obs_data_t *obs_output_get_settings(const obs_output_t *output);
|
EXPORT obs_data_t *obs_output_get_settings(const obs_output_t *output);
|
||||||
|
@ -1867,6 +1870,8 @@ EXPORT void obs_output_end_data_capture(obs_output_t *output);
|
||||||
*/
|
*/
|
||||||
EXPORT void obs_output_signal_stop(obs_output_t *output, int code);
|
EXPORT void obs_output_signal_stop(obs_output_t *output, int code);
|
||||||
|
|
||||||
|
EXPORT uint64_t obs_output_get_pause_offset(obs_output_t *output);
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------- */
|
||||||
/* Encoders */
|
/* Encoders */
|
||||||
|
|
||||||
|
@ -2031,6 +2036,9 @@ EXPORT void obs_encoder_packet_release(struct encoder_packet *packet);
|
||||||
EXPORT void *obs_encoder_create_rerouted(obs_encoder_t *encoder,
|
EXPORT void *obs_encoder_create_rerouted(obs_encoder_t *encoder,
|
||||||
const char *reroute_id);
|
const char *reroute_id);
|
||||||
|
|
||||||
|
/** Returns whether encoder is paused */
|
||||||
|
EXPORT bool obs_encoder_paused(const obs_encoder_t *output);
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------- */
|
||||||
/* Stream Services */
|
/* Stream Services */
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue