Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding dynamic stream delay to OBS #11756

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions frontend/plugins/frontend-tools/data/scripts/stream-delay.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
obs = obslua

function get_output()
local output = obs.obs_get_output_by_name("rtmp multitrack video")
return output
end

-- Function to get the current delay and add 1 second
function increment_stream_delay()
-- Get the output (stream or recording)
local output = get_output()
local current_delay = obs.obs_output_get_delay(output)

-- Add 1 second to the current delay
local new_delay = current_delay + 1

obs.obs_output_set_delay(output, new_delay, 0)

-- Log the new delay
obs.script_log(obs.LOG_INFO, "Increased stream delay from " .. current_delay .. " to " .. new_delay .. " seconds.")
end

function decrement_stream_delay()
-- Get the output (stream or recording)
local output = get_output()
local current_delay = obs.obs_output_get_delay(output)

-- Add 1 second to the current delay
local new_delay = current_delay - 1

obs.obs_output_set_delay(output, new_delay, 0)

-- Log the new delay
obs.script_log(obs.LOG_INFO, "Decreased stream delay from " .. current_delay .. " to " .. new_delay .. " seconds.")
end

-- Register the function as a hotkey
hotkey_id = obs.obs_hotkey_register_frontend("increment_stream_delay", "Increase Stream Delay by 1s", increment_stream_delay)
hotkey_id = obs.obs_hotkey_register_frontend("decrement_stream_delay", "Descrease Stream Delay by 1s", decrement_stream_delay)
-- Script description (appears in the script UI in OBS)
function script_description()
return "This script increases the stream delay by 1 second every time the hotkey is pressed."
end

-- Called when the script is unloaded
function script_unload()
-- obs.obs_hotkey_unregister(hotkey_id)
end

1 change: 0 additions & 1 deletion frontend/utility/MultitrackVideoOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,6 @@ bool MultitrackVideoOutput::HandleIncompatibleSettings(QWidget *parent, config_t
num += 1;
};

check_setting(useDelay, "Basic.Settings.Advanced.StreamDelay", "Basic.Settings.Advanced.StreamDelay");
#ifdef _WIN32
check_setting(enableNewSocketLoop, "Basic.Settings.Advanced.Network.EnableNewSocketLoop",
"Basic.Settings.Advanced.Network");
Expand Down
91 changes: 86 additions & 5 deletions libobs/obs-output-delay.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
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 <inttypes.h>
#include "obs-internal.h"
#include <string.h> // For memset

static inline bool delay_active(const struct obs_output *output)
{
Expand All @@ -43,6 +43,57 @@
return ret;
}

static void generate_blank_video_frame(struct encoder_packet *packet, struct obs_output *output)
{
video_t *video = obs_output_video(output);
if (!video) {
blog(LOG_ERROR, "Failed to retrieve video output for blank frame generation");
return;
}

uint32_t width = video_output_get_width(video);
uint32_t height = video_output_get_height(video);
size_t frame_size = width * height * 3 / 2;
uint8_t *frame_data = bmalloc(frame_size);
memset(frame_data, 0, frame_size);

packet->data = frame_data;
packet->size = frame_size;
packet->type = OBS_ENCODER_VIDEO;
packet->pts = os_gettime_ns();
packet->dts = packet->pts;
packet->keyframe = true;
packet->priority = 1;
packet->encoder = obs_output_get_video_encoder(output); // Use the associated video encoder

blog(LOG_DEBUG, "Generated blank video frame (%ux%u)", width, height);
}

static void generate_silent_audio_packet(struct encoder_packet *packet, struct obs_output *output)
{
audio_t *audio = obs_output_audio(output);
if (!audio) {
blog(LOG_ERROR, "Failed to retrieve audio output for silent packet generation");
return;
}

uint32_t sample_rate = audio_output_get_sample_rate(audio);
size_t channels = audio_output_get_channels(audio);
size_t frame_size = sample_rate * channels * sizeof(float) / 100;
uint8_t *frame_data = bmalloc(frame_size);
memset(frame_data, 0, frame_size);

packet->data = frame_data;
packet->size = frame_size;
packet->type = OBS_ENCODER_AUDIO;
packet->pts = os_gettime_ns();
packet->dts = packet->pts;
packet->encoder = obs_output_get_audio_encoder(output, 1);


blog(LOG_DEBUG, "Generated silent audio frame (%uHz, %u channels)", sample_rate, channels);

Check failure on line 94 in libobs/obs-output-delay.c

View workflow job for this annotation

GitHub Actions / Build 🧱 / macOS 🍏 (arm64)

format specifies type 'unsigned int' but the argument has type 'size_t' (aka 'unsigned long') [-Werror,-Wformat]

Check failure on line 94 in libobs/obs-output-delay.c

View workflow job for this annotation

GitHub Actions / Build 🧱 / macOS 🍏 (x86_64)

format specifies type 'unsigned int' but the argument has type 'size_t' (aka 'unsigned long') [-Werror,-Wformat]
}

static inline void push_packet(struct obs_output *output, struct encoder_packet *packet,
struct encoder_packet_time *packet_time, uint64_t t)
{
Expand All @@ -60,6 +111,23 @@
pthread_mutex_unlock(&output->delay_mutex);
}


static inline void push_blank_packet(struct obs_output *output)
{
struct encoder_packet blank_packet = {0};

if (obs_output_video(output)) {
generate_blank_video_frame(&blank_packet, output);
push_packet(output, &blank_packet, NULL, os_gettime_ns());
}

/* if (obs_output_audio(output)) {
generate_silent_audio_packet(&blank_packet, output);
push_packet(output, &blank_packet, NULL, os_gettime_ns());
}*/
}


static inline void process_delay_data(struct obs_output *output, struct delay_data *dd)
{
switch (dd->msg) {
Expand Down Expand Up @@ -99,29 +167,37 @@
struct delay_data dd;
bool popped = false;
bool preserve;
bool blanks = false;

/* ------------------------------------------------ */

preserve = (output->delay_cur_flags & OBS_OUTPUT_DELAY_PRESERVE) != 0;

pthread_mutex_lock(&output->delay_mutex);

if (output->delay_data.size) {
deque_peek_front(&output->delay_data, &dd, sizeof(dd));
elapsed_time = (t - dd.ts);

if (preserve && output->reconnecting) {
output->active_delay_ns = elapsed_time;

} else if (elapsed_time > output->active_delay_ns) {
deque_pop_front(&output->delay_data, NULL, sizeof(dd));
popped = true;
} else {
blanks = true;
// push_blank_packet(output);
}
}

pthread_mutex_unlock(&output->delay_mutex);

/* ------------------------------------------------ */
// Fix blank packets, error with discarding unsued audio packets
// possible issue is how the packets are being interleaved,
// didnt have time to study the encoder pipeline and hoping an expert
// can answer this question relatively easy.
//
//
//if (blanks)
// push_blank_packet(output);

if (popped)
process_delay_data(output, &dd);
Expand Down Expand Up @@ -195,8 +271,12 @@

output->delay_sec = delay_sec;
output->delay_flags = flags;
output->active_delay_ns = (uint64_t)delay_sec * 1000000000ULL;

blog(LOG_INFO, "Delay set for output '%s' to %u seconds", output->context.name, delay_sec);
}


uint32_t obs_output_get_delay(const obs_output_t *output)
{
return obs_output_valid(output, "obs_output_set_delay") ? output->delay_sec : 0;
Expand All @@ -207,3 +287,4 @@
return obs_output_valid(output, "obs_output_set_delay") ? (uint32_t)(output->active_delay_ns / 1000000000ULL)
: 0;
}

Loading