mirror of
http://www.pogo.org.uk/~mark/trx.git
synced 2024-12-22 15:35:06 +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
|
||||
|
||||
LDLIBS_ASOUND?=-lasound
|
||||
LDLIBS_CELT?=-lcelt
|
||||
LDLIBS_ORTP?=-lortp
|
||||
|
||||
LDLIBS+=$(LDLIBS_ASOUND) $(LDLIBS_CELT) $(LDLIBS_ORTP)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: rx tx
|
||||
|
||||
rx: rx.o device.o jitter.o sched.o
|
||||
rx: LDLIBS+=-lcelt -lasound
|
||||
rx: rx.o device.o sched.o
|
||||
|
||||
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
|
||||
|
10
defaults.h
10
defaults.h
@ -4,11 +4,15 @@
|
||||
#define DEFAULT_DEVICE "default"
|
||||
#define DEFAULT_BUFFER 16
|
||||
|
||||
#define DEFAULT_SERVICE "1350"
|
||||
#define DEFAULT_FRAME 128
|
||||
#define DEFAULT_ADDR "0.0.0.0"
|
||||
#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_BITRATE 128
|
||||
|
||||
#define DEFAULT_VERBOSE 1
|
||||
|
||||
#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 <alsa/asoundlib.h>
|
||||
#include <celt/celt.h>
|
||||
#include <ortp/ortp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "defaults.h"
|
||||
#include "device.h"
|
||||
#include "jitter.h"
|
||||
#include "sched.h"
|
||||
|
||||
#define ENCODED_BYTES 71
|
||||
static unsigned int verbose = DEFAULT_VERBOSE;
|
||||
|
||||
/*
|
||||
* Bind to a network socket for receiving packets on the given port
|
||||
*/
|
||||
|
||||
static int listen_on_network(const char *service)
|
||||
static RtpSession* create_rtp_recv(const char *addr_desc, const int port,
|
||||
unsigned int jitter)
|
||||
{
|
||||
int r, sd;
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
RtpSession *session;
|
||||
|
||||
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;
|
||||
session = rtp_session_new(RTP_SESSION_RECVONLY);
|
||||
rtp_session_set_scheduling_mode(session, 0);
|
||||
rtp_session_set_blocking_mode(session, 0);
|
||||
rtp_session_set_local_addr(session, addr_desc, port);
|
||||
rtp_session_set_connected_mode(session, FALSE);
|
||||
rtp_session_enable_adaptive_jitter_compensation(session, TRUE);
|
||||
rtp_session_set_jitter_compensation(session, jitter); /* ms */
|
||||
if (rtp_session_set_payload_type(session, 0) != 0)
|
||||
abort();
|
||||
if (rtp_session_signal_connect(session, "timestamp_jump",
|
||||
(RtpCallback)rtp_session_resync, 0) != 0)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/*
|
||||
* Playback audio from the buffer to the audio device
|
||||
*/
|
||||
|
||||
static int play_audio(struct jitbuf_t *jb, CELTDecoder *decoder,
|
||||
snd_pcm_t *snd)
|
||||
static int play_one_frame(void *packet,
|
||||
size_t len,
|
||||
CELTDecoder *decoder,
|
||||
snd_pcm_t *snd,
|
||||
const unsigned int channels,
|
||||
const snd_pcm_uframes_t samples)
|
||||
{
|
||||
int r;
|
||||
void *data;
|
||||
float *pcm;
|
||||
snd_pcm_sframes_t f;
|
||||
|
||||
pcm = alloca(sizeof(float) * DEFAULT_FRAME * DEFAULT_CHANNELS);
|
||||
pcm = alloca(sizeof(float) * samples * channels);
|
||||
|
||||
data = jitbuf_front(jb);
|
||||
if (data == NULL) {
|
||||
if (packet == NULL) {
|
||||
r = celt_decode_float(decoder, NULL, 0, pcm);
|
||||
} else {
|
||||
r = celt_decode_float(decoder, data, ENCODED_BYTES, pcm);
|
||||
r = celt_decode_float(decoder, packet, len, pcm);
|
||||
}
|
||||
if (r != 0) {
|
||||
fputs("Error in celt_decode\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
f = snd_pcm_writei(snd, pcm, DEFAULT_FRAME);
|
||||
f = snd_pcm_writei(snd, pcm, samples);
|
||||
if (f < 0) {
|
||||
aerror("snd_pcm_writei", f);
|
||||
return -1;
|
||||
}
|
||||
if (f < DEFAULT_FRAME)
|
||||
if (f < samples)
|
||||
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)
|
||||
static int run_rx(RtpSession *session,
|
||||
CELTDecoder *decoder,
|
||||
snd_pcm_t *snd,
|
||||
const unsigned int channels,
|
||||
const snd_pcm_uframes_t frame)
|
||||
{
|
||||
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);
|
||||
int ts = 0;
|
||||
|
||||
for (;;) {
|
||||
for (;;) {
|
||||
r = read_from_network(sd, &jb);
|
||||
if (r == -EAGAIN)
|
||||
break;
|
||||
if (r != 0)
|
||||
return -1;
|
||||
int r, have_more;
|
||||
char buf[32768];
|
||||
void *packet;
|
||||
|
||||
r = rtp_session_recv_with_ts(session, buf,
|
||||
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);
|
||||
|
||||
if (play_audio(&jb, decoder, snd) == -1)
|
||||
r = play_one_frame(packet, r, decoder, snd, channels, frame);
|
||||
if (r == -1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
jitbuf_clear(&jb);
|
||||
ts += frame;
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(FILE *fd)
|
||||
@ -171,32 +109,54 @@ 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",
|
||||
fprintf(fd, " -d <dev> Device name (default '%s')\n",
|
||||
DEFAULT_DEVICE);
|
||||
fprintf(fd, " -m <ms> Buffer time (milliseconds, default %d)\n",
|
||||
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, " -h <addr> IP address to listen on (default %s)\n",
|
||||
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 sd, r;
|
||||
int r;
|
||||
snd_pcm_t *snd;
|
||||
CELTMode *mode;
|
||||
CELTDecoder *decoder;
|
||||
RtpSession *session;
|
||||
|
||||
/* command-line options */
|
||||
const char *device = DEFAULT_DEVICE,
|
||||
*service = DEFAULT_SERVICE;
|
||||
unsigned int buffer = DEFAULT_BUFFER;
|
||||
*addr = DEFAULT_ADDR;
|
||||
unsigned int buffer = DEFAULT_BUFFER,
|
||||
rate = DEFAULT_RATE,
|
||||
frame = DEFAULT_FRAME,
|
||||
jitter = DEFAULT_JITTER,
|
||||
channels = DEFAULT_CHANNELS,
|
||||
port = DEFAULT_PORT;
|
||||
|
||||
for (;;) {
|
||||
int c;
|
||||
|
||||
c = getopt(argc, argv, "d:m:p:");
|
||||
c = getopt(argc, argv, "d:h:j:m:p:v:");
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
@ -204,11 +164,20 @@ int main(int argc, char *argv[])
|
||||
case 'd':
|
||||
device = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
addr = optarg;
|
||||
break;
|
||||
case 'j':
|
||||
jitter = atoi(optarg);
|
||||
break;
|
||||
case 'm':
|
||||
buffer = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
service = optarg;
|
||||
port = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
verbose = atoi(optarg);
|
||||
break;
|
||||
default:
|
||||
usage(stderr);
|
||||
@ -216,8 +185,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
mode = celt_mode_create(DEFAULT_RATE, DEFAULT_CHANNELS, DEFAULT_FRAME,
|
||||
NULL);
|
||||
mode = celt_mode_create(rate, channels, frame, NULL);
|
||||
if (mode == NULL) {
|
||||
fputs("celt_mode_create failed\n", stderr);
|
||||
return -1;
|
||||
@ -228,36 +196,35 @@ int main(int argc, char *argv[])
|
||||
return -1;
|
||||
}
|
||||
|
||||
sd = listen_on_network(service);
|
||||
if (sd == -1)
|
||||
if (go_realtime() != 0)
|
||||
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);
|
||||
if (r < 0) {
|
||||
aerror("snd_pcm_open", r);
|
||||
return -1;
|
||||
}
|
||||
if (set_alsa_hw(snd, DEFAULT_RATE, DEFAULT_CHANNELS,
|
||||
buffer * 1000) == -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_rx(sd, decoder, snd);
|
||||
r = run_rx(session, decoder, snd, channels, frame);
|
||||
|
||||
if (snd_pcm_close(snd) < 0)
|
||||
abort();
|
||||
|
||||
rtp_session_destroy(session);
|
||||
ortp_exit();
|
||||
ortp_global_stats_display();
|
||||
|
||||
celt_decoder_destroy(decoder);
|
||||
celt_mode_destroy(mode);
|
||||
|
||||
if (close(sd) == -1)
|
||||
abort();
|
||||
|
||||
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 <alsa/asoundlib.h>
|
||||
#include <celt/celt.h>
|
||||
#include <ortp/ortp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
@ -9,162 +10,139 @@
|
||||
#include "device.h"
|
||||
#include "sched.h"
|
||||
|
||||
typedef unsigned int seq_t;
|
||||
static unsigned int verbose = DEFAULT_VERBOSE;
|
||||
|
||||
/*
|
||||
* 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)
|
||||
static RtpSession* create_rtp_send(const char *addr_desc, const int port)
|
||||
{
|
||||
int r, sd;
|
||||
struct addrinfo hints, *p;
|
||||
RtpSession *session;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
session = rtp_session_new(RTP_SESSION_SENDONLY);
|
||||
assert(session != NULL);
|
||||
|
||||
r = getaddrinfo(host, service, &hints, servinfo);
|
||||
if (r != 0) {
|
||||
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(r));
|
||||
return -1;
|
||||
}
|
||||
rtp_session_set_scheduling_mode(session, 0);
|
||||
rtp_session_set_blocking_mode(session, 0);
|
||||
rtp_session_set_connected_mode(session, FALSE);
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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,
|
||||
static int send_one_frame(snd_pcm_t *snd,
|
||||
const unsigned int channels,
|
||||
const snd_pcm_uframes_t samples,
|
||||
CELTEncoder *encoder,
|
||||
const size_t bytes_per_frame,
|
||||
int sd, struct addrinfo *theirs, seq_t *seq)
|
||||
RtpSession *session)
|
||||
{
|
||||
float *pcm;
|
||||
void *packet, *encoded;
|
||||
void *packet;
|
||||
ssize_t z;
|
||||
snd_pcm_sframes_t f;
|
||||
static int ts = 0;
|
||||
|
||||
pcm = alloca(sizeof(float) * samples * channels);
|
||||
packet = alloca(bytes_per_frame + sizeof(uint32_t));
|
||||
encoded = packet + sizeof(uint32_t);
|
||||
packet = alloca(bytes_per_frame);
|
||||
|
||||
f = snd_pcm_readi(snd, pcm, samples);
|
||||
if (f < 0) {
|
||||
aerror("snd_pcm_readi", f);
|
||||
return -1;
|
||||
}
|
||||
if (f < DEFAULT_FRAME)
|
||||
if (f < samples)
|
||||
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) {
|
||||
fputs("celt_encode_float failed\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*(uint32_t*)packet = htonl(*seq);
|
||||
(*seq)++;
|
||||
|
||||
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);
|
||||
rtp_session_send_with_ts(session, packet, z, ts);
|
||||
ts += samples;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_tx(snd_pcm_t *snd,
|
||||
const snd_pcm_uframes_t samples,
|
||||
const unsigned int channels,
|
||||
const snd_pcm_uframes_t frame,
|
||||
CELTEncoder *encoder,
|
||||
const size_t bytes_per_frame,
|
||||
int sd, struct addrinfo *theirs)
|
||||
RtpSession *session)
|
||||
{
|
||||
seq_t seq = 0;
|
||||
|
||||
for (;;) {
|
||||
int r;
|
||||
|
||||
r = send_audio(snd, samples, channels,
|
||||
r = send_one_frame(snd, channels, frame,
|
||||
encoder, bytes_per_frame,
|
||||
sd, theirs, &seq);
|
||||
session);
|
||||
if (r == -1)
|
||||
return -1;
|
||||
|
||||
if (verbose > 1)
|
||||
fputc('>', stderr);
|
||||
}
|
||||
}
|
||||
|
||||
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, " -d <device> Device name (default '%s')\n",
|
||||
fprintf(fd, " -d <dev> Device name (default '%s')\n",
|
||||
DEFAULT_DEVICE);
|
||||
fprintf(fd, " -m <ms> Buffer time (milliseconds, default %d)\n",
|
||||
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, " -h <addr> IP address to send to (default %s)\n",
|
||||
DEFAULT_ADDR);
|
||||
fprintf(fd, " -p <port> UDP port number (default %d)\n",
|
||||
DEFAULT_PORT);
|
||||
|
||||
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);
|
||||
fprintf(fd, " -c <n> Number of channels (default %d)\n",
|
||||
fprintf(fd, " -c <n> Number of channels (default %d)\n",
|
||||
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);
|
||||
|
||||
fprintf(fd, "\nDisplay parameters:\n");
|
||||
fprintf(fd, " -v <n> Verbosity level (default %d)\n",
|
||||
DEFAULT_VERBOSE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int sd, r;
|
||||
struct addrinfo *servinfo, *theirs;
|
||||
int r;
|
||||
size_t bytes_per_frame;
|
||||
snd_pcm_t *snd;
|
||||
CELTMode *mode;
|
||||
CELTEncoder *encoder;
|
||||
size_t bytes_per_frame;
|
||||
RtpSession *session;
|
||||
|
||||
/* command-line options */
|
||||
const char *device = DEFAULT_DEVICE,
|
||||
*service = DEFAULT_SERVICE,
|
||||
*host;
|
||||
*addr = DEFAULT_ADDR;
|
||||
unsigned int buffer = DEFAULT_BUFFER,
|
||||
rate = DEFAULT_RATE,
|
||||
channels = DEFAULT_CHANNELS,
|
||||
frame = DEFAULT_FRAME,
|
||||
kbps = DEFAULT_BITRATE;
|
||||
kbps = DEFAULT_BITRATE,
|
||||
port = DEFAULT_PORT;
|
||||
|
||||
for (;;) {
|
||||
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)
|
||||
break;
|
||||
|
||||
@ -181,28 +159,27 @@ int main(int argc, char *argv[])
|
||||
case 'f':
|
||||
frame = atol(optarg);
|
||||
break;
|
||||
case 'h':
|
||||
addr = optarg;
|
||||
break;
|
||||
case 'm':
|
||||
buffer = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
service = optarg;
|
||||
port = atoi(optarg);
|
||||
break;
|
||||
case 'r':
|
||||
rate = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
verbose = 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);
|
||||
@ -216,10 +193,15 @@ int main(int argc, char *argv[])
|
||||
bytes_per_frame = kbps * 1024 * frame / rate / 8;
|
||||
fprintf(stderr, "bytes_per_frame = %d\n", bytes_per_frame);
|
||||
|
||||
sd = bind_to_network(host, service, &servinfo, &theirs);
|
||||
if (sd == -1)
|
||||
if (go_realtime() != 0)
|
||||
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);
|
||||
if (r < 0) {
|
||||
aerror("snd_pcm_open", r);
|
||||
@ -230,21 +212,17 @@ int main(int argc, char *argv[])
|
||||
if (set_alsa_sw(snd) == -1)
|
||||
return -1;
|
||||
|
||||
if (go_realtime() != 0)
|
||||
return -1;
|
||||
|
||||
r = run_tx(snd, frame, channels, encoder, bytes_per_frame, sd, theirs);
|
||||
|
||||
freeaddrinfo(servinfo);
|
||||
r = run_tx(snd, channels, frame, encoder, bytes_per_frame, session);
|
||||
|
||||
if (snd_pcm_close(snd) < 0)
|
||||
abort();
|
||||
|
||||
rtp_session_destroy(session);
|
||||
ortp_exit();
|
||||
ortp_global_stats_display();
|
||||
|
||||
celt_encoder_destroy(encoder);
|
||||
celt_mode_destroy(mode);
|
||||
|
||||
if (close(sd) == -1)
|
||||
abort();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user