Browse Source

obs-outputs: Add eRTMP/eFLV support for FLV

John Bradley 2 years ago
parent
commit
fab8f0f80e
2 changed files with 340 additions and 18 deletions
  1. 339 17
      plugins/obs-outputs/flv-output.c
  2. 1 1
      plugins/obs-outputs/rtmp-stream.c

+ 339 - 17
plugins/obs-outputs/flv-output.c

@@ -18,6 +18,11 @@
 #include <stdio.h>
 #include <obs-module.h>
 #include <obs-avc.h>
+#ifdef ENABLE_HEVC
+#include "rtmp-hevc.h"
+#include <obs-hevc.h>
+#endif
+#include "rtmp-av1.h"
 #include <util/platform.h>
 #include <util/dstr.h>
 #include <util/threading.h>
@@ -41,12 +46,101 @@ struct flv_output {
 	bool sent_headers;
 	int64_t last_packet_ts;
 
+	enum video_id_t video_codec[MAX_OUTPUT_VIDEO_ENCODERS];
+
 	pthread_mutex_t mutex;
 
 	bool got_first_video;
 	int32_t start_dts_offset;
 };
 
+/* Adapted from FFmpeg's libavutil/pixfmt.h
+ *
+ * Renamed to make it apparent that these are not imported as this module does
+ * not use or link against FFmpeg.
+ */
+
+/* clang-format off */
+
+/**
+ * Chromaticity Coordinates of the Source Primaries
+ * These values match the ones defined by ISO/IEC 23091-2_2019 subclause 8.1 and ITU-T H.273.
+ */
+enum OBSColorPrimaries {
+	OBSCOL_PRI_RESERVED0    =  0,
+	OBSCOL_PRI_BT709        =  1, ///< also ITU-R BT1361 / IEC 61966-2-4 / SMPTE RP 177 Annex B
+	OBSCOL_PRI_UNSPECIFIED  =  2,
+	OBSCOL_PRI_RESERVED     =  3,
+	OBSCOL_PRI_BT470M       =  4, ///< also FCC Title 47 Code of Federal Regulations 73.682 (a)(20)
+	OBSCOL_PRI_BT470BG      =  5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM
+	OBSCOL_PRI_SMPTE170M    =  6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC
+	OBSCOL_PRI_SMPTE240M    =  7, ///< identical to above, also called "SMPTE C" even though it uses D65
+	OBSCOL_PRI_FILM         =  8, ///< colour filters using Illuminant C
+	OBSCOL_PRI_BT2020       =  9, ///< ITU-R BT2020
+	OBSCOL_PRI_SMPTE428     = 10, ///< SMPTE ST 428-1 (CIE 1931 XYZ)
+	OBSCOL_PRI_SMPTEST428_1 = OBSCOL_PRI_SMPTE428,
+	OBSCOL_PRI_SMPTE431     = 11, ///< SMPTE ST 431-2 (2011) / DCI P3
+	OBSCOL_PRI_SMPTE432     = 12, ///< SMPTE ST 432-1 (2010) / P3 D65 / Display P3
+	OBSCOL_PRI_EBU3213      = 22, ///< EBU Tech. 3213-E (nothing there) / one of JEDEC P22 group phosphors
+	OBSCOL_PRI_JEDEC_P22    = OBSCOL_PRI_EBU3213,
+	OBSCOL_PRI_NB                 ///< Not part of ABI
+};
+
+/**
+ * Color Transfer Characteristic
+ * These values match the ones defined by ISO/IEC 23091-2_2019 subclause 8.2.
+ */
+enum OBSColorTransferCharacteristic {
+	OBSCOL_TRC_RESERVED0    =  0,
+	OBSCOL_TRC_BT709        =  1, ///< also ITU-R BT1361
+	OBSCOL_TRC_UNSPECIFIED  =  2,
+	OBSCOL_TRC_RESERVED     =  3,
+	OBSCOL_TRC_GAMMA22      =  4, ///< also ITU-R BT470M / ITU-R BT1700 625 PAL & SECAM
+	OBSCOL_TRC_GAMMA28      =  5, ///< also ITU-R BT470BG
+	OBSCOL_TRC_SMPTE170M    =  6, ///< also ITU-R BT601-6 525 or 625 / ITU-R BT1358 525 or 625 / ITU-R BT1700 NTSC
+	OBSCOL_TRC_SMPTE240M    =  7,
+	OBSCOL_TRC_LINEAR       =  8, ///< "Linear transfer characteristics"
+	OBSCOL_TRC_LOG          =  9, ///< "Logarithmic transfer characteristic (100:1 range)"
+	OBSCOL_TRC_LOG_SQRT     = 10, ///< "Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range)"
+	OBSCOL_TRC_IEC61966_2_4 = 11, ///< IEC 61966-2-4
+	OBSCOL_TRC_BT1361_ECG   = 12, ///< ITU-R BT1361 Extended Colour Gamut
+	OBSCOL_TRC_IEC61966_2_1 = 13, ///< IEC 61966-2-1 (sRGB or sYCC)
+	OBSCOL_TRC_BT2020_10    = 14, ///< ITU-R BT2020 for 10-bit system
+	OBSCOL_TRC_BT2020_12    = 15, ///< ITU-R BT2020 for 12-bit system
+	OBSCOL_TRC_SMPTE2084    = 16, ///< SMPTE ST 2084 for 10-, 12-, 14- and 16-bit systems
+	OBSCOL_TRC_SMPTEST2084  = OBSCOL_TRC_SMPTE2084,
+	OBSCOL_TRC_SMPTE428     = 17, ///< SMPTE ST 428-1
+	OBSCOL_TRC_SMPTEST428_1 = OBSCOL_TRC_SMPTE428,
+	OBSCOL_TRC_ARIB_STD_B67 = 18, ///< ARIB STD-B67, known as "Hybrid log-gamma"
+	OBSCOL_TRC_NB                 ///< Not part of ABI
+};
+
+/**
+ * YUV Colorspace Type
+ * These values match the ones defined by ISO/IEC 23091-2_2019 subclause 8.3.
+ */
+enum OBSColorSpace {
+	OBSCOL_SPC_RGB                =  0, ///< order of coefficients is actually GBR, also IEC 61966-2-1 (sRGB), YZX and ST 428-1
+	OBSCOL_SPC_BT709              =  1, ///< also ITU-R BT1361 / IEC 61966-2-4 xvYCC709 / derived in SMPTE RP 177 Annex B
+	OBSCOL_SPC_UNSPECIFIED        =  2,
+	OBSCOL_SPC_RESERVED           =  3, ///< reserved for future use by ITU-T and ISO/IEC just like 15-255 are
+	OBSCOL_SPC_FCC                =  4, ///< FCC Title 47 Code of Federal Regulations 73.682 (a)(20)
+	OBSCOL_SPC_BT470BG            =  5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM / IEC 61966-2-4 xvYCC601
+	OBSCOL_SPC_SMPTE170M          =  6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC / functionally identical to above
+	OBSCOL_SPC_SMPTE240M          =  7, ///< derived from 170M primaries and D65 white point, 170M is derived from BT470 System M's primaries
+	OBSCOL_SPC_YCGCO              =  8, ///< used by Dirac / VC-2 and H.264 FRext, see ITU-T SG16
+	OBSCOL_SPC_YCOCG              =  OBSCOL_SPC_YCGCO,
+	OBSCOL_SPC_BT2020_NCL         =  9, ///< ITU-R BT2020 non-constant luminance system
+	OBSCOL_SPC_BT2020_CL          = 10, ///< ITU-R BT2020 constant luminance system
+	OBSCOL_SPC_SMPTE2085          = 11, ///< SMPTE 2085, Y'D'zD'x
+	OBSCOL_SPC_CHROMA_DERIVED_NCL = 12, ///< Chromaticity-derived non-constant luminance system
+	OBSCOL_SPC_CHROMA_DERIVED_CL  = 13, ///< Chromaticity-derived constant luminance system
+	OBSCOL_SPC_ICTCP              = 14, ///< ITU-R BT.2100-0, ICtCp
+	OBSCOL_SPC_NB                       ///< Not part of ABI
+};
+
+/* clang-format on */
+
 static inline bool stopping(struct flv_output *stream)
 {
 	return os_atomic_load_bool(&stream->stopping);
@@ -101,6 +195,37 @@ static int write_packet(struct flv_output *stream,
 	return ret;
 }
 
+static int write_packet_ex(struct flv_output *stream,
+			   struct encoder_packet *packet, bool is_header,
+			   bool is_footer, size_t idx)
+{
+	uint8_t *data;
+	size_t size = 0;
+	int ret = 0;
+
+	if (is_header) {
+		flv_packet_start(packet, stream->video_codec[idx], &data, &size,
+				 idx);
+	} else if (is_footer) {
+		flv_packet_end(packet, stream->video_codec[idx], &data, &size,
+			       idx);
+	} else {
+		flv_packet_frames(packet, stream->video_codec[idx],
+				  stream->start_dts_offset, &data, &size, idx);
+	}
+
+	fwrite(data, 1, size, stream->file);
+	bfree(data);
+
+	// manually created packets
+	if (is_header || is_footer)
+		bfree(packet->data);
+	else
+		obs_encoder_packet_release(packet);
+
+	return ret;
+}
+
 static void write_meta_data(struct flv_output *stream)
 {
 	uint8_t *meta_data;
@@ -111,23 +236,27 @@ static void write_meta_data(struct flv_output *stream)
 	bfree(meta_data);
 }
 
-static void write_audio_header(struct flv_output *stream)
+static bool write_audio_header(struct flv_output *stream, size_t idx)
 {
 	obs_output_t *context = stream->output;
-	obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, 0);
+	obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, idx);
 
 	struct encoder_packet packet = {.type = OBS_ENCODER_AUDIO,
 					.timebase_den = 1};
 
-	if (!obs_encoder_get_extra_data(aencoder, &packet.data, &packet.size))
-		return;
-	write_packet(stream, &packet, true);
+	if (!aencoder)
+		return false;
+
+	if (obs_encoder_get_extra_data(aencoder, &packet.data, &packet.size))
+		write_packet(stream, &packet, true);
+
+	return true;
 }
 
-static void write_video_header(struct flv_output *stream)
+static bool write_video_header(struct flv_output *stream, size_t idx)
 {
 	obs_output_t *context = stream->output;
-	obs_encoder_t *vencoder = obs_output_get_video_encoder(context);
+	obs_encoder_t *vencoder = obs_output_get_video_encoder2(context, idx);
 	uint8_t *header;
 	size_t size;
 
@@ -135,18 +264,184 @@ static void write_video_header(struct flv_output *stream)
 					.timebase_den = 1,
 					.keyframe = true};
 
+	if (!vencoder)
+		return false;
+
 	if (!obs_encoder_get_extra_data(vencoder, &header, &size))
-		return;
-	packet.size = obs_parse_avc_header(&packet.data, header, size);
-	write_packet(stream, &packet, true);
-	bfree(packet.data);
+		return false;
+
+	switch (stream->video_codec[idx]) {
+	case CODEC_H264:
+		packet.size = obs_parse_avc_header(&packet.data, header, size);
+		// Always send H.264 on track 0 as old style for compatibility.
+		if (idx == 0) {
+			write_packet(stream, &packet, true);
+		} else {
+			write_packet_ex(stream, &packet, true, false, idx);
+		}
+		return true;
+	case CODEC_HEVC:
+#ifdef ENABLE_HEVC
+		packet.size = obs_parse_hevc_header(&packet.data, header, size);
+		break;
+#else
+		return false;
+#endif
+	case CODEC_AV1:
+		packet.size = obs_parse_av1_header(&packet.data, header, size);
+		break;
+	}
+	write_packet_ex(stream, &packet, true, false, idx);
+
+	return true;
+}
+
+// only returns false if there's an error, not if no metadata needs to be sent
+static bool write_video_metadata(struct flv_output *stream, size_t idx)
+{
+	// send metadata only if HDR
+	obs_encoder_t *encoder =
+		obs_output_get_video_encoder2(stream->output, idx);
+	if (!encoder)
+		return false;
+
+	video_t *video = obs_encoder_video(encoder);
+	if (!video)
+		return false;
+
+	const struct video_output_info *info = video_output_get_info(video);
+	enum video_colorspace colorspace = info->colorspace;
+	if (!(colorspace == VIDEO_CS_2100_PQ ||
+	      colorspace == VIDEO_CS_2100_HLG))
+		return true;
+
+	// Y2023 spec
+	if (stream->video_codec[idx] == CODEC_H264)
+		return true;
+
+	uint8_t *data;
+	size_t size;
+
+	enum video_format format = info->format;
+
+	int bits_per_raw_sample;
+	switch (format) {
+	case VIDEO_FORMAT_I010:
+	case VIDEO_FORMAT_P010:
+	case VIDEO_FORMAT_I210:
+		bits_per_raw_sample = 10;
+		break;
+	case VIDEO_FORMAT_I412:
+	case VIDEO_FORMAT_YA2L:
+		bits_per_raw_sample = 12;
+		break;
+	default:
+		bits_per_raw_sample = 8;
+	}
+
+	int pri = 0;
+	int trc = 0;
+	int spc = 0;
+	switch (colorspace) {
+	case VIDEO_CS_601:
+		pri = OBSCOL_PRI_SMPTE170M;
+		trc = OBSCOL_PRI_SMPTE170M;
+		spc = OBSCOL_PRI_SMPTE170M;
+		break;
+	case VIDEO_CS_DEFAULT:
+	case VIDEO_CS_709:
+		pri = OBSCOL_PRI_BT709;
+		trc = OBSCOL_PRI_BT709;
+		spc = OBSCOL_PRI_BT709;
+		break;
+	case VIDEO_CS_SRGB:
+		pri = OBSCOL_PRI_BT709;
+		trc = OBSCOL_TRC_IEC61966_2_1;
+		spc = OBSCOL_PRI_BT709;
+		break;
+	case VIDEO_CS_2100_PQ:
+		pri = OBSCOL_PRI_BT2020;
+		trc = OBSCOL_TRC_SMPTE2084;
+		spc = OBSCOL_SPC_BT2020_NCL;
+		break;
+	case VIDEO_CS_2100_HLG:
+		pri = OBSCOL_PRI_BT2020;
+		trc = OBSCOL_TRC_ARIB_STD_B67;
+		spc = OBSCOL_SPC_BT2020_NCL;
+	}
+
+	int max_luminance = 0;
+	if (trc == OBSCOL_TRC_ARIB_STD_B67)
+		max_luminance = 1000;
+	else if (trc == OBSCOL_TRC_SMPTE2084)
+		max_luminance = (int)obs_get_video_hdr_nominal_peak_level();
+
+	flv_packet_metadata(stream->video_codec[idx], &data, &size,
+			    bits_per_raw_sample, pri, trc, spc, 0,
+			    max_luminance, idx);
+
+	fwrite(data, 1, size, stream->file);
+	bfree(data);
+
+	return true;
 }
 
 static void write_headers(struct flv_output *stream)
 {
+	for (size_t i = 0; i < MAX_OUTPUT_VIDEO_ENCODERS; i++) {
+		obs_encoder_t *enc =
+			obs_output_get_video_encoder2(stream->output, i);
+		if (!enc)
+			break;
+
+		const char *codec = obs_encoder_get_codec(enc);
+		stream->video_codec[i] = to_video_type(codec);
+	}
+
 	write_meta_data(stream);
-	write_video_header(stream);
-	write_audio_header(stream);
+	write_audio_header(stream, 0);
+
+	for (size_t i = 0; i < MAX_OUTPUT_VIDEO_ENCODERS; i++) {
+		obs_encoder_t *enc =
+			obs_output_get_video_encoder2(stream->output, i);
+		if (!enc)
+			continue;
+
+		if (!write_video_header(stream, i) ||
+		    !write_video_metadata(stream, i))
+			return;
+	}
+
+	for (size_t i = 1; write_audio_header(stream, i); i++)
+		;
+}
+
+static bool write_video_footer(struct flv_output *stream, size_t idx)
+{
+	struct encoder_packet packet = {.type = OBS_ENCODER_VIDEO,
+					.timebase_den = 1,
+					.keyframe = false};
+	packet.size = 0;
+
+	return write_packet_ex(stream, &packet, false, true, idx) >= 0;
+}
+
+static inline bool write_footers(struct flv_output *stream)
+{
+	for (size_t i = 0; i < MAX_OUTPUT_VIDEO_ENCODERS; i++) {
+		obs_encoder_t *encoder =
+			obs_output_get_video_encoder2(stream->output, i);
+		if (!encoder)
+			continue;
+
+		if (i == 0 && stream->video_codec[i] == CODEC_H264)
+			continue;
+
+		if (!write_video_footer(stream, i))
+			return false;
+	}
+
+	return true;
 }
 
 static bool flv_output_start(void *data)
@@ -196,6 +491,7 @@ static void flv_output_actual_stop(struct flv_output *stream, int code)
 	os_atomic_set_bool(&stream->active, false);
 
 	if (stream->file) {
+		write_footers(stream);
 		write_file_info(stream->file, stream->last_packet_ts,
 				os_ftelli64(stream->file));
 
@@ -244,8 +540,30 @@ static void flv_output_data(void *data, struct encoder_packet *packet)
 			stream->got_first_video = true;
 		}
 
-		obs_parse_avc_packet(&parsed_packet, packet);
-		write_packet(stream, &parsed_packet, false);
+		switch (stream->video_codec[packet->track_idx]) {
+		case CODEC_H264:
+			obs_parse_avc_packet(&parsed_packet, packet);
+			break;
+		case CODEC_HEVC:
+#ifdef ENABLE_HEVC
+			obs_parse_hevc_packet(&parsed_packet, packet);
+			break;
+#else
+			goto unlock;
+#endif
+		case CODEC_AV1:
+			obs_parse_av1_packet(&parsed_packet, packet);
+			break;
+		}
+
+		if (stream->video_codec[packet->track_idx] != CODEC_H264 ||
+		    (stream->video_codec[packet->track_idx] == CODEC_H264 &&
+		     packet->track_idx != 0)) {
+			write_packet_ex(stream, &parsed_packet, false, false,
+					packet->track_idx);
+		} else {
+			write_packet(stream, &parsed_packet, false);
+		}
 		obs_encoder_packet_release(&parsed_packet);
 	} else {
 		write_packet(stream, packet, false);
@@ -269,8 +587,12 @@ static obs_properties_t *flv_output_properties(void *unused)
 
 struct obs_output_info flv_output_info = {
 	.id = "flv_output",
-	.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED,
-	.encoded_video_codecs = "h264",
+	.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK_AV,
+#ifdef ENABLE_HEVC
+	.encoded_video_codecs = "h264;hevc;av1",
+#else
+	.encoded_video_codecs = "h264;av1",
+#endif
 	.encoded_audio_codecs = "aac",
 	.get_name = flv_output_getname,
 	.create = flv_output_create,

+ 1 - 1
plugins/obs-outputs/rtmp-stream.c

@@ -957,7 +957,7 @@ static inline bool send_headers(struct rtmp_stream *stream)
 		return false;
 
 	for (size_t j = 0; j < MAX_OUTPUT_VIDEO_ENCODERS; j++) {
-		obs_output_t *enc =
+		obs_encoder_t *enc =
 			obs_output_get_video_encoder2(stream->output, j);
 		if (!enc)
 			continue;