Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
1587ac6
Snapshot Manager and MP4Muxer
350d Jun 2, 2025
d93f9a5
Add intelligent write buffering for MP4 recording - 1MB memory buffer…
350d Jun 2, 2025
7dfdb04
Fix security issue: change file permissions from 0644 to 0600 - Resol…
350d Jun 2, 2025
30644ff
Enhanced MP4 Buffering & Fixed Code Duplication - MP4Muxer: Add intel…
350d Jun 2, 2025
0bf24b1
FIXED: Restore working MP4 creation with intelligent buffering - Rest…
350d Jun 2, 2025
7e72d56
Fix MP4Muxer destructor exception warning - Add noexcept specificatio…
350d Jun 2, 2025
bea0bb7
Improve MP4Muxer code quality for CodeFactor: Remove unused method de…
350d Jun 2, 2025
5e27975
Refactor MP4 muxer to use live555-compatible structure
350d Oct 2, 2025
098f807
Fix: Improve command-line argument parsing and device detection
350d Oct 2, 2025
6d52986
Fix: Add early debug output and fix -d option parsing
350d Oct 2, 2025
20e34d7
Fix: Implement proper MP4 moov box structure
350d Oct 2, 2025
802d4fc
Implement complete MP4 structure based on live555 QuickTimeFileSink
350d Oct 2, 2025
6e4c239
Fix: Write moov box at end of file (live555 approach)
350d Oct 2, 2025
8c191c6
Remove old MP4Muxer - replaced by QuickTimeMuxer
350d Oct 2, 2025
e898122
Refactor: Extract H.264 debug dump functionality to separate files
350d Oct 2, 2025
8ae3835
Simplify SnapshotManager with live555-based approach
350d Oct 2, 2025
a3f9614
Fix: Implement proper RTSPS/SRTP state tracking
350d Oct 2, 2025
8fbfa10
Fix: Add missing <cstdint> include in H264DebugDumper.cpp
350d Oct 2, 2025
616e617
Fix: Remove calls to deleted SnapshotManager methods
350d Oct 2, 2025
79eb5f9
Fix: Correct MP4 box order in snapshot (ftyp→mdat→moov)
350d Oct 2, 2025
0704e03
Fix: Add 4-byte length prefix to frame data in mdat box
350d Oct 2, 2025
6831681
Fix: Include SPS+PPS in mdat box for snapshot (matching old muxer)
350d Oct 2, 2025
fa7147f
Debug: Add detailed logging to snapshot MP4 creation
350d Oct 2, 2025
5e125e8
Debug: Add detailed logging to createFtypBox
350d Oct 2, 2025
8a85fbd
Debug: Add mp4Data logging in createMP4Snapshot
350d Oct 2, 2025
b959c43
Fix ftyp box size to 24 bytes to match actual content
350d Oct 2, 2025
f98ca9d
Fix stco offset search in moov box
350d Oct 2, 2025
d657ecd
Fix moov box structure - properly embed mvhd with size field
350d Oct 2, 2025
01c2789
Debug: Add detailed logging for stco search in moov
350d Oct 2, 2025
d498ed8
Fix stco search loop condition - use <= instead of <
350d Oct 2, 2025
7ed566f
Fix moov box assembly - properly prepend mvhd size field
350d Oct 2, 2025
8b9209f
Fix stbl box assembly - remove duplicate size fields
350d Oct 2, 2025
c4609af
Fix sample table box size calculation
350d Oct 2, 2025
24c1684
Fix stsz entry_sizes population for snapshots and recordings
350d Oct 2, 2025
75a5c8d
Update stsz entry sizes for recordings in writeMoovBox
350d Oct 2, 2025
55c13ec
Fix trak box assembly - create tkhdBox with size field
350d Oct 2, 2025
a126cf9
Fix mdia and minf box assembly - add size fields for child boxes
350d Oct 2, 2025
ea22cbd
Fix dinf box assembly - add size field for dref
350d Oct 2, 2025
baa83bd
Fix mvhd size calculation - use 4 + mvhd.size() instead of 8
350d Oct 2, 2025
26f121f
Add btrt (Bit Rate) box to avc1 for player compatibility
350d Oct 2, 2025
90aae72
Fix ftyp box: use isom as major brand with full compatible brands list
350d Oct 2, 2025
a4f8f3d
Fix avcC box structure: separate content from type/size
350d Oct 2, 2025
d12106b
Fix avc1 and stsd structure: add size placeholders, follow old MP4Mux…
350d Oct 2, 2025
2eba189
Fix sample table boxes: add size/type header to stts/stss/stsc/stsz/stco
350d Oct 2, 2025
d42e261
Fix mvhd timescale: use fps*1000 instead of fps
350d Oct 2, 2025
663951a
Fix tkhd size calculation: use 4+size instead of 8+size
350d Oct 2, 2025
8f92a8c
Fix all box size calculations: use 4+size instead of 8+size for mdhd/…
350d Oct 2, 2025
8e0a294
Fix dref size calculation: use 4+size instead of 8+size
350d Oct 2, 2025
d17a9e0
Fix stss/stsz dynamic sizes for multi-frame recordings
350d Oct 2, 2025
1a8242a
Prepend SPS/PPS to first frame in recording for proper playback
350d Oct 2, 2025
469871a
Fix: Remove SPS/PPS from recording mdat (standard MP4 structure)
350d Oct 3, 2025
3e5616d
Add ftruncate to remove garbage after moov box
350d Oct 3, 2025
447d664
Convert Annex-B to length-prefixed NALUs for MP4 recording
350d Oct 3, 2025
57371c8
Fix: Remove incorrect Annex-B conversion (input is already clean NAL …
350d Oct 3, 2025
e8ba3de
Fix: Calculate moov position correctly (ignore garbage at file end)
350d Oct 3, 2025
ea17715
Fix: Save m_currentPos before lseek operations
350d Oct 3, 2025
a2f791a
CRITICAL FIX: Update stco chunk offset to point to mdat data
350d Oct 3, 2025
12a97ad
Fix stss to contain only actual keyframes, not all frames
350d Oct 3, 2025
6d34b3c
Fix: Call fsync before ftruncate to ensure data is written
350d Oct 3, 2025
6cbf23a
Add write buffering to QuickTimeMuxer for better performance
350d Oct 3, 2025
54482e6
Fix: Add missing #include <chrono> to QuickTimeMuxer.h
350d Oct 3, 2025
7f97b07
Fix: Update parent container sizes when stss is resized
350d Oct 3, 2025
dae1a8d
Safe optimization: Add BoxBuilder helper class
350d Oct 3, 2025
0083df3
Step 1: Convert createFtypBox to use BoxBuilder
350d Oct 3, 2025
4f13467
Step 2: Convert createMdatBox to use BoxBuilder
350d Oct 3, 2025
a1f65a9
Step 3: Convert stts, stsc, stco in createStblBox to BoxBuilder
350d Oct 3, 2025
4f6075b
Step 4: Remove excessive DEBUG logging in createMP4Snapshot
350d Oct 3, 2025
e7183d4
Step 5: Convert mvhd in createVideoTrackMoovBox to BoxBuilder
350d Oct 3, 2025
57b7704
Step 6: Convert tkhd in createTrakBox to BoxBuilder
350d Oct 3, 2025
c71c8d0
Step 7: Convert mdhd and hdlr in createMdiaBox to BoxBuilder
350d Oct 3, 2025
4592f70
Step 8: Convert vmhd and dinf/dref in createMinfBox to BoxBuilder
350d Oct 3, 2025
7220d00
Step 9: Convert stss and stsz in createStblBox to BoxBuilder
350d Oct 3, 2025
6ceebb2
Step 10: Convert avc1 creation to BoxBuilder with addZeros
350d Oct 3, 2025
a65f5dc
Step 11: Convert avcC creation to BoxBuilder
350d Oct 3, 2025
a260a9a
Step 12: Convert stsd creation to BoxBuilder
350d Oct 3, 2025
1040b13
Step 13: Compact write32/16/8 helper functions
350d Oct 3, 2025
1db3eb0
Step 14: Move vector declarations closer to usage
350d Oct 3, 2025
2463fd6
Step 15: Remove redundant 'Build...' comments
350d Oct 3, 2025
7686aab
Step 16: Remove remaining redundant box comments in createStblBox
350d Oct 3, 2025
33dfb36
Step 17: Remove unnecessary blank lines and comments
350d Oct 3, 2025
74e9888
Step 18 (FINAL): Compact createMdatBox and createFtypBox
350d Oct 3, 2025
af635f2
Step 19: Extract helper methods from writeMoovBox
350d Oct 3, 2025
c6e52a7
Step 20: Simplify createMP4Snapshot and fix security hotspot
350d Oct 3, 2025
5d8e068
Step B (Option B): Unify static and instance helper methods
350d Oct 3, 2025
4dc22f8
Step A (Option A): Complete migration to BoxBuilder
350d Oct 3, 2025
3c70b62
Fix Security Hotspots: strncpy buffer overflow prevention
350d Oct 3, 2025
6e01925
Add detailed implementation documentation
350d Oct 3, 2025
7d902f4
Fix Security Hotspots and update documentation style
350d Oct 3, 2025
ab6feb6
Merge upstream/master: resolve snapshot endpoint conflict
350d Oct 3, 2025
20d5355
Fix snapshot architecture: MJPEG works without RTSP client
350d Oct 3, 2025
38f1dfb
Add comprehensive testing guide for snapshot and recording
350d Oct 3, 2025
f781674
Clarify MJPEG recording: block only .mp4, allow .mjpeg files
350d Oct 3, 2025
bfa4b00
Update libv4l2cpp: fix -fMJPEG parameter parsing
350d Oct 3, 2025
00355b4
Update TESTING_GUIDE: document -fMJPEG alias support
350d Oct 3, 2025
c8e04ae
Clean up repository: remove temporary and duplicate files
350d Oct 3, 2025
5b889d9
Remove H264DebugDumper: Fix CodeFactor security and complexity issues
Oct 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
582 changes: 582 additions & 0 deletions .github/workflows/pi-cross.yml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ install_manifest.txt
#live
live/
live555-latest.tar.gz
# OS
.DS_Store
/tmp
/test
.cursor
.ssh
1 change: 1 addition & 0 deletions inc/ALSACapture.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class ALSACapture : public DeviceInterface

virtual int getSampleRate() { return m_params.m_sampleRate; }
virtual int getChannels () { return m_params.m_channels; }
virtual int getFps() { return -1; } // Not applicable for audio
virtual int getAudioFormat () { return m_fmt; }
virtual std::list<int> getAudioFormatList() { return m_fmtList; }

Expand Down
1 change: 1 addition & 0 deletions inc/DeviceInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class DeviceInterface
virtual unsigned long getBufferSize() = 0;
virtual int getWidth() { return -1; }
virtual int getHeight() { return -1; }
virtual int getFps() { return -1; }
virtual int getVideoFormat() { return -1; }
virtual std::list<int> getVideoFormatList() { return std::list<int>(); }
virtual int getSampleRate() { return -1; }
Expand Down
8 changes: 4 additions & 4 deletions inc/DeviceSourceFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@

class DeviceSourceFactory {
public:
static FramedSource* createFramedSource(UsageEnvironment* env, int format, DeviceInterface* devCapture, int queueSize = 5, V4L2DeviceSource::CaptureMode captureMode = V4L2DeviceSource::CAPTURE_INTERNAL_THREAD, int outfd = -1, bool repeatConfig = true) {
static FramedSource* createFramedSource(UsageEnvironment* env, int format, DeviceInterface* devCapture, int queueSize = 5, V4L2DeviceSource::CaptureMode captureMode = V4L2DeviceSource::CAPTURE_INTERNAL_THREAD, int outfd = -1, bool repeatConfig = true, bool isMP4 = false) {
FramedSource* source = NULL;
if (format == V4L2_PIX_FMT_H264)
{
source = H264_V4L2DeviceSource::createNew(*env, devCapture, outfd, queueSize, captureMode, repeatConfig, false);
source = H264_V4L2DeviceSource::createNew(*env, devCapture, outfd, queueSize, captureMode, repeatConfig, false, isMP4);
}
else if (format == V4L2_PIX_FMT_HEVC)
{
Expand All @@ -36,9 +36,9 @@ class DeviceSourceFactory {
return source;
}

static StreamReplicator* createStreamReplicator(UsageEnvironment* env, int format, DeviceInterface* devCapture, int queueSize = 5, V4L2DeviceSource::CaptureMode captureMode = V4L2DeviceSource::CAPTURE_INTERNAL_THREAD, int outfd = -1, bool repeatConfig = true) {
static StreamReplicator* createStreamReplicator(UsageEnvironment* env, int format, DeviceInterface* devCapture, int queueSize = 5, V4L2DeviceSource::CaptureMode captureMode = V4L2DeviceSource::CAPTURE_INTERNAL_THREAD, int outfd = -1, bool repeatConfig = true, bool isMP4 = false) {
StreamReplicator* replicator = NULL;
FramedSource* framedSource = DeviceSourceFactory::createFramedSource(env, format, devCapture, queueSize, captureMode, outfd, repeatConfig);
FramedSource* framedSource = DeviceSourceFactory::createFramedSource(env, format, devCapture, queueSize, captureMode, outfd, repeatConfig, isMP4);
if (framedSource != NULL)
{
// extend buffer size if needed
Expand Down
31 changes: 26 additions & 5 deletions inc/H264_V4l2DeviceSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,46 @@
**
** -------------------------------------------------------------------------*/


#pragma once

#include <vector>

// project
#include "H26x_V4l2DeviceSource.h"
#include "SnapshotManager.h"

class QuickTimeMuxer; // Forward declaration

// ---------------------------------
// H264 V4L2 FramedSource
// ---------------------------------
// Note: H264marker and H264shortmarker are defined in H26x_V4l2DeviceSource.h

class H264_V4L2DeviceSource : public H26X_V4L2DeviceSource
{
public:
static H264_V4L2DeviceSource* createNew(UsageEnvironment& env, DeviceInterface * device, int outputFd, unsigned int queueSize, CaptureMode captureMode, bool repeatConfig, bool keepMarker) {
return new H264_V4L2DeviceSource(env, device, outputFd, queueSize, captureMode, repeatConfig, keepMarker);
static H264_V4L2DeviceSource* createNew(UsageEnvironment& env, DeviceInterface * device, int outputFd, unsigned int queueSize, CaptureMode captureMode, bool repeatConfig, bool keepMarker, bool isMP4 = false) {
return new H264_V4L2DeviceSource(env, device, outputFd, queueSize, captureMode, repeatConfig, keepMarker, isMP4);
}

protected:
H264_V4L2DeviceSource(UsageEnvironment& env, DeviceInterface * device, int outputFd, unsigned int queueSize, CaptureMode captureMode, bool repeatConfig, bool keepMarker)
: H26X_V4L2DeviceSource(env, device, outputFd, queueSize, captureMode, repeatConfig, keepMarker) {}
H264_V4L2DeviceSource(UsageEnvironment& env, DeviceInterface * device, int outputFd, unsigned int queueSize, CaptureMode captureMode, bool repeatConfig, bool keepMarker, bool isMP4 = false)
: H26X_V4L2DeviceSource(env, device, outputFd, queueSize, captureMode, repeatConfig, keepMarker), m_quickTimeMuxer(nullptr), m_isMP4(isMP4), m_currentFrameData(), m_currentFrameIsKeyframe(false) {
// Check if output file is MP4 based on file descriptor (simple heuristic)
// This could be improved by passing a flag from the caller
}

virtual ~H264_V4L2DeviceSource();

// overide V4L2DeviceSource
virtual std::list< std::pair<unsigned char*,size_t> > splitFrames(unsigned char* frame, unsigned frameSize);
virtual std::list< std::string > getInitFrames();
virtual bool isKeyFrame(const char*, int);

private:
QuickTimeMuxer* m_quickTimeMuxer;
bool m_isMP4;
std::vector<unsigned char> m_currentFrameData;
bool m_currentFrameIsKeyframe;
void initQuickTimeMuxerIfNeeded();
};
29 changes: 14 additions & 15 deletions inc/HTTPServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#pragma once

#include <list>
#include <vector>

// hacking private members RTSPServer::fWeServeSRTP & RTSPServer::fWeEncryptSRTP
#define private protected
Expand Down Expand Up @@ -144,6 +145,7 @@ class HTTPServer : public RTSPServer
void sendHeader(const char* contentType, unsigned int contentLength);
void streamSource(FramedSource* source);
void streamSource(const std::string & content);
void streamSource(const std::vector<unsigned char>& binaryData);
ServerMediaSubsession* getSubsesion(const char* urlSuffix);
bool sendFile(char const* urlSuffix);
bool sendM3u8PlayList(char const* urlSuffix);
Expand Down Expand Up @@ -232,10 +234,10 @@ class HTTPServer : public RTSPServer

#if LIVEMEDIA_LIBRARY_VERSION_INT < 1611187200
HTTPServer(UsageEnvironment& env, int ourSocketIPv4, int ourSocketIPv6, Port rtspPort, MyUserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds, unsigned int hlsSegment, const std::string & webroot, const std::string & sslCert, bool enableRTSPS)
: RTSPServer(env, ourSocketIPv4, rtspPort, authDatabase, reclamationTestSeconds), m_hlsSegment(hlsSegment), m_webroot(webroot)
: RTSPServer(env, ourSocketIPv4, rtspPort, authDatabase, reclamationTestSeconds), m_hlsSegment(hlsSegment), m_webroot(webroot), m_enableRTSPS(false), m_enableSRTP(false)
#else
HTTPServer(UsageEnvironment& env, int ourSocketIPv4, int ourSocketIPv6, Port rtspPort, MyUserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds, unsigned int hlsSegment, const std::string & webroot, const std::string & sslCert, bool enableRTSPS)
: RTSPServer(env, ourSocketIPv4, ourSocketIPv6, rtspPort, authDatabase, reclamationTestSeconds), m_hlsSegment(hlsSegment), m_webroot(webroot)
: RTSPServer(env, ourSocketIPv4, ourSocketIPv6, rtspPort, authDatabase, reclamationTestSeconds), m_hlsSegment(hlsSegment), m_webroot(webroot), m_enableRTSPS(false), m_enableSRTP(false)
#endif
{
if ( (!m_webroot.empty()) && (*m_webroot.rend() != '/') ) {
Expand All @@ -257,40 +259,35 @@ class HTTPServer : public RTSPServer
#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1642723200
if (!sslCert.empty()) {
this->setTLSFileNames(sslCert.c_str(), sslCert.c_str());
fWeServeSRTP = true;
fWeEncryptSRTP = encryptSRTP;
if (enableRTSPS) {
fOurConnectionsUseTLS = true;
} else {
fOurConnectionsUseTLS = false;
}
m_enableRTSPS = enableRTSPS;
m_enableSRTP = encryptSRTP;
} else {
fOurConnectionsUseTLS = false;
fWeServeSRTP = false;
fWeEncryptSRTP = false;
// Reset TLS configuration
m_enableRTSPS = false;
m_enableSRTP = false;
}
#endif
}

bool isRTSPS() {
#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1642723200
return fOurConnectionsUseTLS;
return m_enableRTSPS;
#else
return false;
#endif
}

bool isSRTP() {
#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1642723200
return fWeServeSRTP;
return m_enableSRTP;
#else
return false;
#endif
}

bool isSRTPEncrypted() {
#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1642723200
return fWeEncryptSRTP;
return m_enableSRTP;
#else
return false;
#endif
Expand Down Expand Up @@ -323,5 +320,7 @@ class HTTPServer : public RTSPServer
private:
const unsigned int m_hlsSegment;
std::string m_webroot;
bool m_enableRTSPS;
bool m_enableSRTP;
};

2 changes: 2 additions & 0 deletions inc/MJPEGVideoSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

#include "logger.h"
#include "JPEGVideoSource.hh"
#include "V4L2DeviceSource.h"
#include "SnapshotManager.h"

class MJPEGVideoSource : public JPEGVideoSource
{
Expand Down
121 changes: 121 additions & 0 deletions inc/QuickTimeMuxer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/* ---------------------------------------------------------------------------
** This software is in the public domain, furnished "as is", without technical
** support, and with no warranty, express or implied, as to its usefulness for
** any purpose.
**
** QuickTimeMuxer.h
**
** Wrapper for live555 QuickTimeFileSink for MP4 recording
**
** -------------------------------------------------------------------------*/

#pragma once

#include <string>
#include <memory>
#include <vector>
#include <cstdint>
#include <chrono>

class QuickTimeMuxer {
public:
QuickTimeMuxer();
~QuickTimeMuxer() noexcept;

// Initialize MP4 file with parameters
bool initialize(int fd, const std::string& sps, const std::string& pps, int width, int height, int fps = 30);

// Add H264 frame data
bool addFrame(const unsigned char* h264Data, size_t dataSize, bool isKeyFrame);

// Finalize MP4 file
bool finalize();

// Check if muxer is initialized
bool isInitialized() const { return m_initialized; }

// Get file descriptor for sync operations
int getFileDescriptor() const { return m_fd; }

// Static method for creating MP4 snapshot in memory (for SnapshotManager)
static std::vector<uint8_t> createMP4Snapshot(const unsigned char* h264Data, size_t dataSize,
const std::string& sps, const std::string& pps,
int width, int height, int fps = 30);

// Helper static methods for NAL analysis
static std::string getNALTypeName(uint8_t nalType);
static std::string getCurrentTimestamp();

private:
bool m_initialized;
int m_fd;
std::string m_sps;
std::string m_pps;
int m_width;
int m_height;
int m_fps;

// File position tracking
size_t m_mdatStartPos;
size_t m_currentPos;

// Frame counting
uint32_t m_frameCount;
uint32_t m_keyFrameCount;

// Frame metadata for streaming
struct FrameInfo {
size_t offset;
size_t size;
bool isKeyFrame;
};
std::vector<FrameInfo> m_frames;

// Write buffer for performance (like old MP4Muxer)
std::vector<uint8_t> m_writeBuffer;
size_t m_bufferMaxSize;
int m_flushIntervalMs;
std::chrono::steady_clock::time_point m_lastFlushTime;

// Helper methods
void writeToFile(const void* data, size_t size);
bool writeMP4Header();
bool writeMoovBox();
void flushBufferToDisk(bool force);
bool shouldFlushBuffer(bool isKeyFrame);

// Static helper methods - based on live555 QuickTimeFileSink structure
static std::vector<uint8_t> createFtypBox();
static std::vector<uint8_t> createVideoTrackMoovBox(const std::vector<uint8_t>& sps,
const std::vector<uint8_t>& pps,
int width, int height, int fps,
uint32_t frameCount);
static std::vector<uint8_t> createTrakBox(const std::vector<uint8_t>& sps,
const std::vector<uint8_t>& pps,
int width, int height,
uint32_t timescale, uint32_t duration,
uint32_t frameCount);
static std::vector<uint8_t> createMdiaBox(const std::vector<uint8_t>& sps,
const std::vector<uint8_t>& pps,
int width, int height,
uint32_t timescale, uint32_t duration,
uint32_t frameCount);
static std::vector<uint8_t> createMinfBox(const std::vector<uint8_t>& sps,
const std::vector<uint8_t>& pps,
int width, int height,
uint32_t frameCount);
static std::vector<uint8_t> createStblBox(const std::vector<uint8_t>& sps,
const std::vector<uint8_t>& pps,
int width, int height,
uint32_t frameCount);
static std::vector<uint8_t> createMdatBox(const std::vector<uint8_t>& frameData);

// Helper methods for writeMoovBox (Step 19)
bool updateMdatSize(size_t mdatTotalSize);
void updateFrameSizes(std::vector<uint8_t>& moovBox);
void updateKeyframes(std::vector<uint8_t>& moovBox);

// Universal static helpers (Step B) - used by both recordings and snapshots
static void updateChunkOffset(std::vector<uint8_t>& moovBox, uint32_t actualChunkOffset);
static void updateFrameSize(std::vector<uint8_t>& moovBox, uint32_t frameSize, size_t frameIndex = 0);
};
Loading