提交 84e1f47c 编写于 作者: J jp9000

(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.
上级 e4fdd61c
......@@ -34,7 +34,7 @@ struct audio_input {
struct audio_convert_info conversion;
audio_resampler_t *resampler;
void (*callback)(void *param, struct audio_data *data);
audio_output_callback_t callback;
void *param;
};
......@@ -55,6 +55,9 @@ struct audio_line {
uint64_t next_ts_min;
/* specifies which mixes this line applies to via bits */
uint32_t mixers;
/* states whether this line is still being used. if not, then when the
* buffer is depleted, it's destroyed */
bool alive;
......@@ -75,6 +78,11 @@ static inline void audio_line_destroy_data(struct audio_line *line)
bfree(line);
}
struct audio_mix {
DARRAY(struct audio_input) inputs;
DARRAY(uint8_t) mix_buffers[MAX_AV_PLANES];
};
struct audio_output {
struct audio_output_info info;
size_t block_size;
......@@ -84,15 +92,14 @@ struct audio_output {
pthread_t thread;
os_event_t *stop_event;
DARRAY(uint8_t) mix_buffers[MAX_AV_PLANES];
bool initialized;
pthread_mutex_t line_mutex;
struct audio_line *first_line;
pthread_mutex_t input_mutex;
DARRAY(struct audio_input) inputs;
struct audio_mix mixes[MAX_AUDIO_MIXES];
};
static inline void audio_output_removeline(struct audio_output *audio,
......@@ -188,29 +195,33 @@ static inline size_t min_size(size_t a, size_t b)
#define MIX_BUFFER_SIZE 256
/* TODO: optimize mixing */
static void mix_float(uint8_t *mix_in, struct circlebuf *buf, size_t size)
static void mix_float(struct audio_output *audio, struct audio_line *line,
size_t size, size_t time_offset, size_t plane)
{
float *mix = (float*)mix_in;
float *mixes[MAX_AUDIO_MIXES];
float vals[MIX_BUFFER_SIZE];
register float mix_val;
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
uint8_t *bytes = audio->mixes[mix_idx].mix_buffers[plane].array;
mixes[mix_idx] = (float*)&bytes[time_offset];
}
while (size) {
size_t pop_count = min_size(size, sizeof(vals));
size -= pop_count;
circlebuf_pop_front(buf, vals, pop_count);
circlebuf_pop_front(&line->buffers[plane], vals, pop_count);
pop_count /= sizeof(float);
/* This sequence provides hints for MSVC to use packed SSE
* instructions addps, minps, maxps, etc. */
for (size_t i = 0; i < pop_count; i++) {
mix_val = *mix + vals[i];
/* clamp confuses the optimisation */
mix_val = (mix_val > 1.0f) ? 1.0f : mix_val;
mix_val = (mix_val < -1.0f) ? -1.0f : mix_val;
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
/* only include this audio line in this mix if it's set
* via the line's 'mixes' variable */
if ((line->mixers & (1 << mix_idx)) == 0)
continue;
*(mix++) = mix_val;
for (size_t i = 0; i < pop_count; i++) {
*(mixes[mix_idx]++) += vals[i];
}
}
}
}
......@@ -232,8 +243,7 @@ static inline bool mix_audio_line(struct audio_output *audio,
for (size_t i = 0; i < audio->planes; i++) {
size_t pop_size = min_size(size, line->buffers[i].size);
mix_float(audio->mix_buffers[i].array + time_offset,
&line->buffers[i], pop_size);
mix_float(audio, line, pop_size, time_offset, i);
}
return true;
......@@ -266,27 +276,55 @@ static bool resample_audio_output(struct audio_input *input,
}
static inline void do_audio_output(struct audio_output *audio,
uint64_t timestamp, uint32_t frames)
size_t mix_idx, uint64_t timestamp, uint32_t frames)
{
struct audio_mix *mix = &audio->mixes[mix_idx];
struct audio_data data;
for (size_t i = 0; i < MAX_AV_PLANES; i++)
data.data[i] = audio->mix_buffers[i].array;
data.data[i] = mix->mix_buffers[i].array;
data.frames = frames;
data.timestamp = timestamp;
data.volume = 1.0f;
pthread_mutex_lock(&audio->input_mutex);
for (size_t i = 0; i < audio->inputs.num; i++) {
struct audio_input *input = audio->inputs.array+i;
for (size_t i = 0; i < mix->inputs.num; i++) {
struct audio_input *input = mix->inputs.array+i;
if (resample_audio_output(input, &data))
input->callback(input->param, &data);
input->callback(input->param, mix_idx, &data);
}
pthread_mutex_unlock(&audio->input_mutex);
}
static inline void clamp_audio_output(struct audio_output *audio, size_t bytes)
{
size_t float_size = bytes / sizeof(float);
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
struct audio_mix *mix = &audio->mixes[mix_idx];
/* do not process mixing if a specific mix is inactive */
if (!mix->inputs.num)
continue;
for (size_t plane = 0; plane < audio->planes; plane++) {
float *mix_data = (float*)mix->mix_buffers[plane].array;
float *mix_end = &mix_data[float_size];
while (mix_data < mix_end) {
float val = *mix_data;
val = (val > 1.0f) ? 1.0f : val;
val = (val < -1.0f) ? -1.0f : val;
*(mix_data++) = val;
}
}
}
}
static uint64_t mix_and_output(struct audio_output *audio, uint64_t audio_time,
uint64_t prev_time)
{
......@@ -305,9 +343,13 @@ static uint64_t mix_and_output(struct audio_output *audio, uint64_t audio_time,
audio_time = prev_time + conv_frames_to_time(audio, frames);
/* resize and clear mix buffers */
for (size_t i = 0; i < audio->planes; i++) {
da_resize(audio->mix_buffers[i], bytes);
memset(audio->mix_buffers[i].array, 0, bytes);
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
struct audio_mix *mix = &audio->mixes[mix_idx];
for (size_t i = 0; i < audio->planes; i++) {
da_resize(mix->mix_buffers[i], bytes);
memset(mix->mix_buffers[i].array, 0, bytes);
}
}
/* mix audio lines */
......@@ -338,8 +380,12 @@ static uint64_t mix_and_output(struct audio_output *audio, uint64_t audio_time,
line = next;
}
/* clamps audio data to -1.0..1.0 */
clamp_audio_output(audio, bytes);
/* output */
do_audio_output(audio, prev_time, frames);
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++)
do_audio_output(audio, i, prev_time, frames);
return audio_time;
}
......@@ -373,12 +419,14 @@ static void *audio_thread(void *param)
/* ------------------------------------------------------------------------- */
static size_t audio_get_input_idx(const audio_t *audio,
void (*callback)(void *param, struct audio_data *data),
void *param)
static size_t audio_get_input_idx(const audio_t *audio, size_t mix_idx,
audio_output_callback_t callback, void *param)
{
for (size_t i = 0; i < audio->inputs.num; i++) {
struct audio_input *input = audio->inputs.array+i;
const struct audio_mix *mix = &audio->mixes[mix_idx];
for (size_t i = 0; i < mix->inputs.num; i++) {
struct audio_input *input = mix->inputs.array+i;
if (input->callback == callback && input->param == param)
return i;
}
......@@ -417,18 +465,18 @@ static inline bool audio_input_init(struct audio_input *input,
return true;
}
bool audio_output_connect(audio_t *audio,
bool audio_output_connect(audio_t *audio, size_t mi,
const struct audio_convert_info *conversion,
void (*callback)(void *param, struct audio_data *data),
void *param)
audio_output_callback_t callback, void *param)
{
bool success = false;
if (!audio) return false;
if (!audio || mi >= MAX_AUDIO_MIXES) return false;
pthread_mutex_lock(&audio->input_mutex);
if (audio_get_input_idx(audio, callback, param) == DARRAY_INVALID) {
if (audio_get_input_idx(audio, mi, callback, param) == DARRAY_INVALID) {
struct audio_mix *mix = &audio->mixes[mi];
struct audio_input input;
input.callback = callback;
input.param = param;
......@@ -452,7 +500,7 @@ bool audio_output_connect(audio_t *audio,
success = audio_input_init(&input, audio);
if (success)
da_push_back(audio->inputs, &input);
da_push_back(mix->inputs, &input);
}
pthread_mutex_unlock(&audio->input_mutex);
......@@ -460,18 +508,18 @@ bool audio_output_connect(audio_t *audio,
return success;
}
void audio_output_disconnect(audio_t *audio,
void (*callback)(void *param, struct audio_data *data),
void *param)
void audio_output_disconnect(audio_t *audio, size_t mix_idx,
audio_output_callback_t callback, void *param)
{
if (!audio) return;
if (!audio || mix_idx >= MAX_AUDIO_MIXES) return;
pthread_mutex_lock(&audio->input_mutex);
size_t idx = audio_get_input_idx(audio, callback, param);
size_t idx = audio_get_input_idx(audio, mix_idx, callback, param);
if (idx != DARRAY_INVALID) {
audio_input_free(audio->inputs.array+idx);
da_erase(audio->inputs, idx);
struct audio_mix *mix = &audio->mixes[mix_idx];
audio_input_free(mix->inputs.array+idx);
da_erase(mix->inputs, idx);
}
pthread_mutex_unlock(&audio->input_mutex);
......@@ -545,25 +593,32 @@ void audio_output_close(audio_t *audio)
line = next;
}
for (size_t i = 0; i < audio->inputs.num; i++)
audio_input_free(audio->inputs.array+i);
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
struct audio_mix *mix = &audio->mixes[mix_idx];
for (size_t i = 0; i < MAX_AV_PLANES; i++)
da_free(audio->mix_buffers[i]);
for (size_t i = 0; i < mix->inputs.num; i++)
audio_input_free(mix->inputs.array+i);
for (size_t i = 0; i < MAX_AV_PLANES; i++)
da_free(mix->mix_buffers[i]);
da_free(mix->inputs);
}
da_free(audio->inputs);
os_event_destroy(audio->stop_event);
pthread_mutex_destroy(&audio->line_mutex);
bfree(audio);
}
audio_line_t *audio_output_create_line(audio_t *audio, const char *name)
audio_line_t *audio_output_create_line(audio_t *audio, const char *name,
uint32_t mixers)
{
if (!audio) return NULL;
struct audio_line *line = bzalloc(sizeof(struct audio_line));
line->alive = true;
line->audio = audio;
line->mixers = mixers;
if (pthread_mutex_init(&line->mutex, NULL) != 0) {
blog(LOG_ERROR, "audio_output_createline: Failed to create "
......@@ -606,7 +661,15 @@ void audio_line_destroy(struct audio_line *line)
bool audio_output_active(const audio_t *audio)
{
if (!audio) return false;
return audio->inputs.num != 0;
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
const struct audio_mix *mix = &audio->mixes[mix_idx];
if (mix->inputs.num != 0)
return true;
}
return false;
}
size_t audio_output_get_block_size(const audio_t *audio)
......@@ -741,3 +804,14 @@ void audio_line_output(audio_line_t *line, const struct audio_data *data)
pthread_mutex_unlock(&line->mutex);
}
void audio_line_set_mixers(audio_line_t *line, uint32_t mixers)
{
if (!!line)
line->mixers = mixers;
}
uint32_t audio_line_get_mixers(audio_line_t *line)
{
return !!line ? line->mixers : 0;
}
......@@ -24,6 +24,8 @@
extern "C" {
#endif
#define MAX_AUDIO_MIXES 4
/*
* Base audio output component. Use this to create an audio output track
* for the media.
......@@ -172,13 +174,14 @@ static inline size_t get_audio_size(enum audio_format format,
EXPORT int audio_output_open(audio_t **audio, struct audio_output_info *info);
EXPORT void audio_output_close(audio_t *audio);
EXPORT bool audio_output_connect(audio_t *video,
typedef void (*audio_output_callback_t)(void *param, size_t mix_idx,
struct audio_data *data);
EXPORT bool audio_output_connect(audio_t *video, size_t mix_idx,
const struct audio_convert_info *conversion,
void (*callback)(void *param, struct audio_data *data),
void *param);
EXPORT void audio_output_disconnect(audio_t *video,
void (*callback)(void *param, struct audio_data *data),
void *param);
audio_output_callback_t callback, void *param);
EXPORT void audio_output_disconnect(audio_t *video, size_t mix_idx,
audio_output_callback_t callback, void *param);
EXPORT bool audio_output_active(const audio_t *audio);
......@@ -189,7 +192,10 @@ EXPORT uint32_t audio_output_get_sample_rate(const audio_t *audio);
EXPORT const struct audio_output_info *audio_output_get_info(
const audio_t *audio);
EXPORT audio_line_t *audio_output_create_line(audio_t *audio, const char *name);
EXPORT audio_line_t *audio_output_create_line(audio_t *audio, const char *name,
uint32_t mixers);
EXPORT void audio_line_set_mixers(audio_line_t *line, uint32_t mixers);
EXPORT uint32_t audio_line_get_mixers(audio_line_t *line);
EXPORT void audio_line_destroy(audio_line_t *line);
EXPORT void audio_line_output(audio_line_t *line, const struct audio_data *data);
......
......@@ -57,7 +57,7 @@ static bool init_encoder(struct obs_encoder *encoder, const char *name,
static struct obs_encoder *create_encoder(const char *id,
enum obs_encoder_type type, const char *name,
obs_data_t *settings)
obs_data_t *settings, size_t mixer_idx)
{
struct obs_encoder *encoder;
struct obs_encoder_info *ei = find_encoder(id);
......@@ -68,6 +68,7 @@ static struct obs_encoder *create_encoder(const char *id,
encoder = bzalloc(sizeof(struct obs_encoder));
encoder->info = *ei;
encoder->mixer_idx = mixer_idx;
success = init_encoder(encoder, name, settings);
if (!success) {
......@@ -87,18 +88,18 @@ obs_encoder_t *obs_video_encoder_create(const char *id, const char *name,
obs_data_t *settings)
{
if (!name || !id) return NULL;
return create_encoder(id, OBS_ENCODER_VIDEO, name, settings);
return create_encoder(id, OBS_ENCODER_VIDEO, name, settings, 0);
}
obs_encoder_t *obs_audio_encoder_create(const char *id, const char *name,
obs_data_t *settings)
obs_data_t *settings, size_t mixer_idx)
{
if (!name || !id) return NULL;
return create_encoder(id, OBS_ENCODER_AUDIO, name, settings);
return create_encoder(id, OBS_ENCODER_AUDIO, name, settings, mixer_idx);
}
static void receive_video(void *param, struct video_data *frame);
static void receive_audio(void *param, struct audio_data *data);
static void receive_audio(void *param, size_t mix_idx, struct audio_data *data);
static inline struct audio_convert_info *get_audio_info(
const struct obs_encoder *encoder,
......@@ -149,8 +150,8 @@ static void add_connection(struct obs_encoder *encoder)
if (encoder->info.type == OBS_ENCODER_AUDIO) {
get_audio_info(encoder, &audio_info);
audio_output_connect(encoder->media, &audio_info, receive_audio,
encoder);
audio_output_connect(encoder->media, encoder->mixer_idx,
&audio_info, receive_audio, encoder);
} else {
struct video_scale_info *info =
get_video_info(encoder, &video_info);
......@@ -180,8 +181,8 @@ static void add_connection(struct obs_encoder *encoder)
static void remove_connection(struct obs_encoder *encoder)
{
if (encoder->info.type == OBS_ENCODER_AUDIO)
audio_output_disconnect(encoder->media, receive_audio,
encoder);
audio_output_disconnect(encoder->media, encoder->mixer_idx,
receive_audio, encoder);
else
video_output_disconnect(encoder->media, receive_video,
encoder);
......@@ -588,6 +589,7 @@ static inline void do_encode(struct obs_encoder *encoder,
pkt.timebase_num = encoder->timebase_num;
pkt.timebase_den = encoder->timebase_den;
pkt.encoder = encoder;
success = encoder->info.encode(encoder->context.data, frame, &pkt,
&received);
......@@ -702,7 +704,7 @@ static void send_audio_data(struct obs_encoder *encoder)
encoder->cur_pts += encoder->framesize;
}
static void receive_audio(void *param, struct audio_data *data)
static void receive_audio(void *param, size_t mix_idx, struct audio_data *data)
{
struct obs_encoder *encoder = param;
......@@ -711,6 +713,8 @@ static void receive_audio(void *param, struct audio_data *data)
while (encoder->audio_input_buffer[0].size >= encoder->framesize_bytes)
send_audio_data(encoder);
UNUSED_PARAMETER(mix_idx);
}
void obs_encoder_add_output(struct obs_encoder *encoder,
......
......@@ -71,6 +71,12 @@ struct encoder_packet {
* priority or higher to continue transmission.
*/
int drop_priority;
/** Audio track index (used with outputs) */
size_t track_idx;
/** Encoder from which the track originated from */
obs_encoder_t *encoder;
};
/** Encoder input frame */
......
......@@ -388,7 +388,7 @@ struct obs_output {
bool received_video;
bool received_audio;
int64_t video_offset;
int64_t audio_offset;
int64_t audio_offsets[MAX_AUDIO_MIXES];
int64_t highest_audio_ts;
int64_t highest_video_ts;
pthread_mutex_t interleaved_mutex;
......@@ -412,8 +412,9 @@ struct obs_output {
video_t *video;
audio_t *audio;
obs_encoder_t *video_encoder;
obs_encoder_t *audio_encoder;
obs_encoder_t *audio_encoders[MAX_AUDIO_MIXES];
obs_service_t *service;
size_t mixer_idx;
uint32_t scaled_width;
uint32_t scaled_height;
......@@ -451,6 +452,8 @@ struct obs_encoder {
size_t framesize;
size_t framesize_bytes;
size_t mixer_idx;
uint32_t scaled_width;
uint32_t scaled_height;
......
......@@ -135,9 +135,13 @@ void obs_output_destroy(obs_output_t *output)
obs_encoder_remove_output(output->video_encoder,
output);
}
if (output->audio_encoder) {
obs_encoder_remove_output(output->audio_encoder,
output);
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
if (output->audio_encoders[i]) {
obs_encoder_remove_output(
output->audio_encoders[i],
output);
}
}
pthread_mutex_destroy(&output->interleaved_mutex);
......@@ -330,15 +334,33 @@ audio_t *obs_output_audio(const obs_output_t *output)
return output ? output->audio : NULL;
}
void obs_output_set_mixer(obs_output_t *output, size_t mixer_idx)
{
if (!output)
return;
if (!output->active)
output->mixer_idx = mixer_idx;
}
size_t obs_output_get_mixer(const obs_output_t *output)
{
return output ? output->mixer_idx : 0;
}
void obs_output_remove_encoder(struct obs_output *output,
struct obs_encoder *encoder)
{
if (!output) return;
if (output->video_encoder == encoder)
if (output->video_encoder == encoder) {
output->video_encoder = NULL;
else if (output->audio_encoder == encoder)
output->audio_encoder = NULL;
} else {
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
if (output->audio_encoders[i] == encoder)
output->audio_encoders[i] = NULL;
}
}
}
void obs_output_set_video_encoder(obs_output_t *output, obs_encoder_t *encoder)
......@@ -357,15 +379,27 @@ void obs_output_set_video_encoder(obs_output_t *output, obs_encoder_t *encoder)
output->scaled_width, output->scaled_height);
}
void obs_output_set_audio_encoder(obs_output_t *output, obs_encoder_t *encoder)
void obs_output_set_audio_encoder(obs_output_t *output, obs_encoder_t *encoder,
size_t idx)
{
if (!output) return;
if (output->audio_encoder == encoder) return;
if (encoder && encoder->info.type != OBS_ENCODER_AUDIO) return;
obs_encoder_remove_output(output->audio_encoder, output);
if ((output->info.flags & OBS_OUTPUT_MULTI_TRACK) != 0) {
if (idx >= MAX_AUDIO_MIXES) {
return;
}
} else {
if (idx > 0) {
return;
}
}
if (output->audio_encoders[idx] == encoder) return;
obs_encoder_remove_output(output->audio_encoders[idx], output);
obs_encoder_add_output(encoder, output);
output->audio_encoder = encoder;
output->audio_encoders[idx] = encoder;
}
obs_encoder_t *obs_output_get_video_encoder(const obs_output_t *output)
......@@ -373,9 +407,22 @@ obs_encoder_t *obs_output_get_video_encoder(const obs_output_t *output)
return output ? output->video_encoder : NULL;
}
obs_encoder_t *obs_output_get_audio_encoder(const obs_output_t *output)
obs_encoder_t *obs_output_get_audio_encoder(const obs_output_t *output,
size_t idx)
{
return output ? output->audio_encoder : NULL;
if (!output) return NULL;
if ((output->info.flags & OBS_OUTPUT_MULTI_TRACK) != 0) {
if (idx >= MAX_AUDIO_MIXES) {
return NULL;
}
} else {
if (idx > 0) {
return NULL;
}
}
return output->audio_encoders[idx];
}
void obs_output_set_service(obs_output_t *output, obs_service_t *service)
......@@ -491,6 +538,61 @@ void obs_output_set_audio_conversion(obs_output_t *output,
output->audio_conversion_set = true;
}
static inline bool service_supports_multitrack(const struct obs_output *output)
{
const struct obs_service *service = output->service;
if (!service || !service->info.supports_multitrack) {
return false;
}
return service->info.supports_multitrack(service->context.data);
}
static inline size_t num_audio_mixes(const struct obs_output *output)
{
size_t mix_count = 1;
if ((output->info.flags & OBS_OUTPUT_SERVICE) != 0) {
if (!service_supports_multitrack(output)) {
return 1;
}
}
if ((output->info.flags & OBS_OUTPUT_MULTI_TRACK) != 0) {
mix_count = 0;
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
if (!output->audio_encoders[i])
break;
mix_count++;
}
}
return mix_count;
}
static inline bool audio_valid(const struct obs_output *output, bool encoded)
{
if (encoded) {
size_t mix_count = num_audio_mixes(output);
if (!mix_count)
return false;
for (size_t i = 0; i < mix_count; i++) {
if (!output->audio_encoders[i]) {
return false;
}
}
} else {
if (!output->audio)
return false;
}
return true;
}
static bool can_begin_data_capture(const struct obs_output *output,
bool encoded, bool has_video, bool has_audio, bool has_service)
{
......@@ -505,12 +607,8 @@ static bool can_begin_data_capture(const struct obs_output *output,
}
if (has_audio) {
if (encoded) {
if (!output->audio_encoder)
return false;
} else {
if (!output->audio)
return false;
if (!audio_valid(output, encoded)) {
return false;
}
}
......@@ -565,6 +663,20 @@ static inline struct audio_convert_info *get_audio_conversion(
return output->audio_conversion_set ? &output->audio_conversion : NULL;
}
static size_t get_track_index(const struct obs_output *output,
struct encoder_packet *pkt)
{
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
struct obs_encoder *encoder = output->audio_encoders[i];
if (pkt->encoder == encoder)
return i;
}
assert(false);
return 0;
}
static void apply_interleaved_packet_offset(struct obs_output *output,
struct encoder_packet *out)
{
......@@ -583,7 +695,7 @@ static void apply_interleaved_packet_offset(struct obs_output *output,
if (!output->received_audio)
output->received_audio = true;
offset = output->audio_offset;
offset = output->audio_offsets[out->track_idx];
}
out->dts -= offset;
......@@ -681,14 +793,20 @@ static void prune_interleaved_packets(struct obs_output *output)
}
static struct encoder_packet *find_first_packet_type(struct obs_output *output,
enum obs_encoder_type type)
enum obs_encoder_type type, size_t audio_idx)
{
for (size_t i = 0; i < output->interleaved_packets.num; i++) {
struct encoder_packet *packet =
&output->interleaved_packets.array[i];
if (packet->type == type)
if (packet->type == type) {
if (type == OBS_ENCODER_AUDIO &&
packet->track_idx != audio_idx) {
continue;
}
return packet;
}
}
return NULL;
......@@ -697,25 +815,32 @@ static struct encoder_packet *find_first_packet_type(struct obs_output *output,
static bool initialize_interleaved_packets(struct obs_output *output)
{
struct encoder_packet *video;
struct encoder_packet *audio;
struct encoder_packet *audio[MAX_AUDIO_MIXES];
size_t audio_mixes = num_audio_mixes(output);
video = find_first_packet_type(output, OBS_ENCODER_VIDEO);
audio = find_first_packet_type(output, OBS_ENCODER_AUDIO);
if (!audio)
output->received_audio = false;
video = find_first_packet_type(output, OBS_ENCODER_VIDEO, 0);
if (!video)
output->received_video = false;
if (!audio || !video) {
for (size_t i = 0; i < audio_mixes; i++) {
audio[i] = find_first_packet_type(output, OBS_ENCODER_AUDIO, i);
if (!audio[i]) {
output->received_audio = false;
return false;
}
}
if (!video) {
return false;
}
/* get new offsets */
output->video_offset = video->dts;
output->audio_offset = audio->dts;
for (size_t i = 0; i < audio_mixes; i++)
output->audio_offsets[i] = audio[i]->dts;
/* subtract offsets from highest TS offset variables */
output->highest_audio_ts -= audio->dts_usec;
output->highest_audio_ts -= audio[0]->dts_usec;
output->highest_video_ts -= video->dts_usec;
/* apply new offsets to all existing packet DTS/PTS values */
......@@ -763,6 +888,9 @@ static void interleave_packets(void *data, struct encoder_packet *packet)
struct encoder_packet out;
bool was_started;
if (packet->type == OBS_ENCODER_AUDIO)
packet->track_idx = get_track_index(output, packet);
pthread_mutex_lock(&output->interleaved_mutex);
was_started = output->received_audio && output->received_video;
......@@ -792,6 +920,10 @@ static void interleave_packets(void *data, struct encoder_packet *packet)
static void default_encoded_callback(void *param, struct encoder_packet *packet)
{
struct obs_output *output = param;
if (packet->type == OBS_ENCODER_AUDIO)
packet->track_idx = get_track_index(output, packet);
if (!output->stopped)
output->info.encoded_packet(output->context.data, packet);
......@@ -807,17 +939,33 @@ static void default_raw_video_callback(void *param, struct video_data *frame)
output->total_frames++;
}
static void default_raw_audio_callback(void *param, struct audio_data *frames)
static void default_raw_audio_callback(void *param, size_t mix_idx,
struct audio_data *frames)
{
struct obs_output *output = param;
if (!output->stopped)
output->info.raw_audio(output->context.data, frames);
UNUSED_PARAMETER(mix_idx);
}
typedef void (*encoded_callback_t)(void *data, struct encoder_packet *packet);
static inline void start_audio_encoders(struct obs_output *output,
encoded_callback_t encoded_callback)
{
size_t num_mixes = num_audio_mixes(output);
for (size_t i = 0; i < num_mixes; i++) {
obs_encoder_start(output->audio_encoders[i],
encoded_callback, output);
}
}
static void hook_data_capture(struct obs_output *output, bool encoded,
bool has_video, bool has_audio)
{
void (*encoded_callback)(void *data, struct encoder_packet *packet);
encoded_callback_t encoded_callback;
if (encoded) {
output->received_audio = false;
......@@ -825,7 +973,10 @@ static void hook_data_capture(struct obs_output *output, bool encoded,
output->highest_audio_ts = 0;
output->highest_video_ts = 0;
output->video_offset = 0;
output->audio_offset = 0;
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++)
output->audio_offsets[0] = 0;
free_packets(output);
encoded_callback = (has_video && has_audio) ?
......@@ -835,15 +986,14 @@ static void hook_data_capture(struct obs_output *output, bool encoded,
obs_encoder_start(output->video_encoder,
encoded_callback, output);
if (has_audio)
obs_encoder_start(output->audio_encoder,
encoded_callback, output);
start_audio_encoders(output, encoded_callback);
} else {
if (has_video)
video_output_connect(output->video,
get_video_conversion(output),
default_raw_video_callback, output);
if (has_audio)
audio_output_connect(output->audio,
audio_output_connect(output->audio, output->mixer_idx,
get_audio_conversion(output),
default_raw_audio_callback, output);
}
......@@ -912,9 +1062,38 @@ bool obs_output_can_begin_data_capture(const obs_output_t *output,
has_service);
}
static inline bool initialize_audio_encoders(obs_output_t *output,
size_t num_mixes)
{
for (size_t i = 0; i < num_mixes; i++) {
if (!obs_encoder_initialize(output->audio_encoders[i])) {
return false;
}
}
return true;
}
static inline bool pair_encoders(obs_output_t *output, size_t num_mixes)
{
if (num_mixes == 1 &&
!output->audio_encoders[0]->active &&
!output->video_encoder->active) {
output->audio_encoders[0]->wait_for_video = true;
output->audio_encoders[0]->paired_encoder =
output->video_encoder;
output->video_encoder->paired_encoder =
output->audio_encoders[0];
}
return true;
}
bool obs_output_initialize_encoders(obs_output_t *output, uint32_t flags)
{
bool encoded, has_video, has_audio, has_service;
size_t num_mixes = num_audio_mixes(output);
if (!output) return false;
if (output->active) return false;
......@@ -928,14 +1107,13 @@ bool obs_output_initialize_encoders(obs_output_t *output, uint32_t flags)
return false;
if (has_video && !obs_encoder_initialize(output->video_encoder))
return false;
if (has_audio && !obs_encoder_initialize(output->audio_encoder))
if (has_audio && !initialize_audio_encoders(output, num_mixes))
return false;
if (has_video && has_audio && !output->audio_encoder->active &&
!output->video_encoder->active) {
output->audio_encoder->wait_for_video = true;
output->video_encoder->paired_encoder = output->audio_encoder;
output->audio_encoder->paired_encoder = output->video_encoder;
if (has_video && has_audio) {
if (!pair_encoders(output, num_mixes)) {
return false;
}
}
return true;
......@@ -974,10 +1152,21 @@ bool obs_output_begin_data_capture(obs_output_t *output, uint32_t flags)
return true;
}
static inline void stop_audio_encoders(obs_output_t *output,
encoded_callback_t encoded_callback)
{
size_t num_mixes = num_audio_mixes(output);
for (size_t i = 0; i < num_mixes; i++) {
obs_encoder_stop(output->audio_encoders[i],
encoded_callback, output);
}
}
void obs_output_end_data_capture(obs_output_t *output)
{
bool encoded, has_video, has_audio, has_service;
void (*encoded_callback)(void *data, struct encoder_packet *packet);
encoded_callback_t encoded_callback;
if (!output) return;
if (!output->active) return;
......@@ -993,16 +1182,15 @@ void obs_output_end_data_capture(obs_output_t *output)
obs_encoder_stop(output->video_encoder,
encoded_callback, output);
if (has_audio)
obs_encoder_stop(output->audio_encoder,
encoded_callback, output);
stop_audio_encoders(output, encoded_callback);
} else {
if (has_video)
video_output_disconnect(output->video,
default_raw_video_callback, output);
if (has_audio)
audio_output_disconnect(output->audio,
output->info.raw_audio,
output->context.data);
output->mixer_idx,
default_raw_audio_callback, output);
}
if (has_service)
......
......@@ -26,6 +26,7 @@ extern "C" {
#define OBS_OUTPUT_AV (OBS_OUTPUT_VIDEO | OBS_OUTPUT_AUDIO)
#define OBS_OUTPUT_ENCODED (1<<2)
#define OBS_OUTPUT_SERVICE (1<<3)
#define OBS_OUTPUT_MULTI_TRACK (1<<4)
struct encoder_packet;
......
......@@ -63,6 +63,7 @@ struct obs_service_info {
const char *(*get_username)(void *data);
const char *(*get_password)(void *data);
bool (*supports_multitrack)(void *data);
/* TODO: more stuff later */
};
......
......@@ -84,6 +84,7 @@ static const char *source_signals[] = {
"void update_flags(ptr source, int flags)",
"void audio_sync(ptr source, int out int offset)",
"void audio_data(ptr source, ptr data)",
"void audio_mixers(ptr source, in out int mixers)",
NULL
};
......@@ -126,7 +127,7 @@ bool obs_source_init(struct obs_source *source,
if (info && info->output_flags & OBS_SOURCE_AUDIO) {
source->audio_line = audio_output_create_line(obs->audio.audio,
source->context.name);
source->context.name, 0xF);
if (!source->audio_line) {
blog(LOG_ERROR, "Failed to create audio line for "
"source '%s'", source->context.name);
......@@ -2167,6 +2168,37 @@ uint32_t obs_source_get_flags(const obs_source_t *source)
return source ? source->flags : 0;
}
void obs_source_set_audio_mixers(obs_source_t *source, uint32_t mixers)
{
struct calldata data = {0};
uint32_t cur_mixers;
if (!source) return;
if ((source->info.output_flags & OBS_SOURCE_AUDIO) == 0) return;
cur_mixers = audio_line_get_mixers(source->audio_line);
if (cur_mixers == mixers)
return;
calldata_set_ptr(&data, "source", source);
calldata_set_int(&data, "mixers", mixers);
signal_handler_signal(source->context.signals, "audio_mixers", &data);
mixers = (uint32_t)calldata_int(&data, "mixers");
calldata_free(&data);
audio_line_set_mixers(source->audio_line, mixers);
}
uint32_t obs_source_get_audio_mixers(const obs_source_t *source)
{
if (!source) return 0;
if ((source->info.output_flags & OBS_SOURCE_AUDIO) == 0) return 0;
return audio_line_get_mixers(source->audio_line);
}
void obs_source_draw_set_color_matrix(const struct matrix4 *color_matrix,
const struct vec3 *color_range_min,
const struct vec3 *color_range_max)
......
......@@ -1231,6 +1231,7 @@ obs_source_t *obs_load_source(obs_data_t *source_data)
double volume;
int64_t sync;
uint32_t flags;
uint32_t mixers;
source = obs_source_create(OBS_SOURCE_TYPE_INPUT, id, name, settings);
......@@ -1241,6 +1242,10 @@ obs_source_t *obs_load_source(obs_data_t *source_data)
sync = obs_data_get_int(source_data, "sync");
obs_source_set_sync_offset(source, sync);
obs_data_set_default_int(source_data, "mixers", 0xF);
mixers = (uint32_t)obs_data_get_int(source_data, "mixers");
obs_source_set_audio_mixers(source, mixers);
obs_data_set_default_int(source_data, "flags", source->default_flags);
flags = (uint32_t)obs_data_get_int(source_data, "flags");
obs_source_set_flags(source, flags);
......@@ -1283,6 +1288,7 @@ obs_data_t *obs_save_source(obs_source_t *source)
obs_data_t *source_data = obs_data_create();
obs_data_t *settings = obs_source_get_settings(source);
float volume = obs_source_get_volume(source);
uint32_t mixers = obs_source_get_audio_mixers(source);
int64_t sync = obs_source_get_sync_offset(source);
uint32_t flags = obs_source_get_flags(source);
const char *name = obs_source_get_name(source);
......@@ -1293,6 +1299,7 @@ obs_data_t *obs_save_source(obs_source_t *source)
obs_data_set_string(source_data, "name", name);
obs_data_set_string(source_data, "id", id);
obs_data_set_obj (source_data, "settings", settings);
obs_data_set_int (source_data, "mixers", mixers);
obs_data_set_int (source_data, "sync", sync);
obs_data_set_int (source_data, "flags", flags);
obs_data_set_double(source_data, "volume", volume);
......
......@@ -771,6 +771,15 @@ EXPORT void obs_source_set_flags(obs_source_t *source, uint32_t flags);
/** Gets source flags. */
EXPORT uint32_t obs_source_get_flags(const obs_source_t *source);
/**
* Sets audio mixer flags. These flags are used to specify which mixers
* the source's audio should be applied to.
*/
EXPORT void obs_source_set_audio_mixers(obs_source_t *source, uint32_t mixers);
/** Gets audio mixer flags */
EXPORT uint32_t obs_source_get_audio_mixers(const obs_source_t *source);
/* ------------------------------------------------------------------------- */
/* Functions used by sources */
......@@ -1033,6 +1042,12 @@ EXPORT video_t *obs_output_video(const obs_output_t *output);
/** Returns the audio media context associated with this output */
EXPORT audio_t *obs_output_audio(const obs_output_t *output);
/** Sets the current audio mixer for non-encoded outputs */
EXPORT void obs_output_set_mixer(obs_output_t *output, size_t mixer_idx);
/** Gets the current audio mixer for non-encoded outputs */
EXPORT size_t obs_output_get_mixer(const obs_output_t *output);
/**
* Sets the current video encoder associated with this output,
* required for encoded outputs
......@@ -1042,16 +1057,27 @@ EXPORT void obs_output_set_video_encoder(obs_output_t *output,
/**
* Sets the current audio encoder associated with this output,
* required for encoded outputs
* required for encoded outputs.
*
* The idx parameter specifies the audio encoder index to set the encoder to.
* Only used with outputs that have multiple audio outputs (RTMP typically),
* otherwise the parameter is ignored.
*/
EXPORT void obs_output_set_audio_encoder(obs_output_t *output,
obs_encoder_t *encoder);
obs_encoder_t *encoder, size_t idx);
/** Returns the current video encoder associated with this output */
EXPORT obs_encoder_t *obs_output_get_video_encoder(const obs_output_t *output);
/** Returns the current audio encoder associated with this output */
EXPORT obs_encoder_t *obs_output_get_audio_encoder(const obs_output_t *output);
/**
* Returns the current audio encoder associated with this output
*
* The idx parameter specifies the audio encoder index. Only used with
* outputs that have multiple audio outputs, otherwise the parameter is
* ignored.
*/
EXPORT obs_encoder_t *obs_output_get_audio_encoder(const obs_output_t *output,
size_t idx);
/** Sets the current service associated with this output. */
EXPORT void obs_output_set_service(obs_output_t *output,
......@@ -1154,10 +1180,11 @@ EXPORT obs_encoder_t *obs_video_encoder_create(const char *id, const char *name,
* @param id Audio Encoder ID
* @param name Name to assign to this context
* @param settings Settings
* @param mixer_idx Index of the mixer to use for this audio encoder
* @return The video encoder context, or NULL if failed or not found.
*/
EXPORT obs_encoder_t *obs_audio_encoder_create(const char *id, const char *name,
obs_data_t *settings);
obs_data_t *settings, size_t mixer_idx);
/** Destroys an encoder context */
EXPORT void obs_encoder_destroy(obs_encoder_t *encoder);
......
......@@ -411,11 +411,11 @@ bool OBSBasic::InitEncoders()
if (!x264)
return false;
aac = obs_audio_encoder_create("libfdk_aac", "default_aac", nullptr);
aac = obs_audio_encoder_create("libfdk_aac", "default_aac", nullptr, 0);
if (!aac)
aac = obs_audio_encoder_create("ffmpeg_aac", "default_aac",
nullptr);
nullptr, 0);
if (!aac)
return false;
......@@ -2291,7 +2291,7 @@ void OBSBasic::on_streamButton_clicked()
SetupEncoders();
obs_output_set_video_encoder(streamOutput, x264);
obs_output_set_audio_encoder(streamOutput, aac);
obs_output_set_audio_encoder(streamOutput, aac, 0);
obs_output_set_service(streamOutput, service);
bool reconnect = config_get_bool(basicConfig, "SimpleOutput",
......@@ -2350,7 +2350,7 @@ void OBSBasic::on_recordButton_clicked()
SetupEncoders();
obs_output_set_video_encoder(fileOutput, x264);
obs_output_set_audio_encoder(fileOutput, aac);
obs_output_set_audio_encoder(fileOutput, aac, 0);
obs_data_t *settings = obs_data_create();
obs_data_set_string(settings, "path", strPath.c_str());
......
......@@ -56,11 +56,11 @@ void write_file_info(FILE *file, int64_t duration_ms, int64_t size)
fwrite(buf, 1, enc - buf, file);
}
static void build_flv_meta_data(obs_output_t *context,
uint8_t **output, size_t *size)
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);
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];
......@@ -68,22 +68,29 @@ static void build_flv_meta_data(obs_output_t *context,
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, 14);
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);
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));
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));
......@@ -119,20 +126,25 @@ static void build_flv_meta_data(obs_output_t *context,
*size = enc-buf;
*output = bmemdup(buf, *size);
return true;
}
void flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size,
bool write_header)
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;
uint8_t *meta_data = NULL;
size_t meta_data_size;
uint32_t start_pos;
array_output_serializer_init(&s, &data);
build_flv_meta_data(context, &meta_data, &meta_data_size);
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);
......@@ -158,6 +170,7 @@ void flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size,
*size = data.bytes.num;
bfree(meta_data);
return true;
}
#ifdef DEBUG_TIMESTAMPS
......
......@@ -28,7 +28,7 @@ static uint32_t get_ms_time(struct encoder_packet *packet, int64_t val)
extern void write_file_info(FILE *file, int64_t duration_ms, int64_t size);
extern void flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size,
bool write_header);
extern bool flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size,
bool write_header, size_t audio_idx);
extern void flv_packet_mux(struct encoder_packet *packet,
uint8_t **output, size_t *size, bool is_header);
......@@ -107,7 +107,7 @@ 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);
flv_meta_data(stream->output, &meta_data, &meta_data_size, true, 0);
fwrite(meta_data, 1, meta_data_size, stream->file);
bfree(meta_data);
}
......@@ -115,7 +115,7 @@ static void write_meta_data(struct flv_output *stream)
static void write_audio_header(struct flv_output *stream)
{
obs_output_t *context = stream->output;
obs_encoder_t *aencoder = obs_output_get_audio_encoder(context);
obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, 0);
uint8_t *header;
struct encoder_packet packet = {
......
......@@ -272,7 +272,7 @@ static void send_meta_data(struct rtmp_stream *stream)
uint8_t *meta_data;
size_t meta_data_size;
flv_meta_data(stream->output, &meta_data, &meta_data_size, false);
flv_meta_data(stream->output, &meta_data, &meta_data_size, false, 0);
RTMP_Write(&stream->rtmp, (char*)meta_data, (int)meta_data_size);
bfree(meta_data);
}
......@@ -280,7 +280,7 @@ static void send_meta_data(struct rtmp_stream *stream)
static void send_audio_header(struct rtmp_stream *stream)
{
obs_output_t *context = stream->output;
obs_encoder_t *aencoder = obs_output_get_audio_encoder(context);
obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, 0);
uint8_t *header;
struct encoder_packet packet = {
......
......@@ -281,7 +281,7 @@ static void initialize_output(struct rtmp_common *service, obs_output_t *output,
json_t *root)
{
obs_encoder_t *video_encoder = obs_output_get_video_encoder(output);
obs_encoder_t *audio_encoder = obs_output_get_audio_encoder(output);
obs_encoder_t *audio_encoder = obs_output_get_audio_encoder(output, 0);
json_t *json_service = find_service(root, service->service);
json_t *recommended;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册