diff --git a/libobs/media-io/audio-io.c b/libobs/media-io/audio-io.c index 5e9e7724403b669ca4c6ac454f8cd6176c28d167..88f6b94c1a9e0cc4ac74b70dd3155c143bbd907b 100644 --- a/libobs/media-io/audio-io.c +++ b/libobs/media-io/audio-io.c @@ -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; +} diff --git a/libobs/media-io/audio-io.h b/libobs/media-io/audio-io.h index e1b4a8dd8ee0a91fc3f3624016a4de0cea7b5539..d140ef418af1f0963f74b6a351e3605eb7f972f7 100644 --- a/libobs/media-io/audio-io.h +++ b/libobs/media-io/audio-io.h @@ -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); diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index 885181fe37edb6d34feea92e7d8ae76d14b4cc65..eb1b835a0cf37fd9b360a5cea2e1e78b470e8f22 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -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, diff --git a/libobs/obs-encoder.h b/libobs/obs-encoder.h index 683144d11405110f46b5d42a3177824e529fd933..3cbc1126745152efbad49bbe68b93daf93a318e5 100644 --- a/libobs/obs-encoder.h +++ b/libobs/obs-encoder.h @@ -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 */ diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index eff2494b40909bfa2e8d095942024674c4e8fc7f..63a89c680fb2f96d311d1a00009e50384811ea35 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -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; diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 61ce663040685fb6570aeb45d1ea0970d348130c..13095913ac60abbcb8f73bde9c3ae515d75bc37c 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -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) diff --git a/libobs/obs-output.h b/libobs/obs-output.h index 013f7c29a7780ee1133b6e863d083b851f3e7b52..093aef45e66600b41255e583577d15a68a7db5ab 100644 --- a/libobs/obs-output.h +++ b/libobs/obs-output.h @@ -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; diff --git a/libobs/obs-service.h b/libobs/obs-service.h index 9c1d16f57005a0732baf23d68fd0f86cc5b1b3c6..976575f79e4f28a6e825b360ed90f0b29c09eba3 100644 --- a/libobs/obs-service.h +++ b/libobs/obs-service.h @@ -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 */ }; diff --git a/libobs/obs-source.c b/libobs/obs-source.c index a7fab0bb7b865c89e6ef08a4daa905fa7023c49f..95c5e41f8e02c73ecf0f7943d42c453e5a9c902e 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -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) diff --git a/libobs/obs.c b/libobs/obs.c index e85bc5bd9bcfca17a866ce385edef5d71b24574c..6d9fa5304b0650975a04ef8075ecce551602a99d 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -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); diff --git a/libobs/obs.h b/libobs/obs.h index e6f64886c8bfb16916bd653b3d630fe5fb176b16..2398b93ce693a2c99b9d3301bafd88fb500dd97f 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -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); diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index b40fb09c328231a241bcb546afe2eb010a0f6350..6f7ccadea4243e498211974ec58f47b24c31ef0b 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -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()); diff --git a/plugins/obs-outputs/flv-mux.c b/plugins/obs-outputs/flv-mux.c index 984bd2fe56f227ed3dcdb56c268d84783b603e66..154c98d60a8c4eec3ff0b9fcc793582ec2085010 100644 --- a/plugins/obs-outputs/flv-mux.c +++ b/plugins/obs-outputs/flv-mux.c @@ -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 diff --git a/plugins/obs-outputs/flv-mux.h b/plugins/obs-outputs/flv-mux.h index 7e0da0f37d0a2ffb351e66de69f4832270aec680..e4c655e5a6601f8a539ac580c401f434b9bfe31f 100644 --- a/plugins/obs-outputs/flv-mux.h +++ b/plugins/obs-outputs/flv-mux.h @@ -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); diff --git a/plugins/obs-outputs/flv-output.c b/plugins/obs-outputs/flv-output.c index 9a7748a05c116424f025b404029eefd217d2a1ab..2608d223e66058977611b23fe54e2a4fa3976218 100644 --- a/plugins/obs-outputs/flv-output.c +++ b/plugins/obs-outputs/flv-output.c @@ -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 = { diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index edad9fd016966a6b594954843a601b5707be3df7..d1f93cf0dd255c7d73d18f8ef848ed278be566fc 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -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 = { diff --git a/plugins/rtmp-services/rtmp-common.c b/plugins/rtmp-services/rtmp-common.c index 8ee91f4717ae96da4957ee1f767260c901dd6c67..ac7ec3b03b02c3f583a518f56a2aef4c0313ddcc 100644 --- a/plugins/rtmp-services/rtmp-common.c +++ b/plugins/rtmp-services/rtmp-common.c @@ -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;