1 /**
2  * @file pulse/recorder.c  Pulseaudio sound driver - recorder
3  *
4  * Copyright (C) 2010 - 2016 Creytiv.com
5  */
6 #include <pulse/pulseaudio.h>
7 #include <pulse/simple.h>
8 #include <pthread.h>
9 #include <re.h>
10 #include <rem.h>
11 #include <baresip.h>
12 #include "pulse.h"
13 
14 
15 struct ausrc_st {
16 	const struct ausrc *as;      /* inheritance */
17 
18 	pa_simple *s;
19 	pthread_t thread;
20 	bool run;
21 	void *sampv;
22 	size_t sampc;
23 	size_t sampsz;
24 	uint32_t ptime;
25 	ausrc_read_h *rh;
26 	void *arg;
27 };
28 
29 
ausrc_destructor(void * arg)30 static void ausrc_destructor(void *arg)
31 {
32 	struct ausrc_st *st = arg;
33 
34 	/* Wait for termination of other thread */
35 	if (st->run) {
36 		debug("pulse: stopping record thread\n");
37 		st->run = false;
38 		(void)pthread_join(st->thread, NULL);
39 	}
40 
41 	if (st->s)
42 		pa_simple_free(st->s);
43 
44 	mem_deref(st->sampv);
45 }
46 
47 
read_thread(void * arg)48 static void *read_thread(void *arg)
49 {
50 	struct ausrc_st *st = arg;
51 	const size_t num_bytes = st->sampc * st->sampsz;
52 	int ret, pa_error = 0;
53 	uint64_t now, last_read, diff;
54 	unsigned dropped = 0;
55 	bool init = true;
56 
57 	if (pa_simple_flush(st->s, &pa_error)) {
58 		warning("pulse: pa_simple_flush error (%s)\n",
59 		        pa_strerror(pa_error));
60 	}
61 
62 	last_read = tmr_jiffies();
63 
64 	while (st->run) {
65 
66 		ret = pa_simple_read(st->s, st->sampv, num_bytes, &pa_error);
67 		if (ret < 0) {
68 			warning("pulse: pa_simple_write error (%s)\n",
69 				pa_strerror(pa_error));
70 			continue;
71 		}
72 
73 		/* Some devices might send a burst of samples right after the
74 		   initialization - filter them out */
75 		if (init) {
76 			now = tmr_jiffies();
77 			diff = (now > last_read)? now - last_read : 0;
78 
79 			if (diff < st->ptime / 2) {
80 				last_read = now;
81 				++dropped;
82 				continue;
83 			}
84 			else {
85 				init = false;
86 
87 				if (dropped)
88 					debug("pulse: dropped %u frames of "
89 					      "garbage at the beginning of "
90 					      "the recording\n", dropped);
91 			}
92 		}
93 
94 		st->rh(st->sampv, st->sampc, st->arg);
95 	}
96 
97 	return NULL;
98 }
99 
100 
aufmt_to_pulse_format(enum aufmt fmt)101 static int aufmt_to_pulse_format(enum aufmt fmt)
102 {
103 	switch (fmt) {
104 
105 	case AUFMT_S16LE:  return PA_SAMPLE_S16NE;
106 	case AUFMT_FLOAT:  return PA_SAMPLE_FLOAT32NE;
107 	default: return 0;
108 	}
109 }
110 
111 
pulse_recorder_alloc(struct ausrc_st ** stp,const struct ausrc * as,struct media_ctx ** ctx,struct ausrc_prm * prm,const char * device,ausrc_read_h * rh,ausrc_error_h * errh,void * arg)112 int pulse_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as,
113 			 struct media_ctx **ctx,
114 			 struct ausrc_prm *prm, const char *device,
115 			 ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
116 {
117 	struct ausrc_st *st;
118 	pa_sample_spec ss;
119 	pa_buffer_attr attr;
120 	int pa_error;
121 	int err;
122 
123 	(void)ctx;
124 	(void)device;
125 	(void)errh;
126 
127 	if (!stp || !as || !prm)
128 		return EINVAL;
129 
130 	debug("pulse: opening recorder (%u Hz, %d channels, device '%s')\n",
131 	      prm->srate, prm->ch, device);
132 
133 	st = mem_zalloc(sizeof(*st), ausrc_destructor);
134 	if (!st)
135 		return ENOMEM;
136 
137 	st->as  = as;
138 	st->rh  = rh;
139 	st->arg = arg;
140 
141 	st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
142 	st->sampsz = aufmt_sample_size(prm->fmt);
143 	st->ptime = prm->ptime;
144 
145 	st->sampv = mem_alloc(st->sampsz * st->sampc, NULL);
146 	if (!st->sampv) {
147 		err = ENOMEM;
148 		goto out;
149 	}
150 
151 	ss.format   = aufmt_to_pulse_format(prm->fmt);
152 	ss.channels = prm->ch;
153 	ss.rate     = prm->srate;
154 
155 	attr.maxlength = (uint32_t)-1;
156 	attr.tlength   = (uint32_t)-1;
157 	attr.prebuf    = (uint32_t)-1;
158 	attr.minreq    = (uint32_t)-1;
159 	attr.fragsize  = (uint32_t)pa_usec_to_bytes(prm->ptime * 1000, &ss);
160 
161 	st->s = pa_simple_new(NULL,
162 			      "Baresip",
163 			      PA_STREAM_RECORD,
164 			      str_isset(device) ? device : 0,
165 			      "VoIP Record",
166 			      &ss,
167 			      NULL,
168 			      &attr,
169 			      &pa_error);
170 	if (!st->s) {
171 		warning("pulse: could not connect to server (%s)\n",
172 			pa_strerror(pa_error));
173 		err = ENODEV;
174 		goto out;
175 	}
176 
177 	st->run = true;
178 	err = pthread_create(&st->thread, NULL, read_thread, st);
179 	if (err) {
180 		st->run = false;
181 		goto out;
182 	}
183 
184 	debug("pulse: recording started\n");
185 
186  out:
187 	if (err)
188 		mem_deref(st);
189 	else
190 		*stp = st;
191 
192 	return err;
193 }
194