diff --git a/include/content-streamer.h b/include/content-streamer.h index c95c63a56..00dc25ca3 100644 --- a/include/content-streamer.h +++ b/include/content-streamer.h @@ -22,7 +22,9 @@ namespace rgb_matrix { class FrameCanvas; -// An abstraction of a data stream. +// An abstraction of a data stream. Two implementations exist for files and +// an in-memory representation, but this allows your own implementation, e.g. +// reading from a socket. class StreamIO { public: virtual ~StreamIO() {} @@ -30,7 +32,8 @@ class StreamIO { // Rewind stream. virtual void Rewind() = 0; - // Read bytes into buffer. Similar to Posix behavior that allows short reads. + // Read bytes into buffer at current position of stream. + // Similar to Posix behavior that allows short reads. virtual ssize_t Read(void *buf, size_t count) = 0; // Write bytes from buffer. Similar to Posix behavior that allows short @@ -43,25 +46,47 @@ class FileStreamIO : public StreamIO { explicit FileStreamIO(int fd); ~FileStreamIO(); - virtual void Rewind(); - virtual ssize_t Read(void *buf, size_t count); - virtual ssize_t Append(const void *buf, size_t count); + void Rewind() final; + ssize_t Read(void *buf, size_t count) final; + ssize_t Append(const void *buf, size_t count) final; private: const int fd_; }; +// Storing a stream in memory. Owns the memory. class MemStreamIO : public StreamIO { public: - virtual void Rewind(); - virtual ssize_t Read(void *buf, size_t count); - virtual ssize_t Append(const void *buf, size_t count); + void Rewind() final; + ssize_t Read(void *buf, size_t count) final; + ssize_t Append(const void *buf, size_t count) final; private: std::string buffer_; // super simplistic. size_t pos_; }; +// Just a view around the memory, possibly a memory mapped file. +class MemMapViewInput : public StreamIO { +public: + MemMapViewInput(int fd); + ~MemMapViewInput(); + + // Since mmmap() might fail, this tells us if it was successful. + bool IsInitialized() const { return buffer_ != nullptr; } + + void Rewind() final; + ssize_t Read(void *buf, size_t count) final; + + // No append, this is purely read-only. + ssize_t Append(const void *buf, size_t count) final { return -1; } + +private: + char *buffer_; + char *end_; + char *pos_; +}; + class StreamWriter { public: // Does not take ownership of StreamIO diff --git a/lib/content-streamer.cc b/lib/content-streamer.cc index 7dc742501..2d2d40055 100644 --- a/lib/content-streamer.cc +++ b/lib/content-streamer.cc @@ -3,12 +3,14 @@ #include "content-streamer.h" #include "led-matrix.h" +#include #include #include #include #include #include #include +#include #include @@ -75,6 +77,40 @@ ssize_t MemStreamIO::Append(const void *buf, size_t count) { return count; } +MemMapViewInput::MemMapViewInput(int fd) : buffer_(nullptr) { + struct stat s; + if (fstat(fd, &s) < 0) { + close(fd); + perror("Couldn't get size"); + return; // Can't return error state from constructor. Stay uninitialized. + } + + const size_t file_size = s.st_size; + buffer_ = (char*)mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (buffer_ == MAP_FAILED) { + perror("Can't mmmap()"); + return; + } + end_ = buffer_ + file_size; +#ifdef POSIX_MADV_WILLNEED + // Trigger read-ahead if possible. + posix_madvise(buffer_, file_size, POSIX_MADV_WILLNEED); +#endif +} + +void MemMapViewInput::Rewind() { pos_ = buffer_; } +ssize_t MemMapViewInput::Read(void *buf, size_t count) { + if (pos_ + count >= end_) return -1; + memcpy(buf, pos_, count); + pos_ += count; + return count; +} + +MemMapViewInput::~MemMapViewInput() { + if (buffer_) munmap(buffer_, end_ - buffer_); +} + // Read exactly count bytes including retries. Returns success. static bool FullRead(StreamIO *io, void *buf, const size_t count) { int remaining = count; diff --git a/utils/led-image-viewer.cc b/utils/led-image-viewer.cc index acbe69293..35da8c59e 100644 --- a/utils/led-image-viewer.cc +++ b/utils/led-image-viewer.cc @@ -62,8 +62,8 @@ struct ImageParams { struct FileInfo { ImageParams params; // Each file might have specific timing settings - bool is_multi_frame; - rgb_matrix::StreamIO *content_stream; + bool is_multi_frame = false; + rgb_matrix::StreamIO *content_stream = nullptr; }; volatile bool interrupt_received = false; @@ -209,6 +209,7 @@ static int usage(const char *progname) { fprintf(stderr, "Options:\n" "\t-O : Output to stream-file instead of matrix (Don't need to be root).\n" "\t-C : Center images.\n" + "\t-m : if this is a stream, mmap() it. This can work around IO latencies in SD-card and refilling kernel buffers. This will use physical memory so only use if you have enough to map file size\n" "\nThese options affect images FOLLOWING them on the command line,\n" "so it is possible to have different options for each image\n" @@ -261,6 +262,7 @@ int main(int argc, char *argv[]) { return usage(argv[0]); } + bool do_mmap = false; bool do_forever = false; bool do_center = false; bool do_shuffle = false; @@ -283,7 +285,7 @@ int main(int argc, char *argv[]) { const char *stream_output = NULL; int opt; - while ((opt = getopt(argc, argv, "w:t:l:fr:c:P:LhCR:sO:V:D:")) != -1) { + while ((opt = getopt(argc, argv, "w:t:l:fr:c:P:LhCR:sO:V:D:m")) != -1) { switch (opt) { case 'w': img_param.wait_ms = roundf(atof(optarg) * 1000.0f); @@ -297,6 +299,9 @@ int main(int argc, char *argv[]) { case 'D': img_param.anim_delay_ms = atoi(optarg); break; + case 'm': + do_mmap = true; + break; case 'f': do_forever = true; break; @@ -417,7 +422,18 @@ int main(int argc, char *argv[]) { if (fd >= 0) { file_info = new FileInfo(); file_info->params = filename_params[filename]; - file_info->content_stream = new rgb_matrix::FileStreamIO(fd); + if (do_mmap) { + rgb_matrix::MemMapViewInput *stream_input = + new rgb_matrix::MemMapViewInput(fd); + if (stream_input->IsInitialized()) { + file_info->content_stream = stream_input; + } else { + delete stream_input; + } + } + if (!file_info->content_stream) { + file_info->content_stream = new rgb_matrix::FileStreamIO(fd); + } StreamReader reader(file_info->content_stream); if (reader.GetNext(offscreen_canvas, NULL)) { // header+size ok file_info->is_multi_frame = reader.GetNext(offscreen_canvas, NULL);