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);
|
||||
|
||||
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<NodeText>("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<MP4Encoder>();
|
||||
m_encoder->init(1024, 512, 30, 500 << 10);
|
||||
}
|
||||
while(rec_running)
|
||||
{
|
||||
std::unique_ptr<Image> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@ public:
|
||||
std::mutex rec_mutex;
|
||||
std::condition_variable rec_cv;
|
||||
std::deque<std::unique_ptr<Image>> rec_frames;
|
||||
std::unique_ptr<MP4Encoder> m_encoder;
|
||||
|
||||
RTT uirtt;
|
||||
Sampler sampler;
|
||||
|
||||
@@ -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<Image>(equirect.get_image());
|
||||
{
|
||||
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_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<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);
|
||||
|
||||
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<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);
|
||||
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<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_modes.h"
|
||||
#include <stack>
|
||||
#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<MP4Encoder> m_encoder;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_disrty_stroke_time;
|
||||
std::stack<CameraData> 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;
|
||||
};
|
||||
|
||||
@@ -209,7 +209,7 @@ bool Image::read(BinaryStreamReader& r)
|
||||
auto img_raw = d.get<Serializer::RawData>("data");
|
||||
int png_width, png_height, png_comp;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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
|
||||
#include "image.h"
|
||||
#include "serializer.h"
|
||||
|
||||
class MP4Encoder
|
||||
class MP4Encoder : public Serializer::Type
|
||||
{
|
||||
struct Frame
|
||||
{
|
||||
@@ -14,7 +15,7 @@ class MP4Encoder
|
||||
std::vector<uint8_t> SPS_data;
|
||||
std::vector<uint8_t> 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<Frame> m_frames;
|
||||
std::vector<uint8_t> 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;
|
||||
};
|
||||
|
||||
@@ -414,23 +414,23 @@ public:
|
||||
};
|
||||
struct RawData : public Type
|
||||
{
|
||||
std::vector<uint8_t> data;
|
||||
std::vector<uint8_t> value;
|
||||
RawData() = default;
|
||||
//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 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
|
||||
|
||||
Reference in New Issue
Block a user