123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- /******************************************************************************
- Copyright (C) 2023 by Lain Bailey <[email protected]>
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- 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 <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>
- #include <inttypes.h>
- #include "flv-mux.h"
- #define do_log(level, format, ...) \
- blog(level, "[flv output: '%s'] " format, obs_output_get_name(stream->output), ##__VA_ARGS__)
- #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
- #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
- struct flv_output {
- obs_output_t *output;
- struct dstr path;
- FILE *file;
- volatile bool active;
- volatile bool stopping;
- uint64_t stop_ts;
- bool sent_headers;
- int64_t last_packet_ts;
- enum audio_id_t audio_codec[MAX_OUTPUT_AUDIO_ENCODERS];
- enum video_id_t video_codec[MAX_OUTPUT_VIDEO_ENCODERS];
- pthread_mutex_t mutex;
- bool got_first_packet;
- 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);
- }
- static inline bool active(struct flv_output *stream)
- {
- return os_atomic_load_bool(&stream->active);
- }
- static const char *flv_output_getname(void *unused)
- {
- UNUSED_PARAMETER(unused);
- return obs_module_text("FLVOutput");
- }
- static void flv_output_stop(void *data, uint64_t ts);
- static void flv_output_destroy(void *data)
- {
- struct flv_output *stream = data;
- pthread_mutex_destroy(&stream->mutex);
- dstr_free(&stream->path);
- bfree(stream);
- }
- static void *flv_output_create(obs_data_t *settings, obs_output_t *output)
- {
- struct flv_output *stream = bzalloc(sizeof(struct flv_output));
- stream->output = output;
- pthread_mutex_init(&stream->mutex, NULL);
- UNUSED_PARAMETER(settings);
- return stream;
- }
- static int write_packet(struct flv_output *stream, struct encoder_packet *packet, bool is_header)
- {
- uint8_t *data;
- size_t size;
- int ret = 0;
- stream->last_packet_ts = get_ms_time(packet, packet->dts);
- flv_packet_mux(packet, is_header ? 0 : stream->start_dts_offset, &data, &size, is_header);
- fwrite(data, 1, size, stream->file);
- bfree(data);
- 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 int write_audio_packet_ex(struct flv_output *stream, struct encoder_packet *packet, bool is_header, size_t idx)
- {
- uint8_t *data;
- size_t size = 0;
- int ret = 0;
- if (is_header) {
- flv_packet_audio_start(packet, stream->audio_codec[idx], &data, &size, idx);
- } else {
- flv_packet_audio_frames(packet, stream->audio_codec[idx], stream->start_dts_offset, &data, &size, idx);
- }
- fwrite(data, 1, size, stream->file);
- bfree(data);
- return ret;
- }
- static void write_meta_data(struct flv_output *stream)
- {
- uint8_t *meta_data;
- size_t meta_data_size;
- flv_meta_data(stream->output, &meta_data, &meta_data_size, true);
- fwrite(meta_data, 1, meta_data_size, stream->file);
- bfree(meta_data);
- }
- 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, idx);
- struct encoder_packet packet = {.type = OBS_ENCODER_AUDIO, .timebase_den = 1};
- if (!aencoder)
- return false;
- if (obs_encoder_get_extra_data(aencoder, &packet.data, &packet.size)) {
- if (idx == 0) {
- write_packet(stream, &packet, true);
- } else {
- write_audio_packet_ex(stream, &packet, true, idx);
- }
- }
- return true;
- }
- 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_encoder2(context, idx);
- uint8_t *header;
- size_t size;
- struct encoder_packet packet = {.type = OBS_ENCODER_VIDEO, .timebase_den = 1, .keyframe = true};
- if (!vencoder)
- return false;
- if (!obs_encoder_get_extra_data(vencoder, &header, &size))
- return false;
- switch (stream->video_codec[idx]) {
- case CODEC_NONE:
- do_log(LOG_ERROR, "Codec not initialized for track %zu while sending header", idx);
- return false;
- 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_AUDIO_ENCODERS; i++) {
- obs_encoder_t *enc = obs_output_get_audio_encoder(stream->output, i);
- if (!enc)
- break;
- const char *codec = obs_encoder_get_codec(enc);
- stream->audio_codec[i] = to_audio_type(codec);
- }
- 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_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)
- {
- struct flv_output *stream = data;
- obs_data_t *settings;
- const char *path;
- if (!obs_output_can_begin_data_capture(stream->output, 0))
- return false;
- if (!obs_output_initialize_encoders(stream->output, 0))
- return false;
- stream->got_first_packet = false;
- stream->sent_headers = false;
- os_atomic_set_bool(&stream->stopping, false);
- /* get path */
- settings = obs_output_get_settings(stream->output);
- path = obs_data_get_string(settings, "path");
- dstr_copy(&stream->path, path);
- obs_data_release(settings);
- stream->file = os_fopen(stream->path.array, "wb");
- if (!stream->file) {
- warn("Unable to open FLV file '%s'", stream->path.array);
- return false;
- }
- /* write headers and start capture */
- os_atomic_set_bool(&stream->active, true);
- obs_output_begin_data_capture(stream->output, 0);
- info("Writing FLV file '%s'...", stream->path.array);
- return true;
- }
- static void flv_output_stop(void *data, uint64_t ts)
- {
- struct flv_output *stream = data;
- stream->stop_ts = ts / 1000;
- os_atomic_set_bool(&stream->stopping, true);
- }
- 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));
- fclose(stream->file);
- }
- if (code) {
- obs_output_signal_stop(stream->output, code);
- } else {
- obs_output_end_data_capture(stream->output);
- }
- info("FLV file output complete");
- }
- static void flv_output_data(void *data, struct encoder_packet *packet)
- {
- struct flv_output *stream = data;
- struct encoder_packet parsed_packet;
- pthread_mutex_lock(&stream->mutex);
- if (!active(stream))
- goto unlock;
- if (!packet) {
- flv_output_actual_stop(stream, OBS_OUTPUT_ENCODE_ERROR);
- goto unlock;
- }
- if (stopping(stream)) {
- if (packet->sys_dts_usec >= (int64_t)stream->stop_ts) {
- flv_output_actual_stop(stream, 0);
- goto unlock;
- }
- }
- if (!stream->sent_headers) {
- write_headers(stream);
- stream->sent_headers = true;
- }
- if (packet->type == OBS_ENCODER_VIDEO) {
- if (!stream->got_first_packet) {
- stream->start_dts_offset = get_ms_time(packet, packet->dts);
- stream->got_first_packet = true;
- }
- switch (stream->video_codec[packet->track_idx]) {
- case CODEC_NONE:
- do_log(LOG_ERROR, "Codec not initialized for track %zu", packet->track_idx);
- goto unlock;
- 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 {
- if (!stream->got_first_packet) {
- stream->start_dts_offset = get_ms_time(packet, packet->dts);
- stream->got_first_packet = true;
- }
- if (packet->track_idx != 0) {
- write_audio_packet_ex(stream, packet, false, packet->track_idx);
- } else {
- write_packet(stream, packet, false);
- }
- }
- unlock:
- pthread_mutex_unlock(&stream->mutex);
- }
- static obs_properties_t *flv_output_properties(void *unused)
- {
- UNUSED_PARAMETER(unused);
- obs_properties_t *props = obs_properties_create();
- obs_properties_add_text(props, "path", obs_module_text("FLVOutput.FilePath"), OBS_TEXT_DEFAULT);
- return props;
- }
- struct obs_output_info flv_output_info = {
- .id = "flv_output",
- .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,
- .destroy = flv_output_destroy,
- .start = flv_output_start,
- .stop = flv_output_stop,
- .encoded_packet = flv_output_data,
- .get_properties = flv_output_properties,
- };
|