diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43ad57c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Binaries +Intermediate diff --git a/GStreamer/Content/M_SensorBGRA.uasset b/GStreamer/Content/M_SensorBGRA.uasset new file mode 100644 index 0000000..7b84ec4 Binary files /dev/null and b/GStreamer/Content/M_SensorBGRA.uasset differ diff --git a/GStreamer/Content/M_SensorDepth.uasset b/GStreamer/Content/M_SensorDepth.uasset new file mode 100644 index 0000000..276328f Binary files /dev/null and b/GStreamer/Content/M_SensorDepth.uasset differ diff --git a/GStreamer/GStreamer.uplugin b/GStreamer/GStreamer.uplugin index 57e06f4..7813b89 100644 --- a/GStreamer/GStreamer.uplugin +++ b/GStreamer/GStreamer.uplugin @@ -1,25 +1,29 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "0.0.1", - "FriendlyName": "GStreamer", - "Description": "", - "CreatedBy": "", - "CreatedByURL": "", - "DocsURL": "", - "Category": "Media", - "CanContainContent": true, - "Modules": [ - { - "Name": "GStreamer", - "Type": "Runtime", - "LoadingPhase": "PostEngineInit" - } - ], - "Plugins": [ - { - "Name": "GStreamerLoader", - "Enabled": true - } - ] -} +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "0.0.1", + "FriendlyName": "GStreamer", + "Description": "", + "Category": "Simbotic", + "CreatedBy": "", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "GStreamer", + "Type": "Runtime", + "LoadingPhase": "PostEngineInit" + } + ], + "Plugins": [ + { + "Name": "GStreamerLoader", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/GStreamer/Source/GStreamer/Private/GStreamerModule.cpp b/GStreamer/Source/GStreamer/Private/GStreamerModule.cpp index acd40d0..9cf7e66 100644 --- a/GStreamer/Source/GStreamer/Private/GStreamerModule.cpp +++ b/GStreamer/Source/GStreamer/Private/GStreamerModule.cpp @@ -15,15 +15,11 @@ DEFINE_LOG_CATEGORY(LogGStreamer); static FString GetGstRoot() { - const int32 BufSize = 2048; - TCHAR RootPath[BufSize] = {0}; - - FPlatformMisc::GetEnvironmentVariable(TEXT("GSTREAMER_ROOT_X86_64"), RootPath, BufSize); - if (!RootPath[0]) + FString RootPath = FPlatformMisc::GetEnvironmentVariable(TEXT("GSTREAMER_ROOT_X86_64")); + if (RootPath.IsEmpty()) { - FPlatformMisc::GetEnvironmentVariable(TEXT("GSTREAMER_ROOT"), RootPath, BufSize); + RootPath = FPlatformMisc::GetEnvironmentVariable(TEXT("GSTREAMER_ROOT")); } - return FString(RootPath); } diff --git a/GStreamer/Source/GStreamer/Private/GstAppSinkComponent.cpp b/GStreamer/Source/GStreamer/Private/GstAppSinkComponent.cpp index 30d3c4e..7a29127 100644 --- a/GStreamer/Source/GStreamer/Private/GstAppSinkComponent.cpp +++ b/GStreamer/Source/GStreamer/Private/GstAppSinkComponent.cpp @@ -26,7 +26,7 @@ void UGstAppSinkComponent::CbPipelineStart(IGstPipeline* Pipeline) if (AppSinkEnabled && !AppSinkName.IsEmpty()) { - AppSink = IGstAppSink::CreateInstance(); + AppSink = IGstAppSink::CreateInstance(TCHAR_TO_ANSI(*AppSinkName)); Texture = new FGstTexture(AppSinkName, AppSink, this); AppSink->Connect(Pipeline, TCHAR_TO_ANSI(*AppSinkName), this); } diff --git a/GStreamer/Source/GStreamer/Private/GstAppSinkImpl.cpp b/GStreamer/Source/GStreamer/Private/GstAppSinkImpl.cpp index d1cbbbb..bb6f88a 100644 --- a/GStreamer/Source/GStreamer/Private/GstAppSinkImpl.cpp +++ b/GStreamer/Source/GStreamer/Private/GstAppSinkImpl.cpp @@ -41,16 +41,16 @@ class FGstAppSinkImpl : public IGstAppSink std::mutex m_SampleMx; }; -IGstAppSink* IGstAppSink::CreateInstance() +IGstAppSink* IGstAppSink::CreateInstance(const char *ElementName) { auto Obj = new FGstAppSinkImpl(); - GST_LOG_DBG_A("GstAppSink: CreateInstance %p", Obj); + GST_LOG_DBG_A("GstAppSink: CreateInstance %p %s", Obj, ElementName); return Obj; } void FGstAppSinkImpl::Destroy() { - GST_LOG_DBG_A("GstAppSink: Destroy %p", this); + GST_LOG_DBG_A("GstAppSink: Destroy %p", this, m_Name.c_str()); delete this; } @@ -180,7 +180,7 @@ IGstSample* FGstAppSinkImpl::AllocSample() } } - auto Sample = IGstSample::CreateInstance(); + auto Sample = IGstSample::CreateInstance(nullptr); return Sample; } diff --git a/GStreamer/Source/GStreamer/Private/GstAppSrcComponent.cpp b/GStreamer/Source/GStreamer/Private/GstAppSrcComponent.cpp index 31cf6bc..a9a8b5f 100644 --- a/GStreamer/Source/GStreamer/Private/GstAppSrcComponent.cpp +++ b/GStreamer/Source/GStreamer/Private/GstAppSrcComponent.cpp @@ -7,7 +7,6 @@ UGstAppSrcComponent::UGstAppSrcComponent() { PrimaryComponentTick.bCanEverTick = true; - PrimaryComponentTick.TickInterval = 0.1; } void UGstAppSrcComponent::UninitializeComponent() @@ -15,6 +14,11 @@ void UGstAppSrcComponent::UninitializeComponent() ResetState(); } +void UGstAppSrcComponent::BeginPlay() +{ + Super::BeginPlay(); +} + void UGstAppSrcComponent::ResetState() { if (AppSrc) @@ -28,8 +32,8 @@ void UGstAppSrcComponent::CbPipelineStart(IGstPipeline *Pipeline) if (AppSrcEnabled && !AppSrcName.IsEmpty()) { - AppSrc = IGstAppSrc::CreateInstance(); - AppSrc->Connect(Pipeline, TCHAR_TO_ANSI(*AppSrcName)); + AppSrc = IGstAppSrc::CreateInstance(TCHAR_TO_ANSI(*AppSrcName)); + AppSrc->Connect(Pipeline, TCHAR_TO_ANSI(*AppSrcName), this); } } @@ -38,20 +42,54 @@ void UGstAppSrcComponent::CbPipelineStop() ResetState(); } +void UGstAppSrcComponent::CbGstPushTexture() +{ + NeedsData = true; +} + void UGstAppSrcComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (AppSrc) { AActor *Actor = GetOwner(); - for (FComponentReference ComponentReference : AppSrcCaptures) + USceneCaptureComponent2D *CaptureComponent = Cast(AppSrcCapture.GetComponent(Actor)); + if (CaptureComponent) { - USceneCaptureComponent2D *CaptureComponent = Cast(ComponentReference.GetComponent(Actor)); UTextureRenderTarget2D *TextureTarget = CaptureComponent->TextureTarget; - TArray TextureData; - FTextureRenderTargetResource *TextureResource = TextureTarget->GameThread_GetRenderTargetResource(); - TextureResource->ReadPixels(TextureData); - AppSrc->PushTexture((uint8_t *)TextureData.GetData(), TextureData.Num() * 4); + if (TextureTarget) + { + if (NeedsData) + { + NeedsData = false; + TArray TextureData; + FTextureRenderTargetResource *TextureResource = TextureTarget->GameThread_GetRenderTargetResource(); + TextureResource->ReadPixels(TextureData); + AppSrc->PushTexture((uint8_t *)TextureData.GetData(), TextureData.Num() * 4); + } + } + else if (AppSrc->GetTextureFormat() == EGstTextureFormat::GST_VIDEO_FORMAT_BGRA) + { + UTextureRenderTarget2D *NewRenderTarget2D = NewObject(); + check(NewRenderTarget2D); + NewRenderTarget2D->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8; + NewRenderTarget2D->InitAutoFormat(AppSrc->GetTextureWidth(), AppSrc->GetTextureHeight()); + NewRenderTarget2D->UpdateResourceImmediate(true); + CaptureComponent->TextureTarget = NewRenderTarget2D; + } + else + { + GST_LOG_ERR(TEXT("GstAppSrc: Missing TextureTarget")); + } + } + else + { + GST_LOG_ERR(TEXT("GstAppSrc: AppSrcCapture is not a USceneCaptureComponent2D")); } } } + +void UGstAppSrcComponent::SetKlv(TArray _Klv) +{ + Klv = _Klv; +} diff --git a/GStreamer/Source/GStreamer/Private/GstAppSrcImpl.cpp b/GStreamer/Source/GStreamer/Private/GstAppSrcImpl.cpp index d52c1ab..3ea66b7 100644 --- a/GStreamer/Source/GStreamer/Private/GstAppSrcImpl.cpp +++ b/GStreamer/Source/GStreamer/Private/GstAppSrcImpl.cpp @@ -11,39 +11,67 @@ extern "C" #include #include +GstClockTime SecsToNano(float secs) +{ + return (GstClockTime)(secs * 1000000000); +} + class FGstAppSrcImpl : public IGstAppSrc { - public: +public: FGstAppSrcImpl() {} ~FGstAppSrcImpl() { Disconnect(); } virtual void Destroy(); - virtual bool Connect(IGstPipeline *Pipeline, const char *ElementName); + virtual bool Connect(IGstPipeline *Pipeline, const char *ElementName, IGstAppSrcCallback *Callback); virtual void Disconnect(); + virtual void PushTexture(const uint8_t *TextureData, size_t TextureSize); - private: + virtual int GetTextureWidth() + { + return m_Width; + } + virtual int GetTextureHeight() + { + return m_Height; + } + virtual EGstTextureFormat GetTextureFormat() + { + return m_Format; + } + + void OnNeedData(GstElement *Sink, guint size); + +private: std::string m_Name; + IGstAppSrcCallback *m_Callback = nullptr; GstElement *m_AppSrc = nullptr; - int m_Width = 0; - int m_Height = 0; + gint m_Width = 0; + gint m_Height = 0; + EGstTextureFormat m_Format; + gint m_Framerate = 1; + + GstClockTime m_Timestamp = 0; }; -IGstAppSrc *IGstAppSrc::CreateInstance() +IGstAppSrc *IGstAppSrc::CreateInstance(const char *ElementName) { auto Obj = new FGstAppSrcImpl(); - GST_LOG_DBG_A("GstAppSrc: CreateInstance %p", Obj); + GST_LOG_DBG_A("GstAppSrc: CreateInstance %p %s", Obj, ElementName); return Obj; } void FGstAppSrcImpl::Destroy() { - GST_LOG_DBG_A("GstAppSrc: Destroy %p", this); + GST_LOG_DBG_A("GstAppSrc: Destroy %p %s", this, m_Name.c_str()); delete this; } -bool FGstAppSrcImpl::Connect(IGstPipeline *Pipeline, const char *ElementName) +static void NeedDataFunc(GstElement *Sink, guint Size, FGstAppSrcImpl *Context) { Context->OnNeedData(Sink, Size); } + +bool FGstAppSrcImpl::Connect(IGstPipeline *Pipeline, const char *ElementName, IGstAppSrcCallback *Callback) { GST_LOG_DBG_A("GstAppSrc: Connect <%s>", ElementName); @@ -56,6 +84,7 @@ bool FGstAppSrcImpl::Connect(IGstPipeline *Pipeline, const char *ElementName) for (;;) { m_Name = ElementName; + m_Callback = Callback; m_AppSrc = gst_bin_get_by_name(GST_BIN(Pipeline->GetGPipeline()), ElementName); if (!m_AppSrc) @@ -64,7 +93,38 @@ bool FGstAppSrcImpl::Connect(IGstPipeline *Pipeline, const char *ElementName) break; } - g_object_set(m_AppSrc, "emit-signals", TRUE, nullptr); + GstCaps *caps = gst_app_src_get_caps(GST_APP_SRC(m_AppSrc)); + guint num_caps = gst_caps_get_size(caps); + if (num_caps > 0) + { + gchar *format; + gint fps_n = 0, fps_d; + GstStructure *st = gst_caps_get_structure(caps, 0); + if (gst_structure_get(st, + "width", G_TYPE_INT, &m_Width, + "height", G_TYPE_INT, &m_Height, + "format", G_TYPE_STRING, &format, + "framerate", GST_TYPE_FRACTION, &fps_n, &fps_d, + NULL)) + { + m_Framerate = fps_n / fps_d; + GST_LOG_DBG_A("GstAppSrc: Found CAPS width:%i height:%i format:%s fps:%i", m_Width, m_Height, format, m_Framerate); + if (strncmp(format, "BGRA", 4) == 0) + { + m_Format = EGstTextureFormat::GST_VIDEO_FORMAT_BGRA; + } + g_free(format); + } + } + gst_caps_unref(caps); + + g_object_set(m_AppSrc, + "emit-signals", TRUE, + "do-timestamp", TRUE, + "format", GST_FORMAT_TIME, + nullptr); + + g_signal_connect(m_AppSrc, "need-data", G_CALLBACK(NeedDataFunc), this); GST_LOG_DBG_A("GstAppSrc: Connect SUCCESS"); return true; @@ -88,12 +148,24 @@ void FGstAppSrcImpl::Disconnect() m_Width = 0; m_Height = 0; + m_Timestamp = 0; } void FGstAppSrcImpl::PushTexture(const uint8_t *TextureData, size_t TextureSize) { GstBuffer *buffer = gst_buffer_new_allocate(nullptr, TextureSize, nullptr); gst_buffer_fill(buffer, 0, TextureData, TextureSize); - const GstFlowReturn result = gst_app_src_push_buffer(GST_APP_SRC(m_AppSrc), buffer); - GST_LOG_DBG_A("GstAppSrc: GstFlowReturn <%s> TextureSize <%i>", gst_flow_get_name(result), TextureSize); + + GST_BUFFER_PTS(buffer) = m_Timestamp; + GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale_int(1, GST_SECOND, m_Framerate); + m_Timestamp += GST_BUFFER_DURATION(buffer); + + gst_app_src_push_buffer(GST_APP_SRC(m_AppSrc), buffer); + // const GstFlowReturn result = gst_app_src_push_buffer(GST_APP_SRC(m_AppSrc), buffer); + // GST_LOG_DBG_A("GstAppSrc: GstFlowReturn <%s> TextureSize <%i>", gst_flow_get_name(result), TextureSize); +} + +void FGstAppSrcImpl::OnNeedData(GstElement *Sink, guint Size) +{ + m_Callback->CbGstPushTexture(); } \ No newline at end of file diff --git a/GStreamer/Source/GStreamer/Private/GstAppSrcImpl.h b/GStreamer/Source/GStreamer/Private/GstAppSrcImpl.h deleted file mode 100644 index 078f60b..0000000 --- a/GStreamer/Source/GStreamer/Private/GstAppSrcImpl.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "GstCoreImpl.h" - -class IGstAppSrc -{ - GST_INTERFACE_IMPL(IGstAppSrc) - - public: - virtual bool Connect(class IGstPipeline *Pipeline, const char *ElementName) = 0; - virtual void Disconnect() = 0; - virtual void PushTexture(const uint8_t *TextureData, size_t TextureSize) = 0; -}; diff --git a/GStreamer/Source/GStreamer/Private/GstCoreImpl.cpp b/GStreamer/Source/GStreamer/Private/GstCoreImpl.cpp index f61478f..39d0972 100644 --- a/GStreamer/Source/GStreamer/Private/GstCoreImpl.cpp +++ b/GStreamer/Source/GStreamer/Private/GstCoreImpl.cpp @@ -26,3 +26,4 @@ void FGstCoreImpl::Deinit() { gst_deinit(); } + diff --git a/GStreamer/Source/GStreamer/Private/GstKlv.cpp b/GStreamer/Source/GStreamer/Private/GstKlv.cpp new file mode 100644 index 0000000..a7a80ac --- /dev/null +++ b/GStreamer/Source/GStreamer/Private/GstKlv.cpp @@ -0,0 +1,2 @@ +#include "GstKlv.h" +#include "RenderUtils.h" diff --git a/GStreamer/Source/GStreamer/Private/GstPipelineComponent.cpp b/GStreamer/Source/GStreamer/Private/GstPipelineComponent.cpp index 440845c..306ffe6 100644 --- a/GStreamer/Source/GStreamer/Private/GstPipelineComponent.cpp +++ b/GStreamer/Source/GStreamer/Private/GstPipelineComponent.cpp @@ -9,110 +9,103 @@ UGstPipelineComponent::UGstPipelineComponent() void UGstPipelineComponent::UninitializeComponent() { - ResetState(); + ResetState(); } void UGstPipelineComponent::ResetState() { - SafeDestroy(Pipeline); + SafeDestroy(Pipeline); } void UGstPipelineComponent::BeginPlay() { - Super::BeginPlay(); + Super::BeginPlay(); - if (PipelineAutostart) - { - StartPipeline(); - } + if (PipelineAutostart) + { + StartPipeline(); + } } void UGstPipelineComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { - Super::EndPlay(EndPlayReason); + Super::EndPlay(EndPlayReason); - StopPipeline(); + StopPipeline(); } bool UGstPipelineComponent::StartPipeline() { - if (Pipeline) - { - GST_LOG_ERR(TEXT("GstPipelineComponent: Already started")); - return false; - } + if (Pipeline) + { + GST_LOG_ERR(TEXT("GstPipelineComponent: Already started")); + return false; + } - if (!PipelineConfig.IsEmpty()) - { - if (UseGstMainLoop) - { - Pipeline = IGstPipeline::CreateLoop(); - } - else - { - Pipeline = IGstPipeline::CreateTick(); - } - if (Pipeline->Init(TCHAR_TO_ANSI(*PipelineName), TCHAR_TO_ANSI(*PipelineConfig))) - { - { - TInlineComponentArray Components; - GetOwner()->GetComponents(Components); - for (auto *Comp : Components) - { - if (Comp->PipelineName == PipelineName) - { - Comp->CbPipelineStart(Pipeline); - } - } - } - { - TInlineComponentArray Components; - GetOwner()->GetComponents(Components); - for (auto *Comp : Components) - { - if (Comp->PipelineName == PipelineName) - { - Comp->CbPipelineStart(Pipeline); - } - } - } + if (!PipelineConfig.IsEmpty()) + { + Pipeline = IGstPipeline::CreateInstance(TCHAR_TO_ANSI(*PipelineName)); + if (Pipeline->Init(TCHAR_TO_ANSI(*PipelineName), TCHAR_TO_ANSI(*PipelineConfig))) + { + { + TInlineComponentArray Components; + GetOwner()->GetComponents(Components); + for (auto *Comp : Components) + { + if (Comp->PipelineName == PipelineName) + { + Comp->CbPipelineStart(Pipeline); + } + } + } + { + TInlineComponentArray Components; + GetOwner()->GetComponents(Components); + for (auto *Comp : Components) + { + if (Comp->PipelineName == PipelineName) + { + Comp->CbPipelineStart(Pipeline); + } + } + } - if (Pipeline->Start()) - return true; - } - } + if (Pipeline->Start()) + return true; + } + } - StopPipeline(); - return false; + StopPipeline(); + return false; } void UGstPipelineComponent::StopPipeline() { - if (Pipeline) - { - { - TInlineComponentArray Components; - GetOwner()->GetComponents(Components); - for (auto *Comp : Components) - { - if (Comp->PipelineName == PipelineName) - { - Comp->CbPipelineStop(); - } - } - } - { - TInlineComponentArray Components; - GetOwner()->GetComponents(Components); - for (auto *Comp : Components) - { - if (Comp->PipelineName == PipelineName) - { - Comp->CbPipelineStop(); - } - } - } - } + if (Pipeline) + { + { + TInlineComponentArray Components; + GetOwner()->GetComponents(Components); + for (auto *Comp : Components) + { + if (Comp->PipelineName == PipelineName) + { + Comp->CbPipelineStop(); + } + } + } + { + TInlineComponentArray Components; + GetOwner()->GetComponents(Components); + for (auto *Comp : Components) + { + if (Comp->PipelineName == PipelineName) + { + Comp->CbPipelineStop(); + } + } + } + } - ResetState(); + ResetState(); } diff --git a/GStreamer/Source/GStreamer/Private/GstPipelineLoop.cpp b/GStreamer/Source/GStreamer/Private/GstPipelineImpl.cpp similarity index 79% rename from GStreamer/Source/GStreamer/Private/GstPipelineLoop.cpp rename to GStreamer/Source/GStreamer/Private/GstPipelineImpl.cpp index 52414f5..d0ab348 100644 --- a/GStreamer/Source/GStreamer/Private/GstPipelineLoop.cpp +++ b/GStreamer/Source/GStreamer/Private/GstPipelineImpl.cpp @@ -6,12 +6,12 @@ extern "C" { #include } -class FGstPipelineLoop : public IGstPipeline +class FGstPipelineImpl : public IGstPipeline { public: - FGstPipelineLoop() {} - ~FGstPipelineLoop() { Shutdown(); } + FGstPipelineImpl() {} + ~FGstPipelineImpl() { Shutdown(); } virtual void Destroy(); virtual bool Init(const char* Name, const char* Config); @@ -35,29 +35,24 @@ class FGstPipelineLoop : public IGstPipeline std::unique_ptr m_Worker; }; -IGstPipeline* IGstPipeline::CreateInstance() +IGstPipeline* IGstPipeline::CreateInstance(const char *ElementName) { - return nullptr; -} - -IGstPipeline *IGstPipeline::CreateLoop() -{ - auto Obj = new FGstPipelineLoop(); - GST_LOG_DBG_A("GstPipeline: CreateInstance %p", Obj); + auto Obj = new FGstPipelineImpl(); + GST_LOG_DBG_A("GstPipeline: CreateInstance %p %s", Obj, ElementName); return Obj; } -void FGstPipelineLoop::Destroy() +void FGstPipelineImpl::Destroy() { - GST_LOG_DBG_A("GstPipeline: Destroy %p", this); + GST_LOG_DBG_A("GstPipeline: Destroy %p %s", this, m_Name.c_str()); delete this; } #define GST_RELEASE(func, ptr) do { if (ptr) { func(ptr); ptr = nullptr; } } while (0) -static gboolean BusMessageFunc(GstBus*, GstMessage* Message, FGstPipelineLoop* Context) { return Context->OnBusMessage(Message); } -static void ThreadWorkerFunc(FGstPipelineLoop* Context) { Context->WorkerLoop(); } +static gboolean BusMessageFunc(GstBus*, GstMessage* Message, FGstPipelineImpl* Context) { return Context->OnBusMessage(Message); } +static void ThreadWorkerFunc(FGstPipelineImpl* Context) { Context->WorkerLoop(); } -bool FGstPipelineLoop::Init(const char* Name, const char* Config) +bool FGstPipelineImpl::Init(const char* Name, const char* Config) { GST_LOG_DBG_A("GstPipeline: Init <%s>", Name); @@ -99,7 +94,7 @@ bool FGstPipelineLoop::Init(const char* Name, const char* Config) return false; } -void FGstPipelineLoop::Shutdown() +void FGstPipelineImpl::Shutdown() { Stop(); @@ -113,7 +108,7 @@ void FGstPipelineLoop::Shutdown() GST_RELEASE(gst_object_unref, m_Pipeline); } -bool FGstPipelineLoop::Start() +bool FGstPipelineImpl::Start() { GST_LOG_DBG_A("GstPipeline: Start <%s>", m_Name.c_str()); @@ -143,7 +138,7 @@ bool FGstPipelineLoop::Start() return false; } -void FGstPipelineLoop::Stop() +void FGstPipelineImpl::Stop() { if (m_Loop) { @@ -161,7 +156,7 @@ void FGstPipelineLoop::Stop() } } -void FGstPipelineLoop::WorkerLoop() +void FGstPipelineImpl::WorkerLoop() { GST_LOG_DBG_A("GstPipelineWorker: Start <%s>", m_Name.c_str()); @@ -172,7 +167,7 @@ void FGstPipelineLoop::WorkerLoop() GST_LOG_DBG_A("GstPipelineWorker: Stop <%s>", m_Name.c_str()); } -gboolean FGstPipelineLoop::OnBusMessage(GstMessage* Message) +gboolean FGstPipelineImpl::OnBusMessage(GstMessage* Message) { const int Type = GST_MESSAGE_TYPE(Message); switch (Type) diff --git a/GStreamer/Source/GStreamer/Private/GstPipelineTick.cpp b/GStreamer/Source/GStreamer/Private/GstPipelineTick.cpp deleted file mode 100644 index 440fe82..0000000 --- a/GStreamer/Source/GStreamer/Private/GstPipelineTick.cpp +++ /dev/null @@ -1,205 +0,0 @@ -#include "GstPipelineImpl.h" -#include -#include - -extern "C" { - #include -} - -class FGstPipelineTick : public IGstPipeline -{ -public: - - FGstPipelineTick() {} - ~FGstPipelineTick() { Shutdown(); } - virtual void Destroy(); - - virtual bool Init(const char* Name, const char* Config); - virtual void Shutdown(); - virtual bool Start(); - virtual void Stop(); - - virtual const char* GetName() const { return m_Name.c_str(); } - virtual struct _GstElement* GetGPipeline() { return m_Pipeline; } - virtual struct _GstBus* GetGBus() { return m_Bus; } - - gboolean OnBusMessage(GstMessage* Message); - -private: - - std::string m_Name; - GstElement* m_Pipeline = nullptr; - GstBus* m_Bus = nullptr; -}; - -IGstPipeline *IGstPipeline::CreateTick() -{ - auto Obj = new FGstPipelineTick(); - GST_LOG_DBG_A("GstPipeline: CreateInstance %p", Obj); - return Obj; -} - -void FGstPipelineTick::Destroy() -{ - GST_LOG_DBG_A("GstPipeline: Destroy %p", this); - delete this; -} - -#define GST_RELEASE(func, ptr) do { if (ptr) { func(ptr); ptr = nullptr; } } while (0) -static gboolean BusMessageFunc(GstBus*, GstMessage* Message, FGstPipelineTick* Context) { return Context->OnBusMessage(Message); } - -bool FGstPipelineTick::Init(const char* Name, const char* Config) -{ - GST_LOG_DBG_A("GstPipeline: Init <%s>", Name); - - if (m_Pipeline) - { - GST_LOG_ERR_A("GstPipeline: Already initialized"); - return false; - } - - for (;;) - { - m_Name = Name; - - GST_LOG_DBG_A("GstPipeline: gst_parse_launch \"%s\"", Config); - GError* Error = nullptr; - m_Pipeline = gst_parse_launch(Config, &Error); - if (Error) - { - GST_LOG_ERR_A("gst_parse_launch failed -> %s", Error->message); - g_error_free(Error); - break; - } - - m_Bus = gst_pipeline_get_bus(GST_PIPELINE(m_Pipeline)); - if (!m_Bus) - { - GST_LOG_ERR_A("gst_pipeline_get_bus failed"); - break; - } - - gst_bus_add_watch(m_Bus, (GstBusFunc)BusMessageFunc, this); - - GST_LOG_DBG_A("GstPipeline: Init SUCCESS"); - return true; - } - - GST_LOG_ERR_A("GstPipeline: Init FAILED"); - Shutdown(); - return false; -} - -void FGstPipelineTick::Shutdown() -{ - Stop(); - - if (m_Pipeline) - { - GST_LOG_DBG_A("GstPipeline: Shutdown <%s>", m_Name.c_str()); - gst_element_set_state(m_Pipeline, GST_STATE_NULL); - } - - GST_RELEASE(gst_object_unref, m_Bus); - GST_RELEASE(gst_object_unref, m_Pipeline); -} - -bool FGstPipelineTick::Start() -{ - GST_LOG_DBG_A("GstPipeline: Start <%s>", m_Name.c_str()); - - gst_element_set_state(m_Pipeline, GST_STATE_PLAYING); - - return true; - - // if (m_Loop) - // { - // GST_LOG_ERR_A("GstPipeline: Already started"); - // return false; - // } - - // for (;;) - // { - // m_Loop = g_main_loop_new(nullptr, FALSE); - // if (!m_Loop) - // { - // GST_LOG_ERR_A("g_main_loop_new failed"); - // break; - // } - - // m_Worker.reset(new std::thread(ThreadWorkerFunc, this)); - - // GST_LOG_DBG_A("GstPipeline: Start SUCCESS"); - // return true; - // } - - // GST_LOG_ERR_A("GstPipeline: Start FAILED"); - // Stop(); - // return false; -} - -void FGstPipelineTick::Stop() -{ - gst_element_set_state(m_Pipeline, GST_STATE_NULL); - // if (m_Loop) - // { - // GST_LOG_DBG_A("GstPipeline: Stop <%s>", m_Name.c_str()); - - // g_main_loop_quit(m_Loop); - - // if (m_Worker && m_Worker->joinable()) - // { - // m_Worker->join(); - // } - // m_Worker.reset(nullptr); - - // GST_RELEASE(g_main_loop_unref, m_Loop); - // } -} - -// void FGstPipelineTick::WorkerLoop() -// { -// GST_LOG_DBG_A("GstPipelineWorker: Start <%s>", m_Name.c_str()); - -// gst_element_set_state(m_Pipeline, GST_STATE_PLAYING); -// g_main_loop_run(m_Loop); -// gst_element_set_state(m_Pipeline, GST_STATE_NULL); - -// GST_LOG_DBG_A("GstPipelineWorker: Stop <%s>", m_Name.c_str()); -// } - -gboolean FGstPipelineTick::OnBusMessage(GstMessage* Message) -{ - const int Type = GST_MESSAGE_TYPE(Message); - switch (Type) - { - // ignore - case GST_MESSAGE_TAG: - case GST_MESSAGE_BUFFERING: - break; - - case GST_MESSAGE_EOS: - gst_element_seek(m_Pipeline, - 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, - GST_SEEK_TYPE_SET, 0, // (in nanoseconds) - GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); - break; - - case GST_MESSAGE_ERROR: - GST_LOG_ERR_A("GstPipeline: BUS ERROR <%s>", m_Name.c_str()); - // g_main_loop_quit(m_Loop); - break; - - case GST_MESSAGE_WARNING: - GST_LOG_ERR_A("GstPipeline: BUS WARNING <%s>", m_Name.c_str()); - break; - - default: - { - GST_LOG_DBG_A("GstPipeline: OnBusMessage <%s> %s (%d)", m_Name.c_str(), gst_message_type_get_name((GstMessageType)Type), Type); - break; - } - } - - return TRUE; -} diff --git a/GStreamer/Source/GStreamer/Private/GstSampleImpl.cpp b/GStreamer/Source/GStreamer/Private/GstSampleImpl.cpp index 4bddb85..f04e636 100644 --- a/GStreamer/Source/GStreamer/Private/GstSampleImpl.cpp +++ b/GStreamer/Source/GStreamer/Private/GstSampleImpl.cpp @@ -36,7 +36,7 @@ class FGstSampleImpl : public IGstSample int m_Height = 0; }; -IGstSample* IGstSample::CreateInstance() +IGstSample* IGstSample::CreateInstance(const char *ElementName) { return new FGstSampleImpl(); } diff --git a/GStreamer/Source/GStreamer/Private/GstTexture.cpp b/GStreamer/Source/GStreamer/Private/GstTexture.cpp index 9178170..8b981c5 100644 --- a/GStreamer/Source/GStreamer/Private/GstTexture.cpp +++ b/GStreamer/Source/GStreamer/Private/GstTexture.cpp @@ -62,10 +62,8 @@ void FGstTexture::TickGameThread() if (m_TextureObject) { // render commands should be submitted only from game thread - ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER( - UpdateTextureCmd, - FGstTexture*, Context, this, - IGstSample*, Sample, Sample, + FGstTexture* Context = this; + ENQUEUE_RENDER_COMMAND(UpdateTextureCmd)([Context, Sample](FRHICommandListImmediate& RHICmdList) { Context->RenderCmd_UpdateTexture(Sample); }); @@ -146,9 +144,8 @@ void FGstTexture::Resize(IGstSample* Sample) m_Height = Sample->GetHeight(); m_Pitch = m_Width * GPixelFormats[m_UeFormat].BlockBytes; - ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER( - CreateTextureCmd, - FGstTexture*, Context, this, + FGstTexture* Context = this; + ENQUEUE_RENDER_COMMAND(CreateTextureCmd)([Context](FRHICommandListImmediate& RHICmdList) { Context->RenderCmd_CreateTexture(); }); diff --git a/GStreamer/Source/GStreamer/Public/GStreamerModule.h b/GStreamer/Source/GStreamer/Public/GStreamerModule.h index 94647bb..5940e51 100644 --- a/GStreamer/Source/GStreamer/Public/GStreamerModule.h +++ b/GStreamer/Source/GStreamer/Public/GStreamerModule.h @@ -1,6 +1,6 @@ #pragma once -#include "ModuleManager.h" +#include "Modules/ModuleManager.h" class GSTREAMER_API IGStreamerModule : public IModuleInterface { diff --git a/GStreamer/Source/GStreamer/Private/GstAppSinkComponent.h b/GStreamer/Source/GStreamer/Public/GstAppSinkComponent.h similarity index 92% rename from GStreamer/Source/GStreamer/Private/GstAppSinkComponent.h rename to GStreamer/Source/GStreamer/Public/GstAppSinkComponent.h index 5bb2583..e641e28 100644 --- a/GStreamer/Source/GStreamer/Private/GstAppSinkComponent.h +++ b/GStreamer/Source/GStreamer/Public/GstAppSinkComponent.h @@ -8,7 +8,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FGstTextureCreatedSignature, UGstAppSinkComponent*, AppSink, UTexture2D*, NewTexture, EGstVideoFormat, Format, int, Width, int, Height); -UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +UCLASS( ClassGroup=(Simbotic), meta=(BlueprintSpawnableComponent) ) class GSTREAMER_API UGstAppSinkComponent : public UGstElementComponent, public IGstAppSinkCallback, public IGstTextureCallback { GENERATED_BODY() diff --git a/GStreamer/Source/GStreamer/Private/GstAppSinkImpl.h b/GStreamer/Source/GStreamer/Public/GstAppSinkImpl.h similarity index 100% rename from GStreamer/Source/GStreamer/Private/GstAppSinkImpl.h rename to GStreamer/Source/GStreamer/Public/GstAppSinkImpl.h diff --git a/GStreamer/Source/GStreamer/Private/GstAppSrcComponent.h b/GStreamer/Source/GStreamer/Public/GstAppSrcComponent.h similarity index 70% rename from GStreamer/Source/GStreamer/Private/GstAppSrcComponent.h rename to GStreamer/Source/GStreamer/Public/GstAppSrcComponent.h index 42461b8..d01a537 100644 --- a/GStreamer/Source/GStreamer/Private/GstAppSrcComponent.h +++ b/GStreamer/Source/GStreamer/Public/GstAppSrcComponent.h @@ -3,21 +3,24 @@ #include "GstElementComponent.h" #include "GstAppSrcImpl.h" #include "GstVideoFormat.h" +#include "GstKlv.h" #include "GstAppSrcComponent.generated.h" -UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) -class GSTREAMER_API UGstAppSrcComponent : public UGstElementComponent +UCLASS(ClassGroup = (Simbotic), meta = (BlueprintSpawnableComponent)) +class GSTREAMER_API UGstAppSrcComponent : public UGstElementComponent, public IGstAppSrcCallback { GENERATED_BODY() - public: +public: UGstAppSrcComponent(); + virtual void BeginPlay() override; virtual void UninitializeComponent() override; virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; virtual void CbPipelineStart(class IGstPipeline *Pipeline); virtual void CbPipelineStop(); + virtual void CbGstPushTexture(); UPROPERTY(Category = "GstAppSrc", EditAnywhere, BlueprintReadWrite) FString PipelineName; @@ -29,10 +32,15 @@ class GSTREAMER_API UGstAppSrcComponent : public UGstElementComponent bool AppSrcEnabled; UPROPERTY(Category = "GstAppSrc", EditAnywhere) - TArray AppSrcCaptures; + FComponentReference AppSrcCapture; - protected: + void SetKlv(TArray Klv); + +protected: void ResetState(); IGstAppSrc *AppSrc = nullptr; + TArray Klv; + + bool NeedsData = false; }; diff --git a/GStreamer/Source/GStreamer/Public/GstAppSrcImpl.h b/GStreamer/Source/GStreamer/Public/GstAppSrcImpl.h new file mode 100644 index 0000000..44c6d8c --- /dev/null +++ b/GStreamer/Source/GStreamer/Public/GstAppSrcImpl.h @@ -0,0 +1,40 @@ +#pragma once + +#include "GstCoreImpl.h" + +enum class EGstTextureFormat : unsigned char +{ + GST_VIDEO_FORMAT_UNKNOWN, + GST_VIDEO_FORMAT_ENCODED, + GST_VIDEO_FORMAT_I420, + GST_VIDEO_FORMAT_YV12, + GST_VIDEO_FORMAT_YUY2, + GST_VIDEO_FORMAT_UYVY, + GST_VIDEO_FORMAT_AYUV, + GST_VIDEO_FORMAT_RGBx, + GST_VIDEO_FORMAT_BGRx, + GST_VIDEO_FORMAT_xRGB, + GST_VIDEO_FORMAT_xBGR, + GST_VIDEO_FORMAT_RGBA, + GST_VIDEO_FORMAT_BGRA, +}; + +class IGstAppSrcCallback +{ +public: + virtual void CbGstPushTexture() = 0; +}; + +class IGstAppSrc +{ + GST_INTERFACE_IMPL(IGstAppSrc) + + public: + virtual bool Connect(class IGstPipeline *Pipeline, const char *ElementName, IGstAppSrcCallback* Callback) = 0; + virtual void Disconnect() = 0; + virtual void PushTexture(const uint8_t *TextureData, size_t TextureSize) = 0; + + virtual int GetTextureWidth() = 0; + virtual int GetTextureHeight() = 0; + virtual EGstTextureFormat GetTextureFormat() = 0; +}; diff --git a/GStreamer/Source/GStreamer/Private/GstCoreImpl.h b/GStreamer/Source/GStreamer/Public/GstCoreImpl.h similarity index 80% rename from GStreamer/Source/GStreamer/Private/GstCoreImpl.h rename to GStreamer/Source/GStreamer/Public/GstCoreImpl.h index 1db3ace..7d41611 100644 --- a/GStreamer/Source/GStreamer/Private/GstCoreImpl.h +++ b/GStreamer/Source/GStreamer/Public/GstCoreImpl.h @@ -8,7 +8,7 @@ Name() {}\ virtual ~Name() {}\ public:\ - static Name* CreateInstance();\ + static Name* CreateInstance(const char *ElementName);\ virtual void Destroy() = 0;\ private: diff --git a/GStreamer/Source/GStreamer/Private/GstElementComponent.h b/GStreamer/Source/GStreamer/Public/GstElementComponent.h similarity index 91% rename from GStreamer/Source/GStreamer/Private/GstElementComponent.h rename to GStreamer/Source/GStreamer/Public/GstElementComponent.h index 06ccf01..3c58c92 100644 --- a/GStreamer/Source/GStreamer/Private/GstElementComponent.h +++ b/GStreamer/Source/GStreamer/Public/GstElementComponent.h @@ -4,7 +4,7 @@ #include "Components/SceneComponent.h" #include "GstElementComponent.generated.h" -UCLASS( ClassGroup=(Custom) ) +UCLASS( ClassGroup=(Simbotic) ) class GSTREAMER_API UGstElementComponent : public UActorComponent { GENERATED_BODY() diff --git a/GStreamer/Source/GStreamer/Public/GstKlv.h b/GStreamer/Source/GStreamer/Public/GstKlv.h new file mode 100644 index 0000000..9fe17a6 --- /dev/null +++ b/GStreamer/Source/GStreamer/Public/GstKlv.h @@ -0,0 +1,25 @@ + +#pragma once + +#include "SharedUnreal.h" + +enum class EGstKvlKey : uint8 +{ + OdometryPosePosition = 10, + OdometryPoseOrientation = 11, + OdometryTwistLinear = 12, + OdometryTwistAngular = 13, + None +}; + +struct FGstKlv +{ + // GENERATED_USTRUCT_BODY() + + EGstKvlKey Key; + + double V0; + double V1; + double V2; + double V3; +}; diff --git a/GStreamer/Source/GStreamer/Private/GstPipelineComponent.h b/GStreamer/Source/GStreamer/Public/GstPipelineComponent.h similarity index 81% rename from GStreamer/Source/GStreamer/Private/GstPipelineComponent.h rename to GStreamer/Source/GStreamer/Public/GstPipelineComponent.h index d18ee61..aa0a6b7 100644 --- a/GStreamer/Source/GStreamer/Private/GstPipelineComponent.h +++ b/GStreamer/Source/GStreamer/Public/GstPipelineComponent.h @@ -4,7 +4,7 @@ #include "GstPipelineImpl.h" #include "GstPipelineComponent.generated.h" -UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +UCLASS( ClassGroup=(Simbotic), meta=(BlueprintSpawnableComponent) ) class GSTREAMER_API UGstPipelineComponent : public UGstElementComponent { GENERATED_BODY() @@ -17,9 +17,6 @@ class GSTREAMER_API UGstPipelineComponent : public UGstElementComponent virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; virtual void UninitializeComponent() override; - UPROPERTY(Category="GstPipeline", EditAnywhere, BlueprintReadWrite) - bool UseGstMainLoop = true; - UPROPERTY(Category="GstPipeline", EditAnywhere, BlueprintReadWrite) FString PipelineName; diff --git a/GStreamer/Source/GStreamer/Private/GstPipelineImpl.h b/GStreamer/Source/GStreamer/Public/GstPipelineImpl.h similarity index 100% rename from GStreamer/Source/GStreamer/Private/GstPipelineImpl.h rename to GStreamer/Source/GStreamer/Public/GstPipelineImpl.h diff --git a/GStreamer/Source/GStreamer/Private/GstSampleImpl.h b/GStreamer/Source/GStreamer/Public/GstSampleImpl.h similarity index 100% rename from GStreamer/Source/GStreamer/Private/GstSampleImpl.h rename to GStreamer/Source/GStreamer/Public/GstSampleImpl.h diff --git a/GStreamer/Source/GStreamer/Private/GstTexture.h b/GStreamer/Source/GStreamer/Public/GstTexture.h similarity index 100% rename from GStreamer/Source/GStreamer/Private/GstTexture.h rename to GStreamer/Source/GStreamer/Public/GstTexture.h diff --git a/GStreamer/Source/GStreamer/Private/GstVideoFormat.h b/GStreamer/Source/GStreamer/Public/GstVideoFormat.h similarity index 100% rename from GStreamer/Source/GStreamer/Private/GstVideoFormat.h rename to GStreamer/Source/GStreamer/Public/GstVideoFormat.h diff --git a/GStreamer/Source/GStreamer/Private/Profiler.h b/GStreamer/Source/GStreamer/Public/Profiler.h similarity index 100% rename from GStreamer/Source/GStreamer/Private/Profiler.h rename to GStreamer/Source/GStreamer/Public/Profiler.h diff --git a/GStreamer/Source/GStreamer/Private/Profiler.inl b/GStreamer/Source/GStreamer/Public/Profiler.inl similarity index 100% rename from GStreamer/Source/GStreamer/Private/Profiler.inl rename to GStreamer/Source/GStreamer/Public/Profiler.inl diff --git a/GStreamer/Source/GStreamer/Private/Shared.h b/GStreamer/Source/GStreamer/Public/Shared.h similarity index 100% rename from GStreamer/Source/GStreamer/Private/Shared.h rename to GStreamer/Source/GStreamer/Public/Shared.h diff --git a/GStreamer/Source/GStreamer/Private/SharedUnreal.h b/GStreamer/Source/GStreamer/Public/SharedUnreal.h similarity index 88% rename from GStreamer/Source/GStreamer/Private/SharedUnreal.h rename to GStreamer/Source/GStreamer/Public/SharedUnreal.h index 6ad1fd8..1cc4957 100644 --- a/GStreamer/Source/GStreamer/Private/SharedUnreal.h +++ b/GStreamer/Source/GStreamer/Public/SharedUnreal.h @@ -3,6 +3,7 @@ #include "Shared.h" #include "CoreMinimal.h" #include "UObject/Package.h" +#include "UObject/ObjectMacros.h" DECLARE_LOG_CATEGORY_EXTERN(LogGStreamer, Log, All); diff --git a/GStreamerLoader/GStreamerLoader.uplugin b/GStreamerLoader/GStreamerLoader.uplugin index f16a1a0..f65dae4 100644 --- a/GStreamerLoader/GStreamerLoader.uplugin +++ b/GStreamerLoader/GStreamerLoader.uplugin @@ -1,20 +1,23 @@ -{ - "FileVersion" : 3, - "Version" : 1, - "VersionName" : "0.0.1", - "FriendlyName" : "GStreamerLoader", - "Description" : "", - "CreatedBy" : "", - "CreatedByURL" : "", - "DocsURL" : "", - "Category" : "Media", - "CanContainContent" : false, - "Modules" : - [ - { - "Name" : "GStreamerLoader", - "Type": "Runtime", - "LoadingPhase" : "PostConfigInit" - } - ] -} +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "0.0.1", + "FriendlyName": "GStreamerLoader", + "Description": "", + "Category": "Simbotic", + "CreatedBy": "", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": false, + "IsBetaVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "GStreamerLoader", + "Type": "Runtime", + "LoadingPhase": "PostConfigInit" + } + ] +} \ No newline at end of file diff --git a/GStreamerLoader/Source/GStreamerLoader/Public/GStreamerLoaderModule.h b/GStreamerLoader/Source/GStreamerLoader/Public/GStreamerLoaderModule.h index 3ebb191..4eaeb98 100644 --- a/GStreamerLoader/Source/GStreamerLoader/Public/GStreamerLoaderModule.h +++ b/GStreamerLoader/Source/GStreamerLoader/Public/GStreamerLoaderModule.h @@ -1,6 +1,6 @@ #pragma once -#include "ModuleManager.h" +#include "Modules/ModuleManager.h" class GSTREAMERLOADER_API IGStreamerLoaderModule : public IModuleInterface { diff --git a/README.md b/README.md index 9a011bf..9585eec 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,40 @@ # Setup -- Clone this repository: +- Clone this repository in the `Plugins` directory of your UE4 project: + +``` +git clone git@github.com:Simbotic/ue4-gst-plugin.git ``` -git clone git@github.com:racsoraul/ue4-gst-plugin.git + +- Update your `.uproject` adding the `Engine` as additional dependency and the `GStreamer` plugin: + +``` +..., +"Modules": [ + { + "Name": "GST_Test", + "Type": "Runtime", + "LoadingPhase": "Default", + "AdditionalDependencies": [ + "Engine" + ] + } +], +"Plugins": [ + { + "Name": "GStreamer", + "Enabled": true + }, +], +... + +``` +- Add, in the constructors of the classes of your project: `Source/PROJECT_NAME.Target.cs` and `Source/PROJECT_NAMEEditor.Target.cs` the following: +``` +bUseUnityBuild = false; +bUsePCHFiles = false; ``` -- Copy content in the `Plugins` directory of your UE4 project. # Usage @@ -14,6 +43,14 @@ Blueprints: ![gst-plugin-ue4 blueprints](docs/media/gst_pipeline.png) +GstAppSrc component: + +![gst-plugin-ue4 blueprints](docs/media/GstAppSrc.png) + +GstPipeline component: + +![gst-plugin-ue4 blueprints](docs/media/GstPipeline.png) + Details panel: ![gst-plugin-ue4 details panel](docs/media/details_panel.png) \ No newline at end of file diff --git a/docs/media/GstAppSrc.png b/docs/media/GstAppSrc.png new file mode 100644 index 0000000..3b82d94 Binary files /dev/null and b/docs/media/GstAppSrc.png differ diff --git a/docs/media/GstPipeline.png b/docs/media/GstPipeline.png new file mode 100644 index 0000000..af06afa Binary files /dev/null and b/docs/media/GstPipeline.png differ diff --git a/docs/media/details_panel.png b/docs/media/details_panel.png index 8c88a38..444a18a 100644 Binary files a/docs/media/details_panel.png and b/docs/media/details_panel.png differ