diff --git a/src/app.cpp b/src/app.cpp index 2657b27..534cce4 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -471,6 +471,8 @@ void App::init() uirtt.create(width, height, -1, GL_RGBA8, true); + rec_start(); + if (!check_license()) { message_box("License", "Could not validate this license, running in demo mode."); @@ -695,10 +697,10 @@ void App::update_rec_frames() { if (auto txt = layout[main_id]->find("txt-rec")) { - if (rec_running) + if (rec_running && Canvas::I->m_encoder) { static char buffer[128]; - sprintf(buffer, "Recorded %d frames", rec_count); + sprintf(buffer, "Recorded %d frames", Canvas::I->m_encoder->frames_count()); txt->set_text(buffer); } else @@ -779,7 +781,7 @@ void App::rec_export(std::string path) }); #endif */ - m_encoder->write_mp4(data_path + "/export.mp4"); + Canvas::I->m_encoder->write_mp4(data_path + "/export.mp4"); pb->destroy(); } @@ -788,11 +790,6 @@ void App::rec_loop() { BT_SetTerminate(); rec_running = true; - if (!m_encoder) - { - m_encoder = std::make_unique(); - m_encoder->init(1024, 512, 30, 500 << 10); - } while(rec_running) { std::unique_ptr frame; @@ -810,8 +807,8 @@ void App::rec_loop() rec_frames.pop_front(); } lock.unlock(); - if (frame) - m_encoder->encode(*frame); + if (frame && Canvas::I->m_encoder) + Canvas::I->m_encoder->encode(*frame); update_rec_frames(); } } diff --git a/src/app.h b/src/app.h index 1186e74..c0c8d29 100644 --- a/src/app.h +++ b/src/app.h @@ -96,7 +96,6 @@ public: std::mutex rec_mutex; std::condition_variable rec_cv; std::deque> rec_frames; - std::unique_ptr m_encoder; RTT uirtt; Sampler sampler; diff --git a/src/app_events.cpp b/src/app_events.cpp index 685d126..7a1390c 100644 --- a/src/app_events.cpp +++ b/src/app_events.cpp @@ -89,8 +89,11 @@ void App::tick(float dt) canvas->m_canvas->m_dirty_stroke = false; LOG("rec tick"); - Canvas::I->draw_merge(true); - Texture2D equirect = Canvas::I->m_layers_merge.gen_equirect({ 1024, 512 }); + Texture2D equirect; + App::I->render_task([&] { + Canvas::I->draw_merge(true); + equirect = Canvas::I->m_layers_merge.gen_equirect({ 1024, 512 }); + }); auto img = std::make_unique(equirect.get_image()); { std::lock_guard lock(rec_mutex); diff --git a/src/canvas.cpp b/src/canvas.cpp index da3b595..a5b7abb 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -1644,6 +1644,9 @@ bool Canvas::create(int width, int height) m_merge_rtt.create(width, height); m_merge_tex.create(width, height); m_unsaved = true; + + timelapse_reset_encoder(); + return true; } @@ -2213,7 +2216,27 @@ bool Canvas::project_save_thread(std::string file_path, bool show_progress) } } } + + if (ppi_header.doc_version.minor >= 4) + { + BinaryStreamWriter sw; + sw.init(BinaryStream::ByteOrder::LittleEndian); + + Serializer::Descriptor info; + info.class_id = "ppi_info"; + info.name = L"info header"; + info.props["has_encoder"] = std::make_shared(m_encoder != nullptr); + sw << info; + if (m_encoder != nullptr) + sw << *m_encoder; + + int bytes = sw.m_data.size(); + fwrite(&bytes, sizeof(int), 1, fp); + fwrite((char*)sw.m_data.data(), sw.m_data.size(), 1, fp); + } + fclose(fp); + bool success = false; if (use_tmp) { @@ -2415,6 +2438,33 @@ bool Canvas::project_open_thread(std::string file_path) std::swap(tmp_layers, m_layers); + if (ppi_header.doc_version.minor >= 4) + { + int bytes = 0; + fread(&bytes, sizeof(int), 1, fp); + std::vector data(bytes); + fread(data.data(), bytes, 1, fp); + BinaryStreamReader sr; + sr.init(data.data(), data.size(), BinaryStream::ByteOrder::LittleEndian); + Serializer::Descriptor info; + sr >> info; + + if (info.value("has_encoder")) + { + m_encoder = std::make_unique(); + sr >> *m_encoder; + m_encoder->init(); + } + else + { + timelapse_reset_encoder(); + } + } + else + { + timelapse_reset_encoder(); + } + fclose(fp); LOG("project restore from %s", file_path.c_str()); @@ -2829,3 +2879,10 @@ void Canvas::set_camera(const CameraData& c) m_proj = c.m_proj; m_vp = c.m_vp; } + +void Canvas::timelapse_reset_encoder() noexcept +{ + m_encoder = std::make_unique(); + int res = glm::min(m_width / 2, 1024); + m_encoder->init(res * 4, res * 2, 30, 2000 << 10); +} diff --git a/src/canvas.h b/src/canvas.h index 110ff32..0fc2887 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -8,6 +8,7 @@ #include "canvas_actions.h" #include "canvas_modes.h" #include +#include "mp4enc.h" #if __WEB__ #define CANVAS_RES 512 @@ -33,7 +34,8 @@ struct PPIDocVersion // version 1: initial // version 2: added blend mode, alpha and visibility // version 3: added animation frames - int minor = 3; + // version 4: (released in 0.2.3) add info struct + int minor = 4; }; struct PPISoftVersion @@ -92,6 +94,7 @@ public: bool m_dirty = false; bool m_commit_delayed = false; bool m_dirty_stroke = false; + std::unique_ptr m_encoder; std::chrono::time_point m_disrty_stroke_time; std::stack m_camera_stack; @@ -251,4 +254,5 @@ public: void pop_camera(); CameraData get_camera(); void set_camera(const CameraData& c); + void timelapse_reset_encoder() noexcept; }; diff --git a/src/image.cpp b/src/image.cpp index c8f9092..1a78f4f 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -209,7 +209,7 @@ bool Image::read(BinaryStreamReader& r) auto img_raw = d.get("data"); int png_width, png_height, png_comp; m_data = std::unique_ptr(stbi_load_from_memory( - img_raw->data.data(), img_raw->data.size(), &png_width, &png_height, &png_comp, 4)); + img_raw->value.data(), img_raw->value.size(), &png_width, &png_height, &png_comp, 4)); return true; } diff --git a/src/mp4enc.cpp b/src/mp4enc.cpp index 9078822..7a47fdd 100644 --- a/src/mp4enc.cpp +++ b/src/mp4enc.cpp @@ -12,6 +12,11 @@ static void encoder_trace_callback(void* context, int level, const char* message LOG("ENCODER: %s", message); } +MP4Encoder::~MP4Encoder() +{ + destroy(); +} + bool MP4Encoder::init(int width, int height, int fps, int bitrate) noexcept { if (WelsCreateSVCEncoder(&m_encoder) != 0) @@ -75,6 +80,11 @@ bool MP4Encoder::init(int width, int height, int fps, int bitrate) noexcept return true; } +bool MP4Encoder::init() noexcept +{ + return init(m_width, m_height, m_framerate, m_bitrate); +} + bool MP4Encoder::encode(const Image& rgba) noexcept { // lazy allocation of YUV buffer @@ -167,7 +177,7 @@ bool MP4Encoder::write_mp4(const std::string& filename) const noexcept MP4SetTimeScale(mp4, 90000); MP4SetVideoProfileLevel(mp4, 1); MP4TrackId mp4_track = MP4AddH264VideoTrack(mp4, 90000, 90000 / m_framerate, m_width, m_height, - m_header.avc_profile, m_header.avc_profile_compat, m_header.avc_level, 3); + m_header.avc_profile, m_header.avc_compat, m_header.avc_level, 3); MP4AddH264SequenceParameterSet(mp4, mp4_track, m_header.SPS_data.data(), m_header.SPS_data.size()); MP4AddH264PictureParameterSet(mp4, mp4_track, m_header.PPS_data.data(), m_header.PPS_data.size()); for (const auto& f : m_frames) @@ -196,3 +206,54 @@ void MP4Encoder::destroy() noexcept m_header.PPS_data.clear(); m_header.SPS_data.clear(); } + +bool MP4Encoder::read(BinaryStreamReader& r) +{ + Serializer::Descriptor d; + r >> d; + d.value("width", m_width); + d.value("height", m_height); + d.value("bitrate", m_bitrate); + d.value("framerate", m_framerate); + d.value("SPS", m_header.SPS_data); + d.value("PPS", m_header.PPS_data); + d.value("avc_profile", m_header.avc_profile); + d.value("avc_compat", m_header.avc_compat); + d.value("avc_level", m_header.avc_level); + auto frames = d.get("frames"); + for (const auto& f : frames->items) + { + if (auto fd = std::dynamic_pointer_cast(f)) + { + m_frames.emplace_back(); + m_frames.back().type = (Frame::kType)fd->value("type"); + m_frames.back().data = fd->value("data"); + } + } + return true; +} + +void MP4Encoder::write(BinaryStreamWriter& w) const +{ + Serializer::Descriptor d; + d.class_id = "mp4enc"; + d.name = L"MP4 Encoder class"; + d.props["width"] = std::make_shared(m_width); + d.props["height"] = std::make_shared(m_height); + d.props["bitrate"] = std::make_shared(m_bitrate); + d.props["framerate"] = std::make_shared(m_framerate); + d.props["SPS"] = std::make_shared(m_header.SPS_data); + d.props["PPS"] = std::make_shared(m_header.PPS_data); + d.props["avc_profile"] = std::make_shared(m_header.avc_profile); + d.props["avc_compat"] = std::make_shared(m_header.avc_compat); + d.props["avc_level"] = std::make_shared(m_header.avc_level); + auto frames = std::make_shared(); + for (const auto& f : m_frames) + { + auto fd = frames->add(); + fd->props["type"] = std::make_shared((int)f.type); + fd->props["data"] = std::make_shared(f.data); + } + d.props["frames"] = frames; + w << d; +} diff --git a/src/mp4enc.h b/src/mp4enc.h index 62f6f05..063aecc 100644 --- a/src/mp4enc.h +++ b/src/mp4enc.h @@ -1,7 +1,8 @@ #pragma once #include "image.h" +#include "serializer.h" -class MP4Encoder +class MP4Encoder : public Serializer::Type { struct Frame { @@ -14,7 +15,7 @@ class MP4Encoder std::vector SPS_data; std::vector PPS_data; uint8_t avc_profile; - uint8_t avc_profile_compat; + uint8_t avc_compat; uint8_t avc_level; }; class ISVCEncoder* m_encoder = nullptr; @@ -26,8 +27,13 @@ class MP4Encoder std::vector m_frames; std::vector m_yuv_buffer; public: + ~MP4Encoder(); + bool init() noexcept; bool init(int width, int height, int fps, int bitrate) noexcept; bool encode(const Image& rgba) noexcept; bool write_mp4(const std::string& filename) const noexcept; void destroy() noexcept; + int frames_count() const noexcept { return m_frames.size(); } + virtual bool read(BinaryStreamReader& r) override; + virtual void write(BinaryStreamWriter& w) const override; }; diff --git a/src/serializer.h b/src/serializer.h index 0f386e7..dd9336e 100644 --- a/src/serializer.h +++ b/src/serializer.h @@ -414,23 +414,23 @@ public: }; struct RawData : public Type { - std::vector data; + std::vector value; RawData() = default; //RawData(const RawData&) = delete; - RawData(std::vector data) : data(data) { } + RawData(std::vector data) : value(data) { } virtual std::string type_key() const override { return "tdta"; } virtual std::string str(int indent, const std::string& prefix) const override { - return std::string(indent, '-') + prefix + fmt::format("raw: {} bytes", data.size()); + return std::string(indent, '-') + prefix + fmt::format("raw: {} bytes", value.size()); } virtual bool read(BinaryStreamReader& r) override { - data = r.rraw(); + value = r.rraw(); return true; } virtual void write(BinaryStreamWriter& w) const override { - w.wraw(data); + w.wraw(value); } }; struct Descriptor : public Type