2014-03-07 14:24:32 +00:00
|
|
|
/*
|
2015-04-13 07:36:54 +00:00
|
|
|
* This file is part of mpv.
|
2014-03-07 14:24:32 +00:00
|
|
|
*
|
audio/out: change license of some core files to LGPL
All contributors of the current code have agreed. ao.c requires a
"driver" entry for each audio output - we assume that if someone who
didn't agree to LGPL added a line, it's fine for ao.c to be LGPL
anyway. If the affected audio output is not disabled at compilation
time, the resulting binary will be GPL anyway, and ootherwise the
code is not included.
The audio output code itself was inspired or partially copied from
libao in 7a2eec4b59f4 (thus why MPlayer's audio code is named libao2).
Just to be sure we got permission from Aaron Holtzman, Jack Moffitt, and
Stan Seibert, who according to libao's SVN history and README are the
initial author. (Something similar was done for libvo, although the
commit relicensing it forgot to mention it.)
242aa6ebd40: anders mostly disagreed with the LGPL relicensing, but we
got permission for this particular commit.
0ef8e555735: nick could not be reached, but the include statement was
removed again anyway.
879e05a7c17: iive agreed to LGPL v3+ only, but this line of code was
removed anyway, so ao_null.c can be LGPL v2.1+.
9dd8f241ac2: patch author could not be reached, but the corresponding
code (old slave mode interface) was completely removed later.
2017-05-20 09:35:25 +00:00
|
|
|
* mpv is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2014-03-07 14:24:32 +00:00
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2014-03-07 14:24:32 +00:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
audio/out: change license of some core files to LGPL
All contributors of the current code have agreed. ao.c requires a
"driver" entry for each audio output - we assume that if someone who
didn't agree to LGPL added a line, it's fine for ao.c to be LGPL
anyway. If the affected audio output is not disabled at compilation
time, the resulting binary will be GPL anyway, and ootherwise the
code is not included.
The audio output code itself was inspired or partially copied from
libao in 7a2eec4b59f4 (thus why MPlayer's audio code is named libao2).
Just to be sure we got permission from Aaron Holtzman, Jack Moffitt, and
Stan Seibert, who according to libao's SVN history and README are the
initial author. (Something similar was done for libvo, although the
commit relicensing it forgot to mention it.)
242aa6ebd40: anders mostly disagreed with the LGPL relicensing, but we
got permission for this particular commit.
0ef8e555735: nick could not be reached, but the include statement was
removed again anyway.
879e05a7c17: iive agreed to LGPL v3+ only, but this line of code was
removed anyway, so ao_null.c can be LGPL v2.1+.
9dd8f241ac2: patch author could not be reached, but the corresponding
code (old slave mode interface) was completely removed later.
2017-05-20 09:35:25 +00:00
|
|
|
* GNU Lesser General Public License for more details.
|
2014-03-07 14:24:32 +00:00
|
|
|
*
|
audio/out: change license of some core files to LGPL
All contributors of the current code have agreed. ao.c requires a
"driver" entry for each audio output - we assume that if someone who
didn't agree to LGPL added a line, it's fine for ao.c to be LGPL
anyway. If the affected audio output is not disabled at compilation
time, the resulting binary will be GPL anyway, and ootherwise the
code is not included.
The audio output code itself was inspired or partially copied from
libao in 7a2eec4b59f4 (thus why MPlayer's audio code is named libao2).
Just to be sure we got permission from Aaron Holtzman, Jack Moffitt, and
Stan Seibert, who according to libao's SVN history and README are the
initial author. (Something similar was done for libvo, although the
commit relicensing it forgot to mention it.)
242aa6ebd40: anders mostly disagreed with the LGPL relicensing, but we
got permission for this particular commit.
0ef8e555735: nick could not be reached, but the include statement was
removed again anyway.
879e05a7c17: iive agreed to LGPL v3+ only, but this line of code was
removed anyway, so ao_null.c can be LGPL v2.1+.
9dd8f241ac2: patch author could not be reached, but the corresponding
code (old slave mode interface) was completely removed later.
2017-05-20 09:35:25 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2014-03-07 14:24:32 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef MP_AO_INTERNAL_H_
|
|
|
|
#define MP_AO_INTERNAL_H_
|
|
|
|
|
2023-10-19 12:18:52 +00:00
|
|
|
#include <stdatomic.h>
|
2023-10-19 14:26:26 +00:00
|
|
|
#include <stdbool.h>
|
|
|
|
|
2014-10-09 20:21:24 +00:00
|
|
|
#include "audio/out/ao.h"
|
2014-03-08 23:04:37 +00:00
|
|
|
|
2014-03-07 14:24:32 +00:00
|
|
|
/* global data used by ao.c and ao drivers */
|
|
|
|
struct ao {
|
|
|
|
int samplerate;
|
|
|
|
struct mp_chmap channels;
|
|
|
|
int format; // one of AF_FORMAT_...
|
2024-05-09 23:10:46 +00:00
|
|
|
int64_t bps; // bytes per second (per plane)
|
2014-03-07 14:24:32 +00:00
|
|
|
int sstride; // size of a sample on each plane
|
|
|
|
// (format_size*num_channels/num_planes)
|
2014-03-08 23:04:37 +00:00
|
|
|
int num_planes;
|
2014-03-07 14:24:32 +00:00
|
|
|
bool probing; // if true, don't fail loudly on init
|
|
|
|
bool untimed; // don't assume realtime playback
|
2014-03-08 23:04:37 +00:00
|
|
|
int device_buffer; // device buffer in samples (guessed by
|
|
|
|
// common init code if not set by driver)
|
2014-03-07 14:24:32 +00:00
|
|
|
const struct ao_driver *driver;
|
audio: merge pull/push ring buffer glue code
This is preparation to further cleanups (and eventually actual
improvements) of the audio output code.
AOs are split into two classes: pull and push. Pull AOs let an audio
callback of the native audio API read from a ring buffer. Push AOs
expose a function that works similar to write(), and for which we start
a "feeder" thread. It seems making this split was beneficial, because of
the different data flow, and emulating the one or other in the AOs
directly would have created code duplication (all the "pull" AOs had
their own ring buffer implementation before it was cleaned up).
Unfortunately, both types had completely separate implementations (in
pull.c and push.c). The idea was that little can be shared anyway. But
that's very annoying now, because I want to change the API between AO
and player.
This commit attempts to merge them. I've moved everything from push.c to
pull.c, the trivial entrypoints from ao.c to pull.c, and attempted to
reconcile the differences. It's a mess, but at least there's only one
ring buffer within the AO code now. Everything should work mostly the
same. Pull AOs now always copy the audio data under a lock; before this
commit, all ring buffer access was lock-free (except for the decoder
wakeup callback, which acquired a mutex). In theory, this is "bad", and
people obsessed with lock-free stuff will hate me, but in practice
probably won't matter. The planned change will probably remove this
copying-under-lock again, but who knows when this will happen.
One change for the push AOs now makes it drop audio, where before only a
warning was logged. This is only in case of AOs or drivers which exhibit
unexpected (and now unsupported) behavior.
This is a risky change. Although it's completely trivial conceptually,
there are too many special cases. In addition, I barely tested it, and
I've messed with it in a half-motivated state over a longer time, barely
making any progress, and finishing it under a rush when I already should
have been asleep. Most things seem to work, and I made superficial tests
with alsa, sdl, and encode mode. This should cover most things, but
there are a lot of tricky things that received no coverage. All this
text means you should be prepared to roll back to an older commit and
report your problem.
2020-05-24 23:53:41 +00:00
|
|
|
bool driver_initialized;
|
2014-03-07 14:24:32 +00:00
|
|
|
void *priv;
|
2016-09-02 18:07:25 +00:00
|
|
|
struct mpv_global *global;
|
2014-03-07 14:24:32 +00:00
|
|
|
struct encode_lavc_context *encode_lavc_ctx;
|
2016-09-16 12:23:54 +00:00
|
|
|
void (*wakeup_cb)(void *ctx);
|
|
|
|
void *wakeup_ctx;
|
2014-03-07 14:24:32 +00:00
|
|
|
struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix
|
2016-08-04 18:49:20 +00:00
|
|
|
int init_flags; // AO_INIT_* flags
|
2016-08-09 14:22:06 +00:00
|
|
|
bool stream_silence; // if audio inactive, just play silence
|
2014-03-08 23:04:37 +00:00
|
|
|
|
2014-10-09 19:21:31 +00:00
|
|
|
// The device as selected by the user, usually using ao_device_desc.name
|
|
|
|
// from an entry from the list returned by driver->list_devices. If the
|
|
|
|
// default device should be used, this is set to NULL.
|
|
|
|
char *device;
|
|
|
|
|
2014-11-07 14:54:35 +00:00
|
|
|
// Application name to report to the audio API.
|
|
|
|
char *client_name;
|
|
|
|
|
2014-10-22 15:12:08 +00:00
|
|
|
// Used during init: if init fails, redirect to this ao
|
|
|
|
char *redirect;
|
|
|
|
|
2015-02-12 15:53:56 +00:00
|
|
|
// Internal events (use ao_request_reload(), ao_hotplug_event())
|
2019-10-11 17:25:45 +00:00
|
|
|
atomic_uint events_;
|
2014-11-09 08:58:44 +00:00
|
|
|
|
2017-11-30 00:14:33 +00:00
|
|
|
// Float gain multiplicator
|
2023-10-19 12:18:52 +00:00
|
|
|
_Atomic float gain;
|
audio: add audio softvol processing to AO
This does what af_volume used to do. Since we couldn't relicense it,
just rewrite it. Since we don't have a new filter mechanism yet, and the
libavfilter is too inconvenient, do applying the volume gain in ao.c
directly. This is done before handling the audio data to the driver.
Since push.c runs a separate thread, and pull.c is called asynchronously
from the audio driver's thread, the volume value needs to be
synchronized. There's no existing central mutex, so do some shit with
atomics. Since there's no atomic_float type predefined (which is at
least needed when using the legacy wrapper), do some nonsense about
reinterpret casting the float value to an int for the purpose of atomic
access. Not sure if using memcpy() is undefined behavior, but for now I
don't care.
The advantage of not using a filter is lower complexity (no filter auto
insertion), and lower latency (gain processing is done after our
internal audio buffer of at least 200ms).
Disavdantages include inability to use native volume control _before_
other filters with custom filter chains, and the need to add new
processing for each new sample type.
Since this doesn't reuse any of the old GPL code, nor does indirectly
rely on it, volume and replaygain handling now works in LGPL mode.
How to process the gain is inspired by libavfilter's af_volume (LGPL).
In particular, we use exactly the same rounding, and we quantize
processing for integer sample types by 256 steps. Some of libavfilter's
copyright may or may not apply, but I think not, and it's the same
license anyway.
2017-11-29 20:30:10 +00:00
|
|
|
|
2014-03-08 23:04:37 +00:00
|
|
|
int buffer;
|
2014-09-04 21:48:27 +00:00
|
|
|
double def_buffer;
|
audio: merge pull/push ring buffer glue code
This is preparation to further cleanups (and eventually actual
improvements) of the audio output code.
AOs are split into two classes: pull and push. Pull AOs let an audio
callback of the native audio API read from a ring buffer. Push AOs
expose a function that works similar to write(), and for which we start
a "feeder" thread. It seems making this split was beneficial, because of
the different data flow, and emulating the one or other in the AOs
directly would have created code duplication (all the "pull" AOs had
their own ring buffer implementation before it was cleaned up).
Unfortunately, both types had completely separate implementations (in
pull.c and push.c). The idea was that little can be shared anyway. But
that's very annoying now, because I want to change the API between AO
and player.
This commit attempts to merge them. I've moved everything from push.c to
pull.c, the trivial entrypoints from ao.c to pull.c, and attempted to
reconcile the differences. It's a mess, but at least there's only one
ring buffer within the AO code now. Everything should work mostly the
same. Pull AOs now always copy the audio data under a lock; before this
commit, all ring buffer access was lock-free (except for the decoder
wakeup callback, which acquired a mutex). In theory, this is "bad", and
people obsessed with lock-free stuff will hate me, but in practice
probably won't matter. The planned change will probably remove this
copying-under-lock again, but who knows when this will happen.
One change for the push AOs now makes it drop audio, where before only a
warning was logged. This is only in case of AOs or drivers which exhibit
unexpected (and now unsupported) behavior.
This is a risky change. Although it's completely trivial conceptually,
there are too many special cases. In addition, I barely tested it, and
I've messed with it in a half-motivated state over a longer time, barely
making any progress, and finishing it under a rush when I already should
have been asleep. Most things seem to work, and I made superficial tests
with alsa, sdl, and encode mode. This should cover most things, but
there are a lot of tricky things that received no coverage. All this
text means you should be prepared to roll back to an older commit and
report your problem.
2020-05-24 23:53:41 +00:00
|
|
|
struct buffer_state *buffer_state;
|
2014-03-07 14:24:32 +00:00
|
|
|
};
|
|
|
|
|
audio: merge pull/push ring buffer glue code
This is preparation to further cleanups (and eventually actual
improvements) of the audio output code.
AOs are split into two classes: pull and push. Pull AOs let an audio
callback of the native audio API read from a ring buffer. Push AOs
expose a function that works similar to write(), and for which we start
a "feeder" thread. It seems making this split was beneficial, because of
the different data flow, and emulating the one or other in the AOs
directly would have created code duplication (all the "pull" AOs had
their own ring buffer implementation before it was cleaned up).
Unfortunately, both types had completely separate implementations (in
pull.c and push.c). The idea was that little can be shared anyway. But
that's very annoying now, because I want to change the API between AO
and player.
This commit attempts to merge them. I've moved everything from push.c to
pull.c, the trivial entrypoints from ao.c to pull.c, and attempted to
reconcile the differences. It's a mess, but at least there's only one
ring buffer within the AO code now. Everything should work mostly the
same. Pull AOs now always copy the audio data under a lock; before this
commit, all ring buffer access was lock-free (except for the decoder
wakeup callback, which acquired a mutex). In theory, this is "bad", and
people obsessed with lock-free stuff will hate me, but in practice
probably won't matter. The planned change will probably remove this
copying-under-lock again, but who knows when this will happen.
One change for the push AOs now makes it drop audio, where before only a
warning was logged. This is only in case of AOs or drivers which exhibit
unexpected (and now unsupported) behavior.
This is a risky change. Although it's completely trivial conceptually,
there are too many special cases. In addition, I barely tested it, and
I've messed with it in a half-motivated state over a longer time, barely
making any progress, and finishing it under a rush when I already should
have been asleep. Most things seem to work, and I made superficial tests
with alsa, sdl, and encode mode. This should cover most things, but
there are a lot of tricky things that received no coverage. All this
text means you should be prepared to roll back to an older commit and
report your problem.
2020-05-24 23:53:41 +00:00
|
|
|
void init_buffer_pre(struct ao *ao);
|
|
|
|
bool init_buffer_post(struct ao *ao);
|
2014-03-08 23:04:37 +00:00
|
|
|
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
struct mp_pcm_state {
|
|
|
|
// Note: free_samples+queued_samples <= ao->device_buffer; the sum may be
|
|
|
|
// less if the audio API can report partial periods played, while
|
2020-08-29 14:27:56 +00:00
|
|
|
// free_samples should be period-size aligned. If free_samples is not
|
|
|
|
// period-size aligned, the AO thread might get into a situation where
|
|
|
|
// it writes a very small number of samples in each iteration, leading
|
|
|
|
// to extremely inefficient behavior.
|
|
|
|
// Keep in mind that write() may write less than free_samples (or your
|
|
|
|
// period size alignment) anyway.
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
int free_samples; // number of free space in ring buffer
|
|
|
|
int queued_samples; // number of samples to play in ring buffer
|
|
|
|
double delay; // total latency in seconds (includes queued_samples)
|
2020-06-02 18:30:59 +00:00
|
|
|
bool playing; // set if underlying API is actually playing audio;
|
|
|
|
// the AO must unset it on underrun (accidental
|
|
|
|
// underrun and EOF are indistinguishable; the upper
|
|
|
|
// layers decide what it was)
|
|
|
|
// real pausing may assume playing=true
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
};
|
|
|
|
|
2014-03-08 23:04:37 +00:00
|
|
|
/* Note:
|
|
|
|
*
|
|
|
|
* In general, there are two types of audio drivers:
|
|
|
|
* a) push based (the user queues data that should be played)
|
|
|
|
* b) pull callback based (the audio API calls a callback to get audio)
|
|
|
|
*
|
|
|
|
* The ao.c code can handle both. It basically implements two audio paths
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
* and provides a uniform API for them. If ao_driver->write is NULL, it assumes
|
2014-03-08 23:04:37 +00:00
|
|
|
* that the driver uses a callback based audio API, otherwise push based.
|
|
|
|
*
|
|
|
|
* Requirements:
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
* a+b) Mandatory for both types:
|
2014-03-08 23:04:37 +00:00
|
|
|
* init
|
|
|
|
* uninit
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
* start
|
|
|
|
* Optional for both types:
|
|
|
|
* control
|
2024-03-25 20:08:13 +00:00
|
|
|
* set_pause
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
* a) ->write is called to queue audio. push.c creates a thread to regularly
|
|
|
|
* refill audio device buffers with ->write, but all driver functions are
|
|
|
|
* always called under an exclusive lock.
|
|
|
|
* Mandatory:
|
2014-03-08 23:04:37 +00:00
|
|
|
* reset
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
* write
|
|
|
|
* get_state
|
|
|
|
* b) ->write must be NULL. ->start must be provided, and should make the
|
2014-05-29 21:56:44 +00:00
|
|
|
* audio API start calling the audio callback. Your audio callback should
|
|
|
|
* in turn call ao_read_data() to get audio data. Most functions are
|
|
|
|
* optional and will be emulated if missing (e.g. pausing is emulated as
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
* silence).
|
audio/out/pull: remove race conditions
There were subtle and minor race conditions in the pull.c code, and AOs
using it (jack, portaudio, sdl, wasapi). Attempt to remove these.
There was at least a race condition in the ao_reset() implementation:
mp_ring_reset() was called concurrently to the audio callback. While the
ringbuffer uses atomics to allow concurrent access, the reset function
wasn't concurrency-safe (and can't easily be made to).
Fix this by stopping the audio callback before doing a reset. After
that, we can do anything without needing synchronization. The callback
is resumed when resuming playback at a later point.
Don't call driver->pause, and make driver->resume and driver->reset
start/stop the audio callback. In the initial state, the audio callback
must be disabled.
JackAudio of course is different. Maybe there is no way to suspend the
audio callback without "disconnecting" it (what jack_deactivate() would
do), so I'm not trying my luck, and implemented a really bad hack doing
active waiting until we get the audio callback into a state where it
won't interfere. Once the callback goes from AO_STATE_WAIT to NONE, we
can be sure that the callback doesn't access the ringbuffer or anything
else anymore. Since both sched_yield() and pthread_yield() apparently
are not always available, use mp_sleep_us(1) to avoid burning CPU during
active waiting.
The ao_jack.c change also removes a race condition: apparently we didn't
initialize _all_ ao fields before starting the audio callback.
In ao_wasapi.c, I'm not sure whether reset really waits for the audio
callback to return. Kovensky says it's not guaranteed, so disable the
reset callback - for now the behavior of ao_wasapi.c is like with
ao_jack.c, and active waiting is used to deal with the audio callback.
2014-05-29 00:24:17 +00:00
|
|
|
* Also, the following optional callbacks can be provided:
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
* reset (stops the audio callback, start() restarts it)
|
2014-03-08 23:04:37 +00:00
|
|
|
*/
|
2014-03-07 14:24:32 +00:00
|
|
|
struct ao_driver {
|
|
|
|
// If true, use with encoding only.
|
|
|
|
bool encode;
|
|
|
|
// Name used for --ao.
|
|
|
|
const char *name;
|
|
|
|
// Description shown with --ao=help.
|
|
|
|
const char *description;
|
2018-04-29 17:42:18 +00:00
|
|
|
// This requires waiting for a AO_EVENT_INITIAL_UNBLOCK event before the
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
// first write() call is done. Encode mode uses this, and push mode
|
2018-04-29 17:42:18 +00:00
|
|
|
// respects it automatically (don't use with pull mode).
|
|
|
|
bool initially_blocked;
|
audio: refactor how data is passed to AO
This replaces the two buffers (ao_chain.ao_buffer in the core, and
buffer_state.buffers in the AO) with a single queue. Instead of having a
byte based buffer, the queue is simply a list of audio frames, as output
by the decoder. This should make dataflow simpler and reduce copying.
It also attempts to simplify fill_audio_out_buffers(), the function I
always hated most, because it's full of subtle and buggy logic.
Unfortunately, I got assaulted by corner cases, dumb features (attempt
at seamless looping, really?), and other crap, so it got pretty
complicated again. fill_audio_out_buffers() is still full of subtle and
buggy logic. Maybe it got worse. On the other hand, maybe there really
is some progress. Who knows.
Originally, the data flow parts was meant to be in f_output_chain, but
due to tricky interactions with the playloop code, it's now in the dummy
filter in audio.c.
At least this improves the way the audio PTS is passed to the encoder in
encoding mode. Now it attempts to pass frames directly, along with the
pts, which should minimize timestamp problems. But to be honest, encoder
mode is one big kludge that shouldn't exist in this way.
This commit should be considered pre-alpha code. There are lots of bugs
still hiding.
2020-08-28 18:23:54 +00:00
|
|
|
// If true, write units of entire frames. The write() call is modified to
|
|
|
|
// use data==mp_aframe. Useful for encoding AO only.
|
|
|
|
bool write_frames;
|
2014-03-07 14:24:32 +00:00
|
|
|
// Init the device using ao->format/ao->channels/ao->samplerate. If the
|
|
|
|
// device doesn't accept these parameters, you can attempt to negotiate
|
|
|
|
// fallback parameters, and set the ao format fields accordingly.
|
|
|
|
int (*init)(struct ao *ao);
|
2014-03-08 23:04:37 +00:00
|
|
|
// Optional. See ao_control() etc. in ao.c
|
2014-03-07 14:24:32 +00:00
|
|
|
int (*control)(struct ao *ao, enum aocontrol cmd, void *arg);
|
2014-03-08 23:49:39 +00:00
|
|
|
void (*uninit)(struct ao *ao);
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
// Stop all audio playback, clear buffers, back to state after init().
|
|
|
|
// Optional for pull AOs.
|
2018-03-01 19:16:23 +00:00
|
|
|
void (*reset)(struct ao *ao);
|
2024-03-25 20:08:13 +00:00
|
|
|
// pull based: set pause state. Only called after start() and before reset().
|
|
|
|
// The return value is ignored.
|
|
|
|
// The pausing state is also cleared by reset().
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
// push based: set pause state. Only called after start() and before reset().
|
|
|
|
// returns success (this is intended for paused=true; if it
|
2020-06-02 18:30:59 +00:00
|
|
|
// returns false, playback continues, and the core emulates via
|
|
|
|
// reset(); unpausing always works)
|
2020-08-27 09:40:56 +00:00
|
|
|
// The pausing state is also cleared by reset().
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
bool (*set_pause)(struct ao *ao, bool paused);
|
2014-05-29 21:56:44 +00:00
|
|
|
// pull based: start the audio callback
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
// push based: start playing queued data
|
2024-04-10 05:12:40 +00:00
|
|
|
// AO should call ao_wakeup() if a period boundary
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
// is crossed, or playback stops due to external reasons
|
|
|
|
// (including underruns or device removal)
|
2020-06-02 18:30:59 +00:00
|
|
|
// must set mp_pcm_state.playing; unset on error/underrun/end
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
void (*start)(struct ao *ao);
|
|
|
|
// push based: queue new data. This won't try to write more data than the
|
|
|
|
// reported free space (samples <= mp_pcm_state.free_samples).
|
|
|
|
// This must NOT start playback. start() does that, and write() may be
|
|
|
|
// called multiple times before start() is called. It may also happen that
|
|
|
|
// reset() is called to discard the buffer. start() without write() will
|
|
|
|
// immediately reported an underrun.
|
|
|
|
// Return false on failure.
|
|
|
|
bool (*write)(struct ao *ao, void **data, int samples);
|
|
|
|
// push based: return mandatory stream information
|
|
|
|
void (*get_state)(struct ao *ao, struct mp_pcm_state *state);
|
2014-03-07 14:24:32 +00:00
|
|
|
|
2014-10-09 19:21:31 +00:00
|
|
|
// Return the list of devices currently available in the system. Use
|
|
|
|
// ao_device_list_add() to add entries. The selected device will be set as
|
|
|
|
// ao->device (using ao_device_desc.name).
|
2015-02-12 15:53:56 +00:00
|
|
|
// Warning: the ao struct passed is not initialized with ao_driver->init().
|
|
|
|
// Instead, hotplug_init/hotplug_uninit is called. If these
|
|
|
|
// callbacks are not set, no driver initialization call is done
|
|
|
|
// on the ao struct.
|
2014-10-10 16:27:21 +00:00
|
|
|
void (*list_devs)(struct ao *ao, struct ao_device_list *list);
|
2014-10-09 19:21:31 +00:00
|
|
|
|
2015-02-12 15:53:56 +00:00
|
|
|
// If set, these are called before/after ao_driver->list_devs is called.
|
|
|
|
// It is also assumed that the driver can do hotplugging - which means
|
|
|
|
// it is expected to call ao_hotplug_event(ao) whenever the system's
|
|
|
|
// audio device list changes. The player will then call list_devs() again.
|
|
|
|
int (*hotplug_init)(struct ao *ao);
|
|
|
|
void (*hotplug_uninit)(struct ao *ao);
|
|
|
|
|
2014-03-07 14:24:32 +00:00
|
|
|
// For option parsing (see vo.h)
|
|
|
|
int priv_size;
|
|
|
|
const void *priv_defaults;
|
|
|
|
const struct m_option *options;
|
2016-11-25 20:00:39 +00:00
|
|
|
const char *options_prefix;
|
2016-09-05 19:04:17 +00:00
|
|
|
const struct m_sub_options *global_opts;
|
2014-03-07 14:24:32 +00:00
|
|
|
};
|
|
|
|
|
2014-03-08 23:04:37 +00:00
|
|
|
// These functions can be called by AOs.
|
|
|
|
|
2024-04-22 11:47:34 +00:00
|
|
|
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns, bool *eof, bool pad_silence, bool blocking);
|
2014-03-08 23:04:37 +00:00
|
|
|
|
2014-03-07 14:24:32 +00:00
|
|
|
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
|
|
|
struct mp_chmap *map);
|
2016-08-04 18:49:20 +00:00
|
|
|
bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s,
|
|
|
|
struct mp_chmap *map, bool safe_multichannel);
|
2014-03-07 14:24:32 +00:00
|
|
|
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
|
|
|
struct mp_chmap *map, int num);
|
|
|
|
|
2014-10-09 19:21:31 +00:00
|
|
|
// Add a deep copy of e to the list.
|
2014-10-10 16:27:21 +00:00
|
|
|
// Call from ao_driver->list_devs callback only.
|
|
|
|
void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
|
2014-10-09 19:21:31 +00:00
|
|
|
struct ao_device_desc *e);
|
|
|
|
|
audio: add audio softvol processing to AO
This does what af_volume used to do. Since we couldn't relicense it,
just rewrite it. Since we don't have a new filter mechanism yet, and the
libavfilter is too inconvenient, do applying the volume gain in ao.c
directly. This is done before handling the audio data to the driver.
Since push.c runs a separate thread, and pull.c is called asynchronously
from the audio driver's thread, the volume value needs to be
synchronized. There's no existing central mutex, so do some shit with
atomics. Since there's no atomic_float type predefined (which is at
least needed when using the legacy wrapper), do some nonsense about
reinterpret casting the float value to an int for the purpose of atomic
access. Not sure if using memcpy() is undefined behavior, but for now I
don't care.
The advantage of not using a filter is lower complexity (no filter auto
insertion), and lower latency (gain processing is done after our
internal audio buffer of at least 200ms).
Disavdantages include inability to use native volume control _before_
other filters with custom filter chains, and the need to add new
processing for each new sample type.
Since this doesn't reuse any of the old GPL code, nor does indirectly
rely on it, volume and replaygain handling now works in LGPL mode.
How to process the gain is inspired by libavfilter's af_volume (LGPL).
In particular, we use exactly the same rounding, and we quantize
processing for integer sample types by 256 steps. Some of libavfilter's
copyright may or may not apply, but I think not, and it's the same
license anyway.
2017-11-29 20:30:10 +00:00
|
|
|
void ao_post_process_data(struct ao *ao, void **data, int num_samples);
|
|
|
|
|
2017-07-07 15:35:09 +00:00
|
|
|
struct ao_convert_fmt {
|
|
|
|
int src_fmt; // source AF_FORMAT_*
|
|
|
|
int channels; // number of channels
|
|
|
|
int dst_bits; // total target data sample size
|
|
|
|
int pad_msb; // padding in the MSB (i.e. required shifting)
|
|
|
|
int pad_lsb; // padding in LSB (required 0 bits) (ignored)
|
|
|
|
};
|
|
|
|
|
|
|
|
bool ao_can_convert_inplace(struct ao_convert_fmt *fmt);
|
|
|
|
bool ao_need_conversion(struct ao_convert_fmt *fmt);
|
|
|
|
void ao_convert_inplace(struct ao_convert_fmt *fmt, void **data, int num_samples);
|
|
|
|
|
2024-04-10 05:12:40 +00:00
|
|
|
void ao_wakeup(struct ao *ao);
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
|
2017-07-07 15:35:09 +00:00
|
|
|
int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
|
2023-10-11 18:45:40 +00:00
|
|
|
void **data, int samples, int64_t out_time_ns);
|
2017-07-07 15:35:09 +00:00
|
|
|
|
2024-04-24 00:30:07 +00:00
|
|
|
void ao_stop_streaming(struct ao *ao);
|
|
|
|
|
2014-03-07 14:24:32 +00:00
|
|
|
#endif
|