Complete.Org: Mailing Lists: Archives: freeciv-dev: April 2004:
[Freeciv-Dev] Re: (PR#8509) ALSA sound plugin for freeciv
Home

[Freeciv-Dev] Re: (PR#8509) ALSA sound plugin for freeciv

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: jpello@xxxxxxxxxxxxx
Subject: [Freeciv-Dev] Re: (PR#8509) ALSA sound plugin for freeciv
From: "Per I. Mathisen" <per@xxxxxxxxxxx>
Date: Sun, 18 Apr 2004 14:44:05 -0700
Reply-to: rt@xxxxxxxxxxx

<URL: http://rt.freeciv.org/Ticket/Display.html?id=8509 >

On Wed, 14 Apr 2004, Javier Pello wrote:
> I have developed a sound plugin for freeciv to support ALSA,
> and I would like to contribute it in case someone else finds
> it useful.

I've fixed a few bugs and cleaned up the style issues. The patch and the
new files are attached. I cannot test it myself, since I cannot get ALSA
working on this box, so if someone with working ALSA could test it, I
would be very grateful.

The two new files should go in client/, patch up, and then rerun
./autogen_sh.

  - Per

/********************************************************************** 
 Freeciv - Copyright (C) 2004 - J. Pello
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   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 General Public License for more details.
***********************************************************************/

#ifndef FC__AUDIO_ALSA_H
#define FC__AUDIO_ALSA_H

void audio_alsa_init (void);

#endif  /* FC__AUDIO_ALSA_H */
/********************************************************************** 
 Freeciv - Copyright (C) 2004 - J. Pello
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   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 General Public License for more details.
***********************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <unistd.h>

#include <audiofile.h>
#include <alsa/asoundlib.h>

#include "support.h"
#include "log.h"
#include "audio.h"

#include "audio_alsa.h"

static snd_pcm_t *sound_handle = NULL;
static snd_async_handler_t *ah;
static snd_pcm_sframes_t period_size;

static AFfilehandle file_handle = AF_NULL_FILEHANDLE;
static double file_rate;
static AFframecount file_fcount, left_fcount;
static bool file_repeat;

/**********************************************************************
  Drain if running
***********************************************************************/
static inline void snd_pcm_drain_if (snd_pcm_t *h)
{
  switch (snd_pcm_state(h))  {
    case SND_PCM_STATE_RUNNING:
    case SND_PCM_STATE_XRUN:
    case SND_PCM_STATE_PAUSED:
      snd_pcm_drain (h);
    default: ;
  }
}

/**********************************************************************
  Reset as appropiate
***********************************************************************/
static inline void snd_pcm_drop_free(snd_pcm_t *h)
{
  file_repeat = FALSE;
  switch (snd_pcm_state(h))  {
    case SND_PCM_STATE_RUNNING:
    case SND_PCM_STATE_XRUN:
    case SND_PCM_STATE_DRAINING:
    case SND_PCM_STATE_PAUSED:
      snd_pcm_drop(h);     /* fall through */
    case SND_PCM_STATE_PREPARED:
    case SND_PCM_STATE_SETUP:
      snd_pcm_hw_free(h);
    default: ;
  }
}

/**********************************************************************
  Shut down
***********************************************************************/
static void my_shutdown(void)
{
  snd_pcm_close(sound_handle);
  sound_handle = NULL;
}

/**********************************************************************
  Stop
***********************************************************************/
static void my_stop(void)
{
  file_repeat = FALSE;
  snd_pcm_drop_free(sound_handle);
}

/**********************************************************************
  Wait
***********************************************************************/
static void my_wait(void)
{
  file_repeat = FALSE;
  snd_pcm_drain_if(sound_handle);
  while (snd_pcm_state(sound_handle) == SND_PCM_STATE_DRAINING) {
    usleep(100000);
  }
}

/**********************************************************************
  Set HW parameters
***********************************************************************/
static int set_hw_params(void)
{
  snd_pcm_hw_params_t *hwparams;
  unsigned rrate;
  unsigned period_time = 100000;

  snd_pcm_hw_params_alloca(&hwparams);

  if (snd_pcm_hw_params_any(sound_handle, hwparams) < 0) {
    return -1;
  }
  if (snd_pcm_hw_params_set_access(sound_handle, hwparams,
      SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) {
    return -1;
  }
  if (snd_pcm_hw_params_set_format(sound_handle, hwparams,
      SND_PCM_FORMAT_S16) < 0) {
    return -1;
  }
  if (snd_pcm_hw_params_set_channels(sound_handle, hwparams, 2) < 0) {
    return -1;
  }
  rrate = file_rate;
  if (snd_pcm_hw_params_set_rate_near(sound_handle, hwparams, &rrate, 0)
      < 0 ) {
    return -1;
  }
  if (rrate != (unsigned) file_rate) {
    freelog(LOG_VERBOSE, "ALSA: asked for rate %u, got %u",
            (unsigned) file_rate, rrate);
  }
  snd_pcm_hw_params_set_period_time_near(sound_handle, hwparams,
                                         &period_time, &rrate);
  if (snd_pcm_hw_params_get_period_size(hwparams, &period_size, &rrate)
      < 0) {
    return -1;
  }
  return snd_pcm_hw_params(sound_handle, hwparams);
}

/**********************************************************************
  Underrun recovery
***********************************************************************/
static int xrun_recovery(const char *s, int e)
{
  if (e == -EPIPE) {
    e = snd_pcm_prepare(sound_handle);
    if (e < 0) {
      freelog(LOG_ERROR, "ALSA: Cannot recover from underrun: %s",
              snd_strerror(e));
    } else {
      return 0;
    }
  } else if (e < 0) {
    freelog(LOG_ERROR, "ALSA: %s: %s", s, snd_strerror(e));
  }
  return e;
}

/**********************************************************************
  Send a burst of samples
***********************************************************************/
static int snd_mmap_run(void)
{
  snd_pcm_uframes_t left, frames, offset;
  snd_pcm_sframes_t commit;
  const snd_pcm_channel_area_t *area;

  left = period_size;
  if (left > left_fcount) {
    left = left_fcount;
  }

  while (left > 0) {
    frames = left;
    if (xrun_recovery("MMAP begin",
        snd_pcm_mmap_begin(sound_handle, &area, &offset, &frames)) < 0) {
      return -1;
    }

    /* Confirm that our parameters match what we expect for
     * SND_PCM_ACCESS_MMAP_INTERLEAVED and SND_PCM_FORMAT_S16.
     * Of course, we could adapt the code below, but it is easier to leave it
     * this way as long as ALSA's input format matches audiofile's output.
     */
    if (area[0].step != 32 
        || area[1].step != 32
        || area[0].addr != area[1].addr) {
      snd_pcm_mmap_commit (sound_handle, offset, 0);
      freelog(LOG_ERROR, "ALSA: unexpected area parameters");
      return -1;
    }

    if (afReadFrames(file_handle, AF_DEFAULT_TRACK,
                     ADD_TO_POINTER(area->addr, + 4 * offset), frames) < 0 )  {
      snd_pcm_mmap_commit(sound_handle, offset, 0);
      freelog(LOG_ERROR, "ALSA: cannot read frames");
      break;
    }

    commit = snd_pcm_mmap_commit(sound_handle, offset, frames);
    if (commit < 0 || commit != frames) {
      if (xrun_recovery("MMAP commit", commit<0 ? commit : -EPIPE) < 0) {
        return -1;
      }
    }
    left_fcount -= frames;
    left -= frames;
  }

  return 0;
}

/**********************************************************************
  Play callback
***********************************************************************/
static void play_callback(snd_async_handler_t *ah)
{
  snd_pcm_state_t state;
  snd_pcm_sframes_t avail;
  int restart = 0;

  while (TRUE)  {
    if (!left_fcount) {
      if (file_repeat == FALSE) {
        return;
      }
      /* rewind */
      if (afSeekFrame (file_handle, AF_DEFAULT_TRACK, 0) < 0) {
        break;
      }
      left_fcount = file_fcount;
    }

    state = snd_pcm_state(sound_handle);
    if (state == SND_PCM_STATE_XRUN) {
      if (xrun_recovery(NULL,-EPIPE) < 0) {
        break;
      }
    }

    avail =
      xrun_recovery ("avail update", snd_pcm_avail_update(sound_handle));
    if (avail < 0) {
      restart = 1;
      continue;
    }
    if (avail < period_size) {
      if (!restart) {
        return;
      }
      restart = 0;
      if (xrun_recovery("start", snd_pcm_start(sound_handle)) < 0) {
        break;
      }
    }

    if (snd_mmap_run() < 0) {
      break;
    }
  }

  /* error path */
  snd_pcm_drop_free(sound_handle);
}

/**********************************************************************
  Play
***********************************************************************/
static bool my_play(const char *tag, const char *fullpath, bool repeat)
{
  AFfilehandle new_handle;
  double new_rate;
  AFframecount new_fcount;

  if (fullpath == NULL) {
    return FALSE;
  }

  new_handle = afOpenFile(fullpath, "r", 0);
  if (new_handle == AF_NULL_FILEHANDLE)  {
    freelog(LOG_ERROR, "ALSA: cannot open %s", fullpath);
    return FALSE;
  }

  afSetVirtualSampleFormat(new_handle, AF_DEFAULT_TRACK,
                           AF_SAMPFMT_TWOSCOMP, 16);
  afSetVirtualChannels(new_handle, AF_DEFAULT_TRACK, 2);
  new_rate = afGetRate(new_handle, AF_DEFAULT_TRACK);
  new_fcount = afGetFrameCount(new_handle, AF_DEFAULT_TRACK);
  freelog(LOG_VERBOSE, "ALSA: %s: rate %f, %d frames", fullpath,
          new_rate, (int) new_fcount);

  snd_pcm_drop_free(sound_handle);
  if (file_handle != AF_NULL_FILEHANDLE) {
    afCloseFile(file_handle);
  }

  file_handle = new_handle;
  file_rate = new_rate;
  left_fcount = file_fcount = new_fcount;
  file_repeat = repeat;

  if (set_hw_params() < 0) {
    freelog (LOG_ERROR, "ALSA: bad format in %s", fullpath);
    return FALSE;
  }

  if (snd_mmap_run() < 0 || snd_mmap_run() < 0) {
    return FALSE;
  }

  if (xrun_recovery("ALSA: start error", snd_pcm_start(sound_handle)) < 0 )  {
    return FALSE;
  }

  return TRUE;
}

/**********************************************************************
  Initialize
***********************************************************************/
static bool my_init(void)
{
  if (snd_pcm_open(&sound_handle, "default", SND_PCM_STREAM_PLAYBACK, 0) 
      < 0)  {
    return FALSE;
  }

  if (snd_async_add_pcm_handler(&ah, sound_handle, play_callback, &ah)
      < 0 )  {
    freelog(LOG_ERROR, "ALSA: cannot set callback handler");
    return FALSE;
  }

  return TRUE;
}

/**********************************************************************
  Initialize. Called early during startup -- no logging available.
***********************************************************************/
void audio_alsa_init(void)
{
  struct audio_plugin self;

  sz_strlcpy(self.name, "alsa");
  sz_strlcpy(self.descr, "ALSA plugin");
  self.init = my_init;
  self.shutdown = my_shutdown;
  self.stop = my_stop;
  self.wait = my_wait;
  self.play = my_play;
  audio_add_plugin (&self);
}
Index: Makefile.am
===================================================================
RCS file: /home/freeciv/CVS/freeciv/Makefile.am,v
retrieving revision 1.35
diff -u -r1.35 Makefile.am
--- Makefile.am 13 Jan 2004 03:48:04 -0000      1.35
+++ Makefile.am 18 Apr 2004 21:38:26 -0000
@@ -47,6 +47,7 @@
                m4/c99.m4                       \
                m4/debug.m4                     \
                m4/esd.m4                       \
+               m4/alsa.m4                      \
                m4/freetype2.m4                 \
                m4/gettext.m4                   \
                m4/gettimeofday.m4              \
Index: configure.ac
===================================================================
RCS file: /home/freeciv/CVS/freeciv/configure.ac,v
retrieving revision 1.60
diff -u -r1.60 configure.ac
--- configure.ac        16 Apr 2004 17:30:27 -0000      1.60
+++ configure.ac        18 Apr 2004 21:38:26 -0000
@@ -473,6 +473,7 @@
 AC_SUBST(SOUND_LIBS)
 AM_CONDITIONAL(ESD, test "x$ESD" = "xyes")
 AM_CONDITIONAL(SDL, test "x$SDL_mixer" = "xyes")
+AM_CONDITIONAL(ALSA, test "x$ALSA" = "xyes")
 AM_CONDITIONAL(WINMM, test "x$WINMM" = "xyes")
 AM_CONDITIONAL(CLIENT_GUI_SDL, test "$gui_sources" = "gui-sdl")
 AM_CONDITIONAL(CLIENT_GUI_GTK, test "$gui_sources" = "gui-gtk")
Index: configure.in
===================================================================
RCS file: /home/freeciv/CVS/freeciv/configure.in,v
retrieving revision 1.235
diff -u -r1.235 configure.in
--- configure.in        16 Apr 2004 17:30:27 -0000      1.235
+++ configure.in        18 Apr 2004 21:38:26 -0000
@@ -456,6 +456,7 @@
 AC_SUBST(SOUND_CFLAGS)
 AC_SUBST(SOUND_LIBS)
 AM_CONDITIONAL(ESD, test x"$ESD" = "xyes")
+AM_CONDITIONAL(ALSA, test "x$ALSA" = "xyes")
 AM_CONDITIONAL(SDL, test x"$SDL_mixer" = "xyes")
 AM_CONDITIONAL(WINMM, test x"$WINMM" = "xyes")
 AM_CONDITIONAL(CLIENT_GUI_SDL, test "$gui_sources" = "gui-sdl")
Index: client/Makefile.am
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/Makefile.am,v
retrieving revision 1.54
diff -u -r1.54 Makefile.am
--- client/Makefile.am  10 Apr 2004 03:47:48 -0000      1.54
+++ client/Makefile.am  18 Apr 2004 21:38:26 -0000
@@ -28,6 +28,7 @@
 
 ALL_ESD_FILES=audio_esd.c audio_esd.h
 ALL_SDL_FILES=audio_sdl.c audio_sdl.h
+ALL_ALSA_FILES=audio_alsa.c audio_alsa.h
 ALL_WINMM_FILES=audio_winmm.c audio_winmm.h
 ALL_AMIGA_FILES=audio_amiga.c audio_amiga.h
 
@@ -37,6 +38,9 @@
 if SDL
 SDL_FILES=$(ALL_SDL_FILES)
 endif
+if ALSA
+ALSA_FILES=$(ALL_ALSA_FILES)
+endif
 if WINMM
 WINMM_FILES=$(ALL_WINMM_FILES)
 endif
@@ -111,6 +115,7 @@
                \
                $(ALL_ESD_FILES)                 \
                $(ALL_SDL_FILES)                 \
+               $(ALL_ALSA_FILES)                \
                $(ALL_WINMM_FILES)               \
                $(ALL_AMIGA_FILES)
 
@@ -133,7 +138,7 @@
 
 ## Above, note -I../intl instead of -I$(top_srdir/intl) is deliberate.
 
-civclient_SOURCES = $(ESD_FILES) $(SDL_FILES) $(WINMM_FILES) \
+civclient_SOURCES = $(ESD_FILES) $(SDL_FILES) $(ALSA_FILES) $(WINMM_FILES) \
        attribute.h     \
        attribute.c     \
        citydlg_common.c \
Index: client/audio.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/audio.c,v
retrieving revision 1.18
diff -u -r1.18 audio.c
--- client/audio.c      5 May 2003 12:11:12 -0000       1.18
+++ client/audio.c      18 Apr 2004 21:38:26 -0000
@@ -41,10 +41,13 @@
 #ifdef WINMM
 #include "audio_winmm.h"
 #endif
+#ifdef ALSA
+#include "audio_alsa.h"
+#endif
 
 #include "audio.h"
 
-#define MAX_NUM_PLUGINS                3
+#define MAX_NUM_PLUGINS                4
 #define SNDSPEC_SUFFIX         ".soundspec"
 
 /* keep it open throughout */
@@ -157,6 +160,9 @@
 #ifdef SDL
   audio_sdl_init();
 #endif
+#ifdef ALSA
+  audio_alsa_init();
+#endif
 #ifdef WINMM
   audio_winmm_init();
 #endif
@@ -275,6 +281,9 @@
 #endif
 #ifdef SDL
   if (audio_select_plugin("sdl")) return; 
+#endif
+#ifdef ALSA
+  if (audio_select_plugin("alsa")) return;
 #endif
 #ifdef WINMM
   if (audio_select_plugin("winmm")) return;
Index: m4/sound.m4
===================================================================
RCS file: /home/freeciv/CVS/freeciv/m4/sound.m4,v
retrieving revision 1.3
diff -u -r1.3 sound.m4
--- m4/sound.m4 31 Jan 2004 17:49:52 -0000      1.3
+++ m4/sound.m4 18 Apr 2004 21:38:27 -0000
@@ -7,6 +7,10 @@
    [  --disable-sdl-mixer     Do not try to use the SDL mixer],
    USE_SOUND=no, USE_SOUND_SDL=yes)
 
+ AC_ARG_ENABLE(alsa,
+   [  --disable-alsa          Do not try to use ALSA],
+   USE_SOUND=no, USE_SOUND_ALSA=yes)
+
  AC_ARG_ENABLE(winmm,
    [  --disable-winmm         Do not try to use WinMM for sound],
    USE_SOUND=no, USE_SOUND_WINMM=yes)
@@ -45,6 +49,18 @@
       AC_MSG_RESULT([no, install SDL_mixer first: 
http://www.libsdl.org/projects/SDL_mixer/index.html])
       SDL_mixer="xno"
     fi
+  fi
+ fi
+
+ if test "x$USE_SOUND_ALSA" = "xyes"; then
+  dnl Add ALSA support to client
+  AM_ALSA_SUPPORT(ALSA=yes, ALSA=no)
+  if test "x$ALSA" != "xno"; then
+    SOUND_CFLAGS="$SOUND_CFLAGS $ALSA_CFLAGS"
+    SOUND_LIBS="$SOUND_LIBS $ALSA_LIB"
+    AC_DEFINE(ALSA, 1, [ALSA support])
+    AC_MSG_CHECKING(building ALSA support)
+    AC_MSG_RESULT(yes)
   fi
  fi
 

[Prev in Thread] Current Thread [Next in Thread]