libobs: Refactor obs-output encoded use of mixes

There was quite a bit of conflated usage of mixes (which refers
to raw audio) and encoder counts. This fully separates the two
and makes a distinct separation when iterating over mixes vs
encoders.
This commit is contained in:
John Bradley 2023-01-17 00:48:34 -06:00 committed by tt2468
parent d70171daa6
commit 61284cf9ba
3 changed files with 108 additions and 113 deletions

View file

@ -1024,7 +1024,7 @@ struct obs_output {
volatile bool data_active; volatile bool data_active;
volatile bool end_data_capture_thread_active; volatile bool end_data_capture_thread_active;
int64_t video_offset; int64_t video_offset;
int64_t audio_offsets[MAX_AUDIO_MIXES]; int64_t audio_offsets[MAX_OUTPUT_AUDIO_ENCODERS];
int64_t highest_audio_ts; int64_t highest_audio_ts;
int64_t highest_video_ts; int64_t highest_video_ts;
pthread_t end_data_capture_thread; pthread_t end_data_capture_thread;
@ -1054,7 +1054,7 @@ struct obs_output {
video_t *video; video_t *video;
audio_t *audio; audio_t *audio;
obs_encoder_t *video_encoder; obs_encoder_t *video_encoder;
obs_encoder_t *audio_encoders[MAX_AUDIO_MIXES]; obs_encoder_t *audio_encoders[MAX_OUTPUT_AUDIO_ENCODERS];
obs_service_t *service; obs_service_t *service;
size_t mixer_mask; size_t mixer_mask;

View file

@ -180,7 +180,7 @@ static inline void free_packets(struct obs_output *output)
da_free(output->interleaved_packets); da_free(output->interleaved_packets);
} }
static inline void clear_audio_buffers(obs_output_t *output) static inline void clear_raw_audio_buffers(obs_output_t *output)
{ {
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
for (size_t j = 0; j < MAX_AV_PLANES; j++) { for (size_t j = 0; j < MAX_AV_PLANES; j++) {
@ -215,14 +215,14 @@ void obs_output_destroy(obs_output_t *output)
output); output);
} }
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (output->audio_encoders[i]) { if (output->audio_encoders[i]) {
obs_encoder_remove_output( obs_encoder_remove_output(
output->audio_encoders[i], output); output->audio_encoders[i], output);
} }
} }
clear_audio_buffers(output); clear_raw_audio_buffers(output);
os_event_destroy(output->stopping_event); os_event_destroy(output->stopping_event);
pthread_mutex_destroy(&output->pause.mutex); pthread_mutex_destroy(&output->pause.mutex);
@ -572,16 +572,16 @@ static inline bool pause_can_stop(struct pause_data *pause)
static bool obs_encoded_output_pause(obs_output_t *output, bool pause) static bool obs_encoded_output_pause(obs_output_t *output, bool pause)
{ {
obs_encoder_t *venc; obs_encoder_t *venc;
obs_encoder_t *aenc[MAX_AUDIO_MIXES]; obs_encoder_t *aenc[MAX_OUTPUT_AUDIO_ENCODERS];
uint64_t closest_v_ts; uint64_t closest_v_ts;
bool success = false; bool success = false;
venc = output->video_encoder; venc = output->video_encoder;
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++)
aenc[i] = output->audio_encoders[i]; aenc[i] = output->audio_encoders[i];
pthread_mutex_lock(&venc->pause.mutex); pthread_mutex_lock(&venc->pause.mutex);
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (aenc[i]) { if (aenc[i]) {
pthread_mutex_lock(&aenc[i]->pause.mutex); pthread_mutex_lock(&aenc[i]->pause.mutex);
} }
@ -595,7 +595,7 @@ static bool obs_encoded_output_pause(obs_output_t *output, bool pause)
if (!pause_can_start(&venc->pause)) { if (!pause_can_start(&venc->pause)) {
goto fail; goto fail;
} }
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (aenc[i] && !pause_can_start(&aenc[i]->pause)) { if (aenc[i] && !pause_can_start(&aenc[i]->pause)) {
goto fail; goto fail;
} }
@ -604,7 +604,7 @@ static bool obs_encoded_output_pause(obs_output_t *output, bool pause)
os_atomic_set_bool(&venc->paused, true); os_atomic_set_bool(&venc->paused, true);
venc->pause.ts_start = closest_v_ts; venc->pause.ts_start = closest_v_ts;
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (aenc[i]) { if (aenc[i]) {
os_atomic_set_bool(&aenc[i]->paused, true); os_atomic_set_bool(&aenc[i]->paused, true);
aenc[i]->pause.ts_start = closest_v_ts; aenc[i]->pause.ts_start = closest_v_ts;
@ -614,7 +614,7 @@ static bool obs_encoded_output_pause(obs_output_t *output, bool pause)
if (!pause_can_stop(&venc->pause)) { if (!pause_can_stop(&venc->pause)) {
goto fail; goto fail;
} }
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (aenc[i] && !pause_can_stop(&aenc[i]->pause)) { if (aenc[i] && !pause_can_stop(&aenc[i]->pause)) {
goto fail; goto fail;
} }
@ -623,7 +623,7 @@ static bool obs_encoded_output_pause(obs_output_t *output, bool pause)
os_atomic_set_bool(&venc->paused, false); os_atomic_set_bool(&venc->paused, false);
end_pause(&venc->pause, closest_v_ts); end_pause(&venc->pause, closest_v_ts);
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (aenc[i]) { if (aenc[i]) {
os_atomic_set_bool(&aenc[i]->paused, false); os_atomic_set_bool(&aenc[i]->paused, false);
end_pause(&aenc[i]->pause, closest_v_ts); end_pause(&aenc[i]->pause, closest_v_ts);
@ -636,7 +636,7 @@ static bool obs_encoded_output_pause(obs_output_t *output, bool pause)
success = true; success = true;
fail: fail:
for (size_t i = MAX_AUDIO_MIXES; i > 0; i--) { for (size_t i = MAX_OUTPUT_AUDIO_ENCODERS; i > 0; i--) {
if (aenc[i - 1]) { if (aenc[i - 1]) {
pthread_mutex_unlock(&aenc[i - 1]->pause.mutex); pthread_mutex_unlock(&aenc[i - 1]->pause.mutex);
} }
@ -801,8 +801,9 @@ void obs_output_remove_encoder_internal(struct obs_output *output,
if (output->video_encoder == encoder) { if (output->video_encoder == encoder) {
output->video_encoder = NULL; output->video_encoder = NULL;
} else { } else {
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (output->audio_encoders[i] == encoder) obs_encoder_t *audio = output->audio_encoders[i];
if (audio == encoder)
output->audio_encoders[i] = NULL; output->audio_encoders[i] = NULL;
} }
} }
@ -870,7 +871,7 @@ void obs_output_set_audio_encoder(obs_output_t *output, obs_encoder_t *encoder,
} }
if ((output->info.flags & OBS_OUTPUT_MULTI_TRACK) != 0) { if ((output->info.flags & OBS_OUTPUT_MULTI_TRACK) != 0) {
if (idx >= MAX_AUDIO_MIXES) { if (idx >= MAX_OUTPUT_AUDIO_ENCODERS) {
return; return;
} }
} else { } else {
@ -900,15 +901,8 @@ obs_encoder_t *obs_output_get_audio_encoder(const obs_output_t *output,
if (!obs_output_valid(output, "obs_output_get_audio_encoder")) if (!obs_output_valid(output, "obs_output_get_audio_encoder"))
return NULL; return NULL;
if ((output->info.flags & OBS_OUTPUT_MULTI_TRACK) != 0) { if (idx >= MAX_OUTPUT_AUDIO_ENCODERS)
if (idx >= MAX_AUDIO_MIXES) { return NULL;
return NULL;
}
} else {
if (idx > 0) {
return NULL;
}
}
return output->audio_encoders[idx]; return output->audio_encoders[idx];
} }
@ -1054,42 +1048,18 @@ void obs_output_set_audio_conversion(
output->audio_conversion_set = true; output->audio_conversion_set = true;
} }
static inline size_t num_audio_mixes(const struct obs_output *output)
{
size_t mix_count = 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) static inline bool audio_valid(const struct obs_output *output, bool encoded)
{ {
if (encoded) { if (encoded) {
size_t mix_count = num_audio_mixes(output); for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (!mix_count) if (output->audio_encoders[i]) {
return false; return true;
for (size_t i = 0; i < mix_count; i++) {
if (!output->audio_encoders[i]) {
return false;
} }
} }
} else { return false;
if (!output->audio)
return false;
} }
return true; return output->audio != NULL;
} }
static bool can_begin_data_capture(const struct obs_output *output, static bool can_begin_data_capture(const struct obs_output *output,
@ -1166,10 +1136,10 @@ get_audio_conversion(struct obs_output *output)
static size_t get_track_index(const struct obs_output *output, static size_t get_track_index(const struct obs_output *output,
struct encoder_packet *pkt) struct encoder_packet *pkt)
{ {
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
struct obs_encoder *encoder = output->audio_encoders[i]; struct obs_encoder *encoder = output->audio_encoders[i];
if (pkt->encoder == encoder) if (encoder && pkt->encoder == encoder)
return i; return i;
} }
@ -1420,7 +1390,6 @@ static size_t get_interleaved_start_idx(struct obs_output *output)
static int prune_premature_packets(struct obs_output *output) static int prune_premature_packets(struct obs_output *output)
{ {
size_t audio_mixes = num_audio_mixes(output);
struct encoder_packet *video; struct encoder_packet *video;
int video_idx; int video_idx;
int max_idx; int max_idx;
@ -1438,10 +1407,13 @@ static int prune_premature_packets(struct obs_output *output)
video = &output->interleaved_packets.array[video_idx]; video = &output->interleaved_packets.array[video_idx];
duration_usec = video->timebase_num * 1000000LL / video->timebase_den; duration_usec = video->timebase_num * 1000000LL / video->timebase_den;
for (size_t i = 0; i < audio_mixes; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
struct encoder_packet *audio; struct encoder_packet *audio;
int audio_idx; int audio_idx;
if (!output->audio_encoders[i])
continue;
audio_idx = find_first_packet_type_idx(output, audio_idx = find_first_packet_type_idx(output,
OBS_ENCODER_AUDIO, i); OBS_ENCODER_AUDIO, i);
if (audio_idx == -1) { if (audio_idx == -1) {
@ -1565,18 +1537,20 @@ find_last_packet_type(struct obs_output *output, enum obs_encoder_type type,
static bool get_audio_and_video_packets(struct obs_output *output, static bool get_audio_and_video_packets(struct obs_output *output,
struct encoder_packet **video, struct encoder_packet **video,
struct encoder_packet **audio, struct encoder_packet **audio)
size_t audio_mixes)
{ {
*video = find_first_packet_type(output, OBS_ENCODER_VIDEO, 0); *video = find_first_packet_type(output, OBS_ENCODER_VIDEO, 0);
if (!*video) if (!*video)
output->received_video = false; output->received_video = false;
for (size_t i = 0; i < audio_mixes; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
audio[i] = find_first_packet_type(output, OBS_ENCODER_AUDIO, i); if (output->audio_encoders[i]) {
if (!audio[i]) { audio[i] = find_first_packet_type(output,
output->received_audio = false; OBS_ENCODER_AUDIO, i);
return false; if (!audio[i]) {
output->received_audio = false;
return false;
}
} }
} }
@ -1587,26 +1561,50 @@ static bool get_audio_and_video_packets(struct obs_output *output,
return true; return true;
} }
static bool get_first_audio_encoder_index(const struct obs_output *output,
size_t *index)
{
if (!index)
return false;
for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (output->audio_encoders[i]) {
*index = i;
return true;
}
}
return false;
}
static bool initialize_interleaved_packets(struct obs_output *output) static bool initialize_interleaved_packets(struct obs_output *output)
{ {
struct encoder_packet *video; struct encoder_packet *video;
struct encoder_packet *audio[MAX_AUDIO_MIXES]; struct encoder_packet *audio[MAX_OUTPUT_AUDIO_ENCODERS];
struct encoder_packet *last_audio[MAX_AUDIO_MIXES]; struct encoder_packet *last_audio[MAX_OUTPUT_AUDIO_ENCODERS];
size_t audio_mixes = num_audio_mixes(output);
size_t start_idx; size_t start_idx;
size_t first_audio_idx;
if (!get_audio_and_video_packets(output, &video, audio, audio_mixes)) if (!get_first_audio_encoder_index(output, &first_audio_idx))
return false; return false;
for (size_t i = 0; i < audio_mixes; i++) if (!get_audio_and_video_packets(output, &video, audio))
last_audio[i] = return false;
find_last_packet_type(output, OBS_ENCODER_AUDIO, i);
for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (output->audio_encoders[i]) {
last_audio[i] = find_last_packet_type(
output, OBS_ENCODER_AUDIO, i);
}
}
/* ensure that there is audio past the first video packet */ /* ensure that there is audio past the first video packet */
for (size_t i = 0; i < audio_mixes; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (last_audio[i]->dts_usec < video->dts_usec) { if (output->audio_encoders[i]) {
output->received_audio = false; if (last_audio[i]->dts_usec < video->dts_usec) {
return false; output->received_audio = false;
return false;
}
} }
} }
@ -1614,19 +1612,20 @@ static bool initialize_interleaved_packets(struct obs_output *output)
start_idx = get_interleaved_start_idx(output); start_idx = get_interleaved_start_idx(output);
if (start_idx) { if (start_idx) {
discard_to_idx(output, start_idx); discard_to_idx(output, start_idx);
if (!get_audio_and_video_packets(output, &video, audio, if (!get_audio_and_video_packets(output, &video, audio))
audio_mixes))
return false; return false;
} }
/* get new offsets */ /* get new offsets */
output->video_offset = video->pts; output->video_offset = video->pts;
for (size_t i = 0; i < audio_mixes; i++) for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
output->audio_offsets[i] = audio[i]->dts; if (output->audio_encoders[i]) {
output->audio_offsets[i] = audio[i]->dts;
}
}
#if DEBUG_STARTING_PACKETS == 1 #if DEBUG_STARTING_PACKETS == 1
int64_t v = video->dts_usec; int64_t v = video->dts_usec;
int64_t a = audio[0]->dts_usec; int64_t a = audio[first_audio_idx]->dts_usec;
int64_t diff = v - a; int64_t diff = v - a;
blog(LOG_DEBUG, blog(LOG_DEBUG,
@ -1636,7 +1635,7 @@ static bool initialize_interleaved_packets(struct obs_output *output)
#endif #endif
/* subtract offsets from highest TS offset variables */ /* subtract offsets from highest TS offset variables */
output->highest_audio_ts -= audio[0]->dts_usec; output->highest_audio_ts -= audio[first_audio_idx]->dts_usec;
output->highest_video_ts -= video->dts_usec; output->highest_video_ts -= video->dts_usec;
/* apply new offsets to all existing packet DTS/PTS values */ /* apply new offsets to all existing packet DTS/PTS values */
@ -1885,11 +1884,11 @@ static void default_raw_audio_callback(void *param, size_t mix_idx,
static inline void start_audio_encoders(struct obs_output *output, static inline void start_audio_encoders(struct obs_output *output,
encoded_callback_t encoded_callback) encoded_callback_t encoded_callback)
{ {
size_t num_mixes = num_audio_mixes(output); for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (output->audio_encoders[i]) {
for (size_t i = 0; i < num_mixes; i++) { obs_encoder_start(output->audio_encoders[i],
obs_encoder_start(output->audio_encoders[i], encoded_callback, encoded_callback, output);
output); }
} }
} }
@ -1919,7 +1918,7 @@ static void reset_packet_data(obs_output_t *output)
output->highest_video_ts = 0; output->highest_video_ts = 0;
output->video_offset = 0; output->video_offset = 0;
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++)
output->audio_offsets[i] = 0; output->audio_offsets[i] = 0;
free_packets(output); free_packets(output);
@ -2049,14 +2048,14 @@ bool obs_output_can_begin_data_capture(const obs_output_t *output,
has_service); has_service);
} }
static inline bool initialize_audio_encoders(obs_output_t *output, static inline bool initialize_audio_encoders(obs_output_t *output)
size_t num_mixes)
{ {
for (size_t i = 0; i < num_mixes; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
if (!obs_encoder_initialize(output->audio_encoders[i])) { obs_encoder_t *audio = output->audio_encoders[i];
if (audio && !obs_encoder_initialize(audio)) {
obs_output_set_last_error( obs_output_set_last_error(
output, obs_encoder_get_last_error( output, obs_encoder_get_last_error(audio));
output->audio_encoders[i]));
return false; return false;
} }
} }
@ -2064,10 +2063,9 @@ static inline bool initialize_audio_encoders(obs_output_t *output,
return true; return true;
} }
static inline obs_encoder_t *find_inactive_audio_encoder(obs_output_t *output, static inline obs_encoder_t *find_inactive_audio_encoder(obs_output_t *output)
size_t num_mixes)
{ {
for (size_t i = 0; i < num_mixes; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
struct obs_encoder *audio = output->audio_encoders[i]; struct obs_encoder *audio = output->audio_encoders[i];
if (audio && !audio->active && !audio->paired_encoder) if (audio && !audio->active && !audio->paired_encoder)
@ -2077,11 +2075,10 @@ static inline obs_encoder_t *find_inactive_audio_encoder(obs_output_t *output,
return NULL; return NULL;
} }
static inline void pair_encoders(obs_output_t *output, size_t num_mixes) static inline void pair_encoders(obs_output_t *output)
{ {
struct obs_encoder *video = output->video_encoder; struct obs_encoder *video = output->video_encoder;
struct obs_encoder *audio = struct obs_encoder *audio = find_inactive_audio_encoder(output);
find_inactive_audio_encoder(output, num_mixes);
if (video && audio) { if (video && audio) {
pthread_mutex_lock(&audio->init_mutex); pthread_mutex_lock(&audio->init_mutex);
@ -2103,7 +2100,6 @@ static inline void pair_encoders(obs_output_t *output, size_t num_mixes)
bool obs_output_initialize_encoders(obs_output_t *output, uint32_t flags) bool obs_output_initialize_encoders(obs_output_t *output, uint32_t flags)
{ {
bool encoded, has_video, has_audio, has_service; bool encoded, has_video, has_audio, has_service;
size_t num_mixes = num_audio_mixes(output);
if (!obs_output_valid(output, "obs_output_initialize_encoders")) if (!obs_output_valid(output, "obs_output_initialize_encoders"))
return false; return false;
@ -2122,7 +2118,7 @@ bool obs_output_initialize_encoders(obs_output_t *output, uint32_t flags)
obs_encoder_get_last_error(output->video_encoder)); obs_encoder_get_last_error(output->video_encoder));
return false; return false;
} }
if (has_audio && !initialize_audio_encoders(output, num_mixes)) if (has_audio && !initialize_audio_encoders(output))
return false; return false;
return true; return true;
@ -2150,7 +2146,7 @@ static bool begin_delayed_capture(obs_output_t *output)
static void reset_raw_output(obs_output_t *output) static void reset_raw_output(obs_output_t *output)
{ {
clear_audio_buffers(output); clear_raw_audio_buffers(output);
if (output->audio) { if (output->audio) {
const struct audio_output_info *aoi = const struct audio_output_info *aoi =
@ -2187,7 +2183,6 @@ static void reset_raw_output(obs_output_t *output)
bool obs_output_begin_data_capture(obs_output_t *output, uint32_t flags) bool obs_output_begin_data_capture(obs_output_t *output, uint32_t flags)
{ {
bool encoded, has_video, has_audio, has_service; bool encoded, has_video, has_audio, has_service;
size_t num_mixes;
if (!obs_output_valid(output, "obs_output_begin_data_capture")) if (!obs_output_valid(output, "obs_output_begin_data_capture"))
return false; return false;
@ -2210,9 +2205,8 @@ bool obs_output_begin_data_capture(obs_output_t *output, uint32_t flags)
has_service)) has_service))
return false; return false;
num_mixes = num_audio_mixes(output);
if (has_video && has_audio) if (has_video && has_audio)
pair_encoders(output, num_mixes); pair_encoders(output);
os_atomic_set_bool(&output->data_active, true); os_atomic_set_bool(&output->data_active, true);
hook_data_capture(output, encoded, has_video, has_audio); hook_data_capture(output, encoded, has_video, has_audio);
@ -2240,11 +2234,10 @@ bool obs_output_begin_data_capture(obs_output_t *output, uint32_t flags)
static inline void stop_audio_encoders(obs_output_t *output, static inline void stop_audio_encoders(obs_output_t *output,
encoded_callback_t encoded_callback) encoded_callback_t encoded_callback)
{ {
size_t num_mixes = num_audio_mixes(output); for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
obs_encoder_t *audio = output->audio_encoders[i];
for (size_t i = 0; i < num_mixes; i++) { if (audio)
obs_encoder_stop(output->audio_encoders[i], encoded_callback, obs_encoder_stop(audio, encoded_callback, output);
output);
} }
} }
@ -2650,7 +2643,7 @@ const char *obs_output_get_last_error(obs_output_t *output)
return vencoder->last_error_message; return vencoder->last_error_message;
} }
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
obs_encoder_t *aencoder = output->audio_encoders[i]; obs_encoder_t *aencoder = output->audio_encoders[i];
if (aencoder && aencoder->last_error_message) { if (aencoder && aencoder->last_error_message) {
return aencoder->last_error_message; return aencoder->last_error_message;

View file

@ -29,6 +29,8 @@ extern "C" {
#define OBS_OUTPUT_MULTI_TRACK (1 << 4) #define OBS_OUTPUT_MULTI_TRACK (1 << 4)
#define OBS_OUTPUT_CAN_PAUSE (1 << 5) #define OBS_OUTPUT_CAN_PAUSE (1 << 5)
#define MAX_OUTPUT_AUDIO_ENCODERS 6
struct encoder_packet; struct encoder_packet;
struct obs_output_info { struct obs_output_info {