shithub: opus-tools

Download patch

ref: 77577926b80e43ecee40484e60b8a593463a1b7b
parent: ceb46c225fd9967cc346c7aede2d4f436ab162c0
parent: ffc271b3cbac8b15af8518c1b5526694c8dbf51d
author: Ralph Giles <giles@mozilla.com>
date: Mon Aug 27 12:48:40 EDT 2012

Merge branch 'rtp' from users/giles/opus-tools.git.

--- a/Makefile.am
+++ b/Makefile.am
@@ -8,7 +8,7 @@
 
 AM_CFLAGS = $(OGG_CFLAGS) $(Opus_CFLAGS) -DOPUSTOOLS -D_FORTIFY_SOURCE=2 -DHAVE_GETOPT_H -DSPX_RESAMPLE_EXPORT= -DRANDOM_PREFIX=opustools -DOUTSIDE_SPEEX -DFLOATING_POINT
 
-bin_PROGRAMS = opusenc opusdec opusinfo
+bin_PROGRAMS = opusenc opusdec opusinfo opusrtp
 
 noinst_HEADERS = src/arch.h \
                  src/diag_range.h \
@@ -52,5 +52,8 @@
 opusinfo_SOURCES = src/opus_header.c src/opusinfo.c src/info_opus.c win32/unicode_support.c
 opusinfo_LDADD = $(OGG_LIBS)
 opusinfo_MANS = man/opusinfo.1
+
+opusrtp_SOURCES = src/opusrtp.c
+opusrtp_LDADD = @LIBPCAP@ $(OGG_LIBS)
 
 #TESTS = FIXME
--- a/configure.ac
+++ b/configure.ac
@@ -182,6 +182,12 @@
   AC_MSG_WARN([Audio support not found -- no direct audio output in opusdec])
 fi
 
+dnl check for pcap
+AC_CHECK_LIB([pcap], [pcap_open_live], [
+  AC_DEFINE([HAVE_PCAP], 1, [Define if building with libpcap support])
+  LIBPCAP="-lpcap"
+])
+AC_SUBST(LIBPCAP)
 
 on_x86=no
 case "$host_cpu" in
--- /dev/null
+++ b/src/opusrtp.c
@@ -1,0 +1,500 @@
+/* dump opus rtp packets into an ogg file
+ *
+ * compile with: gcc -g -Wall -o opusrtc opusrtp.c -lpcap -logg
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_PCAP
+#include <pcap.h>
+#endif
+#include <ogg/ogg.h>
+
+/* state struct for passing around our handles */
+typedef struct {
+  ogg_stream_state *stream;
+  FILE *out;
+  int seq;
+} state;
+
+/* helper, write a little-endian 32 bit int to memory */
+void le32(unsigned char *p, int v)
+{
+  p[0] = v & 0xff;
+  p[1] = (v >> 8) & 0xff;
+  p[2] = (v >> 16) & 0xff;
+  p[3] = (v >> 24) & 0xff;
+}
+
+
+/* helper, write a little-endian 16 bit int to memory */
+void le16(unsigned char *p, int v)
+{
+  p[0] = v & 0xff;
+  p[1] = (v >> 8) & 0xff;
+}
+
+/* manufacture a generic OpusHead packet */
+ogg_packet *op_opushead(void)
+{
+  int size = 19;
+  unsigned char *data = malloc(size);
+  ogg_packet *op = malloc(sizeof(*op));
+
+  if (!data) {
+    fprintf(stderr, "Couldn't allocate data buffer.\n");
+    return NULL;
+  }
+  if (!op) {
+    fprintf(stderr, "Couldn't allocate Ogg packet.\n");
+    return NULL;
+  }
+
+  memcpy(data, "OpusHead", 8);  /* identifier */
+  data[8] = 1;                  /* version */
+  data[9] = 2;                  /* channels */
+  le16(data+10, 0);             /* pre-skip */
+  le32(data + 12, 48000);       /* original sample rate */
+  le16(data + 16, 0);           /* gain */
+  data[18] = 0;                 /* channel mapping family */
+
+  op->packet = data;
+  op->bytes = size;
+  op->b_o_s = 1;
+  op->e_o_s = 0;
+  op->granulepos = 0;
+  op->packetno = 0;
+
+  return op;
+}
+
+
+/* manufacture a generic OpusTags packet */
+ogg_packet *op_opustags(void)
+{
+  char *identifier = "OpusTags";
+  char *vendor = "opus rtp packet dump";
+  int size = strlen(identifier) + 4 + strlen(vendor) + 4;
+  unsigned char *data = malloc(size);
+  ogg_packet *op = malloc(sizeof(*op));
+
+  if (!data) {
+    fprintf(stderr, "Couldn't allocate data buffer.\n");
+    return NULL;
+  }
+  if (!op) {
+    fprintf(stderr, "Couldn't allocate Ogg packet.\n");
+    return NULL;
+  }
+
+  memcpy(data, identifier, 8);
+  le32(data + 8, strlen(vendor));
+  memcpy(data + 12, vendor, strlen(vendor));
+  le32(data + 12 + strlen(vendor), 0);
+
+  op->packet = data;
+  op->bytes = size;
+  op->b_o_s = 0;
+  op->e_o_s = 0;
+  op->granulepos = 0;
+  op->packetno = 1;
+
+  return op;
+}
+
+ogg_packet *op_from_pkt(unsigned char *pkt, int len)
+{
+  ogg_packet *op = malloc(sizeof(*op));
+  if (!op) {
+    fprintf(stderr, "Couldn't allocate Ogg packet.\n");
+    return NULL;
+  }
+
+  op->packet = pkt;
+  op->bytes = len;
+  op->b_o_s = 0;
+  op->e_o_s = 0;
+
+  return op;
+}
+
+/* free a packet and its contents */
+void op_free(ogg_packet *op) {
+  if (op) {
+    if (op->packet) {
+      free(op->packet);
+    }
+    free(op);
+  }
+}
+
+/* helper, write out available ogg pages */
+int ogg_write(state *params)
+{
+  ogg_page page;
+  size_t written;
+
+  if (!params || !params->stream || !params->out) {
+    return -1;
+  }
+
+  while (ogg_stream_pageout(params->stream, &page)) {
+    written = fwrite(page.header, 1, page.header_len, params->out);
+    if (written != (size_t)page.header_len) {
+      fprintf(stderr, "Error writing Ogg page header\n");
+      return -2;
+    }
+    written = fwrite(page.body, 1, page.body_len, params->out);
+    if (written != (size_t)page.body_len) {
+      fprintf(stderr, "Error writing Ogg page body\n");
+      return -3;
+    }
+  }
+
+  return 0;
+}
+
+/* helper, flush remaining ogg data */
+int ogg_flush(state *params)
+{
+  ogg_page page;
+  size_t written;
+
+  if (!params || !params->stream || !params->out) {
+    return -1;
+  }
+
+  while (ogg_stream_flush(params->stream, &page)) {
+    written = fwrite(page.header, 1, page.header_len, params->out);
+    if (written != (size_t)page.header_len) {
+      fprintf(stderr, "Error writing Ogg page header\n");
+      return -2;
+    }
+    written = fwrite(page.body, 1, page.body_len, params->out);
+    if (written != (size_t)page.body_len) {
+      fprintf(stderr, "Error writing Ogg page body\n");
+      return -3;
+    }
+  }
+
+  return 0;
+}
+
+#define ETH_HEADER_LEN 14
+typedef struct {
+  unsigned char src[6], dst[6]; /* ethernet MACs */
+  int type;
+} eth_header;
+
+#define IP_HEADER_MIN 20
+typedef struct {
+  int version;
+  int header_size;
+  unsigned char src[4], dst[4]; /* ipv4 addrs */
+  int protocol;
+} ip_header;
+
+#define UDP_HEADER_LEN 8
+typedef struct {
+  int src, dst; /* ports */
+  int size, checksum;
+} udp_header;
+
+#define RTP_HEADER_MIN 12
+typedef struct {
+  int version;
+  int type;
+  int pad, ext, cc, mark;
+  int seq, time;
+  int ssrc;
+  int *csrc;
+  int header_size;
+  int payload_size;
+} rtp_header;
+
+/* helper, read a big-endian 16 bit int from memory */
+static int rbe16(const unsigned char *p)
+{
+  int v = p[0] << 8 | p[1];
+  return v;
+}
+
+/* helper, read a big-endian 32 bit int from memory */
+static int rbe32(const unsigned char *p)
+{
+  int v = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
+  return v;
+}
+
+int parse_eth_header(const unsigned char *packet, int size, eth_header *eth)
+{
+  if (!packet || !eth) {
+    return -2;
+  }
+  if (size < ETH_HEADER_LEN) {
+    fprintf(stderr, "Packet too short for eth\n");
+    return -1;
+  }
+  memcpy(eth->src, packet + 0, 6);
+  memcpy(eth->dst, packet + 6, 6);
+  eth->type = rbe16(packet + 12);
+
+  return 0;
+}
+
+int parse_ip_header(const unsigned char *packet, int size, ip_header *ip)
+{
+  if (!packet || !ip) {
+    return -2;
+  }
+  if (size < IP_HEADER_MIN) {
+    fprintf(stderr, "Packet too short for ip\n");
+    return -1;
+  }
+
+  ip->version = (packet[0] >> 4) & 0x0f;
+  if (ip->version != 4) {
+    fprintf(stderr, "unhandled ip version %d\n", ip->version);
+    return 1;
+  }
+
+  /* ipv4 header */
+  ip->header_size = 4 * (packet[0] & 0x0f);
+  ip->protocol = packet[9];
+  memcpy(ip->src, packet + 12, 4);
+  memcpy(ip->dst, packet + 16, 4);
+
+  if (size < ip->header_size) {
+    fprintf(stderr, "Packet too short for ipv4 with options\n");
+    return -1;
+  }
+
+  return 0;
+}
+
+int parse_udp_header(const unsigned char *packet, int size, udp_header *udp)
+{
+  if (!packet || !udp) {
+    return -2;
+  }
+  if (size < UDP_HEADER_LEN) {
+    fprintf(stderr, "Packet too short for udp\n");
+    return -1;
+  }
+
+  udp->src = rbe16(packet);
+  udp->dst = rbe16(packet + 2);
+  udp->size = rbe16(packet + 4);
+  udp->checksum = rbe16(packet + 6);
+
+  return 0;
+}
+
+
+int parse_rtp_header(const unsigned char *packet, int size, rtp_header *rtp)
+{
+  if (!packet || !rtp) {
+    return -2;
+  }
+  if (size < RTP_HEADER_MIN) {
+    fprintf(stderr, "Packet too short for rtp\n");
+    return -1;
+  }
+  rtp->version = (packet[0] >> 6) & 3;
+  rtp->pad = (packet[0] >> 5) & 1;
+  rtp->ext = (packet[0] >> 4) & 1;
+  rtp->cc = packet[0] & 7;
+  rtp->header_size = 12 + 4 * rtp->cc;
+  rtp->payload_size = size - rtp->header_size;
+
+  rtp->mark = (packet[1] >> 7) & 1;
+  rtp->type = (packet[1]) & 127;
+  rtp->seq  = rbe16(packet + 2);
+  rtp->time = rbe32(packet + 4);
+  rtp->ssrc = rbe32(packet + 8);
+  rtp->csrc = NULL;
+  if (size < rtp->header_size) {
+    fprintf(stderr, "Packet too short for RTP header\n");
+    return -1;
+  }
+
+  return 0;
+}
+
+#ifdef HAVE_PCAP
+/* pcap 'got_packet' callback */
+void write_packet(u_char *args, const struct pcap_pkthdr *header,
+                  const u_char *data)
+{
+  state *params = (state *)args;
+  const unsigned char *packet;
+  int size;
+  eth_header eth;
+  ip_header ip;
+  udp_header udp;
+  rtp_header rtp;
+
+  fprintf(stderr, "Got %d byte packet (%d bytes captured)\n",
+          header->len, header->caplen);
+  packet = data;
+  size = header->caplen;
+
+  if (parse_eth_header(packet, size, &eth)) {
+    fprintf(stderr, "error parsing eth header\n");
+    return;
+  }
+  fprintf(stderr, "  eth 0x%04x", eth.type);
+  fprintf(stderr, " %02x:%02x:%02x:%02x:%02x:%02x ->",
+          eth.src[0], eth.src[1], eth.src[2],
+          eth.src[3], eth.src[4], eth.src[5]);
+  fprintf(stderr, " %02x:%02x:%02x:%02x:%02x:%02x\n",
+          eth.dst[0], eth.dst[1], eth.dst[2],
+          eth.dst[3], eth.dst[4], eth.dst[5]);
+  packet += ETH_HEADER_LEN;
+  size -= ETH_HEADER_LEN;
+
+  if (parse_ip_header(packet, size, &ip)) {
+    fprintf(stderr, "error parsing ip header\n");
+    return;
+  }
+  fprintf(stderr, " ipv%d protocol %d", ip.version, ip.protocol);
+  fprintf(stderr, " %d.%d.%d.%d ->",
+          ip.src[0], ip.src[1], ip.src[2], ip.src[3]);
+  fprintf(stderr, " %d.%d.%d.%d",
+          ip.dst[0], ip.dst[1], ip.dst[2], ip.dst[3]);
+  fprintf(stderr, " header %d bytes\n", ip.header_size);
+  packet += ip.header_size;
+  size -= ip.header_size;
+
+  if (parse_udp_header(packet, size, &udp)) {
+    fprintf(stderr, "error parsing udp header\n");
+    return;
+  }
+  fprintf(stderr, "  udp %d bytes %d -> %d crc 0x%04x\n",
+          udp.size, udp.src, udp.dst, udp.checksum);
+  packet += UDP_HEADER_LEN;
+  size -= UDP_HEADER_LEN;
+
+  if (parse_rtp_header(packet, size, &rtp)) {
+    fprintf(stderr, "error parsing rtp header\n");
+    return;
+  }
+  fprintf(stderr, "  rtp 0x%08x %d %d %d",
+          rtp.ssrc, rtp.type, rtp.seq, rtp.time);
+  fprintf(stderr, "  v%d %s%s%s CC %d", rtp.version,
+          rtp.pad ? "P":".", rtp.ext ? "X":".",
+          rtp.mark ? "M":".", rtp.cc);
+  fprintf(stderr, " %5d bytes\n", rtp.payload_size);
+
+  packet += rtp.header_size;
+  size -= rtp.header_size;
+
+  if (size < 0) {
+    fprintf(stderr, "skipping short packet\n");
+    return;
+  }
+
+  if (rtp.seq < params->seq) {
+    fprintf(stderr, "skipping out-of-sequence packet\n");
+    return;
+  }
+  params->seq = rtp.seq;
+
+  if (rtp.type != 109) {
+    fprintf(stderr, "skipping non-opus packet\n");
+    return;
+  }
+
+  /* write the payload to our opus file */
+  ogg_packet *op = op_from_pkt(packet, size);
+  op->granulepos = 960*rtp.seq; // FIXME: get this from the toc byte
+  ogg_stream_packetin(params->stream, op);
+  free(op);
+  ogg_write(params);
+
+  if (size < rtp.payload_size) {
+    fprintf(stderr, "!! truncated %d uncaptured bytes\n",
+            rtp.payload_size - size);
+  }
+}
+
+/* use libpcap to capture packets and write them to a file */
+int sniff(char *device)
+{
+  state *params;
+  pcap_t *pcap;
+  char errbuf[PCAP_ERRBUF_SIZE];
+  ogg_packet *op;
+
+  if (!device) {
+    device = "lo";
+  }
+
+  /* set up */
+  pcap = pcap_open_live(device, 9600, 0, 1000, errbuf);
+  if (pcap == NULL) {
+    fprintf(stderr, "Couldn't open device %s: %s\n", device, errbuf);
+    return(2);
+  }
+  params = malloc(sizeof(state));
+  if (!params) {
+    fprintf(stderr, "Couldn't allocate param struct.\n");
+    return -1;
+  }
+  params->stream = malloc(sizeof(ogg_stream_state));
+  if (!params->stream) {
+    fprintf(stderr, "Couldn't allocate stream struct.\n");
+    return -1;
+  }
+  if (ogg_stream_init(params->stream, rand()) < 0) {
+    fprintf(stderr, "Couldn't initialize Ogg stream state.\n");
+    return -1;
+  }
+  params->out = fopen("rtpdump.opus", "wb");
+  if (!params->out) {
+    fprintf(stderr, "Couldn't open output file.\n");
+    return -2;
+  }
+  params->seq = 0;
+
+  /* write stream headers */
+  op = op_opushead();
+  ogg_stream_packetin(params->stream, op);
+  op_free(op);
+  op = op_opustags();
+  ogg_stream_packetin(params->stream, op);
+  op_free(op);
+  ogg_flush(params);
+
+  /* start capture loop */
+  fprintf(stderr, "Capturing packets\n");
+  pcap_loop(pcap, 300, write_packet, (u_char *)params);
+
+  /* write outstanding data */
+  ogg_flush(params);
+
+  /* clean up */
+  fclose(params->out);
+  ogg_stream_destroy(params->stream);
+  free(params);
+  pcap_close(pcap);
+
+  return 0;
+}
+#endif /* HAVE_PCAP */
+
+int main(int argc, char *argv[])
+{
+#ifdef HAVE_PCAP
+  sniff("lo");
+#endif
+
+  return 0;
+}