22#include <libavutil/pixfmt.h>
29 AVPixelFormat vk_format_to_avpixfmt(uint32_t vk_fmt_uint)
31 switch (
static_cast<vk::Format
>(vk_fmt_uint)) {
32 case vk::Format::eR8G8B8A8Unorm:
33 case vk::Format::eR8G8B8A8Srgb:
34 return AV_PIX_FMT_RGBA;
35 case vk::Format::eB8G8R8A8Unorm:
36 case vk::Format::eB8G8R8A8Srgb:
37 return AV_PIX_FMT_BGRA;
38 case vk::Format::eR16G16B16A16Sfloat:
39 return AV_PIX_FMT_RGBA64LE;
41 return AV_PIX_FMT_BGRA;
51 return AV_PIX_FMT_RGBA;
54 return AV_PIX_FMT_BGRA;
56 return AV_PIX_FMT_RGB24;
59 return AV_PIX_FMT_RGBA64LE;
61 return AV_PIX_FMT_RGBAF32LE;
63 return AV_PIX_FMT_NONE;
74 : m_queue(
std::make_unique<Memory::LockFreeQueue<
WorkItem, k_queue_capacity>>())
82 }
else if (
m_open.load(std::memory_order_acquire) && !
m_closing.exchange(
true)) {
84 if (fut.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) {
86 "VideoFileWriter destructor timed out; worker detached, file may be incomplete");
100 const std::string& filepath,
111 if (!svc || !svc->register_frame_observer) {
112 set_error(
"record: DisplayService unavailable");
125 if (!window->is_capture_enabled()) {
126 window->set_capture_enabled(
true);
130 auto handle = std::static_pointer_cast<void>(window);
132 uint32_t obs_id = svc->register_frame_observer(handle,
133 [
this](
const std::shared_ptr<std::vector<uint8_t>>& buf,
134 uint32_t w, uint32_t
h, uint32_t vk_fmt) {
135 if (!buf || buf->empty())
139 const AVPixelFormat av_fmt = vk_format_to_avpixfmt(vk_fmt);
143 "[VideoFileWriter] record: failed to open encoder for "
151 if (!
m_open.load(std::memory_order_acquire))
155 .
pixels = std::vector<uint8_t>(buf->begin(), buf->end()),
161 set_error(
"record: register_frame_observer returned 0 — "
162 "capture not yet active for this window");
164 window->set_capture_enabled(
false);
174 "[VideoFileWriter] record: observer {} registered for '{}' -> '{}'",
175 obs_id, window->get_create_info().title, filepath);
182 const uint32_t obs_id =
m_observer_id.exchange(0, std::memory_order_acq_rel);
200 if (
m_open.load(std::memory_order_acquire))
203 std::promise<bool> p;
205 return p.get_future();
216 AVPixelFormat src_pixel_format,
217 AVCodecID explicit_codec)
219 if (
m_open.load(std::memory_order_acquire)) {
220 set_error(
"open() called while already open");
228 m_closing.store(
false, std::memory_order_release);
231 filepath,
width, height, frame_rate, src_pixel_format, explicit_codec);
233 constexpr int k_spin_ms = 500;
234 constexpr int k_sleep_us = 500;
235 for (
int i = 0; i < (k_spin_ms * 1000 / k_sleep_us); ++i) {
236 if (
m_open.load(std::memory_order_acquire))
238 std::this_thread::sleep_for(std::chrono::microseconds(k_sleep_us));
259 if (!
m_open.load(std::memory_order_acquire) || !
pixels || size == 0)
269 if (!
m_open.load(std::memory_order_acquire) ||
pixels.empty())
284 if (!
m_open.load(std::memory_order_acquire) || !container)
287 auto span = container->pixel_bytes(layer);
290 "VideoFileWriter::write(TextureContainer): pixel_bytes empty for layer {}",
296 .
pixels = std::vector<uint8_t>(span.begin(), span.end()),
297 .width = container->get_width(),
298 .height = container->get_height() });
306 uint64_t frame_index)
308 if (!
m_open.load(std::memory_order_acquire) || !container)
311 auto span = container->get_frame_pixels(frame_index);
314 "VideoFileWriter::write(VideoStreamContainer): get_frame_pixels({}) returned empty",
320 .
pixels = std::vector<uint8_t>(span.begin(), span.end()),
321 .width = container->get_width(),
322 .height = container->get_height() });
331 if (!
m_open.load(std::memory_order_acquire) || !buffer)
334 if (buffer->get_pixel_data().empty() && !buffer->has_texture()) {
336 "VideoFileWriter::write(TextureBuffer): no CPU pixels and no GPU texture");
372 AVPixelFormat src_fmt,
378 auto fail = [&](std::string msg) {
380 m_open.store(
false, std::memory_order_release);
384 if (!mux.
open(filepath)) {
388 if (!enc.
open(mux,
width, height, frame_rate, src_fmt, codec_id)) {
397 m_open.store(
true, std::memory_order_release);
400 "[VideoFileWriter] worker started: '{}' {}x{} @{:.3f}fps",
401 filepath,
width, height, frame_rate);
404 auto item_opt =
m_queue->pop();
406 std::this_thread::sleep_for(std::chrono::microseconds(100));
410 bool done = std::visit([&](
auto&
cmd) ->
bool {
411 using T = std::decay_t<
decltype(
cmd)>;
413 if constexpr (std::is_same_v<T, RawFrame>) {
415 cmd.width,
cmd.height, mux)) {
418 "[VideoFileWriter] encode_frame failed: {}", enc.
last_error());
423 if constexpr (std::is_same_v<T, DownloadCmd>) {
424 const auto img_fmt =
cmd.buffer->get_format();
425 const AVPixelFormat av_fmt = image_format_to_avpixfmt(img_fmt);
426 if (av_fmt == AV_PIX_FMT_NONE) {
428 "[VideoFileWriter] DownloadCmd: unsupported ImageFormat {}",
429 static_cast<int>(img_fmt));
433 const auto& cpu =
cmd.buffer->get_pixel_data();
436 cmd.buffer->get_width(),
cmd.buffer->get_height(), mux)) {
439 "[VideoFileWriter] encode_frame (cpu) failed: {}", enc.
last_error());
444 auto tex =
cmd.buffer->get_texture();
447 "[VideoFileWriter] DownloadCmd: no CPU pixels and no GPU texture");
452 const size_t mip0_bytes =
static_cast<size_t>(tex->get_width())
454 * TextureLoom::get_bytes_per_pixel(img_fmt);
459 std::vector<uint8_t>
pixels(mip0_bytes);
460 TextureLoom::instance().download_data_async(tex,
pixels.data(), mip0_bytes);
463 tex->get_width(), tex->get_height(), mux)) {
466 "[VideoFileWriter] encode_frame (gpu) failed: {}", enc.
last_error());
471 return static_cast<bool>(std::is_same_v<T, CloseCmd>);
479 bool ok = enc.
drain(mux);
483 "[VideoFileWriter] drain failed: {}", enc.
last_error());
487 m_open.store(
false, std::memory_order_release);
491 "[VideoFileWriter] worker finished: '{}' status={}",
492 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.
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.
@ 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.