I'm not sure if the api tutorial is slightly out of date or just completely useless due to it's complete simplicity in that the buffer is created after you find out the period_size so you never partially fill a period but it is for everyone else in the world who has a buffer before they find out the period.
I'm trying to figure out why i get little clicks and blips when i really dont think i should since i think i'm doing everything correctly. My theory is that the period_size available is more than the size i'm sending it even though I set the period size. ok, my situation is this. I have a buffer coming it that's 2KB, 16384 bits. Parameters are set up like normal and in the tutorial for standard pcm playback. I set any, access, format, channels, rate_near, period_size. periodsize is set to 1/4 buffersize. Then i go and have a loop that loops my writei command in another function until the stream ends. my writei command has a size argument of 1/4 of the buffer i'm sending it. now when I do this, and use it the audio playback is slow sounding. So i have a function that waits some time and a conditional wrapped around the writei command to only do the write when the available periodsize is greater than or equal to 1/4 of the buffer size. This allows the audio to play at normal speed but like i started off saying, i get clicks and blips throughout the playing. I'm not sure what i'm not doing here. I've attached the main portion of the code, you can get the entire thing from http://sourceforge.net/cvs/?group_id=51494 pull zinf ...currently mp3s wont work at all due to their tinier buffer that gets sent to the alsapmo but ogg vorbis files almost work. If someone could explain where i'm going wrong here I might be able to finish this up. Thanks I'm getting desperate as it doesn't seem the 0.5x code that this is built off of had to do anything special timing wise to work and the tutorial examples bypass all timing problems because they make the buffersize to the available buffersize that alsa says it has available.
/*____________________________________________________________________________ FreeAmp - The Free MP3 Player Driver for Advanced Linux Sound Architecture http://www.alsa-project.org Portions Copyright (C) 1998-1999 EMusic.com alsapmo.cpp by Fleischer Gabor <[EMAIL PROTECTED]> uses code by Anders Semb Hermansen <[EMAIL PROTECTED]> and Jaroslav Kysela <[EMAIL PROTECTED]> 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.4 2002/06/26 04:20:53 safemode Exp $ ____________________________________________________________________________*/ /* system headers */ #include <stdlib.h> #include <iostream.h> #include <alsa/asoundlib.h> #include <ctype.h> #include <errno.h> #include <string.h> /* project headers */ #include <config.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_iBytesPerSample = 0; m_iBaseTime = -1; m_iDataSize = 0; if (!m_pBufferThread) { m_pBufferThread = Thread::CreateThread(); assert(m_pBufferThread); m_pBufferThread->Create(AlsaPMO::StartWorkerThread, this); } m_handle = NULL; m_device = NULL; m_channels = -1; m_samples = -1; } AlsaPMO::~AlsaPMO() { m_bExit = true; m_pSleepSem->Signal(); m_pPauseSem->Signal(); if (m_pBufferThread) { m_pBufferThread->Join(); delete m_pBufferThread; } snd_pcm_close(m_handle); } Error AlsaPMO::Init(OutputInfo* info) { int err; snd_pcm_hw_params_t *params; // printf("init\n"); m_properlyInitialized = false; m_iDataSize = info->max_buffer_size; err=snd_pcm_open(&m_handle, "plughw:0,0", SND_PCM_STREAM_PLAYBACK, 0); // SND_PCM_NONBLOCK); if (err < 0) { ReportError("Audio device is busy. Please make sure that " "another program is not using the device."); return (Error)pmoError_DeviceOpenFailed; } snd_output_t *errlog; snd_output_stdio_attach(&errlog, stderr, 0); // configure the device: m_channels=info->number_of_channels; m_samples=info->samples_per_second; m_samplesPerFrame=info->samples_per_frame; snd_pcm_hw_params_alloca(¶ms); // printf("%d: %d\n", __LINE__, err); err = snd_pcm_hw_params_any(m_handle, params); // printf("%d: %d\n", __LINE__, err); err = snd_pcm_hw_params_set_access(m_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); // printf("%d: %d\n", __LINE__, m_samplesPerFrame); err = snd_pcm_hw_params_set_format(m_handle, params, SND_PCM_FORMAT_S16_LE); // printf("%d: %d\n", __LINE__, err); err = snd_pcm_hw_params_set_channels(m_handle, params, m_channels); // printf("%d: %d\n", __LINE__, err); err = snd_pcm_hw_params_set_rate_near(m_handle, params, m_samples, 0); // printf("%d: %d\n", __LINE__, err); err = snd_pcm_hw_params_set_period_size(m_handle, params,m_iDataSize/4, 0); err = snd_pcm_hw_params_set_periods(m_handle, params, 4, 0); // printf("%d: %d\n", __LINE__, m_iDataSize); // err = snd_pcm_hw_params_set_periods(m_handle, params, 2, 0); // printf("%d: %d\n", __LINE__, err); err = snd_pcm_hw_params(m_handle, params); if (err < 0) { ReportError("Cannot initialize audio device."); return (Error)pmoError_DeviceOpenFailed; } snd_pcm_prepare(m_handle); // printf("init complete\n"); m_properlyInitialized = true; return kError_NoErr; } Error AlsaPMO::Reset(bool user_stop) { // printf("reset\n"); if (user_stop) snd_pcm_drain(m_handle); else snd_pcm_drop(m_handle); snd_pcm_prepare(m_handle); return kError_NoErr; } void AlsaPMO::Pause(void) { // printf("pause\n"); m_iBaseTime = -1; PhysicalMediaOutput::Pause(); } bool AlsaPMO::WaitForDrain(void) { snd_pcm_status_t *status; printf("wait for drain\n"); snd_pcm_status_alloca(&status); for(; !m_bExit && !m_bPause; ) { snd_pcm_status(m_handle, status); printf("num avail: %d\n", snd_pcm_status_get_avail(status)); if (snd_pcm_status_get_avail(status) == snd_pcm_status_get_avail_max(status)) { return true; } WasteTime(); } return false; } void AlsaPMO::HandleTimeInfoEvent(PMOTimeInfoEvent *pEvent) { MediaTimeInfoEvent *pmtpi; int32 hours, minutes, seconds; int iTotalTime = 0; return; if (m_iBaseTime < 0) { m_iBaseTime = (pEvent->GetFrameNumber() * m_samplesPerFrame) / m_samples; } hours = iTotalTime / 3600; minutes = (iTotalTime / 60) % 60; seconds = iTotalTime % 60; if (hours < 0 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59) return; pmtpi = new MediaTimeInfoEvent(hours, minutes, seconds, 0, iTotalTime, 0); m_pTarget->AcceptEvent(pmtpi); } void AlsaPMO::StartWorkerThread(void *pVoidBuffer) { ((AlsaPMO*)pVoidBuffer)->WorkerThread(); } void AlsaPMO::WorkerThread(void) { void *pBuffer; Error eErr; int iRet = -1; Event *pEvent; snd_pcm_status_t *status; snd_pcm_status_alloca(&status); // 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: #ifdef __linux__ struct sched_param sParam; sParam.sched_priority = sched_get_priority_max(SCHED_OTHER); pthread_setschedparam(pthread_self(), SCHED_OTHER, &sParam); #endif for(; !m_bExit;) { if (m_bPause) { m_pPauseSem->Wait(); continue; } // Loop until we get an Init event from the LMC 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; } // Set up reading a block from the buffer. If not enough bytes are // available, sleep for a little while and try again. for(;;) { eErr = ((EventBuffer *)m_pInputBuffer)->BeginRead(pBuffer, m_iDataSize); 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; } // Now write the block to the audio device. If the block doesn't // all fit, pause and loop until the entire block has been played. // This loop could be written using non-blocking io... for(;!m_bExit && !m_bPause;) { snd_pcm_status(m_handle, status); WasteTime(); if(snd_pcm_status_get_avail(status) >= m_iDataSize/4){ ZinfVolumize((short*)pBuffer, m_iDataSize); iRet = snd_pcm_writei(m_handle, pBuffer, m_iDataSize/4); } if (iRet == -EAGAIN) { CheckForBufferUp(); WasteTime(); continue; } if (iRet == -EIO) { snd_pcm_prepare(m_handle); continue; } if (iRet < 0) { printf("ret: %d\n", iRet); m_pInputBuffer->EndRead(0); //exit(0); } break; } if (m_bExit) { m_pInputBuffer->EndRead(0); return; } if (m_bPause) { if (iRet == -EAGAIN) m_pInputBuffer->EndRead(0); else { m_pInputBuffer->EndRead(iRet); UpdateBufferStatus(); } continue; } if (iRet < 0) { m_pInputBuffer->EndRead(0); ReportError("Could not write sound data to the soundcard."); m_pContext->log->Error("Failed to write to the soundcard: %s\n", strerror(errno)); break; } m_pInputBuffer->EndRead(iRet); m_pLmc->Wake(); UpdateBufferStatus(); } }