From 1fefd20320982d98759d64cb615a95e8c6843e53 Mon Sep 17 00:00:00 2001 From: xtne6f Date: Wed, 1 May 2024 20:11:24 +0900 Subject: [PATCH 1/2] Insert seg_name field for Unix FIFOs --- Readme.txt | 49 ++++++++++++++++++++++++++++++ tsmemseg.cpp | 84 +++++++++++++++++++++++++++++++++------------------- 2 files changed, 102 insertions(+), 31 deletions(-) diff --git a/Readme.txt b/Readme.txt index 08b2276..f434196 100644 --- a/Readme.txt +++ b/Readme.txt @@ -72,8 +72,34 @@ Information about MP4 fragments (16 bytes units) are placed in the extra readabl The 0-3rd byte of the units stores the duration of fragment in milliseconds. Besides the fragment information, if there is space in the extra readable area, it is MP4 header box (ftyp/moov). +For Unix FIFO only, there is a 64-bytes field preceding the information units to store the seg_name. +The field can be used as a signature to prevent multiple processes from reading the FIFO in a race condition. + All other unused bytes are initialized with 0. + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 ++----------------------+-----------------------------+----------------------------------+-----------------+ +|seg_name field (Unix FIFO only) : ++----------------------+-----------------------------+----------------------------------+-----------------+ +: : ++----------------------+-----------------------------+----------------------------------+-----------------+ +: : ++----------------------+-----------------------------+----------------------------------+-----------------+ +: | ++----------------------+-----------------------------+----------------------------------+-----------------+ +|seg_num 0 0 0|UNIX_time_updated |no_longer_updated incomplete MP4 0|extra_area_length| ++----------------------+-----------------------------+----------------------------------+-----------------+ +|seg_index 0 frag_num 0|sequential_number unavailable|seg_duration_msec |sum_of_durations | ++----------------------+-----------------------------+----------------------------------+-----------------+ +... ++----------------------+-----------------------------+----------------------------------+-----------------+ +|frag_duration_msec |0 0 0 0 |0 0 0 0|0 0 0 0 | ++----------------------+-----------------------------+----------------------------------+-----------------+ +... ++----------------------+-----------------------------+----------------------------------+-----------------+ +|ftyp/moov : +... + Specification of "segment pipe": "segment pipe" contains MPEG-TS packets or MP4 moof boxes. @@ -84,8 +110,31 @@ The sequence of 4-6th bytes (immediately after TS-NULL header) stores the sequen 12th stores whether this segment is MPEG-TS (0) or MP4 (1). 32-35th, and subsequent 4 bytes units (until its value is 0) store the size of all fragments contained in the stream. +For Unix FIFO only, there is a 188-bytes field preceding the information packet to store the seg_name. + All other unused bytes are initialized with 0. + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +(The following 188 bytes are Unix FIFO only) ++-------------------+-----------------------------+------------------------+-----------+ +|0x47 0x1f 0xff 0x10|seg_name field : +...(188 bytes) ++-------------------+-----------------------------+------------------------+-----------+ +|0x47 0x1f 0xff 0x10|sequential_number unavailable|number_of_units_or_bytes|MP4 0 0 0 | ++-------------------+-----------------------------+------------------------+-----------+ +|0 0 0 0 |0 0 0 0 |0 0 0 0 |0 0 0 0 | ++-------------------+-----------------------------+------------------------+-----------+ +|0 0 0 0 |0 0 0 0 |0 0 0 0 |0 0 0 0 | ++-------------------+-----------------------------+------------------------+-----------+ +|0 0 0 0 |0 0 0 0 |0 0 0 0 |0 0 0 0 | ++-------------------+-----------------------------+------------------------+-----------+ +|frag_size_0 |frag_size_1 |frag_size_2 |frag_size_3| ++-------------------+-----------------------------+------------------------+-----------+ +...(188 bytes) ++-------------------+-----------------------------+------------------------+-----------+ +|MPEG-TS/MP4 stream : +... + Notes: This tool currently only supports Windows and Linux. diff --git a/tsmemseg.cpp b/tsmemseg.cpp index d2edd8e..0ca61bb 100644 --- a/tsmemseg.cpp +++ b/tsmemseg.cpp @@ -357,21 +357,28 @@ void WriteUint32(uint8_t *buf, uint32_t n) buf[3] = static_cast(n >> 24); } -void AssignSegmentList(std::vector &buf, const std::vector &segments, size_t segIndex, +void AssignSegmentList(std::vector &buf, const char *signature, const std::vector &segments, size_t segIndex, bool endList, bool incomplete, bool isMp4, const std::vector &mp4Header) { - buf.assign(segments.size() * 16, 0); - WriteUint32(&buf[0], static_cast(segments.size() - 1)); - WriteUint32(&buf[4], GetCurrentUnixTime()); - buf[8] = endList; - buf[9] = incomplete; - buf[10] = isMp4; + buf.assign(segments.size() * 16 + (signature ? 64 : 0), 0); + size_t ofs = 0; + if (signature) { + for (size_t i = 0; i < 64 && signature[i]; ++i) { + buf[i] = signature[i]; + } + ofs = 64; + } + WriteUint32(&buf[ofs], static_cast(segments.size() - 1)); + WriteUint32(&buf[ofs + 4], GetCurrentUnixTime()); + buf[ofs + 8] = endList; + buf[ofs + 9] = incomplete; + buf[ofs + 10] = isMp4; for (size_t i = segIndex, j = 1; j < segments.size(); ++j) { - WriteUint32(&buf[j * 16], static_cast(i)); - WriteUint32(&buf[j * 16 + 2], static_cast(segments[i].fragDurationsMsec.size())); - WriteUint32(&buf[j * 16 + 4], segments[i].segCount); - WriteUint32(&buf[j * 16 + 8], segments[i].segDurationMsec); - WriteUint32(&buf[j * 16 + 12], static_cast(segments[i].segTimeMsec / 10)); + WriteUint32(&buf[ofs + j * 16], static_cast(i)); + WriteUint32(&buf[ofs + j * 16 + 2], static_cast(segments[i].fragDurationsMsec.size())); + WriteUint32(&buf[ofs + j * 16 + 4], segments[i].segCount); + WriteUint32(&buf[ofs + j * 16 + 8], segments[i].segDurationMsec); + WriteUint32(&buf[ofs + j * 16 + 12], static_cast(segments[i].segTimeMsec / 10)); for (size_t k = 0; k < segments[i].fragDurationsMsec.size(); ++k) { buf.insert(buf.end(), 16, 0); WriteUint32(&buf[buf.size() - 16], segments[i].fragDurationsMsec[k]); @@ -379,27 +386,39 @@ void AssignSegmentList(std::vector &buf, const std::vector(buf.size() - segments.size() * 16)); + WriteUint32(&buf[ofs + 12], static_cast(buf.size() - segments.size() * 16 - ofs)); } -void WriteSegmentHeader(std::vector &buf, uint32_t segCount, bool isMp4, const std::vector &fragSizes) +void WriteSegmentHeader(std::vector &buf, const char *signature, uint32_t segCount, bool isMp4, const std::vector &fragSizes) { + size_t ofs = 0; + if (signature) { + // NULL TS header + buf[0] = 0x47; + buf[1] = 0x1f; + buf[2] = 0xff; + buf[3] = 0x10; + for (size_t i = 0; i < 184 && signature[i]; ++i) { + buf[i + 4] = signature[i]; + } + ofs = 188; + } // NULL TS header - buf[0] = 0x47; - buf[1] = 0x1f; - buf[2] = 0xff; - buf[3] = 0x10; - WriteUint32(&buf[4], segCount); - WriteUint32(&buf[8], static_cast((buf.size() - 188) / (isMp4 ? 1 : 188))); - buf[12] = isMp4; + buf[ofs] = 0x47; + buf[ofs + 1] = 0x1f; + buf[ofs + 2] = 0xff; + buf[ofs + 3] = 0x10; + WriteUint32(&buf[ofs + 4], segCount); + WriteUint32(&buf[ofs + 8], static_cast((buf.size() - 188 - ofs) / (isMp4 ? 1 : 188))); + buf[ofs + 12] = isMp4; if (isMp4) { - size_t remainSize = buf.size() - 188; + size_t remainSize = buf.size() - 188 - ofs; size_t i = 0; for (; i + 1 < std::min(fragSizes.size(), MP4_FRAG_MAX_NUM) && remainSize >= fragSizes[i]; ++i) { - WriteUint32(&buf[i * 4 + 32], static_cast(fragSizes[i])); + WriteUint32(&buf[ofs + i * 4 + 32], static_cast(fragSizes[i])); remainSize -= fragSizes[i]; } - WriteUint32(&buf[i * 4 + 32], static_cast(remainSize)); + WriteUint32(&buf[ofs + i * 4 + 32], static_cast(remainSize)); } } @@ -807,6 +826,9 @@ int main(int argc, char **argv) #ifdef _WIN32 // Used for asynchronous writing of segments. std::vector> events; + const char *signature = nullptr; +#else + const char *signature = destName; #endif while (segments.size() < 1 + segNum) { @@ -836,8 +858,8 @@ int main(int argc, char **argv) #endif seg.segCount = SEGMENT_COUNT_EMPTY; if (!segments.empty()) { - seg.buf.assign(188, 0); - WriteSegmentHeader(seg.buf, seg.segCount, isMp4, mp4frag.GetFragmentSizes()); + seg.buf.assign(signature ? 376 : 188, 0); + WriteSegmentHeader(seg.buf, signature, seg.segCount, isMp4, mp4frag.GetFragmentSizes()); } segments.push_back(std::move(seg)); } @@ -846,7 +868,7 @@ int main(int argc, char **argv) fprintf(stderr, "Error: pipe/fifo creation failed.\n"); return 1; } - AssignSegmentList(segments.front().buf, segments, 1, false, false, isMp4, mp4frag.GetHeader()); + AssignSegmentList(segments.front().buf, signature, segments, 1, false, false, isMp4, mp4frag.GetHeader()); #ifndef _WIN32 struct sigaction sigact = {}; @@ -949,7 +971,7 @@ int main(int argc, char **argv) } std::vector &segBuf = SelectWritableSegmentBuffer(seg); - segBuf.assign(188, 0); + segBuf.assign(signature ? 376 : 188, 0); if (isMp4) { seg.fragDurationsMsec = mp4frag.GetFragmentDurationsMsec(); @@ -974,12 +996,12 @@ int main(int argc, char **argv) segBuf.insert(segBuf.end(), packets.begin(), packets.end()); } - WriteSegmentHeader(segBuf, seg.segCount, isMp4, mp4frag.GetFragmentSizes()); + WriteSegmentHeader(segBuf, signature, seg.segCount, isMp4, mp4frag.GetFragmentSizes()); if (!segIncomplete) { mp4frag.ClearFragments(); } std::vector &segfrBuf = SelectWritableSegmentBuffer(segments.front()); - AssignSegmentList(segfrBuf, segments, segIndex, false, segIncomplete, isMp4, mp4frag.GetHeader()); + AssignSegmentList(segfrBuf, signature, segments, segIndex, false, segIncomplete, isMp4, mp4frag.GetHeader()); return false; }); @@ -988,7 +1010,7 @@ int main(int argc, char **argv) // End list std::vector &segfrBuf = SelectWritableSegmentBuffer(segments.front()); - AssignSegmentList(segfrBuf, segments, segIndex, true, false, isMp4, mp4frag.GetHeader()); + AssignSegmentList(segfrBuf, signature, segments, segIndex, true, false, isMp4, mp4frag.GetHeader()); } if (syncError) { From bbc1ec347d280ed3705d739c5b2691d8c8330ebb Mon Sep 17 00:00:00 2001 From: xtne6f Date: Wed, 1 May 2024 20:17:17 +0900 Subject: [PATCH 2/2] Add feature to specify the directory for FIFOs --- Readme.txt | 6 +++++- tsmemseg.cpp | 25 +++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Readme.txt b/Readme.txt index f434196..3704edf 100644 --- a/Readme.txt +++ b/Readme.txt @@ -2,7 +2,7 @@ tsmemseg - In-memory transport stream segmenter mainly for HLS/LL-HLS Usage: -tsmemseg [-4][-i inittime][-t time][-p ptime][-a acc_timeout][-c cmd][-r readrate][-f fill_readrate][-s seg_num][-m max_kbytes] seg_name +tsmemseg [-4][-i inittime][-t time][-p ptime][-a acc_timeout][-c cmd][-r readrate][-f fill_readrate][-s seg_num][-m max_kbytes][-g dir] seg_name -4 Convert to fragmented MP4. @@ -34,6 +34,10 @@ tsmemseg [-4][-i inittime][-t time][-p ptime][-a acc_timeout][-c cmd][-r readrat -m max_kbytes (kbytes), 32<=range<=32768, default=4096 Maximum size of each segment. If segment length exceeds this limit, the segment is forcibly cut whether on a key packet or not. +-g dir, default="" + Specify the directory for creating FIFOs. If not specified, created in "/tmp" with 0600 permission. + This option is ignored on Windows. + seg_name Used for the name pattern of named-pipes/FIFOs used to access segments, or "-" (stdout). If "-" is specified, simply prints stream to standard output. -a -c -r -f -s options are ignored. diff --git a/tsmemseg.cpp b/tsmemseg.cpp index 0ca61bb..200e4d8 100644 --- a/tsmemseg.cpp +++ b/tsmemseg.cpp @@ -7,6 +7,7 @@ #else #include #include +#include #include #include #include @@ -95,7 +96,7 @@ struct SEGMENT_PIPE_CONTEXT struct SEGMENT_CONTEXT { - char path[128]; + char path[256]; SEGMENT_PIPE_CONTEXT pipes[2]; std::vector buf; std::vector backBuf; @@ -677,6 +678,9 @@ int main(int argc, char **argv) int nextReadRatePerMille = 0; size_t segNum = 8; size_t segMaxBytes = 4096 * 1024; +#ifndef _WIN32 + const char *fifoDir = ""; +#endif const char *destName = ""; CMp4Fragmenter mp4frag; @@ -686,7 +690,7 @@ int main(int argc, char **argv) c = argv[i][1]; } if (c == 'h') { - fprintf(stderr, "Usage: tsmemseg [-4][-i inittime][-t time][-p ptime][-a acc_timeout][-c cmd][-r readrate][-f fill_readrate][-s seg_num][-m max_kbytes] seg_name\n"); + fprintf(stderr, "Usage: tsmemseg [-4][-i inittime][-t time][-p ptime][-a acc_timeout][-c cmd][-r readrate][-f fill_readrate][-s seg_num][-m max_kbytes][-g dir] seg_name\n"); return 2; } bool invalid = false; @@ -729,6 +733,12 @@ int main(int argc, char **argv) segMaxBytes = static_cast(strtol(argv[++i], nullptr, 10) * 1024); invalid = segMaxBytes < 32 * 1024 || 32 * 1024 * 1024 < segMaxBytes; } + else if (c == 'g') { + ++i; +#ifndef _WIN32 + fifoDir = argv[i]; +#endif + } } else { destName = argv[i]; @@ -851,8 +861,15 @@ int main(int argc, char **argv) break; } #else - sprintf(seg.path, "/tmp/tsmemseg_%s%02d.fifo", destName, static_cast(segments.size())); - if (mkfifo(seg.path, S_IRWXU) != 0) { + size_t dirLen = strlen(fifoDir); + if ((dirLen ? dirLen + (fifoDir[dirLen - 1] != '/' ? 1 : 0) : 5) + strlen(destName) + 16 >= sizeof(seg.path)) { + // path too long + break; + } + sprintf(seg.path, "%s%stsmemseg_%s%02d.fifo", dirLen ? fifoDir : "/tmp/", + dirLen && fifoDir[dirLen - 1] != '/' ? "/" : "", + destName, static_cast(segments.size())); + if (mkfifo(seg.path, S_IRUSR + S_IWUSR + (dirLen ? S_IRGRP + S_IWGRP + S_IROTH + S_IWOTH : 0)) != 0) { break; } #endif