#include #include #include #include #define ALSA_DEV "plughw:0,0" #define LATENCY (60) enum { ERR_OK = 0, ERR_NOMEM, ERR_OPEN, ERR_CONFIG, ERR_SETACCESS, ERR_SETFORMAT, ERR_SMPLRATE, ERR_CHNNLS, ERR_PERIODS, ERR_BUFSZ, ERR_POLLDESC, ERR_PREPARE, ERR_FILEOPEN, }; typedef struct { /* snd_pcm_hw_params_t: * This structure contains information about * the hardware and can be used to specify the * configuration to be used for the PCM stream. */ snd_pcm_hw_params_t* m_pHWParams; snd_pcm_t* m_pAlsaHndl; snd_async_handler_t* m_pAsyncHandler; int m_nFrames; uint8_t* m_pPCMBuf; size_t m_PCMBufSz; size_t m_currBufLoc; } type_Alsa; typedef struct { int m_smplRate; int m_nChnnls; } type_ASettings; static int _initAlsa(type_Alsa** ppAlsa) { if (NULL == (*ppAlsa = realloc(NULL, sizeof(type_Alsa)))) { return ERR_NOMEM; } memset(*ppAlsa, 0, sizeof(type_Alsa)); return ERR_OK; } static int _realizeAlsa(type_Alsa* pAlsa, type_ASettings* pSettings) { int exactSmplRate = 0; /* snd_pcm_open(...): * Open PCM. The last parameter of this functioin is the mode. * If this is set to 0, the standard mode is used. Possible * other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC. If * SND_PCM_NONBLOCK is used, read/write access to the * PCM device will return immediately. If SND_PCM_ASYNC is * specified, SIGIO will be emitted whenever a period has * been completely processed by the soundcard. */ if (snd_pcm_open( &pAlsa->m_pAlsaHndl, ALSA_DEV, SND_PCM_STREAM_PLAYBACK, 0) < 0) { return ERR_OPEN; } /* Init hwparams with full configuration space */ if (snd_pcm_hw_params_malloc(&pAlsa->m_pHWParams)) { return ERR_NOMEM; } if (NULL == pAlsa->m_pHWParams) { return ERR_NOMEM; } if (snd_pcm_hw_params_any( pAlsa->m_pAlsaHndl, pAlsa->m_pHWParams) < 0) { return ERR_CONFIG; } /* * Set access type. This can be either * SND_PCM_ACCESS_RW_INTERLEAVED or * SND_PCM_ACCESS_RW_NONINTERLEAVED. * There are also access types for MMAPed * access, but this is beyond the scope * of this introduction. */ if (snd_pcm_hw_params_set_access( pAlsa->m_pAlsaHndl, pAlsa->m_pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { return ERR_SETACCESS; } /* Set sample format */ if (snd_pcm_hw_params_set_format( pAlsa->m_pAlsaHndl, pAlsa->m_pHWParams, SND_PCM_FORMAT_S16_LE) < 0) { return ERR_SETFORMAT; } /* Set sample rate. If the exact rate is not supported * by the hardware, use the nearest possible rate. */ exactSmplRate = pSettings->m_smplRate; if (snd_pcm_hw_params_set_rate_near( pAlsa->m_pAlsaHndl, pAlsa->m_pHWParams, &exactSmplRate, 0) < 0) { return ERR_SMPLRATE; } if (pSettings->m_smplRate != exactSmplRate) { fprintf( stderr, "The rate %d Hz is not supported by your hardware.\n" "Using %d Hz instead.\n", pSettings->m_smplRate, exactSmplRate); } /* Set number of channels */ if (snd_pcm_hw_params_set_channels( pAlsa->m_pAlsaHndl, pAlsa->m_pHWParams, pSettings->m_nChnnls) < 0) { return ERR_CHNNLS; } /* Set number of periods. Periods used to be called fragments. */ if (snd_pcm_hw_params_set_periods( pAlsa->m_pAlsaHndl, pAlsa->m_pHWParams, 2, 0) < 0) { return ERR_PERIODS; } /* Calculate the required buffer size for the desired latency * (latency in seconds * rate * bytesPerFrame) / periods = period size */ { int desiredBufSz = (LATENCY * exactSmplRate * 4) / 1000; snd_pcm_uframes_t minBufFrames = 0; snd_pcm_uframes_t desiredBufFrames; int minBufSz = 0; snd_pcm_hw_params_get_buffer_size_min( pAlsa->m_pHWParams, &minBufFrames); minBufSz = minBufFrames * 4; if (minBufSz > desiredBufSz) { fprintf( stderr, "Using min audio buffer size: %d" " Latency: %d instead of buffer size" " %d, Latency: %d", minBufSz, (minBufSz * 1000) / (exactSmplRate * 4), desiredBufSz, LATENCY); pAlsa->m_nFrames = minBufFrames; } else { pAlsa->m_nFrames = (snd_pcm_uframes_t)(desiredBufSz / 4); } if (snd_pcm_hw_params_set_buffer_size( pAlsa->m_pAlsaHndl, pAlsa->m_pHWParams, pAlsa->m_nFrames) < 0) { return ERR_BUFSZ; } } /* Apply HW parameter settings to PCM device and prepare device */ if (snd_pcm_hw_params(pAlsa->m_pAlsaHndl, pAlsa->m_pHWParams) < 0) { return ERR_PREPARE; } return ERR_OK; } static int _destroyAlsa(type_Alsa** ppAlsa) { /* stop cpm device and drop pending frames */ snd_pcm_drop((*ppAlsa)->m_pAlsaHndl); /* Stop PCM device after pending frames have been played */ snd_pcm_drain((*ppAlsa)->m_pAlsaHndl); /* free alsa */ snd_pcm_hw_params_free((*ppAlsa)->m_pHWParams); /* close handle */ snd_pcm_close((*ppAlsa)->m_pAlsaHndl); *ppAlsa = realloc(*ppAlsa, 0); return ERR_OK; } static int _loadVorbis( uint8_t** ppPCMBuf, size_t* pPCMSz, int* pNChnnls, int* pNSmplRate, const char* pFileName) { size_t bytesRead = 0; vorbis_info* pInfo; OggVorbis_File oggFile; int bitstream; FILE* pFile = NULL; uint8_t tmpBuf[1024]; /* open file */ if (NULL == (pFile = fopen(pFileName, "rb"))) { return ERR_FILEOPEN; } ov_open(pFile, &oggFile, NULL, 0); /* retreive ogg stream info */ pInfo = ov_info(&oggFile, -1); *pNChnnls = pInfo->channels; *pNSmplRate = pInfo->rate; /* decode file */ *ppPCMBuf = NULL; *pPCMSz = 0; while(0 < (bytesRead = ov_read(&oggFile, tmpBuf, sizeof(tmpBuf), 0, 2, 1, &bitstream))) { if (NULL == (*ppPCMBuf = realloc(*ppPCMBuf, *pPCMSz + bytesRead))) { ov_clear(&oggFile); fclose(pFile); return ERR_NOMEM; } memcpy(&((*ppPCMBuf)[*pPCMSz]), tmpBuf, bytesRead); *pPCMSz += bytesRead; } /* clean up */ ov_clear(&oggFile); return ERR_OK; } static void _writePCM(type_Alsa* pAlsa) { int periodSize = pAlsa->m_nFrames / 2; int err = snd_pcm_writei( pAlsa->m_pAlsaHndl, &pAlsa->m_pPCMBuf[pAlsa->m_currBufLoc], periodSize); if (err < 0) { printf("Write error: %s\n", snd_strerror(err)); exit(-1); } else if (err != periodSize) { printf("Write error: written %i expected %i\n", err, periodSize); exit(-1); } else { pAlsa->m_currBufLoc += (err * 4); } } static void _asyncPCMNotify(snd_async_handler_t* pAlsaHndlr) { snd_pcm_t* pHndl = snd_async_handler_get_pcm(pAlsaHndlr); type_Alsa* pAlsa = snd_async_handler_get_callback_private(pAlsaHndlr); snd_pcm_sframes_t availFrames = snd_pcm_avail_update(pHndl); int periodSize = pAlsa->m_nFrames / 2; while (availFrames >= periodSize) { _writePCM(pAlsa); availFrames = snd_pcm_avail_update(pAlsa->m_pAlsaHndl); } } int main(int argc, char* argv[]) { int err; type_ASettings asettings; type_Alsa* pAlsa = NULL; /* verify command line args */ if (argc != 2) { fprintf(stderr, "ERROR: alsa_test \n"); return -127; } /* init alsa object */ if (0 != (err = _initAlsa(&pAlsa))) { fprintf(stderr, "Error initing alsa: %d\n", err); return -1; } /* load ogg file */ if (0 != (err = _loadVorbis( &pAlsa->m_pPCMBuf, &pAlsa->m_PCMBufSz, &asettings.m_nChnnls, &asettings.m_smplRate, argv[1]))) { fprintf(stderr, "Error loading ogg/vorbis file: %d\n", err); } fprintf( stdout, "Initalize alsa {%d channels, %dhz}\n", asettings.m_nChnnls, asettings.m_smplRate); /* realize alsa */ if (0 != (err = _realizeAlsa(pAlsa, &asettings))) { fprintf(stderr, "Error realizing alsa: %d\n", err); return -1; } /* register async handler */ if (0 > (err = snd_async_add_pcm_handler( &pAlsa->m_pAsyncHandler, pAlsa->m_pAlsaHndl, _asyncPCMNotify, pAlsa))) { fprintf(stderr, "Error setting pcm handler: %s\n", snd_strerror(err)); return -1; } /* play sound */ if (0 > (err = snd_pcm_prepare(pAlsa->m_pAlsaHndl))) { fprintf( stderr, "Error preparing audio interface: %s\n", snd_strerror(err)); } /* write initial buffer */ _writePCM(pAlsa); _writePCM(pAlsa); /* wait for stream to finish */ while (pAlsa->m_currBufLoc < pAlsa->m_PCMBufSz) { sleep(1); } /* release buffers */ pAlsa->m_pPCMBuf = realloc(pAlsa->m_pPCMBuf, 0); if (0 != (err = _destroyAlsa(&pAlsa))) { fprintf(stderr, "Error destroying alsa: %d\n", err); return -1; } return 0; }