This commit is contained in:
2026-08-26 14:11:26 +08:00
parent 513f838874
commit 6a6a106dda
9 changed files with 412 additions and 210 deletions
+32 -91
View File
@@ -2,17 +2,14 @@
#include <frame_statistics.hpp>
#include <render_common.hpp>
#include "detail/Gallery_Frame_Atlas.hpp"
#include <concurrentqueue-1.0.5/blockingconcurrentqueue.h>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <future>
#include <optional>
#include <stdexcept>
#include <string>
#include <thread>
#include <utility>
namespace aethera::web {
namespace {
@@ -46,71 +43,7 @@ std::string exception_description(const std::exception_ptr& failure) {
return "empty gallery video failure";
}
}
namespace detail {
/*
* FFmpeg 硬件编码 API 可能在驱动内部等待硬件队列。它仍是 Scene completion
* DAG 的一个业务节点,但不能占住 Kernel Taskflow Worker。每个图集只有一个
* H264_Encoder,因此用一个串行编码域维持 codec context 的唯一执行位置;
* 调用节点通过 Task_Graph::corun_until 协作让出,完成后恢复同一帧 DAG。
*/
struct Video_Encode_Domain final {
struct Work {
std::function<void()> function;
std::shared_ptr<std::promise<void>> completion;
};
public:
Video_Encode_Domain() : thread_([this] {
run();
}) {}
~Video_Encode_Domain() {
stopping_.store(true, std::memory_order_release);
if (thread_.joinable()) thread_.join();
}
Video_Encode_Domain(const Video_Encode_Domain&) = delete;
Video_Encode_Domain& operator=(const Video_Encode_Domain&) = delete;
void invoke(std::function<void()> function) {
if (!function) throw std::invalid_argument("video encode work is empty");
if (stopping_.load(std::memory_order_acquire)) throw std::runtime_error("video encode domain is stopping");
auto completion = std::make_shared<std::promise<void>>();
auto completed = completion->get_future();
auto work = std::make_unique<Work>(
Work{std::move(function), std::move(completion)});
if (!queue_.enqueue(std::move(work))) throw std::bad_alloc{};
Task_Graph::corun_until([&completed] {
return completed.wait_for(std::chrono::seconds(0)) ==
std::future_status::ready;
});
completed.get();
}
private:
void run() noexcept {
for (;;) {
std::unique_ptr<Work> work;
const bool received = queue_.wait_dequeue_timed(
work, std::chrono::milliseconds(1));
if (received && work) {
try {
work->function();
work->completion->set_value();
}
catch (...) {
try {
work->completion->set_exception(
std::current_exception());
}
catch (...) {}
}
}
if (!received && stopping_.load(std::memory_order_acquire)) return;
}
}
moodycamel::BlockingConcurrentQueue<std::unique_ptr<Work>> queue_{8};
std::atomic_bool stopping_{};
std::thread thread_;
};
}
Gallery_Video_Stream::Private::Private() : encoder(gallery_frame_rate),
encode_domain(std::make_unique<detail::Video_Encode_Domain>()) {}
Gallery_Video_Stream::Private::Private() : encoder(gallery_frame_rate) {}
Gallery_Video_Stream::Private::~Private() = default;
void Gallery_Video_Stream::Private::initialize(
std::vector<Plot_Entry> plots) {
@@ -331,27 +264,32 @@ void Gallery_Video_Stream::Private::compose_media_frame() {
}
void Gallery_Video_Stream::Private::encode_media_frame() {
if (!active_composition) return;
encode_domain->invoke([this] {
const auto started = std::chrono::steady_clock::now();
if (key_frame_requested.exchange(false,
std::memory_order_acq_rel))
encoder.request_key_frame();
active_video = encoder.encode(
active_composition->pixels, active_composition->width,
active_composition->height,
active_composition->layout == Plot_Pixel_Layout::bgra8
? Video_Pixel_Layout::bgra
: Video_Pixel_Layout::rgba,
active_encode_tick.sequence,
std::chrono::microseconds{
static_cast<std::int64_t>(
std::llround(active_encode_tick.time_milliseconds *
1'000.0))
});
static_cast<void>(encode_ms.submit(
std::chrono::duration<double, std::milli>(
std::chrono::steady_clock::now() - started).count()));
});
/*
* 直接在 FFmpeg.H264.encode Taskflow 节点中调用 FFmpeg。这样实际
* avcodec_send_frame/avcodec_receive_packet(以及硬件后端等待)全部落在
* 同一个业务节点的 wall/CPU 统计中,不再由额外编码线程隐藏真实耗时。
* gallery.video.frame 本身串行 compose -> encode -> publish,页面唯一
* H264_Encoder 因而仍只有一个执行位置,不需要额外 mutex 或专用线程。
*/
const auto started = std::chrono::steady_clock::now();
if (key_frame_requested.exchange(false,
std::memory_order_acq_rel))
encoder.request_key_frame();
active_video = encoder.encode(
active_composition->pixels, active_composition->width,
active_composition->height,
active_composition->layout == Plot_Pixel_Layout::bgra8
? Video_Pixel_Layout::bgra
: Video_Pixel_Layout::rgba,
active_encode_tick.sequence,
std::chrono::microseconds{
static_cast<std::int64_t>(
std::llround(active_encode_tick.time_milliseconds *
1'000.0))
});
static_cast<void>(encode_ms.submit(
std::chrono::duration<double, std::milli>(
std::chrono::steady_clock::now() - started).count()));
}
void Gallery_Video_Stream::Private::publish_media_frame() {
if (!active_video || !active_composition) return;
@@ -418,7 +356,7 @@ void Gallery_Video_Stream::bind_plots() {
});
compose.describe("owner", "gallery")
.describe("stage", "latest completed Plot frames to atlas");
auto encode = media->add("gallery.h264.encode", [weak] {
auto encode = media->add("FFmpeg.H264.encode", [weak] {
if (const auto owner = weak.lock()) {
auto& owner_data = static_cast<Private&>(*owner->d);
try {
@@ -430,7 +368,10 @@ void Gallery_Video_Stream::bind_plots() {
}
});
encode.describe("owner", "gallery")
.describe("stage", "H.264 encode in Scene completion pipeline");
.describe("stage", "FFmpeg H.264 encode in Scene completion pipeline")
.describe("backend", "FFmpeg")
.describe("codec", "H.264")
.describe("execution", "Taskflow worker");
auto publish = media->add("gallery.webrtc.publish", [weak] {
if (const auto owner = weak.lock()) {
auto& owner_data = static_cast<Private&>(*owner->d);
-4
View File
@@ -4,9 +4,6 @@
#include <atomic>
#include <chrono>
namespace aethera::web {
namespace detail {
struct Video_Encode_Domain;
}
struct Gallery_Video_Stream::Private : Prev_Private {
using Object = Impl<Gallery_Video_Stream>;
struct Source {
@@ -28,7 +25,6 @@ struct Gallery_Video_Stream::Private : Prev_Private {
std::vector<Source> sources{}; /* 已按业务标识排序的稳定图集来源。 */
std::unique_ptr<detail::Gallery_Frame_Atlas> atlas{}; /* 最近完成帧与 RGBA 图集的唯一状态源。 */
H264_Encoder encoder; /* 页面唯一 H.264 编码器。 */
std::unique_ptr<detail::Video_Encode_Domain> encode_domain{}; /* FFmpeg 驱动调用域。 */
Plot_Render_Tick active_encode_tick{}; /* 当前媒体 DAG 的输入时钟。 */
std::optional<detail::Gallery_Atlas_Composition> active_composition{}; /* 当前合成结果所有权。 */
std::optional<Encoded_Video_Frame> active_video{}; /* 当前编码结果所有权。 */
+3
View File
@@ -243,6 +243,9 @@ nlohmann::json taskflow_trace_json(const Taskflow_Frame_Trace& trace) {
{"completed_ms", task.completed_ms},
{"duration_ms", task.duration_ms},
{"cpu_duration_ms", task.cpu_duration_ms},
{"cpu_cycles", task.cpu_cycles},
{"cooperative_wait_ms", task.cooperative_wait_ms},
{"cpu_time_coarse", task.cpu_time_coarse},
{"observer_entry_ms", task.observer_entry_ms},
{"observer_exit_ms", task.observer_exit_ms},
{"observer_entry_cpu_ms", task.observer_entry_cpu_ms},