mirror of
https://github.com/servalproject/serval-dna.git
synced 2025-03-11 15:03:56 +00:00
substantial work towards playing audio on IDEOS/G1 type MSM chipset
android phones directly from C to dramatically reduce latency.
This commit is contained in:
parent
d96d8ff8d6
commit
8d2792ce74
12
Android.mk
12
Android.mk
@ -67,7 +67,10 @@ LOCAL_SRC_FILES:= \
|
|||||||
serval-dna/vomp.c \
|
serval-dna/vomp.c \
|
||||||
serval-dna/lsif.c \
|
serval-dna/lsif.c \
|
||||||
serval-dna/monitor.c \
|
serval-dna/monitor.c \
|
||||||
serval-dna/monitor-cli.c
|
serval-dna/monitor-cli.c \
|
||||||
|
serval-dna/codecs.c \
|
||||||
|
serval-dna/audiodevices.c \
|
||||||
|
serval-dna/audio_msm_g1.c
|
||||||
|
|
||||||
LOCAL_MODULE:= serval
|
LOCAL_MODULE:= serval
|
||||||
|
|
||||||
@ -95,3 +98,10 @@ LOCAL_MODULE:= servald
|
|||||||
LOCAL_SRC_FILES:= serval-dna/servalwrap.c
|
LOCAL_SRC_FILES:= serval-dna/servalwrap.c
|
||||||
|
|
||||||
include $(BUILD_EXECUTABLE)
|
include $(BUILD_EXECUTABLE)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE:= playwav
|
||||||
|
LOCAL_SRC_FILES:= serval-dna/playwav.c
|
||||||
|
|
||||||
|
include $(BUILD_EXECUTABLE)
|
||||||
|
@ -40,7 +40,10 @@ SRCS= batman.c \
|
|||||||
vomp.c \
|
vomp.c \
|
||||||
lsif.c \
|
lsif.c \
|
||||||
monitor.c \
|
monitor.c \
|
||||||
monitor-cli.c
|
monitor-cli.c \
|
||||||
|
codecs.c \
|
||||||
|
audiodevices.c \
|
||||||
|
audio_msm_g1.c
|
||||||
|
|
||||||
HAVE_VOIPTEST= @HAVE_VOIPTEST@
|
HAVE_VOIPTEST= @HAVE_VOIPTEST@
|
||||||
ifeq ($(HAVE_VOIPTEST), 1)
|
ifeq ($(HAVE_VOIPTEST), 1)
|
||||||
|
396
audio_msm_g1.c
Normal file
396
audio_msm_g1.c
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
|
||||||
|
Contains code derived from playwav2.c, which has the following notice:
|
||||||
|
|
||||||
|
Copyright (C) 2008 The Android Open Source Project
|
||||||
|
*/
|
||||||
|
|
||||||
|
int setReason(char *fmt, ...);
|
||||||
|
#define WHY(X) setReason("%s:%d:%s() %s",__FILE__,__LINE__,__FUNCTION__,X)
|
||||||
|
#define WHYRETNULL(X) { setReason("%s:%d:%s() %s",__FILE__,__LINE__,__FUNCTION__,X); return NULL; }
|
||||||
|
#define WHYF(F, ...) setReason("%s:%d:%s() " F, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)
|
||||||
|
#define WHY_perror(X) setReason("%s:%d:%s() %s: %s [errno=%d]", __FILE__, __LINE__, __FUNCTION__, X, strerror(errno), errno)
|
||||||
|
|
||||||
|
extern int playFd;
|
||||||
|
extern int recordFd;
|
||||||
|
extern int playBufferSize;
|
||||||
|
extern int recordBufferSize;
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <linux/ioctl.h>
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#include <linux/msm_audio.h>
|
||||||
|
#else
|
||||||
|
/* ---------- linux/msm_audio.h -------- */
|
||||||
|
|
||||||
|
#define AUDIO_IOCTL_MAGIC 'a'
|
||||||
|
|
||||||
|
#define AUDIO_START _IOW(AUDIO_IOCTL_MAGIC, 0, unsigned)
|
||||||
|
#define AUDIO_STOP _IOW(AUDIO_IOCTL_MAGIC, 1, unsigned)
|
||||||
|
#define AUDIO_FLUSH _IOW(AUDIO_IOCTL_MAGIC, 2, unsigned)
|
||||||
|
#define AUDIO_GET_CONFIG _IOR(AUDIO_IOCTL_MAGIC, 3, unsigned)
|
||||||
|
#define AUDIO_SET_CONFIG _IOW(AUDIO_IOCTL_MAGIC, 4, unsigned)
|
||||||
|
#define AUDIO_GET_STATS _IOR(AUDIO_IOCTL_MAGIC, 5, unsigned)
|
||||||
|
#define AUDIO_ENABLE_AUDPP _IOW(AUDIO_IOCTL_MAGIC, 6, unsigned)
|
||||||
|
#define AUDIO_SET_ADRC _IOW(AUDIO_IOCTL_MAGIC, 7, unsigned)
|
||||||
|
#define AUDIO_SET_EQ _IOW(AUDIO_IOCTL_MAGIC, 8, unsigned)
|
||||||
|
#define AUDIO_SET_RX_IIR _IOW(AUDIO_IOCTL_MAGIC, 9, unsigned)
|
||||||
|
|
||||||
|
#define EQ_MAX_BAND_NUM 12
|
||||||
|
|
||||||
|
#define ADRC_ENABLE 0x0001
|
||||||
|
#define ADRC_DISABLE 0x0000
|
||||||
|
#define EQ_ENABLE 0x0002
|
||||||
|
#define EQ_DISABLE 0x0000
|
||||||
|
#define IIR_ENABLE 0x0004
|
||||||
|
#define IIR_DISABLE 0x0000
|
||||||
|
|
||||||
|
struct eq_filter_type
|
||||||
|
{
|
||||||
|
int16_t gain;
|
||||||
|
uint16_t freq;
|
||||||
|
uint16_t type;
|
||||||
|
uint16_t qf;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct eqalizer
|
||||||
|
{
|
||||||
|
uint16_t bands;
|
||||||
|
uint16_t params[132];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rx_iir_filter
|
||||||
|
{
|
||||||
|
uint16_t num_bands;
|
||||||
|
uint16_t iir_params[48];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct msm_audio_config
|
||||||
|
{
|
||||||
|
uint32_t buffer_size;
|
||||||
|
uint32_t buffer_count;
|
||||||
|
uint32_t channel_count;
|
||||||
|
uint32_t sample_rate;
|
||||||
|
uint32_t codec_type;
|
||||||
|
uint32_t unused[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct msm_audio_stats
|
||||||
|
{
|
||||||
|
uint32_t out_bytes;
|
||||||
|
uint32_t unused[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Audio routing */
|
||||||
|
|
||||||
|
#define SND_IOCTL_MAGIC 's'
|
||||||
|
|
||||||
|
#define SND_MUTE_UNMUTED 0
|
||||||
|
#define SND_MUTE_MUTED 1
|
||||||
|
|
||||||
|
struct msm_snd_device_config
|
||||||
|
{
|
||||||
|
uint32_t device;
|
||||||
|
uint32_t ear_mute;
|
||||||
|
uint32_t mic_mute;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SND_SET_DEVICE _IOW(SND_IOCTL_MAGIC, 2, struct msm_device_config *)
|
||||||
|
|
||||||
|
#define SND_METHOD_VOICE 0
|
||||||
|
|
||||||
|
#define SND_METHOD_VOICE_1 1
|
||||||
|
|
||||||
|
struct msm_snd_volume_config
|
||||||
|
{
|
||||||
|
uint32_t device;
|
||||||
|
uint32_t method;
|
||||||
|
uint32_t volume;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SND_SET_VOLUME _IOW(SND_IOCTL_MAGIC, 3, struct msm_snd_volume_config *)
|
||||||
|
|
||||||
|
/* Returns the number of SND endpoints supported. */
|
||||||
|
|
||||||
|
#define SND_GET_NUM_ENDPOINTS _IOR(SND_IOCTL_MAGIC, 4, unsigned *)
|
||||||
|
|
||||||
|
struct msm_snd_endpoint
|
||||||
|
{
|
||||||
|
int id; /* input and output */
|
||||||
|
char name[64]; /* output only */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Takes an index between 0 and one less than the number returned by
|
||||||
|
* SND_GET_NUM_ENDPOINTS, and returns the SND index and name of a
|
||||||
|
* SND endpoint. On input, the .id field contains the number of the
|
||||||
|
* endpoint, and on exit it contains the SND index, while .name contains
|
||||||
|
* the description of the endpoint.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define SND_GET_ENDPOINT _IOWR(SND_IOCTL_MAGIC, 5, struct msm_snd_endpoint *)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int
|
||||||
|
do_route_audio_rpc (uint32_t device, int ear_mute, int mic_mute)
|
||||||
|
{
|
||||||
|
if (device == -1UL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
printf ("rpc_snd_set_device(%d, %d, %d)\n", device, ear_mute, mic_mute);
|
||||||
|
|
||||||
|
fd = open ("/dev/msm_snd", O_RDWR);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
perror ("Can not open snd device");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// RPC call to switch audio path
|
||||||
|
/* rpc_snd_set_device(
|
||||||
|
* device, # Hardware device enum to use
|
||||||
|
* ear_mute, # Set mute for outgoing voice audio
|
||||||
|
* # this should only be unmuted when in-call
|
||||||
|
* mic_mute, # Set mute for incoming voice audio
|
||||||
|
* # this should only be unmuted when in-call or
|
||||||
|
* # recording.
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
struct msm_snd_device_config args;
|
||||||
|
args.device = device;
|
||||||
|
args.ear_mute = ear_mute ? SND_MUTE_MUTED : SND_MUTE_UNMUTED;
|
||||||
|
args.mic_mute = mic_mute ? SND_MUTE_MUTED : SND_MUTE_UNMUTED;
|
||||||
|
|
||||||
|
if (ioctl (fd, SND_SET_DEVICE, &args) < 0)
|
||||||
|
{
|
||||||
|
perror ("snd_set_device error.");
|
||||||
|
close (fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
close (fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
set_volume_rpc (uint32_t device, uint32_t method, uint32_t volume)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
printf ("rpc_snd_set_volume(%d, %d, %d)\n", device, method, volume);
|
||||||
|
|
||||||
|
if (device == -1UL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fd = open ("/dev/msm_snd", O_RDWR);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
perror ("Can not open snd device");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* rpc_snd_set_volume(
|
||||||
|
* device, # Any hardware device enum, including
|
||||||
|
* # SND_DEVICE_CURRENT
|
||||||
|
* method, # must be SND_METHOD_VOICE to do anything useful
|
||||||
|
* volume, # integer volume level, in range [0,5].
|
||||||
|
* # note that 0 is audible (not quite muted)
|
||||||
|
* )
|
||||||
|
* rpc_snd_set_volume only works for in-call sound volume.
|
||||||
|
*/
|
||||||
|
struct msm_snd_volume_config args;
|
||||||
|
args.device = device;
|
||||||
|
args.method = method;
|
||||||
|
args.volume = volume;
|
||||||
|
|
||||||
|
if (ioctl (fd, SND_SET_VOLUME, &args) < 0)
|
||||||
|
{
|
||||||
|
perror ("snd_set_volume error.");
|
||||||
|
close (fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
close (fd);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
int audio_msm_g1_start_play()
|
||||||
|
{
|
||||||
|
/* Get audio control device */
|
||||||
|
int fd = open ("/dev/msm_snd", O_RDWR);
|
||||||
|
if (fd<0) return -1;
|
||||||
|
|
||||||
|
/* Look through endpoints for the regular in-call endpoint */
|
||||||
|
int endpoints=0;
|
||||||
|
int rc =ioctl(fd,SND_GET_NUM_ENDPOINTS,&endpoints);
|
||||||
|
int endpoint=-1;
|
||||||
|
int i;
|
||||||
|
for(i=0;i<endpoints;i++) {
|
||||||
|
struct msm_snd_endpoint ep;
|
||||||
|
ep.id=i;
|
||||||
|
ep.name[0]=0;
|
||||||
|
ioctl(fd,SND_GET_ENDPOINT,&ep);
|
||||||
|
if (!strcasecmp(ep.name,"HANDSET"))
|
||||||
|
/* should this be i, or ep.id ? */
|
||||||
|
endpoint=i;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
/* Set the specified endpoint and unmute microphone and speaker */
|
||||||
|
do_route_audio_rpc(endpoint,SND_MUTE_UNMUTED,SND_MUTE_UNMUTED);
|
||||||
|
|
||||||
|
/* Set the volume (somewhat arbitrarily for now) */
|
||||||
|
int vol=5;
|
||||||
|
int dev=0xd; /* no one seems to know what this magic value means */
|
||||||
|
set_volume_rpc(dev,SND_METHOD_VOICE_1, vol);
|
||||||
|
|
||||||
|
playFd=open("/dev/msm_pcm_out",O_RDWR);
|
||||||
|
struct msm_audio_config config;
|
||||||
|
if (ioctl(playFd, AUDIO_GET_CONFIG,&config))
|
||||||
|
{
|
||||||
|
close(playFd);
|
||||||
|
return WHY("Could not read audio device configuration");
|
||||||
|
}
|
||||||
|
config.channel_count=1;
|
||||||
|
config.sample_rate=8000;
|
||||||
|
playBufferSize=config.buffer_size;
|
||||||
|
if (ioctl(playFd, AUDIO_SET_CONFIG,&config))
|
||||||
|
{
|
||||||
|
close(playFd);
|
||||||
|
return WHY("Could not set audio device configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tell hardware to start playing */
|
||||||
|
ioctl(playFd,AUDIO_START,0);
|
||||||
|
|
||||||
|
WHY("G1/IDEOS style MSM audio device initialised and ready to play");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int audio_msm_g1_stop_play()
|
||||||
|
{
|
||||||
|
if (playFd>-1) close(playFd);
|
||||||
|
playFd=-1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int audio_msm_g1_start_record()
|
||||||
|
{
|
||||||
|
recordFd=open("/dev/msm_pcm_out",O_RDWR);
|
||||||
|
struct msm_audio_config config;
|
||||||
|
if (ioctl(recordFd, AUDIO_GET_CONFIG,&config))
|
||||||
|
{
|
||||||
|
close(recordFd);
|
||||||
|
return WHY("Could not read audio device configuration");
|
||||||
|
}
|
||||||
|
config.channel_count=1;
|
||||||
|
config.sample_rate=8000;
|
||||||
|
if (ioctl(recordFd, AUDIO_SET_CONFIG,&config))
|
||||||
|
{
|
||||||
|
close(recordFd);
|
||||||
|
return WHY("Could not set audio device configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
If recordBufferSize equates to too long an interval,
|
||||||
|
then try to reduce it in various ways.
|
||||||
|
*/
|
||||||
|
recordBufferSize=config.buffer_size;
|
||||||
|
float bufferTime=recordBufferSize/2*1.0/config.sample_rate;
|
||||||
|
if (bufferTime>0.02) {
|
||||||
|
WHYF("REC buf=%.3fsecs, which is too long. Trying to reduce it.");
|
||||||
|
|
||||||
|
/* 64 bytes = 32 samples = ~4ms */
|
||||||
|
config.buffer_size=64;
|
||||||
|
if (!ioctl(recordFd, AUDIO_SET_CONFIG,&config))
|
||||||
|
{
|
||||||
|
recordBufferSize=config.buffer_size;
|
||||||
|
bufferTime=recordBufferSize/2*1.0/config.sample_rate;
|
||||||
|
WHYF("Succeeded in reducing record buffer to %d bytes (%.3fsecs)",
|
||||||
|
recordBufferSize,bufferTime);
|
||||||
|
goto fixedBufferSize;
|
||||||
|
}
|
||||||
|
recordBufferSize=64;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* Ask for 2x speed and 2x channels, to divide effective buffer size by 4.
|
||||||
|
*/
|
||||||
|
config.sample_rate=16000;
|
||||||
|
config.channel_count=2;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
fixedBufferSize:
|
||||||
|
|
||||||
|
/* tell hardware to start playing */
|
||||||
|
ioctl(recordFd,AUDIO_START,0);
|
||||||
|
|
||||||
|
WHY("G1/IDEOS style MSM audio device initialised and ready to record");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int audio_msm_g1_stop_record()
|
||||||
|
{
|
||||||
|
if (recordFd>-1) close(recordFd);
|
||||||
|
recordFd=-1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int audio_msm_g1_stop()
|
||||||
|
{
|
||||||
|
audio_msm_g1_stop_play();
|
||||||
|
audio_msm_g1_stop_record();
|
||||||
|
}
|
||||||
|
|
||||||
|
int audio_msm_g1_start()
|
||||||
|
{
|
||||||
|
if (audio_msm_g1_start_play()) return -1;
|
||||||
|
if (audio_msm_g1_start_record()) {
|
||||||
|
audio_msm_g1_stop_play();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
143
audiodevices.c
Normal file
143
audiodevices.c
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
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 "serval.h"
|
||||||
|
|
||||||
|
#define AUDIO_MSM_G1_ETC 1
|
||||||
|
#define AUDIO_MSM_N1_ETC 2
|
||||||
|
int detectedAudioDevice=-1;
|
||||||
|
char *detectedAudioDeviceName=NULL;
|
||||||
|
|
||||||
|
int playFd=-1;
|
||||||
|
int recordFd=-1;
|
||||||
|
int playBufferSize=0;
|
||||||
|
int recordBufferSize=0;
|
||||||
|
|
||||||
|
int detectAudioDevice()
|
||||||
|
{
|
||||||
|
detectedAudioDeviceName=audio_msm_g1_detect();
|
||||||
|
if (detectedAudioDeviceName) {
|
||||||
|
detectedAudioDevice=AUDIO_MSM_G1_ETC;
|
||||||
|
WHYF("Detected audio device '%s'",detectedAudioDeviceName);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getAudioPlayFd()
|
||||||
|
{
|
||||||
|
return playFd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getAudioRecordFd()
|
||||||
|
{
|
||||||
|
return recordFd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read some audio, but not more than bufferSize-offset bytes.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return WHYF("Reading audio for device class #%d not implemented",
|
||||||
|
detectedAudioDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
42
codecs.c
Normal file
42
codecs.c
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
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 "serval.h"
|
||||||
|
|
||||||
|
int encodeAndDispatchRecordedAudio(int fd,int callSessionToken,
|
||||||
|
int recordCodec,
|
||||||
|
unsigned char *sampleData,
|
||||||
|
int sampleBytes)
|
||||||
|
{
|
||||||
|
switch (recordCodec) {
|
||||||
|
case VOMP_CODEC_PCM:
|
||||||
|
/* leave data raw, so need to rewrite sampleData or sampleBytes */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
WHYF("Codec not yet supported");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char msg[128+MAX_AUDIO_BYTES];
|
||||||
|
snprintf(msg,128,"\n*%d:AUDIO:%x:%d\n",sampleBytes,callSessionToken,recordCodec);
|
||||||
|
int len=strlen(msg);
|
||||||
|
bcopy(&sampleData[0],&msg[len],sampleBytes);
|
||||||
|
len+=sampleBytes;
|
||||||
|
write(fd,msg,len);
|
||||||
|
return 0;
|
||||||
|
}
|
196
monitor-cli.c
196
monitor-cli.c
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
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 <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -28,6 +46,16 @@ int writeLine(char *msg)
|
|||||||
|
|
||||||
int processChar(int c);
|
int processChar(int c);
|
||||||
|
|
||||||
|
int autoAnswerP=1;
|
||||||
|
int pipeAudio=1;
|
||||||
|
int syntheticAudio=0;
|
||||||
|
int showReceived=1;
|
||||||
|
int interactiveP=1;
|
||||||
|
int recordCodec=VOMP_CODEC_PCM;
|
||||||
|
int recordCodecBlockSamples=320;
|
||||||
|
int recordCodecTimespan=20;
|
||||||
|
int callSessionToken=0;
|
||||||
|
|
||||||
int app_monitor_cli(int argc, const char *const *argv, struct command_line_option *o)
|
int app_monitor_cli(int argc, const char *const *argv, struct command_line_option *o)
|
||||||
{
|
{
|
||||||
const char *sid=NULL;
|
const char *sid=NULL;
|
||||||
@ -52,15 +80,26 @@ int app_monitor_cli(int argc, const char *const *argv, struct command_line_optio
|
|||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pipeAudio) {
|
||||||
|
detectAudioDevice();
|
||||||
|
char *name=detectedAudioDeviceName;
|
||||||
|
if (!name) {
|
||||||
|
WHY("Could not detect any audio device. Will not pipe audio.");
|
||||||
|
pipeAudio=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct pollfd fds[128];
|
struct pollfd fds[128];
|
||||||
int fdcount=0;
|
int fdcount=0;
|
||||||
|
|
||||||
fds[fdcount].fd=fd;
|
fds[fdcount].fd=fd;
|
||||||
fds[fdcount].events=POLLIN;
|
fds[fdcount].events=POLLIN;
|
||||||
fdcount++;
|
fdcount++;
|
||||||
fds[fdcount].fd=STDIN_FILENO;
|
if (interactiveP) {
|
||||||
fds[fdcount].events=POLLIN;
|
fds[fdcount].fd=STDIN_FILENO;
|
||||||
fdcount++;
|
fds[fdcount].events=POLLIN;
|
||||||
|
fdcount++;
|
||||||
|
}
|
||||||
|
|
||||||
writeLine("monitor vomp\n");
|
writeLine("monitor vomp\n");
|
||||||
writeLine("monitor rhizome\n");
|
writeLine("monitor rhizome\n");
|
||||||
@ -71,25 +110,76 @@ int app_monitor_cli(int argc, const char *const *argv, struct command_line_optio
|
|||||||
writeLine(msg);
|
writeLine(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char line[1024];
|
||||||
|
/* Allow for up to one second of audio read from the microphone
|
||||||
|
to be buffered. This is probably more than we will ever need.
|
||||||
|
The primary purpose of the buffer is in fact to handle the fact
|
||||||
|
that we are unlikely to ever read exaclty the number of samples
|
||||||
|
we need, so we need to keep any left over ones from the previous
|
||||||
|
read. */
|
||||||
|
int audioRecordBufferBytes=0;
|
||||||
|
unsigned char audioRecordBuffer[8000*2];
|
||||||
|
|
||||||
|
int base_fd_count=fdcount;
|
||||||
while(1) {
|
while(1) {
|
||||||
|
fdcount=base_fd_count;
|
||||||
|
int audio_fd = getAudioRecordFd();
|
||||||
|
if (audio_fd>-1) {
|
||||||
|
fds[fdcount].fd=STDIN_FILENO;
|
||||||
|
fds[fdcount].events=POLLIN;
|
||||||
|
fdcount++;
|
||||||
|
}
|
||||||
poll(fds,fdcount,1000);
|
poll(fds,fdcount,1000);
|
||||||
|
|
||||||
fcntl(fd,F_SETFL,
|
fcntl(fd,F_SETFL,
|
||||||
fcntl(fd, F_GETFL, NULL)|O_NONBLOCK);
|
fcntl(fd, F_GETFL, NULL)|O_NONBLOCK);
|
||||||
fcntl(STDIN_FILENO,F_SETFL,
|
if (interactiveP)
|
||||||
fcntl(STDIN_FILENO, F_GETFL, NULL)|O_NONBLOCK);
|
fcntl(STDIN_FILENO,F_SETFL,
|
||||||
|
fcntl(STDIN_FILENO, F_GETFL, NULL)|O_NONBLOCK);
|
||||||
|
if (audio_fd>-1)
|
||||||
|
fcntl(audio_fd,F_SETFL,
|
||||||
|
fcntl(audio_fd, F_GETFL, NULL)|O_NONBLOCK);
|
||||||
|
|
||||||
char line[1024];
|
|
||||||
int bytes;
|
int bytes;
|
||||||
int i;
|
int i;
|
||||||
|
line[0]=0;
|
||||||
bytes=read(fd,line,1024);
|
bytes=read(fd,line,1024);
|
||||||
if (bytes>0)
|
if (bytes>0)
|
||||||
for(i=0;i<bytes;i++) processChar(line[i]);
|
for(i=0;i<bytes;i++) processChar(line[i]);
|
||||||
bytes=read(STDIN_FILENO,line,1024);
|
if (interactiveP) {
|
||||||
if (bytes>0) {
|
bytes=read(STDIN_FILENO,line,1024);
|
||||||
line[bytes]=0;
|
if (bytes>0) {
|
||||||
printf("< %s",line);
|
line[bytes]=0;
|
||||||
write(fd,line,bytes);
|
printf("< %s",line);
|
||||||
|
write(fd,line,bytes);
|
||||||
|
}
|
||||||
|
if (audio_fd>-1) {
|
||||||
|
/* attempt to read next audio buffer.
|
||||||
|
Some audio devices that we are using require an entire buffer
|
||||||
|
to be read at a time. */
|
||||||
|
audioRecordBufferBytes
|
||||||
|
=getAudioBytes(audioRecordBuffer,audioRecordBufferBytes,sizeof(audioRecordBuffer));
|
||||||
|
/* 8KHz 16 bit samples = 16000 bytes per second.
|
||||||
|
Thus one 1ms of audio = 16 bytes. */
|
||||||
|
int audioRecordBufferOffset=0;
|
||||||
|
while ((audioRecordBufferBytes-audioRecordBufferOffset)
|
||||||
|
>recordCodecTimespan*16) {
|
||||||
|
/* encode and deliver audio block to servald via monitor interface */
|
||||||
|
encodeAndDispatchRecordedAudio(fd,callSessionToken,recordCodec,
|
||||||
|
&audioRecordBuffer[audioRecordBufferOffset],
|
||||||
|
recordCodecTimespan*16);
|
||||||
|
/* skip over the samples we have already processed */
|
||||||
|
audioRecordBufferOffset+=recordCodecTimespan*16;
|
||||||
|
}
|
||||||
|
/* copy the remaining buffered bytes down and correct buffer length */
|
||||||
|
if (audioRecordBufferOffset<0) audioRecordBufferOffset=0;
|
||||||
|
if (audioRecordBufferOffset>audioRecordBufferBytes)
|
||||||
|
audioRecordBufferOffset=audioRecordBufferBytes;
|
||||||
|
bcopy(&audioRecordBuffer[audioRecordBufferOffset],
|
||||||
|
&audioRecordBuffer[0],
|
||||||
|
audioRecordBufferBytes-audioRecordBufferOffset);
|
||||||
|
audioRecordBufferBytes-=audioRecordBufferOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fcntl(fd,F_SETFL,
|
fcntl(fd,F_SETFL,
|
||||||
@ -106,52 +196,70 @@ int counter=0;
|
|||||||
int callState=0;
|
int callState=0;
|
||||||
int processLine(char *cmd,unsigned char *data,int dataLen)
|
int processLine(char *cmd,unsigned char *data,int dataLen)
|
||||||
{
|
{
|
||||||
int l_id,r_id,l_state,r_state;
|
int l_id,r_id,l_state,r_state,codec;
|
||||||
printf("> %s\n",cmd);
|
long long start_time,end_time;
|
||||||
if (data) {
|
if (showReceived) {
|
||||||
int i,j;
|
printf("> %s\n",cmd);
|
||||||
for(i=0;i<dataLen;i+=16) {
|
if (data) {
|
||||||
printf(" %04x :",i);
|
int i,j;
|
||||||
for(j=0;j<16;j++)
|
for(i=0;i<dataLen;i+=16) {
|
||||||
if (i+j<dataLen) printf(" %02x",data[i+j]); else printf(" ");
|
printf(" %04x :",i);
|
||||||
printf(" ");
|
for(j=0;j<16;j++)
|
||||||
for(j=0;j<16;j++)
|
if (i+j<dataLen) printf(" %02x",data[i+j]); else printf(" ");
|
||||||
if (i+j<dataLen) {
|
printf(" ");
|
||||||
if (data[i+j]>=0x20&&data[i+j]<0x7e)
|
for(j=0;j<16;j++)
|
||||||
printf("%c",data[i+j]); else printf(".");
|
if (i+j<dataLen) {
|
||||||
}
|
if (data[i+j]>=0x20&&data[i+j]<0x7e)
|
||||||
printf("\n");
|
printf("%c",data[i+j]); else printf(".");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (sscanf(cmd,"AUDIOPACKET:%x:%x:%d:%d:%d:%lld:%lld",
|
||||||
|
&l_id,&r_id,&l_state,&r_state,
|
||||||
|
&codec,&start_time,&end_time)==7)
|
||||||
|
{
|
||||||
|
if (pipeAudio) {
|
||||||
|
playAudio(data,dataLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (sscanf(cmd,"CALLSTATUS:%x:%x:%d:%d",
|
if (sscanf(cmd,"CALLSTATUS:%x:%x:%d:%d",
|
||||||
&l_id,&r_id,&l_state,&r_state)==4)
|
&l_id,&r_id,&l_state,&r_state)==4)
|
||||||
{
|
{
|
||||||
if (l_state==4) {
|
if (l_state==4&&autoAnswerP) {
|
||||||
// We are ringing, so pickup
|
// We are ringing, so pickup
|
||||||
char msg[1024];
|
char msg[1024];
|
||||||
sprintf(msg,"pickup %x\n",l_id);
|
sprintf(msg,"pickup %x\n",l_id);
|
||||||
writeLine(msg);
|
writeLine(msg);
|
||||||
}
|
}
|
||||||
|
if (l_state==5) {
|
||||||
|
startAudio();
|
||||||
|
callSessionToken=l_id;
|
||||||
|
} else {
|
||||||
|
stopAudio();
|
||||||
|
callSessionToken=0;
|
||||||
|
}
|
||||||
callState=l_state;
|
callState=l_state;
|
||||||
}
|
}
|
||||||
if (sscanf(cmd,"KEEPALIVE:%x",&l_id)==1) {
|
if (sscanf(cmd,"KEEPALIVE:%x",&l_id)==1) {
|
||||||
if (callState==5) {
|
if (callState==5&&syntheticAudio) {
|
||||||
/* Send synthetic audio packet */
|
/* Send synthetic audio packet */
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
sprintf(buffer,"*320:AUDIO:%x:8\n"
|
sprintf(buffer,"*320:AUDIO:%x:8\n"
|
||||||
"%08d pasdfghjklzxcvbnm123456"
|
"%08d pasdfghjklzxcvbnm123456"
|
||||||
"qwertyuiopasdfghjklzxcvbnm123456"
|
"qwertyuiopasdfghjklzxcvbnm123456"
|
||||||
"qwertyuiopasdfghjklzxcvbnm123456"
|
"qwertyuiopasdfghjklzxcvbnm123456"
|
||||||
"qwertyuiopasdfghjklzxcvbnm123456"
|
"qwertyuiopasdfghjklzxcvbnm123456"
|
||||||
"qwertyuiopasdfghjklzxcvbnm123456"
|
"qwertyuiopasdfghjklzxcvbnm123456"
|
||||||
"qwertyuiopasdfghjklzxcvbnm123456"
|
"qwertyuiopasdfghjklzxcvbnm123456"
|
||||||
"qwertyuiopasdfghjklzxcvbnm123456"
|
"qwertyuiopasdfghjklzxcvbnm123456"
|
||||||
"qwertyuiopasdfghjklzxcvbnm123456"
|
"qwertyuiopasdfghjklzxcvbnm123456"
|
||||||
"qwertyuiopasdfghjklzxcvbnm123456"
|
"qwertyuiopasdfghjklzxcvbnm123456"
|
||||||
"qwertyuiopasdfghjklzxcvbnm123456",l_id,counter++);
|
"qwertyuiopasdfghjklzxcvbnm123456",l_id,counter++);
|
||||||
writeLine(buffer);
|
writeLine(buffer);
|
||||||
printf("< *320:AUDIO:%x:8\\n<320 bytes>\n",l_id);
|
printf("< *320:AUDIO:%x:8\\n<320 bytes>\n",l_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd[0]=0;
|
cmd[0]=0;
|
||||||
cmdLen=0;
|
cmdLen=0;
|
||||||
|
22
serval.h
22
serval.h
@ -1407,3 +1407,25 @@ int monitor_get_fds(struct pollfd *fds,int *fdcount,int fdmax);
|
|||||||
int monitor_call_status(vomp_call_state *call);
|
int monitor_call_status(vomp_call_state *call);
|
||||||
int monitor_send_audio(vomp_call_state *call,overlay_mdp_frame *audio);
|
int monitor_send_audio(vomp_call_state *call,overlay_mdp_frame *audio);
|
||||||
extern int monitor_socket_count;
|
extern int monitor_socket_count;
|
||||||
|
|
||||||
|
#define AUDIO_MSM_G1_ETC 1
|
||||||
|
#define AUDIO_MSM_N1_ETC 2
|
||||||
|
extern int detectedAudioDevice;
|
||||||
|
extern char *detectedAudioDeviceName;
|
||||||
|
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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user