diff --git a/include/rfraw.h b/include/rfraw.h
new file mode 100644
index 00000000..ad21887e
--- /dev/null
+++ b/include/rfraw.h
@@ -0,0 +1,24 @@
+/** @file
+    RfRaw format functions.
+
+    Copyright (C) 2020 Christian W. Zuckschwerdt <zany@triq.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+*/
+
+#ifndef INCLUDE_RFRAW_H_
+#define INCLUDE_RFRAW_H_
+
+#include "pulse_detect.h"
+#include <stdbool.h>
+
+/// Check if a given string is in RfRaw format.
+bool rfraw_check(char const *p);
+
+/// Decode RfRaw string to pulse data.
+bool rfraw_parse(pulse_data_t *data, char const *p);
+
+#endif /* INCLUDE_RFRAW_H_ */
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4a6b1aef..6f418ffc 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -26,6 +26,7 @@ add_library(r_433 STATIC
     pulse_detect_fsk.c
     r_api.c
     r_util.c
+    rfraw.c
     samp_grab.c
     sdr.c
     term_ctl.c
diff --git a/src/Makefile.am b/src/Makefile.am
index ea5b2095..558ae871 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,6 +24,7 @@ rtl_433_SOURCES      = abuf.c \
                        pulse_detect_fsk.c \
                        r_api.c \
                        r_util.c \
+                       rfraw.c \
                        rtl_433.c \
                        samp_grab.c \
                        sdr.c \
diff --git a/src/pulse_detect.c b/src/pulse_detect.c
index d9d61c1b..daa02f4a 100644
--- a/src/pulse_detect.c
+++ b/src/pulse_detect.c
@@ -10,6 +10,7 @@
 */
 
 #include "pulse_detect.h"
+#include "rfraw.h"
 #include "pulse_demod.h"
 #include "pulse_detect_fsk.h"
 #include "baseband.h"
@@ -112,9 +113,9 @@ void pulse_data_print_vcd(FILE *file, pulse_data_t const *data, int ch_id)
 
 void pulse_data_load(FILE *file, pulse_data_t *data, uint32_t sample_rate)
 {
-    char s[256];
+    char s[1024];
     int i    = 0;
-    int size = sizeof(data->pulse) / sizeof(int);
+    int size = sizeof(data->pulse) / sizeof(*data->pulse);
 
     pulse_data_clear(data);
     data->sample_rate = sample_rate;
@@ -136,6 +137,11 @@ void pulse_data_load(FILE *file, pulse_data_t *data, uint32_t sample_rate)
                 continue; // still reading a header
             }
         }
+        if (rfraw_check(s)) {
+            rfraw_parse(data, s);
+            i = data->num_pulses;
+            continue;
+        }
         // parse two ints.
         char *p = s;
         char *endptr;
diff --git a/src/rfraw.c b/src/rfraw.c
new file mode 100644
index 00000000..4ecf5736
--- /dev/null
+++ b/src/rfraw.c
@@ -0,0 +1,175 @@
+/** @file
+    RfRaw format functions.
+
+    Copyright (C) 2020 Christian W. Zuckschwerdt <zany@triq.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+*/
+
+#include "rfraw.h"
+#include "fatal.h"
+#include <string.h>
+
+static int hexstr_get_nibble(char const **p)
+{
+    if (!p || !*p || !**p) return -1;
+    while (**p == ' ' || **p == '\t' || **p == '-' || **p == ':') ++*p;
+
+    int c = **p;
+    if (c >= '0' && c <= '9') {
+        ++*p;
+        return c - '0';
+    }
+    if (c >= 'A' && c <= 'F') {
+        ++*p;
+        return c - 'A' + 10;
+    }
+    if (c >= 'a' && c <= 'f') {
+        ++*p;
+        return c - 'a' + 10;
+    }
+
+    return -1;
+}
+
+static int hexstr_get_byte(char const **p)
+{
+    int h = hexstr_get_nibble(p);
+    int l = hexstr_get_nibble(p);
+    if (h >= 0 && l >= 0)
+        return (h << 4) | l;
+    return -1;
+}
+
+static int hexstr_get_word(char const **p)
+{
+    int h = hexstr_get_byte(p);
+    int l = hexstr_get_byte(p);
+    if (h >= 0 && l >= 0)
+        return (h << 8) | l;
+    return -1;
+}
+
+static int hexstr_peek_byte(char const *p)
+{
+    int h = hexstr_get_nibble(&p);
+    int l = hexstr_get_nibble(&p);
+    if (h >= 0 && l >= 0)
+        return (h << 4) | l;
+    return -1;
+}
+
+bool rfraw_check(char const *p)
+{
+    // require 0xaa 0xb0 or 0xaa 0xb1
+    return hexstr_get_nibble(&p) == 0xa
+            && hexstr_get_nibble(&p) == 0xa
+            && hexstr_get_nibble(&p) == 0xb
+            && (hexstr_get_nibble(&p) | 1) == 0x1;
+/*
+    if (!p || !*p) return false;
+    while (*p == ' ' || *p == '\t' || *p == '-' || *p == ':') ++p;
+    if (*p != 'A' && *p != 'a') return false;
+    p++;
+    while (*p == ' ' || *p == '\t' || *p == '-' || *p == ':') ++p;
+    if (*p != 'A' && *p != 'a') return false;
+    p++;
+    while (*p == ' ' || *p == '\t' || *p == '-' || *p == ':') ++p;
+    if (*p != 'B' && *p != 'b') return false;
+    p++;
+    while (*p == ' ' || *p == '\t' || *p == '-' || *p == ':') ++p;
+    if (*p != '0' && *p != '1') return false;
+    p++;
+    while (*p == ' ' || *p == '\t' || *p == '-' || *p == ':') ++p;
+    if (*p != '0') return false;
+    return true;
+*/
+}
+
+static bool parse_rfraw(pulse_data_t *data, char const **p)
+{
+    if (!p || !*p || !**p) return false;
+
+    int hdr = hexstr_get_byte(p);
+    if (hdr !=0xaa) return false;
+
+    int fmt = hexstr_get_byte(p);
+    if (fmt != 0xb0 && fmt != 0xb1)
+        return false;
+
+    if (fmt == 0xb0) {
+        hexstr_get_byte(p); // ignore len
+    }
+
+    int bins_len = hexstr_get_byte(p);
+    if (bins_len > 8) return false;
+
+    int repeats = 1;
+    if (fmt == 0xb0) {
+        repeats = hexstr_get_byte(p);
+    }
+
+    int bins[8] = {0};
+    for (int i = 0; i < bins_len; ++i) {
+        bins[i] = hexstr_get_word(p);
+    }
+
+    unsigned prev_pulses = data->num_pulses;
+    bool pulse_needed = true;
+    while (*p) {
+        if (hexstr_peek_byte(*p) == 0x55) {
+            hexstr_get_byte(p); // consume 0x55
+            break;
+        }
+
+        int w = hexstr_get_nibble(p);
+        if (w < 0) return false;
+        if (w >= 8) { // pulse
+            if (!pulse_needed) {
+                data->gap[data->num_pulses] = 0;
+                data->num_pulses++;
+            }
+            data->pulse[data->num_pulses] = bins[w & 7];
+            pulse_needed = false;
+        }
+        else { // gap
+            if (pulse_needed) {
+                data->pulse[data->num_pulses] = 0;
+            }
+            data->gap[data->num_pulses] = bins[w];
+            data->num_pulses++;
+            pulse_needed = true;
+        }
+    }
+    //data->gap[data->num_pulses - 1] = 3000; // TODO: extend last gap?
+
+    unsigned pkt_pulses = data->num_pulses - prev_pulses;
+    for (int i = 1; i < repeats && data->num_pulses + pkt_pulses <= PD_MAX_PULSES; ++i) {
+        memcpy(&data->pulse[data->num_pulses], &data->pulse[prev_pulses], pkt_pulses * sizeof (*data->pulse));
+        memcpy(&data->gap[data->num_pulses], &data->gap[prev_pulses], pkt_pulses * sizeof (*data->pulse));
+        data->num_pulses += pkt_pulses;
+    }
+    //pulse_data_print(data);
+
+    data->sample_rate = 1000000; // us
+    return true;
+}
+
+bool rfraw_parse(pulse_data_t *data, char const *p)
+{
+    if (!p || !*p)
+        return false;
+
+    // don't reset pulse data
+    // pulse_data_clear(data);
+
+    while (*p) {
+        if (!parse_rfraw(data, &p))
+            break;
+    }
+    //pulse_data_print(data);
+    return true;
+}
diff --git a/src/rtl_433.c b/src/rtl_433.c
index 25c0ff4e..1382a3f7 100644
--- a/src/rtl_433.c
+++ b/src/rtl_433.c
@@ -39,6 +39,7 @@
 #include "pulse_detect.h"
 #include "pulse_detect_fsk.h"
 #include "pulse_demod.h"
+#include "rfraw.h"
 #include "data.h"
 #include "r_util.h"
 #include "optparse.h"
@@ -1349,10 +1350,29 @@ int main(int argc, char **argv) {
                 }
                 if (cfg->verbosity)
                     fprintf(stderr, "Verifying test data with device %s.\n", r_dev->name);
+                if (rfraw_check(e)) {
+                    pulse_data_t pulse_data = {0};
+                    rfraw_parse(&pulse_data, e);
+                    list_t single_dev = {0};
+                    list_push(&single_dev, r_dev);
+                    if (!pulse_data.fsk_f2_est)
+                        r += run_ook_demods(&single_dev, &pulse_data);
+                    else
+                        r += run_fsk_demods(&single_dev, &pulse_data);
+                    list_free_elems(&single_dev, NULL);
+                } else
                 r += pulse_demod_string(e, r_dev);
                 continue;
             }
             // otherwise test all decoders
+            if (rfraw_check(line)) {
+                pulse_data_t pulse_data = {0};
+                rfraw_parse(&pulse_data, line);
+                if (!pulse_data.fsk_f2_est)
+                    r += run_ook_demods(&demod->r_devs, &pulse_data);
+                else
+                    r += run_fsk_demods(&demod->r_devs, &pulse_data);
+            } else
             for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {
                 r_device *r_dev = *iter;
                 if (cfg->verbosity)
@@ -1371,6 +1391,14 @@ int main(int argc, char **argv) {
     // Special case for string test data
     if (cfg->test_data) {
         r = 0;
+        if (rfraw_check(cfg->test_data)) {
+            pulse_data_t pulse_data = {0};
+            rfraw_parse(&pulse_data, cfg->test_data);
+            if (!pulse_data.fsk_f2_est)
+                r += run_ook_demods(&demod->r_devs, &pulse_data);
+            else
+                r += run_fsk_demods(&demod->r_devs, &pulse_data);
+        } else
         for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {
             r_device *r_dev = *iter;
             if (cfg->verbosity)
diff --git a/vs15/rtl_433.vcxproj b/vs15/rtl_433.vcxproj
index b688946b..bea40b4c 100644
--- a/vs15/rtl_433.vcxproj
+++ b/vs15/rtl_433.vcxproj
@@ -118,6 +118,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command>
     <ClInclude Include="..\include\r_device.h" />
     <ClInclude Include="..\include\r_private.h" />
     <ClInclude Include="..\include\r_util.h" />
+    <ClInclude Include="..\include\rfraw.h" />
     <ClInclude Include="..\include\rtl_433.h" />
     <ClInclude Include="..\include\rtl_433_devices.h" />
     <ClInclude Include="..\include\samp_grab.h" />
@@ -149,6 +150,7 @@ COPY ..\..\libusb\MS64\dll\libusb*.dll $(TargetDir)</Command>
     <ClCompile Include="..\src\pulse_detect_fsk.c" />
     <ClCompile Include="..\src\r_api.c" />
     <ClCompile Include="..\src\r_util.c" />
+    <ClCompile Include="..\src\rfraw.c" />
     <ClCompile Include="..\src\rtl_433.c" />
     <ClCompile Include="..\src\samp_grab.c" />
     <ClCompile Include="..\src\sdr.c" />
diff --git a/vs15/rtl_433.vcxproj.filters b/vs15/rtl_433.vcxproj.filters
index dcb792e0..89c587fc 100644
--- a/vs15/rtl_433.vcxproj.filters
+++ b/vs15/rtl_433.vcxproj.filters
@@ -178,6 +178,9 @@
     <ClCompile Include="..\src\r_util.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\rfraw.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\src\rtl_433.c">
       <Filter>Source Files</Filter>
     </ClCompile>