GNU Radio中的数据元(Metadata)

A stream of samples is much more interesting when there is parsable metadata connected to that stream, such as the time of reception, centre frequency, sampling rate or even protocol-specific
information such as node identification.

In GNU Radio, adding metadata to sample streams is done via a mechanism called stream tags. A stream tag is an object that is connected to a specific item (e.g.
a sample). This can be a scalar value of any type, a vector, a list, a dictionary or whatever the user specifies.

When saving streams to disk, the metadata can be saved as well (see also the Metadata
Documentation Page
).

下面是C++ API中对Metadata的具体介绍:

Metadata Information

Introduction

Metadata files have extra information in the form of headers that carry metadata about the samples in the file. Raw, binary files carry no extra information and must be handled delicately. Any changes in the system state such as sample rate or if
a receiver's frequency are not conveyed with the data in the file itself. Header of metadata solve this problem.

We write metadata files using gr::blocks::file_meta_sink and
read metadata files using gr::blocks::file_meta_source.

Metadata files have headers that carry information about a segment of data within the file. The header structure is described in detail in the next section. A metadata file always starts with a header that describes the basic structure of the data.
It contains information about the item size, data type, if it's complex, the sample rate of the segment, the time stamp of the first sample of the segment, and information regarding the header size and segment size.

The first static portion of the header file contains the following information.

  • version: (char) version number (usually set to METADATA_VERSION)
  • rx_rate: (double) Stream's sample rate
  • rx_time: (pmt::pmt_t pair
    - (uint64_t, double)) Time stamp (format from UHD)
  • size: (int) item size in bytes - reflects vector length if any.
  • type: (int) data type (enum below)
  • cplx: (bool) true if data is complex
  • strt: (uint64_t) start of data relative to current header
  • bytes: (uint64_t) size of following data segment in bytes

An optional extra section of the header stores information in any received tags. The two main tags associated with tags are:

  • rx_rate: the sample rate of the stream.
  • rx_time: the time stamp of the first item in the segment.

These tags were inspired by the UHD tag format.

The header gives enough information to process and handle the data. One cautionary note, though, is that the data type should never change within a file. There should be very little need for this, but more importantly. GNU Radio blocks can only
set the data type of their IO signatures in the constructor, so changes in the data type afterward will not be recognized.

We also have an extra header segment that is option. This can be loaded up at the beginning by the user specifying some extra metadata that should be transmitted along with the data. It also grows whenever it sees a stream tag, so the dictionary
will contain and key:value pairs out of tags from the flowgraph.

Types of Metadata Files

GNU Radio currently supports two types of metadata files:

  • inline: headers are inline with the data in the same file.
  • detached: headers are in a separate header file from the data.

The inline method is the standard version. When a detached header is used, the headers are simply inserted back-to-back in the detached header file. The dat file, then, is the standard raw binary format with no interruptions in the data.

Updating Headers

While there is always a header that starts a metadata file, they are updated throughout as well. There are two events that trigger a new header. We define a segment as the unit of data associated with the last header.

The first event that will trigger a new header is when enough samples have been written for the given segment. This number is defined as the maximum segment size and is a parameter we pass to the file_meta_sink. It defaults to 1 million items (items,
not bytes). When that number of items is reached, a new header is generated and a new segment is started. This makes it easier for us to manipulate the data later and helps protect against catastrophic data loss.

The second event to trigger a new segment is if a new tag is observed. If the tag is a standard tag in the header, the header value is updated, the header and current extras are written to file, and the segment begins again. If a tag from the extras
is seen, the value associated with that tag is updated; and if a new tag is seen, a new key:value pair are added to the extras dictionary.

When new tags are seen, we generate a new segment so that we make sure that all samples in that segment are defined by the header. If the sample rate changes, we create a new segment where all of the new samples are at that new rate. Also, in the
case of UHD devices, if a segment loss is observed, it will generate a new timestamp as a tag of 'rx_time'. We create a new file segment that reflects this change to keep the sample times exact.

Implementation

Metadata files are created using gr::blocks::file_meta_sink.
The default behavior is to create a single file with inline headers as metadata. An option can be set to switch to detached header mode.

Metadata file are read into a flowgraph using gr::blocks::file_meta_source.
This source reads a metadata file, inline by default with a settable option to use detached headers. The data from the segments is converted into a standard streaming output. The 'rx_rate' and 'rx_time' and all key:value pairs in the extra header are converted
into tags and added to the stream tags interface.

Structure

The file metadata consists of a static mandatory header and a dynamic optional extras header. Each header is a separate PMT dictionary. Headers are created by building a PMT dictionary (pmt::make_dict)
of key:value pairs, then the dictionary is serialized into a string to be written to file. The header is always the same length that is predetermined by the version of the header (this must be known already). The header will then indicate if there is an extra
data to be extracted as a separate serialized dictionary.

To work with the PMTs for creating and extracting header information, we use PMT operators. For example, we create a simplified version of the header in C++ like this:

const
char METADATA_VERSION = 0x0;

pmt::pmt_t header;

header = pmt::make_dict();

header = pmt::dict_add(header, pmt::mp("version"),
pmt::mp(METADATA_VERSION));

header = pmt::dict_add(header, pmt::mp("rx_rate"),
pmt::mp(samp_rate));

std::string hdr_str = pmt::serialize_str(header);

The call to pmt::dict_add adds
a new key:value pair to the dictionary. Notice that it both takes and returns the 'header' variable. This is because we are actually creating a new dictionary with this function, so we just assign it to the same variable.

The 'mp' functions are convenience functions provided by the PMT library. They interpret the data type of the value being inserted and call the correct 'pmt::from_xxx' function. For more direct control over the data type, see PMT functions in pmt.h,
such as pmt::from_uint64 or pmt::from_double.

We finish this off by using pmt::serialize_str to
convert the PMT dictionary into a specialized string format that makes it easy to write to a file.

The header is always METADATA_HEADER_SIZE bytes long and a metadata file always starts with a header. So to extract the header from a file, we need to read in this many bytes from the beginning of the file and deserialize it. An important note about
this is that the deserialize function must operate on a std::string. The serialized format of a dictionary contains null characters, so normal C character arrays (e.g., 'char *s') get confused.

Assuming that 'std::string str' contains the full string as read from a file, we can access the dictionary in C++ like this:

pmt::pmt_t hdr =
pmt::deserialize_str(str);

if(pmt::dict_has_key(hdr,
pmt::string_to_symbol("strt"))) {

pmt::pmt_t r =
pmt::dict_ref(hdr, pmt::string_to_symbol("strt"),
pmt::PMT_NIL);

uint64_t seg_start = pmt::to_uint64(r);

uint64_t extra_len = seg_start - METADATA_HEADER_SIZE;

}

This example first deserializes the string into a PMT dictionary again. This will throw an error if the string is malformed and cannot be deserialized correctly. We then want to get access to the item with key 'strt'. As the next subsection will
show, this value indicates at which byte the data segment starts. We first check to make sure that this key exists in the dictionary. If not, our header does not contain the correct information and we might want to handle this as an error.

Assuming the header is properly formatted, we then get the particular item referenced by the key 'strt'. This is a uint64_t, so we use the PMT function to extract and convert this value properly. We now know if we have an extra header in the file
by looking at the difference between 'seg_start' and the static header size, METADATA_HEADER_SIZE. If the 'extra_len' is greater than 0, we know we have an extra header that we can process. Moreover, this also tells us the size of the serialized PMT dictionary
in bytes, so we can easily read this many bytes from the file. We can then deserialize and parse this header just like the first.

Header Information

The header is a PMT dictionary with a known structure. This structure may change, but we version the headers, so all headers of version X must be the same length and structure. As of now, we only have version 0 headers, which look like the following:

  • version: (char) version number (usually set to METADATA_VERSION)
  • rx_rate: (double) Stream's sample rate
  • rx_time: (pmt::pmt_t pair
    - (uint64_t, double)) Time stamp (format from UHD)
  • size: (int) item size in bytes - reflects vector length if any.
  • type: (int) data type (enum below)
  • cplx: (bool) true if data is complex
  • strt: (uint64_t) start of data relative to current header
  • bytes: (uint64_t) size of following data segment in bytes

The data types are indicated by an integer value from the following enumeration type:

enum gr_file_types {

GR_FILE_BYTE=0,

GR_FILE_CHAR=0,

GR_FILE_SHORT=1,

GR_FILE_INT,

GR_FILE_LONG,

GR_FILE_LONG_LONG,

GR_FILE_FLOAT,

GR_FILE_DOUBLE,

};

Extras Information

The extras section is an optional segment of the header. If 'strt' == METADATA_HEADER_SIZE, then there is no extras. Otherwise, it is simply a PMT dictionary of key:value pairs. The extras header can contain anything and can grow while a program
is running.

We can insert extra data into the header at the beginning if we wish. All we need to do is use the pmt::dict_add function
to insert our hand-made metadata. This can be useful to add our own markers and information.

The main role of the extras header, though, is as a container to hold any stream tags. When a stream tag is observed coming in, the tag's key and value are added to the dictionary. Like a standard dictionary, any time a key already exists, the value
will be updated. If the key does not exist, a new entry is created and the new key:value pair are added together. So any new tags that the file metadata sink sees will add to the dictionary. It is therefore important to always check the 'strt' value of the
header to see if the length of the extras dictionary has changed at all.

When reading out data from the extras, we do not necessarily know the data type of the PMT value. The key is always a PMT symbol, but the value can be any other PMT type. There are PMT functions that allow us to query the PMT to test if it is a
particular type. We also have the ability to do pmt::print on
any PMT object to print it to screen. Before converting from a PMT to it's natural data type, it is necessary to know the data type.

Utilities

GNU Radio comes with a couple of utilities to help in debugging and manipulating metadata files. There is a general parser in Python that will convert the PMT header and extra header into Python dictionaries. This utility is:

  • gr-blocks/python/parse_file_metadata.py

This program is installed into the Python directory under the 'gnuradio' module, so it can be accessed with:

from gnuradio.blocks import parse_file_metadata

It defines HEADER_LENGTH as the static length of the metadata header size. It also has dictionaries that can be used to convert from the file type to a string (ftype_to_string) and one to convert from the file type to the size of the data type in
bytes (ftype_to_size).

The 'parse_header' takes in a PMT dictionary, parses it, and returns a Python dictionary. An optional 'VERBOSE' bool can be set to print the information to standard out.

The 'parse_extra_dict' is similar in that it converts from a PMT dictionary to a Python dictionary. The values are kept in their PMT format since we do not necessarily know the native data type.

A program called 'gr_read_file_metadata' is installed into the path and can be used to read out all header information from a metadata file. This program is just called with the file name as the first command-line argument. An option '-D' will handle
detached header files where the file of headers is expected to be the file name of the data with '.hdr' appended to it.

Examples

Examples are located in:

  • gr-blocks/examples/metadata

Currently, there are a few GRC example programs.

  • file_metadata_sink: create a metadata file from UHD samples.
  • file_metadata_source: read the metadata file as input to a simple graph.
  • file_metadata_vector_sink: create a metadata file from UHD samples.
  • file_metadata_vector_source: read the metadata file as input to a simple graph.

The file sink example can be switched to use a signal source instead of a UHD source, but no extra tagged data is used in this mode.

The file source example pushes the data stream to a new raw file while a tag debugger block prints out any tags observed in the metadata file. A QT GUI time sink is used to look at the signal as well.

The versions with 'vector' in the name are similar except they use vectors of data.

The following shows a simple way of creating extra metadata for a metadata file. This example is just showing how we can insert a date into the metadata to keep track of later. The date in this case is encoded as a vector of uint16 with [day, month,
year].

import pmt

from gnuradio import blocks

key = pmt.intern("date")

val = pmt.init_u16vector(3, [13,12,2012])

extras = pmt.make_dict()

extras = pmt.dict_add(extras, key, val)

extras_str = pmt.serialize_str(extras)

self.sink = blocks.file_meta_sink(gr.sizeof_gr_complex,

"/tmp/metadat_file.out",

samp_rate, 1,

blocks.GR_FILE_FLOAT, True,

1000000, extra_str, False)

时间: 2024-09-19 09:14:30

GNU Radio中的数据元(Metadata)的相关文章

GNU Radio中协议数据包的传递方式

All the blocks presented so far operate as "infinite stream" blocks, i.e., they simply continue working as long as items are fed into their inputs. The low pass filter is a good example: Every new item is interpreted as a new sample, and the out

GNU Radio中流图的动态配置

How can I reconfigure a flow graph? How do I use lock(), unlock()?¶ A running flow graph is static, and can't be changed. There are two ways to implement reconfigurability: Use lock() / unlock() Create blocks that react dynamically Using lock() and u

GNU  Radio 中OFDM  Tunnel 详解

(在gnuradio3.4中确实有文中的例子,而且也在相应的目录下,在gnuradio3.7.1中,基于GMSK的是在/usr/local/share/gnuradio/examples/digital/narrowband目录下,基于OFDM的是在/usr/local/share/gnuradio/examples/digital/ofdm目录下,本文讲的应该是在3.4下的,但看了3.7的感觉大同小异) OFDM Tunnel是GNU Radio中很经典的例子.Tunnel有两个,一个是基于G

GNU Radio message

前面介绍过GNU Radio中协议数据包的传递方式之一--消息机制.仔细研究GNU Radio的源代码会发现,其中的消息机制是很重要的.几乎在所有实际的发送.接收中都会用到消息(message):在GRC中的packet_encoder和packet_decoder.pkt.py中的pkd_mod和pkd_demod以及packet.py中的packet_encoder和packet_decoder.packet_mod.packet_demod等等都用到了message_source和mess

GNU Radio协议数据包传递方式之一——消息机制

Message Passing Introduction GNU Radio was originally a streaming system with no other mechanism to pass data between blocks. Streams of data are a model that work well for samples, bits, etc., but are not really the right mechanism for control data,

GNU Radio协议数据包传递方式之二——标签机制

Tagged Stream Blocks Introduction A tagged stream block is a block that works on streamed, but packetized input data. Think of packet data transmission: A data packet consists of N bytes. However, in traditional GNU Radio blocks, if we stream N bytes

对 GNU Radio 进行 Octave 分析

Octave 是 GNU Radio 的最流行的分析工具,鉴于此 GNU Radio 软件包也包含它自身的一组脚本( scripts )用于读取和语法分析输出. 安装- Installing 可以从源码( source)来安装 Octave,或者在 Ubuntu 内使用如下命令: sudo apt-get install octave 以便 GNU Radio 的 Octave 脚本能够被直接使用,必须在 Octave 的路径变量中添加 GNU Radio 的路径. 这通过操作 Octave 的

[转]GNU Radio Companion - GRC

GNU Radio Companion - GRC 注:该文档适应于捆绑 GNU Radio 的 GRC,它不适应任何独立发行本的 GRC.如果想使用 GRC 0.70 请参阅 GNU Radio Companion (Old) . GNU Radio Companion (GRC) 是一个用来产生信号流程图及流程图源代码的图形化工具.它目前是由 Josh Blum 构建.   新特性  同稳定版 GRC 0.70 不同的,GRC 目前有哪些新特性? 捆绑式发行(Bundled)- GRC 目前

脚本安装GNU Radio

1.打开终端 2.进入一个文件夹 3.运行如下指令 $wget http://www.sbrac.org/files/build-gnuradio&& chmod a+x ./build-gnuradio &&./build-gnuradio 之前最好sudoapt-get update,安装过程会出现一些提示什么的,输入y和用户权限密码就行. 安装时间会比较长 --------------------------------------------------------