374{
375 FFmpegMuxContext mux;
376 VideoEncodeContext enc;
377
378 auto fail = [&](std::string msg) {
380 m_open.store(
false, std::memory_order_release);
382 };
383
384 if (!mux.open(filepath)) {
385 fail(mux.last_error());
386 return;
387 }
388 if (!enc.open(mux,
width, height, frame_rate, src_fmt, codec_id)) {
389 fail(enc.last_error());
390 return;
391 }
392 if (!mux.write_header()) {
393 fail(mux.last_error());
394 return;
395 }
396
397 m_open.store(
true, std::memory_order_release);
398
400 "[VideoFileWriter] worker started: '{}' {}x{} @{:.3f}fps",
401 filepath,
width, height, frame_rate);
402
403 while (true) {
404 auto item_opt =
m_queue->pop();
405 if (!item_opt) {
406 std::this_thread::sleep_for(std::chrono::microseconds(100));
407 continue;
408 }
409
410 bool done = std::visit([&](
auto&
cmd) ->
bool {
411 using T = std::decay_t<
decltype(
cmd)>;
412
413 if constexpr (std::is_same_v<T, RawFrame>) {
414 if (!enc.encode_frame(
cmd.pixels.data(),
cmd.pixels.size(),
415 cmd.width,
cmd.height, mux)) {
418 "[VideoFileWriter] encode_frame failed: {}", enc.last_error());
419 }
420 return false;
421 }
422
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));
430 return false;
431 }
432
433 const auto& cpu =
cmd.buffer->get_pixel_data();
434 if (!cpu.empty()) {
435 if (!enc.encode_frame(cpu.data(), cpu.size(),
436 cmd.buffer->get_width(),
cmd.buffer->get_height(), mux)) {
439 "[VideoFileWriter] encode_frame (cpu) failed: {}", enc.last_error());
440 }
441 return false;
442 }
443
444 auto tex =
cmd.buffer->get_texture();
445 if (!tex) {
447 "[VideoFileWriter] DownloadCmd: no CPU pixels and no GPU texture");
448 return false;
449 }
450
451 using Portal::Graphics::TextureLoom;
452 const size_t mip0_bytes = static_cast<size_t>(tex->get_width())
453 * tex->get_height()
454 * TextureLoom::get_bytes_per_pixel(img_fmt);
455
456 if (mip0_bytes == 0)
457 return false;
458
459 std::vector<uint8_t>
pixels(mip0_bytes);
460 TextureLoom::instance().download_data_async(tex,
pixels.data(), mip0_bytes);
461
463 tex->get_width(), tex->get_height(), mux)) {
466 "[VideoFileWriter] encode_frame (gpu) failed: {}", enc.last_error());
467 }
468 return false;
469 }
470
471 return static_cast<bool>(std::is_same_v<T, CloseCmd>);
472 },
473 *item_opt);
474
475 if (done)
476 break;
477 }
478
479 bool ok = enc.drain(mux);
480 if (!ok) {
483 "[VideoFileWriter] drain failed: {}", enc.last_error());
484 }
485
486 mux.close();
487 m_open.store(
false, std::memory_order_release);
489
491 "[VideoFileWriter] worker finished: '{}' status={}",
492 filepath, ok ? "ok" : "error");
493}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
const std::vector< float > * pixels
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
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.