gnu radio学习Stream Tags流标签详解

Posted I_believe_CWJ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gnu radio学习Stream Tags流标签详解相关的知识,希望对你有一定的参考价值。

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 can lack for control and meta data.
GNU Radio最初是一个流媒体系统,最初没有其他的机制可以在块与块之间传递数据。数据流是一个适用于样本,位等等的模型,但是缺少控制和元数据。

Part of this is solved using the existing message passing interface, which allows blocks to subscribe to messages published by any other block in the flowgraph (see Message Passing). The main drawback to the message passing system is that it works asynchronously, meaning that there is no guarantee when a message may arrive relative to the data stream.
部分问题是通过现有的消息传递接口解决的,该接口允许块接收在流图中任何其他块产生的信息。消息传递系统的主要缺点是它是异步工作的,这意味着无法保证消息相对于数据量何时到达。

Stream tags are an isosynchronous data stream that runs parallel to the main data stream. A stream tag is generated by a block’s work function and from there on flows downstream alongside a particular sample, until it reaches a sink or is forced to stop propagating by another block.
流标签是一个与主数据流同时运行的的同步数据流。流标签由块工作函数生成并且从那里随着特动的样本向下传输,直到它到达一个输出接口或者被一个其他的块强行制止。

Stream tags are defined for a specific item in the data stream and are formed as a key:value pair. The key identifies what the value represents while the value holds the data that the tag contains. Both key and value are Polymorphic Types (PMTs) where the key is a PMT symbol while the value is any type of PMT and can therefore handle any data we wish to pass. An additional part of the tag is the srcid, which is a PMT symbol and is used to identify the block that created the tag (which is usually the block’s alias).
流标签是数据流中的某个特定的项而定义的,形式为键值对。键key表示值内容表示什么,值value用用来存放标签包含的数据。key和value都是PMT对象类型,其中key是PMT符号而value是任何形式的PMT,所以可以处理任何我们想传递的信息。标签的另一个部分是srcid,它是PMT符号用来表示创建标签的块是哪个(通常是块的别名)

API Extensions to the gr::block(gr::block的API扩展)

To enable the stream tags, we have extended the API of gr::block to understand absolute item numbers. In the data stream model, each block’s work function is given a buffer in the data stream that is referenced from 0 to N-1. This is a relative offset into the data stream. The absolute reference starts from the beginning of the flowgraph and continues to count up with every item. Each input stream is associated with a concept of the ‘number of items read’ and each output stream has a ‘number of items written’. These are retrieved during runtime using the two API calls:
为了使用流标签,我们扩展了gr::block块的API来理解绝对的项目数字。在数据流模型中,每一个块的工作函数在数据流中被赋予了一个从0~N-1引用的缓存区。这是数据流中的相对偏移量。绝对引用随着流图的开始而开始,应随着每一个项目一直计数。每一个输入流都与‘读取的项目数相关联’,每个输出流都包含一个‘写入项目数’。这些都是在运行时使用两个API调用得到的。

unsigned long int nitems_read(unsigned int which_input);
unsigned long int nitems_written(unsigned int which_output);

Each tag is associated with some item in this absolute time scale that is calculated using these functions.
每个标签都与用这些函数计算的绝对时间尺度内的项目联系起来。

Like the rest of the data stream, the number of items read/written are only updated once during the call to work. So in a work function, nitems_read/written will refer to the state of the data stream at the start of the work function. We must therefore add to this value the current relative offset in the data stream. So if we are iterating i over all output items, we would write the stream tag to output ports at nitems_written(0)+i for the 0th output port.
与数据流的其他剩余部分一样,读写的项目数量在被调用期间只会被更新一次。所以在一个工作函数内,nitems——read/writen将会引用工作函数开始时的数据流状态。因此我们必须将这个值添加到数据流当中的相对偏移量。因此如果我们在所有输出项目上对i进行迭代,我们将会把流标签写入到相对于第0个输出端口的nitems_written(0)+i号输出端口。

Stream Tags API(流标签API)

The stream tags API is split into two parts: adding tags to a stream, and getting tags from a stream. Note that the functions described below are only meant to be accessed within a call to general_work/work. While they can be called at other points in time by a block, the behavior outside of work is undefined without exact knowledge of the item counts in the buffers.
流标签API被分为两个部分:向流中增加标签以及从流中获取标签。注意下面表述的函数只能在对general_work/work的调用中访问。尽管它们可以在其他时间点被一个块调用,但是在不准确了解缓冲区项目数量的情况下工作区之外的行为是未被定义的。

Adding a Tag to a Stream(向流中增加一个标签)

We add a tag to a particular output stream of the block using:
我们通过下面的方法向块中的特定的输出流增加一个标签:

gr::block::add_item_tag: Adds an item tag to a particular output port using a gr::tag_t data type or by specifying the tag values.
gr::block::add_item_tag:通过一个gr::tag_t数据类型或者特定的标签值添加一个项目标签到特定的输出端口。

We can output them to multiple output streams if we want, but to do so means calling this function once for each port. This function can be provided with a gr::tag_t data type, or each value of the tag can be explicitly given.
如果有需要,我们可以将它们输出到多个输出流中,但是这样做意味着对每个端口都要调用一次这个函数。这个函数可以提供一个gr::tag_t数据类型或者标签的每个值都能明确的给出。

Again, a tag is defined as:
同样,标签定义为:

offset: The offset, in absolute item time, of the tag in the data stream.(标签在数据流中的绝对项目时间偏移量)
key: the PMT symbol identifying the type of tag.(代表标签类型的PMT符号)
value: the PMT holding the data of the tag.(储存标签数据的PMT对象)
srcid: (optional) the PMT symbol identifying the block which created the tag.((可选)标记创建标签的块的PMT符号)

We can create a gr::tag_t structure to hold all of the above information of a tag, which is probably the easiest/best way to do it. The gr::tag_t struct is defined as having the same members as in the above list. To add a gr::tag_t tag to a stream, use the function:
我们可以创建一个gr::tag_t结构去储存一个标签的以上的所有信息,这可能也是实现这个操作的最容易也是最好的方法。gr::tag_t结构里面有包括上方列表内同样的成员。通过下方的函数;哎吧一个gr::tag_t标签添加到一个流中去:

 void add_item_tag(unsigned int which_output, const tag_t &tag);

The secondary API allows us to create a tag by explicitly listing all of the tag information in the function call:
第二个接口允许我们去创建一个标签通过调用下面的函数以及明确一系列的标签参数信息:

 void add_item_tag(unsigned int which_output,
                   uint64_t abs_offset,
                   const pmt::pmt_t &key,
                   const pmt::pmt_t &value,
                   const pmt::pmt_t &srcid=pmt::PMT_F);

In Python, we can add a tag to a stream using one of the following:
在python中我们通过如下的方法来向流中增加一个标签:

add_item_tag(which_output, abs_offset, key, value)
add_item_tag(which_output, abs_offset, key, value, srcid)

Note that the key and value are both PMTs. To create a string type PMT you can use pmt.intern(“example_key”).
注意key和value参数都是PMT对象。创建一个PMT类型的字符串可以使用函数pmt.intern(“example_key”)来实现。

Consider the following flowgraph as an example. We will have an embedded python block insert stream tags at random intervals, and view these tags on the QT GUI Time sink.
考虑下面一个的一个流图实例。我们将在随机间隔内向一个嵌入式python块插入标签,随后我们可以在QT GUI Time sink中查看到标签。

Add the following code into the embedded python block. This code outputs the same signal as in the input, except with tags on randomly selected input samples:
将下面的代码加入到嵌入式python块中。除了随机选择的输入样例,此代码输入与输出有着相同的信号:

 import numpy as np
 from gnuradio import gr
 import pmt
 
 class blk(gr.sync_block):
     def __init__(self):
         gr.sync_block.__init__(
             self,
             name='Embedded Python Block',
             in_sig=[np.float32],
             out_sig=[np.float32]
         )
 
     def work(self, input_items, output_items):
         for indx, sample in enumerate(input_items[0]): # Enumerate through the input samples in port in0
             if np.random.rand() > 0.95: # 5% chance this sample is chosen
                 key = pmt.intern("example_key")
                 value = pmt.intern("example_value")
                 self.add_item_tag(0, # Write to output port 0
                          self.nitems_written(0) + indx, # Index of the tag in absolute terms
                          key, # Key of the tag
                          value # Value of the tag
                 )
                 # note: (self.nitems_written(0) + indx) is our current sample, in absolute time
         output_items[0][:] = input_items[0] # copy input to output
         return len(output_items[0])

You may expect an output similar to the screenshot of the time sink below.
你可能得到一个类似下面截图中所示的时间接收器的输出:

Getting tags from a Stream(从流中获取标签)

To get tags from a particular input stream, we have two functions we can use:
我们可以从下面两种函数来从特定的输出流中得到标签:

gr::block::get_tags_in_range: Gets all tags from a particular input port between a certain range of items (in absolute item time).
gr::block::get_tags_in_window: Gets all tags from a particular input port between a certain range of items (in relative item time within the work function).

The difference between these functions is working in absolute item time versus relative item time. Both of these pass back vectors of gr::tag_t, and they both allow specifying a particular key (as a PMT symbol) to filter against (or the fifth argument can be left out to search for all keys). Filtering for a certain key reduces the effort inside the work function for getting the right tag’s data.
这两个函数的不同之处在于它们工作在绝对项目时间还是相对项目时间。它们都返回一个gr::tag_t向量,并且它们都允许指定一个特定的键值(作为一个PMT对象)进行过滤(或者省略第五个参数来搜索所有的键)。对一个确定的键值进行过滤可以减少在工作函数内得到正确的标签数据的花费。

For example, this call just returns any tags between the given range of items:
例如,此次调用只会返回指定范围内的所有标签:

 void get_tags_in_range(std::vector<tag_t> &v,
                        无符号整数 which_input,
                        uint64_t abs_start,
                        uint64_t abs_end);

Adding a fifth argument to this function allows us to filter on the key key.
增加第五个参数允许我们过滤键值key:

 void get_tags_in_range(std::vector<tag_t> &v,
                        unsigned int which_input,
                        uint64_t abs_start,
                        uint64_t abs_end,
                        const pmt::pmt_t &key);

In Python, the main difference from the C++ function is that instead of having the first argument be a vector where the tags are stored, the Python version just returns a list of tags. We would use it like this:
在python中,与c++函数的主要不同之处在于它只是放回一个标签的列表,而不是需要有第一个作为一个向量的参数来存放标签。使用方法如下:

 def work(self, input_items, output_items):
     ....
     tags = self.get_tags_in_window(which_input, rel_start, rel_end)
     ....

If you want to grab all tags on the samples currently being processed by work(), on input port 0, here’s a minimal example of doing that:
如果你想在端口0上获取当前work()函数处理的样本的所有标签,这里有一个很简单的例子:

 import numpy as np
 from gnuradio import gr
 import pmt
 
 class blk(gr.sync_block):
     def __init__(self):
         gr.sync_block.__init__(self,name='Read Tags', in_sig=[np.float32], out_sig=None)
 
     def work(self, input_items, output_items):
         tags = self.get_tags_in_window(0, 0, len(input_items[0]))
         for tag in tags:
             key = pmt.to_python(tag.key) # convert from PMT to python string
             value = pmt.to_python(tag.value) # Note that the type(value) can be several things, it depends what PMT type it was
             print 'key:', key
             print 'value:', value, type(value)
             print ''
         return len(input_items[0])

Tag Propagation(标签传播)

We now know how to add tags to streams, and how to read them. But what happens to tags after they were read? And what happens to tags that aren’t used? After all, there are many blocks that don’t care about tags at all.
我们现在知道如何向流中添加标签以及如何从流中获取标签。但是在它们被读取之后会发生什么呢?以没有被使用到的标签又会发生什么呢?毕竟有很多块现在不关注标签。

The answer is: It depends on the tag propagation policy of a block what happens to tags that enter it.
There are three policies to choose from:
答案就是:这取决于块与块之间的标签传输方式,在标签进入块之后会发生什么。下面有三种传输方式:

All-to-All: all tags from any input port are replicated to all output ports
One-to-One: tags from input port i are only copied to output port i (depends on num inputs = num outputs).
Dont: Does not propagate tags. Tags are either stopped here or the work function recreates them in some manner.

The default behavior of a block is the ‘All-to-All’ method of propagation.
一个块的默认的传输方式是多对多传输。

We generally set the tag propagation policy in the block’s constructor using @set_tag_propagation_policy
我们一般通过@set_tag_propagation_policy来设置块的传输策略。

void set_tag_propagation_policy(tag_propagation_policy_t p);

See the gr::block::tag_propagation_policy_t documentation for details on this enum type.
有关此类型的详细传输信息请参考gr::block::tag_propagation_policy_t文档。

When the tag propagation policy is set to TPP_ALL_TO_ALL or TPP_ONE_TO_ONE, the GNU Radio scheduler uses any information available to figure out which output item corresponds to which input item. The block may read them and add new tags, but existing tags are automatically moved downstream in a manner deemed appropriate.
当标签传输方式设置为多对多、一对一时,GNU Radio调度程序将会使用任何的有效信息来确定哪个输入项对应那个输出项。模块会读取它们并且添加新的标签,但是现有的标签会以适当的方式向下传输。

When a tag is propagated through a block that has a rate change, the item’s offset in the data stream will change. The scheduler uses the block’s gr::block::relative_rate concept to perform the update on the tag’s offset value. The relative rate of a block determines the relationship between the input rate and output rate. Decimators that decimate by a factor of D have a relative rate of 1/D.
当一个标签在一个块中的传输速度不断改变时,项目在数据流中的偏移量将会发生变化。调度器使用块中的gr::block::relative_rate函数来更新标签的偏移量。块的相对速率决定了输入速率与输出速率之间的关系。抽取因子为D的抽取器的速率为1/D。

Synchronous blocks (gr::sync_block), decimators (gr::sync_decimator), and interpolators (gr::sync_interpolator) all have pre-defined and well-understood relative rates. A standard gr::block has a default relative rate of 1.0, but this must be set if it does not work this way. Often, we use a gr::block because we have no pre-conceived notion of the number of input to output items. If it is important to pass tags through these blocks that respect the change in item value, we would have to use the TPP_DONT tag propagation policy and handle the propagation internally.
同步块(gr::sync_block)、抽取器(gr::sync_decimator)和插值器(gr::sync_interpolator)都有预定义并且容易理解相对速率。一个标准的块有一个默认的相对速率为1.0,但是如果在这种条件下不工作就必须得重新设置。通常我们使用块是由于我们不能提前知道有多少项目数量从输入到输出。如果通过块来传输代表项目值的改变的标签很重要,我们将使用TPP_DONT标签传输策略并在内部处理内部。

In no case is the value of the tag modified when propagating through a block. This becomes relevant when using Tagged Stream Blocks.
在通过块进行传输时标签的值的变化不可能发生改变。这使得在通过标签流块传输时它们之间是有联系的。

As an example, consider an interpolating block. See the following flow graph:
举一个例子,考虑一个插值快。来看一下下面的流图:


As you can tell, we produce tags on every 10th sample, and then pass them through a block that repeats every sample 100 times. Tags do not get repeated with the standard tag propagation policies (after all, they’re not tag manipulation policies), so the scheduler takes care that every tag is put on the output item that corresponds to the input item it was on before. Here, the scheduler makes an educated guess and puts the tag on the first of 100 items.
正如你所看到的,我们美10个样例产生一个标签,并且在通过一个块传输时重复传输一个样例100次。在标准的传输策略下,标签没有重复传输(毕竟,它们不是标签控制策略),所以带调度器会注意把每个标签放在与之前输入项对应的输出项上。在这里,调度器会做一个有根据的猜测并把标签放在100个项目的首个位置上。

Note: We can’t use one QT GUI Time Sink for both signals here, because they are running at a different rate. Note the time difference on the x-axis!
注意:我们不能对相同的信号使用同一个QT GUI Time Sink,因为它们可能不是按照相同的速率运行的。注意x轴上的时差。

On decimating blocks, the behavior is similar. Consider this very simple flow graph, and the position of the samples:
在抽取块时,操作是类似的。考虑下面一个非常简单的流图以及每个样例的位置


We can see that no tags are destroyed, and tags are indeed spaced at one-tenth of the original spacing of 100 items. Of course, the actual item that was passed through the block might be destroyed, or modified (think of a decimating FIR filter).
我们没有标签被破坏,标签确实是原来100个项目间隔的1/10,当然,通过块的实际项目可能会被破坏或者修改(想想FIR滤波器)。

In fact, this works with any rate-changing block. Note that there are cases where the relation of tag positions of in- and output are ambiguous, the GNU Radio scheduler will then try and get as close as possible.
事实上, 这适用于任何速度改变的块。注意在某些情况下输入和输出标签的位置关系不明确,GNU Radio调度程序将会尽量让它们更靠近。

Here’s another interesting example: Consider this flow graph, which has a delay block, and the position of the tags after it:
这里有另一个有趣的样例:在下面的流图中有一个延迟的块,以及它后面标签的位置:


Before the delay block, tags were positioned at the beginning of the ramp. After the delay, they’re still in the same position! Would we inspect the source code of the delay block, we’d find that there is absolutely no tag handling code. Instead, the block declares a delay to the scheduler, which then propagates tags with this delay.
在延迟的块之前,标签位于斜坡的开始处。在延迟之后,它们同样在同样的位置!如果我们检查延迟块的源代码,我们会发现里面根本没有标签处理代码。相反,块向调度程序声明了一个延迟,然后调度程序按照延迟传输标签。

Using these mechanisms, we can let GNU Radio handle tag propagation for a large set of cases. For specialized or corner cases, there is no option than to set the tag propagation policy to TPP_DONT and manually propagate tags (actually, there’s another way: Say we want most tags to propagate normally, but a select few should be treated differently. We can use remove_item_tag() (DEPRECATED. Will be removed in 3.8.) to remove these tags from the input; they will then not be propagated any more even if the tag propagation policy is set to something other than TPP_DONT. But that’s more advanced use and will not be elaborated on here).
通过这些机制,我们可以让GNU Radio处理大量案例中的标签传输。对于特殊或者极端情况,除了将标签传输策略设置为TPP_DONT和手动传输以外没有其他的选择(实际上,还有另一种方法:假如我们想让大多数标签正常传输,少量的标签被特殊对待的话,我们可以使用remove_item_tag()函数(已弃用,将在3.8中删除)从输入中删除这些标签。这样一来,即使标签传输策略不设置为TPP_DONT,这些标签也不会在被传输。但是这是更高级的用法,在这里不会详细说明)

Notes on How to Use Tags(标签使用注意事项)

Tags can be very useful to an application, and their use is spreading. USRP sources generate tag information on the time, sample rate, and frequency of the board if anything changes. We have a meta data file source/sink (see Metadata_Information) that use tags to store information about the data stream. But there are things to think about when using tags in a block.
标签对于一个应用程序来说非常有用,并且使用范围正在扩大。如果有任何改变,USRP源会产生时间、样本率以及电路板频率的标签信息。我们的元数据文件输入/输出(参考Metadata_Information)都是用标签储存数据流的信息。

First, when tags are not being used, there is almost no effect on the scheduler. However, when we use tags, we add overhead by getting and extracting tags from a data stream. We also use overhead in propagating the tags. For each tag, each block must copy a vector of tags from the output port(s) of one block to the input port(s) of the next block(s). These copy operations can add up.
首先,当标签没有被使用,调度程序几乎不会受到任何影响。然而,当我们使用标签的时候,我们从数据流中添加和获取标签时增加了开销。在传输标签时也会增加开销。对于每个标签,每个块必须从上一个块的输出端复制一个标签向量到另一个块的输入端。这些复制操作可以被叠加起来。

The key is to minimize the use of tags. Use them when and only when necessary and try to provide some control over how tags are generated to control their frequency. A good example is the USRP source, which generates a time tag. If it generated a tag with every sample, we would have thousands of tags per second, which would add a significant amount of overhead. This is because if we started at time t0 at sample rate sr, then after N samples, we know that we are now at time t0 + N/sr. So continuously producing new tags adds no information.
关键是减少标签的使用。在不必要的时候尽量不使用标签同时尝试通过控制标签的生成来控制使用它们的频率。USRP源就是一个非常好的例子,它生成一个时间标签。如果对于每个样本都生成一个标签,那么每秒钟将会产生数以千计的标签,这会产生极大的开销。这是因为如果我们在t0时刻以sr的采样率开始,那么在N个样本后,我们可以知道我们现在的时间为t0+N/sr。因此增加的新标签并没有增加任何信息。

The main issue we need to deal with in the above situation is when there is a discontinuity in the packets received from the USRP. Since we have no way of knowing in the flowgraph how many samples were potentially lost, we have lost track of the timing information. The USRP driver recognizes when packets have been dropped and uses this to queue another tag, which allows us to resync. Likewise, any point the sample rate or frequency changes, a new tag is issued.
现在主要的问题是我们需要处理的是当我们从USRP中接收得到的包内是不连续的情形。因为我们没法知道在流图中有多少样例可能丢失了,因此我们无法跟踪时序信息。USRP驱动知道包何时被丢弃并通过它来等待另一个标签,这又让我们可以实现同步。 同样,任何一个 采样率或者频率发生任何变化时,一个新的标签将产生。

Example Flowgraph(流图样例)

Let’s have a look at a simple example:
让我们来看看下面这个例子:

In this flow graph, we have two sources: A sinusoid and a tag strobe. A tag strobe is a block that will output a constant tag, in this case, on every 1000th item (the actual value of the items is always zero). Those sources get added up. The signal after the adder is identical to the sine wave we produced, because we are always adding zeros. However, the tags stay attached to the same position as they were coming from the tag strobe! This means every 1000th sample of the sinusoid now has a tag. The QT scope can display tags, and even trigger on them.
在这个流图中,我们又两个源:一个正弦曲线和标签频闪。一个标签屏闪意味着一个块将要输出一个连续的标签,在这种情况下,在每1000个项目中(项目的实际值可能时0)。这些源被加起来。加法器之后的信号与我们产生的正弦波完全相同,因为我们总是在添加0。然而这些标签仍然被添加在来自标签频闪相同的位置,这意味着每1000个正弦曲线的样本现在都有一个标签。QT范围内可以显示标签,甚至可以触发它们。

We now have a mechanism to randomly attach any metadata to specific items. There are several blocks that use tags. One of them is the UHD Sink block, the driver used for transmitting with USRP devices. It will react to tags with certain keys, one of them being tx_freq, which can be used to set the transmit frequency of a USRP while streaming.
我们现在有一个机制将任何的元数据添加到特定的项目。这里有几个有几个使用标签的块。它们其中有一个是UHD输入块。它是用于与USRP装置进行传输的驱动。它将对带有某些键的标签做出反应,其中之一是tx_freq,它能被用来设置USRP数据流的传输速率。

Adding tags to the QPSK demodulator(向QPSK解调器添加标签)

Going back to our QPSK demodulation example, we might want to add a feature to tell downstream blocks that the demodulation is not going well. Remember the output of our block is always hard-decision, and we have to output something. So we could use tags to notify that the input is not well formed, and that the output is not reliable.
回到我们的QPSK解调示例,我们可能想要增加一个特点去告诉下游的块当前的解调并不是很顺利。记住我们的输出块总是执行的硬策略,我们必须输出一些东西。所以我们可以使用标签去提示输入格式不准确以及输出也不正确。

As a failure criterion, we discuss the case where the input amplitude is too small, say smaller than 0.01. When the amplitude drops below this value, we output one tag. Another tag is only sent when the amplitude has recovered, and falls back below the threshold. We extend our work function like this:
作为一个失效准则,我们来看看输入幅度太小的案例,比如小于0.1的情况。当幅度小于这个值的时候,我们输出一个标签。当幅度重新恢复正常值或者又掉下去的时候才会产生另一个标签。我们像下面这样来扩展我们的工作函数:

if (std::abs(in[i]) < 0.01 and not d_low_ampl_state) {
    add_item_tag(0, // Port number
                 nitems_written(0) + i, // Offset
                 pmt::mp("amplitude_warning"), // Key
                 pmt::from_double(std::abs(in[i])) // Value
    );
    d_low_ampl_state = true;
}
else if (std::abs(in[i]) >= 0.01 and d_low_ampl_state) {
    add_item_tag(0, // Port number
        nitems_written(0) + i, // Offset
        pmt::mp("amplitude_recovered"), // Key
        pmt::PMT_T // Value
    );
    d_low_ampl_state = false; // Reset state
}

In Python, the code would look like this (assuming we have a member of our block class called d_low_ampl_state):
python代码像下面这样实现(假设我们有一个叫做d_low_ampl_state的块类成员):

# The vector 'in' is called 'in0' here because 'in' is a Python keyword
if abs(in0[i]) < 0.01 and not d_low_ampl_state:
    self.add_item_tag(0, # Port number
                 self.nitems_written(0) + i, # Offset
                 pmt.intern("amplitude_warning"), # Key
                 pmt.from_double(numpy.double(abs(in0[i]))) # Value
                 # Note: We need to explicitly create a 'double' here,
                 # because in0[i] is an explicit 32-bit float here
    )
    self.d_low_ampl_state = True
elif abs(in0[i]) >= 0.01 and d_low_ampl_state:
    self.add_item_tag(0, # Port number
                 self.nitems_written(0) + i, # Offset
                 pmt.intern("amplitude_recovered"), # Key
                 pmt.PMT_T # Value
    )
    self.d_low_ampl_state = False; // Reset state

We can also create a tag data type tag_t and directly pass this along:
我们也可以创建一个标签数据类型tag_t并且直接像下面这样传递它:

  if (std::abs(in[i]) < 0.01 and not d_low_ampl_state) {
    tag_t tag;
    tag.offset = nitems_written(0) + i;
    tag.key = pmt::mp("amplitude_warning");
    tag.value = pmt::from_double(std::abs(in[i]));
    add_item_tag(0, tag);
    d_low_ampl_state = true;
}

Here’s a flow graph that uses the tagged version of the demodulator. We input 20 valid QPSK symbols, then 10 zeros. Since the output of this block is always either 0, 1, 2 or 3, we normally have no way to see if the input was not clearly one of these values.
这是一个使用解调器标签版本的流图。我们输入20个有效的QPSK样本,然后输入10个0。由于块的输出值一直是0,1,2,3,所以我们一般没有办法去判断这些输入这些输入的确不是这些值。

Here’s the output. You can see we have tags on those values which were not from a valid QPSK symbol, but from something unreliable.
这里有输出。可以看到有一些有一些标签值不是来自有效的QPSK样例,而是一些不可靠的东西。

Use case: FIR filters(用例:FIR滤波器)

Assume we have a block that is actually an FIR filter. We want to let GNU Radio handle the tag propagation. How do we configure the block?
假设我们有一个实际是FIR滤波器的块。我们想用GNU Radio来处理标签的传输。我们应该如何配置块呢?

Now, an FIR filter has one input and one output. So, it doesn’t matter if we set the propagation policy to TPP_ALL_TO_ALL or TPP_ONE_TO_ONE, and we can leave it as the default. The items going in and those coming out are different, so how do we match input to output? Since we want to preserve the timing of the tag position, we need to use the filter’s group delay as a delay for tags (which for this symmetric FIR filter is (N-1)/2, where N is the number of filter taps). Finally, we might be interpolating, decimating or both (say, for sample rate changes) and we need to tell the scheduler about this as well.
现在,一个滤波器有一个输入和一个输出。因此,无论我们把传输策略设置为多对多还是一对一并不重要,我们还可以将其设置为默认值。输入输出的项目是不同的,那么我们要怎样去匹配这些输入输出呢?由于我们想保留标签位置的时序我们要使用滤波器组的延迟作为标签的延迟(对于对称的FIR滤波器来说是(N-1)/2,N是滤波器头的数量)。最后,我们肯进行插值,抽取或者都有(例如,对于采样率的变化),同时我们还要告知调度程序这一情况。

以上是关于gnu radio学习Stream Tags流标签详解的主要内容,如果未能解决你的问题,请参考以下文章

如何减慢 GNU Radio 中的文件源?

Java 学习笔记 - IO篇:常见的流Stream通道Channl以及相互关系

GNU Radio: Overview of the GNU Radio Scheduler

HTML - 学习笔记 - 点击文字选中 radio

[SDR] GNU Radio 系列教程(十四) —— GNU Radio 低阶到高阶用法的分水岭 ZMQ 的使用详解

GNU Radio Radar Toolbox