commit d27c908791a33e6e2ab2a9e4c463aef729a2e6e9
parent b0075102feb5d10ec7a6f9a79cc012bc00027a89
Author: sin <sin@2f30.org>
Date: Mon, 28 May 2012 14:39:08 +0100
sscall: Initial commit
Diffstat:
A | Makefile | | | 17 | +++++++++++++++++ |
A | README | | | 4 | ++++ |
D | README.md | | | 5 | ----- |
A | list.h | | | 99 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | 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;
+}