Logo Search packages:      
Sourcecode: zinf version File versions  Download package

alsapmo.cpp

/*____________________________________________________________________________
        
        Zinf - Zinf Is Not FreeA*p (The Free MP3 Player)
        Driver for Advanced Linux Sound Architecture 
              http://www.alsa-project.org
 
        Portions Copyright (C) 1998-1999 EMusic.com
      
      alsapmo.cpp was coded primarily by Robert Kaye <rob@eorbit.net>
      and Ed Sweetman <ed.sweetman@wmich.edu> with a little help
      from Joy <joy@pingfm.org> who contributed most of get_space()
      
      It is (very) loosely based on the older pmo  contributed
      by Fleischer Gabor <flocsy@usa.net>
      
        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 of the License, 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.

        You should have received a copy of the GNU General Public License
        along with this program; if not, write to the Free Software
        Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
        
        $Id: alsapmo.cpp,v 1.30 2004/02/07 14:58:13 turtledavid Exp $

____________________________________________________________________________*/

/* system headers */
#include <stdlib.h>
#define ALSA_PCM_OLD_HW_PARAMS_API 1
#include <alsa/asoundlib.h>
#include <ctype.h>
#include <errno.h>
#include <string>
#include <iostream>

/* project headers */
using namespace std;
#include "config.h"
#include "preferences.h"
#include "alsapmo.h"
#include "facontext.h"
#include "log.h"

#define DB printf("%s:%d\n", __FILE__, __LINE__);

extern "C"
{
   PhysicalMediaOutput *Initialize(FAContext *context) {
      return new AlsaPMO(context);
   }
}




AlsaPMO::AlsaPMO(FAContext *context) :
            PhysicalMediaOutput(context)
{
   m_properlyInitialized = false;
   m_pBufferThread = NULL;
   m_context = context;
   m_iBytesPerSample = 0;
   m_iBaseTime = -1;
   m_iDataSize = 0;
   snd_mixer_t *pMixer;
 
   if (!m_pBufferThread){
      m_pBufferThread = Thread::CreateThread();
      assert(m_pBufferThread);
      m_pBufferThread->Create(AlsaPMO::StartWorkerThread, this);
   }

   m_handle = NULL;
   m_channels = -1;
   m_samples = -1;                                                            
    snd_mixer_elem_t *selem;
    snd_mixer_selem_id_t *m_sid;
    snd_mixer_selem_id_alloca(&m_sid);
    snd_mixer_selem_id_set_index(m_sid, 0);
    snd_mixer_selem_id_set_name(m_sid, "PCM");
    
    snd_mixer_open(&pMixer, 0);
    if(snd_mixer_attach(pMixer, "default") < 0 ){
      printf("ALSA: unable to attach to mixer\n");
      snd_mixer_close(pMixer);
      return;
    }
    if(snd_mixer_selem_register(pMixer, NULL, NULL) < 0){
      printf(_("ALSA: unable to register mixer\n"));
      snd_mixer_close(pMixer);
      return;
    }
    if(snd_mixer_load(pMixer) < 0){
      printf(_("ALSA: unable to load mixer\n"));
      snd_mixer_close(pMixer);
      return;
    }
    selem = snd_mixer_find_selem(pMixer, m_sid);
    if(!selem){
      printf(_("ALSA: unable to find control\n"));
      snd_mixer_close(pMixer);
      return;
    }
    snd_mixer_selem_get_playback_volume_range(selem,&m_group.min,&m_group.max);
    snd_mixer_close(pMixer);
}

AlsaPMO::~AlsaPMO() 
{
    m_bExit = true;
    m_pSleepSem->Signal();
    m_pPauseSem->Signal();

    if (m_pBufferThread){
       m_pBufferThread->Join();
       delete m_pBufferThread;
    }
    if(m_handle)
      snd_pcm_close(m_handle);
    else
      m_bExit = true;

    if(pfds) free(pfds);
}

void AlsaPMO::SetVolume(int32_t left, int32_t right)                                
{                                                                               
    int   err;
    snd_mixer_t *pMixer;                                                         
    snd_mixer_selem_id_t *m_sid;
    snd_mixer_elem_t *selem;
    snd_mixer_selem_id_alloca(&m_sid);
    snd_mixer_selem_id_set_index(m_sid, 0);
    snd_mixer_selem_id_set_name(m_sid, "PCM");

    err = snd_mixer_open(&pMixer, 0);                                   
    if (err < 0){
      printf(_("ALSA: unable to open mixer for set\n"));                                                                 
      return;                                                 
    }
    snd_mixer_attach(pMixer, "default");
    snd_mixer_selem_register(pMixer, NULL, NULL);
    snd_mixer_load(pMixer);
    selem = snd_mixer_find_selem(pMixer, m_sid);
    if (m_iChannel >= 0){                                                                                   
      left = (int)((double)(m_group.max - m_group.min) *                        
            (double)left * 0.01) + m_group.min;                              
      right = (int)((double)(m_group.max - m_group.min) *                       
            (double)right * 0.01) + m_group.min;                             
      if(snd_mixer_selem_set_playback_volume(selem, (snd_mixer_selem_channel_id_t)0, left) < 0)
          printf(_("ALSA: Unable to set left volume\n"));                               
      if(snd_mixer_selem_set_playback_volume(selem, (snd_mixer_selem_channel_id_t)1, right) < 0)
          printf(_("ALSA: Unable to set right volume\n"));                                                                         
    }                                                                            
    snd_mixer_close(pMixer);  
    //snd_mixer_selem_id_free(m_sid);                                                   
}

void AlsaPMO::GetVolume(int32_t &left, int32_t &right)
{
    int   err;
    snd_mixer_t *pMixer;
    snd_mixer_selem_id_t *m_sid;
    snd_mixer_elem_t *selem;
    snd_mixer_selem_id_alloca(&m_sid);
    snd_mixer_selem_id_set_index(m_sid, 0);
    snd_mixer_selem_id_set_name(m_sid, "PCM");

    err = snd_mixer_open(&pMixer, 0);
    if (err != 0){
      printf(_("ALSA: unable to open mixer for read\n"));
      return;
    }
    snd_mixer_attach(pMixer, "default");
    snd_mixer_selem_register(pMixer, NULL, NULL);
    snd_mixer_load(pMixer);
    selem = snd_mixer_find_selem(pMixer, m_sid);      
    if (m_iChannel >= 0){
      if(snd_mixer_selem_get_playback_volume(selem, (snd_mixer_selem_channel_id_t)0, &m_group.volumeL) < 0 )
          printf(_("ALSA: Unable to retrieve left volume\n"));
      if(snd_mixer_selem_get_playback_volume(selem, (snd_mixer_selem_channel_id_t)1, &m_group.volumeR) < 0)
          printf(_("ALSA: Unable to retrieve right volume\n"));
    }
    else{
      printf(_("strange no channels error\n"));
      return;
    }
    snd_mixer_close(pMixer);
    left = (int)(((float)((m_group.volumeL - m_group.min) * 100) /
          (float)(m_group.max - m_group.min)) + 0.5);
    right = (int)(((float)((m_group.volumeR - m_group.min) * 100) /
          (float)(m_group.max - m_group.min)) + 0.5);
    //snd_mixer_selem_id_free(m_sid);
}


Error AlsaPMO::Init(OutputInfo* info) 
{
    int                  err;
    snd_pcm_hw_params_t *params;
    snd_pcm_sw_params_t *sw_params;
    string m_adev;
    string alsa_dev;
    
    m_properlyInitialized = false;
    m_iDataSize = info->max_buffer_size;
    m_context->prefs->GetPrefString(kALSADevicePref, &m_adev);
    alsa_dev = "plughw:";
    alsa_dev += m_adev.c_str()[0];

    err=snd_pcm_open(&m_handle, alsa_dev.c_str() , SND_PCM_STREAM_PLAYBACK, 
                 SND_PCM_NONBLOCK); 
    if (err < 0){
        ReportError(_("Audio device is busy. Please make sure that "
                    "another program is not using the device."));
      m_bExit=1;
        return (Error)pmoError_DeviceOpenFailed;
    }
    m_channels=info->number_of_channels;
    m_samples=info->samples_per_second;
    m_samplesPerFrame=info->samples_per_frame;
    m_iBytesPerSample = (m_channels*(info->bits_per_sample) / 8);
    
    snd_pcm_hw_params_malloc(&params);
    
    err = snd_pcm_hw_params_any(m_handle, params);
    err = snd_pcm_hw_params_set_access(m_handle, params, 
                                 SND_PCM_ACCESS_RW_INTERLEAVED);
#if  SMALL_ENDIAN
    err = snd_pcm_hw_params_set_format(m_handle, params, SND_PCM_FORMAT_S16_LE);
#else
    err = snd_pcm_hw_params_set_format(m_handle, params, SND_PCM_FORMAT_S16_BE);
#endif        
    err = snd_pcm_hw_params_set_channels(m_handle, params, m_channels);
    err = snd_pcm_hw_params_set_rate_near(m_handle, params, m_samples, 0);
    err = snd_pcm_hw_params_set_period_size(m_handle, params,m_iDataSize/16, 0);
    err = snd_pcm_hw_params(m_handle, params);
    if (err < 0){
        ReportError(_("Cannot initialize audio device."));
        return (Error)pmoError_DeviceOpenFailed;
    }
    snd_pcm_hw_params_free (params);

    
    snd_pcm_sw_params_malloc(&sw_params);
    snd_pcm_sw_params_current(m_handle, sw_params);
    snd_pcm_sw_params_set_avail_min(m_handle, sw_params,m_iDataSize/8);
    snd_pcm_sw_params(m_handle, sw_params);
    snd_pcm_sw_params_free (sw_params);

#if  SMALL_ENDIAN
    alsa_frame_size = (snd_pcm_format_physical_width(SND_PCM_FORMAT_S16_LE) * 2)/8;
#else
    alsa_frame_size = (snd_pcm_format_physical_width(SND_PCM_FORMAT_S16_BE) * 2)/8;
#endif
    
    snd_pcm_prepare(m_handle);

    // Sets up our polling timings that are used when writing to driver.
    nfds = snd_pcm_poll_descriptors_count (m_handle);
    pfds = (struct pollfd *)malloc(sizeof(struct pollfd) * nfds);
    snd_pcm_poll_descriptors (m_handle, pfds, nfds);
    
    m_properlyInitialized = true;
    return kError_NoErr;
}

Error AlsaPMO::Reset(bool user_stop) 
{
    if(m_handle){
//    if (user_stop) 
//              snd_pcm_drain(m_handle);
//    else
          snd_pcm_drop(m_handle);
      snd_pcm_prepare(m_handle);
    }
    else{
      // Attempting to handle fast track changes
      // Not fully functional. (only good for some)
//    if(pfds) free(pfds);
//    pfds = NULL;
//    m_properlyInitialized = false;
//    m_bExit=true;
    }
    return kError_NoErr;
}

void AlsaPMO::Pause(void)
{
    m_iBaseTime = -1;

    PhysicalMediaOutput::Pause();
}

 
          
bool AlsaPMO::WaitForDrain(void)
{
    snd_pcm_status_t *status;   

    snd_pcm_status_alloca(&status);

    for(; !m_bExit && !m_bPause; ){
      snd_pcm_status(m_handle, status);
      if (snd_pcm_status_get_avail(status) == 
          snd_pcm_status_get_avail_max(status)){
//        snd_pcm_status_free(status);
          return true;
      }
      WasteTime();
    }
//     snd_pcm_status_free(status);
    return false;
} 

void AlsaPMO::HandleTimeInfoEvent(PMOTimeInfoEvent *pEvent)
{
   if (m_iBaseTime < 0){
       m_iBaseTime = (pEvent->GetFrameNumber() * 
                      m_samplesPerFrame) / 
                      m_samples;
   }

   int32_t iTotalTime;
   iTotalTime = totalWrittenToALSABuffer /( (m_iBytesPerSample) * m_samples);
   iTotalTime = iTotalTime + m_iBaseTime;
   

   MediaTimeInfoEvent *pmtpi = new MediaTimeInfoEvent( iTotalTime, 
                                                       pEvent->GetFrameNumber());
   m_pTarget->AcceptEvent(pmtpi);
}

void AlsaPMO::StartWorkerThread(void *pVoidBuffer)
{
   ((AlsaPMO*)pVoidBuffer)->WorkerThread();
}

int AlsaPMO::get_space(void)
{
    snd_pcm_status_t *status;
    int ret;
    if ((ret = snd_pcm_status_malloc(&status)) < 0){
      snd_pcm_status_free(status);
      printf(_("alsa-space: memory allocation error: %s\n"), snd_strerror(ret));
      return(0);
    }
    if ((ret = snd_pcm_status(m_handle, status)) < 0){
      snd_pcm_status_free(status);
      printf(_("alsa-space: cannot get pcm status: %s\n"), snd_strerror(ret));
      return(0);
    }
    switch(snd_pcm_status_get_state(status)){
      case SND_PCM_STATE_OPEN:
      case SND_PCM_STATE_PREPARED:
      case SND_PCM_STATE_RUNNING:
      // If the amount available is greater than a period send the 
      // period size instead of the amount available. 
      if((ret = snd_pcm_status_get_avail(status)) > m_iDataSize/16){
          ret =  (m_iDataSize/16);
      }
      break;
      default:
          ret = 0;
    }
    snd_pcm_status_free(status);
    if(ret < 0)
      ret = 0;
    return(ret);
}
                                                                                                                                    


void AlsaPMO::WorkerThread(void)
{
    void                *pBuffer;
    Error               eErr;
    Event               *pEvent;
    int                       length,l1=0;      
    int                       bytesWrittenToALSABuffer=0;
    bool                bMixer;
    // The following keeps track of what we sent to alsa since start.
    totalWrittenToALSABuffer = 0;
     
    // Don't do anything until resume is called.
    m_pPauseSem->Wait();

    // Sleep for a pre buffer period
    PreBuffer();

    // The following should be abstracted out into the general thread
    // classes:  This is also found in our decoders
    struct sched_param sParam;

    sParam.sched_priority = sched_get_priority_max(SCHED_OTHER);
    pthread_setschedparam(pthread_self(), SCHED_OTHER, &sParam);


    for(; !m_bExit;){ 
      if (m_bPause){
          m_pPauseSem->Wait();
          continue;
      }
      // Loop until we get an Init event from the LMC
      // ES: how does this conditional loop Robert?
      if (!m_properlyInitialized){
          pEvent = ((EventBuffer *)m_pInputBuffer)->GetEvent();
          if (pEvent == NULL){
            m_pLmc->Wake();
            WasteTime();
            continue;
          }
          if (pEvent->Type() == PMO_Init){
            if (IsError(Init(((PMOInitEvent *)pEvent)->GetInfo()))){
                delete pEvent;
                break;
            }
          }
          delete pEvent;
          continue;
      }
      // RK: after each begin/endread combo we should come back here
      // Set up reading a block from the buffer. If not enough bytes are
      // available, sleep for a little while and try again.
      for(;;){
          /*
            RK: Determine the number of bytes we're supposed to can send here.
            If know ahead of time, then pass that num if not already
            contained in m_iDataSize
          
            Dynamically allocate buffer to how much alsa has free :) 
            Thanks goes out to joy@pingfm.org and mplayer? for most of 
            the get_space function
          */
          length = get_space();
          eErr = ((EventBuffer *)m_pInputBuffer)->BeginRead(pBuffer, 
                                                     length*alsa_frame_size);    
          if (eErr == kError_EndOfStream || eErr == kError_Interrupt)
            break;
          if (eErr == kError_NoDataAvail){
            m_pLmc->Wake();
            CheckForBufferUp();
            WasteTime();
            continue;
          }
          // Is there an event pending that we need to take care of
          // before we play this block of samples?
          if (eErr == kError_EventPending){
            pEvent = ((EventBuffer *)m_pInputBuffer)->PeekEvent();
            if (pEvent == NULL)
                continue; 
            if (pEvent->Type() == PMO_Quit && 
                ((EventBuffer *)m_pInputBuffer)->GetNumBytesInBuffer() > 0){
                if (WaitForDrain()){
                  Reset(true);
                        m_pTarget->AcceptEvent(new Event(INFO_DoneOutputting));
                  return;
                }
                continue;
            }
            pEvent = ((EventBuffer *)m_pInputBuffer)->GetEvent();
            if (pEvent->Type() == PMO_Init)
                Init(((PMOInitEvent *)pEvent)->GetInfo());
            if (pEvent->Type() == PMO_Reset)
                Reset(false);
            if (pEvent->Type() == PMO_Info) 
                HandleTimeInfoEvent((PMOTimeInfoEvent *)pEvent);
            if (pEvent->Type() == PMO_Quit){
                delete pEvent;
                    m_pTarget->AcceptEvent(new Event(INFO_DoneOutputting));
                return;
            }
            delete pEvent;
            continue;
          }
          if (IsError(eErr)){
            ReportError(_("Internal error occured."));
            m_pContext->log->Error(_("Cannot read from buffer in PMO "
                                    "worker tread: %d\n"), eErr);
            break;
          }

          break;
      }
      if(!m_bExit && !m_bPause){
          if (poll (pfds, nfds, 1000)<=0 ) {
            printf(_("poll failed OH NO\n"));
          }
      }
      for (l1 = 0; l1 < nfds; l1++) { 
          if (pfds[l1].revents == 0 ) {
            printf(_("poll descriptor failed !\n"));
          }
          if ((snd_pcm_avail_update (m_handle)) == EPIPE) {
                  printf (_("xrun occured\n"));
                break;
          }
          /* deliver the data */
          for(;!m_bPause && !m_bExit;){
            m_context->prefs->GetPrefBoolean(kSoftMixerPref, &bMixer);
            if(bMixer)
                      ZinfVolumize((short*)pBuffer, length*alsa_frame_size);        
            if((bytesWrittenToALSABuffer = snd_pcm_writei(m_handle, pBuffer, 
                            length) ) != length) {
                // This code here may actually make the rest obscolete
                // But i'm unsure how exactly to test the errors that
                // they represent so until then, this way works.
                while (bytesWrittenToALSABuffer <= 0){
                  snd_pcm_prepare(m_handle);
                  bytesWrittenToALSABuffer = snd_pcm_writei(m_handle,pBuffer,
                            length);
                }
            }
            if (bytesWrittenToALSABuffer == -EAGAIN ){
                printf("EGAIN\n");
                CheckForBufferUp(); 
                WasteTime();
                continue;
            } 
            else 
            if (bytesWrittenToALSABuffer == -EIO){
                printf("EIO\n");    
                snd_pcm_prepare(m_handle);
                continue;
            }
            break;
          }
          if(bytesWrittenToALSABuffer < 0){
            m_pInputBuffer->EndRead(0);
            break;
          }
          if (m_bExit){
            m_pInputBuffer->EndRead(0);
            break;
          } 
          if (m_bPause){
            if (bytesWrittenToALSABuffer == -EAGAIN)
                m_pInputBuffer->EndRead(0);
            else{
                m_pInputBuffer->EndRead(bytesWrittenToALSABuffer);
                UpdateBufferStatus();
            }
            break;
          }
          totalWrittenToALSABuffer += length *alsa_frame_size;
          ((EventBuffer *)m_pInputBuffer)->EndRead(length*alsa_frame_size);
          m_pLmc->Wake();
          UpdateBufferStatus();
      }
    }

}

/* arch-tag: c066ff65-1921-4adc-9885-f6fcb76c4fc7
   (do not change this comment) */

Generated by  Doxygen 1.6.0   Back to index