Files
hailort/hailort/libhailort/bindings/gstreamer/gst-hailo/gsthailosend.cpp
HailoRT-Automation 86bb9c4968 v4.13.0 (#8)
Co-authored-by: HailoRT-Automation <contact@hailo.ai>
2023-03-30 14:42:56 +03:00

344 lines
14 KiB
C++

/*
* Copyright (c) 2021-2022 Hailo Technologies Ltd. All rights reserved.
* Distributed under the LGPL 2.1 license (https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "gsthailosend.hpp"
#include "gsthailonet.hpp"
#include "metadata/hailo_buffer_flag_meta.hpp"
#include <chrono>
#include <iostream>
#include <gst/video/video.h>
#include <gst/video/gstvideofilter.h>
GST_DEBUG_CATEGORY_STATIC(gst_hailosend_debug_category);
#define GST_CAT_DEFAULT gst_hailosend_debug_category
#define RGB_FEATURES_SIZE (3)
#define RGBA_FEATURES_SIZE (4)
#define YUY2_FEATURES_SIZE (2)
#define NV12_FEATURES_SIZE (3)
#define NV21_FEATURES_SIZE (3)
#define I420_FEATURES_SIZE (3)
static void gst_hailosend_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
static void gst_hailosend_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
static GstCaps *gst_hailosend_transform_caps(GstBaseTransform *trans, GstPadDirection direction, GstCaps *caps, GstCaps *filter);
static GstFlowReturn gst_hailosend_transform_frame_ip(GstVideoFilter *filter, GstVideoFrame *frame);
static gboolean gst_hailosend_propose_allocation(GstBaseTransform *trans, GstQuery *decide_query, GstQuery *query);
static GstStateChangeReturn gst_hailosend_change_state(GstElement *element, GstStateChange transition);
enum
{
PROP_0,
PROP_DEBUG
};
G_DEFINE_TYPE(GstHailoSend, gst_hailosend, GST_TYPE_VIDEO_FILTER);
static void gst_hailosend_class_init(GstHailoSendClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
GstBaseTransformClass *base_transform_class = GST_BASE_TRANSFORM_CLASS(klass);
GstVideoFilterClass *video_filter_class = GST_VIDEO_FILTER_CLASS(klass);
/* Setting up pads and setting metadata should be moved to
base_class_init if you intend to subclass this class. */
gst_element_class_add_pad_template(GST_ELEMENT_CLASS(klass),
gst_pad_template_new("src", GST_PAD_SRC, GST_PAD_ALWAYS, gst_caps_from_string(HAILO_VIDEO_CAPS)));
gst_element_class_add_pad_template(GST_ELEMENT_CLASS(klass),
gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_ALWAYS, gst_caps_from_string(HAILO_VIDEO_CAPS)));
gst_element_class_set_static_metadata(GST_ELEMENT_CLASS(klass),
"hailosend element", "Hailo/Filter/Video", "Send RGB/RGBA/YUY2/NV12/NV21/I420 video to HailoRT", PLUGIN_AUTHOR);
element_class->change_state = GST_DEBUG_FUNCPTR(gst_hailosend_change_state);
gobject_class->set_property = gst_hailosend_set_property;
gobject_class->get_property = gst_hailosend_get_property;
g_object_class_install_property (gobject_class, PROP_DEBUG,
g_param_spec_boolean ("debug", "debug", "debug", false,
(GParamFlags)(GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
base_transform_class->transform_caps = GST_DEBUG_FUNCPTR(gst_hailosend_transform_caps);
base_transform_class->propose_allocation = GST_DEBUG_FUNCPTR(gst_hailosend_propose_allocation);
video_filter_class->transform_frame_ip = GST_DEBUG_FUNCPTR(gst_hailosend_transform_frame_ip);
}
Expected<std::unique_ptr<HailoSendImpl>> HailoSendImpl::create(GstHailoSend *element)
{
if (nullptr == element) {
return make_unexpected(HAILO_INVALID_ARGUMENT);
}
auto ptr = make_unique_nothrow<HailoSendImpl>(element);
if (nullptr == ptr) {
return make_unexpected(HAILO_OUT_OF_HOST_MEMORY);
}
return ptr;
}
HailoSendImpl::HailoSendImpl(GstHailoSend *element) : m_element(element), m_hailonet(nullptr), m_props(),
m_batch_size(HAILO_DEFAULT_BATCH_SIZE), m_last_frame_pts(0)
{
GST_DEBUG_CATEGORY_INIT(gst_hailosend_debug_category, "hailosend", 0, "debug category for hailosend element");
}
void HailoSendImpl::set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
GST_DEBUG_OBJECT(m_element, "set_property");
if ((object == nullptr) || (value == nullptr) || (pspec == nullptr)) {
g_error("set_property got null parameter!");
return;
}
switch (property_id) {
case PROP_DEBUG:
m_props.m_debug = g_value_get_boolean(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
}
}
void HailoSendImpl::get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
GST_DEBUG_OBJECT(m_element, "get_property");
if ((object == nullptr) || (value == nullptr) || (pspec == nullptr)) {
g_error("get_property got null parameter!");
return;
}
switch (property_id) {
case PROP_DEBUG:
g_value_set_boolean(value, m_props.m_debug.get());
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
}
}
GstFlowReturn HailoSendImpl::handle_frame(GstVideoFilter */*filter*/, GstVideoFrame *frame)
{
assert(nullptr != frame);
m_last_frame_pts = GST_BUFFER_TIMESTAMP(frame->buffer);
if (!GST_HAILONET(GST_ELEMENT_PARENT(m_element))->impl->is_active()) {
GstHailoBufferFlagMeta *meta = GST_HAILO_BUFFER_FLAG_META_ADD(frame->buffer);
meta->flag = BUFFER_FLAG_SKIP;
return GST_FLOW_OK;
}
guint8 *frame_buffer = reinterpret_cast<guint8*>(GST_VIDEO_FRAME_PLANE_DATA(frame, 0));
hailo_status status = HAILO_UNINITIALIZED;
if (m_props.m_debug.get()) {
std::chrono::duration<double, std::milli> latency;
std::chrono::time_point<std::chrono::system_clock> start_time;
start_time = std::chrono::system_clock::now();
status = write_to_vstreams(frame_buffer, GST_VIDEO_FRAME_SIZE(frame));
latency = std::chrono::system_clock::now() - start_time;
GST_DEBUG("hailosend latency: %f milliseconds", latency.count());
} else {
status = write_to_vstreams(frame_buffer, GST_VIDEO_FRAME_SIZE(frame));
}
if (HAILO_SUCCESS != status) {
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
}
hailo_status HailoSendImpl::write_to_vstreams(void *buf, size_t size)
{
for (auto &in_vstream : m_input_vstreams) {
auto status = in_vstream.write(MemoryView(buf, size));
GST_CHECK_SUCCESS(status, m_element, STREAM, "Failed writing to input vstream %s, status = %d", in_vstream.name().c_str(), status);
}
return HAILO_SUCCESS;
}
uint32_t get_height_by_order(const hailo_vstream_info_t &input_vstream_info)
{
auto original_height = input_vstream_info.shape.height;
switch (input_vstream_info.format.order) {
case HAILO_FORMAT_ORDER_NV12:
case HAILO_FORMAT_ORDER_NV21:
return original_height * 2;
default:
break;
}
return original_height;
}
GstCaps *HailoSendImpl::get_caps(GstBaseTransform */*trans*/, GstPadDirection /*direction*/, GstCaps *caps, GstCaps */*filter*/)
{
GST_DEBUG_OBJECT(m_element, "transform_caps");
if (0 == m_input_vstream_infos.size()) {
// Init here because it is guaranteed that we have a parent element
m_hailonet = GST_HAILONET(GST_ELEMENT_PARENT(m_element));
hailo_status status = m_hailonet->impl->set_hef();
if (HAILO_SUCCESS != status) {
return NULL;
}
}
const gchar *format = nullptr;
switch (m_input_vstream_infos[0].format.order) {
case HAILO_FORMAT_ORDER_RGB4:
case HAILO_FORMAT_ORDER_NHWC:
if (m_input_vstream_infos[0].shape.features == RGBA_FEATURES_SIZE) {
format = "RGBA";
break;
}
/* Fallthrough */
case HAILO_FORMAT_ORDER_NHCW:
case HAILO_FORMAT_ORDER_FCR:
case HAILO_FORMAT_ORDER_F8CR:
format = "RGB";
GST_CHECK(RGB_FEATURES_SIZE == m_input_vstream_infos[0].shape.features, NULL, m_element, STREAM,
"Features of input vstream %s is not %d for RGB format! (features=%d)", m_input_vstream_infos[0].name, RGB_FEATURES_SIZE,
m_input_vstream_infos[0].shape.features);
break;
case HAILO_FORMAT_ORDER_YUY2:
format = "YUY2";
GST_CHECK(YUY2_FEATURES_SIZE == m_input_vstream_infos[0].shape.features, NULL, m_element, STREAM,
"Features of input vstream %s is not %d for YUY2 format! (features=%d)", m_input_vstream_infos[0].name, YUY2_FEATURES_SIZE,
m_input_vstream_infos[0].shape.features);
break;
case HAILO_FORMAT_ORDER_NV12:
format = "NV12";
GST_CHECK(NV12_FEATURES_SIZE == m_input_vstream_infos[0].shape.features, NULL, m_element, STREAM,
"Features of input vstream %s is not %d for NV12 format! (features=%d)", m_input_vstream_infos[0].name, NV12_FEATURES_SIZE,
m_input_vstream_infos[0].shape.features);
break;
case HAILO_FORMAT_ORDER_NV21:
format = "NV21";
GST_CHECK(NV21_FEATURES_SIZE == m_input_vstream_infos[0].shape.features, NULL, m_element, STREAM,
"Features of input vstream %s is not %d for NV21 format! (features=%d)", m_input_vstream_infos[0].name, NV21_FEATURES_SIZE,
m_input_vstream_infos[0].shape.features);
break;
case HAILO_FORMAT_ORDER_I420:
format = "I420";
GST_CHECK(I420_FEATURES_SIZE == m_input_vstream_infos[0].shape.features, NULL, m_element, STREAM,
"Features of input vstream %s is not %d for I420 format! (features=%d)", m_input_vstream_infos[0].name, I420_FEATURES_SIZE,
m_input_vstream_infos[0].shape.features);
break;
default:
GST_ELEMENT_ERROR(m_element, RESOURCE, FAILED,
("Input VStream %s has an unsupported format order! order = %d", m_input_vstream_infos[0].name, m_input_vstream_infos[0].format.order), (NULL));
return NULL;
}
/* filter against set allowed caps on the pad */
GstCaps *new_caps = gst_caps_new_simple("video/x-raw",
"format", G_TYPE_STRING, format,
"width", G_TYPE_INT, m_input_vstream_infos[0].shape.width,
"height", G_TYPE_INT, get_height_by_order(m_input_vstream_infos[0]),
NULL);
auto result = gst_caps_intersect(caps, new_caps);
gst_caps_unref(new_caps);
return result;
}
void HailoSendImpl::set_input_vstream_infos(std::vector<hailo_vstream_info_t> &&input_vstream_infos)
{
m_input_vstream_infos = std::move(input_vstream_infos);
}
void HailoSendImpl::set_input_vstreams(std::vector<InputVStream> &&input_vstreams)
{
m_input_vstreams = std::move(input_vstreams);
}
hailo_status HailoSendImpl::clear_vstreams()
{
return InputVStream::clear(m_input_vstreams);
}
hailo_status HailoSendImpl::abort_vstreams()
{
for (auto& input_vstream : m_input_vstreams) {
auto status = input_vstream.abort();
GST_CHECK_SUCCESS(status, m_element, STREAM, "Failed aborting input vstream %s, status = %d", input_vstream.name().c_str(), status);
}
return HAILO_SUCCESS;
}
static void gst_hailosend_init(GstHailoSend *self)
{
auto hailosend_impl = HailoSendImpl::create(self);
if (!hailosend_impl) {
GST_ELEMENT_ERROR(self, RESOURCE, FAILED, ("Creating hailosend implementation has failed! status = %d", hailosend_impl.status()), (NULL));
return;
}
self->impl = hailosend_impl.release();
}
void gst_hailosend_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
GST_HAILOSEND(object)->impl->set_property(object, property_id, value, pspec);
}
void gst_hailosend_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
GST_HAILOSEND(object)->impl->get_property(object, property_id, value, pspec);
}
static GstFlowReturn gst_hailosend_transform_frame_ip(GstVideoFilter *filter, GstVideoFrame *frame)
{
GST_DEBUG_OBJECT(filter, "transform_frame_ip");
return GST_HAILOSEND(filter)->impl->handle_frame(filter, frame);
}
static GstCaps *gst_hailosend_transform_caps(GstBaseTransform *trans, GstPadDirection direction, GstCaps *caps, GstCaps *filter)
{
return GST_HAILOSEND(trans)->impl->get_caps(trans, direction, caps, filter);
}
static gboolean gst_hailosend_propose_allocation(GstBaseTransform *trans, GstQuery *decide_query, GstQuery *query)
{
if (GST_HAILOSEND(trans)->impl->batch_size() > 1) {
return FALSE;
}
return GST_BASE_TRANSFORM_CLASS(gst_hailosend_parent_class)->propose_allocation(trans, decide_query, query);
}
static GstStateChangeReturn gst_hailosend_change_state(GstElement *element, GstStateChange transition)
{
GstStateChangeReturn ret = GST_ELEMENT_CLASS(gst_hailosend_parent_class)->change_state(element, transition);
if (GST_STATE_CHANGE_FAILURE == ret) {
return ret;
}
if (GST_STATE_CHANGE_READY_TO_NULL == transition) {
auto status = GST_HAILOSEND(element)->impl->abort_vstreams();
GST_CHECK(HAILO_SUCCESS == status, GST_STATE_CHANGE_FAILURE, element, STREAM, "Aborting input vstreams failed, status = %d\n", status);
// Cleanup all of hailosend memory
GST_HAILOSEND(element)->impl.reset();
}
return ret;
}