Skip to content

Initial implementation of ADGStreamer - #1

Open
guirodrigueslima wants to merge 14 commits into
mainfrom
dev
Open

Initial implementation of ADGStreamer#1
guirodrigueslima wants to merge 14 commits into
mainfrom
dev

Conversation

@guirodrigueslima

Copy link
Copy Markdown
Collaborator

Hi everyone,

I'm currently developing an AreaDetector driver based on GStreamer to acquire images from network video streams. The current version supports RTSP communication only, and it has been working very well.

My goal is to extend the driver to support additional streaming protocols, such as HTTP, RTMP, and SRT. To make future development and maintenance easier, I introduced a PipelineBuilder class that encapsulates the pipeline construction and provides a scalable foundation for adding new protocols.

@gustavosr8 gustavosr8 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review, for now.

Overall, I think that a great improve for this project could be to add a .clang-format rule set.

For references on how to do it, you can refer to lnls-dig/uhal#67.

Comment thread ADGStreamerApp/src/ADGStreamer.cpp Outdated
Comment on lines +7 to +16
1,
1,
maxBuffers,
maxMemory,
0,
0,
ASYN_CANBLOCK,
1,
priority,
stackSize)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to document which parameter the literal values refer to, similar to what is being done in ADSpecsPEEM

ADGStreamer(const char *portName, int maxBuffers=0, size_t maxMemory=0, int priority=0, int stackSize=0);
virtual ~ADGStreamer();

virtual asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add the override identifier to explicit the function purpose.

Comment on lines +15 to +22
public:
ADGStreamer(const char *portName, int maxBuffers=0, size_t maxMemory=0, int priority=0, int stackSize=0);
virtual ~ADGStreamer();

protected:

private:
bool initializeGStreamer();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since classes have private as default, you can declare all private members first, and the members which require specifiers like public and protected after that. This way, it is not necessary to specify private.

Since private has no members, I think that it is also not necessary to be specified.

Comment thread ADGStreamerApp/src/ADGStreamer.cpp Outdated
Comment on lines +130 to +133
if (error)
{

}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really needed?

Comment thread ADGStreamerApp/src/ADGStreamer.cpp Outdated
Comment on lines +128 to +140
if (!pipeline_)
{
if (error)
{

}
return false;
}

if (error)
{
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about unifying this checks in a single if statement?

return true;
}

bool ADGStreamer::createPipeline()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm missing some explanation in the commit message about the required steps when creating a new pipeline

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for creating/stopping the pipeline

pPvt->acquisitionTask();
}

void ADGStreamer::acquisitionTask()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no reason on creating an empty acquisition task. You can create the task without it

Comment thread ADGStreamerApp/src/ADGStreamer.cpp Outdated
Comment on lines +381 to +384
if (!bus_)
{
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this checking really needed once you are already checking for bus_ in createPipeline()?

}
else
{
processBusMessage();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I get it right, the bus messages warns us about pipelines states. If it is the case, the message processing shouldn't be a standalone thread instead of being attached to the acquisition one?

Implemented the pipeline start and stop logic through the
`startPipeline()` and `stopPipeline()` routines. When an acquisition
starts, samples are received by the `onNewSampleCallback()` callback function.

At this stage, the pipeline string configuration is hardcoded.
Implemented the `processSample()` routine to process samples received by
the GStreamer callback. The function extracts the image data from the
`GstSample` and prepares it for delivery to the AreaDetector driver and
plugins.
An epicsThread has been added to support the future implementation of a
GstBus monitoring.
Added `GstBus` monitoring for pipeline events such as state changes,
errors, and End Of Stream (EOS), keeping the driver updated with the
current pipeline execution state.
construction

Introduce the PipelineBuilder architecture to separate GStreamer
pipeline construction from the ADGStreamer driver implementation.

Common pipeline stages, such as queue, decoder, converter, and sink,
are implemented in PipelineBuilder, allowing them to be shared across
different communication protocols.

Protocol-specific implementations, such as PipelineBuilderRTSP, are
responsible only for configuring protocol-related pipeline elements,
making it easier to support additional protocols such as HTTP, RTMP,
and SRT without requiring changes to the driver implementation.
Add the reportStatus() helper method to centralize driver status
updates and status message reporting.
@gustavosr8

Copy link
Copy Markdown

Just another overall suggestion, I've notice you are using a lot of booleans to handling the status of different operations. I suggest changing it to use asynStatus interface, or throw exceptions where it fits.

@gustavosr8 gustavosr8 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit suggestions:

08dea92: s/clang format/clang-format

5f8d88d: configure: include areaDetector CONFIG_SITE files

8b55858: ADGStreamerApp: add ADGStreamer constructor and iocsh functions

Comment thread configure/RELEASE
Comment on lines +42 to +43
-include $(TOP)/../configure/RELEASE_LIBS_INCLUDE
-include $(TOP)/RELEASE.local

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those related to AREA_DETECTOR?

Comment on lines +5 to +19
ADGStreamer::ADGStreamer(const char *portName, int maxBuffers, size_t maxMemory,
int priority, int stackSize)
: ADDriver(portName, 1, 1, maxBuffers, maxMemory, 0, 0, ASYN_CANBLOCK, 1,
priority, stackSize)
{
initializeGStreamer();
}

ADGStreamer::~ADGStreamer() { }

bool ADGStreamer::initializeGStreamer()
{
gst_init(nullptr, nullptr);
return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply call gst_init() directly in the constructor?

Comment on lines +46 to +51
GstStateChangeReturn ret;
ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);

if (ret == GST_STATE_CHANGE_FAILURE) {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about

if (gst_element_set_state(pipeline_, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
 ...
}

Comment on lines +53 to +60
GstState state;
gst_element_get_state(pipeline_, &state, nullptr, GST_CLOCK_TIME_NONE);

if (state != GST_STATE_PLAYING) {
return false;
}

return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (gst_element_get_state(pipeline_, &state, nullptr, GST_CLOCK_TIME_NONE) != GST_STATE_PLAYING) {
 ...
}

Comment on lines +66 to +78
GstStateChangeReturn ret;
ret = gst_element_set_state(pipeline_, GST_STATE_NULL);

if (ret == GST_STATE_CHANGE_FAILURE) {
return false;
}

GstState state;
gst_element_get_state(pipeline_, &state, nullptr, GST_CLOCK_TIME_NONE);

if (state != GST_STATE_NULL) {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as commented in startPipeline()

Comment thread ADGStreamerApp/src/ADGStreamer.cpp Outdated
Comment on lines +97 to +105
= std::string("rtspsrc "
"location=rtsp://admin:r00tr00t@10.20.21.40:554/cam/"
"realmonitor?channel=1&subtype=0 protocols=udp latency=0 "
"drop-on-latency=true")
+ std::string(" ! rtph264depay ! h264parse config-interval=-1")
+ std::string(
" ! queue max-size-buffers=1 leaky=downstream silent=true")
+ std::string(" ! avdec_h264 ! videoconvert ! video/x-raw,format=RGB ! "
"appsink name=appsink sync=false");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing some explanation in commit message about the reasoning around this string.

Comment on lines +110 to +117
if (!pipeline_) {
if (error) { }
return false;
}

if (error) {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply

if ( !pipeline_ || error)

Comment on lines +148 to +150
if (!sink_) {
return GST_FLOW_ERROR;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand, this function in registered as the callback of sink when receiving a new sample. Is there any way to get here without sink_ being defined? Is this check really needed?

@gustavosr8 gustavosr8 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that 15fdbff and b519d2d should be squashed together

Comment on lines +172 to +173
int imageCounter_;
int numImagesCounter_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between those two?

Comment on lines +209 to +218
getIntegerParam(NDArrayCounter, &imageCounter_);
getIntegerParam(ADNumImagesCounter, &numImagesCounter_);
getIntegerParam(NDArrayCallbacks, &arrayCallbacks_);
imageCounter_++;
numImagesCounter_++;
setIntegerParam(NDArrayCounter, imageCounter_);
setIntegerParam(ADNumImagesCounter, numImagesCounter_);
pImage_->uniqueId = imageCounter_;
pImage_->timeStamp = startTime.secPastEpoch + startTime.nsec / 1.e9;
updateTimeStamp(&pImage_->epicsTS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't missing a callParamCallbacks() after those operations?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants