-
Notifications
You must be signed in to change notification settings - Fork 170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Filter] Allow dynamic framerate changes #4078
Open
tschulz
wants to merge
53
commits into
nnstreamer:main
Choose a base branch
from
eyepop-ai:filter-allow-framerate-change
base: main
Could not load branches
Branch not found: {{ refName }}
Could not load tags
Nothing to show
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+521
−52
Open
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
82b9557
nmu4
tschulz 0a735cd
Merge branch 'nnstreamer:main' into main
tschulz 59d60c1
Update rules
tschulz 9911d32
Merge branch 'nnstreamer:main' into main
tschulz b786511
New non-maintainer version
tschulz 46df20b
Remove libprotobuf dependency for TensorFlow 2 support
tschulz 0b93e27
new build including TF
tschulz 63b0f33
Fix tensorflow package (was empty)
tschulz e20f9bd
Attempt to workaround 'dpkg-shlibdeps: error: no dependency informati…
tschulz 49dc8e9
Attempt to workaround 'dpkg-shlibdeps: error: no dependency informati…
tschulz b3f2ea9
Pickup pytorch 2.0.0
tschulz 24993d9
redo
tschulz 51a85d7
redo
tschulz 33b71fe
Workaround to use PyTorch 2.0, deactivate some tests:
tschulz 64a155c
Eliminating caffe2 from debian build to get it to work with pytorch 2…
tschulz 952ddf8
[filter] For PyTorch, load model directly onto CUDA device if use_gpu…
tschulz 33c6702
Merge branch 'main' of github.com:eyepop-ai/eyepop-nnstreamer
tschulz 86d402d
Merge pull request #1 from nnstreamer/main
tschulz 2c7c76c
Merging upstream changes
tschulz 82516d9
Can't get the tests run on ARM
tschulz 0c9d10a
Merge branch 'nnstreamer:main' into main
tschulz 0cbf941
[Filter] PyTorch, ignore extra output tensors
tschulz 6c0b201
nnstreamer (2.3.0.0+nmu13) UNRELEASED; urgency=medium
tschulz a8a0563
[Filter] Allow dynamic framerate changes
tschulz 8493768
Merge pull request #2 from eyepop-ai/filter_dynamic_rate
tschulz 63c151e
patch release
tschulz 561187b
[Filter] Allow dynamic framerate changes
tschulz e4430c6
[port] MacOS, flatbuffer dependency required at compile time.
tschulz d5b5d66
[build] embed tensorflow lite (v2.13.0-RC2 into the build)
tschulz 601bae5
[build] fix typos in Makefile
tschulz 6097584
[build} shared local install root
tschulz 32f61ff
[build] fix directories, nnstreamer_filter now with recent - embedded…
tschulz 9693171
Merge pull request #3 from eyepop-ai/tflite-embedded-build
tschulz 5c733fe
[build] remove custom build dependencies
tschulz 6bba3d1
[build] MacOS embed TFlite build
tschulz 127d952
[build] Pytorch support compiles well with clang on MacOS
db76d83
test debian package
tschulz 5bc7cbc
[build] Release build for TFlite
tschulz 090efae
[build] Force use of embedded libcpuinfo
tschulz 6633e9d
Upgrading to official TFlite release 2.13.0
tschulz 79e6ffb
CI/CD script
tschulz 93bf3ef
[build] adopt embedded TFlite build for debian packaging
tschulz eab5006
[build] apply the same build restirictions when building debian package.
tschulz 0c20ee0
[build] Debian package requires mqtt
tschulz b38a6b9
[build] revert the ill-fated idea of embedding TFlite into this project.
tschulz 2382f99
[build] fix debian package build on Jenkins
tschulz 310da65
[build] TODO run tests properly
tschulz 98cb42b
[custom] smaller release build
tschulz 9b394c0
[build] Include nnstreamer-tensorflow in debian package
tschulz e706823
[internal release] v2.3.0-ep1
tschulz ede3a63
merge main from upstream
tschulz 3a21e2c
Merge branch 'main' into filter-allow-framerate-change
tschulz 8bba150
Merge branch 'main' into filter-allow-framerate-change
tschulz File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
328 changes: 328 additions & 0 deletions
328
tests/nnstreamer_filter_rate_change/unittest_filter_rate_change.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,328 @@ | ||
/** | ||
* @file unittest_filter_rate_change.cc | ||
* @date 10 May 2023 | ||
* @brief Unit test for tensor_filter element with dynamic rate changes | ||
* @see https://github.com/nnstreamer/nnstreamer | ||
* @author Torsten Schulz <torsten.schulz@gmail.com> | ||
* @bug No known bugs | ||
*/ | ||
|
||
#include <gtest/gtest.h> | ||
#include <glib.h> | ||
#include <unittest_util.h> | ||
|
||
#include <nnstreamer_plugin_api.h> | ||
|
||
#define NNS_TENSOR_RATE_NAME "tensor_rate" | ||
|
||
class NNSFilterRateChangeTest; | ||
|
||
static GstFlowReturn new_data_cb ( | ||
GstElement *element, GstBuffer *buffer, NNSFilterRateChangeTest *_this); | ||
|
||
/** | ||
* @brief Test Fixture class for a tensor_rate element | ||
*/ | ||
class NNSFilterRateChangeTest : public testing::Test | ||
{ | ||
protected: | ||
guint source_num_buffers; | ||
const gchar *target_framerate_1; | ||
const gchar *target_framerate_2; | ||
const gchar *framework; | ||
gchar *model_path; | ||
|
||
GstElement *pipeline; | ||
GstElement *throttle; | ||
GstElement *rate; | ||
GstElement *tensorsink; | ||
|
||
guint64 num_samples; | ||
|
||
const guint DEFAULT_SOURCE_NUM_BUFFERS = 300; | ||
const char *DEFAULT_SOURCE_FRAMERATE = "30/1"; | ||
|
||
/** | ||
* @brief Construct a new NNSFilterRateChangeTest object | ||
*/ | ||
NNSFilterRateChangeTest () | ||
: source_num_buffers (0), target_framerate_1 (nullptr), | ||
target_framerate_2 (nullptr), framework (nullptr), model_path (nullptr), | ||
pipeline (nullptr), throttle (nullptr), tensorsink (nullptr) | ||
{ | ||
} | ||
|
||
/** | ||
* @brief Wait until the EOS message is received or the timeout is expired. | ||
* @param pipeline target pipeline element to watch. | ||
* @return @c TRUE if EOS message is received in the timeout period. Otherwise FALSE. | ||
*/ | ||
static gboolean wait_pipeline_eos (GstElement *pipeline) | ||
{ | ||
GstBus *bus = gst_element_get_bus (pipeline); | ||
gboolean got_eos_message = FALSE; | ||
|
||
if (GST_IS_BUS (bus)) { | ||
const gulong timeout = G_USEC_PER_SEC * 10; | ||
const gulong timeout_slice = G_USEC_PER_SEC / 10; | ||
gulong timeout_accum = 0; | ||
GstMessage *msg; | ||
|
||
while (!got_eos_message && timeout_accum < timeout) { | ||
g_usleep (timeout_slice); | ||
timeout_accum += timeout_slice; | ||
|
||
while ((msg = gst_bus_pop (bus)) != NULL) { | ||
gst_bus_async_signal_func (bus, msg, NULL); | ||
GstObject *src = GST_MESSAGE_SRC (msg); | ||
gchar *name = GST_IS_OBJECT (src) ? gst_object_get_name (src) : | ||
g_strdup ("no-name"); | ||
const gchar *type_name = GST_IS_OBJECT (src) ? G_OBJECT_TYPE_NAME (src) : "no-type"; | ||
GError *gerror; | ||
gchar *debug; | ||
|
||
switch (GST_MESSAGE_TYPE (msg)) { | ||
case GST_MESSAGE_WARNING: | ||
gst_message_parse_warning (msg, &gerror, &debug); | ||
g_info ("WARNING from (%s) %s: %s (debug: %s)", type_name, name, | ||
gerror->message, debug); | ||
g_clear_error (&gerror); | ||
g_free (debug); | ||
break; | ||
case GST_MESSAGE_ERROR: | ||
gst_message_parse_error (msg, &gerror, &debug); | ||
g_info ("WARNING from (%s) %s: %s (debug: %s)", type_name, name, | ||
gerror->message, debug); | ||
g_clear_error (&gerror); | ||
g_free (debug); | ||
break; | ||
case GST_MESSAGE_EOS: | ||
got_eos_message = TRUE; | ||
break; | ||
default: | ||
{ | ||
g_info ("GST_MESSAGE UNKNOWN (0x%02X) for (%s) '%s'", | ||
GST_MESSAGE_TYPE (msg), type_name, name); | ||
/* just be quiet by default */ | ||
break; | ||
} | ||
} | ||
gst_message_unref (msg); | ||
} | ||
} | ||
gst_object_unref (bus); | ||
} | ||
|
||
return got_eos_message; | ||
} | ||
|
||
/** | ||
* @brief SetUp method for each test case | ||
*/ | ||
void SetUp () override | ||
{ | ||
source_num_buffers = DEFAULT_SOURCE_NUM_BUFFERS; | ||
} | ||
|
||
/** | ||
* @brief TearDown method for each test case | ||
*/ | ||
void TearDown () override | ||
{ | ||
gst_object_unref (throttle); | ||
gst_object_unref (tensorsink); | ||
gst_object_unref (rate); | ||
gst_object_unref (pipeline); | ||
} | ||
|
||
/** | ||
* @brief Make the pipeline description for each mode and construct the pipeline element. | ||
* @return @c TRUE if success. Otherwise FALSE. | ||
*/ | ||
gboolean setupPipeline () | ||
{ | ||
g_autofree gchar *str_pipeline = nullptr; | ||
|
||
str_pipeline = g_strdup_printf ( | ||
"videotestsrc num-buffers=%u ! video/x-raw,framerate=%s ! " | ||
"videorate name=rate ! capsfilter name=throttle caps=video/x-raw,framerate=%s ! " | ||
"tensor_converter ! tensor_filter framework=%s model=%s ! " | ||
"tensor_sink emit-signal=true name=testsink", | ||
source_num_buffers, DEFAULT_SOURCE_FRAMERATE, target_framerate_1, | ||
framework, model_path); | ||
|
||
this->pipeline = gst_parse_launch (str_pipeline, NULL); | ||
this->tensorsink = gst_bin_get_by_name (GST_BIN (pipeline), "testsink"); | ||
this->throttle = gst_bin_get_by_name (GST_BIN (pipeline), "throttle"); | ||
this->rate = gst_bin_get_by_name (GST_BIN (pipeline), "rate"); | ||
|
||
return pipeline != NULL ? TRUE : FALSE; | ||
} | ||
|
||
|
||
/** | ||
* @brief Run one pipeline test and change framerate midway | ||
*/ | ||
void runPipeline () | ||
{ | ||
const gchar *root_path = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH"); | ||
if (root_path == NULL) | ||
root_path = ".."; | ||
|
||
gchar *model_file = g_build_filename (root_path, "build", "tests", | ||
"nnstreamer_example", "libnnstreamer_customfilter_passthrough.so", NULL); | ||
ASSERT_TRUE (g_file_test (model_file, G_FILE_TEST_EXISTS)); | ||
|
||
framework = "custom"; | ||
model_path = model_file; | ||
ASSERT_TRUE (setupPipeline ()); | ||
|
||
ASSERT_TRUE (this->pipeline != NULL); | ||
ASSERT_TRUE (this->throttle != NULL); | ||
ASSERT_TRUE (this->tensorsink != NULL); | ||
ASSERT_TRUE (this->rate != NULL); | ||
|
||
this->num_samples = 0; | ||
|
||
g_signal_connect (this->tensorsink, "new-data", G_CALLBACK (new_data_cb), this); | ||
|
||
EXPECT_EQ (setPipelineStateSync (pipeline, GST_STATE_PLAYING, UNITTEST_STATECHANGE_TIMEOUT), | ||
0); | ||
EXPECT_TRUE (NNSFilterRateChangeTest::wait_pipeline_eos (pipeline)); | ||
} | ||
|
||
public: | ||
/** | ||
* @brief Records a received sample in the tensorsink | ||
*/ | ||
void incrementSampleCounter () | ||
{ | ||
this->num_samples++; | ||
|
||
if (this->target_framerate_2 && this->num_samples == this->source_num_buffers / 2) { | ||
g_autofree gchar *str_caps | ||
= g_strdup_printf ("video/x-raw,framerate=%s", this->target_framerate_2); | ||
GstCaps *caps = gst_caps_from_string (str_caps); | ||
g_object_set (this->throttle, "caps", caps, NULL); | ||
gst_caps_unref (caps); | ||
} | ||
} | ||
}; | ||
|
||
/** | ||
* @brief Callback function to count buffers arriving at the appsink | ||
*/ | ||
|
||
static GstFlowReturn | ||
new_data_cb (GstElement *element, GstBuffer *buffer, NNSFilterRateChangeTest *_this) | ||
{ | ||
_this->incrementSampleCounter (); | ||
return GST_FLOW_OK; | ||
} | ||
|
||
/** | ||
* @brief Test tensor_filter with passthrough framerate filter | ||
*/ | ||
TEST_F (NNSFilterRateChangeTest, passthrough) | ||
{ | ||
guint64 in, out, dup, drop; | ||
|
||
this->target_framerate_1 = DEFAULT_SOURCE_FRAMERATE; | ||
this->target_framerate_2 = NULL; | ||
|
||
this->runPipeline (); | ||
|
||
g_object_get (rate, "in", &in, NULL); | ||
g_object_get (rate, "out", &out, NULL); | ||
g_object_get (rate, "duplicate", &dup, NULL); | ||
g_object_get (rate, "drop", &drop, NULL); | ||
|
||
/** we don't expect the exact values */ | ||
EXPECT_EQ (in, source_num_buffers); | ||
EXPECT_EQ (out, source_num_buffers); | ||
|
||
EXPECT_EQ (out, this->num_samples); | ||
|
||
EXPECT_EQ (setPipelineStateSync (pipeline, GST_STATE_NULL, UNITTEST_STATECHANGE_TIMEOUT), 0); | ||
} | ||
|
||
/** | ||
* @brief Test tensor_filter with a static framerate throttle | ||
*/ | ||
TEST_F (NNSFilterRateChangeTest, static_throttle) | ||
{ | ||
guint64 in, out, dup, drop; | ||
|
||
this->target_framerate_1 = "15/1"; | ||
this->target_framerate_2 = NULL; | ||
|
||
this->runPipeline (); | ||
|
||
g_object_get (rate, "in", &in, NULL); | ||
g_object_get (rate, "out", &out, NULL); | ||
g_object_get (rate, "duplicate", &dup, NULL); | ||
g_object_get (rate, "drop", &drop, NULL); | ||
|
||
/** we don't expect the exact values */ | ||
EXPECT_EQ (in, source_num_buffers); | ||
// ignore possible rounding error | ||
EXPECT_GE (out, source_num_buffers / 2 - 1); | ||
EXPECT_LE (out, source_num_buffers / 2 + 1); | ||
|
||
EXPECT_EQ (out, this->num_samples); | ||
|
||
EXPECT_EQ (setPipelineStateSync (pipeline, GST_STATE_NULL, UNITTEST_STATECHANGE_TIMEOUT), 0); | ||
} | ||
|
||
/** | ||
* @brief Test tensor_filter with passthrough framerate filter | ||
*/ | ||
TEST_F (NNSFilterRateChangeTest, throttling_dynamic_change_dec) | ||
{ | ||
guint64 in, out, dup, drop; | ||
|
||
this->target_framerate_1 = DEFAULT_SOURCE_FRAMERATE; | ||
this->target_framerate_2 = "15/1"; | ||
|
||
this->runPipeline (); | ||
|
||
g_object_get (rate, "in", &in, NULL); | ||
g_object_get (rate, "out", &out, NULL); | ||
g_object_get (rate, "duplicate", &dup, NULL); | ||
g_object_get (rate, "drop", &drop, NULL); | ||
|
||
/** we don't expect the exact values */ | ||
EXPECT_EQ (in, source_num_buffers); | ||
// ignore possible rounding error | ||
EXPECT_GE (out, source_num_buffers / 2 + source_num_buffers / 4 - 1); | ||
EXPECT_LE (out, source_num_buffers / 2 + source_num_buffers / 4 + 1); | ||
|
||
EXPECT_EQ (out, this->num_samples); | ||
|
||
EXPECT_EQ (setPipelineStateSync (pipeline, GST_STATE_NULL, UNITTEST_STATECHANGE_TIMEOUT), 0); | ||
} | ||
|
||
/** | ||
* @brief gtest main | ||
*/ | ||
int | ||
main (int argc, char **argv) | ||
{ | ||
int result = -1; | ||
|
||
try { | ||
testing::InitGoogleTest (&argc, argv); | ||
} catch (...) { | ||
g_warning ("catch 'testing::internal::<unnamed>::ClassUniqueToAlwaysTrue'"); | ||
} | ||
|
||
gst_init (&argc, &argv); | ||
|
||
try { | ||
result = RUN_ALL_TESTS (); | ||
} catch (...) { | ||
g_warning ("catch `testing::internal::GoogleTestFailureException`"); | ||
} | ||
|
||
return result; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows changing GSTCAP properties other than framerate, too. Please check if it's safe to do so with unit tests.
For example, you can change an input stream of
video/x-raw
toaudio/x-raw
in run-time with this change. I'm not sure if other parts of the converter are ready for such changes in run time.If what you intend is to change framerate only and not risking other parts, please allow changing framerate only if it's already configured by current_caps.