mirror of
https://projects.vdr-developer.org/git/vdr-plugin-softhddevice.git
synced 2023-10-10 19:16:51 +02:00
2053 lines
49 KiB
C
2053 lines
49 KiB
C
///
|
|
/// @file audio.c @brief Audio module
|
|
///
|
|
/// Copyright (c) 2009 - 2012 by Johns. All Rights Reserved.
|
|
///
|
|
/// Contributor(s):
|
|
///
|
|
/// License: AGPLv3
|
|
///
|
|
/// This program is free software: you can redistribute it and/or modify
|
|
/// it under the terms of the GNU Affero General Public License as
|
|
/// published by the Free Software Foundation, either version 3 of the
|
|
/// License.
|
|
///
|
|
/// This program is distributed in the hope that it will be useful,
|
|
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
/// GNU Affero General Public License for more details.
|
|
///
|
|
/// $Id$
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
///
|
|
/// @defgroup Audio The audio module.
|
|
///
|
|
/// This module contains all audio output functions.
|
|
///
|
|
/// ALSA PCM/Mixer api is supported.
|
|
/// @see http://www.alsa-project.org/alsa-doc/alsa-lib
|
|
///
|
|
/// @note alsa async playback is broken, don't use it!
|
|
///
|
|
/// OSS PCM/Mixer api is supported.
|
|
/// @see http://manuals.opensound.com/developer/
|
|
///
|
|
///
|
|
/// @todo FIXME: there can be problems with little/big endian.
|
|
/// @todo FIXME: can combine oss and alsa ring buffer
|
|
///
|
|
|
|
#ifdef USE_ALSA // only with alsa supported
|
|
#define USE_AUDIO_THREAD ///< use thread for audio playback
|
|
#endif
|
|
//#define USE_ALSA ///< enable alsa support
|
|
//#define USE_OSS ///< enable oss support
|
|
#define noSEARCH_HDMI_BUG
|
|
#define noSEARCH_HDMI_BUG2
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
|
|
#include <libintl.h>
|
|
#define _(str) gettext(str) ///< gettext shortcut
|
|
#define _N(str) str ///< gettext_noop shortcut
|
|
|
|
#ifdef USE_ALSA
|
|
#include <alsa/asoundlib.h>
|
|
#endif
|
|
#ifdef USE_OSS
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/soundcard.h>
|
|
// SNDCTL_DSP_HALT_OUTPUT compatibility
|
|
#ifndef SNDCTL_DSP_HALT_OUTPUT
|
|
# if defined(SNDCTL_DSP_RESET_OUTPUT)
|
|
# define SNDCTL_DSP_HALT_OUTPUT SNDCTL_DSP_RESET_OUTPUT
|
|
# elif defined(SNDCTL_DSP_RESET)
|
|
# define SNDCTL_DSP_HALT_OUTPUT SNDCTL_DSP_RESET
|
|
# else
|
|
# error "No valid SNDCTL_DSP_HALT_OUTPUT found."
|
|
# endif
|
|
#endif
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#ifdef USE_AUDIO_THREAD
|
|
#ifndef __USE_GNU
|
|
#define __USE_GNU
|
|
#endif
|
|
#include <pthread.h>
|
|
#ifndef HAVE_PTHREAD_NAME
|
|
/// only available with newer glibc
|
|
#define pthread_setname_np(thread, name)
|
|
#endif
|
|
#endif
|
|
|
|
#include <alsa/iatomic.h> // portable atomic_t
|
|
|
|
#include "ringbuffer.h"
|
|
#include "misc.h"
|
|
#include "audio.h"
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Variables
|
|
//----------------------------------------------------------------------------
|
|
|
|
static const char *AudioPCMDevice; ///< alsa/oss PCM device name
|
|
static const char *AudioMixerDevice; ///< alsa/oss mixer device name
|
|
static const char *AudioMixerChannel; ///< alsa/oss mixer channel name
|
|
static volatile char AudioRunning; ///< thread running / stopped
|
|
static int AudioPaused; ///< audio paused
|
|
static unsigned AudioSampleRate; ///< audio sample rate in hz
|
|
static unsigned AudioChannels; ///< number of audio channels
|
|
static const int AudioBytesProSample = 2; ///< number of bytes per sample
|
|
static int64_t AudioPTS; ///< audio pts clock
|
|
|
|
#ifdef USE_AUDIO_THREAD
|
|
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
|
|
|
|
//============================================================================
|
|
// A L S A
|
|
//============================================================================
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Alsa variables
|
|
//----------------------------------------------------------------------------
|
|
|
|
static snd_pcm_t *AlsaPCMHandle; ///< alsa pcm handle
|
|
static char AlsaCanPause; ///< hw supports pause
|
|
static int AlsaUseMmap; ///< use mmap
|
|
|
|
static RingBuffer *AlsaRingBuffer; ///< audio ring buffer
|
|
static unsigned AlsaStartThreshold; ///< start play, if filled
|
|
static volatile char AlsaFlushBuffer; ///< flag empty buffer
|
|
|
|
static snd_mixer_t *AlsaMixer; ///< alsa mixer handle
|
|
static snd_mixer_elem_t *AlsaMixerElem; ///< alsa pcm mixer element
|
|
static int AlsaRatio; ///< internal -> mixer ratio * 1000
|
|
|
|
//----------------------------------------------------------------------------
|
|
// alsa pcm
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Place samples in ringbuffer.
|
|
**
|
|
** @param samples sample buffer
|
|
** @param count number of bytes in sample buffer
|
|
**
|
|
** @returns true if play should be started.
|
|
*/
|
|
static int AlsaAddToRingbuffer(const void *samples, int count)
|
|
{
|
|
int n;
|
|
|
|
n = RingBufferWrite(AlsaRingBuffer, samples, 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 +=
|
|
((int64_t) count * 90000) / (AudioSampleRate * AudioChannels *
|
|
AudioBytesProSample);
|
|
|
|
if (!AudioRunning) {
|
|
if (AlsaStartThreshold < RingBufferUsedBytes(AlsaRingBuffer)) {
|
|
// restart play-back
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
** Play samples from ringbuffer.
|
|
*/
|
|
static int AlsaPlayRingbuffer(void)
|
|
{
|
|
int first;
|
|
int avail;
|
|
int n;
|
|
int err;
|
|
int frames;
|
|
const void *p;
|
|
|
|
first = 1;
|
|
for (;;) {
|
|
// how many bytes can be written?
|
|
n = snd_pcm_avail_update(AlsaPCMHandle);
|
|
if (n < 0) {
|
|
if (n == -EAGAIN) {
|
|
continue;
|
|
}
|
|
Error(_("audio/alsa: underrun error?\n"));
|
|
err = snd_pcm_recover(AlsaPCMHandle, n, 0);
|
|
if (err >= 0) {
|
|
continue;
|
|
}
|
|
Error(_("audio/alsa: snd_pcm_avail_update(): %s\n"),
|
|
snd_strerror(n));
|
|
return -1;
|
|
}
|
|
avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, n);
|
|
if (avail < 256) { // too much overhead
|
|
if (first) {
|
|
// happens with broken alsa drivers
|
|
Error(_("audio/alsa: broken driver %d\n"), avail);
|
|
usleep(5 * 1000);
|
|
}
|
|
Debug(4, "audio/alsa: break state %s\n",
|
|
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
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
if (n < avail) { // not enough bytes in ring buffer
|
|
avail = n;
|
|
}
|
|
if (!avail) { // full or buffer empty
|
|
break;
|
|
}
|
|
frames = snd_pcm_bytes_to_frames(AlsaPCMHandle, avail);
|
|
|
|
again:
|
|
if (AlsaUseMmap) {
|
|
err = snd_pcm_mmap_writei(AlsaPCMHandle, p, frames);
|
|
} else {
|
|
err = snd_pcm_writei(AlsaPCMHandle, p, frames);
|
|
}
|
|
//Debug(3, "audio/alsa: wrote %d/%d frames\n", err, frames);
|
|
if (err != frames) {
|
|
if (err < 0) {
|
|
if (err == -EAGAIN) {
|
|
goto again;
|
|
}
|
|
/*
|
|
if (err == -EBADFD) {
|
|
goto again;
|
|
}
|
|
*/
|
|
Error(_("audio/alsa: underrun error?\n"));
|
|
err = snd_pcm_recover(AlsaPCMHandle, err, 0);
|
|
if (err >= 0) {
|
|
goto again;
|
|
}
|
|
Error(_("audio/alsa: snd_pcm_writei failed: %s\n"),
|
|
snd_strerror(err));
|
|
return -1;
|
|
}
|
|
// this could happen, if underrun happened
|
|
Error(_("audio/alsa: error not all frames written\n"));
|
|
avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, err);
|
|
}
|
|
RingBufferReadAdvance(AlsaRingBuffer, avail);
|
|
first = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
** Flush alsa buffers.
|
|
*/
|
|
static void AlsaFlushBuffers(void)
|
|
{
|
|
int err;
|
|
snd_pcm_state_t state;
|
|
|
|
RingBufferReadAdvance(AlsaRingBuffer, RingBufferUsedBytes(AlsaRingBuffer));
|
|
state = snd_pcm_state(AlsaPCMHandle);
|
|
Debug(3, "audio/alsa: state %d - %s\n", state, snd_pcm_state_name(state));
|
|
if (state != SND_PCM_STATE_OPEN) {
|
|
if ((err = snd_pcm_drop(AlsaPCMHandle)) < 0) {
|
|
Error(_("audio: snd_pcm_drop(): %s\n"), snd_strerror(err));
|
|
}
|
|
// ****ing alsa crash, when in open state here
|
|
if ((err = snd_pcm_prepare(AlsaPCMHandle)) < 0) {
|
|
Error(_("audio: snd_pcm_prepare(): %s\n"), snd_strerror(err));
|
|
}
|
|
}
|
|
AudioPTS = INT64_C(0x8000000000000000);
|
|
}
|
|
|
|
#if 0
|
|
|
|
//----------------------------------------------------------------------------
|
|
// async playback
|
|
//----------------------------------------------------------------------------
|
|
|
|
// async playback is broken, don't use it!
|
|
|
|
/**
|
|
** Alsa async pcm callback function.
|
|
**
|
|
** @param handler alsa async handler
|
|
*/
|
|
static void AlsaAsyncCallback(snd_async_handler_t * handler)
|
|
{
|
|
|
|
Debug(3, "audio/%s: %p\n", __FUNCTION__, handler);
|
|
|
|
// how many bytes can be written?
|
|
for (;;) {
|
|
n = snd_pcm_avail_update(AlsaPCMHandle);
|
|
if (n < 0) {
|
|
Error(_("audio/alsa: snd_pcm_avail_update(): %s\n"),
|
|
snd_strerror(n));
|
|
break;
|
|
}
|
|
avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, n);
|
|
if (avail < 512) { // too much overhead
|
|
break;
|
|
}
|
|
|
|
n = RingBufferGetReadPointer(AlsaRingBuffer, &p);
|
|
if (!n) { // ring buffer empty
|
|
Debug(3, "audio/alsa: ring buffer empty\n");
|
|
break;
|
|
}
|
|
if (n < avail) { // not enough bytes in ring buffer
|
|
avail = n;
|
|
}
|
|
if (!avail) { // full
|
|
break;
|
|
}
|
|
frames = snd_pcm_bytes_to_frames(AlsaPCMHandle, avail);
|
|
|
|
again:
|
|
if (AlsaUseMmap) {
|
|
err = snd_pcm_mmap_writei(AlsaPCMHandle, p, frames);
|
|
} else {
|
|
err = snd_pcm_writei(AlsaPCMHandle, p, frames);
|
|
}
|
|
Debug(3, "audio/alsa: %d => %d\n", frames, err);
|
|
if (err < 0) {
|
|
Error(_("audio/alsa: underrun error?\n"));
|
|
err = snd_pcm_recover(AlsaPCMHandle, err, 0);
|
|
if (err >= 0) {
|
|
goto again;
|
|
}
|
|
Error(_("audio/alsa: snd_pcm_writei failed: %s\n"),
|
|
snd_strerror(err));
|
|
}
|
|
if (err != frames) {
|
|
Error(_("audio/alsa: error not all frames written\n"));
|
|
avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, err);
|
|
}
|
|
RingBufferReadAdvance(AlsaRingBuffer, avail);
|
|
}
|
|
}
|
|
|
|
/**
|
|
** Place samples in audio output queue.
|
|
**
|
|
** @param samples sample buffer
|
|
** @param count number of bytes in sample buffer
|
|
*/
|
|
static void AlsaEnqueue(const void *samples, int count)
|
|
{
|
|
snd_pcm_state_t state;
|
|
int n;
|
|
|
|
//int err;
|
|
|
|
Debug(3, "audio: %6zd + %4d\n", RingBufferUsedBytes(AlsaRingBuffer),
|
|
count);
|
|
n = RingBufferWrite(AlsaRingBuffer, samples, count);
|
|
if (n != count) {
|
|
Fatal(_("audio: can't place %d samples in ring buffer\n"), count);
|
|
}
|
|
// check if running, wait until enough buffered
|
|
state = snd_pcm_state(AlsaPCMHandle);
|
|
if (state == SND_PCM_STATE_PREPARED) {
|
|
Debug(3, "audio/alsa: state %d - %s\n", state,
|
|
snd_pcm_state_name(state));
|
|
// FIXME: adjust start ratio
|
|
if (RingBufferFreeBytes(AlsaRingBuffer)
|
|
< RingBufferUsedBytes(AlsaRingBuffer)) {
|
|
// restart play-back
|
|
#if 0
|
|
if (AlsaCanPause) {
|
|
if ((err = snd_pcm_pause(AlsaPCMHandle, 0))) {
|
|
Error(_("audio: snd_pcm_pause(): %s\n"),
|
|
snd_strerror(err));
|
|
}
|
|
} else {
|
|
if ((err = snd_pcm_prepare(AlsaPCMHandle)) < 0) {
|
|
Error(_("audio: snd_pcm_prepare(): %s\n"),
|
|
snd_strerror(err));
|
|
}
|
|
}
|
|
if ((err = snd_pcm_prepare(AlsaPCMHandle)) < 0) {
|
|
Error(_("audio: snd_pcm_prepare(): %s\n"), snd_strerror(err));
|
|
}
|
|
|
|
Debug(3, "audio/alsa: unpaused\n");
|
|
if ((err = snd_pcm_start(AlsaPCMHandle)) < 0) {
|
|
Error(_("audio: snd_pcm_start(): %s\n"), snd_strerror(err));
|
|
}
|
|
#endif
|
|
state = snd_pcm_state(AlsaPCMHandle);
|
|
Debug(3, "audio/alsa: state %s\n", snd_pcm_state_name(state));
|
|
Debug(3, "audio/alsa: unpaused\n");
|
|
AudioPaused = 0;
|
|
}
|
|
}
|
|
// Update audio clock
|
|
// AudioPTS += (size * 90000) / (AudioSampleRate * AudioChannels * AudioBytesProSample);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if 0
|
|
|
|
//----------------------------------------------------------------------------
|
|
// direct playback
|
|
//----------------------------------------------------------------------------
|
|
|
|
// direct play produces underuns on some hardware
|
|
|
|
/**
|
|
** Place samples in audio output queue.
|
|
**
|
|
** @param samples sample buffer
|
|
** @param count number of bytes in sample buffer
|
|
*/
|
|
static void AlsaEnqueue(const void *samples, int count)
|
|
{
|
|
snd_pcm_state_t state;
|
|
int avail;
|
|
int n;
|
|
int err;
|
|
int frames;
|
|
const void *p;
|
|
|
|
Debug(3, "audio/alsa: %6zd + %4d\n", RingBufferUsedBytes(AlsaRingBuffer),
|
|
count);
|
|
n = RingBufferWrite(AlsaRingBuffer, samples, count);
|
|
if (n != count) {
|
|
Error(_("audio/alsa: can't place %d samples in ring buffer\n"), count);
|
|
}
|
|
// check if running, wait until enough buffered
|
|
state = snd_pcm_state(AlsaPCMHandle);
|
|
Debug(4, "audio/alsa: state %d - %s\n", state, snd_pcm_state_name(state));
|
|
if (state == SND_PCM_STATE_PREPARED) {
|
|
// FIXME: adjust start ratio
|
|
if (RingBufferFreeBytes(AlsaRingBuffer)
|
|
> RingBufferUsedBytes(AlsaRingBuffer)) {
|
|
return;
|
|
}
|
|
Debug(3, "audio/alsa: state %d - %s start play\n", state,
|
|
snd_pcm_state_name(state));
|
|
}
|
|
// Update audio clock
|
|
AudioPTS +=
|
|
(size * 90000) / (AudioSampleRate * AudioChannels *
|
|
AudioBytesProSample);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef USE_AUDIO_THREAD
|
|
|
|
//----------------------------------------------------------------------------
|
|
// thread playback
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Alsa thread
|
|
*/
|
|
static void AlsaThread(void)
|
|
{
|
|
for (;;) {
|
|
int err;
|
|
|
|
pthread_testcancel();
|
|
if (AlsaFlushBuffer) {
|
|
// we can flush too many, but wo cares
|
|
Debug(3, "audio/alsa: flushing buffers\n");
|
|
AlsaFlushBuffers();
|
|
/*
|
|
if ((err = snd_pcm_prepare(AlsaPCMHandle))) {
|
|
Error(_("audio: snd_pcm_prepare(): %s\n"), snd_strerror(err));
|
|
}
|
|
*/
|
|
AlsaFlushBuffer = 0;
|
|
break;
|
|
}
|
|
// wait for space in kernel buffers
|
|
if ((err = snd_pcm_wait(AlsaPCMHandle, 100)) < 0) {
|
|
Error(_("audio/alsa: wait underrun error?\n"));
|
|
err = snd_pcm_recover(AlsaPCMHandle, err, 0);
|
|
if (err >= 0) {
|
|
continue;
|
|
}
|
|
Error(_("audio/alsa: snd_pcm_wait(): %s\n"), snd_strerror(err));
|
|
usleep(100 * 1000);
|
|
continue;
|
|
}
|
|
if (AlsaFlushBuffer) {
|
|
continue;
|
|
}
|
|
if ((err = AlsaPlayRingbuffer())) { // empty / error
|
|
snd_pcm_state_t state;
|
|
|
|
if (err < 0) { // underrun error
|
|
break;
|
|
}
|
|
state = snd_pcm_state(AlsaPCMHandle);
|
|
if (state != SND_PCM_STATE_RUNNING) {
|
|
Debug(3, "audio/alsa: stopping play\n");
|
|
break;
|
|
}
|
|
pthread_yield();
|
|
usleep(20 * 1000); // let fill the buffers
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
** Place samples in audio output queue.
|
|
**
|
|
** @param samples sample buffer
|
|
** @param count number of bytes in sample buffer
|
|
*/
|
|
static void AlsaEnqueue(const void *samples, int count)
|
|
{
|
|
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)) {
|
|
snd_pcm_state_t state;
|
|
|
|
state = snd_pcm_state(AlsaPCMHandle);
|
|
Debug(3, "audio/alsa: enqueue state %s\n", snd_pcm_state_name(state));
|
|
|
|
// no lock needed, can wakeup next time
|
|
AudioRunning = 1;
|
|
pthread_cond_signal(&AudioStartCond);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
** Open alsa pcm device.
|
|
*/
|
|
static snd_pcm_t *AlsaOpenPCM(void)
|
|
{
|
|
const char *device;
|
|
snd_pcm_t *handle;
|
|
int err;
|
|
|
|
if (!(device = AudioPCMDevice)) {
|
|
if (!(device = getenv("ALSA_DEVICE"))) {
|
|
device = "default";
|
|
}
|
|
}
|
|
if ((err =
|
|
snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK,
|
|
SND_PCM_NONBLOCK)) < 0) {
|
|
Error(_("audio/alsa: playback open '%s' error: %s\n"), device,
|
|
snd_strerror(err));
|
|
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
|
|
if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {
|
|
Error(_
|
|
("audio: snd_pcm_hw_params_any: no configurations available: %s\n"),
|
|
snd_strerror(err));
|
|
}
|
|
AlsaCanPause = snd_pcm_hw_params_can_pause(hw_params);
|
|
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);
|
|
|
|
AlsaPCMHandle = handle;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Alsa Mixer
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Set alsa mixer volume (0-100)
|
|
**
|
|
** @param volume volume (0 .. 100)
|
|
*/
|
|
static void AlsaSetVolume(int volume)
|
|
{
|
|
int v;
|
|
|
|
if (AlsaMixer && AlsaMixerElem) {
|
|
v = (volume * AlsaRatio) / 1000;
|
|
snd_mixer_selem_set_playback_volume(AlsaMixerElem, 0, v);
|
|
snd_mixer_selem_set_playback_volume(AlsaMixerElem, 1, v);
|
|
}
|
|
}
|
|
|
|
/**
|
|
** Initialize alsa mixer.
|
|
*/
|
|
static void AlsaInitMixer(void)
|
|
{
|
|
const char *device;
|
|
const char *channel;
|
|
snd_mixer_t *alsa_mixer;
|
|
snd_mixer_elem_t *alsa_mixer_elem;
|
|
long alsa_mixer_elem_min;
|
|
long alsa_mixer_elem_max;
|
|
|
|
if (!(device = AudioMixerDevice)) {
|
|
if (!(device = getenv("ALSA_MIXER"))) {
|
|
device = "default";
|
|
}
|
|
}
|
|
if (!(channel = AudioMixerChannel)) {
|
|
if (!(channel = getenv("ALSA_MIXER_CHANNEL"))) {
|
|
channel = "PCM";
|
|
}
|
|
}
|
|
Debug(3, "audio/alsa: mixer %s - %s open\n", device, channel);
|
|
snd_mixer_open(&alsa_mixer, 0);
|
|
if (alsa_mixer && snd_mixer_attach(alsa_mixer, device) >= 0
|
|
&& snd_mixer_selem_register(alsa_mixer, NULL, NULL) >= 0
|
|
&& snd_mixer_load(alsa_mixer) >= 0) {
|
|
|
|
const char *const alsa_mixer_elem_name = channel;
|
|
|
|
alsa_mixer_elem = snd_mixer_first_elem(alsa_mixer);
|
|
while (alsa_mixer_elem) {
|
|
const char *name;
|
|
|
|
name = snd_mixer_selem_get_name(alsa_mixer_elem);
|
|
if (strcasecmp(name, alsa_mixer_elem_name) == 0) {
|
|
snd_mixer_selem_get_playback_volume_range(alsa_mixer_elem,
|
|
&alsa_mixer_elem_min, &alsa_mixer_elem_max);
|
|
AlsaRatio =
|
|
(1000 * (alsa_mixer_elem_max - alsa_mixer_elem_min)) / 100;
|
|
Debug(3, "audio/alsa: PCM mixer found %ld - %ld ratio %d\n",
|
|
alsa_mixer_elem_min, alsa_mixer_elem_max, AlsaRatio);
|
|
break;
|
|
}
|
|
|
|
alsa_mixer_elem = snd_mixer_elem_next(alsa_mixer_elem);
|
|
}
|
|
|
|
AlsaMixer = alsa_mixer;
|
|
AlsaMixerElem = alsa_mixer_elem;
|
|
} else {
|
|
Error(_("audio/alsa: can't open mixer '%s'\n"), device);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Alsa API
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Get alsa audio delay in time stamps.
|
|
**
|
|
** @returns audio delay in time stamps.
|
|
**
|
|
** @todo FIXME: handle the case no audio running
|
|
*/
|
|
static uint64_t AlsaGetDelay(void)
|
|
{
|
|
int err;
|
|
snd_pcm_sframes_t delay;
|
|
uint64_t pts;
|
|
|
|
if (!AlsaPCMHandle || !AudioSampleRate) {
|
|
return 0UL;
|
|
}
|
|
// FIXME: thread safe? __assert_fail_base in snd_pcm_delay
|
|
|
|
// delay in frames in alsa + kernel buffers
|
|
if ((err = snd_pcm_delay(AlsaPCMHandle, &delay)) < 0) {
|
|
//Debug(3, "audio/alsa: no hw delay\n");
|
|
delay = 0L;
|
|
} else if (snd_pcm_state(AlsaPCMHandle) != SND_PCM_STATE_RUNNING) {
|
|
//Debug(3, "audio/alsa: %ld frames delay ok, but not running\n", delay);
|
|
}
|
|
//Debug(3, "audio/alsa: %ld frames hw delay\n", delay);
|
|
|
|
// delay can be negative when underrun occur
|
|
if (delay < 0) {
|
|
delay = 0L;
|
|
}
|
|
|
|
pts = ((uint64_t) delay * 90 * 1000) / AudioSampleRate;
|
|
pts += ((uint64_t) RingBufferUsedBytes(AlsaRingBuffer) * 90 * 1000)
|
|
/ (AudioSampleRate * AudioChannels * AudioBytesProSample);
|
|
Debug(4, "audio/alsa: hw+sw delay %zd %" PRId64 " ms\n",
|
|
RingBufferUsedBytes(AlsaRingBuffer), pts / 90);
|
|
|
|
return pts;
|
|
}
|
|
|
|
/**
|
|
** Setup alsa audio for requested format.
|
|
**
|
|
** @param freq sample frequency
|
|
** @param channels number of channels
|
|
**
|
|
** @retval 0 everything ok
|
|
** @retval 1 didn't support frequency/channels combination
|
|
** @retval -1 something gone wrong
|
|
**
|
|
** @todo audio changes must be queued and done when the buffer is empty
|
|
*/
|
|
static int AlsaSetup(int *freq, int *channels)
|
|
{
|
|
snd_pcm_uframes_t buffer_size;
|
|
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) {
|
|
AlsaFlushBuffer = 1;
|
|
usleep(1 * 1000);
|
|
}
|
|
AlsaFlushBuffer = 0;
|
|
} else
|
|
#endif
|
|
{
|
|
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;
|
|
AudioSampleRate = *freq;
|
|
|
|
if ((err =
|
|
snd_pcm_set_params(AlsaPCMHandle, SND_PCM_FORMAT_S16,
|
|
AlsaUseMmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED :
|
|
SND_PCM_ACCESS_RW_INTERLEAVED, *channels, *freq, 1,
|
|
125 * 1000))) {
|
|
Error(_("audio/alsa: set params error: %s\n"), snd_strerror(err));
|
|
|
|
/*
|
|
if ( err == -EBADFD ) {
|
|
snd_pcm_close(AlsaPCMHandle);
|
|
AlsaPCMHandle = NULL;
|
|
goto try_again;
|
|
}
|
|
*/
|
|
|
|
switch (*channels) {
|
|
case 1:
|
|
// FIXME: enable channel upmix
|
|
ret = 1;
|
|
*channels = 2;
|
|
goto try_again;
|
|
case 2:
|
|
return -1;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
// FIXME: enable channel downmix
|
|
// FIXME: try 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2
|
|
ret = 1;
|
|
*channels = 2;
|
|
goto try_again;
|
|
default:
|
|
Error(_("audio/alsa: unsupported number of channels\n"));
|
|
// FIXME: must stop sound, AudioChannels ... invalid
|
|
return -1;
|
|
}
|
|
}
|
|
#else
|
|
//
|
|
// complex way to setup parameters
|
|
//
|
|
snd_pcm_hw_params_t *hw_params;
|
|
int dir;
|
|
unsigned buffer_time;
|
|
snd_pcm_uframes_t buffer_size;
|
|
|
|
snd_pcm_hw_params_alloca(&hw_params);
|
|
// choose all parameters
|
|
if ((err = snd_pcm_hw_params_any(AlsaPCMHandle, hw_params)) < 0) {
|
|
Error(_
|
|
("audio: snd_pcm_hw_params_any: no configurations available: %s\n"),
|
|
snd_strerror(err));
|
|
}
|
|
|
|
if ((err =
|
|
snd_pcm_hw_params_set_rate_resample(AlsaPCMHandle, hw_params, 1))
|
|
< 0) {
|
|
Error(_("audio: can't set rate resample: %s\n"), snd_strerror(err));
|
|
}
|
|
if ((err =
|
|
snd_pcm_hw_params_set_format(AlsaPCMHandle, hw_params,
|
|
SND_PCM_FORMAT_S16)) < 0) {
|
|
Error(_("audio: can't set 16-bit: %s\n"), snd_strerror(err));
|
|
}
|
|
if ((err =
|
|
snd_pcm_hw_params_set_access(AlsaPCMHandle, hw_params,
|
|
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
|
Error(_("audio: can't set interleaved read/write %s\n"),
|
|
snd_strerror(err));
|
|
}
|
|
if ((err =
|
|
snd_pcm_hw_params_set_channels(AlsaPCMHandle, hw_params,
|
|
channels)) < 0) {
|
|
Error(_("audio: can't set channels: %s\n"), snd_strerror(err));
|
|
}
|
|
if ((err =
|
|
snd_pcm_hw_params_set_rate(AlsaPCMHandle, hw_params, freq,
|
|
0)) < 0) {
|
|
Error(_("audio: can't set rate: %s\n"), snd_strerror(err));
|
|
}
|
|
// 500000
|
|
// 170667us
|
|
buffer_time = 1000 * 1000 * 1000;
|
|
dir = 1;
|
|
#if 0
|
|
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time, &dir);
|
|
Info(_("audio/alsa: %dus max buffer time\n"), buffer_time);
|
|
|
|
buffer_time = 5 * 200 * 1000; // 1s
|
|
if ((err =
|
|
snd_pcm_hw_params_set_buffer_time_near(AlsaPCMHandle, hw_params,
|
|
&buffer_time, &dir)) < 0) {
|
|
Error(_("audio: snd_pcm_hw_params_set_buffer_time_near failed: %s\n"),
|
|
snd_strerror(err));
|
|
}
|
|
Info(_("audio/alsa: %dus buffer time\n"), buffer_time);
|
|
#endif
|
|
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
|
|
Info(_("audio/alsa: buffer size %lu\n"), buffer_size);
|
|
buffer_size = buffer_size < 65536 ? buffer_size : 65536;
|
|
if ((err =
|
|
snd_pcm_hw_params_set_buffer_size_near(AlsaPCMHandle, hw_params,
|
|
&buffer_size))) {
|
|
Error(_("audio: can't set buffer size: %s\n"), snd_strerror(err));
|
|
}
|
|
Info(_("audio/alsa: buffer size %lu\n"), buffer_size);
|
|
|
|
if ((err = snd_pcm_hw_params(AlsaPCMHandle, hw_params)) < 0) {
|
|
Error(_("audio: snd_pcm_hw_params failed: %s\n"), snd_strerror(err));
|
|
}
|
|
// 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);
|
|
Info(_("audio/alsa: buffer size %lu, period size %lu\n"), buffer_size,
|
|
period_size);
|
|
Debug(3, "audio/alsa: state %s\n",
|
|
snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle)));
|
|
|
|
AlsaStartThreshold = snd_pcm_frames_to_bytes(AlsaPCMHandle, period_size);
|
|
// min 333ms
|
|
if (AlsaStartThreshold < (*freq * *channels * AudioBytesProSample) / 3U) {
|
|
AlsaStartThreshold = (*freq * *channels * AudioBytesProSample) / 3U;
|
|
}
|
|
// no bigger, than the buffer
|
|
if (AlsaStartThreshold > RingBufferFreeBytes(AlsaRingBuffer)) {
|
|
AlsaStartThreshold = RingBufferFreeBytes(AlsaRingBuffer);
|
|
}
|
|
Info(_("audio/alsa: delay %u ms\n"), (AlsaStartThreshold * 1000)
|
|
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
** Empty log callback
|
|
*/
|
|
static void AlsaNoopCallback( __attribute__ ((unused))
|
|
const char *file, __attribute__ ((unused))
|
|
int line, __attribute__ ((unused))
|
|
const char *function, __attribute__ ((unused))
|
|
int err, __attribute__ ((unused))
|
|
const char *fmt, ...)
|
|
{
|
|
}
|
|
|
|
/**
|
|
** Initialize alsa audio output module.
|
|
*/
|
|
static void AlsaInit(void)
|
|
{
|
|
#ifndef DEBUG
|
|
// disable display alsa error messages
|
|
snd_lib_error_set_handler(AlsaNoopCallback);
|
|
#else
|
|
(void)AlsaNoopCallback;
|
|
#endif
|
|
AlsaRingBuffer = RingBufferNew(48000 * 8 * 2); // ~1s 8ch 16bit
|
|
|
|
AlsaInitPCM();
|
|
AlsaInitMixer();
|
|
}
|
|
|
|
/**
|
|
** Cleanup alsa audio output module.
|
|
*/
|
|
static void AlsaExit(void)
|
|
{
|
|
if (AlsaPCMHandle) {
|
|
snd_pcm_close(AlsaPCMHandle);
|
|
AlsaPCMHandle = NULL;
|
|
}
|
|
if (AlsaMixer) {
|
|
snd_mixer_close(AlsaMixer);
|
|
AlsaMixer = NULL;
|
|
AlsaMixerElem = NULL;
|
|
}
|
|
if (AlsaRingBuffer) {
|
|
RingBufferDel(AlsaRingBuffer);
|
|
AlsaRingBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
#endif // USE_ALSA
|
|
|
|
#ifdef USE_OSS
|
|
|
|
//============================================================================
|
|
// O S S
|
|
//============================================================================
|
|
|
|
//----------------------------------------------------------------------------
|
|
// OSS variables
|
|
//----------------------------------------------------------------------------
|
|
|
|
static int OssPcmFildes = -1; ///< pcm file descriptor
|
|
static int OssMixerFildes = -1; ///< mixer file descriptor
|
|
static int OssMixerChannel; ///< mixer channel index
|
|
static RingBuffer *OssRingBuffer; ///< audio ring buffer
|
|
static unsigned OssStartThreshold; ///< start play, if filled
|
|
|
|
//----------------------------------------------------------------------------
|
|
// OSS pcm
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Place samples in ringbuffer.
|
|
**
|
|
** @param samples sample buffer
|
|
** @param count number of bytes in sample buffer
|
|
**
|
|
** @returns true if play should be started.
|
|
*/
|
|
static int OssAddToRingbuffer(const void *samples, int count)
|
|
{
|
|
int n;
|
|
|
|
n = RingBufferWrite(OssRingBuffer, samples, 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 +=
|
|
((int64_t) count * 90000) / (AudioSampleRate * AudioChannels *
|
|
AudioBytesProSample);
|
|
|
|
if (!AudioRunning) {
|
|
if (OssStartThreshold < RingBufferUsedBytes(OssRingBuffer)) {
|
|
// restart play-back
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
** Play samples from ringbuffer.
|
|
*/
|
|
static int OssPlayRingbuffer(void)
|
|
{
|
|
int first;
|
|
const void *p;
|
|
|
|
first = 1;
|
|
for (;;) {
|
|
audio_buf_info bi;
|
|
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(OssRingBuffer, &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!
|
|
}
|
|
|
|
n = write(OssPcmFildes, p, bi.bytes);
|
|
if (n != bi.bytes) {
|
|
if (n < 0) {
|
|
Error(_("audio/oss: write error: %s\n"), strerror(errno));
|
|
return 1;
|
|
}
|
|
Error(_("audio/oss: error not all bytes written\n"));
|
|
}
|
|
// advance how many could written
|
|
RingBufferReadAdvance(OssRingBuffer, n);
|
|
first = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
** Flush oss buffers.
|
|
*/
|
|
static void OssFlushBuffers(void)
|
|
{
|
|
RingBufferReadAdvance(OssRingBuffer, RingBufferUsedBytes(OssRingBuffer));
|
|
// flush kernel buffers
|
|
if (ioctl(OssPcmFildes, SNDCTL_DSP_HALT_OUTPUT, NULL) < 0) {
|
|
Error(_("audio/oss: ioctl(SNDCTL_DSP_HALT_OUTPUT): %s\n"),
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
AudioPTS = INT64_C(0x8000000000000000);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// OSS pcm polled
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Place samples in audio output queue.
|
|
**
|
|
** @param samples sample buffer
|
|
** @param count number of bytes in sample buffer
|
|
*/
|
|
static void OssEnqueue(const void *samples, int count)
|
|
{
|
|
#ifdef DEBUG
|
|
static uint32_t last_tick;
|
|
uint32_t tick;
|
|
|
|
tick = GetMsTicks();
|
|
Debug(4, "audio/oss: %4d %d ms\n", count, tick - last_tick);
|
|
last_tick = tick;
|
|
#endif
|
|
|
|
if (OssPcmFildes == -1) { // setup failure
|
|
Debug(3, "audio/oss: not ready\n");
|
|
return;
|
|
}
|
|
if (OssAddToRingbuffer(samples, count)) {
|
|
AudioRunning = 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
** Play all samples possible, without blocking.
|
|
*/
|
|
static void OssPoller(void)
|
|
{
|
|
if (OssPcmFildes == -1) { // setup failure
|
|
return;
|
|
}
|
|
if (AudioRunning) {
|
|
OssPlayRingbuffer();
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Initialize oss pcm device.
|
|
**
|
|
** @see AudioPCMDevice
|
|
*/
|
|
static void OssInitPCM(void)
|
|
{
|
|
const char *device;
|
|
int fildes;
|
|
|
|
if (!(device = AudioPCMDevice)) {
|
|
if (!(device = getenv("OSS_AUDIODEV"))) {
|
|
device = "/dev/dsp";
|
|
}
|
|
}
|
|
if ((fildes = open(device, O_WRONLY)) < 0) {
|
|
Error(_("audio/oss: can't open dsp device '%s': %s\n"), device,
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
|
|
OssPcmFildes = fildes;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// OSS Mixer
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Set oss mixer volume (0-100)
|
|
**
|
|
** @param volume volume (0 .. 100)
|
|
*/
|
|
static void OssSetVolume(int volume)
|
|
{
|
|
int v;
|
|
|
|
if (OssMixerFildes != -1) {
|
|
v = (volume * 255) / 100;
|
|
v &= 0xff;
|
|
v = (v << 8) | v;
|
|
if (ioctl(OssMixerFildes, MIXER_WRITE(OssMixerChannel), &v) < 0) {
|
|
Error(_("audio/oss: ioctl(MIXER_WRITE): %s\n"), strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
** Mixer channel name table.
|
|
*/
|
|
static const char *OssMixerChannelNames[SOUND_MIXER_NRDEVICES] =
|
|
SOUND_DEVICE_NAMES;
|
|
|
|
/**
|
|
** Initialize oss mixer.
|
|
*/
|
|
static void OssInitMixer(void)
|
|
{
|
|
const char *device;
|
|
const char *channel;
|
|
int fildes;
|
|
int devmask;
|
|
int i;
|
|
|
|
if (!(device = AudioMixerDevice)) {
|
|
if (!(device = getenv("OSS_MIXERDEV"))) {
|
|
device = "/dev/mixer";
|
|
}
|
|
}
|
|
if (!(channel = AudioMixerChannel)) {
|
|
if (!(channel = getenv("OSS_MIXER_CHANNEL"))) {
|
|
channel = "pcm";
|
|
}
|
|
}
|
|
Debug(3, "audio/oss: mixer %s - %s open\n", device, channel);
|
|
|
|
if ((fildes = open(device, O_RDWR)) < 0) {
|
|
Error(_("audio/oss: can't open mixer device '%s': %s\n"), device,
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
// search channel name
|
|
if (ioctl(fildes, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
|
|
Error(_("audio/oss: ioctl(SOUND_MIXER_READ_DEVMASK): %s\n"),
|
|
strerror(errno));
|
|
close(fildes);
|
|
return;
|
|
}
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; ++i) {
|
|
if (!strcasecmp(OssMixerChannelNames[i], channel)) {
|
|
if (devmask & (1 << i)) {
|
|
OssMixerFildes = fildes;
|
|
OssMixerChannel = i;
|
|
return;
|
|
}
|
|
Error(_("audio/oss: channel '%s' not supported\n"), channel);
|
|
break;
|
|
}
|
|
}
|
|
Error(_("audio/oss: channel '%s' not found\n"), channel);
|
|
close(fildes);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// OSS API
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Get oss audio delay in time stamps.
|
|
**
|
|
** @returns audio delay in time stamps.
|
|
*/
|
|
static uint64_t OssGetDelay(void)
|
|
{
|
|
int delay;
|
|
uint64_t pts;
|
|
|
|
if (OssPcmFildes == -1) { // setup failure
|
|
return 0UL;
|
|
}
|
|
|
|
if (!AudioRunning) {
|
|
return 0UL;
|
|
}
|
|
// 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 0UL;
|
|
}
|
|
if (delay == -1) {
|
|
delay = 0UL;
|
|
}
|
|
|
|
pts = ((uint64_t) delay * 90 * 1000)
|
|
/ (AudioSampleRate * AudioChannels * AudioBytesProSample);
|
|
pts += ((uint64_t) RingBufferUsedBytes(OssRingBuffer) * 90 * 1000)
|
|
/ (AudioSampleRate * AudioChannels * AudioBytesProSample);
|
|
if (pts > 600 * 90) {
|
|
Debug(4, "audio/oss: hw+sw delay %zd %" PRId64 " ms\n",
|
|
RingBufferUsedBytes(OssRingBuffer), pts / 90);
|
|
}
|
|
|
|
return pts;
|
|
}
|
|
|
|
/**
|
|
** Setup oss audio for requested format.
|
|
**
|
|
** @param freq sample frequency
|
|
** @param channels number of channels
|
|
**
|
|
** @retval 0 everything ok
|
|
** @retval 1 didn't support frequency/channels combination
|
|
** @retval -1 something gone wrong
|
|
**
|
|
** @todo audio changes must be queued and done when the buffer is empty
|
|
*/
|
|
static int OssSetup(int *freq, int *channels)
|
|
{
|
|
int ret;
|
|
int tmp;
|
|
|
|
if (OssPcmFildes == -1) { // oss not ready
|
|
return -1;
|
|
}
|
|
// flush any buffered data
|
|
{
|
|
AudioRunning = 0;
|
|
OssFlushBuffers();
|
|
}
|
|
AudioPTS = INT64_C(0x8000000000000000);
|
|
|
|
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 = *freq;
|
|
if (ioctl(OssPcmFildes, SNDCTL_DSP_SPEED, &tmp) == -1) {
|
|
Error(_("audio/oss: ioctl(SNDCTL_DSP_SPEED): %s\n"), strerror(errno));
|
|
return -1;
|
|
}
|
|
if (tmp != *freq) {
|
|
Warning(_("audio/oss: device doesn't support %d Hz sample rate.\n"),
|
|
*freq);
|
|
*freq = tmp;
|
|
ret = 1;
|
|
}
|
|
|
|
AudioChannels = *channels;
|
|
AudioSampleRate = *freq;
|
|
|
|
// FIXME: setup buffers
|
|
|
|
if (1) {
|
|
audio_buf_info bi;
|
|
|
|
if (ioctl(OssPcmFildes, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
|
|
Error(_("audio/oss: ioctl(SNDCTL_DSP_GETOSPACE): %s\n"),
|
|
strerror(errno));
|
|
} else {
|
|
Debug(3, "audio/oss: %d bytes buffered\n", bi.bytes);
|
|
}
|
|
|
|
tmp = -1;
|
|
if (ioctl(OssPcmFildes, SNDCTL_DSP_GETODELAY, &tmp) == -1) {
|
|
Error(_("audio/oss: ioctl(SNDCTL_DSP_GETODELAY): %s\n"),
|
|
strerror(errno));
|
|
// FIXME: stop player, set setup failed flag
|
|
return -1;
|
|
}
|
|
if (tmp == -1) {
|
|
tmp = 0;
|
|
}
|
|
// start when enough bytes for initial write
|
|
OssStartThreshold = bi.bytes + tmp;
|
|
// min 333ms
|
|
if (OssStartThreshold < (*freq * *channels * AudioBytesProSample) / 3U) {
|
|
OssStartThreshold = (*freq * *channels * AudioBytesProSample) / 3U;
|
|
}
|
|
// no bigger, than the buffer
|
|
if (OssStartThreshold > RingBufferFreeBytes(OssRingBuffer)) {
|
|
OssStartThreshold = RingBufferFreeBytes(OssRingBuffer);
|
|
}
|
|
|
|
Info(_("audio/oss: delay %u ms\n"), (OssStartThreshold * 1000)
|
|
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
** Initialize oss audio output module.
|
|
*/
|
|
static void OssInit(void)
|
|
{
|
|
OssRingBuffer = RingBufferNew(48000 * 8 * 2); // ~1s 8ch 16bit
|
|
|
|
OssInitPCM();
|
|
OssInitMixer();
|
|
}
|
|
|
|
/**
|
|
** Cleanup oss audio output module.
|
|
*/
|
|
static void OssExit(void)
|
|
{
|
|
if (OssPcmFildes != -1) {
|
|
close(OssPcmFildes);
|
|
OssPcmFildes = -1;
|
|
}
|
|
if (OssMixerFildes != -1) {
|
|
close(OssMixerFildes);
|
|
OssMixerFildes = -1;
|
|
}
|
|
}
|
|
|
|
#endif // USE_OSS
|
|
|
|
//----------------------------------------------------------------------------
|
|
// thread playback
|
|
//----------------------------------------------------------------------------
|
|
|
|
#ifdef USE_AUDIO_THREAD
|
|
|
|
static pthread_t AudioThread; ///< audio play thread
|
|
static pthread_mutex_t AudioMutex; ///< audio condition mutex
|
|
|
|
/**
|
|
** Audio play thread.
|
|
*/
|
|
static void *AudioPlayHandlerThread(void *dummy)
|
|
{
|
|
Debug(3, "audio: play thread started\n");
|
|
for (;;) {
|
|
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();
|
|
#endif
|
|
}
|
|
|
|
return dummy;
|
|
}
|
|
|
|
/**
|
|
** Initialize audio thread.
|
|
*/
|
|
static void AudioInitThread(void)
|
|
{
|
|
pthread_mutex_init(&AudioMutex, NULL);
|
|
pthread_cond_init(&AudioStartCond, NULL);
|
|
pthread_create(&AudioThread, NULL, AudioPlayHandlerThread, NULL);
|
|
pthread_setname_np(AudioThread, "softhddev audio");
|
|
//pthread_detach(AudioThread);
|
|
#ifdef very_old_unused_USE_ALSA
|
|
// wait until thread has opened and is ready
|
|
do {
|
|
pthread_yield();
|
|
} while (!AlsaPCMHandle);
|
|
#endif
|
|
pthread_yield();
|
|
usleep(5 * 1000);
|
|
}
|
|
|
|
/**
|
|
** Cleanup audio thread.
|
|
*/
|
|
static void AudioExitThread(void)
|
|
{
|
|
void *retval;
|
|
|
|
if (AudioThread) {
|
|
if (pthread_cancel(AudioThread)) {
|
|
Error(_("audio: can't queue cancel play thread\n"));
|
|
}
|
|
if (pthread_join(AudioThread, &retval) || retval != PTHREAD_CANCELED) {
|
|
Error(_("audio: can't cancel play thread\n"));
|
|
}
|
|
pthread_cond_destroy(&AudioStartCond);
|
|
pthread_mutex_destroy(&AudioMutex);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
|
|
/**
|
|
** Place samples in audio output queue.
|
|
**
|
|
** @param samples sample buffer
|
|
** @param count number of bytes in sample buffer
|
|
*/
|
|
void AudioEnqueue(const void *samples, int count)
|
|
{
|
|
#ifdef USE_ALSA
|
|
AlsaEnqueue(samples, count);
|
|
#endif
|
|
#ifdef USE_OSS
|
|
OssEnqueue(samples, count);
|
|
#endif
|
|
(void)samples;
|
|
(void)count;
|
|
}
|
|
|
|
/**
|
|
** Flush audio buffers.
|
|
*/
|
|
void AudioFlushBuffers(void)
|
|
{
|
|
#ifdef USE_ALSA
|
|
#ifdef USE_AUDIO_THREAD
|
|
if (AudioRunning) {
|
|
while (AudioRunning) {
|
|
AlsaFlushBuffer = 1;
|
|
usleep(1 * 1000);
|
|
}
|
|
AlsaFlushBuffer = 0;
|
|
} else
|
|
#endif
|
|
{
|
|
AlsaFlushBuffers();
|
|
}
|
|
#endif
|
|
#ifdef USE_OSS
|
|
OssFlushBuffers();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
** Call back to play audio polled.
|
|
*/
|
|
void AudioPoller(void)
|
|
{
|
|
#ifndef USE_AUDIO_THREAD
|
|
#ifdef USE_ALSA
|
|
Error(_("audio/alsa: poller not implemented\n"));
|
|
#endif
|
|
#ifdef USE_OSS
|
|
OssPoller();
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
** Get free bytes in audio output.
|
|
*/
|
|
int AudioFreeBytes(void)
|
|
{
|
|
#ifdef USE_ALSA
|
|
return RingBufferFreeBytes(AlsaRingBuffer);
|
|
#endif
|
|
#ifdef USE_OSS
|
|
return RingBufferFreeBytes(OssRingBuffer);
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
** Set audio clock base.
|
|
**
|
|
** @param pts audio presentation timestamp
|
|
*/
|
|
void AudioSetClock(int64_t pts)
|
|
{
|
|
#ifdef DEBUG
|
|
if (AudioPTS != pts) {
|
|
Debug(4, "audio: set clock to %#012" PRIx64 " %#012" PRIx64 " pts\n",
|
|
AudioPTS, pts);
|
|
|
|
}
|
|
#endif
|
|
AudioPTS = pts;
|
|
}
|
|
|
|
/**
|
|
** Get audio delay in time stamps.
|
|
**
|
|
** @returns audio delay in time stamps.
|
|
*/
|
|
uint64_t AudioGetDelay(void)
|
|
{
|
|
#ifdef USE_ALSA
|
|
return AlsaGetDelay();
|
|
#endif
|
|
#ifdef USE_OSS
|
|
return OssGetDelay();
|
|
#endif
|
|
return 0UL;
|
|
}
|
|
|
|
/**
|
|
** Get current audio clock.
|
|
**
|
|
** @returns the audio clock in time stamps.
|
|
*/
|
|
int64_t AudioGetClock(void)
|
|
{
|
|
int64_t delay;
|
|
|
|
delay = AudioGetDelay();
|
|
if (delay && (uint64_t) AudioPTS != INT64_C(0x8000000000000000)) {
|
|
return AudioPTS - delay;
|
|
}
|
|
return INT64_C(0x8000000000000000);
|
|
}
|
|
|
|
/**
|
|
** Set mixer volume (0-100)
|
|
**
|
|
** @param volume volume (0 .. 100)
|
|
*/
|
|
void AudioSetVolume(int volume)
|
|
{
|
|
#ifdef USE_ALSA
|
|
AlsaSetVolume(volume);
|
|
#endif
|
|
#ifdef USE_OSS
|
|
OssSetVolume(volume);
|
|
#endif
|
|
(void)volume;
|
|
}
|
|
|
|
/**
|
|
** Setup audio for requested format.
|
|
**
|
|
** @param freq sample frequency
|
|
** @param channels number of channels
|
|
**
|
|
** @retval 0 everything ok
|
|
** @retval 1 didn't support frequency/channels combination
|
|
** @retval -1 something gone wrong
|
|
**
|
|
** @todo audio changes must be queued and done when the buffer is empty
|
|
*/
|
|
int AudioSetup(int *freq, int *channels)
|
|
{
|
|
Debug(3, "audio: channels %d frequency %d hz\n", *channels, *freq);
|
|
|
|
// invalid parameter
|
|
if (!freq || !channels || !*freq || !*channels) {
|
|
Debug(3, "audio: bad channels or frequency parameters\n");
|
|
// 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
|
|
#ifdef USE_OSS
|
|
return OssSetup(freq, channels);
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
** Set pcm audio device.
|
|
**
|
|
** @param device name of pcm device (fe. "hw:0,9" or "/dev/dsp")
|
|
*/
|
|
void AudioSetDevice(const char *device)
|
|
{
|
|
AudioPCMDevice = device;
|
|
}
|
|
|
|
/**
|
|
** Initialize audio output module.
|
|
*/
|
|
void AudioInit(void)
|
|
{
|
|
int freq;
|
|
int chan;
|
|
|
|
#ifdef SEARCH_HDMI_BUG2
|
|
AudioRingInit();
|
|
#endif
|
|
#ifdef USE_ALSA
|
|
AlsaInit();
|
|
#endif
|
|
#ifdef USE_OSS
|
|
OssInit();
|
|
#endif
|
|
freq = 48000;
|
|
chan = 2;
|
|
if (AudioSetup(&freq, &chan)) { // set default parameters
|
|
Error(_("audio: can't do initial setup\n"));
|
|
}
|
|
#ifdef USE_AUDIO_THREAD
|
|
AudioInitThread();
|
|
#endif
|
|
|
|
AudioPaused = 1;
|
|
}
|
|
|
|
/**
|
|
** Cleanup audio output module.
|
|
*/
|
|
void AudioExit(void)
|
|
{
|
|
#ifdef USE_AUDIO_THREAD
|
|
AudioExitThread();
|
|
#endif
|
|
#ifdef USE_ALSA
|
|
AlsaExit();
|
|
#endif
|
|
#ifdef USE_OSS
|
|
OssExit();
|
|
#endif
|
|
#ifdef SEARCH_HDMI_BUG2
|
|
AudioRingExit();
|
|
#endif
|
|
}
|
|
|
|
#ifdef AUDIO_TEST
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Test
|
|
//----------------------------------------------------------------------------
|
|
|
|
void AudioTest(void)
|
|
{
|
|
for (;;) {
|
|
unsigned u;
|
|
uint8_t buffer[16 * 1024]; // some random data
|
|
int i;
|
|
|
|
for (u = 0; u < sizeof(buffer); u++) {
|
|
buffer[u] = random() & 0xffff;
|
|
}
|
|
|
|
Debug(3, "audio/test: loop\n");
|
|
for (i = 0; i < 100; ++i) {
|
|
while (RingBufferFreeBytes(AlsaRingBuffer) > sizeof(buffer)) {
|
|
AlsaEnqueue(buffer, sizeof(buffer));
|
|
}
|
|
usleep(20 * 1000);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#include <getopt.h>
|
|
|
|
int SysLogLevel; ///< show additional debug informations
|
|
|
|
/**
|
|
** Print version.
|
|
*/
|
|
static void PrintVersion(void)
|
|
{
|
|
printf("audio_test: audio tester Version " VERSION
|
|
#ifdef GIT_REV
|
|
"(GIT-" GIT_REV ")"
|
|
#endif
|
|
",\n\t(c) 2009 - 2011 by Johns\n"
|
|
"\tLicense AGPLv3: GNU Affero General Public License version 3\n");
|
|
}
|
|
|
|
/**
|
|
** Print usage.
|
|
*/
|
|
static void PrintUsage(void)
|
|
{
|
|
printf("Usage: audio_test [-?dhv]\n"
|
|
"\t-d\tenable debug, more -d increase the verbosity\n"
|
|
"\t-? -h\tdisplay this message\n" "\t-v\tdisplay version information\n"
|
|
"Only idiots print usage on stderr!\n");
|
|
}
|
|
|
|
/**
|
|
** Main entry point.
|
|
**
|
|
** @param argc number of arguments
|
|
** @param argv arguments vector
|
|
**
|
|
** @returns -1 on failures, 0 clean exit.
|
|
*/
|
|
int main(int argc, char *const argv[])
|
|
{
|
|
SysLogLevel = 0;
|
|
|
|
//
|
|
// Parse command line arguments
|
|
//
|
|
for (;;) {
|
|
switch (getopt(argc, argv, "hv?-c:d")) {
|
|
case 'd': // enabled debug
|
|
++SysLogLevel;
|
|
continue;
|
|
|
|
case EOF:
|
|
break;
|
|
case 'v': // print version
|
|
PrintVersion();
|
|
return 0;
|
|
case '?':
|
|
case 'h': // help usage
|
|
PrintVersion();
|
|
PrintUsage();
|
|
return 0;
|
|
case '-':
|
|
PrintVersion();
|
|
PrintUsage();
|
|
fprintf(stderr, "\nWe need no long options\n");
|
|
return -1;
|
|
case ':':
|
|
PrintVersion();
|
|
fprintf(stderr, "Missing argument for option '%c'\n", optopt);
|
|
return -1;
|
|
default:
|
|
PrintVersion();
|
|
fprintf(stderr, "Unkown option '%c'\n", optopt);
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
if (optind < argc) {
|
|
PrintVersion();
|
|
while (optind < argc) {
|
|
fprintf(stderr, "Unhandled argument '%s'\n", argv[optind++]);
|
|
}
|
|
return -1;
|
|
}
|
|
//
|
|
// main loop
|
|
//
|
|
AudioInit();
|
|
for (;;) {
|
|
unsigned u;
|
|
uint8_t buffer[16 * 1024]; // some random data
|
|
|
|
for (u = 0; u < sizeof(buffer); u++) {
|
|
buffer[u] = random() & 0xffff;
|
|
}
|
|
|
|
Debug(3, "audio/test: loop\n");
|
|
for (;;) {
|
|
while (RingBufferFreeBytes(AlsaRingBuffer) > sizeof(buffer)) {
|
|
AlsaEnqueue(buffer, sizeof(buffer));
|
|
}
|
|
}
|
|
}
|
|
AudioExit();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|