0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-20 21:13:04 +02:00
obs-studio/plugins/obs-outputs/flv-mux.c
jp9000 84e1f47ced (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-02-04 16:51:29 -08:00

264 lines
6.6 KiB
C

/******************************************************************************
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 <obs.h>
#include <stdio.h>
#include <util/dstr.h>
#include <util/array-serializer.h>
#include "flv-mux.h"
#include "obs-output-ver.h"
#include "rtmp-helpers.h"
/* TODO: FIXME: this is currently hard-coded to h264 and aac! ..not that we'll
* use anything else for a long time. */
//#define DEBUG_TIMESTAMPS
//#define WRITE_FLV_HEADER
#define VIDEO_HEADER_SIZE 5
static inline double encoder_bitrate(obs_encoder_t *encoder)
{
obs_data_t *settings = obs_encoder_get_settings(encoder);
double bitrate = obs_data_get_double(settings, "bitrate");
obs_data_release(settings);
return bitrate;
}
#define FLV_INFO_SIZE_OFFSET 42
void write_file_info(FILE *file, int64_t duration_ms, int64_t size)
{
char buf[64];
char *enc = buf;
char *end = enc + sizeof(buf);
fseek(file, FLV_INFO_SIZE_OFFSET, SEEK_SET);
enc_num_val(&enc, end, "duration", (double)duration_ms / 1000.0);
enc_num_val(&enc, end, "fileSize", (double)size);
fwrite(buf, 1, enc - buf, file);
}
static bool build_flv_meta_data(obs_output_t *context,
uint8_t **output, size_t *size, size_t a_idx)
{
obs_encoder_t *vencoder = obs_output_get_video_encoder(context);
obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, a_idx);
video_t *video = obs_encoder_video(vencoder);
audio_t *audio = obs_encoder_audio(aencoder);
char buf[4096];
char *enc = buf;
char *end = enc+sizeof(buf);
struct dstr encoder_name = {0};
if (a_idx > 0 && !aencoder)
return false;
enc_str(&enc, end, "onMetaData");
*enc++ = AMF_ECMA_ARRAY;
enc = AMF_EncodeInt32(enc, end, a_idx == 0 ? 14 : 9);
enc_num_val(&enc, end, "duration", 0.0);
enc_num_val(&enc, end, "fileSize", 0.0);
if (a_idx == 0) {
enc_num_val(&enc, end, "width",
(double)obs_encoder_get_width(vencoder));
enc_num_val(&enc, end, "height",
(double)obs_encoder_get_height(vencoder));
enc_str_val(&enc, end, "videocodecid", "avc1");
enc_num_val(&enc, end, "videodatarate",
encoder_bitrate(vencoder));
enc_num_val(&enc, end, "framerate",
video_output_get_frame_rate(video));
}
enc_str_val(&enc, end, "audiocodecid", "mp4a");
enc_num_val(&enc, end, "audiodatarate", encoder_bitrate(aencoder));
enc_num_val(&enc, end, "audiosamplerate",
(double)audio_output_get_sample_rate(audio));
enc_num_val(&enc, end, "audiosamplesize", 16.0);
enc_num_val(&enc, end, "audiochannels",
(double)audio_output_get_channels(audio));
enc_bool_val(&enc, end, "stereo",
audio_output_get_channels(audio) == 2);
dstr_printf(&encoder_name, "%s (libobs version ",
MODULE_NAME);
#ifdef HAVE_OBSCONFIG_H
dstr_cat(&encoder_name, OBS_VERSION);
#else
dstr_catf(&encoder_name, "%d.%d.%d",
LIBOBS_API_MAJOR_VER,
LIBOBS_API_MINOR_VER,
LIBOBS_API_PATCH_VER);
#endif
dstr_cat(&encoder_name, ")");
enc_str_val(&enc, end, "encoder", encoder_name.array);
dstr_free(&encoder_name);
*enc++ = 0;
*enc++ = 0;
*enc++ = AMF_OBJECT_END;
*size = enc-buf;
*output = bmemdup(buf, *size);
return true;
}
bool flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size,
bool write_header, size_t audio_idx)
{
struct array_output_data data;
struct serializer s;
uint8_t *meta_data = NULL;
size_t meta_data_size;
uint32_t start_pos;
array_output_serializer_init(&s, &data);
if (!build_flv_meta_data(context, &meta_data, &meta_data_size,
audio_idx)) {
bfree(meta_data);
return false;
}
if (write_header) {
s_write(&s, "FLV", 3);
s_w8(&s, 1);
s_w8(&s, 5);
s_wb32(&s, 9);
s_wb32(&s, 0);
}
start_pos = serializer_get_pos(&s);
s_w8(&s, RTMP_PACKET_TYPE_INFO);
s_wb24(&s, (uint32_t)meta_data_size);
s_wb32(&s, 0);
s_wb24(&s, 0);
s_write(&s, meta_data, meta_data_size);
s_wb32(&s, (uint32_t)serializer_get_pos(&s) - start_pos + 4 - 1);
*output = data.bytes.array;
*size = data.bytes.num;
bfree(meta_data);
return true;
}
#ifdef DEBUG_TIMESTAMPS
static int32_t last_time = 0;
#endif
static void flv_video(struct serializer *s, struct encoder_packet *packet,
bool is_header)
{
int64_t offset = packet->pts - packet->dts;
int32_t time_ms = get_ms_time(packet, packet->dts);
if (!packet->data || !packet->size)
return;
s_w8(s, RTMP_PACKET_TYPE_VIDEO);
#ifdef DEBUG_TIMESTAMPS
blog(LOG_DEBUG, "Video: %lu", time_ms);
if (last_time > time_ms)
blog(LOG_DEBUG, "Non-monotonic");
last_time = time_ms;
#endif
s_wb24(s, (uint32_t)packet->size + 5);
s_wb24(s, time_ms);
s_w8(s, (time_ms >> 24) & 0x7F);
s_wb24(s, 0);
/* these are the 5 extra bytes mentioned above */
s_w8(s, packet->keyframe ? 0x17 : 0x27);
s_w8(s, is_header ? 0 : 1);
s_wb24(s, get_ms_time(packet, offset));
s_write(s, packet->data, packet->size);
/* write tag size (starting byte doesnt count) */
s_wb32(s, (uint32_t)serializer_get_pos(s) + 4 - 1);
}
static void flv_audio(struct serializer *s, struct encoder_packet *packet,
bool is_header)
{
int32_t time_ms = get_ms_time(packet, packet->dts);
if (!packet->data || !packet->size)
return;
s_w8(s, RTMP_PACKET_TYPE_AUDIO);
#ifdef DEBUG_TIMESTAMPS
blog(LOG_DEBUG, "Audio: %lu", time_ms);
if (last_time > time_ms)
blog(LOG_DEBUG, "Non-monotonic");
last_time = time_ms;
#endif
s_wb24(s, (uint32_t)packet->size + 2);
s_wb24(s, time_ms);
s_w8(s, (time_ms >> 24) & 0x7F);
s_wb24(s, 0);
/* these are the two extra bytes mentioned above */
s_w8(s, 0xaf);
s_w8(s, is_header ? 0 : 1);
s_write(s, packet->data, packet->size);
/* write tag size (starting byte doesnt count) */
s_wb32(s, (uint32_t)serializer_get_pos(s) + 4 - 1);
}
void flv_packet_mux(struct encoder_packet *packet,
uint8_t **output, size_t *size, bool is_header)
{
struct array_output_data data;
struct serializer s;
array_output_serializer_init(&s, &data);
if (packet->type == OBS_ENCODER_VIDEO)
flv_video(&s, packet, is_header);
else
flv_audio(&s, packet, is_header);
*output = data.bytes.array;
*size = data.bytes.num;
}