New audio ring buffer code, now OSS part.

This commit is contained in:
Johns 2012-04-20 16:05:40 +02:00
parent 53f22a2ed2
commit 3e39ffd5e0
2 changed files with 364 additions and 21 deletions

7
Todo
View File

@ -37,6 +37,7 @@ video:
radio no need to wait on video buffers radio no need to wait on video buffers
starting with radio and own X11 server, shows no video starting with radio and own X11 server, shows no video
some low-bandwidth tv channels have hiccups. some low-bandwidth tv channels have hiccups.
check start with 24Hz display rate
vdpau: vdpau:
software decoder path not working software decoder path not working
@ -73,10 +74,8 @@ x11:
support embedded mode support embedded mode
audio: audio:
Combine alsa+oss ringbuffer code.
Make alsa thread/polled and oss thread/polled output module runtime Make alsa thread/polled and oss thread/polled output module runtime
selectable. selectable.
software volume support (could be done with asound.conf)
Mute should do a real mute and not only set volume to zero. Mute should do a real mute and not only set volume to zero.
Starting suspended and muted, didn't register the mute. Starting suspended and muted, didn't register the mute.
Relaxed audio sync checks at end of packet and already in sync Relaxed audio sync checks at end of packet and already in sync
@ -84,12 +83,10 @@ audio:
only wait for video start, if video is running. only wait for video start, if video is running.
Not primary device, don't use and block audio/video. Not primary device, don't use and block audio/video.
multiple open of audio device, reduce them. multiple open of audio device, reduce them.
Not all channel conversions are written (f.e. 2->3 ... 5->6 ...)
audio/alsa: audio/alsa:
better downmix of >2 channels on 2 channel hardware
remix support of unsupported sample rates remix support of unsupported sample rates
libav supports only resample of mono to 2 channels
ffmpeg didn't support resample of 5 to 2 channels
audio/oss: audio/oss:
alsa oss emulation mixer "pcm" not working alsa oss emulation mixer "pcm" not working

378
audio.c
View File

@ -35,13 +35,12 @@
/// ///
/// ///
/// @todo FIXME: there can be problems with little/big endian. /// @todo FIXME: there can be problems with little/big endian.
/// @todo FIXME: can combine OSS and alsa ring buffer
/// ///
//#define USE_ALSA ///< enable alsa support //#define USE_ALSA ///< enable alsa support
//#define USE_OSS ///< enable OSS support //#define USE_OSS ///< enable OSS support
#define USE_AUDIO_THREAD ///< use thread for audio playback #define USE_AUDIO_THREAD ///< use thread for audio playback
#define USE_AUDIORING ///< new audio ring code (incomplete) #define USE_AUDIORING ///< new audio ring code (testing)
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
@ -107,12 +106,16 @@ typedef struct _audio_module_
const char *Name; ///< audio output module name const char *Name; ///< audio output module name
int (*const Thread) (void); ///< module thread handler int (*const Thread) (void); ///< module thread handler
#ifndef USE_AUDIORING
void (*const Enqueue) (const void *, int); ///< enqueue samples for output void (*const Enqueue) (const void *, int); ///< enqueue samples for output
void (*const VideoReady) (void); ///< video ready, start audio void (*const VideoReady) (void); ///< video ready, start audio
#endif
void (*const FlushBuffers) (void); ///< flush sample buffers void (*const FlushBuffers) (void); ///< flush sample buffers
#ifndef USE_AUDIORING
void (*const Poller) (void); ///< output poller void (*const Poller) (void); ///< output poller
int (*const FreeBytes) (void); ///< number of bytes free in buffer int (*const FreeBytes) (void); ///< number of bytes free in buffer
int (*const UsedBytes) (void); ///< number of bytes used in buffer int (*const UsedBytes) (void); ///< number of bytes used in buffer
#endif
int64_t(*const GetDelay) (void); ///< get current audio delay int64_t(*const GetDelay) (void); ///< get current audio delay
void (*const SetVolume) (int); ///< set output volume void (*const SetVolume) (int); ///< set output volume
int (*const Setup) (int *, int *, int); ///< setup channels, samplerate int (*const Setup) (int *, int *, int); ///< setup channels, samplerate
@ -142,13 +145,14 @@ static char AudioDoingInit; ///> flag in init, reduce error
static volatile char AudioRunning; ///< thread running / stopped static volatile char AudioRunning; ///< thread running / stopped
static volatile char AudioPaused; ///< audio paused static volatile char AudioPaused; ///< audio paused
static volatile char AudioVideoIsReady; ///< video ready start early static volatile char AudioVideoIsReady; ///< video ready start early
#ifndef USE_AUDIORING
static unsigned AudioSampleRate; ///< audio sample rate in Hz static unsigned AudioSampleRate; ///< audio sample rate in Hz
static unsigned AudioChannels; ///< number of audio channels static unsigned AudioChannels; ///< number of audio channels
static const int AudioBytesProSample = 2; ///< number of bytes per sample
static int64_t AudioPTS; ///< audio pts clock static int64_t AudioPTS; ///< audio pts clock
#ifndef USE_AUDIO_THREAD
#endif #endif
static const int AudioBytesProSample = 2; ///< number of bytes per sample
static int AudioBufferTime = 336; ///< audio buffer time in ms static int AudioBufferTime = 336; ///< audio buffer time in ms
#ifdef USE_AUDIO_THREAD #ifdef USE_AUDIO_THREAD
@ -170,7 +174,7 @@ static int AudioMaxNormalize; ///< max. normalize factor
static int AudioCompressionFactor; ///< current compression factor static int AudioCompressionFactor; ///< current compression factor
static int AudioMaxCompression; ///< max. compression factor static int AudioMaxCompression; ///< max. compression factor
static int AudioStereoDescent; ///< volume descent for stereo static int AudioStereoDescent; ///< volume descent for stereo
static int AudioVolume; ///< volume (0 .. 1000) static int AudioVolume; ///< current volume (0 .. 1000)
extern int VideoAudioDelay; ///< import audio/video delay extern int VideoAudioDelay; ///< import audio/video delay
@ -752,14 +756,15 @@ static int AlsaRatio; ///< internal -> mixer ratio * 1000
static int AlsaPlayRingbuffer(void) static int AlsaPlayRingbuffer(void)
{ {
int first; int first;
int avail;
int n;
int err;
int frames;
const void *p;
first = 1; first = 1;
for (;;) { // loop for ring buffer wrap for (;;) { // loop for ring buffer wrap
int avail;
int n;
int err;
int frames;
const void *p;
// how many bytes can be written? // how many bytes can be written?
n = snd_pcm_avail_update(AlsaPCMHandle); n = snd_pcm_avail_update(AlsaPCMHandle);
if (n < 0) { if (n < 0) {
@ -821,6 +826,7 @@ static int AlsaPlayRingbuffer(void)
if (AudioSoftVolume && !AudioRing[AudioRingRead].UseAc3) { if (AudioSoftVolume && !AudioRing[AudioRingRead].UseAc3) {
// FIXME: quick&dirty cast // FIXME: quick&dirty cast
AudioSoftAmplifier((int16_t *) p, avail); AudioSoftAmplifier((int16_t *) p, avail);
// FIXME: if not all are written, we double amplify them
} }
frames = snd_pcm_bytes_to_frames(AlsaPCMHandle, avail); frames = snd_pcm_bytes_to_frames(AlsaPCMHandle, avail);
@ -1658,9 +1664,9 @@ static int64_t AlsaGetDelay(void)
/** /**
** Setup alsa audio for requested format. ** Setup alsa audio for requested format.
** **
** @param freq sample frequency ** @param freq sample frequency
** @param channels number of channels ** @param channels number of channels
** @param use_ac3 use ac3/pass-through device ** @param use_ac3 use ac3/pass-through device
** **
** @retval 0 everything ok ** @retval 0 everything ok
** @retval 1 didn't support frequency/channels combination ** @retval 1 didn't support frequency/channels combination
@ -2209,14 +2215,104 @@ static const AudioModule AlsaModule = {
static int OssPcmFildes = -1; ///< pcm file descriptor static int OssPcmFildes = -1; ///< pcm file descriptor
static int OssMixerFildes = -1; ///< mixer file descriptor static int OssMixerFildes = -1; ///< mixer file descriptor
static int OssMixerChannel; ///< mixer channel index static int OssMixerChannel; ///< mixer channel index
static RingBuffer *OssRingBuffer; ///< audio ring buffer
static int OssFragmentTime; ///< fragment time in ms static int OssFragmentTime; ///< fragment time in ms
#ifndef USE_AUDIORING
static RingBuffer *OssRingBuffer; ///< audio ring buffer
static unsigned OssStartThreshold; ///< start play, if filled static unsigned OssStartThreshold; ///< start play, if filled
#endif
#ifdef USE_AUDIO_THREAD #ifdef USE_AUDIO_THREAD
static volatile char OssFlushBuffer; ///< flag empty buffer static volatile char OssFlushBuffer; ///< flag empty buffer
#endif #endif
#ifdef USE_AUDIORING
//----------------------------------------------------------------------------
// OSS pcm
//----------------------------------------------------------------------------
/**
** Play samples from ringbuffer.
**
** @retval 0 ok
** @retval 1 ring buffer empty
** @retval -1 underrun error
*/
static int OssPlayRingbuffer(void)
{
int first;
first = 1;
for (;;) {
audio_buf_info bi;
const void *p;
int n;
if (ioctl(OssPcmFildes, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_GETOSPACE): %s\n"),
strerror(errno));
return -1;
}
Debug(4, "audio/oss: %d bytes free\n", bi.bytes);
n = RingBufferGetReadPointer(AudioRing[AudioRingRead].RingBuffer, &p);
if (!n) { // ring buffer empty
if (first) { // only error on first loop
return 1;
}
return 0;
}
if (n < bi.bytes) { // not enough bytes in ring buffer
bi.bytes = n;
}
if (bi.bytes <= 0) { // full or buffer empty
break; // bi.bytes could become negative!
}
if (AudioSoftVolume && !AudioRing[AudioRingRead].UseAc3) {
// FIXME: quick&dirty cast
AudioSoftAmplifier((int16_t *) p, bi.bytes);
// FIXME: if not all are written, we double amplify them
}
for (;;) {
n = write(OssPcmFildes, p, bi.bytes);
if (n != bi.bytes) {
if (n < 0) {
if (n == EAGAIN) {
continue;
}
Error(_("audio/oss: write error: %s\n"), strerror(errno));
return 1;
}
Warning(_("audio/oss: error not all bytes written\n"));
}
break;
}
// advance how many could written
RingBufferReadAdvance(AudioRing[AudioRingRead].RingBuffer, n);
first = 0;
}
return 0;
}
/**
** Flush OSS buffers.
*/
static void OssFlushBuffers(void)
{
if (OssPcmFildes != -1) {
// flush kernel buffers
if (ioctl(OssPcmFildes, SNDCTL_DSP_HALT_OUTPUT, NULL) < 0) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_HALT_OUTPUT): %s\n"),
strerror(errno));
}
}
}
#else
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// OSS pcm // OSS pcm
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@ -2392,12 +2488,71 @@ static int OssUsedBytes(void)
return OssRingBuffer ? RingBufferUsedBytes(OssRingBuffer) : 0; return OssRingBuffer ? RingBufferUsedBytes(OssRingBuffer) : 0;
} }
#endif
#ifdef USE_AUDIO_THREAD #ifdef USE_AUDIO_THREAD
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// thread playback // thread playback
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
#ifdef USE_AUDIORING
/**
** OSS thread
**
** @retval -1 error
** @retval 0 underrun
** @retval 1 running
*/
static int OssThread(void)
{
int err;
if (!OssPcmFildes) {
usleep(OssFragmentTime * 1000);
return -1;
}
for (;;) {
struct pollfd fds[1];
pthread_testcancel();
if (AudioPaused) {
return 1;
}
// wait for space in kernel buffers
fds[0].fd = OssPcmFildes;
fds[0].events = POLLOUT | POLLERR;
// wait for space in kernel buffers
err = poll(fds, 1, OssFragmentTime);
if (err < 0) {
if (err == EAGAIN) {
continue;
}
Error(_("audio/oss: error poll %s\n"), strerror(errno));
usleep(OssFragmentTime * 1000);
return -1;
}
break;
}
if (!err || AudioPaused) { // timeout or some commands
return 1;
}
if ((err = OssPlayRingbuffer())) { // empty / error
if (err < 0) { // underrun error
return -1;
}
pthread_yield();
usleep(OssFragmentTime * 1000); // let fill/empty the buffers
return 0;
}
return 1;
}
#else
/** /**
** OSS thread ** OSS thread
*/ */
@ -2499,6 +2654,7 @@ static void OssThreadFlushBuffers(void)
} }
} }
#endif
#endif #endif
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@ -2519,7 +2675,10 @@ static int OssOpenPCM(int use_ac3)
&& !(device = AudioPCMDevice) && !(device = getenv("OSS_AUDIODEV"))) { && !(device = AudioPCMDevice) && !(device = getenv("OSS_AUDIODEV"))) {
device = "/dev/dsp"; device = "/dev/dsp";
} }
Info(_("audio/oss: using %sdevice '%s'\n"), use_ac3 ? "ac3" : "", device); if (!AudioDoingInit) {
Info(_("audio/oss: using %sdevice '%s'\n"), use_ac3 ? "ac3 " : "",
device);
}
if ((fildes = open(device, O_WRONLY)) < 0) { if ((fildes = open(device, O_WRONLY)) < 0) {
Error(_("audio/oss: can't open dsp device '%s': %s\n"), device, Error(_("audio/oss: can't open dsp device '%s': %s\n"), device,
@ -2626,6 +2785,171 @@ static void OssInitMixer(void)
// OSS API // OSS API
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
#ifdef USE_AUDIORING
/**
** Get OSS audio delay in time stamps.
**
** @returns audio delay in time stamps.
*/
static int64_t OssGetDelay(void)
{
int delay;
int64_t pts;
// setup failure
if (OssPcmFildes == -1 || !AudioRing[AudioRingRead].HwSampleRate) {
return 0L;
}
if (!AudioRunning) { // audio not running
Error(_("audio/oss: should not happen\n"));
return 0L;
}
// delay in bytes in kernel buffers
delay = -1;
if (ioctl(OssPcmFildes, SNDCTL_DSP_GETODELAY, &delay) == -1) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_GETODELAY): %s\n"),
strerror(errno));
return 0L;
}
if (delay < 0) {
delay = 0;
}
pts = ((int64_t) delay * 90 * 1000)
/ (AudioRing[AudioRingRead].HwSampleRate *
AudioRing[AudioRingRead].HwChannels * AudioBytesProSample);
return pts;
}
/**
** Setup OSS audio for requested format.
**
** @param sample_rate sample rate/frequency
** @param channels number of channels
** @param use_ac3 use ac3/pass-through device
**
** @retval 0 everything ok
** @retval 1 didn't support frequency/channels combination
** @retval -1 something gone wrong
*/
static int OssSetup(int *sample_rate, int *channels, int use_ac3)
{
int ret;
int tmp;
int delay;
audio_buf_info bi;
if (OssPcmFildes == -1) { // OSS not ready
// FIXME: if open fails for ac3, we never recover
return -1;
}
if (1) { // close+open for pcm / ac3
int fildes;
fildes = OssPcmFildes;
OssPcmFildes = -1;
close(fildes);
if (!(fildes = OssOpenPCM(use_ac3))) {
return -1;
}
OssPcmFildes = fildes;
}
ret = 0;
tmp = AFMT_S16_NE; // native 16 bits
if (ioctl(OssPcmFildes, SNDCTL_DSP_SETFMT, &tmp) == -1) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_SETFMT): %s\n"), strerror(errno));
// FIXME: stop player, set setup failed flag
return -1;
}
if (tmp != AFMT_S16_NE) {
Error(_("audio/oss: device doesn't support 16 bit sample format.\n"));
// FIXME: stop player, set setup failed flag
return -1;
}
tmp = *channels;
if (ioctl(OssPcmFildes, SNDCTL_DSP_CHANNELS, &tmp) == -1) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_CHANNELS): %s\n"),
strerror(errno));
return -1;
}
if (tmp != *channels) {
Warning(_("audio/oss: device doesn't support %d channels.\n"),
*channels);
*channels = tmp;
ret = 1;
}
tmp = *sample_rate;
if (ioctl(OssPcmFildes, SNDCTL_DSP_SPEED, &tmp) == -1) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_SPEED): %s\n"), strerror(errno));
return -1;
}
if (tmp != *sample_rate) {
Warning(_("audio/oss: device doesn't support %dHz sample rate.\n"),
*sample_rate);
*sample_rate = tmp;
ret = 1;
}
#ifdef SNDCTL_DSP_POLICY
tmp = 3;
if (ioctl(OssPcmFildes, SNDCTL_DSP_POLICY, &tmp) == -1) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_POLICY): %s\n"), strerror(errno));
} else {
Info("audio/oss: set policy to %d\n", tmp);
}
#endif
if (ioctl(OssPcmFildes, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
Error(_("audio/oss: ioctl(SNDCTL_DSP_GETOSPACE): %s\n"),
strerror(errno));
bi.fragsize = 4096;
bi.fragstotal = 16;
} else {
Debug(3, "audio/oss: %d bytes buffered\n", bi.bytes);
}
OssFragmentTime = (bi.fragsize * 1000)
/ (*sample_rate * *channels * AudioBytesProSample);
Debug(3, "audio/oss: buffer size %d %dms, fragment size %d %dms\n",
bi.fragsize * bi.fragstotal, (bi.fragsize * bi.fragstotal * 1000)
/ (*sample_rate * *channels * AudioBytesProSample), bi.fragsize,
OssFragmentTime);
// start when enough bytes for initial write
AudioStartThreshold = (bi.fragsize - 1) * bi.fragstotal;
// buffer time/delay in ms
delay = AudioBufferTime + 300;
if (VideoAudioDelay > 0) {
delay += VideoAudioDelay / 90;
}
if (AudioStartThreshold <
(*sample_rate * *channels * AudioBytesProSample * delay) / 1000U) {
AudioStartThreshold =
(*sample_rate * *channels * AudioBytesProSample * delay) / 1000U;
}
// no bigger, than 1/3 the buffer
if (AudioStartThreshold > AudioRingBufferSize / 3) {
AudioStartThreshold = AudioRingBufferSize / 3;
}
if (!AudioDoingInit) {
Info(_("audio/oss: delay %ums\n"), (AudioStartThreshold * 1000)
/ (*sample_rate * *channels * AudioBytesProSample));
}
return ret;
}
#else
/** /**
** Get OSS audio delay in time stamps. ** Get OSS audio delay in time stamps.
** **
@ -2795,6 +3119,8 @@ static int OssSetup(int *freq, int *channels, int use_ac3)
return ret; return ret;
} }
#endif
/** /**
** Play audio. ** Play audio.
*/ */
@ -2814,7 +3140,9 @@ void OssPause(void)
*/ */
static void OssInit(void) static void OssInit(void)
{ {
#ifndef USE_AUDIORING
OssRingBuffer = RingBufferNew(AudioRingBufferSize); OssRingBuffer = RingBufferNew(AudioRingBufferSize);
#endif
OssInitPCM(); OssInitPCM();
OssInitMixer(); OssInitMixer();
@ -2843,17 +3171,25 @@ static const AudioModule OssModule = {
.Name = "oss", .Name = "oss",
#ifdef USE_AUDIO_THREAD #ifdef USE_AUDIO_THREAD
.Thread = OssThread, .Thread = OssThread,
#ifdef USE_AUDIORING
//.Enqueue = OssThreadEnqueue,
//.VideoReady = OssVideoReady,
.FlushBuffers = OssFlushBuffers,
#else
.Enqueue = OssThreadEnqueue, .Enqueue = OssThreadEnqueue,
.VideoReady = OssVideoReady, .VideoReady = OssVideoReady,
.FlushBuffers = OssThreadFlushBuffers, .FlushBuffers = OssThreadFlushBuffers,
#endif
#else #else
.Enqueue = OssEnqueue, .Enqueue = OssEnqueue,
.VideoReady = OssVideoReady, .VideoReady = OssVideoReady,
.FlushBuffers = OssFlushBuffers, .FlushBuffers = OssFlushBuffers,
#endif #endif
#ifndef USE_AUDIORING
.Poller = OssPoller, .Poller = OssPoller,
.FreeBytes = OssFreeBytes, .FreeBytes = OssFreeBytes,
.UsedBytes = OssUsedBytes, .UsedBytes = OssUsedBytes,
#endif
.GetDelay = OssGetDelay, .GetDelay = OssGetDelay,
.SetVolume = OssSetVolume, .SetVolume = OssSetVolume,
.Setup = OssSetup, .Setup = OssSetup,
@ -2869,6 +3205,8 @@ static const AudioModule OssModule = {
// Noop // Noop
//============================================================================ //============================================================================
#ifndef USE_AUDIORING
/** /**
** Noop enqueue samples. ** Noop enqueue samples.
** **
@ -2897,6 +3235,8 @@ static int NoopUsedBytes(void)
return 0; // no driver, nothing used return 0; // no driver, nothing used
} }
#endif
/** /**
** Get audio delay in time stamps. ** Get audio delay in time stamps.
** **
@ -2943,12 +3283,16 @@ static void NoopVoid(void)
*/ */
static const AudioModule NoopModule = { static const AudioModule NoopModule = {
.Name = "noop", .Name = "noop",
#ifndef USE_AUDIORING
.Enqueue = NoopEnqueue, .Enqueue = NoopEnqueue,
.VideoReady = NoopVoid, .VideoReady = NoopVoid,
#endif
.FlushBuffers = NoopVoid, .FlushBuffers = NoopVoid,
#ifndef USE_AUDIORING
.Poller = NoopVoid, .Poller = NoopVoid,
.FreeBytes = NoopFreeBytes, .FreeBytes = NoopFreeBytes,
.UsedBytes = NoopUsedBytes, .UsedBytes = NoopUsedBytes,
#endif
.GetDelay = NoopGetDelay, .GetDelay = NoopGetDelay,
.SetVolume = NoopSetVolume, .SetVolume = NoopSetVolume,
.Setup = NoopSetup, .Setup = NoopSetup,
@ -3388,7 +3732,9 @@ void AudioFlushBuffers(void)
*/ */
void AudioPoller(void) void AudioPoller(void)
{ {
#ifndef USE_AUDIORING
AudioUsedModule->Poller(); AudioUsedModule->Poller();
#endif
} }
/** /**