From 519c4f4118e939a0da28965e6ee8c679f37b643e Mon Sep 17 00:00:00 2001 From: jp9000 Date: Thu, 10 Apr 2014 11:59:42 -0700 Subject: [PATCH] Fix issue when using multiple video encoders - Fix an issue that could occur when using more than one video encoder. Audio/video would not sync up correctly because they were expected to be paired with a particular encoder. This simply adds a little helper variable to encoder packets that specifies the system time in microseconds. We then use that system time to sync - Fix an issue with x264 with fractional FPS rates (29.97 and 59.94 particularly) where it would create ridiculously large stream outputs. The problem was that you shouldn't set the timebase_* variables in the x264 params manually, let x264 handle the default values for it and leave them at 0. - Make x264 use CFR output, because there's no reason to ever use VFR in this case. --- libobs/obs-encoder.c | 4 +++ libobs/obs-encoder.h | 3 ++ libobs/obs-internal.h | 13 ++++---- libobs/obs-output.c | 59 +++++++++++++++-------------------- plugins/obs-outputs/flv-mux.c | 11 ++++--- plugins/obs-x264/obs-x264.c | 3 +- 6 files changed, 47 insertions(+), 46 deletions(-) diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index 5740e127e..af7935b94 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -514,6 +514,10 @@ static inline void do_encode(struct obs_encoder *encoder, } if (received) { + /* we use system time here to ensure sync with other encoders, + * you do not want to use relative timestamps here */ + pkt.dts_usec = encoder->start_ts / 1000 + packet_dts_usec(&pkt); + pthread_mutex_lock(&encoder->callbacks_mutex); for (size_t i = 0; i < encoder->callbacks.num; i++) { diff --git a/libobs/obs-encoder.h b/libobs/obs-encoder.h index 90fcd1914..1b0cd3d41 100644 --- a/libobs/obs-encoder.h +++ b/libobs/obs-encoder.h @@ -41,6 +41,9 @@ struct encoder_packet { /* ---------------------------------------------------------------- */ /* Internal video variables (will be parsed automatically) */ + /* DTS in microseconds */ + int64_t dts_usec; + /** * Packet priority * diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 9b5621d6a..e36beeb8e 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -34,7 +34,12 @@ #include "obs.h" #define NUM_TEXTURES 2 +#define MICROSECOND_DEN 1000000 +static inline int64_t packet_dts_usec(struct encoder_packet *packet) +{ + return packet->dts * MICROSECOND_DEN / packet->timebase_den; +} struct draw_callback { void (*draw)(void *param, uint32_t cx, uint32_t cy); @@ -262,12 +267,6 @@ extern void obs_source_video_tick(obs_source_t source, float seconds); /* ------------------------------------------------------------------------- */ /* outputs */ -struct il_packet { - int64_t input_ts_us; - int64_t output_ts_us; - struct encoder_packet packet; -}; - struct obs_output { char *name; void *data; @@ -284,7 +283,7 @@ struct obs_output { int64_t audio_offset; int interleaved_wait; pthread_mutex_t interleaved_mutex; - DARRAY(struct il_packet) interleaved_packets; + DARRAY(struct encoder_packet) interleaved_packets; bool active; video_t video; diff --git a/libobs/obs-output.c b/libobs/obs-output.c index a65c68d00..b044fb8c3 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -95,15 +95,10 @@ fail: return NULL; } -static inline void free_il_packet(struct il_packet *data) -{ - obs_free_encoder_packet(&data->packet); -} - static inline void free_packets(struct obs_output *output) { for (size_t i = 0; i < output->interleaved_packets.num; i++) - free_il_packet(output->interleaved_packets.array+i); + obs_free_encoder_packet(output->interleaved_packets.array+i); da_free(output->interleaved_packets); } @@ -351,28 +346,19 @@ static inline struct audio_convert_info *get_audio_conversion( return output->audio_conversion_set ? &output->audio_conversion : NULL; } -#define MICROSECOND_DEN 1000000 - -static inline int64_t convert_packet_dts(struct encoder_packet *packet) -{ - return packet->dts * MICROSECOND_DEN / packet->timebase_den; -} - static bool prepare_interleaved_packet(struct obs_output *output, - struct il_packet *out, struct encoder_packet *packet) + struct encoder_packet *out, struct encoder_packet *in) { int64_t offset; - out->input_ts_us = convert_packet_dts(packet); - /* audio and video need to start at timestamp 0, and the encoders * may not currently be at 0 when we get data. so, we store the * current dts as offset and subtract that value from the dts/pts * of the output packet. */ - if (packet->type == OBS_ENCODER_VIDEO) { + if (in->type == OBS_ENCODER_VIDEO) { if (!output->received_video) { - output->first_video_ts = out->input_ts_us; - output->video_offset = packet->dts; + output->first_video_ts = in->dts_usec; + output->video_offset = in->dts; output->received_video = true; } @@ -380,47 +366,54 @@ static bool prepare_interleaved_packet(struct obs_output *output, } else{ /* don't accept audio that's before the first video timestamp */ if (!output->received_video || - out->input_ts_us < output->first_video_ts) + in->dts_usec < output->first_video_ts) return false; if (!output->received_audio) { - output->audio_offset = packet->dts; + output->audio_offset = in->dts; output->received_audio = true; } offset = output->audio_offset; } - obs_duplicate_encoder_packet(&out->packet, packet); - out->packet.dts -= offset; - out->packet.pts -= offset; - out->output_ts_us = convert_packet_dts(&out->packet); + obs_duplicate_encoder_packet(out, in); + out->dts -= offset; + out->pts -= offset; + + /* convert the newly adjusted dts to relative dts time to ensure proper + * interleaving. if we're using an audio encoder that's already been + * started on another output, then the first audio packet may not be + * quite perfectly synced up in terms of system time (and there's + * nothing we can really do about that), but it will always at least be + * within a 23ish millisecond threshold (at least for AAC) */ + out->dts_usec = packet_dts_usec(out); return true; } static inline void send_interleaved(struct obs_output *output) { - struct il_packet out = output->interleaved_packets.array[0]; + struct encoder_packet out = output->interleaved_packets.array[0]; da_erase(output->interleaved_packets, 0); - output->info.encoded_packet(output->data, &out.packet); - free_il_packet(&out); + output->info.encoded_packet(output->data, &out); + obs_free_encoder_packet(&out); } static void interleave_packets(void *data, struct encoder_packet *packet) { - struct obs_output *output = data; - struct il_packet out; - size_t idx; + struct obs_output *output = data; + struct encoder_packet out; + size_t idx; pthread_mutex_lock(&output->interleaved_mutex); if (prepare_interleaved_packet(output, &out, packet)) { for (idx = 0; idx < output->interleaved_packets.num; idx++) { - struct il_packet *cur_packet; + struct encoder_packet *cur_packet; cur_packet = output->interleaved_packets.array + idx; - if (out.output_ts_us < cur_packet->output_ts_us) + if (out.dts_usec < cur_packet->dts_usec) break; } diff --git a/plugins/obs-outputs/flv-mux.c b/plugins/obs-outputs/flv-mux.c index 5a13f8d9b..6c931fe8f 100644 --- a/plugins/obs-outputs/flv-mux.c +++ b/plugins/obs-outputs/flv-mux.c @@ -21,10 +21,11 @@ #include "obs-output-ver.h" #include "rtmp-helpers.h" -/* FIXME: this is currently hard-coded to h264 and aac! ..not that we'll +/* TODO: FIXME: this is currently hard-coded to h264 and aac! ..not that we'll * use anything else for a long time. */ -// #define DEBUG_TIMESTAMPS +//#define DEBUG_TIMESTAMPS +//#define WRITE_FLV_HEADER #define VIDEO_HEADER_SIZE 5 #define MILLISECOND_DEN 1000 @@ -94,11 +95,13 @@ void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size) build_flv_meta_data(context, &meta_data, &meta_data_size); - /* s_write(&s, "FLV", 3); +#ifdef WRITE_FLV_HEADER + s_write(&s, "FLV", 3); s_w8(&s, 1); s_w8(&s, 5); s_wb32(&s, 9); - s_wb32(&s, 0); */ + s_wb32(&s, 0); +#endif start_pos = serializer_get_pos(&s); diff --git a/plugins/obs-x264/obs-x264.c b/plugins/obs-x264/obs-x264.c index d8022e591..c64a92dfb 100644 --- a/plugins/obs-x264/obs-x264.c +++ b/plugins/obs-x264/obs-x264.c @@ -226,6 +226,7 @@ static void update_params(struct obs_x264 *obsx264, obs_data_t settings, obsx264->params.i_keyint_max = keyint_sec * voi->fps_num / voi->fps_den; + obsx264->params.b_vfr_input = false; obsx264->params.rc.i_vbv_max_bitrate = bitrate; obsx264->params.rc.i_vbv_buffer_size = buffer_size; obsx264->params.rc.i_bitrate = bitrate; @@ -233,8 +234,6 @@ static void update_params(struct obs_x264 *obsx264, obs_data_t settings, obsx264->params.i_height = voi->height; obsx264->params.i_fps_num = voi->fps_num; obsx264->params.i_fps_den = voi->fps_den; - obsx264->params.i_timebase_num = voi->fps_den; - obsx264->params.i_timebase_den = voi->fps_num; obsx264->params.pf_log = log_x264; obsx264->params.i_log_level = X264_LOG_WARNING;