Browse Source

libobs/media-io: Remove audio lines (skip)

(Note: This commit breaks libobs compilation.  Skip if bisecting)

Uses a callback and allows the caller to mix audio.  Additionally,
allows the callback to return audio later, allowing it to buffer as much
as it needs.
jp9000 9 years ago
parent
commit
ee1842d1f5
2 changed files with 71 additions and 438 deletions
  1. 57 427
      libobs/media-io/audio-io.c
  2. 14 11
      libobs/media-io/audio-io.h

+ 57 - 427
libobs/media-io/audio-io.c

@@ -46,51 +46,9 @@ static inline void audio_input_free(struct audio_input *input)
 	audio_resampler_destroy(input->resampler);
 }
 
-struct audio_line {
-	char                       *name;
-
-	struct audio_output        *audio;
-	struct circlebuf           buffers[MAX_AV_PLANES];
-	pthread_mutex_t            mutex;
-	DARRAY(uint8_t)            volume_buffers[MAX_AV_PLANES];
-	uint64_t                   base_timestamp;
-	uint64_t                   last_timestamp;
-
-	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;
-
-	/* gets set when audio is getting cut off in the front of the buffer */
-	bool                       audio_getting_cut_off;
-
-	/* gets set when audio data is being inserted way outside of bounds of
-	 * the circular buffer */
-	bool                       audio_data_out_of_bounds;
-
-	struct audio_line          **prev_next;
-	struct audio_line          *next;
-};
-
-static inline void audio_line_destroy_data(struct audio_line *line)
-{
-	for (size_t i = 0; i < MAX_AV_PLANES; i++) {
-		circlebuf_free(&line->buffers[i]);
-		da_free(line->volume_buffers[i]);
-	}
-
-	pthread_mutex_destroy(&line->mutex);
-	bfree(line->name);
-	bfree(line);
-}
-
 struct audio_mix {
 	DARRAY(struct audio_input) inputs;
-	DARRAY(uint8_t)            mix_buffers[MAX_AV_PLANES];
+	float buffer[MAX_AUDIO_CHANNELS][AUDIO_OUTPUT_FRAMES];
 };
 
 struct audio_output {
@@ -104,27 +62,12 @@ struct audio_output {
 
 	bool                       initialized;
 
-	pthread_mutex_t            line_mutex;
-	struct audio_line          *first_line;
-
+	audio_input_callback_t     input_cb;
+	void                       *input_param;
 	pthread_mutex_t            input_mutex;
-
 	struct audio_mix           mixes[MAX_AUDIO_MIXES];
 };
 
-static inline void audio_output_removeline(struct audio_output *audio,
-		struct audio_line *line)
-{
-	pthread_mutex_lock(&audio->line_mutex);
-	if (line->prev_next)
-		*line->prev_next = line->next;
-	if (line->next)
-		line->next->prev_next = line->prev_next;
-	pthread_mutex_unlock(&audio->line_mutex);
-
-	audio_line_destroy_data(line);
-}
-
 /* ------------------------------------------------------------------------- */
 /* the following functions are used to calculate frame offsets based upon
  * timestamps.  this will actually work accurately as long as you handle the
@@ -155,47 +98,8 @@ static int64_t ts_diff_bytes(const audio_t *audio, uint64_t ts1, uint64_t ts2)
 	return ts_diff_frames(audio, ts1, ts2) * (int64_t)audio->block_size;
 }
 
-/* unless the value is 3+ hours worth of frames, this won't overflow */
-static inline uint64_t conv_frames_to_time(const audio_t *audio,
-		uint32_t frames)
-{
-	return (uint64_t)frames * 1000000000ULL /
-		(uint64_t)audio->info.samples_per_sec;
-}
-
 /* ------------------------------------------------------------------------- */
 
-/* this only really happens with the very initial data insertion.  can be
- * ignored safely. */
-static inline void clear_excess_audio_data(struct audio_line *line,
-		uint64_t prev_time)
-{
-	size_t size = (size_t)ts_diff_bytes(line->audio, prev_time,
-			line->base_timestamp);
-
-	/*blog(LOG_DEBUG, "Excess audio data for audio line '%s', somehow "
-	                "audio data went back in time by %"PRIu32" bytes.  "
-	                "prev_time: %"PRIu64", line->base_timestamp: %"PRIu64,
-	                line->name, (uint32_t)size,
-	                prev_time, line->base_timestamp);*/
-
-	if (!line->audio_getting_cut_off) {
-		blog(LOG_WARNING, "Audio line '%s' audio data currently "
-		                  "getting cut off.  This could be due to a "
-		                  "negative sync offset that's larger than "
-		                  "the current audio buffering time.",
-		                  line->name);
-		line->audio_getting_cut_off = true;
-	}
-
-	for (size_t i = 0; i < line->audio->planes; i++) {
-		size_t clear_size = (size < line->buffers[i].size) ?
-			size : line->buffers[i].size;
-
-		circlebuf_pop_front(&line->buffers[i], NULL, clear_size);
-	}
-}
-
 static inline uint64_t min_uint64(uint64_t a, uint64_t b)
 {
 	return a < b ? a : b;
@@ -211,63 +115,6 @@ static inline size_t min_size(size_t a, size_t b)
 	((val > maxval) ? maxval : ((val < minval) ? minval : val))
 #endif
 
-#define MIX_BUFFER_SIZE 256
-
-/* TODO: optimize mixing */
-static void mix_float(struct audio_output *audio, struct audio_line *line,
-		size_t size, size_t time_offset, size_t plane)
-{
-	float *mixes[MAX_AUDIO_MIXES];
-	float vals[MIX_BUFFER_SIZE];
-
-	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(&line->buffers[plane], vals, pop_count);
-		pop_count /= sizeof(float);
-
-		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;
-
-			for (size_t i = 0; i < pop_count; i++) {
-				*(mixes[mix_idx]++) += vals[i];
-			}
-		}
-	}
-}
-
-static inline bool mix_audio_line(struct audio_output *audio,
-		struct audio_line *line, size_t size, uint64_t timestamp)
-{
-	size_t time_offset = (size_t)ts_diff_bytes(audio,
-			line->base_timestamp, timestamp);
-	if (time_offset > size)
-		return false;
-
-	size -= time_offset;
-
-#ifdef DEBUG_AUDIO
-	blog(LOG_DEBUG, "shaved off %lu bytes", size);
-#endif
-
-	for (size_t i = 0; i < audio->planes; i++) {
-		size_t pop_size = min_size(size, line->buffers[i].size);
-
-		mix_float(audio, line, pop_size, time_offset, i);
-	}
-
-	return true;
-}
-
 static bool resample_audio_output(struct audio_input *input,
 		struct audio_data *data)
 {
@@ -300,8 +147,8 @@ static inline void do_audio_output(struct audio_output *audio,
 	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] = mix->mix_buffers[i].array;
+	for (size_t i = 0; i < audio->planes; i++)
+		data.data[i] = (uint8_t*)mix->buffer[i];
 
 	data.frames = frames;
 	data.timestamp = timestamp;
@@ -331,7 +178,7 @@ static inline void clamp_audio_output(struct audio_output *audio, size_t bytes)
 			continue;
 
 		for (size_t plane = 0; plane < audio->planes; plane++) {
-			float *mix_data = (float*)mix->mix_buffers[plane].array;
+			float *mix_data = mix->buffer[plane];
 			float *mix_end = &mix_data[float_size];
 
 			while (mix_data < mix_end) {
@@ -344,104 +191,90 @@ static inline void clamp_audio_output(struct audio_output *audio, size_t bytes)
 	}
 }
 
-static uint64_t mix_and_output(struct audio_output *audio, uint64_t audio_time,
-		uint64_t prev_time)
+static void input_and_output(struct audio_output *audio,
+		uint64_t audio_time, uint64_t prev_time)
 {
-	struct audio_line *line = audio->first_line;
-	uint32_t frames = (uint32_t)ts_diff_frames(audio, audio_time,
-	                                           prev_time);
-	size_t bytes = frames * audio->block_size;
+	size_t bytes = AUDIO_OUTPUT_FRAMES * audio->block_size;
+	struct audio_output_data data[MAX_AUDIO_MIXES];
+	uint32_t active_mixes = 0;
+	uint64_t new_ts = 0;
+	bool success;
+
+	memset(data, 0, sizeof(data));
 
 #ifdef DEBUG_AUDIO
 	blog(LOG_DEBUG, "audio_time: %llu, prev_time: %llu, bytes: %lu",
 			audio_time, prev_time, bytes);
 #endif
 
-	/* return an adjusted audio_time according to the amount
-	 * of data that was sampled to ensure seamless transmission */
-	audio_time = prev_time + conv_frames_to_time(audio, frames);
+	/* get mixers */
+	pthread_mutex_lock(&audio->input_mutex);
+	for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
+		if (audio->mixes[i].inputs.num)
+			active_mixes |= (1 << i);
+	}
+	pthread_mutex_unlock(&audio->input_mutex);
 
-	/* resize and clear mix buffers */
+	/* clear mix buffers */
 	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 */
-	while (line) {
-		struct audio_line *next = line->next;
-
-		/* if line marked for removal, destroy and move to the next */
-		if (!line->buffers[0].size) {
-			if (!line->alive) {
-				audio_output_removeline(audio, line);
-				line = next;
-				continue;
-			}
-		}
-
-		pthread_mutex_lock(&line->mutex);
-
-		if (line->buffers[0].size && line->base_timestamp < prev_time) {
-			clear_excess_audio_data(line, prev_time);
-			line->base_timestamp = prev_time;
+		memset(mix->buffer[0], 0, AUDIO_OUTPUT_FRAMES *
+				MAX_AUDIO_CHANNELS * sizeof(float));
 
-		} else if (line->audio_getting_cut_off) {
-			line->audio_getting_cut_off = false;
-			blog(LOG_WARNING, "Audio line '%s' audio data no "
-			                  "longer getting cut off.",
-			                  line->name);
-		}
-
-		if (mix_audio_line(audio, line, bytes, prev_time))
-			line->base_timestamp = audio_time;
-
-		pthread_mutex_unlock(&line->mutex);
-
-		line = next;
+		for (size_t i = 0; i < audio->planes; i++)
+			data[mix_idx].data[i] = mix->buffer[i];
 	}
 
+	/* get new audio data */
+	success = audio->input_cb(audio->input_param, prev_time, audio_time,
+			&new_ts, active_mixes, data);
+	if (!success)
+		return;
+
 	/* clamps audio data to -1.0..1.0 */
 	clamp_audio_output(audio, bytes);
 
 	/* output */
 	for (size_t i = 0; i < MAX_AUDIO_MIXES; i++)
-		do_audio_output(audio, i, prev_time, frames);
-
-	return audio_time;
+		do_audio_output(audio, i, new_ts, AUDIO_OUTPUT_FRAMES);
 }
 
-/* sample audio 40 times a second */
-#define AUDIO_WAIT_TIME (1000/40)
-
 static void *audio_thread(void *param)
 {
 	struct audio_output *audio = param;
-	uint64_t buffer_time = audio->info.buffer_ms * 1000000;
-	uint64_t prev_time = os_gettime_ns() - buffer_time;
-	uint64_t audio_time;
+	size_t rate = audio->info.samples_per_sec;
+	uint64_t samples = 0;
+	uint64_t start_time = os_gettime_ns();
+	uint64_t prev_time = start_time;
+	uint64_t audio_time = prev_time;
+	uint32_t audio_wait_time =
+		(uint32_t)(audio_frames_to_ns(rate, AUDIO_OUTPUT_FRAMES) /
+				1000000);
 
 	os_set_thread_name("audio-io: audio thread");
 
 	const char *audio_thread_name =
 		profile_store_name(obs_get_profiler_name_store(),
 				"audio_thread(%s)", audio->info.name);
-	
+
 	while (os_event_try(audio->stop_event) == EAGAIN) {
-		os_sleep_ms(AUDIO_WAIT_TIME);
+		uint64_t cur_time;
+
+		os_sleep_ms(audio_wait_time);
 
 		profile_start(audio_thread_name);
-		pthread_mutex_lock(&audio->line_mutex);
 
-		audio_time = os_gettime_ns() - buffer_time;
-		audio_time = mix_and_output(audio, audio_time, prev_time);
-		prev_time  = audio_time;
+		cur_time = os_gettime_ns();
+		while (audio_time <= cur_time) {
+			samples += AUDIO_OUTPUT_FRAMES;
+			audio_time = start_time +
+				audio_frames_to_ns(rate, samples);
+
+			input_and_output(audio, audio_time, prev_time);
+			prev_time = audio_time;
+		}
 
-		pthread_mutex_unlock(&audio->line_mutex);
 		profile_end(audio_thread_name);
 
 		profile_reenable_thread();
@@ -578,9 +411,10 @@ int audio_output_open(audio_t **audio, struct audio_output_info *info)
 		goto fail;
 
 	memcpy(&out->info, info, sizeof(struct audio_output_info));
-	pthread_mutex_init_value(&out->line_mutex);
 	out->channels   = get_audio_channels(info->speakers);
 	out->planes     = planar ? out->channels : 1;
+	out->input_cb   = info->input_callback;
+	out->input_param= info->input_param;
 	out->block_size = (planar ? 1 : out->channels) *
 	                  get_audio_bytes_per_channel(info->format);
 
@@ -588,8 +422,6 @@ int audio_output_open(audio_t **audio, struct audio_output_info *info)
 		goto fail;
 	if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
 		goto fail;
-	if (pthread_mutex_init(&out->line_mutex, &attr) != 0)
-		goto fail;
 	if (pthread_mutex_init(&out->input_mutex, &attr) != 0)
 		goto fail;
 	if (os_event_init(&out->stop_event, OS_EVENT_TYPE_MANUAL) != 0)
@@ -609,7 +441,6 @@ fail:
 void audio_output_close(audio_t *audio)
 {
 	void *thread_ret;
-	struct audio_line *line;
 
 	if (!audio)
 		return;
@@ -619,78 +450,24 @@ void audio_output_close(audio_t *audio)
 		pthread_join(audio->thread, &thread_ret);
 	}
 
-	line = audio->first_line;
-	while (line) {
-		struct audio_line *next = line->next;
-		audio_line_destroy_data(line);
-		line = next;
-	}
-
 	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 < 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);
 	}
 
 	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,
-		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 "
-		                "mutex");
-		bfree(line);
-		return NULL;
-	}
-
-	pthread_mutex_lock(&audio->line_mutex);
-
-	if (audio->first_line) {
-		audio->first_line->prev_next = &line->next;
-		line->next = audio->first_line;
-	}
-
-	line->prev_next = &audio->first_line;
-	audio->first_line = line;
-
-	pthread_mutex_unlock(&audio->line_mutex);
-
-	line->name = bstrdup(name ? name : "(unnamed audio line)");
-	return line;
-}
-
 const struct audio_output_info *audio_output_get_info(const audio_t *audio)
 {
 	return audio ? &audio->info : NULL;
 }
 
-void audio_line_destroy(struct audio_line *line)
-{
-	if (line) {
-		if (!line->buffers[0].size)
-			audio_output_removeline(line->audio, line);
-		else
-			line->alive = false;
-	}
-}
-
 bool audio_output_active(const audio_t *audio)
 {
 	if (!audio) return false;
@@ -724,150 +501,3 @@ uint32_t audio_output_get_sample_rate(const audio_t *audio)
 {
 	return audio ? audio->info.samples_per_sec : 0;
 }
-
-/* TODO: optimize these two functions */
-static inline void mul_vol_float(float *array, float volume, size_t count)
-{
-	for (size_t i = 0; i < count; i++)
-		array[i] *= volume;
-}
-
-static void audio_line_place_data_pos(struct audio_line *line,
-		const struct audio_data *data, size_t position)
-{
-	bool   planar     = line->audio->planes > 1;
-	size_t total_num  = data->frames * (planar ? 1 : line->audio->channels);
-	size_t total_size = data->frames * line->audio->block_size;
-
-	for (size_t i = 0; i < line->audio->planes; i++) {
-		da_copy_array(line->volume_buffers[i], data->data[i],
-				total_size);
-
-		uint8_t *array = line->volume_buffers[i].array;
-
-		switch (line->audio->info.format) {
-		case AUDIO_FORMAT_FLOAT:
-		case AUDIO_FORMAT_FLOAT_PLANAR:
-			mul_vol_float((float*)array, data->volume, total_num);
-			break;
-		default:
-			blog(LOG_ERROR, "audio_line_place_data_pos: "
-			                "Unsupported or unknown format");
-			break;
-		}
-
-		circlebuf_place(&line->buffers[i], position,
-				line->volume_buffers[i].array, total_size);
-	}
-}
-
-static inline uint64_t smooth_ts(struct audio_line *line, uint64_t timestamp)
-{
-	if (!line->next_ts_min)
-		return timestamp;
-
-	bool ts_under = (timestamp < line->next_ts_min);
-	uint64_t diff = ts_under ?
-		(line->next_ts_min - timestamp) :
-		(timestamp - line->next_ts_min);
-
-#ifdef DEBUG_AUDIO
-	if (diff >= TS_SMOOTHING_THRESHOLD)
-		blog(LOG_DEBUG, "above TS smoothing threshold by %"PRIu64,
-				diff);
-#endif
-
-	return (diff < TS_SMOOTHING_THRESHOLD) ? line->next_ts_min : timestamp;
-}
-
-static bool audio_line_place_data(struct audio_line *line,
-		const struct audio_data *data)
-{
-	int64_t pos;
-	uint64_t timestamp = smooth_ts(line, data->timestamp);
-
-	pos = ts_diff_bytes(line->audio, timestamp, line->base_timestamp);
-
-	if (pos < 0) {
-		return false;
-	}
-
-	line->next_ts_min =
-		timestamp + conv_frames_to_time(line->audio, data->frames);
-
-#ifdef DEBUG_AUDIO
-	blog(LOG_DEBUG, "data->timestamp: %llu, line->base_timestamp: %llu, "
-			"pos: %lu, bytes: %lu, buf size: %lu",
-			timestamp, line->base_timestamp, pos,
-			data->frames * line->audio->block_size,
-			line->buffers[0].size);
-#endif
-
-	audio_line_place_data_pos(line, data, (size_t)pos);
-	return true;
-}
-
-#define MAX_DELAY_NS 6000000000ULL
-
-/* prevent insertation of data too far away from expected audio timing */
-static inline bool valid_timestamp_range(struct audio_line *line, uint64_t ts)
-{
-	uint64_t buffer_ns = 1000000ULL * line->audio->info.buffer_ms;
-	uint64_t max_ts    = line->base_timestamp + buffer_ns + MAX_DELAY_NS;
-
-	return ts >= line->base_timestamp && ts < max_ts;
-}
-
-void audio_line_output(audio_line_t *line, const struct audio_data *data)
-{
-	bool inserted_audio = false;
-
-	if (!line || !data) return;
-
-	pthread_mutex_lock(&line->mutex);
-
-	if (!line->buffers[0].size) {
-		line->base_timestamp = data->timestamp -
-		                       line->audio->info.buffer_ms * 1000000;
-		inserted_audio = audio_line_place_data(line, data);
-
-	} else if (valid_timestamp_range(line, data->timestamp)) {
-		inserted_audio = audio_line_place_data(line, data);
-	}
-
-	if (!inserted_audio) {
-		if (!line->audio_data_out_of_bounds) {
-			blog(LOG_WARNING, "Audio line '%s' currently "
-			                  "receiving out of bounds audio "
-			                  "data.  This can sometimes happen "
-			                  "if there's a pause in the thread.",
-			                  line->name);
-			line->audio_data_out_of_bounds = true;
-		}
-
-		/*blog(LOG_DEBUG, "Bad timestamp for audio line '%s', "
-		                "data->timestamp: %"PRIu64", "
-		                "line->base_timestamp: %"PRIu64".  This can "
-		                "sometimes happen when there's a pause in "
-		                "the threads.", line->name, data->timestamp,
-		                line->base_timestamp);*/
-
-	} else if (line->audio_data_out_of_bounds) {
-		blog(LOG_WARNING, "Audio line '%s' no longer receiving "
-		                  "out of bounds audio data.", line->name);
-		line->audio_data_out_of_bounds = false;
-	}
-
-	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;
-}

+ 14 - 11
libobs/media-io/audio-io.h

@@ -25,7 +25,9 @@
 extern "C" {
 #endif
 
-#define MAX_AUDIO_MIXES 4
+#define MAX_AUDIO_MIXES     4
+#define MAX_AUDIO_CHANNELS  2
+#define AUDIO_OUTPUT_FRAMES 1024
 
 /*
  * Base audio output component.  Use this to create an audio output track
@@ -33,9 +35,7 @@ extern "C" {
  */
 
 struct audio_output;
-struct audio_line;
 typedef struct audio_output audio_t;
-typedef struct audio_line   audio_line_t;
 
 enum audio_format {
 	AUDIO_FORMAT_UNKNOWN,
@@ -72,13 +72,23 @@ struct audio_data {
 	float               volume;
 };
 
+struct audio_output_data {
+	float               *data[MAX_AUDIO_CHANNELS];
+};
+
+typedef bool (*audio_input_callback_t)(void *param,
+		uint64_t start_ts, uint64_t end_ts, uint64_t *new_ts,
+		uint32_t active_mixers, struct audio_output_data *mixes);
+
 struct audio_output_info {
 	const char          *name;
 
 	uint32_t            samples_per_sec;
 	enum audio_format   format;
 	enum speaker_layout speakers;
-	uint64_t            buffer_ms;
+
+	audio_input_callback_t input_callback;
+	void                   *input_param;
 };
 
 struct audio_convert_info {
@@ -211,13 +221,6 @@ 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,
-		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);
-
 
 #ifdef __cplusplus
 }