Hi all,

I've bolted together some ESD support, and it appears to work to a
first-order on my setup.

The patch is based around the artsd support, and the libao2 ESD module
from mplayer's CVS.  It's under GPL v2.

Features:

- Does output sound via ESD
- wall-clock based timing for buffer fill levels for sync estimation
- Attempted compensation for esd latency.

Issues:

- General bodginess of the code
- Synchronisation issues
  - Lack of buffer usage data in the ESD api
  - Seems to need a -200ms delta on output on my setup.
- Small ESD buffer size means lots of work for the sound dispatch loop
- Sync issues after jumping forward/back
  - No 'flush output' in ESD api
  - Jump, pause, unpause usually fixes things
- Volume code appears to have a bad interaction with output and is
disabled.
- Sometimes no sound when starting a playback.
  - Restarting playback appears to fix it most times.

I've since reconfigured my setup so I don't need ESD any longer[*], but
I wanted to throw this patch out as a starting point for anyone to
continue to work on if it helps their situation.

Cheers,

Mark

[*] I'm using alsa's dmix setup for simultaneous output of multiple
sound streams instead of esd.
-- 
Mark Cooke <[EMAIL PROTECTED]>
Index: configure
===================================================================
--- configure	(revision 7025)
+++ configure	(working copy)
@@ -39,6 +39,7 @@
 audio_oss="default"
 audio_alsa="default"
 audio_arts="default"
+audio_esd="default"
 audio_jack="default"
 ivtv="yes"
 ivtv_header="no"
@@ -138,6 +139,7 @@
 echo "  --disable-audio-oss      disable OSS audio support"
 echo "  --disable-audio-alsa     disable ALSA audio support"
 echo "  --disable-audio-arts     disable aRts audio support"
+echo "  --disable-audio-esd      disable ESD audio support"
 echo "  --disable-audio-jack     disable JACK audio support"
 echo "  --disable-dvd            disable native DVD playback"
 echo "  --enable-valgrind        disables timeouts for valgrind memory debugging"
@@ -443,6 +445,7 @@
 audio_oss="no"
 audio_alsa="no"
 audio_arts="no"
+audio_esd="no"
 audio_jack="no"
 dv1394="no"
 ffserver="no"
@@ -467,6 +470,7 @@
 audio_oss="no"
 audio_alsa="no"
 audio_arts="no"
+audio_esd="no"
 audio_jack="no"
 dv1394="no"
 extralibs=""
@@ -623,6 +627,8 @@
   ;;
   --disable-audio-arts) audio_arts="no"
   ;;
+  --disable-audio-esd) audio_esd="no"
+  ;;
   --disable-audio-jack) audio_jack="no"
   ;;
   --disable-audio-beos) audio_beos="no"
@@ -1292,6 +1298,7 @@
     audio_oss="no"
     audio_alsa="no"
     audio_arts="no"
+    audio_esd="no"
     audio_jack="no"
     dv1394="no"
     dc1394="no"
@@ -1729,6 +1736,27 @@
 fi
 
 ##########################################
+# ESD probe
+
+if test x"$audio_esd" = x"default" -o x"$audio_esd" = x"yes" ; then
+
+cat > $TMPC << EOF
+#include <esd.h>
+int main(void) { return (int) ESD_BUF_SIZE; }
+EOF
+
+audio_esd=no
+if (esd-config --version) > /dev/null 2>&1; then
+audio_esd_libs=`esd-config --libs`
+audio_esd_cflags=`esd-config --cflags`
+if $cc -o $TMPE $TMPC $audio_esd_cflags $audio_esd_libs > /dev/null 2>&1; then
+audio_esd=yes
+fi
+fi
+
+fi
+
+##########################################
 # JACK probe
 
 if test x"$audio_jack" = x"default" -o x"$audio_jack" = x"yes" ; then
@@ -2032,6 +2060,7 @@
   echo "OSS support      $audio_oss"
   echo "ALSA support     $audio_alsa"
   echo "aRts support     $audio_arts"
+  echo "ESD support      $audio_esd"
   echo "JACK support     $audio_jack"
   echo
   echo "# Video Output Support"
@@ -2315,6 +2344,13 @@
   echo "CONFIG_AUDIO_ARTS_CFLAGS=$audio_arts_cflags" >> $MYTH_CONFIG_MAK
 fi
 
+if test "$audio_esd" = "yes" ; then
+    CCONFIG="$CCONFIG using_esd"
+  echo "#define CONFIG_AUDIO_ESD 1" >> $TMPH
+  echo "CONFIG_AUDIO_ESD_LIBS=$audio_esd_libs" >> $MYTH_CONFIG_MAK
+  echo "CONFIG_AUDIO_ESD_CFLAGS=$audio_esd_cflags" >> $MYTH_CONFIG_MAK
+fi
+
 if test "$audio_jack" = "yes" ; then
     CCONFIG="$CCONFIG using_jack"
   echo "#define CONFIG_AUDIO_JACK 1" >> $TMPH
Index: libs/libmyth/audiooutputesd.cpp
===================================================================
--- libs/libmyth/audiooutputesd.cpp	(revision 0)
+++ libs/libmyth/audiooutputesd.cpp	(revision 0)
@@ -0,0 +1,253 @@
+//
+// Based on the oudiooutputartsd.cpp and the esd output module of mplayer
+//
+// License: GNU GENERAL PUBLIC LICENSE VERSION 2
+//
+
+#include <cstdio>
+#include <cstdlib>
+
+
+using namespace std;
+
+#include "mythcontext.h"
+#include "audiooutputesd.h"
+
+#include <sys/time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+AudioOutputESD::AudioOutputESD(QString audiodevice, int audio_bits, 
+                                int audio_channels, int audio_samplerate,
+                                AudioOutputSource source, bool set_initial_vol)
+              : AudioOutputBase(audiodevice, audio_bits, audio_channels,
+                                audio_samplerate, source, set_initial_vol)
+{
+    // our initalisation
+    handle = -1;
+    esd_fmt = 0;
+
+    // Set everything up
+    Reconfigure(audio_bits, audio_channels, audio_samplerate);
+}
+
+AudioOutputESD::~AudioOutputESD()
+{
+    // Close down all audio stuff
+    KillAudio();
+}
+
+void AudioOutputESD::CloseDevice()
+{
+    if (handle != -1)
+        esd_close(handle);
+
+    handle = -1;
+}
+
+bool AudioOutputESD::OpenDevice()
+{
+    int err;
+    int fl;
+    
+    if (handle != -1)
+    {
+        esd_close(handle);
+        handle = -1;
+    }
+
+    esd_fmt = 0;
+    if (audio_bits == 8) {
+      esd_fmt |= ESD_BITS8;
+      bytes_per_sample = 1;
+    }
+    if (audio_bits == 16) {
+      esd_fmt |= ESD_BITS16;
+      bytes_per_sample = 2;
+    }
+    
+    if (audio_channels == 1) {
+      esd_fmt |= ESD_MONO;
+    }
+
+    if (audio_channels == 2) {
+      esd_fmt |= ESD_STEREO;
+      bytes_per_sample *= 2;
+    }
+    
+    esd_fmt |= ESD_STREAM | ESD_PLAY;
+    
+    VERBOSE(VB_GENERAL, QString("Opening ESD audio device '%1' %2 %3 .")
+	    .arg(audiodevice)
+	    .arg(audio_samplerate)
+	    .arg(esd_fmt));
+    err = esd_play_stream(esd_fmt, audio_samplerate, NULL, "mythtv");
+    if (err < 0)
+    {
+      Error(QString("Opening ESD sound device failed"));
+      return false;
+    }
+    
+    if ((fl = fcntl(err, F_GETFL)) >= 0)
+      fcntl(err, F_SETFL, O_NDELAY|fl);
+    
+    // Use the ESD buffer size.  This is pretty small!
+    buff_size = ESD_BUF_SIZE;
+    
+    // Latency is in bytes.  This is used as a pre-load of buffered data in 
+    // the soundcard to get sound and video more in sync.
+    buff_lat  = esd_get_latency(err) * 44100 * 2;
+    
+    // Data tracking things
+    esd_bytes_written = 0;
+    esd_play_start.tv_sec = 0;    
+    
+    // Parent class things
+    soundcard_buffer_size = buff_size;
+    fragment_size = buff_size / 2;
+    
+    // Device opened successfully
+    handle = err;    
+    return true;
+}
+
+void AudioOutputESD::WriteAudio(unsigned char *aubuf, int size)
+{
+  int err;
+  
+  if (handle == -1)
+    return;
+  
+ retry:
+  err = write(handle, aubuf, size);
+  if (err < 0) {
+    if (errno == EAGAIN)
+      goto retry;
+    
+    // FIXME: Fatal?
+    VERBOSE(VB_IMPORTANT, QString("ESD Write error %1")
+	    .arg(strerror(errno)));
+    return;
+  }
+  
+  // Wrote some samples... so setup some tracking data
+  if (err > 0) {
+    esd_bytes_written += err;
+    if (esd_play_start.tv_sec == 0) {
+      gettimeofday(&esd_play_start, NULL);
+    }
+    
+    // Uh-oh!
+    if (err != size) {
+      VERBOSE(VB_IMPORTANT, QString("ESD short write %1 vs %2")
+	      .arg(size)
+	      .arg(err));
+    }
+  }
+}
+
+// Supposed to return bytes actually on the soundcard right now
+//
+// It is used during latency calculations for syncing up
+inline int AudioOutputESD::getBufferedOnSoundcard(void)
+{
+  return buff_lat + (buff_size - getSpaceOnSoundcard());
+}
+
+// Bytes of space in out soundcard buffer (buff_size bytes)
+inline int AudioOutputESD::getSpaceOnSoundcard(void)
+{
+  struct timeval now;
+  double play_time;
+  int bytes_free;
+  unsigned long played_bytes;
+  double divisor = 10;
+  
+  // No bytes in buffer yet...
+  if (esd_play_start.tv_sec == 0)
+    return buff_size;
+  
+  // Calculate time since the sample was queued up (using divisor)
+  gettimeofday(&now, NULL);
+  play_time  = (now.tv_sec - esd_play_start.tv_sec) * divisor;
+  play_time += (now.tv_usec - esd_play_start.tv_usec) / divisor;
+  
+  // Calculate the bytes played so far
+  played_bytes = (unsigned long) ((play_time * audio_samplerate * bytes_per_sample) / divisor);
+  
+  // Underflow.  Oopsie.
+  if (played_bytes > esd_bytes_written) {
+    
+    VERBOSE(VB_IMPORTANT, QString("ESD buffer underflow detected"));
+    
+    // Reset the in-progress counter
+    esd_play_start.tv_sec = 0;
+    esd_bytes_written = 0;
+    
+    return buff_size;
+  }
+  
+  // Clamp to the buff_size limit.
+  bytes_free = buff_size - (esd_bytes_written - played_bytes);
+  if (bytes_free > buff_size)
+    bytes_free = buff_size;
+  
+  return bytes_free;
+}
+
+int AudioOutputESD::GetVolumeChannel(int channel)
+{
+  esd_player_info_t *esd_pi;
+  esd_info_t        *esd_i;
+  int                left, right;
+  
+  return 100;
+
+  // Borrowed from mplayer
+  if ((esd_i = esd_get_all_info(handle)) == NULL) {
+    VERBOSE(VB_IMPORTANT, QString("ESD couldn't get server information"));
+    return 100;
+  }
+  
+  for (esd_pi = esd_i->player_list; esd_pi != NULL; esd_pi = esd_pi->next)
+    if (strcmp(esd_pi->name, "mythtv") == 0)
+      break;
+  
+  if (esd_pi != NULL) {
+    left  = esd_pi->left_vol_scale  * 100 / ESD_VOLUME_BASE;
+    right = esd_pi->right_vol_scale * 100 / ESD_VOLUME_BASE;
+  } else {
+    VERBOSE(VB_IMPORTANT, QString("ESD couldn't find this player's information"));
+    left = right = 100;
+  }
+
+  esd_free_all_info(esd_i);
+
+  if (channel == 0)
+    return left;
+  else
+    return right;
+}
+
+void AudioOutputESD::SetVolumeChannel(int channel, int volume)
+{
+  int vol;
+  
+  return;
+
+  vol = GetVolumeChannel(1-channel);
+  
+  VERBOSE(VB_IMPORTANT, QString("ESD setting volume %1 to %2")
+	  .arg(channel)
+	  .arg(volume));
+
+  if (channel == 0)
+    esd_set_stream_pan(handle, 0,
+		       volume * ESD_VOLUME_BASE / 100,
+		       vol    * ESD_VOLUME_BASE / 100);
+  else
+    esd_set_stream_pan(handle, 0,
+		       vol    * ESD_VOLUME_BASE / 100,
+		       volume * ESD_VOLUME_BASE / 100);
+}
Index: libs/libmyth/audiooutput.cpp
===================================================================
--- libs/libmyth/audiooutput.cpp	(revision 7025)
+++ libs/libmyth/audiooutput.cpp	(working copy)
@@ -19,6 +19,9 @@
 #ifdef USE_ARTS
 #include "audiooutputarts.h"
 #endif
+#ifdef USE_ESD
+#include "audiooutputesd.h"
+#endif
 #ifdef CONFIG_DARWIN
 #include "audiooutputca.h"
 #endif
@@ -57,6 +60,17 @@
         return NULL;
 #endif
     }
+    else if (audiodevice.startsWith("ESD:"))
+    {
+#ifdef USE_ESD
+        return new AudioOutputESD(audiodevice.remove(0, 4), audio_bits,
+                                   audio_channels, audio_samplerate, source, set_initial_vol);
+#else
+        VERBOSE(VB_IMPORTANT, "Audio output device is set to an ESD device "
+                              "but ESD support is not compiled in!");
+        return NULL;
+#endif
+    }
     else if (audiodevice.startsWith("JACK:"))
     {
 #ifdef USE_JACK
Index: libs/libmyth/libmyth.pro
===================================================================
--- libs/libmyth/libmyth.pro	(revision 7025)
+++ libs/libmyth/libmyth.pro	(working copy)
@@ -128,6 +128,13 @@
     LIBS += $$ARTS_LIBS
 }
 
+using_esd {
+    DEFINES += USE_ESD
+    HEADERS += audiooutputesd.h
+    SOURCES += audiooutputesd.cpp
+    LIBS += $$ESD_LIBS
+}
+
 using_jack {
     DEFINES += USE_JACK
     HEADERS += bio2jack.h audiooutputjack.h
Index: libs/libmyth/audiooutputesd.h
===================================================================
--- libs/libmyth/audiooutputesd.h	(revision 0)
+++ libs/libmyth/audiooutputesd.h	(revision 0)
@@ -0,0 +1,49 @@
+#ifndef AUDIOOUTPUTESD
+#define AUDIOOUTPUTESD
+
+#include <qstring.h>
+
+#include <esd.h>
+
+using namespace std;
+
+#include "audiooutputbase.h"
+
+class AudioOutputESD : public AudioOutputBase
+{
+  public:
+     AudioOutputESD(QString audiodevice, int audio_bits, 
+                    int audio_channels, int audio_samplerate,
+                    AudioOutputSource source, bool set_initial_vol);
+     virtual ~AudioOutputESD();
+
+    // Volume control
+    virtual int GetVolumeChannel(int channel); // Returns 0-100
+    virtual void SetVolumeChannel(int channel, int volume); // range 0-100 for vol
+
+  protected:
+
+    // You need to implement the following functions
+    virtual bool OpenDevice(void);
+    virtual void CloseDevice(void);
+    virtual void WriteAudio(unsigned char *aubuf, int size);
+    virtual inline int getSpaceOnSoundcard(void);
+    virtual inline int getBufferedOnSoundcard(void);
+
+     
+
+  private:
+    QString audiodevice;
+    int handle;
+    int play_handle;
+    int esd_fmt;
+    int buff_size; // Size of output buffer in bytes
+    int buff_lat;  // Latency of output to sound in bytes
+    bool can_hw_pause;
+    bool paused;
+    struct timeval esd_play_start;
+    unsigned long  esd_bytes_written;
+    unsigned int   bytes_per_sample;
+};
+
+#endif // AUDIOOUTPUTESD
Index: settings.pro
===================================================================
--- settings.pro	(revision 7025)
+++ settings.pro	(working copy)
@@ -24,6 +24,7 @@
 
 QMAKE_CXXFLAGS += $$ARCHFLAGS
 QMAKE_CXXFLAGS += $$CONFIG_AUDIO_ARTS_CFLAGS
+QMAKE_CXXFLAGS += $$CONFIG_AUDIO_ESD_CFLAGS
 QMAKE_CXXFLAGS += $$CONFIG_DIRECTFB_CXXFLAGS
 QMAKE_CXXFLAGS_SHLIB = -DPIC -fPIC
 QMAKE_CXXFLAGS += $$ECXXFLAGS
@@ -67,6 +68,7 @@
 EXTRA_LIBS = -lfreetype -lmp3lame
 EXTRA_LIBS += $$CONFIG_AUDIO_ALSA_LIBS
 EXTRA_LIBS += $$CONFIG_AUDIO_ARTS_LIBS
+EXTRA_LIBS += $$CONFIG_AUDIO_ESD_LIBS
 EXTRA_LIBS += $$CONFIG_AUDIO_JACK_LIBS
 EXTRA_LIBS += $$CONFIG_FIREWIRE_LIBS
 EXTRA_LIBS += $$CONFIG_DIRECTFB_LIBS
_______________________________________________
mythtv-users mailing list
[email protected]
http://mythtv.org/cgi-bin/mailman/listinfo/mythtv-users

Reply via email to