4 * License: LGPL 2.1+ with static linking exception
6 * soundplay server: waits for a client to connect,
7 * then passes stream received to audio hardware.
8 * designed to stream microphone input from one computer to
14 #include <sys/types.h>
15 #include <alsa/asoundlib.h>
18 #include "micbuffer.h"
20 #include "../rocksockserver.h"
21 #include "../../lib/include/sblist.h"
22 #include "../../lib/include/macros.h"
25 //RcB: CFLAGS "-std=c99 -D_GNU_SOURCE -DVERBOSE"
26 //RcB: LINK "-lasound -lpthread"
29 #define DPRINTF(fd, ...) dprintf(fd, __VA_ARGS__)
31 #define DPRINTF(fd, ...)
34 typedef struct client
{
36 unsigned char wave
[MICSERVER_NUMBUFFERS
][MIC_BUFFER_SIZE
];
37 unsigned long long samples_recvd
;
38 unsigned long long samples_consumed
;
39 snd_pcm_t
*playback_handle
;
45 typedef struct server_state
{
48 const char* audiodevice
;
51 struct client
*client_from_fd(sblist
*clients
, int fd
) {
53 sblist_iter(clients
, c
) {
54 if(c
->fd
== fd
) return c
;
59 static void disconnect_client(server
* s
, int fd
) {
61 sblist_iter_counter(s
->clients
, i
, c
) {
64 pthread_join(c
->thr
, 0);
65 snd_pcm_close(c
->playback_handle
);
66 sblist_delete(s
->clients
, i
);
70 rocksockserver_disconnect_client(&s
->srv
, fd
);
73 static int on_cdisconnect (void* userdata
, int fd
) {
75 disconnect_client(s
, fd
);
79 static const char* getstatus_string(snd_pcm_state_t status
) {
81 case SND_PCM_STATE_PAUSED
:
82 return "SND_PCM_STATE_PAUSED";
83 case SND_PCM_STATE_SETUP
:
84 return "SND_PCM_STATE_SETUP";
85 case SND_PCM_STATE_PREPARED
:
86 return "SND_PCM_STATE_PREPARED";
87 case SND_PCM_STATE_SUSPENDED
:
88 return "SND_PCM_STATE_SUSPENDED";
89 case SND_PCM_STATE_DISCONNECTED
:
90 return "SND_PCM_STATE_DISCONNECTED";
91 case SND_PCM_STATE_XRUN
:
92 return "SND_PCM_STATE_XRUN";
93 case SND_PCM_STATE_DRAINING
:
94 return "SND_PCM_STATE_DRAINING";
95 case SND_PCM_STATE_RUNNING
:
96 return "SND_PCM_STATE_RUNNING";
102 static void chk(const int err
, const char* msg
) {
104 dprintf(2, msg
, snd_strerror(err
));
109 static snd_pcm_t
* init_playback_device(const char* devname
) {
110 snd_pcm_t
*playback_handle
;
111 snd_pcm_hw_params_t
*hw_params
;
112 chk(snd_pcm_open(&playback_handle
, devname
, SND_PCM_STREAM_PLAYBACK
,0), "cannot open audio device (%s)\n");
113 chk(snd_pcm_hw_params_malloc(&hw_params
), "cannot allocate hardware parameter structure (%s)\n");
114 chk(snd_pcm_hw_params_any(playback_handle
, hw_params
), "cannot initialize hardware parameter structure (%s)\n");
115 chk(snd_pcm_hw_params_set_access(playback_handle
, hw_params
, SND_PCM_ACCESS_RW_INTERLEAVED
), "cannot set access type (%s)\n");
116 chk(snd_pcm_hw_params_set_format(playback_handle
, hw_params
, FORMAT
), "cannot set sample format (%s)\n");
117 unsigned rate
= BITRATE
;
118 chk(snd_pcm_hw_params_set_rate_near(playback_handle
, hw_params
, &rate
, 0), "cannot set sample rate (%s)\n");
119 chk(snd_pcm_hw_params_set_channels(playback_handle
, hw_params
, NUMCHANNELS
),"cannot set channel count (%s)\n");
120 chk(snd_pcm_hw_params(playback_handle
, hw_params
), "cannot set parameters (%s)\n");
121 snd_pcm_hw_params_free(hw_params
);
122 chk(snd_pcm_prepare(playback_handle
), "cannot prepare audio interface for use (%s)\n");
123 return playback_handle
;
126 static unsigned long long get_recvd(client
*c
) {
127 unsigned long long l
;
128 pthread_mutex_lock(&c
->mtx
);
129 l
= c
->samples_recvd
;
130 pthread_mutex_unlock(&c
->mtx
);
134 static void play(snd_pcm_t
*playback_handle
, const unsigned char* buf
, size_t s
) {
135 int ret
= snd_pcm_writei(playback_handle
, buf
, s
);
137 DPRINTF(2, "write to audio interface failed (%s)\n", snd_strerror(ret
));
138 snd_pcm_state_t state
= snd_pcm_state(playback_handle
);
139 DPRINTF(2, "%s\n", getstatus_string(state
));
140 if(state
== SND_PCM_STATE_XRUN
) snd_pcm_recover(playback_handle
, ret
, 1);
144 static void *c_thread(void *userdata
) {
145 client
*c
= userdata
;
146 while(!c
->terminate
) {
147 if(get_recvd(c
) - c
->samples_consumed
>= ARRAY_SIZE(c
->wave
)/2) {
149 unsigned x
= c
->samples_consumed
% ARRAY_SIZE(c
->wave
);
150 DPRINTF(2, "playing samplebuf %u\n", x
);
151 play(c
->playback_handle
, c
->wave
[x
], sizeof(c
->wave
[0]));
152 c
->samples_consumed
++;
153 } while(get_recvd(c
) - c
->samples_consumed
> 0);
160 static int on_cconnect (void* userdata
, struct sockaddr_storage
* clientaddr
, int fd
) {
162 server
* s
= userdata
;
163 struct client cb
= {.fd
= fd
};
164 if(sblist_getsize(s
->clients
) || !sblist_add(s
->clients
, &cb
)) {
165 disconnect_client(s
, fd
);
168 struct client
* c
= client_from_fd(s
->clients
, fd
);
169 c
->playback_handle
= init_playback_device(s
->audiodevice
);
170 pthread_mutex_init(&c
->mtx
, 0);
171 pthread_create(&c
->thr
, 0, c_thread
, c
);
175 static int on_cread (void* userdata
, int fd
, size_t dummy
) {
177 server
* s
= userdata
;
179 if(!(c
= client_from_fd(s
->clients
, fd
))) return -1;
180 unsigned x
= c
->samples_recvd
% ARRAY_SIZE(c
->wave
);
181 DPRINTF(2, "receiving samplebuf %u\n", x
);
182 if(sizeof(c
->wave
[0]) != recv(fd
, c
->wave
[x
], sizeof(c
->wave
[0]), 0)) {
183 disconnect_client(s
, fd
);
186 pthread_mutex_lock(&c
->mtx
);
188 pthread_mutex_unlock(&c
->mtx
);
192 static int on_cwantsdata (void* userdata
, int fd
) {
193 server
* s
= userdata
;
195 if(!(c
= client_from_fd(s
->clients
, fd
))) return -1;
199 int main(int argc
, char** argv
) {
201 s
->clients
= sblist_new(sizeof(struct client
), 32);
202 s
->audiodevice
= argc
> 1 ? argv
[1] : "default";
203 const int port
= 9999;
204 const char* listenip
= "0.0.0.0";
205 if(rocksockserver_init(&s
->srv
, listenip
, port
, (void*) s
)) return -1;
206 rocksockserver_set_sleeptime(&s
->srv
, BITRATE
);
207 if(rocksockserver_loop(&s
->srv
, NULL
, 0,
208 &on_cconnect
, &on_cread
,
209 &on_cwantsdata
, &on_cdisconnect
)) return -2;