This source file includes following definitions.
- ALSA_Setup
- ALSA_Shutdown
- ALSA_ConfigureOutput
- ALSA_WriteAudio
- ALSA_SetVolume
- ALSA_SetPan
- ALSA_SetPriority
- ALSA_GetAudioDelay
- ALSA_QueryOutputSampleRate
- NewALSAOutput
- DeleteALSAOutput
- QueryInterfaces
- LoadInterface
- ShutdownInterface
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <poll.h>
#include <alsa/asoundlib.h>
#include <gpac/modules/audio_out.h>
typedef struct
{
snd_pcm_t *playback_handle;
u32 nb_ch, buf_size, delay, num_buffers, total_duration, block_align;
u32 force_sr;
const char *dev_name;
char *wav_buf;
} ALSAContext;
static GF_Err ALSA_Setup(GF_AudioOutput*dr, void *os_handle, u32 num_buffers, u32 total_duration)
{
int err;
const char *opt;
ALSAContext *ctx = (ALSAContext*)dr->opaque;
opt = gf_modules_get_option((GF_BaseInterface *)dr, "ALSA", "ForceSampleRate");
if (opt) ctx->force_sr = atoi(opt);
ctx->dev_name = gf_modules_get_option((GF_BaseInterface *)dr, "ALSA", "DeviceName");
if (!ctx->dev_name) {
ctx->dev_name = "hw:0,0";
gf_modules_set_option((GF_BaseInterface *)dr, "ALSA", "DeviceName", ctx->dev_name);
}
err = snd_pcm_open(&ctx->playback_handle, ctx->dev_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot open audio device %s: %s\n", ctx->dev_name, snd_strerror (err)) );
return GF_IO_ERR;
}
ctx->num_buffers = num_buffers ? num_buffers : 2;
ctx->total_duration = total_duration ? total_duration : 100;
return GF_OK;
}
static void ALSA_Shutdown(GF_AudioOutput*dr)
{
ALSAContext *ctx = (ALSAContext*)dr->opaque;
if (ctx->playback_handle) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[ALSA] Closing alsa output\n") );
snd_pcm_close(ctx->playback_handle);
ctx->playback_handle = NULL;
}
if (ctx->wav_buf) gf_free(ctx->wav_buf);
ctx->wav_buf = NULL;
}
static GF_Err ALSA_ConfigureOutput(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg)
{
snd_pcm_hw_params_t *hw_params = NULL;
int err;
int nb_bufs, sr, period_time;
ALSAContext *ctx = (ALSAContext*)dr->opaque;
if (!ctx) return GF_BAD_PARAM;
if (ctx->playback_handle) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[ALSA] Closing audio device %s\n", ctx->dev_name ));
snd_pcm_close(ctx->playback_handle);
ctx->playback_handle = NULL;
}
if (ctx->wav_buf) gf_free(ctx->wav_buf);
ctx->wav_buf = NULL;
err = snd_pcm_open(&ctx->playback_handle, ctx->dev_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot open audio device %s: %s\n", ctx->dev_name, snd_strerror (err)) );
return GF_IO_ERR;
}
err = snd_pcm_hw_params_malloc(&hw_params);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot allocate hardware params: %s\n", snd_strerror (err)) );
goto err_exit;
}
err = snd_pcm_hw_params_any(ctx->playback_handle, hw_params);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot initialize hardware params: %s\n", snd_strerror (err)) );
goto err_exit;
}
err = snd_pcm_hw_params_set_access(ctx->playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set access type: %s\n", snd_strerror (err)) );
goto err_exit;
}
ctx->nb_ch = (int) (*NbChannels);
ctx->block_align = ctx->nb_ch;
if ((*nbBitsPerSample) == 16) {
ctx->block_align *= 2;
err = snd_pcm_hw_params_set_format(ctx->playback_handle, hw_params, SND_PCM_FORMAT_S16_LE);
} else {
err = snd_pcm_hw_params_set_format(ctx->playback_handle, hw_params, SND_PCM_FORMAT_U8);
}
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set sample format: %s\n", snd_strerror (err)) );
goto err_exit;
}
if (ctx->force_sr) *SampleRate = ctx->force_sr;
sr = *SampleRate;
err = snd_pcm_hw_params_set_rate_near(ctx->playback_handle, hw_params, SampleRate, 0);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set sample rate: %s\n", snd_strerror (err)) );
goto err_exit;
}
if (sr != *SampleRate) {
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[ALSA] Sample rate %d not supported, using %d instead\n", sr, *SampleRate ) );
sr = *SampleRate;
}
err = snd_pcm_hw_params_set_channels_near(ctx->playback_handle, hw_params, NbChannels);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set channel count: %s\n", snd_strerror (err)) );
goto err_exit;
}
if (ctx->nb_ch != *NbChannels) {
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[ALSA] %d channels not supported - using %d instead\n", ctx->nb_ch, *NbChannels ) );
ctx->block_align /= ctx->nb_ch;
ctx->nb_ch = *NbChannels;
ctx->block_align *= ctx->nb_ch;
}
nb_bufs = ctx->num_buffers;
err = snd_pcm_hw_params_set_periods_near(ctx->playback_handle, hw_params, &nb_bufs, 0);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set number of HW buffers (%d): %s\n", nb_bufs, snd_strerror(err) ));
goto err_exit;
}
ctx->buf_size = (sr * ctx->total_duration)/1000 / nb_bufs;
err = snd_pcm_hw_params_set_period_size_near(ctx->playback_handle, hw_params, (snd_pcm_uframes_t *)&ctx->buf_size, 0);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set HW buffer size (%d): %s\n", ctx->buf_size, snd_strerror(err) ));
goto err_exit;
}
err = snd_pcm_hw_params_get_buffer_size(hw_params, (snd_pcm_uframes_t *)&ctx->buf_size);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot get HW buffer size (%d): %s\n", ctx->buf_size, snd_strerror(err) ));
goto err_exit;
}
ctx->buf_size *= ctx->block_align;
snd_pcm_hw_params_get_period_time(hw_params, &period_time, 0);
err = snd_pcm_hw_params(ctx->playback_handle, hw_params);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set parameters: %s\n", snd_strerror (err)) );
goto err_exit;
}
snd_pcm_hw_params_free (hw_params);
hw_params = NULL;
ctx->delay = (ctx->buf_size*1000) / (sr*ctx->block_align);
ctx->wav_buf = gf_malloc(ctx->buf_size*sizeof(char));
if(!ctx->wav_buf) return GF_OUT_OF_MEM;
memset(ctx->wav_buf, 0, ctx->buf_size*sizeof(char));
GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[ALSA] Setup %d ch @ %d hz - %d periods of %d us - total buffer size %d - overall delay %d ms\n", ctx->nb_ch, sr, nb_bufs, period_time, ctx->buf_size, ctx->delay));
return GF_OK;
err_exit:
if (hw_params) snd_pcm_hw_params_free(hw_params);
snd_pcm_close(ctx->playback_handle);
ctx->playback_handle = NULL;
return GF_IO_ERR;
}
static void ALSA_WriteAudio(GF_AudioOutput*dr)
{
u32 written;
snd_pcm_sframes_t nb_frames;
int err;
ALSAContext *ctx = (ALSAContext*)dr->opaque;
err = snd_pcm_wait(ctx->playback_handle, 1);
if (err<0) {
if (err == -EPIPE) {
GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[ALSA] Broken connection to sound card - restoring!\n"));
snd_pcm_prepare(ctx->playback_handle);
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] error %s while waiting!\n", snd_strerror(err) ));
return;
}
}
nb_frames = snd_pcm_avail_update(ctx->playback_handle);
if (nb_frames < 0) {
if (nb_frames == -EPIPE) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] an xrun occured!\n"));
snd_pcm_prepare(ctx->playback_handle);
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] unknown ALSA avail update return value (%d)\n", nb_frames));
}
return;
}
if (!nb_frames) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[ALSA] no frame to write\n" ));
return;
}
written = dr->FillBuffer(dr->audio_renderer, ctx->wav_buf, (u32) (ctx->block_align*nb_frames) );
if (!written) return;
written /= ctx->block_align;
err = snd_pcm_writei(ctx->playback_handle, ctx->wav_buf, written);
if (err == -EPIPE ) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] an xrun occured!\n"));
snd_pcm_prepare(ctx->playback_handle);
err = snd_pcm_writei(ctx->playback_handle, ctx->wav_buf, nb_frames);
}
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Write failure: %s\n", snd_strerror(err)));
}
}
static void ALSA_SetVolume(GF_AudioOutput*dr, u32 Volume)
{
}
static void ALSA_SetPan(GF_AudioOutput*dr, u32 Pan)
{
}
static void ALSA_SetPriority(GF_AudioOutput*dr, u32 Priority)
{
}
static u32 ALSA_GetAudioDelay(GF_AudioOutput*dr)
{
ALSAContext *ctx = (ALSAContext*)dr->opaque;
return ctx->delay;
}
static GF_Err ALSA_QueryOutputSampleRate(GF_AudioOutput*dr, u32 *desired_sr, u32 *NbChannels, u32 *nbBitsPerSample)
{
ALSAContext *ctx = (ALSAContext*)dr->opaque;
int err;
snd_pcm_hw_params_t *hw_params = NULL;
err = snd_pcm_hw_params_malloc(&hw_params);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot allocate hardware params: %s\n", snd_strerror (err)) );
goto err_exit;
}
err = snd_pcm_hw_params_any(ctx->playback_handle, hw_params);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot initialize hardware params: %s\n", snd_strerror (err)) );
goto err_exit;
}
err = snd_pcm_hw_params_set_rate_near(ctx->playback_handle, hw_params, desired_sr, 0);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot check available sample rates: %s\n", snd_strerror (err)) );
goto err_exit;
}
err = snd_pcm_hw_params_set_channels_near(ctx->playback_handle, hw_params, NbChannels);
if (err < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot check available channels: %s\n", snd_strerror (err)) );
goto err_exit;
}
snd_pcm_hw_params_free (hw_params);
hw_params = NULL;
return GF_OK;
err_exit:
snd_pcm_hw_params_free (hw_params);
hw_params = NULL;
return GF_IO_ERR;
}
void *NewALSAOutput()
{
ALSAContext *ctx;
GF_AudioOutput*driv;
GF_SAFEALLOC(ctx, ALSAContext);
if(!ctx) return NULL;
GF_SAFEALLOC(driv, GF_AudioOutput);
if(!driv) {
gf_free(ctx);
return NULL;
}
driv->opaque = ctx;
driv->SelfThreaded = 0;
driv->Setup = ALSA_Setup;
driv->Shutdown = ALSA_Shutdown;
driv->ConfigureOutput = ALSA_ConfigureOutput;
driv->GetAudioDelay = ALSA_GetAudioDelay;
driv->SetVolume = ALSA_SetVolume;
driv->SetPan = ALSA_SetPan;
driv->SetPriority = ALSA_SetPriority;
driv->QueryOutputSampleRate = ALSA_QueryOutputSampleRate;
driv->WriteAudio = ALSA_WriteAudio;
GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "ALSA Audio Output", "gpac distribution");
return driv;
}
void DeleteALSAOutput(void *ifce)
{
GF_AudioOutput*dr = (GF_AudioOutput*) ifce;
ALSAContext *ctx = (ALSAContext *)dr->opaque;
gf_free(ctx);
gf_free(dr);
}
GPAC_MODULE_EXPORT
const u32 *QueryInterfaces()
{
static u32 si [] = {
GF_AUDIO_OUTPUT_INTERFACE,
0
};
return si;
}
GPAC_MODULE_EXPORT
GF_BaseInterface *LoadInterface(u32 InterfaceType)
{
if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE)
return NewALSAOutput();
return NULL;
}
GPAC_MODULE_EXPORT
void ShutdownInterface(GF_BaseInterface *ifce)
{
if (ifce->InterfaceType==GF_AUDIO_OUTPUT_INTERFACE)
DeleteALSAOutput((GF_AudioOutput*)ifce);
}
GPAC_MODULE_STATIC_DECLARATION( alsa )