Network streaming

The dv-processing library an easy to integrate solution to stream data over network TCP connections with optional encryption for security or through local sockets. This achieved by using client-server paradigm implemented using two classes: dv::io::NetworkWriter and dv::io::NetworkReader. The dv::io::NetworkWriter acts as a server, supports multiple concurrent clients, and streams a single data type stream to the clients. The dv::io::NetworkReader connects to a server and is able to receive the streamed data.

This tutorial will introduce basic usage of these classes to stream event data using a network writer and receive the same data using network reader.

Network streaming server

The following sample implements a network streaming server using dv::io::NetworkWriter, that streams periodic event data, that is synthesized in software:

 1#include <dv-processing/data/generate.hpp>
 2#include <dv-processing/io/network_writer.hpp>
 3
 4int main() {
 5    using namespace std::chrono_literals;
 6
 7    // Define image space resolution dimensions
 8    const cv::Size resolution(200, 200);
 9
10    // Define output event stream with valid resolution
11    const dv::io::Stream stream = dv::io::Stream::EventStream(0, "events", "TEST_DATA", resolution);
12
13    // Initiate the server, needs stream definition to initiate
14    dv::io::NetworkWriter server("0.0.0.0", 10101, stream);
15
16    // Print the ready state of the server
17    std::cout << "Waiting for connections..." << std::endl;
18
19    // Stream interval defines the packet frequency for this sample
20    const dv::Duration streamInterval = 10ms;
21
22    // Starting coordinates of the rectangle data that is going to be sent out in this sample
23    cv::Point2i offset(0, 0);
24
25    // Rectangle size in pixels
26    const cv::Point2i rectSize(20, 20);
27
28    // A boolean variable used to define movement direction of the rectangle
29    bool direction = true;
30
31    // Run indefinitely
32    while (true) {
33        // Do not produce output if there are no connected clients
34        if (server.getClientCount() > 0) {
35            // Generate the rectangle at given offset position
36            const dv::EventStore events = dv::data::generate::eventRectangle(dv::now(), offset, offset + rectSize);
37
38            // Increase or decrease the position coordinates depending on the "direction" boolean
39            offset = direction ? offset + cv::Point2i(1, 1) : offset - cv::Point2i(1, 1);
40
41            // Check if the rectangle coordinates reaches borders of the image
42            if (offset.x == 0 || offset.y == 0 || offset.x + rectSize.x == resolution.width
43                || offset.y + rectSize.y == resolution.height) {
44                // Reverse the motion direction
45                direction = !direction;
46            }
47
48            // Send it out to clients
49            server.writeEvents(events);
50        }
51
52        // Sleep the application for the streaming interval duration
53        std::this_thread::sleep_for(streamInterval);
54    }
55
56    return EXIT_SUCCESS;
57}

Compiling and running the sample above will run an event streaming server, which is accessible on port 10101 on all available network devices. The instance will wait for a client to connect, next chapter will introduce a minimal client application to receive and visualize the streamed data.

Network streaming client

The following code sample provide a basic usage of dv::io::NetworkReader to receive network streamed data.

 1#include <dv-processing/io/network_reader.hpp>
 2#include <dv-processing/visualization/event_visualizer.hpp>
 3
 4#include <opencv2/highgui.hpp>
 5
 6int main() {
 7    using namespace std::chrono_literals;
 8
 9    // Initiate the client connection to the same port and localhost loopback address
10    dv::io::NetworkReader client("127.0.0.1", 10101);
11
12    // Validate that this client is connected to an event data stream
13    if (!client.isEventStreamAvailable()) {
14        throw dv::exceptions::RuntimeError("Server does not provide event data!");
15    }
16
17    // Initialize the event visualizer with server reported sensor resolution
18    dv::visualization::EventVisualizer visualizer(client.getEventResolution().value());
19
20    // Create a preview window to show the visualized events
21    cv::namedWindow("Preview", cv::WINDOW_NORMAL);
22
23    // Declare an event stream slicer to synchronized event data packets
24    dv::EventStreamSlicer slicer;
25
26    // Perform visualization every 10 milliseconds, which should match the server publishing frequency
27    slicer.doEveryTimeInterval(10ms, [&visualizer](const dv::EventStore &events) {
28        // Display preview image
29        cv::imshow("Preview", visualizer.generateImage(events));
30
31        // Short sleep, if user clicks escape key (code 27), exit the application
32        if (cv::waitKey(2) == 27) {
33            exit(0);
34        }
35    });
36
37    // While client is connected
38    while (client.isRunning()) {
39        // Read the event data, validate, and feed into the slicer
40        if (const auto events = client.getNextEventBatch(); events.has_value()) {
41            slicer.accept(*events);
42        }
43    }
44
45    return EXIT_SUCCESS;
46}

This code sample connects to the port 10101 on local loopback network, receives event data stream and uses dv::visualization::EventVisualizer to generate a preview image of the event data.

Running the samples

We can now combine both samples to stream the synthetic events and visualize the output. First, run the server sample application, when it prints “Waiting for connections…”, launch the second client application, it should show the preview window with a small rectangle moving from corner to corner.

_images/moving_rectangle.png

Preview of the rectangle that is received as event packets, the triangle is expected to move diagonally through the window pixel space.