Metadata, JSON output and Python callbacks¶
Where results live¶
| Data | Storage | Access |
|---|---|---|
| Boxes, classes, tracking IDs, classifications, keypoints | Standard GstAnalyticsRelationMeta (object-detection, classification, tracking, group and keypoint mtds) |
gst_buffer_get_analytics_relation_meta, as with DL Streamer |
| Instance masks, semantic masks, raw output tensors | AmdAnalysisMeta (amdva/tensor_meta.hpp), GPU memory |
C++ SDK; never copied to the CPU implicitly |
| Frame identity (source, generation, sequence, PTS), inverse image geometry | AmdAnalysisMeta |
C++ SDK |
| Custom per-frame JSON | AmdAnalysisMeta::custom_json (64 KiB limit) |
amdpython, serialized under custom |
GstAnalyticsGroupMtd and GstAnalyticsKeypointMtd need the GStreamer
gst-plugins-bad patch shipped in patches/ (scripts/bootstrap.py applies it).
JSON schema 1¶
amdmetapublish, amdpython and the SDK produce the same record
(analysis_json, src/gst/publishing.cpp). Image buffers, masks and GPU
pointers are never serialized.
{
"schema": 1,
"source": "cam0",
"generation": 3,
"sequence": 1042,
"pts_ns": 34733333333,
"background": false,
"objects": [
{
"label": "person", "confidence": 0.91,
"x": 412, "y": 160, "width": 88, "height": 240,
"tracking_id": 17,
"classifications": [{"label": "adult", "confidence": 0.8}],
"keypoints": [{"x": 440, "y": 180, "confidence": 0.9}]
}
],
"classifications": [{"label": "indoor", "confidence": 0.7}],
"tensors": [
{"name": "mask", "storage": "gpu", "dtype": "float32", "device": 0,
"batch_index": 0, "shape": [3, 160, 160]}
],
"custom": {"people": 1}
}
- Boxes are integer pixels in the decoded frame.
tracking_idappears only after a tracker (gvatrack) ran.classificationson an object come fromamdclassifyin ROI mode; the top-level array holds full-frame classifications.source,generation,sequenceare set by applications (the controller sets them per channel); in plain pipelinessourceis empty.customappears only whenamdpythonattached a dict.
Comparison with other converters: gvametaconvert emits DL Streamer's own JSON
(resolution, objects[].detection.bounding_box normalized, tensors), and
DeepStream's nvmsgconv emits the Schema 1.0 object/videoPath layout. Neither
is produced here. If downstream consumers expect one of those, either keep
gvametaconvert in the pipeline (it reads GstAnalytics) or reshape the record in
an amdpython callback.
amdpython¶
# metadata_callback.py
def process(frame):
people = [o for o in frame['objects'] if o['label'] == 'person']
return {'people': len(people),
'tracked_people': [o['tracking_id'] for o in people if 'tracking_id' in o]}
... ! amdpython module=/path/metadata_callback.py function=process ! ...
| Return value | Effect |
|---|---|
None or True |
pass the frame unchanged |
False |
drop the frame |
JSON-serializable dict |
attached as custom (at most 64 KiB) |
| exception | bus error |
The callback runs on the streaming thread under the Python GIL. It sees the dictionary above and nothing else: no pixels, no device pointers, no GStreamer objects. That is deliberate, so Python can never trigger a copy of the frame out of GPU memory. Slow callbacks reduce throughput; keep them short.
If you used gvapython with VideoFrame/RegionOfInterest objects, move the
logic to the dictionary or to a C++ pad probe.
amdmetapublish¶
| Property | Default | Notes |
|---|---|---|
method |
none |
none, file, mqtt, kafka |
file-path |
JSON lines, appended | |
address |
MQTT tcp://host:1883, Kafka host:9092 |
|
topic |
amdva/analytics |
|
queue-limit |
256 | records; full queues discard the oldest waiting record |
delivered, dropped, errors |
read-only | live counters |
Publication is asynchronous and bounded: broker outages never stall video. MQTT
uses QoS 1 and Kafka uses delivery callbacks; this is best-effort delivery, not
exactly-once storage. To keep DL Streamer's gvametapublish instead, use the
vendored element with gvametaconvert in front of it. A build with
AMDVA_WITH_MQTT=OFF or AMDVA_WITH_KAFKA=OFF rejects that method when the
publisher starts, and omits the matching gvametapublish module; with
AMDVA_WITH_PYTHON=OFF the amdpython element is not registered.
C++ SDK¶
#include <amdva/publishing.hpp> // analysis_json(), PublicationOptions, ResultPublisher
#include <amdva/tensor_meta.hpp> // AmdAnalysisMeta, AnalysisMerge
Attach your own data by adding a GstMeta/GstAnalytics entry in a pad probe;
use get_analysis_meta(buffer) to reach the AMD payload and
AnalysisMerge semantics (amd-analytics-merge) when a custom element merges
branches. Copies of AmdAnalysisMeta clone the attachment list and share GPU
storage, so buffers may outlive the model that produced them.
Persisting results with recordings¶
amdva::streaming::Catalog (amdva/streaming/catalog.hpp) stores analysis
next to the recordings it describes and deletes it with them.
RecordedAnalysis carries one frame: channel, session, run, time, validity, and a
list of RecordedObject (normalized box, class id, person and track IDs).
Optional detail is stored only when set: RecordedObject::label,
keypoints (normalized x, y, confidence), attributes_json (any JSON value, for
example classifications) and the frame-level RecordedAnalysis::custom_json.
Databases created before this detail existed gain the nullable columns on open.
AnalysisHooks hooks; // optional queue metrics
hooks.on_saved = [](double ms, double wait_ms, size_t frames) { /* ... */ };
catalog.analysis_hooks(std::move(hooks));
catalog.enqueue_analysis(std::move(frame)); // bounded, written in batches
auto frames = catalog.analysis("cam0", from_ms, to_ms); // only for live recordings