
.. _program_listing_file_Test_commit_webrtc_streamer_test.cpp:

Program Listing for File webrtc_streamer_test.cpp
=================================================

|exhale_lsh| :ref:`Return to documentation for file <file_Test_commit_webrtc_streamer_test.cpp>` (``Test/commit/webrtc_streamer_test.cpp``)

.. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS

.. code-block:: cpp

   #include "kataglyphis_test_support.h"
   
   #include <gtest/gtest.h>
   
   #include <algorithm>
   #include <chrono>
   #include <condition_variable>
   #include <initializer_list>
   #include <mutex>
   #include <optional>
   #include <string>
   #include <string_view>
   #include <utility>
   #include <vector>
   
   import kataglyphis.config_loader;
   import kataglyphis.gstreamer_pipeline;
   import kataglyphis.webrtc_streamer;
   
   #include "gstreamer_test_support.h"
   
   namespace webrtc = kataglyphis::webrtc;
   namespace cfg = kataglyphis::config;
   using kataglyphis::test::TempFile;
   
   namespace {
   
   // Port 1 refuses the connection at once, so a started stream fails fast and deterministically.
   constexpr std::string_view kRefusingServer = "ws://127.0.0.1:1";
   
   auto test_pattern_config() -> webrtc::StreamConfig
   {
       webrtc::StreamConfig config;
       config.source = webrtc::VideoSource::TestPattern;
       config.encoder = webrtc::VideoEncoder::VP8;
       config.signalling_server_uri = std::string(kRefusingServer);
       config.width = 64;
       config.height = 48;
       config.framerate = 15;
       return config;
   }
   
   }// namespace
   
   // Skips where neither webrtcsink nor webrtcbin exists, which WebRTCStreamer::initialize() demands.
   #define KATAGLYPHIS_REQUIRE_WEBRTC()                                              \
       do {                                                                          \
           if (!webrtc::WebRTCStreamer::initialize()) {                              \
               GTEST_SKIP() << "neither webrtcsink nor webrtcbin is installed here"; \
           }                                                                         \
       } while (false)
   
   TEST(WebRtcStreamConfig, CopiesEveryFieldFromTheSettings)
   {
       cfg::WebRTCConfig settings;
       settings.signaling_server_url = "wss://signal.example/ws";
       settings.video = { 1920, 1080, 60, 6000 };
       settings.stream.source = "v4l2";
       settings.stream.encoder = "vp9";
       settings.stream.device = "/dev/video3";
       settings.stream.camera_id = "imx219";
       settings.stream.input_path = "/clips/a.mp4";
       settings.stream.input_uri = "rtsp://cam/stream";
       settings.stream.peer_id = "peer";
       settings.stream.producer_id = "producer";
       settings.stun_servers = { "stun:a", "stun:b" };
       settings.turn_servers = { "turn:c" };
   
       const auto config = webrtc::create_stream_config_from_webrtc_config(settings);
       EXPECT_EQ(config.source, webrtc::VideoSource::V4L2);
       EXPECT_EQ(config.encoder, webrtc::VideoEncoder::VP9);
       EXPECT_EQ(config.signalling_server_uri, "wss://signal.example/ws");
       EXPECT_EQ(config.width, 1920U);
       EXPECT_EQ(config.height, 1080U);
       EXPECT_EQ(config.framerate, 60U);
       EXPECT_EQ(config.bitrate_kbps, 6000U);
       EXPECT_EQ(config.v4l2_device, "/dev/video3");
       EXPECT_EQ(config.camera_id, "imx219");
       EXPECT_EQ(config.input_path, "/clips/a.mp4");
       EXPECT_EQ(config.input_uri, "rtsp://cam/stream");
       EXPECT_EQ(config.peer_id, "peer");
       EXPECT_EQ(config.producer_id, "producer");
       EXPECT_EQ(config.stun_servers, (std::vector<std::string>{ "stun:a", "stun:b" }));
       EXPECT_EQ(config.turn_servers, (std::vector<std::string>{ "turn:c" }));
   }
   
   TEST(WebRtcStreamConfig, SourceNamesMapToTheirSources)
   {
       const std::initializer_list<std::pair<std::string_view, webrtc::VideoSource>> names{
           { "libcamera", webrtc::VideoSource::Libcamera },
           { "v4l2", webrtc::VideoSource::V4L2 },
           { "test", webrtc::VideoSource::TestPattern },
           { "file", webrtc::VideoSource::File },
           { "uri", webrtc::VideoSource::Uri },
       };
       for (const auto &[name, source] : names) {
           cfg::WebRTCConfig settings;
           settings.stream.source = std::string(name);
           EXPECT_EQ(webrtc::create_stream_config_from_webrtc_config(settings, webrtc::VideoSource::File).source, source)
             << name;
       }
   }
   
   TEST(WebRtcStreamConfig, EncoderNamesMapToTheirEncoders)
   {
       const std::initializer_list<std::pair<std::string_view, webrtc::VideoEncoder>> names{
           { "h264-hw", webrtc::VideoEncoder::H264_Hardware },
           { "h264-sw", webrtc::VideoEncoder::H264_Software },
           { "vp8", webrtc::VideoEncoder::VP8 },
           { "vp9", webrtc::VideoEncoder::VP9 },
       };
       for (const auto &[name, encoder] : names) {
           cfg::WebRTCConfig settings;
           settings.stream.encoder = std::string(name);
           const auto config = webrtc::create_stream_config_from_webrtc_config(
             settings, webrtc::VideoSource::Libcamera, webrtc::VideoEncoder::VP8);
           EXPECT_EQ(config.encoder, encoder) << name;
       }
   }
   
   TEST(WebRtcStreamConfig, UnknownNamesFallBackToTheArguments)
   {
       cfg::WebRTCConfig settings;
       settings.stream.source = "webcam";
       settings.stream.encoder = "h265";
       const auto config = webrtc::create_stream_config_from_webrtc_config(
         settings, webrtc::VideoSource::TestPattern, webrtc::VideoEncoder::VP9);
       EXPECT_EQ(config.source, webrtc::VideoSource::TestPattern);
       EXPECT_EQ(config.encoder, webrtc::VideoEncoder::VP9);
   }
   
   TEST(WebRtcStreamConfig, OverrideModeIgnoresTheSettingsNames)
   {
       cfg::WebRTCConfig settings;
       settings.stream.source = "v4l2";
       settings.stream.encoder = "vp9";
       const auto config = webrtc::create_stream_config_from_webrtc_config(settings,
         webrtc::VideoSource::Uri,
         webrtc::VideoEncoder::H264_Software,
         webrtc::ConfigOverrideMode::Override,
         webrtc::ConfigOverrideMode::Override);
       EXPECT_EQ(config.source, webrtc::VideoSource::Uri);
       EXPECT_EQ(config.encoder, webrtc::VideoEncoder::H264_Software);
   
       const auto mixed = webrtc::create_stream_config_from_webrtc_config(settings,
         webrtc::VideoSource::Uri,
         webrtc::VideoEncoder::H264_Software,
         webrtc::ConfigOverrideMode::UseConfig,
         webrtc::ConfigOverrideMode::Override);
       EXPECT_EQ(mixed.source, webrtc::VideoSource::V4L2);
       EXPECT_EQ(mixed.encoder, webrtc::VideoEncoder::H264_Software);
   }
   
   TEST(WebRtcStreamer, AnUnconfiguredStreamerRefusesToRun)
   {
       webrtc::WebRTCStreamer streamer;
       EXPECT_EQ(streamer.get_state(), webrtc::StreamState::Idle);
       EXPECT_FALSE(streamer.is_streaming());
       EXPECT_TRUE(streamer.get_producer_id().empty());
       EXPECT_EQ(kataglyphis::test::error_of(streamer.start()), webrtc::WebRTCError::PipelineCreationFailed);
       EXPECT_EQ(kataglyphis::test::error_of(streamer.pause()), webrtc::WebRTCError::PipelineCreationFailed);
       EXPECT_EQ(kataglyphis::test::error_of(streamer.resume()), webrtc::WebRTCError::PipelineCreationFailed);
       EXPECT_TRUE(streamer.stop().has_value());
       EXPECT_TRUE(streamer.set_bitrate(4000).has_value());
   }
   
   TEST(WebRtcStreamer, ConfigureRejectsIncompleteSettings)
   {
       KATAGLYPHIS_REQUIRE_WEBRTC();
       auto zero_width = test_pattern_config();
       zero_width.width = 0;
       auto zero_height = test_pattern_config();
       zero_height.height = 0;
       auto zero_rate = test_pattern_config();
       zero_rate.framerate = 0;
       auto no_server = test_pattern_config();
       no_server.signalling_server_uri.clear();
       auto v4l2_without_device = test_pattern_config();
       v4l2_without_device.source = webrtc::VideoSource::V4L2;
       v4l2_without_device.v4l2_device.clear();
       auto file_without_path = test_pattern_config();
       file_without_path.source = webrtc::VideoSource::File;
       auto uri_without_uri = test_pattern_config();
       uri_without_uri.source = webrtc::VideoSource::Uri;
   
       for (const auto &config :
         { zero_width, zero_height, zero_rate, no_server, v4l2_without_device, file_without_path, uri_without_uri }) {
           webrtc::WebRTCStreamer streamer;
           EXPECT_EQ(kataglyphis::test::error_of(streamer.configure(config)), webrtc::WebRTCError::InvalidConfiguration);
           EXPECT_EQ(kataglyphis::test::error_of(streamer.start()), webrtc::WebRTCError::PipelineCreationFailed);
       }
   }
   
   TEST(WebRtcStreamer, ConfiguresATestPatternStream)
   {
       KATAGLYPHIS_REQUIRE_WEBRTC();
       KATAGLYPHIS_REQUIRE_GSTREAMER_ELEMENTS("videotestsrc", "videoconvert", "webrtcsink");
       auto config = test_pattern_config();
       config.producer_id = "kataglyphis-test";
       webrtc::WebRTCStreamer streamer;
       ASSERT_TRUE(streamer.configure(config).has_value());
       EXPECT_EQ(streamer.get_producer_id(), "kataglyphis-test");
       EXPECT_EQ(streamer.get_state(), webrtc::StreamState::Idle);
       // Configuring again replaces the pipeline and keeps the streamer usable.
       config.producer_id.clear();
       config.encoder = webrtc::VideoEncoder::H264_Software;
       ASSERT_TRUE(streamer.configure(config).has_value());
       EXPECT_TRUE(streamer.get_producer_id().starts_with("stream-"));
       EXPECT_TRUE(streamer.stop().has_value());
   }
   
   TEST(WebRtcStreamer, ConfiguresAFileStream)
   {
       KATAGLYPHIS_REQUIRE_WEBRTC();
       KATAGLYPHIS_REQUIRE_GSTREAMER_ELEMENTS(
         "uridecodebin", "queue", "videoconvert", "videoscale", "videorate", "webrtcsink");
       auto config = test_pattern_config();
       config.source = webrtc::VideoSource::File;
       // Configure builds the pipeline without opening the file, so a name with quotes and spaces is enough.
       config.input_path = "clips/a \"quoted\" name.mp4";
       webrtc::WebRTCStreamer streamer;
       EXPECT_TRUE(streamer.configure(config).has_value());
   }
   
   TEST(WebRtcStreamer, AStartWithoutASignallingServerEndsInError)
   {
       KATAGLYPHIS_REQUIRE_WEBRTC();
       KATAGLYPHIS_REQUIRE_GSTREAMER_ELEMENTS("videotestsrc", "videoconvert", "webrtcsink");
       webrtc::WebRTCStreamer streamer;
   
       std::mutex mutex;
       std::condition_variable changed;
       std::vector<webrtc::StreamState> states;
       std::optional<webrtc::WebRTCError> reported;
       streamer.set_state_callback([&](webrtc::StreamState, webrtc::StreamState next) {
           {
               std::scoped_lock lock(mutex);
               states.push_back(next);
           }
           changed.notify_all();
       });
       streamer.set_error_callback([&](webrtc::WebRTCError error, const std::string &) {
           {
               std::scoped_lock lock(mutex);
               reported = error;
           }
           changed.notify_all();
       });
   
       ASSERT_TRUE(streamer.configure(test_pattern_config()).has_value());
       const auto started = streamer.start();
   
       {
           std::unique_lock lock(mutex);
           // A PLAYING state change may still land after the error, so the history is checked, not the final state.
           const bool failed = changed.wait_for(lock, kataglyphis::test::kStreamTimeout, [&] {
               if (started.has_value()) { return reported.has_value(); }
               return std::ranges::find(states, webrtc::StreamState::Error) != states.end();
           });
           EXPECT_TRUE(failed);
           ASSERT_FALSE(states.empty());
           EXPECT_EQ(states.front(), webrtc::StreamState::Connecting);
           EXPECT_NE(std::ranges::find(states, webrtc::StreamState::Error), states.end());
           // A failure after start() returned comes through the bus, and so through the error callback.
           if (started.has_value()) { EXPECT_EQ(reported, webrtc::WebRTCError::MediaError); }
       }
   
       EXPECT_TRUE(streamer.stop().has_value());
       EXPECT_EQ(streamer.get_state(), webrtc::StreamState::Idle);
   }
   
   TEST(WebRtcStreamer, FactoriesConfigureTheirSources)
   {
       KATAGLYPHIS_REQUIRE_WEBRTC();
       KATAGLYPHIS_REQUIRE_GSTREAMER_ELEMENTS("videotestsrc", "videoconvert", "webrtcsink");
       const auto test_stream = webrtc::create_test_webrtc_stream(std::string(kRefusingServer));
       ASSERT_TRUE(test_stream.has_value());
       EXPECT_TRUE(test_stream->get_producer_id().starts_with("stream-"));
       EXPECT_EQ(test_stream->get_state(), webrtc::StreamState::Idle);
   
       EXPECT_EQ(kataglyphis::test::error_of(webrtc::create_v4l2_webrtc_stream("", "")),
         webrtc::WebRTCError::InvalidConfiguration);
       EXPECT_EQ(kataglyphis::test::error_of(webrtc::create_libcamera_webrtc_stream(std::string(kRefusingServer), 0)),
         webrtc::WebRTCError::InvalidConfiguration);
   
       if (kataglyphis::test::missing_gstreamer_element({ "v4l2src" }).empty()) {
           EXPECT_TRUE(
             webrtc::create_v4l2_webrtc_stream(std::string(kRefusingServer), "/dev/video0", 64, 48, 15).has_value());
       }
       if (!kataglyphis::test::missing_gstreamer_element({ "libcamerasrc" }).empty()) {
           EXPECT_EQ(
             kataglyphis::test::error_of(webrtc::create_libcamera_webrtc_stream(std::string(kRefusingServer), 64, 48, 15)),
             webrtc::WebRTCError::PipelineCreationFailed);
       }
   }
   
   TEST(WebRtcStreamer, AConfigFileDrivesTheFactory)
   {
       KATAGLYPHIS_REQUIRE_WEBRTC();
       KATAGLYPHIS_REQUIRE_GSTREAMER_ELEMENTS("videotestsrc", "videoconvert", "webrtcsink");
       const TempFile settings("kataglyphis-webrtc-stream",
         ".json",
         R"({"signalingServerUrl": "ws://127.0.0.1:1", "video": {"defaultWidth": 64, "defaultHeight": 48},
             "stream": {"source": "test", "encoder": "vp8", "producerId": "from-file"}})");
       const auto from_file = webrtc::create_webrtc_stream_from_config(settings.path());
       ASSERT_TRUE(from_file.has_value());
       EXPECT_EQ(from_file->get_producer_id(), "from-file");
   
       // A missing file falls back to the defaults, here with the source forced to the test pattern.
       const auto from_defaults = webrtc::create_webrtc_stream_from_config(kataglyphis::test::missing_path("w.json"),
         webrtc::VideoSource::TestPattern,
         webrtc::VideoEncoder::VP8,
         webrtc::ConfigOverrideMode::Override,
         webrtc::ConfigOverrideMode::Override);
       ASSERT_TRUE(from_defaults.has_value());
       EXPECT_TRUE(from_defaults->get_producer_id().starts_with("stream-"));
   }
