save timelapse stream to file

This commit is contained in:
2019-11-02 18:05:24 +01:00
parent 7db1739df6
commit 83ba717d5b
9 changed files with 150 additions and 23 deletions

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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