375{
376 FFmpegMuxContext mux;
377 VideoEncodeContext enc;
378
379 auto fail = [&](std::string msg) {
381 m_open.store(
false, std::memory_order_release);
383 };
384
385 if (!mux.open(filepath)) {
386 fail(mux.last_error());
387 return;
388 }
389 if (!enc.open(mux,
width,
height, frame_rate, src_fmt, codec_id)) {
390 fail(enc.last_error());
391 return;
392 }
393 if (!mux.write_header()) {
394 fail(mux.last_error());
395 return;
396 }
397
398 m_open.store(
true, std::memory_order_release);
399
401 "[VideoFileWriter] worker started: '{}' {}x{} @{:.3f}fps",
403
404 while (true) {
405 auto item_opt =
m_queue->pop();
406 if (!item_opt) {
407 std::this_thread::sleep_for(std::chrono::microseconds(100));
408 continue;
409 }
410
411 bool done = std::visit([&](
auto&
cmd) ->
bool {
412 using T = std::decay_t<
decltype(
cmd)>;
413
414 if constexpr (std::is_same_v<T, RawFrame>) {
415 if (!enc.encode_frame(
cmd.pixels.data(),
cmd.pixels.size(),
416 cmd.width,
cmd.height, mux)) {
419 "[VideoFileWriter] encode_frame failed: {}", enc.last_error());
420 }
421 return false;
422 }
423
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));
431 return false;
432 }
433
434 const auto& cpu =
cmd.buffer->get_pixel_data();
435 if (!cpu.empty()) {
436 if (!enc.encode_frame(cpu.data(), cpu.size(),
437 cmd.buffer->get_width(),
cmd.buffer->get_height(), mux)) {
440 "[VideoFileWriter] encode_frame (cpu) failed: {}", enc.last_error());
441 }
442 return false;
443 }
444
445 auto tex =
cmd.buffer->get_texture();
446 if (!tex) {
448 "[VideoFileWriter] DownloadCmd: no CPU pixels and no GPU texture");
449 return false;
450 }
451
452 using Portal::Graphics::TextureLoom;
453 const size_t mip0_bytes = static_cast<size_t>(tex->get_width())
454 * tex->get_height()
455 * TextureLoom::get_bytes_per_pixel(img_fmt);
456
457 if (mip0_bytes == 0)
458 return false;
459
460 std::vector<uint8_t>
pixels(mip0_bytes);
461
464 }
465
467
469 tex->get_width(), tex->get_height(), mux)) {
472 "[VideoFileWriter] encode_frame (gpu) failed: {}", enc.last_error());
473 }
474 return false;
475 }
476
477 return static_cast<bool>(std::is_same_v<T, CloseCmd>);
478 },
479 *item_opt);
480
481 if (done)
482 break;
483 }
484
485 bool ok = enc.drain(mux);
486 if (!ok) {
489 "[VideoFileWriter] drain failed: {}", enc.last_error());
490 }
491
492 mux.close();
493 m_open.store(
false, std::memory_order_release);
495
497 "[VideoFileWriter] worker finished: '{}' status={}",
498 filepath, ok ? "ok" : "error");
499}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
const std::vector< float > * pixels
std::shared_ptr< Buffers::VKBuffer > m_staging_buffer
void set_error(std::string msg)
std::atomic< bool > m_open
std::unique_ptr< Memory::LockFreeQueue< WorkItem, k_queue_capacity > > m_queue
std::promise< bool > m_close_promise
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.