2014-05-16 00:18:23 -07:00
|
|
|
/******************************************************************************
|
|
|
|
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.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>
|
2014-07-09 22:12:57 -07:00
|
|
|
#include <obs-module.h>
|
2014-05-16 00:18:23 -07:00
|
|
|
#include <obs-avc.h>
|
|
|
|
#include <util/platform.h>
|
|
|
|
#include <util/dstr.h>
|
|
|
|
#include <util/threading.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include "flv-mux.h"
|
|
|
|
|
2014-08-12 01:00:12 -07:00
|
|
|
#define do_log(level, format, ...) \
|
2014-08-12 01:08:23 -07:00
|
|
|
blog(level, "[flv output: '%s'] " format, \
|
2014-08-12 01:00:12 -07:00
|
|
|
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__)
|
|
|
|
|
2014-05-16 00:18:23 -07:00
|
|
|
struct flv_output {
|
2014-09-25 17:44:05 -07:00
|
|
|
obs_output_t *output;
|
2014-05-16 00:18:23 -07:00
|
|
|
struct dstr path;
|
|
|
|
FILE *file;
|
|
|
|
bool active;
|
2014-12-18 12:38:37 -08:00
|
|
|
bool sent_headers;
|
2014-05-16 00:18:23 -07:00
|
|
|
int64_t last_packet_ts;
|
|
|
|
};
|
|
|
|
|
2015-09-16 01:30:51 -07:00
|
|
|
static const char *flv_output_getname(void *unused)
|
2014-05-16 00:18:23 -07:00
|
|
|
{
|
2015-09-16 01:30:51 -07:00
|
|
|
UNUSED_PARAMETER(unused);
|
2014-07-09 22:12:57 -07:00
|
|
|
return obs_module_text("FLVOutput");
|
2014-05-16 00:18:23 -07:00
|
|
|
}
|
|
|
|
|
2016-06-11 11:42:29 -07:00
|
|
|
static void flv_output_stop(void *data, uint64_t ts);
|
2014-05-16 00:18:23 -07:00
|
|
|
|
|
|
|
static void flv_output_destroy(void *data)
|
|
|
|
{
|
|
|
|
struct flv_output *stream = data;
|
|
|
|
|
|
|
|
if (stream->active)
|
2016-06-11 11:42:29 -07:00
|
|
|
flv_output_stop(data, 0);
|
2014-05-16 00:18:23 -07:00
|
|
|
|
|
|
|
dstr_free(&stream->path);
|
|
|
|
bfree(stream);
|
|
|
|
}
|
|
|
|
|
2014-09-25 17:44:05 -07:00
|
|
|
static void *flv_output_create(obs_data_t *settings, obs_output_t *output)
|
2014-05-16 00:18:23 -07:00
|
|
|
{
|
|
|
|
struct flv_output *stream = bzalloc(sizeof(struct flv_output));
|
|
|
|
stream->output = output;
|
|
|
|
|
|
|
|
UNUSED_PARAMETER(settings);
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
2016-06-11 11:42:29 -07:00
|
|
|
static void flv_output_stop(void *data, uint64_t ts)
|
2014-05-16 00:18:23 -07:00
|
|
|
{
|
|
|
|
struct flv_output *stream = data;
|
|
|
|
|
|
|
|
if (stream->active) {
|
|
|
|
if (stream->file)
|
|
|
|
write_file_info(stream->file, stream->last_packet_ts,
|
|
|
|
os_ftelli64(stream->file));
|
|
|
|
|
|
|
|
fclose(stream->file);
|
|
|
|
obs_output_end_data_capture(stream->output);
|
|
|
|
stream->active = false;
|
2014-12-31 01:39:25 -08:00
|
|
|
stream->sent_headers = false;
|
2014-08-12 01:00:12 -07:00
|
|
|
|
|
|
|
info("FLV file output complete");
|
2014-05-16 00:18:23 -07:00
|
|
|
}
|
2016-06-11 11:42:29 -07:00
|
|
|
|
|
|
|
UNUSED_PARAMETER(ts);
|
2014-05-16 00:18:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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, &data, &size, is_header);
|
|
|
|
fwrite(data, 1, size, stream->file);
|
|
|
|
bfree(data);
|
2016-12-07 12:45:25 -08:00
|
|
|
obs_encoder_packet_release(packet);
|
2014-05-16 00:18:23 -07:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_meta_data(struct flv_output *stream)
|
|
|
|
{
|
|
|
|
uint8_t *meta_data;
|
|
|
|
size_t meta_data_size;
|
|
|
|
|
(API Change) Add support for multiple audio mixers
API changed:
--------------------------
void obs_output_set_audio_encoder(
obs_output_t *output,
obs_encoder_t *encoder);
obs_encoder_t *obs_output_get_audio_encoder(
const obs_output_t *output);
obs_encoder_t *obs_audio_encoder_create(
const char *id,
const char *name,
obs_data_t *settings);
Changed to:
--------------------------
/* 'idx' specifies the track index of the output */
void obs_output_set_audio_encoder(
obs_output_t *output,
obs_encoder_t *encoder,
size_t idx);
/* 'idx' specifies the track index of the output */
obs_encoder_t *obs_output_get_audio_encoder(
const obs_output_t *output,
size_t idx);
/* 'mixer_idx' specifies the mixer index to capture audio from */
obs_encoder_t *obs_audio_encoder_create(
const char *id,
const char *name,
obs_data_t *settings,
size_t mixer_idx);
Overview
--------------------------
This feature allows multiple audio mixers to be used at a time. This
capability was able to be added with surprisingly very little extra
overhead. Audio will not be mixed unless it's assigned to a specific
mixer, and mixers will not mix unless they have an active mix
connection.
Mostly this will be useful for being able to separate out specific audio
for recording versus streaming, but will also be useful for certain
streaming services that support multiple audio streams via RTMP.
I didn't want to use a variable amount of mixers due to the desire to
reduce heap allocations, so currently I set the limit to 4 simultaneous
mixers; this number can be increased later if needed, but honestly I
feel like it's just the right number to use.
Sources:
Sources can now specify which audio mixers their audio is mixed to; this
can be a single mixer or multiple mixers at a time. The
obs_source_set_audio_mixers function sets the audio mixer which an audio
source applies to. For example, 0xF would mean that the source applies
to all four mixers.
Audio Encoders:
Audio encoders now must specify which specific audio mixer they use when
they encode audio data.
Outputs:
Outputs that use encoders can now support multiple audio tracks at once
if they have the OBS_OUTPUT_MULTI_TRACK capability flag set. This is
mostly only useful for certain types of RTMP transmissions, though may
be useful for file formats that support multiple audio tracks as well
later on.
2015-01-14 02:12:08 -08:00
|
|
|
flv_meta_data(stream->output, &meta_data, &meta_data_size, true, 0);
|
2014-05-16 00:18:23 -07:00
|
|
|
fwrite(meta_data, 1, meta_data_size, stream->file);
|
|
|
|
bfree(meta_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_audio_header(struct flv_output *stream)
|
|
|
|
{
|
2014-09-25 17:44:05 -07:00
|
|
|
obs_output_t *context = stream->output;
|
(API Change) Add support for multiple audio mixers
API changed:
--------------------------
void obs_output_set_audio_encoder(
obs_output_t *output,
obs_encoder_t *encoder);
obs_encoder_t *obs_output_get_audio_encoder(
const obs_output_t *output);
obs_encoder_t *obs_audio_encoder_create(
const char *id,
const char *name,
obs_data_t *settings);
Changed to:
--------------------------
/* 'idx' specifies the track index of the output */
void obs_output_set_audio_encoder(
obs_output_t *output,
obs_encoder_t *encoder,
size_t idx);
/* 'idx' specifies the track index of the output */
obs_encoder_t *obs_output_get_audio_encoder(
const obs_output_t *output,
size_t idx);
/* 'mixer_idx' specifies the mixer index to capture audio from */
obs_encoder_t *obs_audio_encoder_create(
const char *id,
const char *name,
obs_data_t *settings,
size_t mixer_idx);
Overview
--------------------------
This feature allows multiple audio mixers to be used at a time. This
capability was able to be added with surprisingly very little extra
overhead. Audio will not be mixed unless it's assigned to a specific
mixer, and mixers will not mix unless they have an active mix
connection.
Mostly this will be useful for being able to separate out specific audio
for recording versus streaming, but will also be useful for certain
streaming services that support multiple audio streams via RTMP.
I didn't want to use a variable amount of mixers due to the desire to
reduce heap allocations, so currently I set the limit to 4 simultaneous
mixers; this number can be increased later if needed, but honestly I
feel like it's just the right number to use.
Sources:
Sources can now specify which audio mixers their audio is mixed to; this
can be a single mixer or multiple mixers at a time. The
obs_source_set_audio_mixers function sets the audio mixer which an audio
source applies to. For example, 0xF would mean that the source applies
to all four mixers.
Audio Encoders:
Audio encoders now must specify which specific audio mixer they use when
they encode audio data.
Outputs:
Outputs that use encoders can now support multiple audio tracks at once
if they have the OBS_OUTPUT_MULTI_TRACK capability flag set. This is
mostly only useful for certain types of RTMP transmissions, though may
be useful for file formats that support multiple audio tracks as well
later on.
2015-01-14 02:12:08 -08:00
|
|
|
obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, 0);
|
2014-05-16 00:18:23 -07:00
|
|
|
uint8_t *header;
|
|
|
|
|
|
|
|
struct encoder_packet packet = {
|
|
|
|
.type = OBS_ENCODER_AUDIO,
|
|
|
|
.timebase_den = 1
|
|
|
|
};
|
|
|
|
|
|
|
|
obs_encoder_get_extra_data(aencoder, &header, &packet.size);
|
|
|
|
packet.data = bmemdup(header, packet.size);
|
|
|
|
write_packet(stream, &packet, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_video_header(struct flv_output *stream)
|
|
|
|
{
|
2014-09-25 17:44:05 -07:00
|
|
|
obs_output_t *context = stream->output;
|
|
|
|
obs_encoder_t *vencoder = obs_output_get_video_encoder(context);
|
2014-05-16 00:18:23 -07:00
|
|
|
uint8_t *header;
|
|
|
|
size_t size;
|
|
|
|
|
|
|
|
struct encoder_packet packet = {
|
|
|
|
.type = OBS_ENCODER_VIDEO,
|
|
|
|
.timebase_den = 1,
|
|
|
|
.keyframe = true
|
|
|
|
};
|
|
|
|
|
|
|
|
obs_encoder_get_extra_data(vencoder, &header, &size);
|
|
|
|
packet.size = obs_parse_avc_header(&packet.data, header, size);
|
|
|
|
write_packet(stream, &packet, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_headers(struct flv_output *stream)
|
|
|
|
{
|
|
|
|
write_meta_data(stream);
|
|
|
|
write_audio_header(stream);
|
|
|
|
write_video_header(stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool flv_output_start(void *data)
|
|
|
|
{
|
|
|
|
struct flv_output *stream = data;
|
2014-09-25 17:44:05 -07:00
|
|
|
obs_data_t *settings;
|
2014-05-16 00:18:23 -07:00
|
|
|
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;
|
|
|
|
|
|
|
|
/* get path */
|
|
|
|
settings = obs_output_get_settings(stream->output);
|
2014-08-05 11:09:29 -07:00
|
|
|
path = obs_data_get_string(settings, "path");
|
2014-05-16 00:18:23 -07:00
|
|
|
dstr_copy(&stream->path, path);
|
|
|
|
obs_data_release(settings);
|
|
|
|
|
|
|
|
stream->file = os_fopen(stream->path.array, "wb");
|
|
|
|
if (!stream->file) {
|
2014-08-12 01:00:12 -07:00
|
|
|
warn("Unable to open FLV file '%s'", stream->path.array);
|
2014-05-16 00:18:23 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* write headers and start capture */
|
|
|
|
stream->active = true;
|
|
|
|
obs_output_begin_data_capture(stream->output, 0);
|
|
|
|
|
2014-08-12 01:00:12 -07:00
|
|
|
info("Writing FLV file '%s'...", stream->path.array);
|
2014-05-16 00:18:23 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void flv_output_data(void *data, struct encoder_packet *packet)
|
|
|
|
{
|
|
|
|
struct flv_output *stream = data;
|
|
|
|
struct encoder_packet parsed_packet;
|
|
|
|
|
2014-12-18 12:38:37 -08:00
|
|
|
if (!stream->sent_headers) {
|
|
|
|
write_headers(stream);
|
|
|
|
stream->sent_headers = true;
|
|
|
|
}
|
|
|
|
|
2014-05-16 00:18:23 -07:00
|
|
|
if (packet->type == OBS_ENCODER_VIDEO) {
|
|
|
|
obs_parse_avc_packet(&parsed_packet, packet);
|
|
|
|
write_packet(stream, &parsed_packet, false);
|
2016-12-07 12:45:25 -08:00
|
|
|
obs_encoder_packet_release(&parsed_packet);
|
2014-05-16 00:18:23 -07:00
|
|
|
} else {
|
|
|
|
write_packet(stream, packet, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-29 08:36:13 -07:00
|
|
|
static obs_properties_t *flv_output_properties(void *unused)
|
2014-05-16 00:18:23 -07:00
|
|
|
{
|
2014-09-29 08:36:13 -07:00
|
|
|
UNUSED_PARAMETER(unused);
|
|
|
|
|
2014-09-25 17:44:05 -07:00
|
|
|
obs_properties_t *props = obs_properties_create();
|
2014-05-16 00:18:23 -07:00
|
|
|
|
2014-07-09 22:12:57 -07:00
|
|
|
obs_properties_add_text(props, "path",
|
|
|
|
obs_module_text("FLVOutput.FilePath"),
|
|
|
|
OBS_TEXT_DEFAULT);
|
2014-05-16 00:18:23 -07:00
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct obs_output_info flv_output_info = {
|
|
|
|
.id = "flv_output",
|
|
|
|
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED,
|
2014-08-04 14:38:26 -07:00
|
|
|
.get_name = flv_output_getname,
|
2014-05-16 00:18:23 -07:00
|
|
|
.create = flv_output_create,
|
|
|
|
.destroy = flv_output_destroy,
|
|
|
|
.start = flv_output_start,
|
|
|
|
.stop = flv_output_stop,
|
|
|
|
.encoded_packet = flv_output_data,
|
2014-08-04 21:27:52 -07:00
|
|
|
.get_properties = flv_output_properties
|
2014-05-16 00:18:23 -07:00
|
|
|
};
|