diff --git a/audio.c b/audio.c index b618250..4afb154 100644 --- a/audio.c +++ b/audio.c @@ -43,6 +43,8 @@ #endif //#define USE_ALSA ///< enable alsa support //#define USE_OSS ///< enable oss support +#define noSEARCH_HDMI_BUG +#define noSEARCH_HDMI_BUG2 #include #include @@ -88,6 +90,8 @@ #endif #endif +#include // portable atomic_t + #include "ringbuffer.h" #include "misc.h" #include "audio.h" @@ -110,6 +114,95 @@ static int64_t AudioPTS; ///< audio pts clock static pthread_cond_t AudioStartCond; ///< condition variable #endif +#ifdef SEARCH_HDMI_BUG2 + +//---------------------------------------------------------------------------- +// ring buffer +//---------------------------------------------------------------------------- + +// FIXME: use this code, to combine alsa&oss ring buffers + +#define AUDIO_RING_MAX 8 ///< number of audio ring buffers + +/** +** Audio ring buffer. +*/ +typedef struct _audio_ring_ring_ +{ + char FlushBuffers; ///< flag: flush buffers + unsigned SampleRate; ///< sample rate in hz + unsigned Channels; ///< number of channels +} AudioRingRing; + + /// ring of audio ring buffers +static AudioRingRing AudioRing[AUDIO_RING_MAX]; +static int AudioRingWrite; ///< audio ring write pointer +static int AudioRingRead; ///< audio ring read pointer +static atomic_t AudioRingFilled; ///< how many of the ring is used + +/** +** Add sample rate, number of channel change to ring. +** +** @param freq sample frequency +** @param channels number of channels +*/ +static int AudioRingAdd(int freq, int channels) +{ + int filled; + + filled = atomic_read(&AudioRingFilled); + if (filled == AUDIO_RING_MAX) { // no free slot + // FIXME: can wait for ring buffer empty + Error(_("audio: out of ring buffers\n")); + return -1; + } + AudioRing[AudioRingWrite].FlushBuffers = 1; + AudioRing[AudioRingWrite].SampleRate = freq; + AudioRing[AudioRingWrite].Channels = channels; + + AudioRingWrite = (AudioRingWrite + 1) % AUDIO_RING_MAX; + atomic_inc(&AudioRingFilled); + +#ifdef USE_AUDIO_THREAD + // tell thread, that something todo + AudioRunning = 1; + pthread_cond_signal(&AudioStartCond); +#endif + + return 0; +} + +/** +** Setup audio ring. +*/ +static void AudioRingInit(void) +{ + int i; + + for (i = 0; i < AUDIO_RING_MAX; ++i) { + // FIXME: + //AlsaRingBuffer = RingBufferNew(48000 * 8 * 2); // ~1s 8ch 16bit + } + // one slot always reservered + AudioRingWrite = 1; + atomic_set(&AudioRingFilled, 1); +} + +/** +** Cleanup audio ring. +*/ +static void AudioRingExit(void) +{ + int i; + + for (i = 0; i < AUDIO_RING_MAX; ++i) { + // FIXME: + //RingBufferDel(AlsaRingBuffer); + } +} + +#endif + #ifdef USE_ALSA //============================================================================ @@ -152,6 +245,7 @@ static int AlsaAddToRingbuffer(const void *samples, int count) if (n != count) { Error(_("audio/alsa: can't place %d samples in ring buffer\n"), count); // too many bytes are lost + // FIXME: should skip more, longer skip, but less often? } // Update audio clock AudioPTS += @@ -207,7 +301,19 @@ static int AlsaPlayRingbuffer(void) snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle))); break; } +#ifdef SEARCH_HDMI_BUG + { + uint16_t buf[8192]; + unsigned u; + for (u = 0; u < sizeof(buf) / 2; u++) { + buf[u] = random() & 0xffff; + } + + n = sizeof(buf); + p = buf; + } +#else n = RingBufferGetReadPointer(AlsaRingBuffer, &p); if (!n) { // ring buffer empty if (first) { // only error on first loop @@ -215,6 +321,7 @@ static int AlsaPlayRingbuffer(void) } return 0; } +#endif if (n < avail) { // not enough bytes in ring buffer avail = n; } @@ -229,7 +336,7 @@ static int AlsaPlayRingbuffer(void) } else { err = snd_pcm_writei(AlsaPCMHandle, p, frames); } - Debug(4, "audio/alsa: wrote %d/%d frames\n", err, frames); + //Debug(3, "audio/alsa: wrote %d/%d frames\n", err, frames); if (err != frames) { if (err < 0) { if (err == -EAGAIN) { @@ -255,6 +362,19 @@ static int AlsaPlayRingbuffer(void) return 0; } +/** +** Flush alsa buffers. +*/ +static void AlsaFlushBuffers(void) +{ + int err; + + RingBufferReadAdvance(AlsaRingBuffer, RingBufferUsedBytes(AlsaRingBuffer)); + if ((err = snd_pcm_drop(AlsaPCMHandle))) { + Error(_("audio: snd_pcm_drop(): %s\n"), snd_strerror(err)); + } +} + #if 0 //---------------------------------------------------------------------------- @@ -462,16 +582,10 @@ static void AlsaThread(void) if (AlsaFlushBuffer) { // we can flush too many, but wo cares Debug(3, "audio/alsa: flushing buffers\n"); - RingBufferReadAdvance(AlsaRingBuffer, - RingBufferUsedBytes(AlsaRingBuffer)); -#if 1 - if ((err = snd_pcm_drop(AlsaPCMHandle))) { - Error(_("audio: snd_pcm_drop(): %s\n"), snd_strerror(err)); - } + AlsaFlushBuffers(); if ((err = snd_pcm_prepare(AlsaPCMHandle))) { Error(_("audio: snd_pcm_prepare(): %s\n"), snd_strerror(err)); } -#endif AlsaFlushBuffer = 0; break; } @@ -499,8 +613,9 @@ static void AlsaThread(void) */ static void AlsaEnqueue(const void *samples, int count) { - if (!AlsaRingBuffer || !AlsaPCMHandle) { - Debug(3, "audio/alsa: not ready\n"); + if (!AlsaRingBuffer || !AlsaPCMHandle || !AudioSampleRate) { + printf("%p %p %d\n", AlsaRingBuffer, AlsaPCMHandle, AudioSampleRate); + Debug(3, "audio/alsa: enqueue not ready\n"); return; } if (AlsaAddToRingbuffer(samples, count)) { @@ -518,35 +633,48 @@ static void AlsaEnqueue(const void *samples, int count) #endif /** -** Initialize alsa pcm device. -** -** @see AudioPCMDevice +** Open alsa pcm device. */ -static void AlsaInitPCM(void) +static snd_pcm_t *AlsaOpenPCM(void) { const char *device; snd_pcm_t *handle; - snd_pcm_hw_params_t *hw_params; int err; - snd_pcm_uframes_t buffer_size; if (!(device = AudioPCMDevice)) { if (!(device = getenv("ALSA_DEVICE"))) { device = "default"; } } - // FIXME: must set alsa error output to /dev/null if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { - Fatal(_("audio/alsa: playback open '%s' error: %s\n"), device, + Error(_("audio/alsa: playback open '%s' error: %s\n"), device, snd_strerror(err)); - // FIXME: no fatal error for plugins! + return NULL; } if ((err = snd_pcm_nonblock(handle, 0)) < 0) { Error(_("audio/alsa: can't set block mode: %s\n"), snd_strerror(err)); } + return handle; +} + +/** +** Initialize alsa pcm device. +** +** @see AudioPCMDevice +*/ +static void AlsaInitPCM(void) +{ + snd_pcm_t *handle; + snd_pcm_hw_params_t *hw_params; + int err; + snd_pcm_uframes_t buffer_size; + + if (!(handle = AlsaOpenPCM())) { + return; + } snd_pcm_hw_params_alloca(&hw_params); // choose all parameters @@ -556,8 +684,7 @@ static void AlsaInitPCM(void) snd_strerror(err)); } AlsaCanPause = snd_pcm_hw_params_can_pause(hw_params); - Info(_("audio/alsa: hw '%s' supports pause: %s\n"), device, - AlsaCanPause ? "yes" : "no"); + Info(_("audio/alsa: supports pause: %s\n"), AlsaCanPause ? "yes" : "no"); snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size); Info(_("audio/alsa: max buffer size %lu\n"), buffer_size); @@ -656,7 +783,7 @@ static uint64_t AlsaGetDelay(void) snd_pcm_sframes_t delay; uint64_t pts; - if (!AlsaPCMHandle) { + if (!AlsaPCMHandle || !AudioSampleRate) { return 0UL; } // FIXME: thread safe? __assert_fail_base in snd_pcm_delay @@ -702,12 +829,14 @@ static int AlsaSetup(int *freq, int *channels) snd_pcm_uframes_t period_size; int err; int ret; + snd_pcm_t *handle; if (!AlsaPCMHandle) { // alsa not running yet return -1; } #if 1 // flush any buffered data +#ifndef SEARCH_HDMI_BUG2 #ifdef USE_AUDIO_THREAD if (AudioRunning) { while (AudioRunning) { @@ -718,11 +847,21 @@ static int AlsaSetup(int *freq, int *channels) } else #endif { - RingBufferReadAdvance(AlsaRingBuffer, - RingBufferUsedBytes(AlsaRingBuffer)); + AlsaFlushBuffers(); } +#endif AudioPTS = INT64_C(0x8000000000000000); + if (1) { // close+open to fix hdmi no sound bugs + handle = AlsaPCMHandle; + AlsaPCMHandle = NULL; + snd_pcm_close(handle); + if (!(handle = AlsaOpenPCM())) { + return -1; + } + AlsaPCMHandle = handle; + } + ret = 0; try_again: AudioChannels = *channels; @@ -844,6 +983,41 @@ static int AlsaSetup(int *freq, int *channels) // FIXME: use hw_params for buffer_size period_size #endif +#if 1 + if (0) { // no underruns allowed, play silence + snd_pcm_sw_params_t *sw_params; + snd_pcm_uframes_t boundary; + + snd_pcm_sw_params_alloca(&sw_params); + err = snd_pcm_sw_params_current(AlsaPCMHandle, sw_params); + if (err < 0) { + Error(_("audio: snd_pcm_sw_params_current failed: %s\n"), + snd_strerror(err)); + } + if ((err = snd_pcm_sw_params_get_boundary(sw_params, &boundary)) < 0) { + Error(_("audio: snd_pcm_sw_params_get_boundary failed: %s\n"), + snd_strerror(err)); + } + Debug(4, "audio/alsa: boundary %lu frames\n", boundary); + if ((err = + snd_pcm_sw_params_set_stop_threshold(AlsaPCMHandle, sw_params, + boundary)) < 0) { + Error(_("audio: snd_pcm_sw_params_set_silence_size failed: %s\n"), + snd_strerror(err)); + } + if ((err = + snd_pcm_sw_params_set_silence_size(AlsaPCMHandle, sw_params, + boundary)) < 0) { + Error(_("audio: snd_pcm_sw_params_set_silence_size failed: %s\n"), + snd_strerror(err)); + } + if ((err = snd_pcm_sw_params(AlsaPCMHandle, sw_params)) < 0) { + Error(_("audio: snd_pcm_sw_params failed: %s\n"), + snd_strerror(err)); + } + } +#endif + // update buffer snd_pcm_get_params(AlsaPCMHandle, &buffer_size, &period_size); @@ -954,6 +1128,7 @@ static int OssAddToRingbuffer(const void *samples, int count) if (n != count) { Error(_("audio/oss: can't place %d samples in ring buffer\n"), count); // too many bytes are lost + // FIXME: should skip more, longer skip, but less often? } // Update audio clock AudioPTS += @@ -1375,12 +1550,59 @@ static void *AudioPlayHandlerThread(void *dummy) Debug(3, "audio: wait on start condition\n"); pthread_mutex_lock(&AudioMutex); AudioRunning = 0; +#ifndef SEARCH_HDMI_BUG do { pthread_cond_wait(&AudioStartCond, &AudioMutex); // cond_wait can return, without signal! } while (!AudioRunning); +#else + usleep(1 * 1000); + AudioRunning = 1; +#endif pthread_mutex_unlock(&AudioMutex); +#ifdef SEARCH_HDMI_BUG2 + if (atomic_read(&AudioRingFilled) > 1) { + int sample_rate; + int channels; + + // skip all sample changes between + while (atomic_read(&AudioRingFilled) > 1) { + Debug(3, "audio: skip ring buffer\n"); + AudioRingRead = (AudioRingRead + 1) % AUDIO_RING_MAX; + atomic_dec(&AudioRingFilled); + } + +#ifdef USE_ALSA + // FIXME: flush only if there is something to flush + AlsaFlushBuffers(); + + sample_rate = AudioRing[AudioRingRead].SampleRate; + channels = AudioRing[AudioRingRead].Channels; + Debug(3, "audio: thread channels %d sample-rate %d hz\n", channels, + sample_rate); + + if (AlsaSetup(&sample_rate, &channels)) { + Error(_("audio: can't set channels %d sample-rate %d hz\n"), + channels, sample_rate); + } + Debug(3, "audio: thread channels %d sample-rate %d hz\n", + AudioChannels, AudioSampleRate); + if (1) { + int16_t buf[6144 / 2]; + + buf[0] = htole16(0xF872); // iec 61937 sync word + buf[1] = htole16(0x4E1F); + buf[2] = htole16((7 << 5) << 8 | 0x00); + buf[3] = htole16(0x0000); + memset(buf + 4, 0, 6144 - 8); + + AlsaEnqueue(buf, 6144); + } +#endif + } +#endif + Debug(3, "audio: play start\n"); #ifdef USE_ALSA AlsaThread(); @@ -1406,6 +1628,8 @@ static void AudioInitThread(void) pthread_yield(); } while (!AlsaPCMHandle); #endif + pthread_yield(); + usleep(5 * 1000); } /** @@ -1550,6 +1774,10 @@ int AudioSetup(int *freq, int *channels) // FIXME: set flag invalid setup return -1; } +#if defined(SEARCH_HDMI_BUG) || defined(SEARCH_HDMI_BUG2) + // FIXME: need to store possible combination and report this + return AudioRingAdd(*freq, *channels); +#endif #ifdef USE_ALSA return AlsaSetup(freq, channels); #endif @@ -1577,6 +1805,9 @@ void AudioInit(void) int freq; int chan; +#ifdef SEARCH_HDMI_BUG2 + AudioRingInit(); +#endif #ifdef USE_ALSA AlsaInit(); #endif @@ -1609,6 +1840,9 @@ void AudioExit(void) #ifdef USE_OSS OssExit(); #endif +#ifdef SEARCH_HDMI_BUG2 + AudioRingExit(); +#endif } #ifdef AUDIO_TEST