{"id":21,"date":"2019-05-14T14:13:28","date_gmt":"2019-05-14T14:13:28","guid":{"rendered":"http:\/\/blogs.igalia.com\/aboya\/?p=21"},"modified":"2019-05-14T14:33:40","modified_gmt":"2019-05-14T14:33:40","slug":"validateflow-a-new-tool-to-test-gstreamer-pipelines","status":"publish","type":"post","link":"https:\/\/blogs.igalia.com\/aboya\/2019\/05\/14\/validateflow-a-new-tool-to-test-gstreamer-pipelines\/","title":{"rendered":"validateflow: A new tool to test GStreamer pipelines"},"content":{"rendered":"<p>It has been a while since <a href=\"https:\/\/gstreamer.freedesktop.org\/data\/doc\/gstreamer\/head\/gst-validate\/html\/\">GstValidate<\/a> has been available. GstValidate has made it easier to write integration tests that check that playback and transcoding executing actions (like seeking, changing subtitle tracks, etc&#8230;) work as expected; testing at a high level rather than fine exact\/fine grained data flow.<\/p>\n<p>As GStreamer is applied to an ever wider variety of cases, testing often becomes cumbersome for those cases that resemble less typical playback. On one hand there is the C testing framework intended for unit tests, which is admittedly low level. Even when using something like <a href=\"https:\/\/gstreamer.freedesktop.org\/data\/doc\/gstreamer\/head\/gstreamer-libs\/html\/gstreamer-libs-GstHarness.html\">GstHarness<\/a>, checking an element outputs the correct buffers and events requires a lot of manual coding. On the other hand gst-validate so far has focused mostly on assets that can be played with a typical playbin, requiring extra effort and coding for the less straightforward cases.<\/p>\n<p>This has historically left many specific test cases within that middle ground without an effective way to be tested. validateflow attempts to fill this gap by allowing gst-validate to test that custom pipelines acted in a certain way produce the expected result.<\/p>\n<p>validateflow itself is a GstValidate plugin that records buffers and events flowing through a given pad and records them in a log file. The first time a test is run, this log becomes the expectation log. Further executions of the test still create a new log file, but this time it&#8217;s compared against the expectation log. Any difference is reported as an error. The user can rebaseline the tests by removing the expectation log file and running it again. This is very similar to how many web browser tests work (e.g. Web Platform Tests).<\/p>\n<h2>How to get it<\/h2>\n<p>validateflow has been landed recently on the development versions of GStreamer. Before 1.16 is released you&#8217;ll be able to use it by checking out the latest master branches of GStreamer subprojects, preferably with something like <a href=\"https:\/\/gitlab.freedesktop.org\/gstreamer\/gst-build\">gst-build<\/a>.<\/p>\n<p>Make sure to update both <a href=\"https:\/\/gitlab.freedesktop.org\/gstreamer\/gst-devtools\">gst-devtools<\/a>. Then update <a href=\"https:\/\/gitlab.freedesktop.org\/gstreamer\/gst-integration-testsuites\">gst-integration-testsuites<\/a> by running the following command, that will update the repo and fetch media files. Otherwise you will get errors.<\/p>\n<pre><code class=\"bash\">gst-validate-launcher --sync -L\n<\/code><\/pre>\n<h2>Writing tests<\/h2>\n<p>The usual way to use validateflow is through <code>pipelines.json<\/code>, a file parsed by the <code>validate<\/code> test suite (the one run by default by <code>gst-validate-launcher<\/code>) where all the necessary elements of a validateflow tests can be placed together.<\/p>\n<p>For instance:<\/p>\n<pre><code class=\"json\">\"qtdemux_change_edit_list\":\n{\n    \"pipeline\": \"appsrc ! qtdemux ! fakesink async=false\",\n    \"config\": [\n        \"%(validateflow)s, pad=fakesink0:sink, record-buffers=false\"\n    ],\n    \"scenarios\": [\n        {\n            \"name\": \"default\",\n            \"actions\": [\n                \"description, seek=false, handles-states=false\",\n                \"appsrc-push, target-element-name=appsrc0, file-name=\\\"%(medias)s\/fragments\/car-20120827-85.mp4\/init.mp4\\\"\",\n                \"appsrc-push, target-element-name=appsrc0, file-name=\\\"%(medias)s\/fragments\/car-20120827-85.mp4\/media1.mp4\\\"\",\n                \"checkpoint, text=\\\"A moov with a different edit list is now pushed\\\"\",\n                \"appsrc-push, target-element-name=appsrc0, file-name=\\\"%(medias)s\/fragments\/car-20120827-86.mp4\/init.mp4\\\"\",\n                \"appsrc-push, target-element-name=appsrc0, file-name=\\\"%(medias)s\/fragments\/car-20120827-86.mp4\/media2.mp4\\\"\",\n                \"stop\"\n            ]\n        }\n    ]\n},\n<\/code><\/pre>\n<p>These are:<\/p>\n<ul>\n<li><code>pipeline<\/code>: A string with the same syntax of gst-launch describing the pipeline to use. Python string interpolation can be used to get the path to the <code>medias<\/code> directory where audio and video assets are placed in the <code>gst-integration-testsuites<\/code> repo by writing <code>%(media)s<\/code>. It can also be used to get a video or audio sink that can be muted, with <code>%(videosink)s<\/code> or <code>%(audiosink)s<\/code><\/p>\n<\/li>\n<li><code>config<\/code>: A validate configuration file. Among other things that can be set, here validateflow overrides are defined, one per line, with <code>%(validateflow)s<\/code>, which expands to <code>validateflow,<\/code> plus some options defining where the logs will be written (which depends on the test name). Each override monitors one pad. The settings here define which pad, and what will be recorded.<\/p>\n<\/li>\n<li><code>scenarios<\/code>: Usually a single scenario is provided. A series of actions performed in order on the pipeline. These are normal GstValidate scenarios, but new actions have been added, e.g. for controlling appsrc elements (so that you can push chunks of data in several steps instead of relying on a filesrc pushing a whole file and be done with it).<\/p>\n<\/li>\n<\/ul>\n<h2>Running tests<\/h2>\n<p>The tests defined in <code>pipelines.json<\/code> are automatically run by default when running <code>gst-validate-launcher<\/code>, since they are part of the default test suite.<\/p>\n<p>You can get the list of all the <code>pipelines.json<\/code> tests like this:<\/p>\n<pre><code class=\"bash\">gst-validate-launcher -L |grep launch_pipeline\n<\/code><\/pre>\n<p>You can use these test names to run specific tests. The <code>-v<\/code> flag is useful to see the actions as they are executed. <code>--gdb<\/code> runs the test inside the GNU debugger.<\/p>\n<pre><code class=\"bash\">gst-validate-launcher -v validate.launch_pipeline.qtdemux_change_edit_list.default\n<\/code><\/pre>\n<p>In the command line argument above <code>validate.<\/code> defines the name of the test suite Python file, <code>testsuites\/validate.py<\/code>. The rest, <code>launch_pipeline.qtdemux_change_edit_list.default<\/code> is actually a regex: actually <code>.<\/code> just happens to match a period but it would match any character (it would be more correct, albeit also more inconvenient, to use <code>\\.<\/code> instead). You can use this feature to run several related tests, for instance:<\/p>\n<pre><code>$ gst-validate-launcher -m 'validate.launch_pipeline\\.appsrc_.*'\n\nSetting up GstValidate default tests\n\n[3 \/ 3]  validate.launch_pipeline.appsrc_preroll_test.single_push: Passed\n\nStatistics:\n-----------                                                  \n\n           Total time spent: 0:00:00.369149 seconds\n\n           Passed: 3\n           Failed: 0\n           ---------\n           Total: 3\n<\/code><\/pre>\n<p>Expectation files are stored in a directory named <code>flow-expectations<\/code>, e.g.:<\/p>\n<pre><code class=\"bash\">~\/gst-validate\/gst-integration-testsuites\/flow-expectations\/qtdemux_change_edit_list\/log-fakesink0:sink-expected\n<\/code><\/pre>\n<p>The actual output log (which is compared to the expectations) is stored as a log file, e.g.:<\/p>\n<pre><code class=\"bash\">~\/gst-validate\/logs\/qtdemux_change_edit_list\/log-fakesink0:sink-actual\n<\/code><\/pre>\n<p>Here is how a validateflow log looks.<\/p>\n<pre><code class=\"yaml\">event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1;\nevent caps: video\/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)2.1, profile=(string)main, codec_data=(buffer)014d4015ffe10016674d4015d901b1fe4e1000003e90000bb800f162e48001000468eb8f20, width=(int)426, height=(int)240, pixel-aspect-ratio=(fraction)1\/1;\nevent segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=none, time=0:00:00.000000000, base=0:00:00.000000000, position=0:00:00.000000000\nevent tag: GstTagList-stream, taglist=(taglist)\"taglist\\,\\ video-codec\\=\\(string\\)\\\"H.264\\\\\\ \/\\\\\\ AVC\\\"\\;\";\nevent tag: GstTagList-global, taglist=(taglist)\"taglist\\,\\ datetime\\=\\(datetime\\)2012-08-27T01:00:50Z\\,\\ container-format\\=\\(string\\)\\\"ISO\\\\\\ fMP4\\\"\\;\";\nevent tag: GstTagList-stream, taglist=(taglist)\"taglist\\,\\ video-codec\\=\\(string\\)\\\"H.264\\\\\\ \/\\\\\\ AVC\\\"\\;\";\nevent caps: video\/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)2.1, profile=(string)main, codec_data=(buffer)014d4015ffe10016674d4015d901b1fe4e1000003e90000bb800f162e48001000468eb8f20, width=(int)426, height=(int)240, pixel-aspect-ratio=(fraction)1\/1, framerate=(fraction)24000\/1001;\n\nCHECKPOINT: A moov with a different edit list is now pushed\n\nevent caps: video\/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)3, profile=(string)main, codec_data=(buffer)014d401effe10016674d401ee8805017fcb0800001f480005dc0078b168901000468ebaf20, width=(int)640, height=(int)360, pixel-aspect-ratio=(fraction)1\/1;\nevent segment: format=TIME, start=0:00:00.041711111, offset=0:00:00.000000000, stop=none, time=0:00:00.000000000, base=0:00:00.000000000, position=0:00:00.041711111\nevent tag: GstTagList-stream, taglist=(taglist)\"taglist\\,\\ video-codec\\=\\(string\\)\\\"H.264\\\\\\ \/\\\\\\ AVC\\\"\\;\";\nevent tag: GstTagList-stream, taglist=(taglist)\"taglist\\,\\ video-codec\\=\\(string\\)\\\"H.264\\\\\\ \/\\\\\\ AVC\\\"\\;\";\nevent caps: video\/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)3, profile=(string)main, codec_data=(buffer)014d401effe10016674d401ee8805017fcb0800001f480005dc0078b168901000468ebaf20, width=(int)640, height=(int)360, pixel-aspect-ratio=(fraction)1\/1, framerate=(fraction)24000\/1001;\n<\/code><\/pre>\n<h2>Prerolling and appsrc<\/h2>\n<p>By default scenarios don&#8217;t start executing actions until the pipeline is playing. Also by default sinks require a preroll for that to occur (that is, a buffer must reach the sink before the state transition to playing is completed).<\/p>\n<p>This poses a problem for scenarios using appsrc, as no action will be executed until a buffer reaches the sink, but a buffer can only be pushed as the result of an <code>appsrc-push<\/code> action, creating a chicken and egg problem.<\/p>\n<p>For many cases that don&#8217;t require playback we can solve this simply by disabling prerolling altogether, setting <code>async=false<\/code> in the sinks.<\/p>\n<p>For cases where prerolling is desired (like playback), <code>handles_states=true<\/code> should be set in the scenario description. This makes the scenario actions run without having to wait for a state change. <code>appsrc-push<\/code> will notice the pipeline is in a state where buffers can&#8217;t flow and enqueue the buffer without waiting for it so that the next action can run immediately. Then the <code>set-state<\/code> can be used to set the state of the pipeline to playing, which will let the appsrc emit the buffer.<\/p>\n<pre><code class=\"yaml\">description, seek=false, handles-states=true\nappsrc-push, target-element-name=appsrc0, file-name=\"raw_h264.0.mp4\"\nset-state, state=playing\nappsrc-eos, target-element-name=appsrc0\n<\/code><\/pre>\n<h2>Documentation<\/h2>\n<p>The documentation of validateflow, explaining its usage in more detail can be found here:<\/p>\n<p><a href=\"https:\/\/gstreamer.freedesktop.org\/documentation\/gst-devtools-1.0\/plugins\/validateflow.html\">https:\/\/gstreamer.freedesktop.org\/documentation\/gst-devtools-1.0\/plugins\/validateflow.html<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>It has been a while since GstValidate has been available. GstValidate has made it easier to write integration tests that check that playback and transcoding executing actions (like seeking, changing subtitle tracks, etc&#8230;) work as expected; testing at a high level rather than fine exact\/fine grained data flow. As GStreamer is applied to an ever &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/blogs.igalia.com\/aboya\/2019\/05\/14\/validateflow-a-new-tool-to-test-gstreamer-pipelines\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;validateflow: A new tool to test GStreamer pipelines&#8221;<\/span><\/a><\/p>\n","protected":false},"author":57,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-21","post","type-post","status-publish","format-standard","hentry","category-uncategorized","entry"],"_links":{"self":[{"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/posts\/21","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/users\/57"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/comments?post=21"}],"version-history":[{"count":4,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/posts\/21\/revisions"}],"predecessor-version":[{"id":26,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/posts\/21\/revisions\/26"}],"wp:attachment":[{"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/media?parent=21"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/categories?post=21"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/tags?post=21"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}