GNU Radio Companion
Verarbeitung im Block
Wie ein GNU-Radio-Block Eingangsdaten verarbeitet — von Input über die Verarbeitungsfunktion bis zum Output.
In GNUSDR werden die Daten in Blöcken verarbeitet. In einem GNU-Radio-Flowgraph besteht jeder Block aus einem oder mehreren Eingängen und Ausgängen, die Datenströme (Samples) weiterleiten. Der Scheduler sorgt dafür, dass die Datenflüsse zwischen den Blöcken koordiniert werden. Dazu überwacht er kontinuierlich, ob ein Block ausreichend Daten erhalten hat, um verarbeitet zu werden, und ob genug Platz im Ausgabepuffer ist, um die verarbeiteten Daten weiterzugeben. Er stellt sicher, dass ein Block erst dann ausgeführt wird, wenn ausreichend Eingabedaten zur Verfügung stehen, und er koordiniert die Übergabe der Daten vom Ausgabepuffer in den Eingabepuffer eines nachfolgenden Blocks. Die Verarbeitung der Daten in den Blöcken erfolgt möglichst parallel, um die Leistungsfähigkeit der Rechnerinfrastruktur optimal auszunutzen.
Jeder Block läuft dabei typischerweise in einem eigenen Thread, wodurch mehrere Blöcke parallel auf mehreren CPU-Kernen arbeiten können. Eine optimale Lastverteilung ergibt sich daraus aber nicht automatisch: Blöcke, die in einer engen Producer-Consumer-Kette hängen (Block B wartet auf die Ausgabe von Block A), sind durch diese Datenabhängigkeit im Parallelitätsgewinn limitiert — echte Parallelität entsteht vor allem zwischen unabhängigen Zweigen eines Flowgraphs.
Input
Wie oben beschrieben, erhält ein Block als Input einen Buffer mit Daten, die einen Teil des Signals abbilden. Blöcke können dabei auch mehrere Inputs haben.

Die Variablentypen sind in GRC farblich gekennzeichnet. Für die Verarbeitung der IQ-Signale kommt der komplexe Datentyp zum Einsatz. Float und Integer werden je nach Anwendungsfall genutzt.
Zur Konvertierung von Datentypen (z. B. Complex to Float) sind eigene Blöcke vorhanden.
Verarbeitung
Die Hauptaufgabe des Blocks besteht darin, eine bestimmte Signalverarbeitungsfunktion auf die eingehenden Daten anzuwenden.
Beispiele:
- Filterung
- Modulation/Demodulation
- Transformation, z.B. eine Fast Fourier Transform
- Decodierung
- Mathematische Operationen
- Benutzerdefinierte Aufgaben
Output
Der Output eines Blocks besteht aus den verarbeiteten Datenströmen, die nach der Anwendung der Signalverarbeitungsoperation zur Verfügung stehen. Die Ausgabe kann ebenfalls in verschiedenen Formen vorliegen, je nach Art des Blocks:
- Einzelner Datenstrom: Nach der Filterung oder Modulation wird ein einzelner Datenstrom (beispielsweise in Form von komplexen Zahlen) ausgegeben.
- Mehrere Datenströme: Manche Blöcke geben mehrere Datenströme aus, z.B. nach einem Splitter oder Multiplexer, um verschiedene Signalanteile getrennt zu verarbeiten.
- Datentypen: Wie beim Input können auch die Ausgabedaten ganzzahlig, gleitkommazahlbasiert oder komplex sein.
Beispiel für die Verarbeitung in einem Block
Der folgende Custom Block zeigt die grundlegenden Funktionen bei der Verarbeitung von Blöcken anhand des intern generierten Python-Codes:
In GRC sieht der Block wie folgt aus:

Der Name des Blocks wird beim Aufruf von Block.__init__ festgelegt: name = 'Embedded Python Block'.
Der example_param wird dem Block automatisch hinzugefügt, wenn er in der init-Funktion definiert wurde: def __init__(self, example_param=1.0).
"""
Embedded Python Blocks:
Each time this file is saved, GRC will instantiate the first class it finds
to get ports and parameters of your block. The arguments to __init__ will
be the parameters. All of them are required to have default values!
"""
import numpy as np
from gnuradio import gr
class blk(gr.sync_block): # other base classes are basic_block, decim_block, interp_block
"""Embedded Python Block example - a simple multiply const"""
def __init__(self, example_param=1.0): # only default arguments here
"""arguments to this function show up as parameters in GRC"""
gr.sync_block.__init__(
self,
name='Embedded Python Block', # will show up in GRC
in_sig=[np.complex64],
out_sig=[np.complex64]
)
# if an attribute with the same name as a parameter is found,
# a callback is registered (properties work, too).
self.example_param = example_param
def work(self, input_items, output_items):
"""example: multiply with constant"""
output_items[0][:] = input_items[0] * self.example_param
return len(output_items[0])
"""
Embedded Python Blocks:
Each time this file is saved, GRC will instantiate the first class it finds
to get ports and parameters of your block. The arguments to __init__ will
be the parameters. All of them are required to have default values!
"""
import numpy as np
from gnuradio import gr
class blk(gr.sync_block): # other base classes are basic_block, decim_block, interp_block
"""Embedded Python Block example - a simple multiply const"""
def __init__(self, example_param=1.0): # only default arguments here
"""arguments to this function show up as parameters in GRC"""
gr.sync_block.__init__(
self,
name='Embedded Python Block', # will show up in GRC
in_sig=[np.complex64],
out_sig=[np.complex64]
)
# if an attribute with the same name as a parameter is found,
# a callback is registered (properties work, too).
self.example_param = example_param
def work(self, input_items, output_items):
"""example: multiply with constant"""
output_items[0][:] = input_items[0] * self.example_param
return len(output_items[0])
In der Init-Funktion werden die Input- und Output-Daten definiert. Hier wird ein Array von komplexen Zahlen als Input übergeben, der Output besteht ebenfalls aus einem komplexen Array.
Der Verarbeitungsschritt multipliziert jedes Element aus dem Input-Array mit einer festen Konstante und befüllt das Output-Array.
input_items und output_items werden für jeden Ein- und Ausgang separat übergeben. input_items[0] enthält deshalb den Input-Array für den ersten Eingang.
Wie unterschiedliche GRC-Ports Daten strukturieren (Stream vs. Vector) und was das für Signal Source und Throttle bedeutet, ist im Artikel Stream vs. Vector erklärt. Neben den eigentlichen Samples lassen sich einem Datenstrom zusätzlich Metadaten an einzelnen Samples anheften — siehe Stream Tags.
Stream vs. Vector
Die zwei Arten, wie GRC-Ports Daten strukturieren — und was das für FFT & Co. bedeutet.
Stream Tags
Metadaten, die synchron an einzelne Samples eines Datenstroms geheftet werden.
Complex to Mag
Berechnet den Betrag einer komplexen Zahl.
Complex to Mag^2
Berechnet das Quadrat des Betrags für Leistungsschätzung und Energiedetektion.
Frequency Shift
Verschiebt die Frequenz eines Signals um einen festen Wert.
Rotator Block
Dreht die Phasenlage einer Frequenzkomponente im komplexen Zahlenraum.
Root Mean Square (RMS)
Berechnet den Effektivwert eines Signals.
Quadratur Demodulation
Demoduliert Trägerinformation über die Phasenänderung aufeinanderfolgender IQ-Samples.