mirror of
http://www.pogo.org.uk/~mark/trx.git
synced 2024-11-22 02:15:05 +01:00
trx based on libortp
This commit is contained in:
parent
c7af697019
commit
d2be2e03eb
13
Makefile
13
Makefile
@ -2,23 +2,24 @@
|
|||||||
|
|
||||||
CFLAGS+=-MMD -Wall
|
CFLAGS+=-MMD -Wall
|
||||||
|
|
||||||
|
LDLIBS_ASOUND?=-lasound
|
||||||
|
LDLIBS_CELT?=-lcelt
|
||||||
|
LDLIBS_ORTP?=-lortp
|
||||||
|
|
||||||
|
LDLIBS+=$(LDLIBS_ASOUND) $(LDLIBS_CELT) $(LDLIBS_ORTP)
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: rx tx
|
all: rx tx
|
||||||
|
|
||||||
rx: rx.o device.o jitter.o sched.o
|
rx: rx.o device.o sched.o
|
||||||
rx: LDLIBS+=-lcelt -lasound
|
|
||||||
|
|
||||||
tx: tx.o device.o sched.o
|
tx: tx.o device.o sched.o
|
||||||
tx: LDLIBS+=-lcelt -lasound
|
|
||||||
|
|
||||||
test-jitter: test-jitter.o jitter.o
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *.o
|
rm -f *.o
|
||||||
rm -f *.d
|
rm -f *.d
|
||||||
rm -f tx
|
rm -f tx
|
||||||
rm -f rx
|
rm -f rx
|
||||||
rm -f test-jitter
|
|
||||||
|
|
||||||
-include *.d
|
-include *.d
|
||||||
|
10
defaults.h
10
defaults.h
@ -4,11 +4,15 @@
|
|||||||
#define DEFAULT_DEVICE "default"
|
#define DEFAULT_DEVICE "default"
|
||||||
#define DEFAULT_BUFFER 16
|
#define DEFAULT_BUFFER 16
|
||||||
|
|
||||||
#define DEFAULT_SERVICE "1350"
|
#define DEFAULT_ADDR "0.0.0.0"
|
||||||
#define DEFAULT_FRAME 128
|
#define DEFAULT_PORT 1350
|
||||||
|
#define DEFAULT_FRAME 256
|
||||||
|
#define DEFAULT_JITTER 16
|
||||||
|
|
||||||
#define DEFAULT_RATE 44100
|
#define DEFAULT_RATE 48000
|
||||||
#define DEFAULT_CHANNELS 2
|
#define DEFAULT_CHANNELS 2
|
||||||
#define DEFAULT_BITRATE 128
|
#define DEFAULT_BITRATE 128
|
||||||
|
|
||||||
|
#define DEFAULT_VERBOSE 1
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
130
jitter.c
130
jitter.c
@ -1,130 +0,0 @@
|
|||||||
#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
34
jitter.h
@ -1,34 +0,0 @@
|
|||||||
#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
|
|
251
rx.c
251
rx.c
@ -2,168 +2,106 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
#include <celt/celt.h>
|
#include <celt/celt.h>
|
||||||
|
#include <ortp/ortp.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "jitter.h"
|
|
||||||
#include "sched.h"
|
#include "sched.h"
|
||||||
|
|
||||||
#define ENCODED_BYTES 71
|
static unsigned int verbose = DEFAULT_VERBOSE;
|
||||||
|
|
||||||
/*
|
static RtpSession* create_rtp_recv(const char *addr_desc, const int port,
|
||||||
* Bind to a network socket for receiving packets on the given port
|
unsigned int jitter)
|
||||||
*/
|
|
||||||
|
|
||||||
static int listen_on_network(const char *service)
|
|
||||||
{
|
{
|
||||||
int r, sd;
|
RtpSession *session;
|
||||||
struct addrinfo hints, *servinfo, *p;
|
|
||||||
|
|
||||||
memset(&hints, 0, sizeof(hints));
|
session = rtp_session_new(RTP_SESSION_RECVONLY);
|
||||||
hints.ai_family = AF_UNSPEC;
|
rtp_session_set_scheduling_mode(session, 0);
|
||||||
hints.ai_socktype = SOCK_DGRAM;
|
rtp_session_set_blocking_mode(session, 0);
|
||||||
hints.ai_flags = AI_PASSIVE; /* use my IP */
|
rtp_session_set_local_addr(session, addr_desc, port);
|
||||||
|
rtp_session_set_connected_mode(session, FALSE);
|
||||||
r = getaddrinfo(NULL, service, &hints, &servinfo);
|
rtp_session_enable_adaptive_jitter_compensation(session, TRUE);
|
||||||
if (r != 0) {
|
rtp_session_set_jitter_compensation(session, jitter); /* ms */
|
||||||
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(r));
|
if (rtp_session_set_payload_type(session, 0) != 0)
|
||||||
return -1;
|
abort();
|
||||||
|
if (rtp_session_signal_connect(session, "timestamp_jump",
|
||||||
|
(RtpCallback)rtp_session_resync, 0) != 0)
|
||||||
|
{
|
||||||
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bind to the first one we can */
|
return session;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static int play_one_frame(void *packet,
|
||||||
* Playback audio from the buffer to the audio device
|
size_t len,
|
||||||
*/
|
CELTDecoder *decoder,
|
||||||
|
snd_pcm_t *snd,
|
||||||
static int play_audio(struct jitbuf_t *jb, CELTDecoder *decoder,
|
const unsigned int channels,
|
||||||
snd_pcm_t *snd)
|
const snd_pcm_uframes_t samples)
|
||||||
{
|
{
|
||||||
int r;
|
int r;
|
||||||
void *data;
|
|
||||||
float *pcm;
|
float *pcm;
|
||||||
snd_pcm_sframes_t f;
|
snd_pcm_sframes_t f;
|
||||||
|
|
||||||
pcm = alloca(sizeof(float) * DEFAULT_FRAME * DEFAULT_CHANNELS);
|
pcm = alloca(sizeof(float) * samples * channels);
|
||||||
|
|
||||||
data = jitbuf_front(jb);
|
if (packet == NULL) {
|
||||||
if (data == NULL) {
|
|
||||||
r = celt_decode_float(decoder, NULL, 0, pcm);
|
r = celt_decode_float(decoder, NULL, 0, pcm);
|
||||||
} else {
|
} else {
|
||||||
r = celt_decode_float(decoder, data, ENCODED_BYTES, pcm);
|
r = celt_decode_float(decoder, packet, len, pcm);
|
||||||
}
|
}
|
||||||
if (r != 0) {
|
if (r != 0) {
|
||||||
fputs("Error in celt_decode\n", stderr);
|
fputs("Error in celt_decode\n", stderr);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
f = snd_pcm_writei(snd, pcm, DEFAULT_FRAME);
|
f = snd_pcm_writei(snd, pcm, samples);
|
||||||
if (f < 0) {
|
if (f < 0) {
|
||||||
aerror("snd_pcm_writei", f);
|
aerror("snd_pcm_writei", f);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (f < DEFAULT_FRAME)
|
if (f < samples)
|
||||||
fprintf(stderr, "Short write %ld\n", f);
|
fprintf(stderr, "Short write %ld\n", f);
|
||||||
|
|
||||||
jitbuf_pop(jb);
|
|
||||||
free(data);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static int run_rx(RtpSession *session,
|
||||||
* Read from the network into the buffer
|
CELTDecoder *decoder,
|
||||||
*/
|
snd_pcm_t *snd,
|
||||||
|
const unsigned int channels,
|
||||||
static int read_from_network(int sd, struct jitbuf_t *jb)
|
const snd_pcm_uframes_t frame)
|
||||||
{
|
{
|
||||||
size_t z;
|
int ts = 0;
|
||||||
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 (;;) {
|
||||||
for (;;) {
|
int r, have_more;
|
||||||
r = read_from_network(sd, &jb);
|
char buf[32768];
|
||||||
if (r == -EAGAIN)
|
void *packet;
|
||||||
break;
|
|
||||||
if (r != 0)
|
r = rtp_session_recv_with_ts(session, buf,
|
||||||
return -1;
|
sizeof(buf), ts, &have_more);
|
||||||
|
assert(r >= 0);
|
||||||
|
assert(have_more == 0);
|
||||||
|
if (r == 0) {
|
||||||
|
packet = NULL;
|
||||||
|
if (verbose > 1)
|
||||||
|
fputc('#', stderr);
|
||||||
|
} else {
|
||||||
|
packet = buf;
|
||||||
|
if (verbose > 1)
|
||||||
|
fputc('.', stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
//jitbuf_debug(&jb, stderr);
|
r = play_one_frame(packet, r, decoder, snd, channels, frame);
|
||||||
|
if (r == -1)
|
||||||
if (play_audio(&jb, decoder, snd) == -1)
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
jitbuf_clear(&jb);
|
ts += frame;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void usage(FILE *fd)
|
static void usage(FILE *fd)
|
||||||
@ -171,32 +109,54 @@ static void usage(FILE *fd)
|
|||||||
fprintf(fd, "Usage: rx [<parameters>]\n");
|
fprintf(fd, "Usage: rx [<parameters>]\n");
|
||||||
|
|
||||||
fprintf(fd, "\nAudio device (ALSA) parameters:\n");
|
fprintf(fd, "\nAudio device (ALSA) parameters:\n");
|
||||||
fprintf(fd, " -d <device> Device name (default '%s')\n",
|
fprintf(fd, " -d <dev> Device name (default '%s')\n",
|
||||||
DEFAULT_DEVICE);
|
DEFAULT_DEVICE);
|
||||||
fprintf(fd, " -m <ms> Buffer time (milliseconds, default %d)\n",
|
fprintf(fd, " -m <ms> Buffer time (milliseconds, default %d)\n",
|
||||||
DEFAULT_BUFFER);
|
DEFAULT_BUFFER);
|
||||||
|
|
||||||
fprintf(fd, "\nNetwork parameters:\n");
|
fprintf(fd, "\nNetwork parameters:\n");
|
||||||
fprintf(fd, " -p <port> UDP port number or name (default %s)\n",
|
fprintf(fd, " -h <addr> IP address to listen on (default %s)\n",
|
||||||
DEFAULT_SERVICE);
|
DEFAULT_ADDR);
|
||||||
|
fprintf(fd, " -p <port> UDP port number (default %d)\n",
|
||||||
|
DEFAULT_PORT);
|
||||||
|
fprintf(fd, " -j <ms> Jitter buffer (milliseconds, default %d)\n",
|
||||||
|
DEFAULT_JITTER);
|
||||||
|
|
||||||
|
fprintf(fd, "\nEncoding parameters (must match sender):\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, " -f <bytes> Frame size (default %d)\n",
|
||||||
|
DEFAULT_FRAME);
|
||||||
|
|
||||||
|
fprintf(fd, "\nDisplay parameters:\n");
|
||||||
|
fprintf(fd, " -v <n> Verbosity level (default %d)\n",
|
||||||
|
DEFAULT_VERBOSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int sd, r;
|
int r;
|
||||||
snd_pcm_t *snd;
|
snd_pcm_t *snd;
|
||||||
CELTMode *mode;
|
CELTMode *mode;
|
||||||
CELTDecoder *decoder;
|
CELTDecoder *decoder;
|
||||||
|
RtpSession *session;
|
||||||
|
|
||||||
/* command-line options */
|
/* command-line options */
|
||||||
const char *device = DEFAULT_DEVICE,
|
const char *device = DEFAULT_DEVICE,
|
||||||
*service = DEFAULT_SERVICE;
|
*addr = DEFAULT_ADDR;
|
||||||
unsigned int buffer = DEFAULT_BUFFER;
|
unsigned int buffer = DEFAULT_BUFFER,
|
||||||
|
rate = DEFAULT_RATE,
|
||||||
|
frame = DEFAULT_FRAME,
|
||||||
|
jitter = DEFAULT_JITTER,
|
||||||
|
channels = DEFAULT_CHANNELS,
|
||||||
|
port = DEFAULT_PORT;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
c = getopt(argc, argv, "d:m:p:");
|
c = getopt(argc, argv, "d:h:j:m:p:v:");
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -204,11 +164,20 @@ int main(int argc, char *argv[])
|
|||||||
case 'd':
|
case 'd':
|
||||||
device = optarg;
|
device = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'h':
|
||||||
|
addr = optarg;
|
||||||
|
break;
|
||||||
|
case 'j':
|
||||||
|
jitter = atoi(optarg);
|
||||||
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
buffer = atoi(optarg);
|
buffer = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
service = optarg;
|
port = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
verbose = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
usage(stderr);
|
usage(stderr);
|
||||||
@ -216,8 +185,7 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mode = celt_mode_create(DEFAULT_RATE, DEFAULT_CHANNELS, DEFAULT_FRAME,
|
mode = celt_mode_create(rate, channels, frame, NULL);
|
||||||
NULL);
|
|
||||||
if (mode == NULL) {
|
if (mode == NULL) {
|
||||||
fputs("celt_mode_create failed\n", stderr);
|
fputs("celt_mode_create failed\n", stderr);
|
||||||
return -1;
|
return -1;
|
||||||
@ -228,36 +196,35 @@ int main(int argc, char *argv[])
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sd = listen_on_network(service);
|
if (go_realtime() != 0)
|
||||||
if (sd == -1)
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
ortp_init();
|
||||||
|
ortp_scheduler_init();
|
||||||
|
session = create_rtp_recv(addr, port, jitter);
|
||||||
|
assert(session != NULL);
|
||||||
|
|
||||||
r = snd_pcm_open(&snd, device, SND_PCM_STREAM_PLAYBACK, 0);
|
r = snd_pcm_open(&snd, device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
aerror("snd_pcm_open", r);
|
aerror("snd_pcm_open", r);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (set_alsa_hw(snd, DEFAULT_RATE, DEFAULT_CHANNELS,
|
if (set_alsa_hw(snd, rate, channels, buffer * 1000) == -1)
|
||||||
buffer * 1000) == -1)
|
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
if (set_alsa_sw(snd) == -1)
|
if (set_alsa_sw(snd) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (go_realtime() != 0)
|
r = run_rx(session, decoder, snd, channels, frame);
|
||||||
return -1;
|
|
||||||
|
|
||||||
r = run_rx(sd, decoder, snd);
|
|
||||||
|
|
||||||
if (snd_pcm_close(snd) < 0)
|
if (snd_pcm_close(snd) < 0)
|
||||||
abort();
|
abort();
|
||||||
|
|
||||||
|
rtp_session_destroy(session);
|
||||||
|
ortp_exit();
|
||||||
|
ortp_global_stats_display();
|
||||||
|
|
||||||
celt_decoder_destroy(decoder);
|
celt_decoder_destroy(decoder);
|
||||||
celt_mode_destroy(mode);
|
celt_mode_destroy(mode);
|
||||||
|
|
||||||
if (close(sd) == -1)
|
|
||||||
abort();
|
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
172
tx.c
172
tx.c
@ -2,6 +2,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
#include <celt/celt.h>
|
#include <celt/celt.h>
|
||||||
|
#include <ortp/ortp.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
@ -9,162 +10,139 @@
|
|||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "sched.h"
|
#include "sched.h"
|
||||||
|
|
||||||
typedef unsigned int seq_t;
|
static unsigned int verbose = DEFAULT_VERBOSE;
|
||||||
|
|
||||||
/*
|
static RtpSession* create_rtp_send(const char *addr_desc, const int port)
|
||||||
* 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;
|
RtpSession *session;
|
||||||
struct addrinfo hints, *p;
|
|
||||||
|
|
||||||
memset(&hints, 0, sizeof(hints));
|
session = rtp_session_new(RTP_SESSION_SENDONLY);
|
||||||
hints.ai_family = AF_UNSPEC;
|
assert(session != NULL);
|
||||||
hints.ai_socktype = SOCK_DGRAM;
|
|
||||||
|
|
||||||
r = getaddrinfo(host, service, &hints, servinfo);
|
rtp_session_set_scheduling_mode(session, 0);
|
||||||
if (r != 0) {
|
rtp_session_set_blocking_mode(session, 0);
|
||||||
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(r));
|
rtp_session_set_connected_mode(session, FALSE);
|
||||||
return -1;
|
if (rtp_session_set_remote_addr(session, addr_desc, port) != 0)
|
||||||
}
|
abort();
|
||||||
|
if (rtp_session_set_payload_type(session, 0) != 0)
|
||||||
|
abort();
|
||||||
|
if (rtp_session_set_multicast_ttl(session, 16) != 0)
|
||||||
|
abort();
|
||||||
|
|
||||||
for (p = *servinfo; p != NULL; p = p->ai_next) {
|
return session;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static int send_one_frame(snd_pcm_t *snd,
|
||||||
* Encode a frame of audio and send it over the network
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int send_audio(snd_pcm_t *snd,
|
|
||||||
const snd_pcm_uframes_t samples,
|
|
||||||
const unsigned int channels,
|
const unsigned int channels,
|
||||||
|
const snd_pcm_uframes_t samples,
|
||||||
CELTEncoder *encoder,
|
CELTEncoder *encoder,
|
||||||
const size_t bytes_per_frame,
|
const size_t bytes_per_frame,
|
||||||
int sd, struct addrinfo *theirs, seq_t *seq)
|
RtpSession *session)
|
||||||
{
|
{
|
||||||
float *pcm;
|
float *pcm;
|
||||||
void *packet, *encoded;
|
void *packet;
|
||||||
ssize_t z;
|
ssize_t z;
|
||||||
snd_pcm_sframes_t f;
|
snd_pcm_sframes_t f;
|
||||||
|
static int ts = 0;
|
||||||
|
|
||||||
pcm = alloca(sizeof(float) * samples * channels);
|
pcm = alloca(sizeof(float) * samples * channels);
|
||||||
packet = alloca(bytes_per_frame + sizeof(uint32_t));
|
packet = alloca(bytes_per_frame);
|
||||||
encoded = packet + sizeof(uint32_t);
|
|
||||||
|
|
||||||
f = snd_pcm_readi(snd, pcm, samples);
|
f = snd_pcm_readi(snd, pcm, samples);
|
||||||
if (f < 0) {
|
if (f < 0) {
|
||||||
aerror("snd_pcm_readi", f);
|
aerror("snd_pcm_readi", f);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (f < DEFAULT_FRAME)
|
if (f < samples)
|
||||||
fprintf(stderr, "Short read, %ld\n", f);
|
fprintf(stderr, "Short read, %ld\n", f);
|
||||||
|
|
||||||
z = celt_encode_float(encoder, pcm, NULL, encoded, bytes_per_frame);
|
z = celt_encode_float(encoder, pcm, NULL, packet, bytes_per_frame);
|
||||||
if (z < 0) {
|
if (z < 0) {
|
||||||
fputs("celt_encode_float failed\n", stderr);
|
fputs("celt_encode_float failed\n", stderr);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
*(uint32_t*)packet = htonl(*seq);
|
rtp_session_send_with_ts(session, packet, z, ts);
|
||||||
(*seq)++;
|
ts += samples;
|
||||||
|
|
||||||
z = sendto(sd, packet, sizeof(uint32_t) + bytes_per_frame, 0,
|
|
||||||
theirs->ai_addr, theirs->ai_addrlen);
|
|
||||||
if (z == -1) {
|
|
||||||
perror("sendto");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
//fputc('>', stderr);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int run_tx(snd_pcm_t *snd,
|
static int run_tx(snd_pcm_t *snd,
|
||||||
const snd_pcm_uframes_t samples,
|
|
||||||
const unsigned int channels,
|
const unsigned int channels,
|
||||||
|
const snd_pcm_uframes_t frame,
|
||||||
CELTEncoder *encoder,
|
CELTEncoder *encoder,
|
||||||
const size_t bytes_per_frame,
|
const size_t bytes_per_frame,
|
||||||
int sd, struct addrinfo *theirs)
|
RtpSession *session)
|
||||||
{
|
{
|
||||||
seq_t seq = 0;
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
r = send_audio(snd, samples, channels,
|
r = send_one_frame(snd, channels, frame,
|
||||||
encoder, bytes_per_frame,
|
encoder, bytes_per_frame,
|
||||||
sd, theirs, &seq);
|
session);
|
||||||
if (r == -1)
|
if (r == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
if (verbose > 1)
|
||||||
|
fputc('>', stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void usage(FILE *fd)
|
static void usage(FILE *fd)
|
||||||
{
|
{
|
||||||
fprintf(fd, "Usage: tx [<parameters>] <host>\n");
|
fprintf(fd, "Usage: tx [<parameters>]\n");
|
||||||
|
|
||||||
fprintf(fd, "\nAudio device (ALSA) parameters:\n");
|
fprintf(fd, "\nAudio device (ALSA) parameters:\n");
|
||||||
fprintf(fd, " -d <device> Device name (default '%s')\n",
|
fprintf(fd, " -d <dev> Device name (default '%s')\n",
|
||||||
DEFAULT_DEVICE);
|
DEFAULT_DEVICE);
|
||||||
fprintf(fd, " -m <ms> Buffer time (milliseconds, default %d)\n",
|
fprintf(fd, " -m <ms> Buffer time (milliseconds, default %d)\n",
|
||||||
DEFAULT_BUFFER);
|
DEFAULT_BUFFER);
|
||||||
|
|
||||||
fprintf(fd, "\nNetwork parameters:\n");
|
fprintf(fd, "\nNetwork parameters:\n");
|
||||||
fprintf(fd, " -p <port> UDP port number or name (default %s)\n",
|
fprintf(fd, " -h <addr> IP address to send to (default %s)\n",
|
||||||
DEFAULT_SERVICE);
|
DEFAULT_ADDR);
|
||||||
fprintf(fd, " -f <bytes> Frame size (default %d)\n",
|
fprintf(fd, " -p <port> UDP port number (default %d)\n",
|
||||||
DEFAULT_FRAME);
|
DEFAULT_PORT);
|
||||||
|
|
||||||
fprintf(fd, "\nEncoding parameters:\n");
|
fprintf(fd, "\nEncoding parameters:\n");
|
||||||
fprintf(fd, " -r <rate> Sample rate (default %d)\n",
|
fprintf(fd, " -r <rate> Sample rate (default %d)\n",
|
||||||
DEFAULT_RATE);
|
DEFAULT_RATE);
|
||||||
fprintf(fd, " -c <n> Number of channels (default %d)\n",
|
fprintf(fd, " -c <n> Number of channels (default %d)\n",
|
||||||
DEFAULT_CHANNELS);
|
DEFAULT_CHANNELS);
|
||||||
fprintf(fd, " -b <bitrate> Bitrate (kbps, approx., default %d)\n",
|
fprintf(fd, " -f <bytes> Frame size (default %d)\n",
|
||||||
|
DEFAULT_FRAME);
|
||||||
|
fprintf(fd, " -b <kbps> Bitrate (approx., default %d)\n",
|
||||||
DEFAULT_BITRATE);
|
DEFAULT_BITRATE);
|
||||||
|
|
||||||
|
fprintf(fd, "\nDisplay parameters:\n");
|
||||||
|
fprintf(fd, " -v <n> Verbosity level (default %d)\n",
|
||||||
|
DEFAULT_VERBOSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int sd, r;
|
int r;
|
||||||
struct addrinfo *servinfo, *theirs;
|
size_t bytes_per_frame;
|
||||||
snd_pcm_t *snd;
|
snd_pcm_t *snd;
|
||||||
CELTMode *mode;
|
CELTMode *mode;
|
||||||
CELTEncoder *encoder;
|
CELTEncoder *encoder;
|
||||||
size_t bytes_per_frame;
|
RtpSession *session;
|
||||||
|
|
||||||
/* command-line options */
|
/* command-line options */
|
||||||
const char *device = DEFAULT_DEVICE,
|
const char *device = DEFAULT_DEVICE,
|
||||||
*service = DEFAULT_SERVICE,
|
*addr = DEFAULT_ADDR;
|
||||||
*host;
|
|
||||||
unsigned int buffer = DEFAULT_BUFFER,
|
unsigned int buffer = DEFAULT_BUFFER,
|
||||||
rate = DEFAULT_RATE,
|
rate = DEFAULT_RATE,
|
||||||
channels = DEFAULT_CHANNELS,
|
channels = DEFAULT_CHANNELS,
|
||||||
frame = DEFAULT_FRAME,
|
frame = DEFAULT_FRAME,
|
||||||
kbps = DEFAULT_BITRATE;
|
kbps = DEFAULT_BITRATE,
|
||||||
|
port = DEFAULT_PORT;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
c = getopt(argc, argv, "b:c:d:f:m:p:r:");
|
c = getopt(argc, argv, "b:c:d:f:h:m:p:r:v:");
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -181,28 +159,27 @@ int main(int argc, char *argv[])
|
|||||||
case 'f':
|
case 'f':
|
||||||
frame = atol(optarg);
|
frame = atol(optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'h':
|
||||||
|
addr = optarg;
|
||||||
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
buffer = atoi(optarg);
|
buffer = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
service = optarg;
|
port = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
rate = atoi(optarg);
|
rate = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'v':
|
||||||
|
verbose = atoi(optarg);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
usage(stderr);
|
usage(stderr);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv[optind] == NULL) {
|
|
||||||
usage(stderr);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
host = argv[optind];
|
|
||||||
|
|
||||||
mode = celt_mode_create(rate, channels, frame, NULL);
|
mode = celt_mode_create(rate, channels, frame, NULL);
|
||||||
if (mode == NULL) {
|
if (mode == NULL) {
|
||||||
fputs("celt_mode_create failed\n", stderr);
|
fputs("celt_mode_create failed\n", stderr);
|
||||||
@ -216,10 +193,15 @@ int main(int argc, char *argv[])
|
|||||||
bytes_per_frame = kbps * 1024 * frame / rate / 8;
|
bytes_per_frame = kbps * 1024 * frame / rate / 8;
|
||||||
fprintf(stderr, "bytes_per_frame = %d\n", bytes_per_frame);
|
fprintf(stderr, "bytes_per_frame = %d\n", bytes_per_frame);
|
||||||
|
|
||||||
sd = bind_to_network(host, service, &servinfo, &theirs);
|
if (go_realtime() != 0)
|
||||||
if (sd == -1)
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
ortp_init();
|
||||||
|
ortp_scheduler_init();
|
||||||
|
ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR);
|
||||||
|
session = create_rtp_send(addr, port);
|
||||||
|
assert(session != NULL);
|
||||||
|
|
||||||
r = snd_pcm_open(&snd, device, SND_PCM_STREAM_CAPTURE, 0);
|
r = snd_pcm_open(&snd, device, SND_PCM_STREAM_CAPTURE, 0);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
aerror("snd_pcm_open", r);
|
aerror("snd_pcm_open", r);
|
||||||
@ -230,21 +212,17 @@ int main(int argc, char *argv[])
|
|||||||
if (set_alsa_sw(snd) == -1)
|
if (set_alsa_sw(snd) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (go_realtime() != 0)
|
r = run_tx(snd, channels, frame, encoder, bytes_per_frame, session);
|
||||||
return -1;
|
|
||||||
|
|
||||||
r = run_tx(snd, frame, channels, encoder, bytes_per_frame, sd, theirs);
|
|
||||||
|
|
||||||
freeaddrinfo(servinfo);
|
|
||||||
|
|
||||||
if (snd_pcm_close(snd) < 0)
|
if (snd_pcm_close(snd) < 0)
|
||||||
abort();
|
abort();
|
||||||
|
|
||||||
|
rtp_session_destroy(session);
|
||||||
|
ortp_exit();
|
||||||
|
ortp_global_stats_display();
|
||||||
|
|
||||||
celt_encoder_destroy(encoder);
|
celt_encoder_destroy(encoder);
|
||||||
celt_mode_destroy(mode);
|
celt_mode_destroy(mode);
|
||||||
|
|
||||||
if (close(sd) == -1)
|
|
||||||
abort();
|
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user