mirror of
https://github.com/obsproject/obs-studio
synced 2024-10-23 16:12:01 +00:00
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:
parent
509d7f49e1
commit
73704f20db
|
@ -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;
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
15
libobs/obs.c
15
libobs/obs.c
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue