blob: 95e9accd20fb644d38d62d5dc4a8cbe7289d30e3 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2012
* All rights reserved
*
* This file is part of GPAC / linux_oss audio render module
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#if defined(__DARWIN__) || defined(__APPLE__)
#include <soundcard.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#else
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef OSS_FIX_INC
#include <soundcard.h>
#else
#include <sys/soundcard.h>
#endif
#endif
#include <gpac/modules/audio_out.h>
#define OSS_AUDIO_DEVICE "/dev/dsp"
typedef struct
{
int audio_dev, sr, nb_ch;
u32 buf_size, delay, num_buffers, total_duration;
u32 force_sr;
char *wav_buf;
} OSSContext;
#define OSSCTX() OSSContext *ctx = (OSSContext *)dr->opaque;
static GF_Err OSS_Setup(GF_AudioOutput*dr, void *os_handle, u32 num_buffers, u32 total_duration)
{
int audio;
const char *opt;
OSSCTX();
opt = gf_opts_get_key("core", "force-alsarate");
if (opt) ctx->force_sr = atoi(opt);
/*open OSS in non-blocking mode*/
audio = open(OSS_AUDIO_DEVICE, 0);
if (audio < 0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[OSS] Cannot open audio device\n"));
return GF_NOT_SUPPORTED;
}
/*set blocking mode back*/
//fcntl(audio, F_SETFL, fcntl(audio, F_GETFL) & ~FNDELAY);
ctx->audio_dev=audio;
ctx->num_buffers = num_buffers;
ctx->total_duration = total_duration;
return GF_OK;
}
static void OSS_Shutdown(GF_AudioOutput*dr)
{
OSSCTX();
ioctl(ctx->audio_dev,SNDCTL_DSP_RESET,NULL);
close(ctx->audio_dev);
if (ctx->wav_buf) gf_free(ctx->wav_buf);
ctx->wav_buf = NULL;
}
static GF_Err OSS_Configure(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u32 channel_cfg)
{
int format, blockalign, nb_bufs, frag_spec;
long flags;
OSSCTX();
if (!ctx) return GF_BAD_PARAM;
/* reset and reopen audio-device */
ioctl(ctx->audio_dev,SNDCTL_DSP_RESET,NULL);
close(ctx->audio_dev);
if (ctx->wav_buf) gf_free(ctx->wav_buf);
ctx->wav_buf = NULL;
ctx->audio_dev=open(OSS_AUDIO_DEVICE,O_WRONLY|O_NONBLOCK);
if (!ctx->audio_dev) return GF_IO_ERR;
/* Make the file descriptor use blocking writes with fcntl() so that
we don't have to handle sleep() ourselves*/
flags = fcntl(ctx->audio_dev, F_GETFL);
flags &= ~O_NONBLOCK;
if (fcntl(ctx->audio_dev, F_SETFL, flags) < 0 ) return GF_IO_ERR;
ctx->nb_ch = (int) (*NbChannels);
if (ioctl(ctx->audio_dev, SNDCTL_DSP_CHANNELS, &ctx->nb_ch)==-1) return GF_IO_ERR;
blockalign = ctx->nb_ch;
//only support for PCM 8/16/24/32 packet mode
switch (*audioFormat) {
case GF_AUDIO_FMT_U8:
format = AFMT_S8;
break;
default:
//otherwise force PCM16
*audioFormat = GF_AUDIO_FMT_S16;
case GF_AUDIO_FMT_S16:
blockalign *= 2;
format = AFMT_S16_LE;
break;
}
if(ioctl(ctx->audio_dev, SNDCTL_DSP_SETFMT,&format)==-1) return GF_IO_ERR;
ctx->sr = (*SampleRate);
if(ioctl(ctx->audio_dev, SNDCTL_DSP_SPEED,&ctx->sr)==-1) return GF_IO_ERR;
nb_bufs = ctx->num_buffers ? ctx->num_buffers : 8;
ctx->buf_size = (*SampleRate * blockalign * ctx->total_duration) / (1000 * nb_bufs);
frag_spec = 4;
while (ctx->buf_size > (1<<(frag_spec+1)))
frag_spec++;
ctx->buf_size = 1<<frag_spec;
ctx->delay = (1000*ctx->buf_size) / (*SampleRate * blockalign);
frag_spec = ((nb_bufs<<16) & 0xFFFF0000) | frag_spec;
ctx->delay = (1000*ctx->buf_size*nb_bufs) / (*SampleRate * blockalign);
if ( ioctl(ctx->audio_dev, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0 ) return GF_IO_ERR;
GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[OSS] setup %d buffers %d bytes each (%d ms buffer delay)", nb_bufs, ctx->buf_size, ctx->delay));
ctx->wav_buf = gf_realloc(ctx->wav_buf, ctx->buf_size*sizeof(char));
if(!ctx->wav_buf) return GF_OUT_OF_MEM;
memset(ctx->wav_buf, 0, ctx->buf_size*sizeof(char));
return GF_OK;
}
static void OSS_WriteAudio(GF_AudioOutput*dr)
{
u32 written;
OSSCTX();
written = dr->FillBuffer(dr->audio_renderer, ctx->wav_buf, ctx->buf_size);
/*this will also perform sleep*/
if (written) {
u32 reallyWritten = write(ctx->audio_dev, ctx->wav_buf, written);
if (reallyWritten != written) {
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[OSS] Failed to write all audio to device, has written %u, should have %u", reallyWritten, written));
}
}
}
static void OSS_SetVolume(GF_AudioOutput*dr, u32 Volume) {}
static void OSS_SetPan(GF_AudioOutput*dr, u32 Pan) {}
static void OSS_SetPriority(GF_AudioOutput*dr, u32 Priority) {}
static u32 OSS_GetAudioDelay(GF_AudioOutput*dr)
{
OSSCTX()
return ctx->delay;
}
/*
* to get the best matching samplerate the oss-device can be set up
* with the desired sr. if not supported the returned value contains the
* best matching sr.
*
* todo: supported samplerate could depend on nb_channels and format
*/
static GF_Err OSS_QueryOutputSampleRate(GF_AudioOutput*dr, u32 *desired_sr, u32 *NbChannels, u32 *nbBitsPerSample)
{
#ifdef FORCE_SR_LIMIT
*NbChannels = 2;
if (!( *desired_sr % 11025)) return GF_OK;
if (*desired_sr<22050) *desired_sr = 22050;
else *desired_sr = 44100;
return GF_OK;
#else
/* reset and reopen audio-device */
int i;
OSSCTX();
if (ctx->force_sr) {
*desired_sr = ctx->force_sr;
return GF_OK;
}
i=*desired_sr;
if(ioctl(ctx->audio_dev, SNDCTL_DSP_SPEED,&i)==-1) return GF_IO_ERR;
GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[OSS] uses samplerate %d for desired sr %d\n", i, *desired_sr));
*desired_sr = i;
i = *NbChannels;
if(ioctl(ctx->audio_dev,SNDCTL_DSP_CHANNELS, &i)==-1) return GF_IO_ERR;
GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[OSS] uses %d channels for %d desired ones\n", i, *NbChannels));
*NbChannels = i;
if(ioctl(ctx->audio_dev, SNDCTL_DSP_SPEED,&ctx->sr)==-1) return GF_OK;
if(ioctl(ctx->audio_dev,SNDCTL_DSP_CHANNELS, &ctx->nb_ch)==-1) return GF_OK;
return GF_OK;
#endif
}
void *NewOSSRender()
{
OSSContext *ctx;
GF_AudioOutput*driv;
ctx = gf_malloc(sizeof(OSSContext));
if(!ctx)
return NULL;
memset(ctx, 0, sizeof(OSSContext));
driv = gf_malloc(sizeof(GF_AudioOutput));
if(!driv)
{
gf_free(ctx);
ctx=NULL;
return NULL;
}
memset(driv, 0, sizeof(GF_AudioOutput));
driv->opaque = ctx;
driv->SelfThreaded = 0;
driv->Setup = OSS_Setup;
driv->Shutdown = OSS_Shutdown;
driv->Configure = OSS_Configure;
driv->GetAudioDelay = OSS_GetAudioDelay;
driv->SetVolume = OSS_SetVolume;
driv->SetPan = OSS_SetPan;
driv->SetPriority = OSS_SetPriority;
driv->QueryOutputSampleRate = OSS_QueryOutputSampleRate;
driv->WriteAudio = OSS_WriteAudio;
GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "OSS Audio Output", "gpac distribution");
return driv;
}
void DeleteOSSRender(void *ifce)
{
GF_AudioOutput*dr = (GF_AudioOutput*) ifce;
OSSContext *ctx = (OSSContext *)dr->opaque;
gf_free(ctx);
gf_free(dr);
}
/*
* ********************************************************************
* interface
*/
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 NewOSSRender();
return NULL;
}
GPAC_MODULE_EXPORT
void ShutdownInterface(GF_BaseInterface *ifce)
{
if (ifce->InterfaceType==GF_AUDIO_OUTPUT_INTERFACE)
DeleteOSSRender((GF_AudioOutput*)ifce);
}
GPAC_MODULE_STATIC_DECLARATION( oss )