MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
DynamicVideoStream.cpp
Go to the documentation of this file.
2
7
9
10namespace MayaFlux::Kakshya {
11
13 : VideoStreamContainer(spec.width, spec.height, spec.format, spec.frame_rate)
14{
15 if (spec.ring_frames > 0) {
17 }
18}
19
20std::vector<uint8_t>& DynamicVideoStream::flat_bytes()
21{
22 if (m_data.empty()) {
23 m_data.resize(1);
24 }
25
26 if (!std::holds_alternative<std::vector<uint8_t>>(m_data[0])) {
27 m_data[0].emplace<std::vector<uint8_t>>();
28 }
29
30 return std::get<std::vector<uint8_t>>(m_data[0]);
31}
32
45
47{
48 const size_t frame_bytes = get_frame_byte_size();
49
50 if (frame_bytes == 0) {
51 return nullptr;
52 }
53
54 if (is_ring_mode()) {
56 }
57
59
60 auto& bytes = flat_bytes();
61 const size_t needed = (m_write_head + 1) * frame_bytes;
62 if (bytes.size() < needed) {
63 bytes.resize(needed);
64 }
65
66 return bytes.data() + m_write_head * frame_bytes;
67}
68
78
79bool DynamicVideoStream::append_frame(std::span<const uint8_t> pixels)
80{
81 const size_t frame_bytes = get_frame_byte_size();
82
83 if (frame_bytes == 0 || pixels.size() != frame_bytes) {
85 "DynamicVideoStream::append_frame expected {} bytes, got {}", frame_bytes, pixels.size());
86 return false;
87 }
88
89 uint8_t* slot = claim_frame_slot();
90 if (!slot) {
91 return false;
92 }
93
94 std::memcpy(slot, pixels.data(), frame_bytes);
96 return true;
97}
98
99bool DynamicVideoStream::append_frame(const std::shared_ptr<Core::VKImage>& image,
100 const std::shared_ptr<Buffers::VKBuffer>& staging)
101{
102 if (!image || !image->is_initialized()) {
104 "DynamicVideoStream::append_frame called with an uninitialised image");
105 return false;
106 }
107
108 uint8_t* slot = claim_frame_slot();
109 if (!slot) {
110 return false;
111 }
112
114 finish_frame();
115 return true;
116}
117
119{
120 if (image.width != m_width || image.height != m_height || image.format != m_format) {
122 "DynamicVideoStream::append_frame image {}x{} does not match stream {}x{}",
123 image.width, image.height, m_width, m_height);
124 return false;
125 }
126
127 return append_frame(std::span<const uint8_t>(
128 static_cast<const uint8_t*>(image.data()), image.byte_size()));
129}
130
132{
133 if (capacity == 0 || capacity > UINT32_MAX || m_width == 0 || m_height == 0) {
135 "DynamicVideoStream::enable_circular_buffer needs a capacity between 1 and {} and a non-empty frame size",
136 UINT32_MAX);
137 return;
138 }
139
140 setup_ring(capacity, static_cast<uint32_t>(capacity), m_width, m_height, m_format, m_frame_rate, 0);
141
142 m_total_source_frames = std::numeric_limits<uint64_t>::max();
143 m_write_head = 0;
145}
146
148{
149 if (is_ring_mode()) {
150 return;
151 }
152
154 flat_bytes().reserve(frames * get_frame_byte_size());
155}
156
157std::shared_ptr<DynamicVideoStream> DynamicVideoStream::snapshot(uint64_t frames) const
158{
159 const uint64_t available = is_ring_mode()
160 ? std::min<uint64_t>(m_write_head, m_ring_capacity)
161 : m_write_head;
162 const uint64_t count = std::min(frames, available);
163
164 if (count == 0) {
165 return nullptr;
166 }
167
168 auto copy = std::make_shared<DynamicVideoStream>(VideoStreamSpec {
169 .width = m_width,
170 .height = m_height,
171 .format = m_format,
172 .frame_rate = m_frame_rate });
173 copy->ensure_capacity(count);
174
175 const std::vector<uint8_t> blank(get_frame_byte_size(), 0);
176
177 for (uint64_t index = m_write_head - count; index < m_write_head; ++index) {
178 const auto pixels = get_frame_pixels(index);
179 copy->append_frame(pixels.empty() ? std::span<const uint8_t>(blank) : pixels);
180 }
181
182 return copy;
183}
184
186{
187 auto processor = std::make_shared<FrameSeekProcessor>();
188 if (get_frame_rate() > 0.0) {
189 processor->set_global_fps(get_frame_rate());
190 }
191 set_default_processor(processor);
192}
193
194std::shared_ptr<FrameSeekProcessor> DynamicVideoStream::seek_processor()
195{
196 auto seek = std::dynamic_pointer_cast<FrameSeekProcessor>(get_default_processor());
197 if (!seek) {
199 seek = std::dynamic_pointer_cast<FrameSeekProcessor>(get_default_processor());
200 }
201 return seek;
202}
203
205{
206 seek_processor()->trail(static_cast<double>(m_write_head), frames);
207}
208
210{
211 const uint32_t ring_capacity = m_ring_capacity;
212
214 m_write_head = 0;
215
216 if (ring_capacity > 0) {
217 enable_circular_buffer(ring_capacity);
218 }
219}
220
221} // namespace MayaFlux::Kakshya
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:47
const std::vector< float > * pixels
Definition Decoder.cpp:48
bool format
bool available
Definition VKDevice.cpp:41
uint32_t index
Definition VKDevice.cpp:122
size_t count
uint32_t width
uint32_t height
void enable_circular_buffer(uint64_t capacity)
Keep only the most recent capacity frames.
void ensure_capacity(uint64_t frames)
Pre-allocate room for frames in linear mode.
void create_default_processor() override
Install a FrameSeekProcessor as the default processor.
void lag_behind_head(double frames)
Play back a fixed number of frames behind the write head.
std::shared_ptr< DynamicVideoStream > snapshot(uint64_t frames) const
Copy the most recent frames into a new linear stream, oldest first.
void clear() override
Drop every frame and restart the write head at zero.
DynamicVideoStream(const VideoStreamSpec &spec)
Build a stream from a spec, enabling the ring when it asks for one.
bool append_frame(std::span< const uint8_t > pixels)
Write one frame at the write head and advance it.
std::shared_ptr< FrameSeekProcessor > seek_processor()
The FrameSeekProcessor that reads this stream, installed on first use.
uint8_t * mutable_slot_ptr(uint64_t frame_index)
Mutable pointer into m_data[0] for the decode thread to write into.
ProcessingState get_processing_state() const override
Get the current processing state of the container.
bool is_ring_mode() const
True if the container is operating in ring mode.
void clear() override
Clear all data in the container.
std::shared_ptr< DataProcessor > get_default_processor() const override
Get the current default data processor.
void setup_ring(uint64_t total_frames, uint32_t ring_capacity, uint32_t width, uint32_t height, ImageFormat format, double frame_rate, uint32_t refill_threshold, uint64_t reader_id=0)
Allocate m_data[0] as a ring of ring_capacity frames.
std::span< const uint8_t > get_frame_pixels(uint64_t frame_index) const
Get raw pixel data for a single frame as a byte span.
void set_default_processor(const std::shared_ptr< DataProcessor > &processor) override
Set the default data processor for this container.
void mark_ready_for_processing(bool ready) override
Mark the container as ready or not ready for processing.
size_t get_frame_byte_size() const
Total byte size of one frame: width * height * bytes_per_pixel.
void commit_frame(uint64_t frame_index)
Publish a decoded frame.
Concrete base implementation for streaming video containers.
RAII guard that brackets a Seqlock write region.
Definition SeqLock.hpp:155
void download_data(const std::shared_ptr< Core::VKImage > &image, void *data, size_t size, const std::shared_ptr< Buffers::VKBuffer > &staging, bool deferred=false, vk::ImageLayout restore_layout=vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlags restore_stage=vk::PipelineStageFlagBits::eFragmentShader)
Download pixel data from a VKImage, reusing a caller-supplied persistent staging buffer.
@ Configuration
Configuration and parameter updates.
@ ContainerProcessing
Container operations (Kakshya - file/stream/region processing)
@ Kakshya
Containers[Signalsource, Stream, File], Regions, DataProcessors.
@ IDLE
Container is inactive with no data or not ready for processing.
Raw image data loaded from file or read back from a GPU image.
Definition ImageData.hpp:55
Describes a DynamicVideoStream so it can be built by designated initializer.