Prototype: need to introduce an interface for the frame data feed

Since I want to segregate the code according to the domain,
several methods must be provided to the video display to conduct
a properly synchronised handover of video pixel data; by using
`Glib::Dispatcher` as base class, all aspects of feed synchronisation
can be packaged into a single connection interface, without having
to share implementation details between the video display and player.
This commit is contained in:
Fischlurch 2026-07-15 20:40:59 +02:00
parent 4699ae8f03
commit 2a61def8a4
4 changed files with 174 additions and 47 deletions

View file

@ -1057,7 +1057,7 @@
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#520f69" CREATED="1783904751803" ID="ID_648202546" MODIFIED="1783904801052" TEXT="lasse mir von Claude zwei Code-Templates generieren">
<icon BUILTIN="edit"/>
<node CREATED="1783904765875" ID="ID_1730135150" MODIFIED="1783904774302" TEXT="die C-L&#xf6;sung mit g_idle_add()"/>
<node BACKGROUND_COLOR="#accdc3" COLOR="#3f4015" CREATED="1783904774961" ID="ID_795180608" MODIFIED="1783984701307" STYLE="fork" TEXT="die Gtkmm-L&#xf6;sung mit dem Glib::Dispatcher">
<node BACKGROUND_COLOR="#accdc3" COLOR="#3f4015" CREATED="1783904774961" ID="ID_795180608" MODIFIED="1784136111537" STYLE="fork" TEXT="die Gtkmm-L&#xf6;sung mit dem Glib::Dispatcher">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
@ -1083,6 +1083,7 @@
</html></richcontent>
<edge COLOR="#808080" STYLE="bezier" WIDTH="thin"/>
<icon BUILTIN="wizard"/>
<icon BUILTIN="edit"/>
</node>
</node>
</node>
@ -1249,7 +1250,7 @@
<icon BUILTIN="yes"/>
</node>
<node CREATED="1783956516487" ID="ID_1971249448" MODIFIED="1783956541623" TEXT="Claude schreibt mir drei Code-Snippets">
<node CREATED="1783956542891" ID="ID_922011151" MODIFIED="1783956927430" TEXT="Setup von Uring und Timer + initiales Bef&#xfc;llen">
<node BACKGROUND_COLOR="#accdc3" COLOR="#3f4015" CREATED="1783956542891" ID="ID_922011151" MODIFIED="1784136111537" STYLE="fork" TEXT="Setup von Uring und Timer + initiales Bef&#xfc;llen">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
@ -1312,8 +1313,11 @@
</p>
</body>
</html></richcontent>
<edge COLOR="#808080" STYLE="bezier" WIDTH="thin"/>
<icon BUILTIN="wizard"/>
<icon BUILTIN="edit"/>
</node>
<node CREATED="1783956564734" ID="ID_519769608" MODIFIED="1783956917195" TEXT="Skelett der Proactor-Loop">
<node BACKGROUND_COLOR="#accdc3" COLOR="#3f4015" CREATED="1783956564734" ID="ID_519769608" MODIFIED="1784136111536" STYLE="fork" TEXT="Skelett der Proactor-Loop">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
@ -1355,15 +1359,41 @@
</p>
</body>
</html></richcontent>
<edge COLOR="#808080" STYLE="bezier" WIDTH="thin"/>
<icon BUILTIN="wizard"/>
<icon BUILTIN="edit"/>
</node>
<node CREATED="1783956594083" ID="ID_1956714359" MODIFIED="1783956898511" TEXT="onTick + follow-up">
<node BACKGROUND_COLOR="#accdc3" COLOR="#3f4015" CREATED="1783956594083" ID="ID_1956714359" MODIFIED="1784136509799" STYLE="fork" TEXT="onTick + follow-up">
<richcontent TYPE="NOTE"><html>
<head/>
<head>
</head>
<body>
void onTick() { if (slots[nextExpected % N].state == Ready) { frameReady.emit(); // GUI thread will draw slots[nextExpected % N] ++nextExpected; // GUI marks the slot Empty once it's done drawing } // else: do nothing &#8212; this frame slips one tick later. Not dropped, just delayed. while (slots[nextToSubmit % N].state == Empty) { submitRead(nextToSubmit); // io_uring_prep_read + set_data64(nextToSubmit) ++nextToSubmit; } io_uring_submit(&amp;ring); }
<pre>void onTick()
&#160;&#160;{
&#160;&#160;&#160;&#160;if (slots[nextExpected % N].state == Ready)
&#160;&#160;&#160;&#160;&#160;&#160;{
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;frameReady.emit(); // GUI thread will draw slots[nextExpected % N]
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;++nextExpected; // GUI marks the slot Empty once it's done drawing
&#160;&#160;&#160;&#160;&#160;&#160;}
&#160;// else: do nothing &#8212; this frame slips one tick later. Not dropped, just delayed.
while (slots[nextToSubmit % N].state == Empty)
&#160;&#160;{
&#160;&#160; &#160;&#160;submitRead(nextToSubmit); // io_uring_prep_read + set_data64(nextToSubmit)
++nextToSubmit;
&#160;&#160; }
io_uring_submit(&amp;ring);
}
</pre>
</body>
</html></richcontent>
<node CREATED="1783956613435" ID="ID_121998302" MODIFIED="1783956875196" TEXT="Claude erl&#xe4;utert dazu....">
</html>
</richcontent>
<edge COLOR="#808080" STYLE="bezier" WIDTH="thin"/>
<icon BUILTIN="wizard"/>
<icon BUILTIN="edit"/>
<node CREATED="1783956613435" ID="ID_121998302" MODIFIED="1784136083060" TEXT="Claude erl&#xe4;utert dazu....">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
@ -1376,7 +1406,7 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#accdc3" COLOR="#3f4015" CREATED="1783957483612" ID="ID_918051178" MODIFIED="1783957589661" STYLE="fork" TEXT="a Zuckerl: Chrono-to-Kernel-Timespec">
<node BACKGROUND_COLOR="#accdc3" COLOR="#3f4015" CREATED="1783957483612" ID="ID_918051178" MODIFIED="1784136111534" STYLE="fork" TEXT="a Zuckerl: Chrono-to-Kernel-Timespec">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
@ -1435,6 +1465,7 @@
</html></richcontent>
<edge COLOR="#808080" STYLE="bezier" WIDTH="thin"/>
<icon BUILTIN="wizard"/>
<icon BUILTIN="edit"/>
</node>
<node BACKGROUND_COLOR="#e9c7a3" COLOR="#440099" CREATED="1783964845540" ID="ID_784995591" MODIFIED="1783964898453" TEXT="schlie&#xdf;e eine allgemeine &#xdc;berlegung an btr. Lumiera-Engine">
<icon BUILTIN="back"/>
@ -2056,7 +2087,7 @@
<node CREATED="1784082547951" ID="ID_1261536746" MODIFIED="1784082582921" TEXT="ist ein std::array aus 8bit-Pixeln"/>
<node COLOR="#5b280f" CREATED="1784082595689" ID="ID_1135557355" MODIFIED="1784132191641" TEXT="verwende diese Typedef aber auch als &#xbb;Buffer&#xab;-Abstraktion">
<icon BUILTIN="stop-sign"/>
<node CREATED="1784132084657" ID="ID_845039951" MODIFIED="1784132129048" TEXT="hier beginnt der Knoten">
<node CREATED="1784132084657" HGAP="24" ID="ID_845039951" MODIFIED="1784132637445" TEXT="hier beginnt der Knoten" VSHIFT="7">
<arrowlink COLOR="#b92b54" DESTINATION="ID_1404935910" ENDARROW="Default" ENDINCLINATION="101;8;" ID="Arrow_ID_1619759457" STARTARROW="None" STARTINCLINATION="53;82;"/>
<icon BUILTIN="broken-line"/>
</node>
@ -2100,6 +2131,21 @@
<node CREATED="1784131484193" ID="ID_169121010" MODIFIED="1784131533952" TEXT="Zugriff als Funktion darstellen &#x27f9; &#x3bb;">
<linktarget COLOR="#5a94d9" DESTINATION="ID_169121010" ENDARROW="Default" ENDINCLINATION="135;0;" ID="Arrow_ID_84801803" SOURCE="ID_1267317279" STARTARROW="None" STARTINCLINATION="76;6;"/>
</node>
<node CREATED="1784136965047" ID="ID_1744030854" MODIFIED="1784137036831" TEXT="zu kurz gedacht: es ist eine Zugriffs-Klammer">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Eine Operation zum Zugriff und eine Operation um den abgeschlossenen Zugriff zu markieren; zudem gibt es auch noch den Dispatcher.
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1784137038242" ID="ID_1199476075" MODIFIED="1784137051164" TEXT="dann doch ein klassisches OO-Callback-Interface"/>
</node>
</node>
</node>

View file

@ -29,5 +29,17 @@ __FAIL (string msg)
std::abort();
}
/**
* Marker class: any copy and copy construction prohibited
*/
class NonCopyable
{
protected:
~NonCopyable() = default;
NonCopyable() = default;
NonCopyable (NonCopyable const&) = delete;
NonCopyable& operator= (NonCopyable const&) = delete;
};
#endif /*COMMONS_H*/

View file

@ -17,8 +17,10 @@
#include <iostream>
#include <memory>
#include <thread>
#include <atomic>
using std::unique_ptr;
using std::make_unique;
//-----------------hard-coded-config--
const size_t RING_SIZ = 5;
@ -29,56 +31,105 @@ const uint VIDEO_HEIGHT = 240;
class UringVideoPlayer
: NonCopyable
{
using VideoDisplay = EglDisplay<uint8_t, VIDEO_WIDTH,VIDEO_HEIGHT>;
using Buffer = VideoDisplay::Buffer;
Glib::Dispatcher dispatchFrame_;
enum BuffState { FREE, READ, SHOW };
using AtomicBuffState = std::atomic<BuffState>;
using BufferAlloc = unique_ptr<Buffer[]>;
using BuffStateAlloc = unique_ptr<AtomicBuffState[]>;
/**
* Connector to the VideoDisplay for dispatching video data.
* @note inherits from Glib::Dispatcher -> signal frame readiness
* @remark the callback functions allow the VideoDisplay to interact
* with the buffer data and state in a threadsafe manner
*/
class BufferDispatcher
: public VideoDisplay::PixelFeed
{
Buffer* retrieveFrame() override;
void completedFrame() override;
UringVideoPlayer& player_;
public:
BufferDispatcher (UringVideoPlayer& player)
: player_{player}
{ }
};
size_t nextRead_{0};
size_t nextShow_{0};
BufferAlloc buffer_{};
BuffStateAlloc buffState_{};
std::atomic_bool playing_{false};
BufferDispatcher dispatchFrame_;
VideoDisplay videoDisplayer_;
std::thread proactorThread_;
public:
UringVideoPlayer (Gtk::Window& appWindow)
: videoDisplayer_{appWindow, dispatchFrame_, [this]{ return retrieveCurrFrame(); }}
: dispatchFrame_{*this}
, videoDisplayer_{appWindow, dispatchFrame_}
, proactorThread_{[this]{ playVideo(); }}
{ }
void terminate();
private:
void
playVideo()
{
// allocate buffer data structure
// initialise URing
// ++ proactor-loop ++
}
Buffer*
retrieveCurrFrame()
{
///////OOO threadsafe hand-over of current frame data
}
void playVideo();
};
/**
* Logic of the »Proactor Thread«:
* - maintain the working buffers
* - open the raw video file to retrieve data
* - establish an IO_URING connection for reading
* - periodic processing to fill and hand-off buffers
*/
void
UringVideoPlayer::playVideo()
{
buffer_ = make_unique<Buffer[]> (RING_SIZ);
buffState_ = make_unique<AtomicBuffState[]> (RING_SIZ);
// initialise URing
// ++ proactor-loop ++
}
UringVideoPlayer::Buffer*
UringVideoPlayer::BufferDispatcher::retrieveFrame()
{
///////OOO threadsafe hand-over of current frame data
}
void
UringVideoPlayer::BufferDispatcher::completedFrame()
{
///////OOO mark end of buffer access
}
void
UringVideoPlayer::terminate()
{
playing_.store (false, std::memory_order_release);
proactorThread_.join();
videoDisplayer_.cleanUp();
}
unique_ptr<UringVideoPlayer>
launchPlayer (Gtk::Window& appWindow)
{
appWindow.resize (VIDEO_WIDTH, VIDEO_HEIGHT);
auto player = std::make_unique<UringVideoPlayer> (appWindow);
return player;
}
void
UringVideoPlayer::terminate()
{
///// signal stop to proactor
videoDisplayer_.cleanUp();
return std::make_unique<UringVideoPlayer> (appWindow);
}

View file

@ -38,20 +38,35 @@ using std::string;
*/
template<typename PIX, uint WIDTH, uint HEIGHT>
class EglDisplay
: NonCopyable
{
public:
using Buffer = std::array<PIX, WIDTH*HEIGHT * 3 >;
using PixFeed = std::function<Buffer*()>;
EglDisplay(Gtk::Window& appWindow
,Glib::Dispatcher& dispatchFrame
,PixFeed frameCallback
)
: retrieveFrame_{frameCallback}
/**
* Interface retrieve pixel data for video frames to display.
* - this is a Glib::Dispatcher, and the attached slot shall be
* triggered whenever data for a new frame is available
* - pixel data is accessed (threadsafe) by invoking #retrieveFrame()
* - when processing is complete and the data will no longer be needed,
* access to the pixel buffer is yielded by invoking #completedFrame()
*/
class PixelFeed
: public Glib::Dispatcher
{
public:
virtual ~PixelFeed() { }; ///< this is an interface
virtual Buffer* retrieveFrame() =0;
virtual void completedFrame() =0;
};
EglDisplay (Gtk::Window& appWindow, PixelFeed& pixelFeed)
: pixelFeed_{pixelFeed}
{
openDisplay (appWindow);
dispatchFrame.connect([this]{ displayFrame(); });
pixelFeed_.connect([this]{ displayFrame(); });
}
void cleanUp();
@ -71,7 +86,7 @@ class EglDisplay
float scaleY_{1};
/** Callback functor to access current pixel data */
PixFeed retrieveFrame_{};
PixelFeed& pixelFeed_{};
void openDisplay (Gtk::Window&);
void displayFrame();
@ -321,7 +336,7 @@ void
EglDisplay<PIX,WIDTH,HEIGHT>::displayFrame()
{
auto currFrame = retrieveFrame_();
auto currFrame = pixelFeed_.retrieveFrame();
if (not currFrame) return;
// pick up current buffer with RGB data
@ -340,6 +355,9 @@ EglDisplay<PIX,WIDTH,HEIGHT>::displayFrame()
// double-buffer flip, automatically invokes glFlush()
eglSwapBuffers (display_, surface_);
// signal that processing of this frame is complete
pixelFeed_.completedFrame();
}