Browse Source

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.
jp9000 11 years ago
parent
commit
519c4f4118

+ 4 - 0
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++) {

+ 3 - 0
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
 	 *

+ 6 - 7
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;

+ 26 - 33
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;
 		}
 

+ 7 - 4
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);
 

+ 1 - 2
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;