mirror of
http://www.pogo.org.uk/~mark/trx.git
synced 2024-11-23 19:05:06 +01:00
Initial import
This commit is contained in:
commit
8d8040f5a6
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.config
|
||||
*.o
|
||||
*.d
|
||||
tx
|
||||
rx
|
||||
test-jitter
|
24
Makefile
Normal file
24
Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
-include .config
|
||||
|
||||
CFLAGS+=-MMD -Wall
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: rx tx
|
||||
|
||||
rx: rx.o device.o jitter.o sched.o
|
||||
rx: LDLIBS+=-lcelt -lasound
|
||||
|
||||
tx: tx.o device.o sched.o
|
||||
tx: LDLIBS+=-lcelt -lasound
|
||||
|
||||
test-jitter: test-jitter.o jitter.o
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f *.d
|
||||
rm -f tx
|
||||
rm -f rx
|
||||
rm -f test-jitter
|
||||
|
||||
-include *.d
|
14
defaults.h
Normal file
14
defaults.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef DEFAULTS_H
|
||||
#define DEFAULTS_H
|
||||
|
||||
#define DEFAULT_DEVICE "default"
|
||||
#define DEFAULT_BUFFER 16
|
||||
|
||||
#define DEFAULT_SERVICE "1350"
|
||||
#define DEFAULT_FRAME 96
|
||||
|
||||
#define DEFAULT_RATE 44100
|
||||
#define DEFAULT_CHANNELS 2
|
||||
#define DEFAULT_BITRATE 128
|
||||
|
||||
#endif
|
78
device.c
Normal file
78
device.c
Normal file
@ -0,0 +1,78 @@
|
||||
#include <stdio.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#define CHK(call, r) { \
|
||||
if (r < 0) { \
|
||||
aerror(call, r); \
|
||||
return -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
void aerror(const char *msg, int r)
|
||||
{
|
||||
fputs(msg, stderr);
|
||||
fputs(": ", stderr);
|
||||
fputs(snd_strerror(r), stderr);
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
|
||||
int set_alsa_hw(snd_pcm_t *pcm,
|
||||
unsigned int rate, unsigned int channels,
|
||||
unsigned int buffer)
|
||||
{
|
||||
int r, dir;
|
||||
snd_pcm_hw_params_t *hw;
|
||||
|
||||
snd_pcm_hw_params_alloca(&hw);
|
||||
|
||||
r = snd_pcm_hw_params_any(pcm, hw);
|
||||
CHK("snd_pcm_hw_params_any", r);
|
||||
|
||||
r = snd_pcm_hw_params_set_rate_resample(pcm, hw, 1);
|
||||
CHK("snd_pcm_hw_params_set_rate_resample", r);
|
||||
|
||||
r = snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
CHK("snd_pcm_hw_params_set_access", r);
|
||||
|
||||
r = snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_FLOAT);
|
||||
CHK("snd_pcm_hw_params_set_format", r);
|
||||
|
||||
r = snd_pcm_hw_params_set_rate(pcm, hw, rate, 0);
|
||||
CHK("snd_pcm_hw_params_set_rate", r);
|
||||
|
||||
r = snd_pcm_hw_params_set_channels(pcm, hw, channels);
|
||||
CHK("snd_pcm_hw_params_set_channels", r);
|
||||
|
||||
dir = 0;
|
||||
r = snd_pcm_hw_params_set_buffer_time_near(pcm, hw, &buffer, &dir);
|
||||
CHK("snd_pcm_hw_params_set_buffer_time_near", r);
|
||||
|
||||
r = snd_pcm_hw_params(pcm, hw);
|
||||
CHK("hw_params", r);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_alsa_sw(snd_pcm_t *pcm)
|
||||
{
|
||||
int r;
|
||||
snd_pcm_sw_params_t *sw;
|
||||
snd_pcm_uframes_t boundary;
|
||||
|
||||
snd_pcm_sw_params_alloca(&sw);
|
||||
|
||||
r = snd_pcm_sw_params_current(pcm, sw);
|
||||
CHK("snd_pcm_sw_params_current", r);
|
||||
|
||||
r = snd_pcm_sw_params_get_boundary(sw, &boundary);
|
||||
CHK("snd_pcm_sw_params_get_boundary", r);
|
||||
|
||||
r = snd_pcm_sw_params_set_stop_threshold(pcm, sw, boundary);
|
||||
CHK("snd_pcm_sw_params_set_stop_threshold", r);
|
||||
|
||||
r = snd_pcm_sw_params(pcm, sw);
|
||||
CHK("snd_pcm_sw_params", r);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
10
device.h
Normal file
10
device.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef DEVICE_H
|
||||
#define DEVICE_H
|
||||
|
||||
void aerror(const char *msg, int r);
|
||||
int set_alsa_hw(snd_pcm_t *pcm,
|
||||
unsigned int rate, unsigned int channels,
|
||||
unsigned int buffer);
|
||||
int set_alsa_sw(snd_pcm_t *pcm);
|
||||
|
||||
#endif
|
130
jitter.c
Normal file
130
jitter.c
Normal file
@ -0,0 +1,130 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "jitter.h"
|
||||
|
||||
void jitbuf_init(struct jitbuf_t *jb)
|
||||
{
|
||||
size_t n;
|
||||
|
||||
for (n = 0; n < JITBUF_MAX; n++)
|
||||
jb->buf[n] = NULL;
|
||||
|
||||
jb->base = 0;
|
||||
jb->out = 0;
|
||||
jb->entries = 0;
|
||||
}
|
||||
|
||||
void jitbuf_clear(struct jitbuf_t *jb)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* Push an entry into the buffer, with the given sequence number
|
||||
*
|
||||
* Pre: data is not NULL
|
||||
*/
|
||||
|
||||
int jitbuf_push(struct jitbuf_t *jb, seq_t t, void *data)
|
||||
{
|
||||
void **entry;
|
||||
|
||||
assert(data != NULL);
|
||||
|
||||
if (jb->entries == 0 && t != jb->base) {
|
||||
fprintf(stderr, "Restarting (%d)\n", t);
|
||||
jb->base = t;
|
||||
} else {
|
||||
if (t < jb->base) {
|
||||
fprintf(stderr, "Late (%d)\n", t);
|
||||
return -1;
|
||||
}
|
||||
if (t >= jb->base + JITBUF_MAX) {
|
||||
fprintf(stderr, "Early (%d)\n", t);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
entry = &jb->buf[(t - jb->base + jb->out) % JITBUF_MAX];
|
||||
|
||||
if (*entry != NULL) {
|
||||
fprintf(stderr, "Dup (%d)\n", t);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*entry = data;
|
||||
jb->entries++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the entry at the front of the buffer, in sequence number order
|
||||
*
|
||||
* Return: NULL if no entry, otherwise entry next in sequence
|
||||
*/
|
||||
|
||||
void* jitbuf_front(const struct jitbuf_t *jb)
|
||||
{
|
||||
return jb->buf[jb->out];
|
||||
}
|
||||
|
||||
/*
|
||||
* Pop the next entry in the buffer
|
||||
*/
|
||||
|
||||
void jitbuf_pop(struct jitbuf_t *jb)
|
||||
{
|
||||
void **entry;
|
||||
|
||||
entry = &jb->buf[jb->out];
|
||||
if (*entry != NULL) {
|
||||
jb->entries--;
|
||||
*entry = NULL;
|
||||
}
|
||||
jb->base++;
|
||||
jb->out++;
|
||||
if (jb->out >= JITBUF_MAX)
|
||||
jb->out = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if this buffer has enough data flow to be declared ready
|
||||
*/
|
||||
|
||||
bool jitbuf_ready(const struct jitbuf_t *jb, seq_t count)
|
||||
{
|
||||
size_t n;
|
||||
|
||||
assert(count <= JITBUF_MAX);
|
||||
|
||||
for (n = 0; n < count; n++) {
|
||||
if (jb->buf[(jb->out + n) % JITBUF_MAX] == NULL)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool jitbuf_empty(const struct jitbuf_t *jb)
|
||||
{
|
||||
if (jb->entries > 0)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void jitbuf_debug(struct jitbuf_t *jb, FILE *fd)
|
||||
{
|
||||
seq_t n;
|
||||
|
||||
fprintf(fd, "% 3d ", jb->entries);
|
||||
|
||||
for (n = 0; n < JITBUF_MAX; n++) {
|
||||
if (jb->buf[(jb->out + n) % JITBUF_MAX] != NULL) {
|
||||
fputc('#', fd);
|
||||
} else {
|
||||
fputc(' ', fd);
|
||||
}
|
||||
}
|
||||
fputc('\n', fd);
|
||||
}
|
34
jitter.h
Normal file
34
jitter.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef JITTER_H
|
||||
#define JITTER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define JITBUF_MAX 64
|
||||
|
||||
typedef unsigned int seq_t;
|
||||
|
||||
/*
|
||||
* De-jitter buffer
|
||||
*/
|
||||
|
||||
struct jitbuf_t {
|
||||
seq_t base;
|
||||
size_t out, entries, contiguous;
|
||||
void *buf[JITBUF_MAX];
|
||||
};
|
||||
|
||||
void jitbuf_init(struct jitbuf_t *jb);
|
||||
void jitbuf_clear(struct jitbuf_t *jb);
|
||||
|
||||
int jitbuf_push(struct jitbuf_t *jb, seq_t t, void *data);
|
||||
void* jitbuf_front(const struct jitbuf_t *jb);
|
||||
void jitbuf_pop(struct jitbuf_t *jb);
|
||||
|
||||
bool jitbuf_ready(const struct jitbuf_t *jb, seq_t count);
|
||||
bool jitbuf_empty(const struct jitbuf_t *jb);
|
||||
|
||||
void jitbuf_debug(struct jitbuf_t *jb, FILE *fd);
|
||||
|
||||
#endif
|
263
rx.c
Normal file
263
rx.c
Normal file
@ -0,0 +1,263 @@
|
||||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <celt/celt.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "defaults.h"
|
||||
#include "device.h"
|
||||
#include "jitter.h"
|
||||
#include "sched.h"
|
||||
|
||||
#define ENCODED_BYTES 128
|
||||
|
||||
/*
|
||||
* Bind to a network socket for receiving packets on the given port
|
||||
*/
|
||||
|
||||
static int listen_on_network(const char *service)
|
||||
{
|
||||
int r, sd;
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_flags = AI_PASSIVE; /* use my IP */
|
||||
|
||||
r = getaddrinfo(NULL, service, &hints, &servinfo);
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(r));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Bind to the first one we can */
|
||||
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
sd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK,
|
||||
p->ai_protocol);
|
||||
if (sd == -1) {
|
||||
perror("socket");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bind(sd, p->ai_addr, p->ai_addrlen) == -1) {
|
||||
if (close(sd) == -1)
|
||||
abort();
|
||||
perror("bind");
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
freeaddrinfo(servinfo);
|
||||
|
||||
if (p == NULL) {
|
||||
fputs("Failed to bind socket\n", stderr);
|
||||
return -1;
|
||||
} else {
|
||||
return sd;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Playback audio from the buffer to the audio device
|
||||
*/
|
||||
|
||||
static int play_audio(struct jitbuf_t *jb, CELTDecoder *decoder,
|
||||
snd_pcm_t *snd)
|
||||
{
|
||||
int r;
|
||||
void *data;
|
||||
float *pcm;
|
||||
snd_pcm_sframes_t f;
|
||||
|
||||
pcm = alloca(sizeof(float) * DEFAULT_FRAME * DEFAULT_CHANNELS);
|
||||
|
||||
data = jitbuf_front(jb);
|
||||
if (data == NULL) {
|
||||
r = celt_decode_float(decoder, NULL, 0, pcm);
|
||||
} else {
|
||||
r = celt_decode_float(decoder, data, ENCODED_BYTES, pcm);
|
||||
}
|
||||
if (r != 0) {
|
||||
fputs("Error in celt_decode\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
f = snd_pcm_writei(snd, pcm, DEFAULT_FRAME);
|
||||
if (f < 0) {
|
||||
aerror("snd_pcm_writei", f);
|
||||
return -1;
|
||||
}
|
||||
if (f < DEFAULT_FRAME)
|
||||
fprintf(stderr, "Short write %ld\n", f);
|
||||
|
||||
jitbuf_pop(jb);
|
||||
free(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from the network into the buffer
|
||||
*/
|
||||
|
||||
static int read_from_network(int sd, struct jitbuf_t *jb)
|
||||
{
|
||||
size_t z;
|
||||
char buf[32768];
|
||||
void *data;
|
||||
seq_t seq;
|
||||
|
||||
z = recvfrom(sd, buf, sizeof(buf), 0, NULL, NULL);
|
||||
if (z == -1) {
|
||||
if (errno == EAGAIN)
|
||||
return -EAGAIN;
|
||||
perror("recvfrom");
|
||||
return -1;
|
||||
}
|
||||
if (z != sizeof(uint32_t) + ENCODED_BYTES) {
|
||||
fputs("small packet", stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* First 32-bits is sequence number, rest is data */
|
||||
seq = ntohl(*(uint32_t*)buf);
|
||||
z -= sizeof(uint32_t);
|
||||
assert(z >= 0);
|
||||
data = malloc(z);
|
||||
memcpy(data, buf + sizeof(uint32_t), z);
|
||||
|
||||
if (jitbuf_push(jb, seq, data) == -1)
|
||||
free(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The main loop of receiving audio and playing it back
|
||||
*/
|
||||
|
||||
static int run_rx(int sd, CELTDecoder *decoder, snd_pcm_t *snd)
|
||||
{
|
||||
int r;
|
||||
struct jitbuf_t jb;
|
||||
|
||||
jitbuf_init(&jb);
|
||||
|
||||
for (;;) {
|
||||
for (;;) {
|
||||
r = read_from_network(sd, &jb);
|
||||
if (r == -EAGAIN)
|
||||
break;
|
||||
if (r != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
jitbuf_debug(&jb, stderr);
|
||||
|
||||
if (play_audio(&jb, decoder, snd) == -1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
jitbuf_clear(&jb);
|
||||
}
|
||||
|
||||
static void usage(FILE *fd)
|
||||
{
|
||||
fprintf(fd, "Usage: rx [<parameters>]\n");
|
||||
|
||||
fprintf(fd, "\nAudio device (ALSA) parameters:\n");
|
||||
fprintf(fd, " -d <device> Device name (default '%s')\n",
|
||||
DEFAULT_DEVICE);
|
||||
fprintf(fd, " -m <ms> Buffer time (milliseconds, default %d)\n",
|
||||
DEFAULT_BUFFER);
|
||||
|
||||
fprintf(fd, "\nNetwork parameters:\n");
|
||||
fprintf(fd, " -p <port> UDP port number or name (default %s)\n",
|
||||
DEFAULT_SERVICE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int sd, r;
|
||||
snd_pcm_t *snd;
|
||||
CELTMode *mode;
|
||||
CELTDecoder *decoder;
|
||||
|
||||
/* command-line options */
|
||||
const char *device = DEFAULT_DEVICE,
|
||||
*service = DEFAULT_SERVICE;
|
||||
unsigned int buffer = DEFAULT_BUFFER;
|
||||
|
||||
for (;;) {
|
||||
int c;
|
||||
|
||||
c = getopt(argc, argv, "d:m:p:");
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'd':
|
||||
device = optarg;
|
||||
break;
|
||||
case 'm':
|
||||
buffer = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
service = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(stderr);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
mode = celt_mode_create(DEFAULT_RATE, DEFAULT_CHANNELS, DEFAULT_FRAME,
|
||||
NULL);
|
||||
if (mode == NULL) {
|
||||
fputs("celt_mode_create failed\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
decoder = celt_decoder_create(mode);
|
||||
if (decoder == NULL) {
|
||||
fputs("celt_decoder_create failed\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sd = listen_on_network(service);
|
||||
if (sd == -1)
|
||||
return -1;
|
||||
|
||||
r = snd_pcm_open(&snd, device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if (r < 0) {
|
||||
aerror("snd_pcm_open", r);
|
||||
return -1;
|
||||
}
|
||||
if (set_alsa_hw(snd, DEFAULT_RATE, DEFAULT_CHANNELS,
|
||||
buffer * 1000) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (set_alsa_sw(snd) == -1)
|
||||
return -1;
|
||||
|
||||
if (go_realtime() != 0)
|
||||
return -1;
|
||||
|
||||
r = run_rx(sd, decoder, snd);
|
||||
|
||||
if (snd_pcm_close(snd) < 0)
|
||||
abort();
|
||||
|
||||
celt_decoder_destroy(decoder);
|
||||
celt_mode_destroy(mode);
|
||||
|
||||
if (close(sd) == -1)
|
||||
abort();
|
||||
|
||||
return r;
|
||||
}
|
32
sched.c
Normal file
32
sched.c
Normal file
@ -0,0 +1,32 @@
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "sched.h"
|
||||
|
||||
#define REALTIME_PRIORITY 80
|
||||
|
||||
int go_realtime(void)
|
||||
{
|
||||
int max_pri;
|
||||
struct sched_param sp;
|
||||
|
||||
if (sched_getparam(0, &sp)) {
|
||||
perror("sched_getparam");
|
||||
return -1;
|
||||
}
|
||||
|
||||
max_pri = sched_get_priority_max(SCHED_FIFO);
|
||||
sp.sched_priority = REALTIME_PRIORITY;
|
||||
|
||||
if (sp.sched_priority > max_pri) {
|
||||
fprintf(stderr, "Invalid priority (maximum %d)\n", max_pri);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sched_setscheduler(0, SCHED_FIFO, &sp)) {
|
||||
perror("sched_setscheduler");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
6
sched.h
Normal file
6
sched.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef MISC_H
|
||||
#define MISC_H
|
||||
|
||||
int go_realtime(void);
|
||||
|
||||
#endif
|
34
test-jitter.c
Normal file
34
test-jitter.c
Normal file
@ -0,0 +1,34 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "jitter.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int n;
|
||||
struct jitbuf_t jb;
|
||||
const char *test = "abcdefghijklmnopqrstuvwxyz1234567890";
|
||||
|
||||
jitbuf_init(&jb);
|
||||
|
||||
for (n = 0; n < strlen(test); n++) {
|
||||
int m;
|
||||
char *out;
|
||||
size_t pos;
|
||||
|
||||
out = jitbuf_front(&jb);
|
||||
if (out != NULL)
|
||||
fputc(*out, stdout);
|
||||
else
|
||||
fputc('#', stdout);
|
||||
jitbuf_pop(&jb);
|
||||
|
||||
for (m = 0; m < 8; m++) {
|
||||
pos = n + rand() % (strlen(test) - n);
|
||||
jitbuf_push(&jb, pos, (void*)&test[pos]);
|
||||
}
|
||||
}
|
||||
fputc('\n', stdout);
|
||||
|
||||
jitbuf_clear(&jb);
|
||||
}
|
235
tx.c
Normal file
235
tx.c
Normal file
@ -0,0 +1,235 @@
|
||||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <celt/celt.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "defaults.h"
|
||||
#include "device.h"
|
||||
#include "sched.h"
|
||||
|
||||
#define ENCODED_BYTES 128
|
||||
|
||||
typedef int seq_t;
|
||||
|
||||
/*
|
||||
* Bind to network socket, for sending to the given host and port
|
||||
*/
|
||||
|
||||
static int bind_to_network(const char *host, const char *service,
|
||||
struct addrinfo **servinfo, struct addrinfo **theirs)
|
||||
{
|
||||
int r, sd;
|
||||
struct addrinfo hints, *p;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
r = getaddrinfo(host, service, &hints, servinfo);
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(r));
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (p = *servinfo; p != NULL; p = p->ai_next) {
|
||||
sd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||
if (sd == -1) {
|
||||
perror("socket");
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (p == NULL) {
|
||||
fputs("Failed to bind socket\n", stderr);
|
||||
return -1;
|
||||
} else {
|
||||
*theirs = p;
|
||||
return sd;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode a frame of audio and send it over the network
|
||||
*/
|
||||
|
||||
static int send_audio(snd_pcm_t *snd, CELTEncoder *encoder,
|
||||
int sd, struct addrinfo *theirs, seq_t *seq)
|
||||
{
|
||||
float *pcm;
|
||||
void *packet, *encoded;
|
||||
ssize_t z;
|
||||
snd_pcm_sframes_t f;
|
||||
|
||||
pcm = alloca(sizeof(float) * DEFAULT_FRAME * DEFAULT_CHANNELS);
|
||||
packet = alloca(ENCODED_BYTES + sizeof(uint32_t));
|
||||
encoded = packet + sizeof(uint32_t);
|
||||
|
||||
f = snd_pcm_readi(snd, pcm, DEFAULT_FRAME);
|
||||
if (f < 0) {
|
||||
aerror("snd_pcm_readi", f);
|
||||
return -1;
|
||||
}
|
||||
if (f < DEFAULT_FRAME)
|
||||
fprintf(stderr, "Short read, %ld\n", f);
|
||||
|
||||
z = celt_encode_float(encoder, pcm, NULL, encoded, ENCODED_BYTES);
|
||||
if (z < 0) {
|
||||
fputs("celt_encode_float failed\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*(uint32_t*)packet = htonl(*seq);
|
||||
(*seq)++;
|
||||
|
||||
z = sendto(sd, packet, sizeof(uint32_t) + ENCODED_BYTES, 0,
|
||||
theirs->ai_addr, theirs->ai_addrlen);
|
||||
if (z == -1) {
|
||||
perror("sendto");
|
||||
return -1;
|
||||
}
|
||||
fputc('>', stderr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_tx(snd_pcm_t *snd, CELTEncoder *encoder,
|
||||
int sd, struct addrinfo *theirs)
|
||||
{
|
||||
seq_t seq = 0;
|
||||
|
||||
for (;;) {
|
||||
int r;
|
||||
|
||||
r = send_audio(snd, encoder, sd, theirs, &seq);
|
||||
if (r == -1)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(FILE *fd)
|
||||
{
|
||||
fprintf(fd, "Usage: tx [<parameters>] <host>\n");
|
||||
|
||||
fprintf(fd, "\nAudio device (ALSA) parameters:\n");
|
||||
fprintf(fd, " -d <device> Device name (default '%s')\n",
|
||||
DEFAULT_DEVICE);
|
||||
fprintf(fd, " -m <ms> Buffer time (milliseconds, default %d)\n",
|
||||
DEFAULT_BUFFER);
|
||||
|
||||
fprintf(fd, "\nNetwork parameters:\n");
|
||||
fprintf(fd, " -p <port> UDP port number or name (default %s)\n",
|
||||
DEFAULT_SERVICE);
|
||||
fprintf(fd, " -f <bytes> Frame size (default %d)\n",
|
||||
DEFAULT_FRAME);
|
||||
|
||||
fprintf(fd, "\nEncoding parameters:\n");
|
||||
fprintf(fd, " -r <rate> Sample rate (default %d)\n",
|
||||
DEFAULT_RATE);
|
||||
fprintf(fd, " -c <n> Number of channels (default %d)\n",
|
||||
DEFAULT_CHANNELS);
|
||||
fprintf(fd, " -b <bitrate> Bitrate (kbps, approx., default %d)\n",
|
||||
DEFAULT_BITRATE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int sd, r;
|
||||
struct addrinfo *servinfo, *theirs;
|
||||
snd_pcm_t *snd;
|
||||
CELTMode *mode;
|
||||
CELTEncoder *encoder;
|
||||
|
||||
/* command-line options */
|
||||
const char *device = DEFAULT_DEVICE,
|
||||
*service = DEFAULT_SERVICE,
|
||||
*host;
|
||||
unsigned int buffer = DEFAULT_BUFFER,
|
||||
rate = DEFAULT_RATE,
|
||||
channels = DEFAULT_CHANNELS;
|
||||
size_t frame = DEFAULT_FRAME;
|
||||
|
||||
for (;;) {
|
||||
int c;
|
||||
|
||||
c = getopt(argc, argv, "c:d:f:m:p:r:");
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'c':
|
||||
channels = atoi(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
device = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
frame = atol(optarg);
|
||||
break;
|
||||
case 'm':
|
||||
buffer = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
service = optarg;
|
||||
break;
|
||||
case 'r':
|
||||
rate = atoi(optarg);
|
||||
break;
|
||||
default:
|
||||
usage(stderr);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (argv[optind] == NULL) {
|
||||
usage(stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
host = argv[optind];
|
||||
|
||||
mode = celt_mode_create(rate, channels, frame, NULL);
|
||||
if (mode == NULL) {
|
||||
fputs("celt_mode_create failed\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
encoder = celt_encoder_create(mode);
|
||||
if (encoder == NULL) {
|
||||
fputs("celt_encoder_create failed\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sd = bind_to_network(host, service, &servinfo, &theirs);
|
||||
if (sd == -1)
|
||||
return -1;
|
||||
|
||||
r = snd_pcm_open(&snd, device, SND_PCM_STREAM_CAPTURE, 0);
|
||||
if (r < 0) {
|
||||
aerror("snd_pcm_open", r);
|
||||
return -1;
|
||||
}
|
||||
if (set_alsa_hw(snd, rate, channels, buffer * 1000) == -1)
|
||||
return -1;
|
||||
if (set_alsa_sw(snd) == -1)
|
||||
return -1;
|
||||
|
||||
if (go_realtime() != 0)
|
||||
return -1;
|
||||
|
||||
r = run_tx(snd, encoder, sd, theirs);
|
||||
|
||||
freeaddrinfo(servinfo);
|
||||
|
||||
if (snd_pcm_close(snd) < 0)
|
||||
abort();
|
||||
|
||||
celt_encoder_destroy(encoder);
|
||||
celt_mode_destroy(mode);
|
||||
|
||||
if (close(sd) == -1)
|
||||
abort();
|
||||
|
||||
return r;
|
||||
}
|
Loading…
Reference in New Issue
Block a user