save timelapse stream to file
This commit is contained in:
17
src/app.cpp
17
src/app.cpp
@@ -471,6 +471,8 @@ void App::init()
|
|||||||
|
|
||||||
uirtt.create(width, height, -1, GL_RGBA8, true);
|
uirtt.create(width, height, -1, GL_RGBA8, true);
|
||||||
|
|
||||||
|
rec_start();
|
||||||
|
|
||||||
if (!check_license())
|
if (!check_license())
|
||||||
{
|
{
|
||||||
message_box("License", "Could not validate this license, running in demo mode.");
|
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<NodeText>("txt-rec"))
|
if (auto txt = layout[main_id]->find<NodeText>("txt-rec"))
|
||||||
{
|
{
|
||||||
if (rec_running)
|
if (rec_running && Canvas::I->m_encoder)
|
||||||
{
|
{
|
||||||
static char buffer[128];
|
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);
|
txt->set_text(buffer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -779,7 +781,7 @@ void App::rec_export(std::string path)
|
|||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
m_encoder->write_mp4(data_path + "/export.mp4");
|
Canvas::I->m_encoder->write_mp4(data_path + "/export.mp4");
|
||||||
|
|
||||||
pb->destroy();
|
pb->destroy();
|
||||||
}
|
}
|
||||||
@@ -788,11 +790,6 @@ void App::rec_loop()
|
|||||||
{
|
{
|
||||||
BT_SetTerminate();
|
BT_SetTerminate();
|
||||||
rec_running = true;
|
rec_running = true;
|
||||||
if (!m_encoder)
|
|
||||||
{
|
|
||||||
m_encoder = std::make_unique<MP4Encoder>();
|
|
||||||
m_encoder->init(1024, 512, 30, 500 << 10);
|
|
||||||
}
|
|
||||||
while(rec_running)
|
while(rec_running)
|
||||||
{
|
{
|
||||||
std::unique_ptr<Image> frame;
|
std::unique_ptr<Image> frame;
|
||||||
@@ -810,8 +807,8 @@ void App::rec_loop()
|
|||||||
rec_frames.pop_front();
|
rec_frames.pop_front();
|
||||||
}
|
}
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
if (frame)
|
if (frame && Canvas::I->m_encoder)
|
||||||
m_encoder->encode(*frame);
|
Canvas::I->m_encoder->encode(*frame);
|
||||||
update_rec_frames();
|
update_rec_frames();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ public:
|
|||||||
std::mutex rec_mutex;
|
std::mutex rec_mutex;
|
||||||
std::condition_variable rec_cv;
|
std::condition_variable rec_cv;
|
||||||
std::deque<std::unique_ptr<Image>> rec_frames;
|
std::deque<std::unique_ptr<Image>> rec_frames;
|
||||||
std::unique_ptr<MP4Encoder> m_encoder;
|
|
||||||
|
|
||||||
RTT uirtt;
|
RTT uirtt;
|
||||||
Sampler sampler;
|
Sampler sampler;
|
||||||
|
|||||||
@@ -89,8 +89,11 @@ void App::tick(float dt)
|
|||||||
canvas->m_canvas->m_dirty_stroke = false;
|
canvas->m_canvas->m_dirty_stroke = false;
|
||||||
LOG("rec tick");
|
LOG("rec tick");
|
||||||
|
|
||||||
|
Texture2D equirect;
|
||||||
|
App::I->render_task([&] {
|
||||||
Canvas::I->draw_merge(true);
|
Canvas::I->draw_merge(true);
|
||||||
Texture2D equirect = Canvas::I->m_layers_merge.gen_equirect({ 1024, 512 });
|
equirect = Canvas::I->m_layers_merge.gen_equirect({ 1024, 512 });
|
||||||
|
});
|
||||||
auto img = std::make_unique<Image>(equirect.get_image());
|
auto img = std::make_unique<Image>(equirect.get_image());
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(rec_mutex);
|
std::lock_guard<std::mutex> lock(rec_mutex);
|
||||||
|
|||||||
@@ -1644,6 +1644,9 @@ bool Canvas::create(int width, int height)
|
|||||||
m_merge_rtt.create(width, height);
|
m_merge_rtt.create(width, height);
|
||||||
m_merge_tex.create(width, height);
|
m_merge_tex.create(width, height);
|
||||||
m_unsaved = true;
|
m_unsaved = true;
|
||||||
|
|
||||||
|
timelapse_reset_encoder();
|
||||||
|
|
||||||
return true;
|
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<Serializer::Boolean>(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);
|
fclose(fp);
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
if (use_tmp)
|
if (use_tmp)
|
||||||
{
|
{
|
||||||
@@ -2415,6 +2438,33 @@ bool Canvas::project_open_thread(std::string file_path)
|
|||||||
|
|
||||||
std::swap(tmp_layers, m_layers);
|
std::swap(tmp_layers, m_layers);
|
||||||
|
|
||||||
|
if (ppi_header.doc_version.minor >= 4)
|
||||||
|
{
|
||||||
|
int bytes = 0;
|
||||||
|
fread(&bytes, sizeof(int), 1, fp);
|
||||||
|
std::vector<uint8_t> 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<Serializer::Boolean>("has_encoder"))
|
||||||
|
{
|
||||||
|
m_encoder = std::make_unique<MP4Encoder>();
|
||||||
|
sr >> *m_encoder;
|
||||||
|
m_encoder->init();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timelapse_reset_encoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timelapse_reset_encoder();
|
||||||
|
}
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
LOG("project restore from %s", file_path.c_str());
|
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_proj = c.m_proj;
|
||||||
m_vp = c.m_vp;
|
m_vp = c.m_vp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Canvas::timelapse_reset_encoder() noexcept
|
||||||
|
{
|
||||||
|
m_encoder = std::make_unique<MP4Encoder>();
|
||||||
|
int res = glm::min<int>(m_width / 2, 1024);
|
||||||
|
m_encoder->init(res * 4, res * 2, 30, 2000 << 10);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "canvas_actions.h"
|
#include "canvas_actions.h"
|
||||||
#include "canvas_modes.h"
|
#include "canvas_modes.h"
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
#include "mp4enc.h"
|
||||||
|
|
||||||
#if __WEB__
|
#if __WEB__
|
||||||
#define CANVAS_RES 512
|
#define CANVAS_RES 512
|
||||||
@@ -33,7 +34,8 @@ struct PPIDocVersion
|
|||||||
// version 1: initial
|
// version 1: initial
|
||||||
// version 2: added blend mode, alpha and visibility
|
// version 2: added blend mode, alpha and visibility
|
||||||
// version 3: added animation frames
|
// version 3: added animation frames
|
||||||
int minor = 3;
|
// version 4: (released in 0.2.3) add info struct
|
||||||
|
int minor = 4;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PPISoftVersion
|
struct PPISoftVersion
|
||||||
@@ -92,6 +94,7 @@ public:
|
|||||||
bool m_dirty = false;
|
bool m_dirty = false;
|
||||||
bool m_commit_delayed = false;
|
bool m_commit_delayed = false;
|
||||||
bool m_dirty_stroke = false;
|
bool m_dirty_stroke = false;
|
||||||
|
std::unique_ptr<MP4Encoder> m_encoder;
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_disrty_stroke_time;
|
std::chrono::time_point<std::chrono::steady_clock> m_disrty_stroke_time;
|
||||||
std::stack<CameraData> m_camera_stack;
|
std::stack<CameraData> m_camera_stack;
|
||||||
|
|
||||||
@@ -251,4 +254,5 @@ public:
|
|||||||
void pop_camera();
|
void pop_camera();
|
||||||
CameraData get_camera();
|
CameraData get_camera();
|
||||||
void set_camera(const CameraData& c);
|
void set_camera(const CameraData& c);
|
||||||
|
void timelapse_reset_encoder() noexcept;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ bool Image::read(BinaryStreamReader& r)
|
|||||||
auto img_raw = d.get<Serializer::RawData>("data");
|
auto img_raw = d.get<Serializer::RawData>("data");
|
||||||
int png_width, png_height, png_comp;
|
int png_width, png_height, png_comp;
|
||||||
m_data = std::unique_ptr<uint8_t[]>(stbi_load_from_memory(
|
m_data = std::unique_ptr<uint8_t[]>(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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ static void encoder_trace_callback(void* context, int level, const char* message
|
|||||||
LOG("ENCODER: %s", message);
|
LOG("ENCODER: %s", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MP4Encoder::~MP4Encoder()
|
||||||
|
{
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
bool MP4Encoder::init(int width, int height, int fps, int bitrate) noexcept
|
bool MP4Encoder::init(int width, int height, int fps, int bitrate) noexcept
|
||||||
{
|
{
|
||||||
if (WelsCreateSVCEncoder(&m_encoder) != 0)
|
if (WelsCreateSVCEncoder(&m_encoder) != 0)
|
||||||
@@ -75,6 +80,11 @@ bool MP4Encoder::init(int width, int height, int fps, int bitrate) noexcept
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MP4Encoder::init() noexcept
|
||||||
|
{
|
||||||
|
return init(m_width, m_height, m_framerate, m_bitrate);
|
||||||
|
}
|
||||||
|
|
||||||
bool MP4Encoder::encode(const Image& rgba) noexcept
|
bool MP4Encoder::encode(const Image& rgba) noexcept
|
||||||
{
|
{
|
||||||
// lazy allocation of YUV buffer
|
// lazy allocation of YUV buffer
|
||||||
@@ -167,7 +177,7 @@ bool MP4Encoder::write_mp4(const std::string& filename) const noexcept
|
|||||||
MP4SetTimeScale(mp4, 90000);
|
MP4SetTimeScale(mp4, 90000);
|
||||||
MP4SetVideoProfileLevel(mp4, 1);
|
MP4SetVideoProfileLevel(mp4, 1);
|
||||||
MP4TrackId mp4_track = MP4AddH264VideoTrack(mp4, 90000, 90000 / m_framerate, m_width, m_height,
|
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());
|
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());
|
MP4AddH264PictureParameterSet(mp4, mp4_track, m_header.PPS_data.data(), m_header.PPS_data.size());
|
||||||
for (const auto& f : m_frames)
|
for (const auto& f : m_frames)
|
||||||
@@ -196,3 +206,54 @@ void MP4Encoder::destroy() noexcept
|
|||||||
m_header.PPS_data.clear();
|
m_header.PPS_data.clear();
|
||||||
m_header.SPS_data.clear();
|
m_header.SPS_data.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MP4Encoder::read(BinaryStreamReader& r)
|
||||||
|
{
|
||||||
|
Serializer::Descriptor d;
|
||||||
|
r >> d;
|
||||||
|
d.value<Serializer::Integer>("width", m_width);
|
||||||
|
d.value<Serializer::Integer>("height", m_height);
|
||||||
|
d.value<Serializer::Integer>("bitrate", m_bitrate);
|
||||||
|
d.value<Serializer::Float>("framerate", m_framerate);
|
||||||
|
d.value<Serializer::RawData>("SPS", m_header.SPS_data);
|
||||||
|
d.value<Serializer::RawData>("PPS", m_header.PPS_data);
|
||||||
|
d.value<Serializer::Integer>("avc_profile", m_header.avc_profile);
|
||||||
|
d.value<Serializer::Integer>("avc_compat", m_header.avc_compat);
|
||||||
|
d.value<Serializer::Integer>("avc_level", m_header.avc_level);
|
||||||
|
auto frames = d.get<Serializer::List>("frames");
|
||||||
|
for (const auto& f : frames->items)
|
||||||
|
{
|
||||||
|
if (auto fd = std::dynamic_pointer_cast<Serializer::Descriptor>(f))
|
||||||
|
{
|
||||||
|
m_frames.emplace_back();
|
||||||
|
m_frames.back().type = (Frame::kType)fd->value<Serializer::Integer>("type");
|
||||||
|
m_frames.back().data = fd->value<Serializer::RawData>("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<Serializer::Integer>(m_width);
|
||||||
|
d.props["height"] = std::make_shared<Serializer::Integer>(m_height);
|
||||||
|
d.props["bitrate"] = std::make_shared<Serializer::Integer>(m_bitrate);
|
||||||
|
d.props["framerate"] = std::make_shared<Serializer::Float>(m_framerate);
|
||||||
|
d.props["SPS"] = std::make_shared<Serializer::RawData>(m_header.SPS_data);
|
||||||
|
d.props["PPS"] = std::make_shared<Serializer::RawData>(m_header.PPS_data);
|
||||||
|
d.props["avc_profile"] = std::make_shared<Serializer::Integer>(m_header.avc_profile);
|
||||||
|
d.props["avc_compat"] = std::make_shared<Serializer::Integer>(m_header.avc_compat);
|
||||||
|
d.props["avc_level"] = std::make_shared<Serializer::Integer>(m_header.avc_level);
|
||||||
|
auto frames = std::make_shared<Serializer::List>();
|
||||||
|
for (const auto& f : m_frames)
|
||||||
|
{
|
||||||
|
auto fd = frames->add<Serializer::Descriptor>();
|
||||||
|
fd->props["type"] = std::make_shared<Serializer::Integer>((int)f.type);
|
||||||
|
fd->props["data"] = std::make_shared<Serializer::RawData>(f.data);
|
||||||
|
}
|
||||||
|
d.props["frames"] = frames;
|
||||||
|
w << d;
|
||||||
|
}
|
||||||
|
|||||||
10
src/mp4enc.h
10
src/mp4enc.h
@@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "image.h"
|
#include "image.h"
|
||||||
|
#include "serializer.h"
|
||||||
|
|
||||||
class MP4Encoder
|
class MP4Encoder : public Serializer::Type
|
||||||
{
|
{
|
||||||
struct Frame
|
struct Frame
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ class MP4Encoder
|
|||||||
std::vector<uint8_t> SPS_data;
|
std::vector<uint8_t> SPS_data;
|
||||||
std::vector<uint8_t> PPS_data;
|
std::vector<uint8_t> PPS_data;
|
||||||
uint8_t avc_profile;
|
uint8_t avc_profile;
|
||||||
uint8_t avc_profile_compat;
|
uint8_t avc_compat;
|
||||||
uint8_t avc_level;
|
uint8_t avc_level;
|
||||||
};
|
};
|
||||||
class ISVCEncoder* m_encoder = nullptr;
|
class ISVCEncoder* m_encoder = nullptr;
|
||||||
@@ -26,8 +27,13 @@ class MP4Encoder
|
|||||||
std::vector<Frame> m_frames;
|
std::vector<Frame> m_frames;
|
||||||
std::vector<uint8_t> m_yuv_buffer;
|
std::vector<uint8_t> m_yuv_buffer;
|
||||||
public:
|
public:
|
||||||
|
~MP4Encoder();
|
||||||
|
bool init() noexcept;
|
||||||
bool init(int width, int height, int fps, int bitrate) noexcept;
|
bool init(int width, int height, int fps, int bitrate) noexcept;
|
||||||
bool encode(const Image& rgba) noexcept;
|
bool encode(const Image& rgba) noexcept;
|
||||||
bool write_mp4(const std::string& filename) const noexcept;
|
bool write_mp4(const std::string& filename) const noexcept;
|
||||||
void destroy() 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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -414,23 +414,23 @@ public:
|
|||||||
};
|
};
|
||||||
struct RawData : public Type
|
struct RawData : public Type
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> value;
|
||||||
RawData() = default;
|
RawData() = default;
|
||||||
//RawData(const RawData&) = delete;
|
//RawData(const RawData&) = delete;
|
||||||
RawData(std::vector<uint8_t> data) : data(data) { }
|
RawData(std::vector<uint8_t> data) : value(data) { }
|
||||||
virtual std::string type_key() const override { return "tdta"; }
|
virtual std::string type_key() const override { return "tdta"; }
|
||||||
virtual std::string str(int indent, const std::string& prefix) const override
|
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
|
virtual bool read(BinaryStreamReader& r) override
|
||||||
{
|
{
|
||||||
data = r.rraw();
|
value = r.rraw();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
virtual void write(BinaryStreamWriter& w) const override
|
virtual void write(BinaryStreamWriter& w) const override
|
||||||
{
|
{
|
||||||
w.wraw(data);
|
w.wraw(value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct Descriptor : public Type
|
struct Descriptor : public Type
|
||||||
|
|||||||
Reference in New Issue
Block a user