Skip to content

Commit

Permalink
Allow to optionally mmap() a stream in the image viewer.
Browse files Browse the repository at this point in the history
  • Loading branch information
hzeller committed Jul 29, 2024
1 parent 631a5db commit f7bcb9d
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 12 deletions.
41 changes: 33 additions & 8 deletions include/content-streamer.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@
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() {}

// 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
Expand All @@ -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
Expand Down
36 changes: 36 additions & 0 deletions lib/content-streamer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
#include "content-streamer.h"
#include "led-matrix.h"

#include <cstddef>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>

#include <algorithm>

Expand Down Expand Up @@ -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;
Expand Down
24 changes: 20 additions & 4 deletions utils/led-image-viewer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -209,6 +209,7 @@ static int usage(const char *progname) {
fprintf(stderr, "Options:\n"
"\t-O<streamfile> : 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"
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit f7bcb9d

Please sign in to comment.