700 lines
16 KiB
C
700 lines
16 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <alsa/asoundlib.h>
|
|
#include <signal.h>
|
|
#include <sys/stat.h>
|
|
#include <pthread.h>
|
|
#include "./include/formats.h"
|
|
#include "./include/linuxrec.h"
|
|
|
|
#define DBG_ON 1
|
|
|
|
#if DBG_ON
|
|
#define dbg printf
|
|
#else
|
|
#define dbg
|
|
#endif
|
|
|
|
|
|
/* Do not change the sequence */
|
|
enum {
|
|
RECORD_STATE_CREATED, /* Init */
|
|
RECORD_STATE_CLOSING,
|
|
RECORD_STATE_READY, /* Opened */
|
|
RECORD_STATE_STOPPING, /* During Stop */
|
|
RECORD_STATE_RECORDING, /* Started */
|
|
};
|
|
|
|
#define SAMPLE_RATE 16000
|
|
#define SAMPLE_BIT_SIZE 16
|
|
#define FRAME_CNT 10
|
|
//#define BUF_COUNT 1
|
|
#define DEF_BUFF_TIME 500000
|
|
#define DEF_PERIOD_TIME 100000
|
|
|
|
#define DEFAULT_FORMAT \
|
|
{\
|
|
WAVE_FORMAT_PCM, \
|
|
1, \
|
|
16000, \
|
|
32000, \
|
|
2, \
|
|
16, \
|
|
sizeof(WAVEFORMATEX) \
|
|
}
|
|
#if 0
|
|
struct bufinfo {
|
|
char *data;
|
|
unsigned int bufsize;
|
|
};
|
|
#endif
|
|
|
|
|
|
static int show_xrun = 1;
|
|
static int start_record_internal(snd_pcm_t *pcm)
|
|
{
|
|
return snd_pcm_start(pcm);
|
|
}
|
|
|
|
static int stop_record_internal(snd_pcm_t *pcm)
|
|
{
|
|
return snd_pcm_drop(pcm);
|
|
}
|
|
|
|
|
|
static int is_stopped_internal(struct recorder *rec)
|
|
{
|
|
snd_pcm_state_t state;
|
|
|
|
state = snd_pcm_state((snd_pcm_t *)rec->wavein_hdl);
|
|
switch (state) {
|
|
case SND_PCM_STATE_RUNNING:
|
|
case SND_PCM_STATE_DRAINING:
|
|
return 0;
|
|
default: break;
|
|
}
|
|
return 1;
|
|
|
|
}
|
|
|
|
static int format_ms_to_alsa(const WAVEFORMATEX * wavfmt,
|
|
snd_pcm_format_t * format)
|
|
{
|
|
snd_pcm_format_t tmp;
|
|
tmp = snd_pcm_build_linear_format(wavfmt->wBitsPerSample,
|
|
wavfmt->wBitsPerSample, wavfmt->wBitsPerSample == 8 ? 1 : 0, 0);
|
|
if ( tmp == SND_PCM_FORMAT_UNKNOWN )
|
|
return -EINVAL;
|
|
*format = tmp;
|
|
return 0;
|
|
}
|
|
|
|
/* set hardware and software params */
|
|
static int set_hwparams(struct recorder * rec, const WAVEFORMATEX *wavfmt,
|
|
unsigned int buffertime, unsigned int periodtime)
|
|
{
|
|
snd_pcm_hw_params_t *params;
|
|
int err;
|
|
unsigned int rate;
|
|
snd_pcm_format_t format;
|
|
snd_pcm_uframes_t size;
|
|
snd_pcm_t *handle = (snd_pcm_t *)rec->wavein_hdl;
|
|
|
|
rec->buffer_time = buffertime;
|
|
rec->period_time = periodtime;
|
|
|
|
snd_pcm_hw_params_alloca(¶ms);
|
|
err = snd_pcm_hw_params_any(handle, params);
|
|
if (err < 0) {
|
|
dbg("Broken configuration for this PCM");
|
|
return err;
|
|
}
|
|
err = snd_pcm_hw_params_set_access(handle, params,
|
|
SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
if (err < 0) {
|
|
dbg("Access type not available");
|
|
return err;
|
|
}
|
|
err = format_ms_to_alsa(wavfmt, &format);
|
|
if (err) {
|
|
dbg("Invalid format");
|
|
return - EINVAL;
|
|
}
|
|
err = snd_pcm_hw_params_set_format(handle, params, format);
|
|
if (err < 0) {
|
|
dbg("Sample format non available");
|
|
return err;
|
|
}
|
|
err = snd_pcm_hw_params_set_channels(handle, params, wavfmt->nChannels);
|
|
if (err < 0) {
|
|
dbg("Channels count non available");
|
|
return err;
|
|
}
|
|
|
|
rate = wavfmt->nSamplesPerSec;
|
|
err = snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0);
|
|
if (err < 0) {
|
|
dbg("Set rate failed");
|
|
return err;
|
|
}
|
|
if(rate != wavfmt->nSamplesPerSec) {
|
|
dbg("Rate mismatch");
|
|
return -EINVAL;
|
|
}
|
|
if (rec->buffer_time == 0 || rec->period_time == 0) {
|
|
err = snd_pcm_hw_params_get_buffer_time_max(params,
|
|
&rec->buffer_time, 0);
|
|
assert(err >= 0);
|
|
if (rec->buffer_time > 500000)
|
|
rec->buffer_time = 500000;
|
|
rec->period_time = rec->buffer_time / 4;
|
|
}
|
|
err = snd_pcm_hw_params_set_period_time_near(handle, params,
|
|
&rec->period_time, 0);
|
|
if (err < 0) {
|
|
dbg("set period time fail");
|
|
return err;
|
|
}
|
|
err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
|
|
&rec->buffer_time, 0);
|
|
if (err < 0) {
|
|
dbg("set buffer time failed");
|
|
return err;
|
|
}
|
|
err = snd_pcm_hw_params_get_period_size(params, &size, 0);
|
|
if (err < 0) {
|
|
dbg("get period size fail");
|
|
return err;
|
|
}
|
|
rec->period_frames = size;
|
|
err = snd_pcm_hw_params_get_buffer_size(params, &size);
|
|
if (size == rec->period_frames) {
|
|
dbg("Can't use period equal to buffer size (%lu == %lu)",
|
|
size, rec->period_frames);
|
|
return -EINVAL;
|
|
}
|
|
rec->buffer_frames = size;
|
|
rec->bits_per_frame = wavfmt->wBitsPerSample;
|
|
|
|
/* set to driver */
|
|
err = snd_pcm_hw_params(handle, params);
|
|
if (err < 0) {
|
|
dbg("Unable to install hw params:");
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
static int set_swparams(struct recorder * rec)
|
|
{
|
|
int err;
|
|
snd_pcm_sw_params_t *swparams;
|
|
snd_pcm_t * handle = (snd_pcm_t*)(rec->wavein_hdl);
|
|
/* sw para */
|
|
snd_pcm_sw_params_alloca(&swparams);
|
|
err = snd_pcm_sw_params_current(handle, swparams);
|
|
if (err < 0) {
|
|
dbg("get current sw para fail");
|
|
return err;
|
|
}
|
|
|
|
err = snd_pcm_sw_params_set_avail_min(handle, swparams,
|
|
rec->period_frames);
|
|
if (err < 0) {
|
|
dbg("set avail min failed");
|
|
return err;
|
|
}
|
|
/* set a value bigger than the buffer frames to prevent the auto start.
|
|
* we use the snd_pcm_start to explicit start the pcm */
|
|
err = snd_pcm_sw_params_set_start_threshold(handle, swparams,
|
|
rec->buffer_frames * 2);
|
|
if (err < 0) {
|
|
dbg("set start threshold fail");
|
|
return err;
|
|
}
|
|
|
|
if ( (err = snd_pcm_sw_params(handle, swparams)) < 0) {
|
|
dbg("unable to install sw params:");
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int set_params(struct recorder *rec, WAVEFORMATEX *fmt,
|
|
unsigned int buffertime, unsigned int periodtime)
|
|
{
|
|
int err;
|
|
WAVEFORMATEX defmt = DEFAULT_FORMAT;
|
|
|
|
if (fmt == NULL) {
|
|
fmt = &defmt;
|
|
}
|
|
err = set_hwparams(rec, fmt, buffertime, periodtime);
|
|
if (err)
|
|
return err;
|
|
err = set_swparams(rec);
|
|
if (err)
|
|
return err;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Underrun and suspend recovery
|
|
*/
|
|
|
|
static int xrun_recovery(snd_pcm_t *handle, int err)
|
|
{
|
|
if (err == -EPIPE) { /* over-run */
|
|
if (show_xrun)
|
|
printf("!!!!!!overrun happend!!!!!!");
|
|
|
|
err = snd_pcm_prepare(handle);
|
|
if (err < 0) {
|
|
if (show_xrun)
|
|
printf("Can't recovery from overrun,"
|
|
"prepare failed: %s\n", snd_strerror(err));
|
|
return err;
|
|
}
|
|
return 0;
|
|
} else if (err == -ESTRPIPE) {
|
|
while ((err = snd_pcm_resume(handle)) == -EAGAIN)
|
|
usleep(200000); /* wait until the suspend flag is released */
|
|
if (err < 0) {
|
|
err = snd_pcm_prepare(handle);
|
|
if (err < 0) {
|
|
if (show_xrun)
|
|
printf("Can't recovery from suspend,"
|
|
"prepare failed: %s\n", snd_strerror(err));
|
|
return err;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
return err;
|
|
}
|
|
static ssize_t pcm_read(struct recorder *rec, size_t rcount)
|
|
{
|
|
ssize_t r;
|
|
size_t count = rcount;
|
|
char *data;
|
|
snd_pcm_t *handle = (snd_pcm_t *)rec->wavein_hdl;
|
|
if(!handle)
|
|
return -EINVAL;
|
|
|
|
data = rec->audiobuf;
|
|
while (count > 0) {
|
|
r = snd_pcm_readi(handle, data, count);
|
|
if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) {
|
|
snd_pcm_wait(handle, 100);
|
|
} else if (r < 0) {
|
|
if(xrun_recovery(handle, r) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (r > 0) {
|
|
count -= r;
|
|
data += r * rec->bits_per_frame / 8;
|
|
}
|
|
}
|
|
return rcount;
|
|
}
|
|
|
|
static void * record_thread_proc(void * para)
|
|
{
|
|
struct recorder * rec = (struct recorder *) para;
|
|
size_t frames, bytes;
|
|
sigset_t mask, oldmask;
|
|
|
|
|
|
sigemptyset(&mask);
|
|
sigaddset(&mask, SIGINT);
|
|
sigaddset(&mask, SIGTERM);
|
|
pthread_sigmask(SIG_BLOCK, &mask, &oldmask);
|
|
|
|
while(1) {
|
|
frames = rec->period_frames;
|
|
bytes = frames * rec->bits_per_frame / 8;
|
|
|
|
/* closing, exit the thread */
|
|
if (rec->state == RECORD_STATE_CLOSING)
|
|
break;
|
|
|
|
if(rec->state < RECORD_STATE_RECORDING)
|
|
usleep(100000);
|
|
|
|
if (pcm_read(rec, frames) != frames) {
|
|
return NULL;
|
|
}
|
|
|
|
if (rec->on_data_ind)
|
|
rec->on_data_ind(rec->audiobuf, bytes,
|
|
rec->user_cb_para);
|
|
}
|
|
return rec;
|
|
|
|
}
|
|
static int create_record_thread(void * para, pthread_t * tidp)
|
|
{
|
|
int err;
|
|
err = pthread_create(tidp, NULL, record_thread_proc, (void *)para);
|
|
if (err != 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if 0 /* don't use it now... cuz only one buffer supported */
|
|
static void free_rec_buffer(struct recorder * rec)
|
|
{
|
|
if (rec->bufheader) {
|
|
unsigned int i;
|
|
struct bufinfo *info = (struct bufinfo *) rec->bufheader;
|
|
|
|
assert(rec->bufcount > 0);
|
|
for (i = 0; i < rec->bufcount; ++i) {
|
|
if (info->data) {
|
|
free(info->data);
|
|
info->data = NULL;
|
|
info->bufsize = 0;
|
|
info->audio_bytes = 0;
|
|
}
|
|
info++;
|
|
}
|
|
free(rec->bufheader);
|
|
rec->bufheader = NULL;
|
|
}
|
|
rec->bufcount = 0;
|
|
}
|
|
|
|
static int prepare_rec_buffer(struct recorder * rec )
|
|
{
|
|
struct bufinfo *buffers;
|
|
unsigned int i;
|
|
int err;
|
|
size_t sz;
|
|
|
|
/* the read and QISRWrite is blocked, currently only support one buffer,
|
|
* if overrun too much, need more buffer and another new thread
|
|
* to write the audio to network */
|
|
rec->bufcount = 1;
|
|
sz = sizeof(struct bufinfo)*rec->bufcount;
|
|
buffers=(struct bufinfo*)malloc(sz);
|
|
if (!buffers) {
|
|
rec->bufcount = 0;
|
|
goto fail;
|
|
}
|
|
memset(buffers, 0, sz);
|
|
rec->bufheader = buffers;
|
|
|
|
for (i = 0; i < rec->bufcount; ++i) {
|
|
buffers[i].bufsize =
|
|
(rec->period_frames * rec->bits_per_frame / 8);
|
|
buffers[i].data = (char *)malloc(buffers[i].bufsize);
|
|
if (!buffers[i].data) {
|
|
buffers[i].bufsize = 0;
|
|
goto fail;
|
|
}
|
|
buffers[i].audio_bytes = 0;
|
|
}
|
|
return 0;
|
|
fail:
|
|
free_rec_buffer(rec);
|
|
return -ENOMEM;
|
|
}
|
|
#else
|
|
static void free_rec_buffer(struct recorder * rec)
|
|
{
|
|
if (rec->audiobuf) {
|
|
free(rec->audiobuf);
|
|
rec->audiobuf = NULL;
|
|
}
|
|
}
|
|
|
|
static int prepare_rec_buffer(struct recorder * rec )
|
|
{
|
|
/* the read and QISRWrite is blocked, currently only support one buffer,
|
|
* if overrun too much, need more buffer and another new thread
|
|
* to write the audio to network */
|
|
size_t sz = (rec->period_frames * rec->bits_per_frame / 8);
|
|
rec->audiobuf = (char *)malloc(sz);
|
|
if(!rec->audiobuf)
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int open_recorder_internal(struct recorder * rec,
|
|
record_dev_id dev, WAVEFORMATEX * fmt)
|
|
{
|
|
int err = 0;
|
|
|
|
err = snd_pcm_open((snd_pcm_t **)&rec->wavein_hdl, dev.u.name,
|
|
SND_PCM_STREAM_CAPTURE, 0);
|
|
if(err < 0)
|
|
goto fail;
|
|
|
|
err = set_params(rec, fmt, DEF_BUFF_TIME, DEF_PERIOD_TIME);
|
|
if(err)
|
|
goto fail;
|
|
|
|
assert(rec->bufheader == NULL);
|
|
err = prepare_rec_buffer(rec);
|
|
if(err)
|
|
goto fail;
|
|
|
|
err = create_record_thread((void*)rec,
|
|
&rec->rec_thread);
|
|
if(err)
|
|
goto fail;
|
|
|
|
|
|
return 0;
|
|
fail:
|
|
if(rec->wavein_hdl)
|
|
snd_pcm_close((snd_pcm_t *) rec->wavein_hdl);
|
|
rec->wavein_hdl = NULL;
|
|
free_rec_buffer(rec);
|
|
return err;
|
|
}
|
|
|
|
static void close_recorder_internal(struct recorder *rec)
|
|
{
|
|
snd_pcm_t * handle;
|
|
|
|
handle = (snd_pcm_t *) rec->wavein_hdl;
|
|
|
|
/* may be the thread is blocked at read, cancel it */
|
|
pthread_cancel(rec->rec_thread);
|
|
|
|
/* wait for the pcm thread quit first */
|
|
pthread_join(rec->rec_thread, NULL);
|
|
|
|
if(handle) {
|
|
snd_pcm_close(handle);
|
|
rec->wavein_hdl = NULL;
|
|
}
|
|
free_rec_buffer(rec);
|
|
}
|
|
/* return the count of pcm device */
|
|
/* list all cards */
|
|
static int get_pcm_device_cnt(snd_pcm_stream_t stream)
|
|
{
|
|
void **hints, **n;
|
|
char *io, *filter, *name;
|
|
int cnt = 0;
|
|
|
|
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
|
|
return 0;
|
|
n = hints;
|
|
filter = stream == SND_PCM_STREAM_CAPTURE ? "Input" : "Output";
|
|
while (*n != NULL) {
|
|
io = snd_device_name_get_hint(*n, "IOID");
|
|
name = snd_device_name_get_hint(*n, "NAME");
|
|
if (name && (io == NULL || strcmp(io, filter) == 0))
|
|
cnt ++;
|
|
if (io != NULL)
|
|
free(io);
|
|
if (name != NULL)
|
|
free(name);
|
|
n++;
|
|
}
|
|
snd_device_name_free_hint(hints);
|
|
return cnt;
|
|
}
|
|
|
|
static void free_name_desc(char **name_or_desc)
|
|
{
|
|
char **ss;
|
|
ss = name_or_desc;
|
|
if(NULL == name_or_desc)
|
|
return;
|
|
while(*name_or_desc) {
|
|
free(*name_or_desc);
|
|
*name_or_desc = NULL;
|
|
name_or_desc++;
|
|
}
|
|
free(ss);
|
|
}
|
|
/* return success: total count, need free the name and desc buffer
|
|
* fail: -1 , *name_out and *desc_out will be NULL */
|
|
static int list_pcm(snd_pcm_stream_t stream, char**name_out,
|
|
char ** desc_out)
|
|
{
|
|
void **hints, **n;
|
|
char **name, **descr;
|
|
char *io;
|
|
const char *filter;
|
|
int cnt = 0;
|
|
int i = 0;
|
|
|
|
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
|
|
return 0;
|
|
n = hints;
|
|
cnt = get_pcm_device_cnt(stream);
|
|
if(!cnt) {
|
|
goto fail;
|
|
}
|
|
|
|
*name_out = calloc(sizeof(char *) , (1+cnt));
|
|
if (*name_out == NULL)
|
|
goto fail;
|
|
*desc_out = calloc(sizeof(char *) , (1 + cnt));
|
|
if (*desc_out == NULL)
|
|
goto fail;
|
|
|
|
/* the last one is a flag, NULL */
|
|
name_out[cnt] = NULL;
|
|
desc_out[cnt] = NULL;
|
|
name = name_out;
|
|
descr = desc_out;
|
|
|
|
filter = stream == SND_PCM_STREAM_CAPTURE ? "Input" : "Output";
|
|
while (*n != NULL && i < cnt) {
|
|
*name = snd_device_name_get_hint(*n, "NAME");
|
|
*descr = snd_device_name_get_hint(*n, "DESC");
|
|
io = snd_device_name_get_hint(*n, "IOID");
|
|
if (name == NULL ||
|
|
(io != NULL && strcmp(io, filter) != 0) ){
|
|
if (*name) free(*name);
|
|
if (*descr) free(*descr);
|
|
} else {
|
|
if (*descr == NULL) {
|
|
*descr = malloc(4);
|
|
memset(*descr, 0, 4);
|
|
}
|
|
name++;
|
|
descr++;
|
|
i++;
|
|
}
|
|
if (io != NULL)
|
|
free(io);
|
|
n++;
|
|
}
|
|
snd_device_name_free_hint(hints);
|
|
return cnt;
|
|
fail:
|
|
free_name_desc(name_out);
|
|
free_name_desc(desc_out);
|
|
snd_device_name_free_hint(hints);
|
|
return -1;
|
|
}
|
|
/* -------------------------------------
|
|
* Interfaces
|
|
--------------------------------------*/
|
|
/* the device id is a pcm string name in linux */
|
|
record_dev_id get_default_input_dev()
|
|
{
|
|
record_dev_id id;
|
|
id.u.name = "default";
|
|
return id;
|
|
}
|
|
|
|
record_dev_id * list_input_device()
|
|
{
|
|
// TODO: unimplemented
|
|
return NULL;
|
|
}
|
|
|
|
int get_input_dev_num()
|
|
{
|
|
return get_pcm_device_cnt(SND_PCM_STREAM_CAPTURE);
|
|
}
|
|
|
|
|
|
/* callback will be run on a new thread */
|
|
int create_recorder(struct recorder ** out_rec,
|
|
void (*on_data_ind)(char *data, unsigned long len, void *user_cb_para),
|
|
void* user_cb_para)
|
|
{
|
|
struct recorder * myrec;
|
|
myrec = (struct recorder *)malloc(sizeof(struct recorder));
|
|
if(!myrec)
|
|
return -RECORD_ERR_MEMFAIL;
|
|
|
|
memset(myrec, 0, sizeof(struct recorder));
|
|
myrec->on_data_ind = on_data_ind;
|
|
myrec->user_cb_para = user_cb_para;
|
|
myrec->state = RECORD_STATE_CREATED;
|
|
|
|
*out_rec = myrec;
|
|
return 0;
|
|
}
|
|
|
|
void destroy_recorder(struct recorder *rec)
|
|
{
|
|
if(!rec)
|
|
return;
|
|
|
|
free(rec);
|
|
}
|
|
|
|
int open_recorder(struct recorder * rec, record_dev_id dev, WAVEFORMATEX * fmt)
|
|
{
|
|
int ret = 0;
|
|
if(!rec )
|
|
return -RECORD_ERR_INVAL;
|
|
if(rec->state >= RECORD_STATE_READY)
|
|
return 0;
|
|
|
|
ret = open_recorder_internal(rec, dev, fmt);
|
|
if(ret == 0)
|
|
rec->state = RECORD_STATE_READY;
|
|
return 0;
|
|
|
|
}
|
|
|
|
void close_recorder(struct recorder *rec)
|
|
{
|
|
if(rec == NULL || rec->state < RECORD_STATE_READY)
|
|
return;
|
|
if(rec->state == RECORD_STATE_RECORDING)
|
|
stop_record(rec);
|
|
|
|
rec->state = RECORD_STATE_CLOSING;
|
|
|
|
close_recorder_internal(rec);
|
|
|
|
rec->state = RECORD_STATE_CREATED;
|
|
}
|
|
|
|
int start_record(struct recorder * rec)
|
|
{
|
|
int ret;
|
|
if(rec == NULL)
|
|
return -RECORD_ERR_INVAL;
|
|
if( rec->state < RECORD_STATE_READY)
|
|
return -RECORD_ERR_NOT_READY;
|
|
if( rec->state == RECORD_STATE_RECORDING)
|
|
return 0;
|
|
|
|
ret = start_record_internal((snd_pcm_t *)rec->wavein_hdl);
|
|
if(ret == 0)
|
|
rec->state = RECORD_STATE_RECORDING;
|
|
return ret;
|
|
}
|
|
|
|
int stop_record(struct recorder * rec)
|
|
{
|
|
int ret;
|
|
if(rec == NULL)
|
|
return -RECORD_ERR_INVAL;
|
|
if( rec->state < RECORD_STATE_RECORDING)
|
|
return 0;
|
|
|
|
rec->state = RECORD_STATE_STOPPING;
|
|
ret = stop_record_internal((snd_pcm_t *)rec->wavein_hdl);
|
|
if(ret == 0) {
|
|
rec->state = RECORD_STATE_READY;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int is_record_stopped(struct recorder *rec)
|
|
{
|
|
if(rec->state == RECORD_STATE_RECORDING)
|
|
return 0;
|
|
|
|
return is_stopped_internal(rec);
|
|
}
|