aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Pöschel <github@basicmaster.de>2016-04-24 22:18:56 +0200
committerStefan Pöschel <github@basicmaster.de>2016-04-24 22:18:56 +0200
commitc1599cb19601443c48c45c5ad8d1b0d40a6238bc (patch)
treea0be96f911a70b3b91654a4b44f998838afb1f22
parent42546c26517013fc0df6a47743c7734a1ea9ecf8 (diff)
downloadODR-AudioEnc-c1599cb19601443c48c45c5ad8d1b0d40a6238bc.tar.gz
ODR-AudioEnc-c1599cb19601443c48c45c5ad8d1b0d40a6238bc.tar.bz2
ODR-AudioEnc-c1599cb19601443c48c45c5ad8d1b0d40a6238bc.zip
DLS: add DL Plus support
- adds support for Dynamic Label Plus to mot-encoder through a new parameter block which prepends the DLS text within the regarding file - adds an option to add DL Plus data to the VLC input ICY text of dabplus-enc
-rw-r--r--src/VLCInput.cpp26
-rw-r--r--src/VLCInput.h7
-rw-r--r--src/dabplus-enc.cpp68
-rw-r--r--src/mot-encoder.cpp278
-rw-r--r--src/utils.c10
-rw-r--r--src/utils.h3
6 files changed, 334 insertions, 58 deletions
diff --git a/src/VLCInput.cpp b/src/VLCInput.cpp
index 468ef70..fc7e07f 100644
--- a/src/VLCInput.cpp
+++ b/src/VLCInput.cpp
@@ -337,20 +337,36 @@ ssize_t VLCInputDirect::read(uint8_t* buf, size_t length)
return read;
}
-bool write_icy_to_file(const std::string& text, const std::string& filename)
+bool write_icy_to_file(const std::string& text, const std::string& filename, bool dl_plus)
{
FILE* fd = fopen(filename.c_str(), "wb");
if (fd) {
- int ret = fputs(text.c_str(), fd);
+ bool ret = true;
+
+ // if desired, prepend DL Plus information
+ if (dl_plus) {
+ std::stringstream ss;
+ ss << "##### parameters { #####\n";
+ ss << "DL_PLUS=1\n";
+
+ // if non-empty text, add PROGRAMME.NOW tag
+ if (!text.empty())
+ ss << "DL_PLUS_TAG=33 0 " << (strlen_utf8(text.c_str()) - 1) << "\n"; // -1 !
+
+ ss << "##### parameters } #####\n";
+ ret &= fputs(ss.str().c_str(), fd) >= 0;
+ }
+
+ ret &= fputs(text.c_str(), fd) >= 0;
fclose(fd);
- return ret >= 0;
+ return ret;
}
return false;
}
-void VLCInput::write_icy_text(const std::string& filename)
+void VLCInput::write_icy_text(const std::string& filename, bool dl_plus)
{
if (icy_text_written.valid()) {
auto status = icy_text_written.wait_for(std::chrono::microseconds(1));
@@ -364,7 +380,7 @@ void VLCInput::write_icy_text(const std::string& filename)
else {
if (m_nowplaying_previous != m_nowplaying) {
icy_text_written = std::async(std::launch::async,
- std::bind(write_icy_to_file, m_nowplaying, filename));
+ std::bind(write_icy_to_file, m_nowplaying, filename, dl_plus));
}
diff --git a/src/VLCInput.h b/src/VLCInput.h
index ffa9258..3467525 100644
--- a/src/VLCInput.h
+++ b/src/VLCInput.h
@@ -25,6 +25,7 @@
#include <cstdio>
#include <string>
+#include <sstream>
#include <vector>
#include <deque>
#include <thread>
@@ -36,6 +37,10 @@
#include "SampleQueue.h"
#include "common.h"
+extern "C" {
+#include "utils.h"
+}
+
/* Common functionality for the direct libvlc input and the
* threaded libvlc input
*/
@@ -67,7 +72,7 @@ class VLCInput
/* Write the last received ICY-Text to the
* file.
*/
- void write_icy_text(const std::string& filename);
+ void write_icy_text(const std::string& filename, bool dl_plus);
// Callbacks for VLC
diff --git a/src/dabplus-enc.cpp b/src/dabplus-enc.cpp
index 81eb24b..0385930 100644
--- a/src/dabplus-enc.cpp
+++ b/src/dabplus-enc.cpp
@@ -126,6 +126,7 @@ void usage(const char* name) {
" -L OPTION Give an additional options to VLC (can be given\n"
" multiple times)\n"
" -w, --write-icy-text=filename Write the ICY Text into the file, so that mot-encoder can read it.\n"
+ " -W, --write-icy-text-dl-plus When writing the ICY Text into the file, add DL Plus information.\n"
#else
" The VLC input was disabled at compile-time\n"
#endif
@@ -306,6 +307,7 @@ int main(int argc, char *argv[])
// For the VLC input
std::string vlc_uri = "";
std::string vlc_icytext_file = "";
+ bool vlc_icytext_dlplus = false;
std::string vlc_gain = "";
std::string vlc_cache = "";
std::vector<std::string> vlc_additional_opts;
@@ -355,35 +357,36 @@ int main(int argc, char *argv[])
char secretkey[CURVE_KEYLEN+1];
const struct option longopts[] = {
- {"bitrate", required_argument, 0, 'b'},
- {"channels", required_argument, 0, 'c'},
- {"dabmode", required_argument, 0, 4 },
- {"dabpsy", required_argument, 0, 5 },
- {"device", required_argument, 0, 'd'},
- {"format", required_argument, 0, 'f'},
- {"input", required_argument, 0, 'i'},
- {"jack", required_argument, 0, 'j'},
- {"output", required_argument, 0, 'o'},
- {"pad", required_argument, 0, 'p'},
- {"pad-fifo", required_argument, 0, 'P'},
- {"rate", required_argument, 0, 'r'},
- {"secret-key", required_argument, 0, 'k'},
- {"silence", required_argument, 0, 's'},
- {"vlc-cache", required_argument, 0, 'C'},
- {"vlc-gain", required_argument, 0, 'g'},
- {"vlc-uri", required_argument, 0, 'v'},
- {"vlc-opt", required_argument, 0, 'L'},
- {"write-icy-text", required_argument, 0, 'w'},
- {"aaclc", no_argument, 0, 0 },
- {"dab", no_argument, 0, 'a'},
- {"drift-comp", no_argument, 0, 'D'},
- {"fifo-silence", no_argument, 0, 3 },
- {"help", no_argument, 0, 'h'},
- {"level", no_argument, 0, 'l'},
- {"no-afterburner", no_argument, 0, 'A'},
- {"ps", no_argument, 0, 2 },
- {"sbr", no_argument, 0, 1 },
- {"verbosity", no_argument, 0, 'V'},
+ {"bitrate", required_argument, 0, 'b'},
+ {"channels", required_argument, 0, 'c'},
+ {"dabmode", required_argument, 0, 4 },
+ {"dabpsy", required_argument, 0, 5 },
+ {"device", required_argument, 0, 'd'},
+ {"format", required_argument, 0, 'f'},
+ {"input", required_argument, 0, 'i'},
+ {"jack", required_argument, 0, 'j'},
+ {"output", required_argument, 0, 'o'},
+ {"pad", required_argument, 0, 'p'},
+ {"pad-fifo", required_argument, 0, 'P'},
+ {"rate", required_argument, 0, 'r'},
+ {"secret-key", required_argument, 0, 'k'},
+ {"silence", required_argument, 0, 's'},
+ {"vlc-cache", required_argument, 0, 'C'},
+ {"vlc-gain", required_argument, 0, 'g'},
+ {"vlc-uri", required_argument, 0, 'v'},
+ {"vlc-opt", required_argument, 0, 'L'},
+ {"write-icy-text", required_argument, 0, 'w'},
+ {"write-icy-text-dl-plus", no_argument, 0, 'W'},
+ {"aaclc", no_argument, 0, 0 },
+ {"dab", no_argument, 0, 'a'},
+ {"drift-comp", no_argument, 0, 'D'},
+ {"fifo-silence", no_argument, 0, 3 },
+ {"help", no_argument, 0, 'h'},
+ {"level", no_argument, 0, 'l'},
+ {"no-afterburner", no_argument, 0, 'A'},
+ {"ps", no_argument, 0, 2 },
+ {"sbr", no_argument, 0, 1 },
+ {"verbosity", no_argument, 0, 'V'},
{0, 0, 0, 0},
};
@@ -407,7 +410,7 @@ int main(int argc, char *argv[])
int index;
while(ch != -1) {
- ch = getopt_long(argc, argv, "aAhDlVb:c:f:i:j:k:L:o:r:d:p:P:s:v:w:g:C:", longopts, &index);
+ ch = getopt_long(argc, argv, "aAhDlVb:c:f:i:j:k:L:o:r:d:p:P:s:v:w:Wg:C:", longopts, &index);
switch (ch) {
case 0: // AAC-LC
aot = AOT_DABPLUS_AAC_LC;
@@ -496,6 +499,9 @@ int main(int argc, char *argv[])
case 'w':
vlc_icytext_file = optarg;
break;
+ case 'W':
+ vlc_icytext_dlplus = true;
+ break;
case 'g':
vlc_gain = optarg;
break;
@@ -929,7 +935,7 @@ int main(int argc, char *argv[])
}
if (not vlc_icytext_file.empty()) {
- vlc_in->write_icy_text(vlc_icytext_file);
+ vlc_in->write_icy_text(vlc_icytext_file, vlc_icytext_dlplus);
}
}
#endif
diff --git a/src/mot-encoder.cpp b/src/mot-encoder.cpp
index 4268d58..28e6ce8 100644
--- a/src/mot-encoder.cpp
+++ b/src/mot-encoder.cpp
@@ -3,7 +3,7 @@
Copyright (C) 2014, 2015 Matthias P. Braendli (http://opendigitalradio.org)
- Copyright (C) 2015 Stefan Pöschel (http://opendigitalradio.org)
+ Copyright (C) 2015, 2016 Stefan Pöschel (http://opendigitalradio.org)
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
@@ -224,7 +224,8 @@ void createMscDG(MSCDG* msc, unsigned short int dgtype, int *cindex, unsigned sh
struct DATA_GROUP;
DATA_GROUP* packMscDG(MSCDG* msc);
-void prepend_dls_dgs(const std::string& text, uint8_t charset);
+struct DL_STATE;
+void prepend_dl_dgs(const DL_STATE& dl_state, uint8_t charset);
void writeDLS(int output_fd, const std::string& dls_file, uint8_t charset, bool raw_dls, bool remove_dls);
// PAD related
@@ -389,15 +390,84 @@ void MOTHeader::AddExtensionVarSize(int param_id, const uint8_t* data_field, siz
#define FPAD_LEN 2
#define DLS_SEG_LEN_PREFIX 2
#define DLS_SEG_LEN_CHAR_MAX 16
-#define DLS_CMD_REMOVE_LABEL 0x01
+#define DLS_CMD_REMOVE_LABEL 0b0001
+#define DLS_CMD_DL_PLUS 0b0010
+#define DL_PLUS_CMD_TAGS 0b0000
+
+#define DL_PARAMS_OPEN "##### parameters { #####"
+#define DL_PARAMS_CLOSE "##### parameters } #####"
CharsetConverter charset_converter;
+struct DL_PLUS_TAG {
+ int content_type;
+ int start_marker;
+ int length_marker;
+
+ DL_PLUS_TAG() :
+ content_type(0), // = DUMMY
+ start_marker(0),
+ length_marker(0)
+ {}
+
+ DL_PLUS_TAG(int content_type, int start_marker, int length_marker) :
+ content_type(content_type),
+ start_marker(start_marker),
+ length_marker(length_marker)
+ {}
+
+ bool operator==(const DL_PLUS_TAG& other) const {
+ return
+ content_type == other.content_type &&
+ start_marker == other.start_marker &&
+ length_marker == other.length_marker;
+ }
+ bool operator!=(const DL_PLUS_TAG& other) const {
+ return !(*this == other);
+ }
+};
+
+typedef std::vector<DL_PLUS_TAG> dl_plus_tags_t;
+
+struct DL_STATE {
+ std::string dl_text;
+
+ bool dl_plus_enabled;
+ bool dl_plus_item_toggle;
+ bool dl_plus_item_running;
+ dl_plus_tags_t dl_plus_tags;
+
+ DL_STATE() :
+ dl_plus_enabled(false),
+ dl_plus_item_toggle(false),
+ dl_plus_item_running(false)
+ {}
+
+ bool operator==(const DL_STATE& other) const {
+ if (dl_text != other.dl_text)
+ return false;
+ if (dl_plus_enabled != other.dl_plus_enabled)
+ return false;
+ if (dl_plus_enabled) {
+ if (dl_plus_item_toggle != other.dl_plus_item_toggle)
+ return false;
+ if (dl_plus_item_running != other.dl_plus_item_running)
+ return false;
+ if (dl_plus_tags != other.dl_plus_tags)
+ return false;
+ }
+ return true;
+ }
+ bool operator!=(const DL_STATE& other) const {
+ return !(*this == other);
+ }
+};
+
+
typedef uint8_vector_t pad_t;
static bool dls_toggle = false;
-static std::string dlstext_prev = "";
-static bool dlstext_prev_set = false;
-
+static bool dl_plus_toggle = false;
+static DL_STATE dl_state_prev;
class PADPacketizer {
@@ -1375,21 +1445,163 @@ DATA_GROUP* createDynamicLabelCommand(uint8_t command) {
return dg;
}
+DATA_GROUP* createDynamicLabelPlus(const DL_STATE& dl_state) {
+ size_t tags_size = dl_state.dl_plus_tags.size();
+ size_t len_dl_plus_cmd_field = 1 + 3 * tags_size;
+ DATA_GROUP* dg = new DATA_GROUP(2 + len_dl_plus_cmd_field, 2, 3);
+ uint8_vector_t &seg_data = dg->data;
+
+ // prefix: toggle? + first seg + last seg + command flag + command
+ seg_data[0] =
+ (dl_plus_toggle ? (1 << 7) : 0) +
+ (1 << 6) +
+ (1 << 5) +
+ (1 << 4) +
+ DLS_CMD_DL_PLUS;
+
+ // prefix: link bit + length
+ seg_data[1] =
+ (dls_toggle ? (1 << 7) : 0) +
+ (len_dl_plus_cmd_field - 1); // -1 !
+
+ // DL Plus tags command: CId + IT + IR + NT
+ seg_data[2] =
+ (DL_PLUS_CMD_TAGS << 4) +
+ (dl_state.dl_plus_item_toggle ? (1 << 3) : 0) +
+ (dl_state.dl_plus_item_running ? (1 << 2) : 0) +
+ (tags_size - 1); // -1 !
+
+ for (size_t i = 0; i < tags_size; i++) {
+ // DL Plus tags command: Content Type + Start Marker + Length Marker
+ seg_data[3 + 3 * i] = dl_state.dl_plus_tags[i].content_type & 0x7F;
+ seg_data[4 + 3 * i] = dl_state.dl_plus_tags[i].start_marker & 0x7F;
+ seg_data[5 + 3 * i] = dl_state.dl_plus_tags[i].length_marker & 0x7F;
+ }
+
+ // CRC
+ dg->AppendCRC();
+
+ return dg;
+}
+
+
+std::vector<std::string> split_string(const std::string &s, const char delimiter) {
+ std::vector<std::string> result;
+ std::stringstream ss(s);
+ std::string part;
+
+ while (std::getline(ss, part, delimiter))
+ result.push_back(part);
+ return result;
+}
+
+bool parse_dl_param_bool(const std::string &key, const std::string &value, bool &target) {
+ if (value == "0") {
+ target = 0;
+ return true;
+ }
+ if (value == "1") {
+ target = 1;
+ return true;
+ }
+ fprintf(stderr, "mot-encoder Warning: DL parameter '%s' has unsupported value '%s' - ignored\n", key.c_str(), value.c_str());
+ return false;
+}
+
+bool parse_dl_param_int_dl_plus_tag(const std::string &key, const std::string &value, int &target) {
+ int value_int = atoi(value.c_str());
+ if (value_int >= 0x00 && value_int <= 0x7F) {
+ target = value_int;
+ return true;
+ }
+ fprintf(stderr, "mot-encoder Warning: DL Plus tag parameter '%s' %d out of range - ignored\n", key.c_str(), value_int);
+ return false;
+}
+
+void parse_dl_params(std::ifstream &dls_fstream, DL_STATE &dl_state) {
+ std::string line;
+ while (std::getline(dls_fstream, line)) {
+ // return on params close
+ if (line == DL_PARAMS_CLOSE)
+ return;
+
+ // ignore empty lines and comments
+ if (line.empty() || line[0] == '#')
+ continue;
+
+ // parse key/value pair
+ size_t separator_pos = line.find('=');
+ if (separator_pos == std::string::npos) {
+ fprintf(stderr, "mot-encoder Warning: DL parameter line '%s' without separator - ignored\n", line.c_str());
+ continue;
+ }
+ std::string key = line.substr(0, separator_pos);
+ std::string value = line.substr(separator_pos + 1);
+#if DEBUG
+ fprintf(stderr, "parse_dl_params: key: '%s', value: '%s'\n", key.c_str(), value.c_str());
+#endif
+
+ if (key == "DL_PLUS") {
+ parse_dl_param_bool(key, value, dl_state.dl_plus_enabled);
+ continue;
+ }
+ if (key == "DL_PLUS_ITEM_TOGGLE") {
+ parse_dl_param_bool(key, value, dl_state.dl_plus_item_toggle);
+ continue;
+ }
+ if (key == "DL_PLUS_ITEM_RUNNING") {
+ parse_dl_param_bool(key, value, dl_state.dl_plus_item_running);
+ continue;
+ }
+ if (key == "DL_PLUS_TAG") {
+ if (dl_state.dl_plus_tags.size() == 4) {
+ fprintf(stderr, "mot-encoder Warning: DL Plus tag ignored, as already four tags present\n");
+ continue;
+ }
+
+ // split value
+ std::vector<std::string> params = split_string(value, ' ');
+ if (params.size() != 3) {
+ fprintf(stderr, "mot-encoder Warning: DL Plus tag value '%s' does not have three parts - ignored\n", value.c_str());
+ continue;
+ }
+
+ int content_type, start_marker, length_marker;
+ if (parse_dl_param_int_dl_plus_tag("content_type", params[0], content_type) &
+ parse_dl_param_int_dl_plus_tag("start_marker", params[1], start_marker) &
+ parse_dl_param_int_dl_plus_tag("length_marker", params[2], length_marker))
+ dl_state.dl_plus_tags.push_back(DL_PLUS_TAG(content_type, start_marker, length_marker));
+ continue;
+ }
+
+ fprintf(stderr, "mot-encoder Warning: DL parameter '%s' unknown - ignored\n", key.c_str());
+ }
+
+ fprintf(stderr, "mot-encoder Warning: no param closing tag, so the DLS text will be empty\n");
+}
+
+
void writeDLS(int output_fd, const std::string& dls_file, uint8_t charset, bool raw_dls, bool remove_dls)
{
- std::ifstream dls_fstream(dls_file.c_str());
+ std::ifstream dls_fstream(dls_file);
if (!dls_fstream.is_open()) {
std::cerr << "Could not open " << dls_file << std::endl;
return;
}
+ DL_STATE dl_state;
+
std::vector<std::string> dls_lines;
std::string line;
// Read and convert lines one by one because the converter doesn't understand
// line endings
while (std::getline(dls_fstream, line)) {
- if (not line.empty()) {
+ if (line.empty())
+ continue;
+ if (line == DL_PARAMS_OPEN) {
+ parse_dl_params(dls_fstream, dl_state);
+ } else {
if (not raw_dls && charset == CHARSET_UTF8) {
dls_lines.push_back(charset_converter.convert(line));
}
@@ -1416,32 +1628,52 @@ void writeDLS(int output_fd, const std::string& dls_file, uint8_t charset, bool
ss << dls_lines[i];
}
- std::string dlstext = ss.str();
- if (dlstext.size() > MAXDLS)
- dlstext.resize(MAXDLS);
+
+ dl_state.dl_text = ss.str();
+ if (dl_state.dl_text.size() > MAXDLS)
+ dl_state.dl_text.resize(MAXDLS);
+
+
+ // if DL Plus enabled, but no DL Plus tags were added, add the required DUMMY tag
+ if (dl_state.dl_plus_enabled && dl_state.dl_plus_tags.empty())
+ dl_state.dl_plus_tags.push_back(DL_PLUS_TAG());
if (not raw_dls)
charset = CHARSET_COMPLETE_EBU_LATIN;
- // Toggle the toggle bit only on (first call or) new text
- bool dlstext_is_new = !dlstext_prev_set || (dlstext != dlstext_prev);
+ // toggle the toggle bit only on new DL state
+ bool dl_state_is_new = dl_state != dl_state_prev;
if (verbose) {
- fprintf(stderr, "mot-encoder writing %s DLS text \"%s\"\n", dlstext_is_new ? "new" : "old", dlstext.c_str());
+ fprintf(stderr, "mot-encoder writing %s DLS text \"%s\"\n", dl_state_is_new ? "new" : "old", dl_state.dl_text.c_str());
+ if (dl_state.dl_plus_enabled) {
+ fprintf(
+ stderr, "mot-encoder writing %s DL Plus tags (IT/IR: %d/%d): ",
+ dl_state_is_new ? "new" : "old",
+ dl_state.dl_plus_item_toggle ? 1 : 0,
+ dl_state.dl_plus_item_running ? 1 : 0);
+ for (dl_plus_tags_t::const_iterator it = dl_state.dl_plus_tags.begin(); it != dl_state.dl_plus_tags.end(); it++) {
+ if (it != dl_state.dl_plus_tags.begin())
+ fprintf(stderr, ", ");
+ fprintf(stderr, "%d (S/L: %d/%d)", it->content_type, it->start_marker, it->length_marker);
+ }
+ fprintf(stderr, "\n");
+ }
}
DATA_GROUP *remove_label_dg = NULL;
- if (dlstext_is_new) {
+ if (dl_state_is_new) {
if (remove_dls)
remove_label_dg = createDynamicLabelCommand(DLS_CMD_REMOVE_LABEL);
dls_toggle = !dls_toggle; // indicate changed text
+ if (dl_state.dl_plus_enabled)
+ dl_plus_toggle = !dl_plus_toggle;
- dlstext_prev = dlstext;
- dlstext_prev_set = true;
+ dl_state_prev = dl_state;
}
- prepend_dls_dgs(dlstext, charset);
+ prepend_dl_dgs(dl_state, charset);
if (remove_label_dg)
pad_packetizer->queue.push_front(remove_label_dg);
pad_packetizer->WriteAllPADs(output_fd);
@@ -1491,17 +1723,21 @@ DATA_GROUP* dls_get(const std::string& text, uint8_t charset, unsigned int seg_i
}
-void prepend_dls_dgs(const std::string& text, uint8_t charset) {
+void prepend_dl_dgs(const DL_STATE& dl_state, uint8_t charset) {
// process all DL segments
- int seg_count = dls_count(text);
+ int seg_count = dls_count(dl_state.dl_text);
std::vector<DATA_GROUP*> segs;
for (int seg_index = 0; seg_index < seg_count; seg_index++) {
#if DEBUG
fprintf(stderr, "Segment number %d\n", seg_index + 1);
#endif
- segs.push_back(dls_get(text, charset, seg_index));
+ segs.push_back(dls_get(dl_state.dl_text, charset, seg_index));
}
+ // if enabled, add DL Plus data group
+ if (dl_state.dl_plus_enabled)
+ segs.push_back(createDynamicLabelPlus(dl_state));
+
// prepend to packetizer
pad_packetizer->queue.insert(pad_packetizer->queue.begin(), segs.begin(), segs.end());
diff --git a/src/utils.c b/src/utils.c
index 0168a23..24da427 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -28,3 +28,13 @@ const char* level(int channel, int peak)
return text[index][channel];
}
+size_t strlen_utf8(const char *s) {
+ size_t result = 0;
+
+ // ignore continuation bytes - only count single/leading bytes
+ while (*s)
+ if ((*s++ & 0xC0) != 0x80)
+ result++;
+
+ return result;
+}
diff --git a/src/utils.h b/src/utils.h
index a0ab1ae..e411963 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -3,6 +3,7 @@
#include <math.h>
#include <stdint.h>
+#include <stddef.h>
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
@@ -45,5 +46,7 @@ struct zmq_frame_header_t
#define ZMQ_FRAME_DATA(f) ( ((uint8_t*)f)+sizeof(struct zmq_frame_header_t) )
+size_t strlen_utf8(const char *s);
+
#endif