Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RTP output #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Usage: mtx [<options>]
-h <addr> IP address (default: 239.48.48.1)
-p <port> UDP port (default: 1350)
-d <dev> ALSA device name, or '-' for stdin (default: 'default')
-R <n> RTP output (default: 0)
-f <n> Use float samples (1) or signed 16 bit integer samples (0) (default: 0)
-r <rate> Audio sample rate (default: 48000 Hz)
-c <n> Audio channel count (default: 2)
Expand Down Expand Up @@ -70,6 +71,17 @@ pcm.pnm {
- On OpenWrt and/or with cheap USB audio cards without PulseAudio, if it doesn't work try **`mrx -d plughw:0,0`**
- It shouldn't be needed anymore, but it might still be useful, so [this is a working `/etc/asound.conf` file for OpenWrt with cheap USB audio cards](https://gist.github.com/VittGam/ad0c1ce0143e4fb7a55fe8947b085e26)

### RTP

RTP output is useful for transmitting to other applications, e.g. FFmpeg.
You don't need any PulseAudio integration; just run mtx with -R 1 and
-d etc. as usual. This will output an SDP file, which you can run in e.g.
ffplay with

```
ffplay -protocol_whitelist rtp,file,udp -i mtx.sdp
```

## Bugs

- Well, all the desync bugs seem to happen (and needed a resync hack in `mtx`) only when using `alsa-pulse` to capture from the null output sink monitor...
Expand Down
9 changes: 9 additions & 0 deletions mtrx.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ struct __attribute__((__packed__)) azzp {
unsigned char data;
};

struct __attribute__((__packed__)) rtp {
uint8_t version_p_x_cc;
uint8_t marker_and_pt;
uint16_t seq;
uint32_t timestamp;
uint32_t ssrc;
unsigned char data;
};

struct __attribute__((__packed__)) timep {
int64_t tv_sec;
uint32_t tv_nsec;
Expand Down
70 changes: 61 additions & 9 deletions mtx.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "mtrx.h"

static unsigned long int kbps = 128;
static unsigned long int use_rtp = 0;

static void *time_sync_thread(void *arg) {
printverbose("Time sync thread started\n");
Expand Down Expand Up @@ -57,7 +58,7 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "Copyright (C) 2014-2017 Vittorio Gambaletta <openwrt@vittgam.net>\n\n");

while (1) {
int c = getopt(argc, argv, "h:p:d:f:r:c:t:k:b:v:");
int c = getopt(argc, argv, "h:p:d:f:r:c:t:k:b:v:T:R:");
if (c == -1) {
break;
} else if (c == 'h') {
Expand All @@ -66,6 +67,8 @@ int main(int argc, char *argv[]) {
port = strtoul(optarg, NULL, 10);
} else if (c == 'd') {
device = optarg;
} else if (c == 'R') {
use_rtp = strtoul(optarg, NULL, 10);
} else if (c == 'f') {
use_float = strtoul(optarg, NULL, 10);
} else if (c == 'r') {
Expand All @@ -87,6 +90,7 @@ int main(int argc, char *argv[]) {
fprintf(stderr, " -h <addr> IP address (default: %s)\n", addr);
fprintf(stderr, " -p <port> UDP port (default: %lu)\n", port);
fprintf(stderr, " -d <dev> ALSA device name, or '-' for stdin (default: '%s')\n", device);
fprintf(stderr, " -R <n> RTP output (default: %lu)\n", use_rtp);
fprintf(stderr, " -f <n> Use float samples (1) or signed 16 bit integer samples (0) (default: %lu)\n", use_float);
fprintf(stderr, " -r <rate> Audio sample rate (default: %lu Hz)\n", rate);
fprintf(stderr, " -c <n> Audio channel count (default: %lu)\n", channels);
Expand All @@ -100,6 +104,11 @@ int main(int argc, char *argv[]) {
}
}

if (use_rtp && enable_time_sync) {
fprintf(stderr, "Disabling time synchronization due to RTP output.\n");
enable_time_sync = 0;
}

int sock = init_socket(0);

set_realtime_prio();
Expand Down Expand Up @@ -139,13 +148,40 @@ int main(int argc, char *argv[]) {
}

void *pcm = alloca(pcm_size);
struct azzp *packet = alloca(bytes_per_frame + sizeof(struct timep));
struct azzp *azzp_packet = NULL;
struct rtp *rtp_packet = NULL;
if (use_rtp) {
rtp_packet = (struct rtp *)alloca(bytes_per_frame + sizeof(struct rtp) - 1);
} else {
azzp_packet = (struct azzp *)alloca(bytes_per_frame + sizeof(struct azzp) - 1);
}

struct timespec clock = {0, 0};
int resync = 1;

drop_privs_if_needed();

// Used for RTP only.
srand(time(NULL));
uint16_t rtp_seq = rand();
uint32_t ssrc = rand();
uint32_t ts = 0;

if (use_rtp) {
printf("SDP file for this stream:\n\n");
printf("v=0\n");
printf("o=- 0 0 IN IP4 127.0.0.1\n");
printf("s=No Name\n");
printf("c=IN IP4 %s\n", addr);
printf("t=0 0\n");
printf("a=tool:mtx\n");
printf("m=audio %ld RTP/AVP 96\n", port);
printf("b=AS:96\n");
printf("a=rtpmap:96 opus/48000/%lu\n", channels);
printf("a=fmtp:96 sprop-stereo=%d\n", channels > 1);
printf("a=control:streamid=0\n\n");
}

while (1) {
printverbose("clock %ld.%09lu\n", clock.tv_sec, clock.tv_nsec);

Expand Down Expand Up @@ -204,11 +240,13 @@ int main(int argc, char *argv[]) {
}
}

unsigned char *data = use_rtp ? &rtp_packet->data : &azzp_packet->data;

ssize_t z;
if (use_float) {
z = opus_encode_float(encoder, pcm, samples, &packet->data, bytes_per_frame);
z = opus_encode_float(encoder, pcm, samples, data, bytes_per_frame);
} else {
z = opus_encode(encoder, pcm, samples, &packet->data, bytes_per_frame);
z = opus_encode(encoder, pcm, samples, data, bytes_per_frame);
}
if (z < 0) {
fprintf(stderr, "opus_encode: %s\n", opus_strerror(z));
Expand Down Expand Up @@ -236,12 +274,26 @@ int main(int argc, char *argv[]) {
printverbose("resync %lld %d\n", (((1000000000LL * (now.tv_sec - clock.tv_sec)) + (now.tv_nsec - clock.tv_nsec))), resync);
clock = now;

packet->tv_sec = htobe64(now.tv_sec);
packet->tv_nsec = htobe32(now.tv_nsec);
if (use_rtp) {
rtp_packet->version_p_x_cc = (2 << 6); // Version 2, the other fields are zero.
rtp_packet->marker_and_pt = 96; // First ID that's dynamically allocated.
rtp_packet->seq = htobe16(rtp_seq++);
rtp_packet->timestamp = htobe32(ts);
rtp_packet->ssrc = htobe32(ssrc);
ts += samples;

if (sendto(sock, packet, z + sizeof(struct timep), 0, (struct sockaddr *) &addrin, sizeof(addrin)) < 0) {
perror("sendto");
exit(1);
if (sendto(sock, rtp_packet, z + sizeof(struct rtp) - 1, 0, (struct sockaddr *) &addrin, sizeof(addrin)) < 0) {
perror("sendto");
exit(1);
}
} else {
azzp_packet->tv_sec = htobe64(now.tv_sec);
azzp_packet->tv_nsec = htobe32(now.tv_nsec);

if (sendto(sock, azzp_packet, z + sizeof(struct azzp) - 1, 0, (struct sockaddr *) &addrin, sizeof(addrin)) < 0) {
perror("sendto");
exit(1);
}
}
}

Expand Down