From 15a7be8f754225c562d0fdcdeddf077d8a37c4de Mon Sep 17 00:00:00 2001 From: John Cox Date: Tue, 19 Aug 2014 14:57:51 +0100 Subject: [PATCH] Add primative ability to extract RTP payloads from pcap and also convert RTP to 264 --- Makefile | 5 +- pcapreport.c | 123 +++++++++++++++++++--- rtp2264.c | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 394 insertions(+), 15 deletions(-) create mode 100644 rtp2264.c diff --git a/Makefile b/Makefile index 2c3568e..fdf11f8 100644 --- a/Makefile +++ b/Makefile @@ -183,7 +183,8 @@ PROGS = \ $(BINDIR)/ts_packet_insert \ $(BINDIR)/m2ts2ts \ $(BINDIR)/pcapreport \ - $(BINDIR)/tsfilter + $(BINDIR)/tsfilter \ + $(BINDIR)/rtp2264 #\ # $(BINDIR)/test_ps @@ -287,6 +288,8 @@ $(BINDIR)/tsfilter: $(OBJDIR)/tsfilter.o $(STATIC_LIB) $(BINDIR)/tsdvbsub: $(OBJDIR)/tsdvbsub.o $(STATIC_LIB) $(CC) $< -o $(BINDIR)/tsdvbsub $(LIBOPTS) $(LDFLAGS) +$(BINDIR)/rtp2264: $(OBJDIR)/rtp2264.o $(STATIC_LIB) + $(CC) $< -o $(BINDIR)/rtp2264 $(LIBOPTS) $(LDFLAGS) diff --git a/pcapreport.c b/pcapreport.c index 950ed66..b83af21 100644 --- a/pcapreport.c +++ b/pcapreport.c @@ -87,6 +87,8 @@ struct pcapreport_section_struct { uint64_t pcr_start; // 90kHz uint64_t pcr_last; int64_t skew_last; + int64_t skew_min; + int64_t skew_max; uint64_t ts_byte_start; uint64_t ts_byte_final; int32_t rtp_skew_min; @@ -111,7 +113,8 @@ typedef struct pcapreport_rtp_info_s // RTP info (if any) in a packet typedef struct rtp_header_s { - int is_rtp; + int is_rtp_ts; + int is_rtp_raw; int marker; uint8_t payload_type; uint16_t sequence_number; @@ -226,6 +229,8 @@ typedef struct pcapreport_ctx_struct uint32_t time_usec; time_t time_sec; + uint8_t rtp_raw_wanted[256]; + pcapreport_stream_t * stream_hash[256]; pcapreport_reassembly_t reassembly_env; } pcapreport_ctx_t; @@ -362,7 +367,8 @@ section_name(const pcapreport_ctx_t * const ctx, const pcapreport_stream_t * con } static void -stream_gen_names2(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st) +stream_gen_names2(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, + const rtp_header_t * const rtp_header) { const uint32_t dest_addr = st->output_dest_addr; const uint32_t dest_port = st->output_dest_port; @@ -395,7 +401,8 @@ stream_gen_names2(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * cons memcpy(name, base_name, base_len + 1); if (!fixed_extract_name) - snprintf(name + base_len, 64, "%s.ts", identifier); + snprintf(name + base_len, 64, "%s.%s", identifier, + (rtp_header != NULL && rtp_header->is_rtp_raw) ? "rtp" : "ts"); st->output_name = name; } @@ -409,11 +416,12 @@ stream_gen_names2(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * cons } static void -stream_gen_names(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st) +stream_gen_names(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, + const rtp_header_t * const rtp_header) { // Only bother if there is some reason if (ctx->extract || ctx->csv_gen) - stream_gen_names2(ctx, st); + stream_gen_names2(ctx, st, rtp_header); } static void @@ -475,6 +483,8 @@ section_create(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const s // Init "obvious" non-zero stuff tsect->rtp_skew_max = -0x7fffffff; tsect->rtp_skew_min = 0x7fffffff; + tsect->skew_max = -0x7fffffff; + tsect->skew_min = 0x7fffffff; tsect->time_final = tsect->time_start = pkt_time(pcap_pkt_hdr); @@ -484,7 +494,7 @@ section_create(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const s tsect->ts_byte_final = st->ts_bytes; if (ctx->file_split_section || last == NULL) - stream_gen_names(ctx, st); + stream_gen_names(ctx, st, NULL); return tsect; } @@ -544,7 +554,7 @@ static int digest_times(pcapreport_ctx_t * const ctx, // Deal with RTP contents - currently held with stream but could be moved to section // especially if we do more timestamp analysis - if (rtp_header->is_rtp) + if (rtp_header->is_rtp_ts) { pcapreport_rtp_info_t * const ri = &st->rtp_info; @@ -728,10 +738,15 @@ static int digest_times(pcapreport_ctx_t * const ctx, cur_jitter = jitter_add(&st->jitter, (int)skew, (uint32_t)(t_pcr & 0xffffffffU), 90000 * 10); + if (tsect->skew_max < skew) + tsect->skew_max = skew; + if (tsect->skew_min > skew) + tsect->skew_min = skew; + if (tsect->jitter_max < cur_jitter) tsect->jitter_max = cur_jitter; - if (rtp_header->is_rtp) + if (rtp_header->is_rtp_ts) { // We have both PCR & RTP times - look for min & max int32_t rtp_skew = (int32_t)(rtp_header->timestamp - (uint32_t)(t_pcr & 0xffffffffU)); @@ -895,6 +910,71 @@ stream_ts_check(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const // RTP payload types - RFC 3551 // M2TS - RFC 2250 +static int write_rtp_raw_packet(pcapreport_ctx_t * const ctx, + pcapreport_stream_t * const st, + const byte *data, + const uint32_t len) +{ + if (st->output_name) + { + int rv; + + if (st->output_file == NULL) + { + fprint_msg("pcapreport: Dumping raw RTP packets for %s:%d to %s\n", + ipv4_addr_to_string(st->output_dest_addr), + st->output_dest_port, + st->output_name); + st->output_file = fopen(st->output_name, "wb"); + if (!st->output_file) + { + fprint_err("### pcapreport: Cannot open %s .\n", + st->output_name); + return 1; + } + } + + if (ctx->verbose) + { + fprint_msg("++ Dumping %d bytes to output file.\n", len); + } + + // need header + { + byte hdr[8]; + hdr[0] = 'R'; + hdr[1] = 'T'; + hdr[2] = 'P'; + hdr[3] = ' '; + hdr[4] = (len >> 24) & 0xff; + hdr[5] = (len >> 16) & 0xff; + hdr[6] = (len >> 8) & 0xff; + hdr[7] = len & 0xff; + rv = fwrite(hdr, sizeof(hdr), 1, st->output_file); + if (rv != 1) + { + fprint_err( "### pcapreport: Couldn't write RTP hdr bytes" + " to %s (error = %d).\n", + st->output_name, + ferror(st->output_file)); + return 1; + } + } + + rv = fwrite(data, 1, len, st->output_file); + if (rv != len) + { + fprint_err( "### pcapreport: Couldn't write %d bytes" + " to %s (error = %d).\n", + len, st->output_name, + ferror(st->output_file)); + return 1; + } + } + return 0; + +} + static int stream_rtp_check(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const st, const byte * const data, @@ -903,6 +983,8 @@ stream_rtp_check(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const { uint32_t offset; uint32_t padlen = 0; + unsigned int payload_type; + int is_raw = FALSE; // Flatten output memset(rh, 0, sizeof(*rh)); @@ -916,7 +998,10 @@ stream_rtp_check(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const if ((data[0] & 0xc0) != 0x80) return FALSE; // We only deal with TS in RTP so check for that alone - if ((data[1] & 0x7f) != 33) // PT bits + payload_type = data[1] & 0x7f; + if (ctx->rtp_raw_wanted[payload_type] != 0) + is_raw = TRUE; + else if ((data[1] & 0x7f) != 33) // PT bits return FALSE; // ??Check sequence?? @@ -942,13 +1027,14 @@ stream_rtp_check(const pcapreport_ctx_t * const ctx, pcapreport_stream_t * const offset += 4 + uint_16_be(data + offset + 2); } - // trivial check for TS in payload - if (offset + 188 + padlen > len || data[offset] != 0x47) + // trivial check for TS in payload if not raw extraction + if (!is_raw && (offset + 188 + padlen > len || data[offset] != 0x47)) return FALSE; - rh->is_rtp = TRUE; + rh->is_rtp_raw = is_raw; + rh->is_rtp_ts = !is_raw; rh->marker = ((data[1] & 0x80) != 0); - rh->payload_type = data[0] & 0x7f; + rh->payload_type = (uint8_t)payload_type; rh->sequence_number = uint_16_be(data + 2); rh->timestamp = uint_32_be(data + 4); rh->ssrc = uint_32_be(data + 8); @@ -1138,7 +1224,9 @@ stream_analysis(const pcapreport_ctx_t * const ctx, const pcapreport_stream_t * fmtx_timestamp(time_len == 0 ? 0LL : drift * 60LL * 90000LL / time_len, ctx->tfmt), drift == 0 ? 0LL : time_len / drift, drift == 0 ? "" : drift < 0 ? " (fast)" : " (slow)"); - fprint_msg(" Max jitter: %s\n", fmtx_timestamp(tsect->jitter_max, ctx->tfmt)); + fprint_msg(" Max jitter: %s; Skew min: %s, max: %s\n", fmtx_timestamp(tsect->jitter_max, ctx->tfmt), + fmtx_timestamp(tsect->skew_min, ctx->tfmt), + fmtx_timestamp(tsect->skew_max, ctx->tfmt)); } if (st->rtp_info.n != 0) { @@ -1450,6 +1538,7 @@ int main(int argc, char **argv) ctx->opt_skew_discontinuity_threshold = SKEW_DISCONTINUITY_THRESHOLD; ctx->tfmt = FMTX_TS_DISPLAY_90kHz_RAW; + ctx->rtp_raw_wanted[96] = 1; ip_reassembly_init(&ctx->reassembly_env); @@ -1845,6 +1934,12 @@ int main(int argc, char **argv) if (stream_rtp_check(ctx, st, data, len, &rtp_hdr)) { + if (ctx->extract && rtp_hdr.is_rtp_raw) + { + stream_gen_names(ctx, st, &rtp_hdr); + write_rtp_raw_packet(ctx, st, data, len); + } + data += rtp_hdr.header_len; len -= rtp_hdr.header_len + rtp_hdr.pad_len; } diff --git a/rtp2264.c b/rtp2264.c new file mode 100644 index 0000000..099e3eb --- /dev/null +++ b/rtp2264.c @@ -0,0 +1,281 @@ +/* + * Report on a pcap (.pcap) file. + * + * 2008-09-05 + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the MPEG TS, PS and ES tools. + * + * The Initial Developer of the Original Code is Amino Communications Ltd. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Richard Watts, Kynesim + * + * ***** END LICENSE BLOCK ***** + */ + +// H.264 over RTP is defined in RFC3984 + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif // _WIN32 + +#include "compat.h" +#include "version.h" +#include "misc_fns.h" +#include "fmtx.h" + +#define RTP_HDR_LEN 8 + +#define RTP_PREFIX_STRING "RTP " +#define RTP_PREFIX_LEN 4 +#define RTP_LEN_OFFSET 4 + +static int c642b(const char c) +{ + return (c >= 'A' && c <= 'Z') ? c - 'A' : + (c >= 'a' && c <= 'z') ? c - 'a' + 26 : + (c >= '0' && c <= '9') ? c - '0' + 52 : + (c == '+' || c == '-') ? 62 : + (c == '/' || c == '_') ? 63 : + (c == '=') ? -1 : -2; +} + +static size_t b64str2binn(byte * const dest0, const size_t dlen, const char ** const plast, const char * src) +{ + byte * dest = dest0; + uint32_t a = 0; + ssize_t i = 4; + size_t slen = (dlen * 4 + 5) / 3; + int b; + + while ((b = c642b(*src++)) >= 0 && --slen != 0) + { + a = (a << 6) | b; + if (--i == 0) + { + *dest++ = (a >> 16) & 0xff; + *dest++ = (a >> 8) & 0xff; + *dest++ = a & 0xff; + i = 4; + } + } + + // Tidy up at the end + if (i < 3) // i == 4 good, all done, i == 3 error + { + a <<= i * 6; + *dest++ = (a >> 16) & 0xff; + + // Consume '=' + if (b == -1) + b = c642b(*src++); + + if (i == 1) + { + *dest++ = (a >> 8) & 0xff; + } + else if (b == -1) + ++src; + } + + if (plast != NULL) + *plast = src - 1; + + return dest - dest0; +} + + +int main(int argc, char **argv) +{ + FILE *f_in = NULL; + FILE *f_out = NULL; + const char * fname_in; + const char * fname_out; + int zcount = 0; + + if (argc < 3) + { + fprintf(stderr, "Usage: \n"); + return 1; + } + + fname_in = argv[1]; + fname_out = argv[2]; + + if ((f_in = fopen(fname_in, "rb")) == NULL) + { + perror(argv[1]); + return 1; + } + + if ((f_out = fopen(fname_out, "wb")) == NULL) + { + perror(argv[2]); + return 1; + } + + if (argc > 3) + { + byte psbuf[0x1000]; + const char * eo64 = argv[3]; + + psbuf[0] = 0; + psbuf[1] = 0; + psbuf[2] = 0; + psbuf[3] = 1; + + do + { + size_t len = b64str2binn(psbuf + 4, sizeof(psbuf) - 4, &eo64, eo64); + + if ((*eo64 != 0 && *eo64 != ',') || len == 0) + { + fprintf(stderr, "Bad B64 string: '%s' (len=%zd, chr=%d)\n", argv[3], len, *eo64); + exit(1); + } + + if (fwrite(psbuf, len + 4, 1, f_out) != 1) + { + perror(fname_out); + exit(1); + } + } while (*eo64++ == ','); + } + + for (;;) + { + byte buf[0x10000]; + uint32_t rtplen; + + if (fread(buf, RTP_HDR_LEN, 1, f_in) != 1) + { + if (ferror(f_in)) + perror(fname_in); + break; + } + if (memcmp(buf, RTP_PREFIX_STRING, RTP_PREFIX_LEN) != 0) + { + fprintf(stderr, "### Bad RTP prefix\n"); + break; + } + rtplen = uint_32_be(buf + RTP_LEN_OFFSET); + if (rtplen > sizeof(buf) || rtplen < 12) + { + fprintf(stderr, "### Bad RTP len: %" PRIu32 "\n", rtplen); + break; + } + + if (fread(buf, rtplen, 1, f_in) != 1) + { + if (ferror(f_in)) + perror(fname_in); + else + fprintf(stderr, "### Unexpected EOF\n"); + break; + } + + { + size_t offset = 12 + (buf[0] & 0xf) * 4; + size_t padlen = ((buf[0] & 0x20) != 0) ? buf[rtplen - 1] : 0; + + // Check for extension + if ((buf[0] & 0x10) != 0) // X bit + offset += 4 + uint_16_be(buf + offset + 2); + + if (rtplen < offset + padlen + 1) + { + fprintf(stderr, "### Bad RTP offset + padding\n"); + } + + // OK - got payload + + { + const byte * p = buf + offset; + const byte * p_end = buf + rtplen - padlen; + byte buf2[0x18000]; // Allow for max expansion + byte * q = buf2; + byte sc1 = *p++; + + if ((sc1 & 0x1f) == 28) + { + byte sc2 = *p++; + if ((sc2 & 0x80) != 0) // S bit + { + // Start of fragmented unit + sc1 = (sc1 & 0xe0) | (sc2 & 0x1f); + *q++ = 0; + *q++ = 0; + *q++ = 0; + *q++ = 1; + *q++ = sc1; + zcount = 0; + + printf("Fragmented block with code: %x\n", sc1); + } + } + else + { + // Normal start code + *q++ = 0; + *q++ = 0; + *q++ = 0; + *q++ = 1; + *q++ = sc1; + zcount = 0; + printf("Start block with code: %x\n", sc1); + } + + + // Engage emulation protect + while (p < p_end) + { + const byte b = *p++; + + if (zcount == 2 && b <= 3) + { + *q++ = 3; + zcount = 0; + } + + *q++ = b; + zcount = (b == 0) ? zcount + 1 : 0; + } + + if (fwrite(buf2, q - buf2, 1, f_out) != 1) + { + perror(fname_out); + exit(1); + } + } + } + + } + + fclose(f_out); + fclose(f_in); + return 0; +} +