sscall

UDP based voice chat
git clone git://git.2f30.org/sscall.git
Log | Files | Refs | README | LICENSE

commit d27c908791a33e6e2ab2a9e4c463aef729a2e6e9
parent b0075102feb5d10ec7a6f9a79cc012bc00027a89
Author: sin <sin@2f30.org>
Date:   Mon May 28 14:39:08 +0100

sscall: Initial commit


Diffstat:
Makefile | 17+++++++++++++++++
README | 4++++
README.md | 5-----
list.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
sscall.c | 323+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 443 insertions(+), 5 deletions(-)
diff --git a/Makefile b/Makefile @@ -0,0 +1,17 @@ +bin = sscall +ver = 0.1 +src = sscall.c + +CC = gcc +CFLAGS += -Wall -Wextra -I/usr/local/include +LDFLAGS += -lao -lpthread -L/usr/local/lib + +all: $(bin) + +%: %.c + $(CC) -o $(bin) $(src) $(CFLAGS) $(LDFLAGS) + +clean: + @rm -rf $(bin) + +.PHONY: all clean diff --git a/README b/README @@ -0,0 +1,4 @@ +What is it? +=========== + +A simple UDP based voice chat program. diff --git a/README.md b/README.md @@ -1,4 +0,0 @@ -sscall -====== - -A simple UDP based voice chat program- \ No newline at end of file diff --git a/list.h b/list.h @@ -0,0 +1,99 @@ +#ifndef _LIST_H +#define _LIST_H + +#define offsetof(TYPE, MEMBER) ((size_t)__builtin_offsetof(TYPE, MEMBER)) +#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) ); }) + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define LIST_HEAD_INIT(name) { &(name), &(name) } /* assign both next and prev to point to &name */ + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +static inline void +INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static inline void +list_add(struct list_head *new, struct list_head *head) +{ + head->next->prev = new; + new->next = head->next; + new->prev = head; + head->next = new; +} + +static inline void +list_add_tail(struct list_head *new, struct list_head *head) +{ + head->prev->next = new; + new->next = head; + new->prev = head->prev; + head->prev = new; +} + +static inline void +list_del(struct list_head *entry) +{ + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + entry->next = NULL; + entry->prev = NULL; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int +list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_first_entry - get the first element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +#endif diff --git a/sscall.c b/sscall.c @@ -0,0 +1,323 @@ +/* Sample file streaming: + * + * cat test.wav | sscall <remote-ip> <remote-port> <local-port> + * + * Similarly one can use arecord(1) or similar to receive input + * from the mic and pipe it over to sscall. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <err.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <ao/ao.h> +#include <pthread.h> + +#include "list.h" + +/* Input/Output PCM buffer size */ +#define PCM_BUF_SIZE (8192) +/* Sleep at least 50ms between each sendto() call */ +#define UDELAY_SEND (50 * 1000) + +/* Command line option, bits per sample */ +static int fbits; +/* Command line option, samples per second (in a single channel) */ +static int frate; +/* Command line option, number of channels */ +static int fchan; +/* Command line option, device driver ID */ +static int fdevid; + +/* Libao handle */ +static ao_device *device; +/* Libao sample format, currently only 44.1kHz */ +static ao_sample_format format; +/* Default driver ID */ +static int default_driver; +/* Output PCM thread */ +static pthread_t output_pcm_thread; +/* Input PCM thread */ +static pthread_t input_pcm_thread; + +struct pcm_buf { + /* PCM buffer */ + void *buf; + /* PCM buffer size */ + size_t len; + struct list_head list; +} pcm_buf; + +/* Private structure for the + * input_pcm thread */ +struct inp_pcm_priv { + /* Input file descriptor */ + int fd; + /* Client socket */ + int sockfd; + /* Client address info */ + struct addrinfo *servinfo; +} inp_pcm_priv; + +/* Lock that protects pcm_buf */ +static pthread_mutex_t pcm_buf_lock; +/* Condition variable on which ao_play() blocks */ +static pthread_cond_t tx_pcm_cond; + +/* Play back audio from the client */ +static void * +output_pcm(void *data __attribute__ ((unused))) +{ + struct pcm_buf *pctx; + struct list_head *iter, *q; + + do { + pthread_mutex_lock(&pcm_buf_lock); + if (list_empty(&pcm_buf.list)) + pthread_cond_wait(&tx_pcm_cond, + &pcm_buf_lock); + /* Dequeue and play buffers via libao */ + list_for_each_safe(iter, q, &pcm_buf.list) { + pctx = list_entry(iter, struct pcm_buf, + list); + ao_play(device, pctx->buf, pctx->len); + free(pctx->buf); + list_del(&pctx->list); + free(pctx); + } + pthread_mutex_unlock(&pcm_buf_lock); + } while (1); + + return NULL; +} + +/* Prepare for output PCM, enqueue buffer + * and signal output_pcm thread */ +static void +do_output_pcm(const void *buf, size_t len) +{ + struct pcm_buf *pctx; + + pthread_mutex_lock(&pcm_buf_lock); + pctx = malloc(sizeof(*pctx)); + if (!pctx) + err(1, "malloc"); + memset(pctx, 0, sizeof(*pctx)); + pctx->buf = malloc(len); + if (!pctx->buf) + err(1, "malloc"); + pctx->len = len; + memcpy(pctx->buf, buf, len); + INIT_LIST_HEAD(&pctx->list); + list_add_tail(&pctx->list, &pcm_buf.list); + pthread_cond_signal(&tx_pcm_cond); + pthread_mutex_unlock(&pcm_buf_lock); +} + +/* Input PCM thread, outbound path */ +static void * +input_pcm(void *data __attribute__ ((unused))) +{ + char buf[PCM_BUF_SIZE]; + ssize_t bytes; + + do { + bytes = read(inp_pcm_priv.fd, buf, sizeof(buf)); + if (bytes > 0) { + bytes = sendto(inp_pcm_priv.sockfd, buf, + bytes, 0, + inp_pcm_priv.servinfo->ai_addr, + inp_pcm_priv.servinfo->ai_addrlen); + if (bytes < 0) + perror("sendto"); + usleep(UDELAY_SEND); + } + } while (1); + + return NULL; +} + +static void +usage(const char *s) +{ + fprintf(stderr, + "usage: %s [OPTIONS] <remote-addr> <remote-port> <local-port>\n", s); + fprintf(stderr, " -b\tBits per sample\n"); + fprintf(stderr, " -r\tSamples per second (in a single channel)\n"); + fprintf(stderr, " -c\tNumber of channels\n"); + fprintf(stderr, " -d\tOverride default driver ID\n"); + fprintf(stderr, " -h\tThis help screen\n"); + exit(EXIT_SUCCESS); +} + +int +main(int argc, char *argv[]) +{ + int recfd = STDIN_FILENO; + ssize_t bytes; + char buf[PCM_BUF_SIZE]; + int cli_sockfd, srv_sockfd; + struct addrinfo cli_hints, *cli_servinfo, *p0, *p1; + struct addrinfo srv_hints, *srv_servinfo; + int rv; + int ret; + socklen_t addr_len; + struct sockaddr_storage their_addr; + char *prog; + int c; + + prog = *argv; + while ((c = getopt(argc, argv, "hb:c:r:d:")) != -1) { + switch (c) { + case 'h': + usage(prog); + break; + case 'b': + fbits = strtol(optarg, NULL, 10); + break; + case 'c': + fchan = strtol(optarg, NULL, 10); + break; + case 'r': + frate = strtol(optarg, NULL, 10); + break; + case 'd': + fdevid = strtol(optarg, NULL, 10); + break; + case '?': + default: + exit(EXIT_FAILURE); + } + } + argc -= optind; + argv += optind; + + if (argc != 3) + usage(prog); + + ao_initialize(); + + default_driver = ao_default_driver_id(); + + memset(&format, 0, sizeof(format)); + if (fbits) + format.bits = fbits; + else + format.bits = 16; + if (fchan) + format.channels = fchan; + else + format.channels = 2; + if (frate) + format.rate = frate; + else + format.rate = 8000; + format.byte_format = AO_FMT_LITTLE; + + if (fdevid) + default_driver = fdevid; + + device = ao_open_live(default_driver, + &format, NULL); + if (!device) + errx(1, "Error opening output device: %d\n", + default_driver); + + memset(&cli_hints, 0, sizeof(cli_hints)); + cli_hints.ai_family = AF_UNSPEC; + cli_hints.ai_socktype = SOCK_DGRAM; + + rv = getaddrinfo(argv[0], argv[1], &cli_hints, &cli_servinfo); + if (rv) { + errx(1, "getaddrinfo: %s", gai_strerror(rv)); + return 1; + } + + for (p0 = cli_servinfo; p0; p0 = p0->ai_next) { + cli_sockfd = socket(p0->ai_family, p0->ai_socktype, + p0->ai_protocol); + if (cli_sockfd < 0) + continue; + break; + } + + if (!p0) + errx(1, "failed to bind socket"); + + memset(&srv_hints, 0, sizeof(srv_hints)); + srv_hints.ai_family = AF_UNSPEC; + srv_hints.ai_socktype = SOCK_DGRAM; + srv_hints.ai_flags = AI_PASSIVE; + + rv = getaddrinfo(NULL, argv[2], &srv_hints, &srv_servinfo); + if (rv) { + errx(1, "getaddrinfo: %s", gai_strerror(rv)); + return 1; + } + + for(p1 = srv_servinfo; p1; p1 = p1->ai_next) { + srv_sockfd = socket(p1->ai_family, p1->ai_socktype, + p1->ai_protocol); + if (srv_sockfd < 0) + continue; + if (bind(srv_sockfd, p1->ai_addr, p1->ai_addrlen) < 0) { + close(srv_sockfd); + perror("bind"); + continue; + } + break; + } + + if (!p1) + errx(1, "failed to bind socket"); + + INIT_LIST_HEAD(&pcm_buf.list); + + pthread_mutex_init(&pcm_buf_lock, NULL); + pthread_cond_init(&tx_pcm_cond, NULL); + + ret = pthread_create(&output_pcm_thread, NULL, + output_pcm, + NULL); + if (ret < 0) + errx(1, "pthread_creapte failed: %d", ret); + + inp_pcm_priv.fd = recfd; + inp_pcm_priv.sockfd = cli_sockfd; + inp_pcm_priv.servinfo = p0; + + ret = pthread_create(&input_pcm_thread, NULL, + input_pcm, + NULL); + if (ret < 0) + errx(1, "pthread_create failed: %d", ret); + + /* Receive audio data from other end and prepare + * for playback */ + do { + addr_len = sizeof(their_addr); + bytes = recvfrom(srv_sockfd, buf, + sizeof(buf), 0, + (struct sockaddr *)&their_addr, + &addr_len); + if (bytes > 0) + do_output_pcm(buf, bytes); + } while (1); + + freeaddrinfo(cli_servinfo); + freeaddrinfo(srv_servinfo); + + ao_close(device); + ao_shutdown(); + + return EXIT_SUCCESS; +}