reworked shortcircuit audio code.

This commit is contained in:
gardners 2012-05-10 16:58:25 +09:30
parent b15e5cfee7
commit 850d7b42d7
5 changed files with 359 additions and 103 deletions

263
audio_alsa.c Normal file
View File

@ -0,0 +1,263 @@
/*
Copyright (C) 2012 Paul Gardner-Stephen
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <alsa/asoundlib.h>
#include "serval.h"
#ifndef ANDROID
#define ALSA_LIB_PATH "/lib/libasound.so.2"
#else
// XXX Verify on a SGS2 ?
#define ALSA_LIB_PATH "/system/lib/libasound.so.2"
#endif
/*
For Android systems we have the fun that this binary (not just the source code)
needs to be able to run on systems that lack the alsa library.
This means it is time for some more dlopen() and dlsym() fun to get the
necessary symbols we need.
*/
typedef struct alsa_functions {
int (*snd_pcm_open)(snd_pcm_t **,char *,int,int);
int (*snd_pcm_close)(snd_pcm_t *);
int (*snd_pcm_hw_params_malloc)(snd_pcm_hw_params_t **);
int (*snd_pcm_hw_params_any)(snd_pcm_t *,snd_pcm_hw_params_t *);
int (*snd_pcm_hw_params_set_access)(snd_pcm_t *,snd_pcm_hw_params_t *,int);
int (*snd_pcm_hw_params_set_format)(snd_pcm_t *,snd_pcm_hw_params_t *,int);
int (*snd_pcm_hw_params_set_rate_near)(snd_pcm_t *,snd_pcm_hw_params_t *,int,int);
int (*snd_pcm_hw_params_set_channels)(snd_pcm_t *,snd_pcm_hw_params_t *,int);
int (*snd_pcm_hw_params)(snd_pcm_t *,snd_pcm_hw_params_t *);
void (*snd_pcm_hw_params_free)(snd_pcm_hw_params_t *);
int (*snd_pcm_prepare)(snd_pcm_t *);
int (*snd_pcm_writei)(snd_pcm_t *,short *,int);
int (*snd_pcm_readi)(snd_pcm_t *,short *,int);
int (*snd_pcm_poll_descriptors)(snd_pcm_t *,struct pollfd *,unsigned int);
} alsa_functions;
#define S(X) #X
#define GETSYM(X) {a->X = dlsym(h,S(X)); if (!a->X) { dlclose(h); free(a); return -1; }}
alsa_functions *alsa = NULL;
int alsa_load()
{
void *h = dlopen(ALSA_LIB_PATH,RTLD_LAZY);
if (!h) return -1;
alsa_functions *a=calloc(sizeof(alsa_functions),1);
GETSYM(snd_pcm_open);
GETSYM(snd_pcm_hw_params_malloc);
GETSYM(snd_pcm_hw_params_any);
GETSYM(snd_pcm_hw_params_set_access);
GETSYM(snd_pcm_hw_params_set_format);
GETSYM(snd_pcm_hw_params_set_rate_near);
GETSYM(snd_pcm_hw_params_set_channels);
GETSYM(snd_pcm_hw_params);
GETSYM(snd_pcm_hw_params_free);
GETSYM(snd_pcm_writei);
GETSYM(snd_pcm_readi);
GETSYM(snd_pcm_close);
GETSYM(snd_pcm_poll_descriptors);
alsa=a;
dlclose(h);
return 0;
}
int alsa_handles_initialised=0;
snd_pcm_t *play_handle;
snd_pcm_t *record_handle;
snd_pcm_hw_params_t *play_params;
snd_pcm_hw_params_t *record_params;
int audio_alsa_stop_play()
{
if (!alsa) return 0;
alsa->snd_pcm_hw_params_free(play_params); play_params=NULL;
alsa->snd_pcm_close(play_handle);
return 0;
}
int audio_alsa_stop_record()
{
if (!alsa) return 0;
alsa->snd_pcm_hw_params_free(record_params); record_params=NULL;
alsa->snd_pcm_close(record_handle);
return 0;
}
int audio_alsa_start_play()
{
int r;
/* if already playing, then return. */
if (alsa_handles_initialised) return 0;
if (!alsa) return -1;
record_handle=NULL; play_handle=NULL;
record_params=NULL; play_params=NULL;
/* Open playback device */
r = alsa->snd_pcm_open (&play_handle,"default",SND_PCM_STREAM_PLAYBACK,
SND_PCM_NONBLOCK);
if (r) { WHYF("ALSA pcm_open() failed"); goto error; }
/* Configure playback device for 8000Hz, 16 bit, mono */
r=alsa->snd_pcm_hw_params_malloc(&play_params);
if (r) { WHYF("ALSA hw_params_malloc() failed"); goto error; }
r=alsa->snd_pcm_hw_params_any(play_handle,play_params);
if (r) { WHYF("ALSA hw_params_any() failed"); goto error; }
r=alsa->snd_pcm_hw_params_set_access(play_handle,play_params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (r) { WHYF("ALSA hw_params_set_access() failed"); goto error; }
r=alsa->snd_pcm_hw_params_set_format(play_handle,play_params,
SND_PCM_FORMAT_S16_LE);
if (r) { WHYF("ALSA hw_params_set_format() failed"); goto error; }
r=alsa->snd_pcm_hw_params_set_rate_near(play_handle,play_params,8000,0);
if (r) { WHYF("ALSA hw_params_set_rate_near() failed"); goto error; }
r=alsa->snd_pcm_hw_params_set_channels(play_handle,play_params,1);
if (r) { WHYF("ALSA hw_params_set_channels() failed"); goto error; }
r=alsa->snd_pcm_hw_params(play_handle,play_params);
if (r) { WHYF("ALSA snd_pcm_hw_params() failed"); goto error; }
alsa->snd_pcm_hw_params_free(play_params); play_params=NULL;
r=alsa->snd_pcm_prepare(play_handle);
if (r) { WHYF("ALSA snd_pcm_prepare() failed"); goto error; }
WHY("Playback device configured");
error:
/* close handles and generally cleanup after ourselves */
audio_alsa_stop_play();
return -1;
}
int audio_alsa_start_record()
{
/* Open recording device non-blocking */
int r = alsa->snd_pcm_open (&record_handle,"default",SND_PCM_STREAM_CAPTURE,
SND_PCM_NONBLOCK);
if (r) { WHYF("ALSA pcm_open() failed"); goto error; }
/* Configure playback device for 8000Hz, 16 bit, mono */
r=alsa->snd_pcm_hw_params_malloc(&record_params);
if (r) { WHYF("ALSA hw_params_malloc() failed"); goto error; }
r=alsa->snd_pcm_hw_params_any(record_handle,record_params);
if (r) { WHYF("ALSA hw_params_any() failed"); goto error; }
r=alsa->snd_pcm_hw_params_set_access(record_handle,record_params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (r) { WHYF("ALSA hw_params_set_access() failed"); goto error; }
r=alsa->snd_pcm_hw_params_set_format(record_handle,record_params,
SND_PCM_FORMAT_S16_LE);
if (r) { WHYF("ALSA hw_params_set_format() failed"); goto error; }
r=alsa->snd_pcm_hw_params_set_rate_near(record_handle,record_params,8000,0);
if (r) { WHYF("ALSA hw_params_set_rate_near() failed"); goto error; }
r=alsa->snd_pcm_hw_params_set_channels(record_handle,record_params,1);
if (r) { WHYF("ALSA hw_params_set_channels() failed"); goto error; }
r=alsa->snd_pcm_hw_params(record_handle,record_params);
if (r) { WHYF("ALSA snd_pcm_hw_params() failed"); goto error; }
alsa->snd_pcm_hw_params_free(record_params); record_params=NULL;
r=alsa->snd_pcm_prepare(record_handle);
if (r) { WHYF("ALSA snd_pcm_prepare() failed"); goto error; }
WHY("Record device configured");
return 0;
error:
/* close handles and generally cleanup after ourselves */
audio_alsa_stop_record();
return -1;
}
int audio_alsa_stop()
{
audio_alsa_stop_play();
audio_alsa_stop_record();
return 0;
}
int audio_alsa_start()
{
if (audio_alsa_start_play()) return -1;
if (audio_alsa_start_record()) {
audio_alsa_stop();
return -1;
}
return 0;
}
int audio_alsa_pollfds(struct pollfd *fds,int slots)
{
int used_play
=alsa->snd_pcm_poll_descriptors(play_handle,fds,slots);
int used_record
=alsa->snd_pcm_poll_descriptors(record_handle,&fds[used_play],slots);
return used_play+used_record;
}
int audio_alsa_read(unsigned char *buffer,int maximum_bytes)
{
int frames_read=0;
if ((frames_read=
alsa->snd_pcm_readi(record_handle, (short *)buffer, maximum_bytes/2))<0)
{
alsa->snd_pcm_prepare(record_handle);
frames_read
=alsa->snd_pcm_readi(play_handle, (short *)buffer, maximum_bytes/2);
}
return frames_read*2;
}
int audio_alsa_write(unsigned char *buffer,int bytes)
{
/* 16 bits per sample, so frames = bytes/2 */
int frames_written=0;
if ((frames_written=alsa->snd_pcm_writei(play_handle, (short *)buffer, bytes/2))<0)
{
alsa->snd_pcm_prepare(play_handle);
return alsa->snd_pcm_writei(play_handle, (short *)buffer, bytes/2)*2;
}
else return 0;
}
monitor_audio *audio_alsa_detect()
{
if (!alsa) alsa_load();
if (!alsa) return NULL;
int r;
snd_pcm_t *handle;
if ((r = alsa->snd_pcm_open (&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
return NULL;
alsa->snd_pcm_close(handle);
monitor_audio *au=calloc(sizeof(monitor_audio),1);
strcpy(au->name,"ALSA compatible");
au->start=audio_alsa_start;
au->stop=audio_alsa_stop;
au->poll_fds=audio_alsa_pollfds;
au->read=audio_alsa_read;
au->write=audio_alsa_write;
return au;
}

View File

@ -31,6 +31,7 @@ extern int recordFd;
extern int playBufferSize;
extern int recordBufferSize;
#include "serval.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@ -239,21 +240,6 @@ set_volume_rpc (uint32_t device, uint32_t method, uint32_t volume)
return 0;
}
/* See if we can query end-points for this device.
If so, assume we have detected it.
*/
char *audio_msm_g1_detect()
{
int fd = open ("/dev/msm_snd", O_RDWR);
if (fd<0) return NULL;
int endpoints=0;
int rc =ioctl(fd,SND_GET_NUM_ENDPOINTS,&endpoints);
close(fd);
if (rc>0) return "G1/IDEOS style MSM audio";
else return NULL;
}
/* Prepare audio path, volume etc, and then open play and
record file descriptors.
*/
@ -265,7 +251,7 @@ int audio_msm_g1_start_play()
/* Look through endpoints for the regular in-call endpoint */
int endpoints=0;
int rc =ioctl(fd,SND_GET_NUM_ENDPOINTS,&endpoints);
ioctl(fd,SND_GET_NUM_ENDPOINTS,&endpoints);
int endpoint=-1;
int i;
for(i=0;i<endpoints;i++) {
@ -394,3 +380,68 @@ int audio_msm_g1_start()
return 0;
}
int audio_msm_g1_poll_fds(struct pollfd *fds,int slots)
{
int count=0;
if (playFd>-1&&slots>0) {
fds[count].fd=playFd;
fds[count].events=POLL_IN;
count++; slots--;
}
return count;
}
int audio_msm_g1_read(unsigned char *buffer,int maximum_count)
{
/* Regardless of the maximum, we must read exactly buffer sized pieces
on this audio device */
if (maximum_count<recordBufferSize) {
return WHY("Supplied buffer has no space for sample quanta");
}
int b=read(recordFd,&buffer[0],recordBufferSize);
return b;
}
int playBufferBytes=0;
unsigned char playBuffer[65536];
int audio_msm_g1_write(unsigned char *data,int bytes)
{
if (bytes+playBufferBytes>65536)
{ WHY("Play marshalling buffer full");
return 0; }
bcopy(&data[0],&playBuffer[playBufferBytes],bytes);
playBufferBytes+=bytes;
int i;
for(i=0;i<playBufferBytes;i+=playBufferSize)
{
if (write(playFd,&playBuffer[i],playBufferSize)<
playBufferSize)
break;
}
bcopy(&playBuffer[i],&playBuffer[0],playBufferBytes-i);
playBufferBytes-=i;
return bytes;
}
/* See if we can query end-points for this device.
If so, assume we have detected it.
*/
monitor_audio *audio_msm_g1_detect()
{
int fd = open ("/dev/msm_snd", O_RDWR);
if (fd<0) return NULL;
int endpoints=0;
int rc =ioctl(fd,SND_GET_NUM_ENDPOINTS,&endpoints);
close(fd);
if (rc>0) {
monitor_audio *au=calloc(sizeof(monitor_audio),1);
strcpy(au->name,"G1/IDEOS style MSM audio");
au->start=audio_msm_g1_start;
au->stop=audio_msm_g1_stop;
au->poll_fds=audio_msm_g1_poll_fds;
au->read=audio_msm_g1_read;
au->write=audio_msm_g1_write;
return au;
} else return NULL;
}

View File

@ -21,8 +21,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#define AUDIO_MSM_G1_ETC 1
#define AUDIO_MSM_N1_ETC 2
int detectedAudioDevice=-1;
char *detectedAudioDeviceName=NULL;
monitor_audio *audev=NULL;
int playFd=-1;
int recordFd=-1;
@ -31,10 +30,10 @@ int recordBufferSize=0;
int detectAudioDevice()
{
detectedAudioDeviceName=audio_msm_g1_detect();
if (detectedAudioDeviceName) {
detectedAudioDevice=AUDIO_MSM_G1_ETC;
WHYF("Detected audio device '%s'",detectedAudioDeviceName);
if (!audev) audev=audio_msm_g1_detect();
if (!audev) audev=audio_alsa_detect();
if (audev) {
WHYF("Detected audio device '%s'",audev->name);
return 0;
}
return -1;
@ -56,88 +55,29 @@ int getAudioBytes(unsigned char *buffer,
int offset,
int bufferSize)
{
switch(detectedAudioDevice) {
/* some devices require reading a whole buffer in one go */
case AUDIO_MSM_G1_ETC:
{
if (bufferSize-offset<recordBufferSize) {
return WHY("Supplied buffer has no space for new samples");
}
int b=read(recordFd,&buffer[offset],recordBufferSize);
if (b>0) offset+=b;
return offset;
}
break;
/* while others allow reading an arbitrary amount */
default:
{
int b=read(recordFd,&buffer[offset],bufferSize-offset);
if (b>0) offset+=b;
return offset;
}
break;
if (audev&&audev->write) {
return audev->write(&buffer[offset],bufferSize-offset);
}
return WHYF("Reading audio for device class #%d not implemented",
detectedAudioDevice);
return -1;
}
/* as with recording, some of the devices have a fixed buffer size that
we must completely fill.
*/
int playBufferBytes=0;
unsigned char playBuffer[65536];
int playAudio(unsigned char *data,int bytes)
{
switch(detectedAudioDevice) {
/* some devices require reading a whole buffer in one go */
case AUDIO_MSM_G1_ETC:
if (bytes+playBufferBytes>65536)
return WHY("Play marshalling buffer full");
bcopy(&data[0],&playBuffer[playBufferBytes],bytes);
playBufferBytes+=bytes;
int i;
for(i=0;i<playBufferBytes;i+=playBufferSize)
{
if (write(playFd,&playBuffer[i],playBufferSize)<
playBufferSize)
break;
}
bcopy(&playBuffer[i],&playBuffer[0],playBufferBytes-i);
playBufferBytes-=i;
break;
/* the rest we can just write() to */
default:
if (write(playFd,data,bytes)<bytes)
return WHY("short write() when playing audio");
return 0;
}
return WHYF("Playing audio for device class #%d not implemented",
detectedAudioDevice);
if (audev&&audev->write) return audev->write(data,bytes);
return -1;
}
int stopAudio()
{
switch(detectedAudioDevice) {
case AUDIO_MSM_G1_ETC:
return audio_msm_g1_stop();
break;
default:
break;
}
return WHYF("Stopping audio for device class #%d not implemented",
detectedAudioDevice);
if (audev&&audev->stop) return audev->stop();
return -1;
}
int startAudio()
{
switch(detectedAudioDevice) {
case AUDIO_MSM_G1_ETC:
return audio_msm_g1_start();
break;
default:
break;
}
return WHYF("Starting audio for device class #%d not implemented",
detectedAudioDevice);
if (audev&&audev->start) return audev->start();
return -1;
}

View File

@ -82,7 +82,7 @@ int app_monitor_cli(int argc, const char *const *argv, struct command_line_optio
if (pipeAudio) {
detectAudioDevice();
char *name=detectedAudioDeviceName;
char *name=audev?audev->name:NULL;
if (!name) {
WHY("Could not detect any audio device. Will not pipe audio.");
pipeAudio=0;

View File

@ -98,6 +98,7 @@ struct in_addr {
#include <fcntl.h>
//FIXME #include <getopt.h>
#include <ctype.h>
#include <sys/stat.h>
#ifdef ANDROID
#define DEFAULT_INSTANCE_PATH "/data/data/org.servalproject/var/serval-node"
@ -1423,24 +1424,25 @@ int monitor_call_status(vomp_call_state *call);
int monitor_send_audio(vomp_call_state *call,overlay_mdp_frame *audio);
extern int monitor_socket_count;
#define AUDIO_MSM_G1_ETC 1
#define AUDIO_MSM_N1_ETC 2
extern int detectedAudioDevice;
extern char *detectedAudioDeviceName;
typedef struct monitor_audio {
char name[128];
int (*start)();
int (*stop)();
int (*poll_fds)(struct pollfd *,int);
int (*read)(unsigned char *,int);
int (*write)(unsigned char *,int);
} monitor_audio;
extern monitor_audio *audev;
monitor_audio *audio_msm_g1_detect();
monitor_audio *audio_alsa_detect();
int detectAudioDevice();
int getAudioPlayFd();
int getAudioRecordFd();
int getAudioFd();
int getAudioBytes(unsigned char *buffer,
int offset,
int bufferSize);
int playAudio(unsigned char *data,int bytes);
int stopAudio();
int startAudio();
int encodeAndDispatchRecordedAudio(int fd,int callSessionToken,
int recordCodec,
unsigned char *sampleData,
int sampleBytes);
char *audio_msm_g1_detect();
int audio_msm_g1_start();
int audio_msm_g1_stop();