libobs: Add audio lines

Adds the "audio_line" internal source type as a bare source type for the
sole purpose of outputting audio, and the obs_source_info::audio_mix
callback which allows mixing of those audio lines, which is then treated
as normal audio for the source.  Audio line objects should be added as
sub-sources when multiple audio lines from a single source are needed,
then mixed together with the audio_mix callback.

The difference between the new obs_source_info::audio_mix callback and
obs_source_info::audio_render is that obs_source_info::audio_mix (along
with the audio_line source) are only one track, and it outputs audio to
the source automatically via obs_source_output_audio() when the call
completes.  This allows the mixed audio to be treated like a normal
source's audio, in that you can filter it, change its volume, or monitor
it.

This change was necessary because the CEF (used with the browser source)
outputs multiple audio streams at once to a single browser source, so
it's the program's responsibility to mix those streams together itself.
This commit is contained in:
jp9000 2019-08-21 14:35:40 -07:00
parent 509d7f49e1
commit 73704f20db
5 changed files with 92 additions and 4 deletions

View file

@ -611,6 +611,7 @@ struct obs_source {
size_t last_audio_input_buf_size;
DARRAY(struct audio_action) audio_actions;
float *audio_output_buf[MAX_AUDIO_MIXES][MAX_AUDIO_CHANNELS];
float *audio_mix_buf[MAX_AUDIO_CHANNELS];
struct resample_info sample_info;
audio_resampler_t *resampler;
pthread_mutex_t audio_actions_mutex;

View file

@ -627,8 +627,6 @@ void obs_register_source_s(const struct obs_source_info *info, size_t size)
#define CHECK_REQUIRED_VAL_(info, val, func) \
CHECK_REQUIRED_VAL(struct obs_source_info, info, val, func)
CHECK_REQUIRED_VAL_(info, get_name, obs_register_source);
CHECK_REQUIRED_VAL_(info, create, obs_register_source);
CHECK_REQUIRED_VAL_(info, destroy, obs_register_source);
if (info->type != OBS_SOURCE_TYPE_FILTER &&
info->type != OBS_SOURCE_TYPE_TRANSITION &&

View file

@ -115,6 +115,16 @@ static void allocate_audio_output_buffer(struct obs_source *source)
}
}
static void allocate_audio_mix_buffer(struct obs_source *source)
{
size_t size = sizeof(float) * AUDIO_OUTPUT_FRAMES * MAX_AUDIO_CHANNELS;
float *ptr = bzalloc(size);
for (size_t i = 0; i < MAX_AUDIO_CHANNELS; i++) {
source->audio_mix_buf[i] = ptr + AUDIO_OUTPUT_FRAMES * i;
}
}
static inline bool is_async_video_source(const struct obs_source *source)
{
return (source->info.output_flags & OBS_SOURCE_ASYNC_VIDEO) ==
@ -167,6 +177,8 @@ bool obs_source_init(struct obs_source *source)
if (is_audio_source(source) || is_composite_source(source))
allocate_audio_output_buffer(source);
if (source->info.audio_mix)
allocate_audio_mix_buffer(source);
if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) {
if (!obs_transition_init(source))
@ -341,10 +353,10 @@ static obs_source_t *obs_source_create_internal(const char *id,
/* allow the source to be created even if creation fails so that the
* user's data doesn't become lost */
if (info)
if (info && info->create)
source->context.data =
info->create(source->context.settings, source);
if (!source->context.data)
if (info->create && !source->context.data)
blog(LOG_ERROR, "Failed to create source '%s'!", name);
blog(LOG_DEBUG, "%ssource '%s' (%s) created", private ? "private " : "",
@ -592,6 +604,7 @@ void obs_source_destroy(struct obs_source *source)
circlebuf_free(&source->audio_input_buf[i]);
audio_resampler_destroy(source->resampler);
bfree(source->audio_output_buf[0][0]);
bfree(source->audio_mix_buf[0]);
obs_source_frame_destroy(source->async_preload_frame);
@ -4341,10 +4354,47 @@ static void custom_audio_render(obs_source_t *source, uint32_t mixers,
apply_audio_volume(source, mixers, channels, sample_rate);
}
static void audio_submix(obs_source_t *source, uint32_t mixers, size_t channels,
size_t sample_rate)
{
struct audio_output_data audio_data;
struct obs_source_audio audio = {0};
bool success;
uint64_t ts;
for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
for (size_t ch = 0; ch < channels; ch++) {
audio_data.data[ch] = source->audio_mix_buf[ch];
}
memset(source->audio_mix_buf[0], 0,
sizeof(float) * AUDIO_OUTPUT_FRAMES * channels);
}
success = source->info.audio_mix(source->context.data, &ts, &audio_data,
channels, sample_rate);
if (!success)
return;
for (size_t i = 0; i < channels; i++)
audio.data[i] = (const uint8_t *)audio_data.data[i];
audio.samples_per_sec = (uint32_t)sample_rate;
audio.frames = AUDIO_OUTPUT_FRAMES;
audio.format = AUDIO_FORMAT_FLOAT_PLANAR;
audio.speakers = (enum speaker_layout)channels;
audio.timestamp = ts;
obs_source_output_audio(source, &audio);
}
static inline void process_audio_source_tick(obs_source_t *source,
uint32_t mixers, size_t channels,
size_t sample_rate, size_t size)
{
bool audio_submix = !!(source->info.output_flags & OBS_SOURCE_SUBMIX);
pthread_mutex_lock(&source->audio_buf_mutex);
if (source->audio_input_buf[0].size < size) {
@ -4362,6 +4412,14 @@ static inline void process_audio_source_tick(obs_source_t *source,
for (size_t mix = 1; mix < MAX_AUDIO_MIXES; mix++) {
uint32_t mix_and_val = (1 << mix);
if (audio_submix) {
if (mix > 1)
break;
mixers = 1;
mix_and_val = 1;
}
if ((source->audio_mixers & mix_and_val) == 0 ||
(mixers & mix_and_val) == 0) {
memset(source->audio_output_buf[mix][0], 0,
@ -4374,6 +4432,11 @@ static inline void process_audio_source_tick(obs_source_t *source,
source->audio_output_buf[0][ch], size);
}
if (audio_submix) {
source->audio_pending = false;
return;
}
if ((source->audio_mixers & 1) == 0 || (mixers & 1) == 0)
memset(source->audio_output_buf[0][0], 0, size * channels);
@ -4394,6 +4457,10 @@ void obs_source_audio_render(obs_source_t *source, uint32_t mixers,
return;
}
if (source->info.audio_mix) {
audio_submix(source, mixers, channels, sample_rate);
}
if (!source->audio_ts) {
source->audio_pending = true;
return;

View file

@ -145,6 +145,9 @@ enum obs_balance_type {
*/
#define OBS_SOURCE_MONITOR_BY_DEFAULT (1 << 11)
/** Used internally for audio submixing */
#define OBS_SOURCE_SUBMIX (1 << 12)
/** @} */
typedef void (*obs_source_enum_proc_t)(obs_source_t *parent,
@ -464,6 +467,10 @@ struct obs_source_info {
* @return The properties data
*/
obs_properties_t *(*get_properties2)(void *data, void *type_data);
bool (*audio_mix)(void *data, uint64_t *ts_out,
struct audio_output_data *audio_output,
size_t channels, size_t sample_rate);
};
EXPORT void obs_register_source_s(const struct obs_source_info *info,

View file

@ -814,6 +814,20 @@ static inline void obs_free_hotkeys(void)
extern const struct obs_source_info scene_info;
extern const struct obs_source_info group_info;
static const char *submix_name(void *unused)
{
UNUSED_PARAMETER(unused);
return "Audio line (internal use only)";
}
const struct obs_source_info audio_line_info = {
.id = "audio_line",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_CAP_DISABLED |
OBS_SOURCE_SUBMIX,
.get_name = submix_name,
};
extern void log_system_info(void);
static bool obs_init(const char *locale, const char *module_config_path,
@ -845,6 +859,7 @@ static bool obs_init(const char *locale, const char *module_config_path,
obs->locale = bstrdup(locale);
obs_register_source(&scene_info);
obs_register_source(&group_info);
obs_register_source(&audio_line_info);
add_default_module_paths();
return true;
}