23#include <libavutil/pixfmt.h>
30 AVPixelFormat vk_format_to_avpixfmt(uint32_t vk_fmt_uint)
32 switch (
static_cast<vk::Format
>(vk_fmt_uint)) {
33 case vk::Format::eR8G8B8A8Unorm:
34 case vk::Format::eR8G8B8A8Srgb:
35 return AV_PIX_FMT_RGBA;
36 case vk::Format::eB8G8R8A8Unorm:
37 case vk::Format::eB8G8R8A8Srgb:
38 return AV_PIX_FMT_BGRA;
39 case vk::Format::eR16G16B16A16Sfloat:
40 return AV_PIX_FMT_RGBA64LE;
42 return AV_PIX_FMT_BGRA;
52 return AV_PIX_FMT_RGBA;
55 return AV_PIX_FMT_BGRA;
57 return AV_PIX_FMT_RGB24;
60 return AV_PIX_FMT_RGBA64LE;
62 return AV_PIX_FMT_RGBAF32LE;
64 return AV_PIX_FMT_NONE;
75 : m_queue(
std::make_unique<Memory::LockFreeQueue<
WorkItem, k_queue_capacity>>())
83 }
else if (
m_open.load(std::memory_order_acquire) && !
m_closing.exchange(
true)) {
85 if (fut.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) {
87 "VideoFileWriter destructor timed out; worker detached, file may be incomplete");
101 const std::string& filepath,
112 if (!svc || !svc->register_frame_observer) {
113 set_error(
"record: DisplayService unavailable");
126 if (!window->is_capture_enabled()) {
127 window->set_capture_enabled(
true);
131 auto handle = std::static_pointer_cast<void>(window);
133 uint32_t obs_id = svc->register_frame_observer(handle,
134 [
this](
const std::shared_ptr<std::vector<uint8_t>>& buf,
135 uint32_t w, uint32_t
h, uint32_t vk_fmt) {
136 if (!buf || buf->empty())
140 const AVPixelFormat av_fmt = vk_format_to_avpixfmt(vk_fmt);
144 "[VideoFileWriter] record: failed to open encoder for "
152 if (!
m_open.load(std::memory_order_acquire))
156 .
pixels = std::vector<uint8_t>(buf->begin(), buf->end()),
162 set_error(
"record: register_frame_observer returned 0 — "
163 "capture not yet active for this window");
165 window->set_capture_enabled(
false);
175 "[VideoFileWriter] record: observer {} registered for '{}' -> '{}'",
176 obs_id, window->get_create_info().title, filepath);
183 const uint32_t obs_id =
m_observer_id.exchange(0, std::memory_order_acq_rel);
201 if (
m_open.load(std::memory_order_acquire))
204 std::promise<bool> p;
206 return p.get_future();
217 AVPixelFormat src_pixel_format,
218 AVCodecID explicit_codec)
220 if (
m_open.load(std::memory_order_acquire)) {
221 set_error(
"open() called while already open");
229 m_closing.store(
false, std::memory_order_release);
232 filepath,
width,
height, frame_rate, src_pixel_format, explicit_codec);
234 constexpr int k_spin_ms = 500;
235 constexpr int k_sleep_us = 500;
236 for (
int i = 0; i < (k_spin_ms * 1000 / k_sleep_us); ++i) {
237 if (
m_open.load(std::memory_order_acquire))
239 std::this_thread::sleep_for(std::chrono::microseconds(k_sleep_us));
260 if (!
m_open.load(std::memory_order_acquire) || !
pixels || size == 0)
270 if (!
m_open.load(std::memory_order_acquire) ||
pixels.empty())
285 if (!
m_open.load(std::memory_order_acquire) || !container)
288 auto span = container->pixel_bytes(layer);
291 "VideoFileWriter::write(TextureContainer): pixel_bytes empty for layer {}",
297 .
pixels = std::vector<uint8_t>(span.begin(), span.end()),
298 .width = container->get_width(),
299 .height = container->get_height() });
307 uint64_t frame_index)
309 if (!
m_open.load(std::memory_order_acquire) || !container)
312 auto span = container->get_frame_pixels(frame_index);
315 "VideoFileWriter::write(VideoStreamContainer): get_frame_pixels({}) returned empty",
321 .
pixels = std::vector<uint8_t>(span.begin(), span.end()),
322 .width = container->get_width(),
323 .height = container->get_height() });
332 if (!
m_open.load(std::memory_order_acquire) || !buffer)
335 if (buffer->get_pixel_data().empty() && !buffer->has_texture()) {
337 "VideoFileWriter::write(TextureBuffer): no CPU pixels and no GPU texture");
373 AVPixelFormat src_fmt,
379 auto fail = [&](std::string msg) {
381 m_open.store(
false, std::memory_order_release);
385 if (!mux.
open(filepath)) {
398 m_open.store(
true, std::memory_order_release);
401 "[VideoFileWriter] worker started: '{}' {}x{} @{:.3f}fps",
405 auto item_opt =
m_queue->pop();
407 std::this_thread::sleep_for(std::chrono::microseconds(100));
411 bool done = std::visit([&](
auto&
cmd) ->
bool {
412 using T = std::decay_t<
decltype(
cmd)>;
414 if constexpr (std::is_same_v<T, RawFrame>) {
416 cmd.width,
cmd.height, mux)) {
419 "[VideoFileWriter] encode_frame failed: {}", enc.
last_error());
424 if constexpr (std::is_same_v<T, DownloadCmd>) {
425 const auto img_fmt =
cmd.buffer->get_format();
426 const AVPixelFormat av_fmt = image_format_to_avpixfmt(img_fmt);
427 if (av_fmt == AV_PIX_FMT_NONE) {
429 "[VideoFileWriter] DownloadCmd: unsupported ImageFormat {}",
430 static_cast<int>(img_fmt));
434 const auto& cpu =
cmd.buffer->get_pixel_data();
437 cmd.buffer->get_width(),
cmd.buffer->get_height(), mux)) {
440 "[VideoFileWriter] encode_frame (cpu) failed: {}", enc.
last_error());
445 auto tex =
cmd.buffer->get_texture();
448 "[VideoFileWriter] DownloadCmd: no CPU pixels and no GPU texture");
453 const size_t mip0_bytes =
static_cast<size_t>(tex->get_width())
455 * TextureLoom::get_bytes_per_pixel(img_fmt);
460 std::vector<uint8_t>
pixels(mip0_bytes);
469 tex->get_width(), tex->get_height(), mux)) {
472 "[VideoFileWriter] encode_frame (gpu) failed: {}", enc.
last_error());
477 return static_cast<bool>(std::is_same_v<T, CloseCmd>);
485 bool ok = enc.
drain(mux);
489 "[VideoFileWriter] drain failed: {}", enc.
last_error());
493 m_open.store(
false, std::memory_order_release);
497 "[VideoFileWriter] worker finished: '{}' status={}",
498 filepath, ok ?
"ok" :
"error");
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
const std::vector< float > * pixels
bool open(const std::string &filepath, const std::string &explicit_format={})
Allocate an output context and open the avio layer for writing.
bool write_header()
Write the container header to the output file.
const std::string & last_error() const
void close()
Write the container trailer, flush avio, and release all resources.
RAII owner of a single AVFormatContext on the write path.
const std::string & last_error() const
bool open(FFmpegMuxContext &mux, uint32_t width, uint32_t height, double frame_rate, AVPixelFormat src_pixel_format, AVCodecID codec_id)
Open the encoder and register a video stream in the mux context.
bool drain(FFmpegMuxContext &mux)
Flush all buffered frames from the encoder to the mux.
bool encode_frame(const uint8_t *src_data, size_t src_size, uint32_t src_width, uint32_t src_height, FFmpegMuxContext &mux)
Encode one raw pixel frame into the mux context.
RAII owner of one video stream's encoder and pixel-format converter on the write path.
std::shared_ptr< Buffers::VKBuffer > m_staging_buffer
double m_capture_frame_rate
void write(const uint8_t *pixels, size_t size)
bool open(const std::string &filepath, uint32_t width, uint32_t height, double frame_rate, AVPixelFormat src_pixel_format, AVCodecID explicit_codec=AV_CODEC_ID_NONE)
bool post(const WorkItem &item)
std::future< bool > stop_recording()
bool record(const std::shared_ptr< Core::Window > &window, const std::string &filepath, double frame_rate, AVCodecID codec_id=AV_CODEC_ID_NONE)
void worker_loop(const std::string &filepath, uint32_t width, uint32_t height, double frame_rate, AVPixelFormat src_fmt, AVCodecID codec_id)
std::atomic< uint32_t > m_observer_id
std::atomic< bool > m_capture_opened
std::shared_ptr< Core::Window > m_capture_window
std::variant< RawFrame, DownloadCmd, CloseCmd > WorkItem
void set_error(std::string msg)
std::atomic< bool > m_open
std::string m_capture_filepath
AVCodecID m_capture_codec_id
std::future< bool > close()
bool m_capture_did_enable
std::atomic< bool > m_closing
std::unique_ptr< Memory::LockFreeQueue< WorkItem, k_queue_capacity > > m_queue
std::string last_error() const
std::promise< bool > m_close_promise
Portal-level texture creation and management.
Interface * get_service()
Query for a backend service.
static BackendRegistry & instance()
Get the global registry instance.
std::shared_ptr< VKBuffer > create_image_staging_buffer(size_t size)
Allocate a persistent host-visible staging buffer sized for repeated streaming uploads to an image of...
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
ImageFormat
User-friendly image format enum.
std::shared_ptr< Buffers::TextureBuffer > buffer
std::vector< uint8_t > pixels
std::function< void(const std::shared_ptr< void > &, uint32_t)> unregister_frame_observer
Unregister a previously registered per-frame observer.
Backend display and presentation service interface.