mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-09-20 04:42:18 +02:00
666 lines
18 KiB
C
666 lines
18 KiB
C
/******************************************************************************
|
|
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
|
|
|
|
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,
|
|
};
|