Prechádzať zdrojové kódy

Decreased latency and user can change frame bitrate now.

jcsyshc 2 rokov pred
rodič
commit
f9468bfde3
5 zmenil súbory, kde vykonal 249 pridanie a 28 odobranie
  1. 3 0
      src/config.h
  2. 50 24
      src/main.cpp
  3. 184 0
      src/third_party/scope_guard.hpp
  4. 11 3
      src/video_encoder.cpp
  5. 1 1
      src/video_encoder.h

+ 3 - 0
src/config.h

@@ -28,6 +28,9 @@ static constexpr auto default_spin_time = std::chrono::milliseconds(100); // 100
 static constexpr auto default_cuda_device_id = 0;
 static constexpr auto default_video_stream_bitrate = 10 * 1e6; // 10mbps
 
+static constexpr auto output_frame_width = 2582;
+static constexpr auto output_frame_height = 1080;
+
 #define RET_ERROR \
     assert(false); \
     return false; \

+ 50 - 24
src/main.cpp

@@ -5,6 +5,8 @@
 #include "texture_renderer.h"
 #include "video_encoder.h"
 
+#include "third_party/scope_guard.hpp"
+
 #include <imgui.h>
 #include <imgui_impl_glfw.h>
 #include <imgui_impl_opengl3.h>
@@ -74,8 +76,8 @@ int main() {
     stereo_camera camera;
     texture_renderer tex_renderer;
 
-    frame_buffer_helper fbo_helper;
-    fbo_helper.initialize(image_width * 2, image_height);
+    frame_buffer_helper output_fbo;
+    output_fbo.initialize(output_frame_width, output_frame_height);
 
     video_encoder encoder;
     encoder.initialize();
@@ -89,8 +91,13 @@ int main() {
     int camera_fps = default_camera_fps;
     float exposure_time_ms = default_camera_exposure_time_ms;
     float analog_gain = default_camera_analog_gain;
+    float output_bitrate_mbps = default_video_stream_bitrate / 1e6f;
 
     FILE *video_save_file = nullptr;
+    auto video_save_file_closer = sg::make_scope_guard([&]() {
+        if (video_save_file == nullptr) return;
+        fclose(video_save_file);
+    });
 
     // main loop
     while (!glfwWindowShouldClose(main_window)) {
@@ -148,8 +155,8 @@ int main() {
                     ImGui::PushItemWidth(200);
                     ImGui::SliderInt("Frame Rate (fps)", &camera_fps, 1, 60);
                     ImGui::DragFloat("Exposure Time (ms)", &exposure_time_ms,
-                                     0.1, 1, 1e3f / (float) camera_fps, "%.1f");
-                    ImGui::DragFloat("Analog Gain (dB)", &analog_gain, 0.1, 0, 24, "%.1f");
+                                     0.1, 1, 1e3f / (float) camera_fps, "%.01f");
+                    ImGui::DragFloat("Analog Gain (dB)", &analog_gain, 0.1, 0, 24, "%.01f");
                     ImGui::PopItemWidth();
 
                     if (camera.is_capturing()) {
@@ -168,21 +175,33 @@ int main() {
                 if (!encoder.is_encoding()) {
                     if (ImGui::Button("Start")) {
                         // create save file
+                        assert(video_save_file == nullptr);
                         auto file_name = fmt::format("record_{:%Y_%m_%d_%H_%M_%S}.hevc",
                                                      std::chrono::system_clock::now());
                         video_save_file = fopen(file_name.c_str(), "wb");
-
-                        encoder.start_encode(fbo_helper.tex_width, fbo_helper.tex_height, camera_fps);
-                        SPDLOG_INFO("Video streamer started.");
+                        encoder.start_encode(output_fbo.tex_width, output_fbo.tex_height,
+                                             camera_fps, (int) (output_bitrate_mbps * 1e6));
                     }
                 } else {
                     if (ImGui::Button("Close")) {
                         encoder.stop_encode();
                         fclose(video_save_file);
-                        SPDLOG_INFO("Video streamer stopped.");
+                        video_save_file = nullptr;
                     }
                 }
 
+                if (encoder.is_encoding()) {
+                    ImGui::BeginDisabled();
+                }
+
+                ImGui::PushItemWidth(200);
+                ImGui::DragFloat("Bitrate (Mbps)", &output_bitrate_mbps, 0.1, 1, 20, "%.01f");
+                ImGui::PopItemWidth();
+
+                if (encoder.is_encoding()) {
+                    ImGui::EndDisabled();
+                }
+
                 ImGui::PopID();
             }
 
@@ -190,40 +209,47 @@ int main() {
         ImGui::End();
         ImGui::Render();
 
-        int frame_width, frame_height;
-        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
-        glfwGetFramebufferSize(main_window, &frame_width, &frame_height);
-        glViewport(0, 0, frame_width, frame_height);
-        glClear(GL_COLOR_BUFFER_BIT);
-
+        std::chrono::high_resolution_clock::time_point start_time;
         if (camera.is_capturing()) {
             camera.retrieve_raw_images();
+            start_time = std::chrono::high_resolution_clock::now();
             camera.debayer_images();
-
-            // draw frame in the screen
-            left_ar.render({-1, 1, 2, -2});
         }
 
-        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
-        glfwSwapBuffers(main_window);
-
         if (encoder.is_encoding()) {
             // draw frame for streaming
-            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_helper.fbo);
-            glViewport(0, 0, fbo_helper.tex_width, fbo_helper.tex_height);
+            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, output_fbo.fbo);
+            glViewport(0, 0, output_fbo.tex_width, output_fbo.tex_height);
             left_ar.render({-1, -1, 1, 2});
             right_ar.render({0, -1, 1, 2});
 
             // encode frame
-            fbo_helper.download_pixels();
+            output_fbo.download_pixels();
             void *frame_data;
             size_t frame_length;
-            encoder.encode_frame(fbo_helper.pbo_res, &frame_data, &frame_length);
+            encoder.encode_frame(output_fbo.pbo_res, &frame_data, &frame_length);
+
+            SPDLOG_TRACE("Time used: {}ms", std::chrono::duration_cast<std::chrono::milliseconds>(
+                    std::chrono::high_resolution_clock::now() - start_time).count());
 
             // save encoded frame
             fwrite(frame_data, frame_length, 1, video_save_file);
         }
 
+        int frame_width, frame_height;
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+        glfwGetFramebufferSize(main_window, &frame_width, &frame_height);
+        glViewport(0, 0, frame_width, frame_height);
+        glClear(GL_COLOR_BUFFER_BIT);
+
+        if (camera.is_capturing()) {
+            // draw frame in the screen
+            left_ar.render({-1, 1, 2, -2});
+        }
+
+        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+        glfwSwapBuffers(main_window);
+
         if (camera.is_capturing()) {
             glFlush();
         }

+ 184 - 0
src/third_party/scope_guard.hpp

@@ -0,0 +1,184 @@
+/*
+ *  Created on: 13/02/2018
+ *      Author: ricab
+ *
+ * See README.md for documentation of this header's public interface.
+ */
+
+#ifndef SCOPE_GUARD_HPP_
+#define SCOPE_GUARD_HPP_
+
+#include <type_traits>
+#include <utility>
+
+#if __cplusplus >= 201703L && defined(SG_REQUIRE_NOEXCEPT_IN_CPP17)
+#define SG_REQUIRE_NOEXCEPT
+#endif
+
+namespace sg
+{
+    namespace detail
+    {
+        /* --- Some custom type traits --- */
+
+        // Type trait determining whether a type is callable with no arguments
+        template<typename T, typename = void>
+        struct is_noarg_callable_t
+                : public std::false_type
+        {}; // in general, false
+
+        template<typename T>
+        struct is_noarg_callable_t<T, decltype(std::declval<T&&>()())>
+                : public std::true_type
+        {}; // only true when call expression valid
+
+        // Type trait determining whether a no-argument callable returns void
+        template<typename T>
+        struct returns_void_t
+                : public std::is_same<void, decltype(std::declval<T&&>()())>
+        {};
+
+        /* Type trait determining whether a no-arg callable is nothrow invocable if
+        required. This is where SG_REQUIRE_NOEXCEPT logic is encapsulated. */
+        template<typename T>
+        struct is_nothrow_invocable_if_required_t
+                : public
+#ifdef SG_REQUIRE_NOEXCEPT
+                  std::is_nothrow_invocable<T> /* Note: _r variants not enough to
+                                          confirm void return: any return can be
+                                          discarded so all returns are
+                                          compatible with void */
+#else
+                  std::true_type
+#endif
+        {};
+
+        // logic AND of two or more type traits
+        template<typename A, typename B, typename... C>
+        struct and_t : public and_t<A, and_t<B, C...>>
+        {}; // for more than two arguments
+
+        template<typename A, typename B>
+        struct and_t<A, B> : public std::conditional<A::value, B, A>::type
+        {}; // for two arguments
+
+        // Type trait determining whether a type is a proper scope_guard callback.
+        template<typename T>
+        struct is_proper_sg_callback_t
+                : public and_t<is_noarg_callable_t<T>,
+                        returns_void_t<T>,
+                        is_nothrow_invocable_if_required_t<T>,
+                        std::is_nothrow_destructible<T>>
+        {};
+
+
+        /* --- The actual scope_guard template --- */
+
+        template<typename Callback,
+                typename = typename std::enable_if<
+                        is_proper_sg_callback_t<Callback>::value>::type>
+        class scope_guard;
+
+
+        /* --- Now the friend maker --- */
+
+        template<typename Callback>
+        detail::scope_guard<Callback> make_scope_guard(Callback&& callback)
+        noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value); /*
+    we need this in the inner namespace due to MSVC bugs preventing
+    sg::detail::scope_guard from befriending a sg::make_scope_guard
+    template instance in the parent namespace (see https://is.gd/xFfFhE). */
+
+
+        /* --- The template specialization that actually defines the class --- */
+
+        template<typename Callback>
+        class scope_guard<Callback> final
+        {
+        public:
+            typedef Callback callback_type;
+
+            scope_guard(scope_guard&& other)
+            noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value);
+
+            ~scope_guard() noexcept; // highlight noexcept dtor
+
+            void dismiss() noexcept;
+
+        public:
+            scope_guard() = delete;
+            scope_guard(const scope_guard&) = delete;
+            scope_guard& operator=(const scope_guard&) = delete;
+            scope_guard& operator=(scope_guard&&) = delete;
+
+        private:
+            explicit scope_guard(Callback&& callback)
+            noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value); /*
+                                                      meant for friends only */
+
+            friend scope_guard<Callback> make_scope_guard<Callback>(Callback&&)
+            noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value); /*
+      only make_scope_guard can create scope_guards from scratch (i.e. non-move)
+      */
+
+        private:
+            Callback m_callback;
+            bool m_active;
+
+        };
+
+    } // namespace detail
+
+
+    /* --- Now the single public maker function --- */
+
+    using detail::make_scope_guard; // see comment on declaration above
+
+} // namespace sg
+
+////////////////////////////////////////////////////////////////////////////////
+template<typename Callback>
+sg::detail::scope_guard<Callback>::scope_guard(Callback&& callback)
+noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value)
+        : m_callback(std::forward<Callback>(callback)) /* use () instead of {} because
+    of DR 1467 (https://is.gd/WHmWuo), which still impacts older compilers
+    (e.g. GCC 4.x and clang <=3.6, see https://godbolt.org/g/TE9tPJ and
+    https://is.gd/Tsmh8G) */
+        , m_active{true}
+{}
+
+////////////////////////////////////////////////////////////////////////////////
+template<typename Callback>
+sg::detail::scope_guard<Callback>::~scope_guard() noexcept
+{
+    if(m_active)
+        m_callback();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+template<typename Callback>
+sg::detail::scope_guard<Callback>::scope_guard(scope_guard&& other)
+noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value)
+        : m_callback(std::forward<Callback>(other.m_callback)) // idem
+        , m_active{std::move(other.m_active)}
+{
+    other.m_active = false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+template<typename Callback>
+inline void sg::detail::scope_guard<Callback>::dismiss() noexcept
+{
+    m_active = false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+template<typename Callback>
+inline auto sg::detail::make_scope_guard(Callback&& callback)
+noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value)
+-> detail::scope_guard<Callback>
+{
+    return detail::scope_guard<Callback>{std::forward<Callback>(callback)};
+}
+
+#endif /* SCOPE_GUARD_HPP_ */

+ 11 - 3
src/video_encoder.cpp

@@ -30,6 +30,7 @@ struct video_encoder::impl {
     int frame_width = image_width * 2, frame_height = image_height;
     int frame_pitch = frame_width * 4; // ARGB image
     int frame_rate = default_camera_fps;
+    int frame_bitrate = default_video_stream_bitrate;
 
     // frame related
     void *frame_ptr = nullptr, **output_ptr = nullptr;
@@ -64,7 +65,7 @@ struct video_encoder::impl {
         encode_config.frameIntervalP = 1;
         auto &rc_params = encode_config.rcParams;
         rc_params.rateControlMode = NV_ENC_PARAMS_RC_CBR;
-        rc_params.averageBitRate = default_video_stream_bitrate;
+        rc_params.averageBitRate = frame_bitrate;
         rc_params.enableAQ = true;
         rc_params.multiPass = NV_ENC_TWO_PASS_QUARTER_RESOLUTION;
         // TODO; fine tune encoder config
@@ -92,6 +93,7 @@ struct video_encoder::impl {
         NVENC_API_CHECK(api.nvEncCreateBitstreamBuffer(encoder, &buffer_config));
         output_buf = buffer_config.bitstreamBuffer;
 
+        SPDLOG_INFO("Video encoder started.");
         return true;
     }
 
@@ -113,6 +115,8 @@ struct video_encoder::impl {
         // close encoder
         api.nvEncDestroyEncoder(encoder);
         encoder = nullptr;
+
+        SPDLOG_INFO("Video encoder stopped.");
     }
 
     bool register_frame_ptr() {
@@ -132,6 +136,7 @@ struct video_encoder::impl {
     bool unregister_frame_ptr() {
         if (frame_reg_ptr == nullptr) return true;
         NVENC_API_CHECK(api.nvEncUnregisterResource(encoder, frame_reg_ptr));
+        frame_reg_ptr = nullptr;
         return true;
     }
 
@@ -179,17 +184,20 @@ struct video_encoder::impl {
 video_encoder::video_encoder()
         : pimpl(std::make_unique<impl>()) {}
 
-video_encoder::~video_encoder() = default;
+video_encoder::~video_encoder() {
+    stop_encode();
+};
 
 bool video_encoder::initialize() {
     return pimpl->initialize();
 }
 
-bool video_encoder::start_encode(int width, int height, int fps) {
+bool video_encoder::start_encode(int width, int height, int fps, int bitrate) {
     pimpl->frame_width = width;
     pimpl->frame_height = height;
     pimpl->frame_pitch = width * 4; // ARGB image
     pimpl->frame_rate = fps;
+    pimpl->frame_bitrate = bitrate;
     return pimpl->start_encode();
 }
 

+ 1 - 1
src/video_encoder.h

@@ -14,7 +14,7 @@ public:
 
     bool initialize();
 
-    bool start_encode(int width, int height, int fps);
+    bool start_encode(int width, int height, int fps, int bitrate);
 
     void stop_encode();