|
@@ -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,
|