--- /dev/null
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "frame_on_disk.h"
+
+using namespace std;
+
+FrameReader::~FrameReader()
+{
+ if (fd != -1) {
+ close(fd);
+ }
+}
+
+string FrameReader::read_frame(FrameOnDisk frame)
+{
+ if (int(frame.filename_idx) != last_filename_idx) {
+ if (fd != -1) {
+ close(fd); // Ignore errors.
+ }
+
+ string filename;
+ {
+ lock_guard<mutex> lock(frame_mu);
+ filename = frame_filenames[frame.filename_idx];
+ }
+
+ fd = open(filename.c_str(), O_RDONLY);
+ if (fd == -1) {
+ perror(filename.c_str());
+ exit(1);
+ }
+
+ // We want readahead. (Ignore errors.)
+ posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+
+ last_filename_idx = frame.filename_idx;
+ }
+
+ string str;
+ str.resize(frame.size);
+ off_t offset = 0;
+ while (offset < frame.size) {
+ int ret = pread(fd, &str[offset], frame.size - offset, frame.offset + offset);
+ if (ret <= 0) {
+ perror("pread");
+ exit(1);
+ }
+
+ offset += ret;
+ }
+ return str;
+}
extern std::vector<FrameOnDisk> frames[MAX_STREAMS]; // Under frame_mu.
extern std::vector<std::string> frame_filenames; // Under frame_mu.
-std::string read_frame(FrameOnDisk frame);
+// A helper class to read frames from disk. It caches the file descriptor
+// so that the kernel has a better chance of doing readahead when it sees
+// the sequential reads. (For this reason, each display has a private
+// FrameReader. Thus, we can easily keep multiple open file descriptors around
+// for a single .frames file.)
+class FrameReader {
+public:
+ ~FrameReader();
+ std::string read_frame(FrameOnDisk frame);
+
+private:
+ int fd = -1;
+ int last_filename_idx = -1;
+};
#endif // !defined(_FRAME_ON_DISK_H)
}
}
-shared_ptr<Frame> decode_jpeg_with_cache(FrameOnDisk frame_spec, CacheMissBehavior cache_miss_behavior, bool *did_decode)
+shared_ptr<Frame> decode_jpeg_with_cache(FrameOnDisk frame_spec, CacheMissBehavior cache_miss_behavior, FrameReader *frame_reader, bool *did_decode)
{
*did_decode = false;
{
}
*did_decode = true;
- shared_ptr<Frame> frame = decode_jpeg(read_frame(frame_spec));
+ shared_ptr<Frame> frame = decode_jpeg(frame_reader->read_frame(frame_spec));
unique_lock<mutex> lock(cache_mu);
cache_bytes_used += frame_size(*frame);
return frame;
}
-void jpeg_decoder_thread_func()
+void JPEGFrameView::jpeg_decoder_thread_func()
{
size_t num_decoded = 0, num_dropped = 0;
}
bool found_in_cache;
- shared_ptr<Frame> frame = decode_jpeg_with_cache(frame_spec, cache_miss_behavior, &found_in_cache);
+ shared_ptr<Frame> frame = decode_jpeg_with_cache(frame_spec, cache_miss_behavior, &decode.destination->frame_reader, &found_in_cache);
if (frame == nullptr) {
assert(cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE);
}
}
if (subframe_idx == 0) {
- primary_frame = move(frame);
+ primary_frame = std::move(frame);
} else {
- secondary_frame = move(frame);
+ secondary_frame = std::move(frame);
}
}
if (drop) {
};
std::shared_ptr<Frame> decode_jpeg(const std::string &filename);
-std::shared_ptr<Frame> decode_jpeg_with_cache(FrameOnDisk id, CacheMissBehavior cache_miss_behavior, bool *did_decode);
+std::shared_ptr<Frame> decode_jpeg_with_cache(FrameOnDisk id, CacheMissBehavior cache_miss_behavior, FrameReader *frame_reader, bool *did_decode);
class JPEGFrameView : public QGLWidget {
Q_OBJECT
void paintGL() override;
private:
+ static void jpeg_decoder_thread_func();
+
+ FrameReader frame_reader;
+
// The stream index of the latest frame we displayed.
unsigned current_stream_idx = 0;
return 0;
}
-
-string read_frame(FrameOnDisk frame)
-{
- string filename;
- {
- lock_guard<mutex> lock(frame_mu);
- filename = frame_filenames[frame.filename_idx];
- }
-
- // TODO: cache the open file handles
- FILE *fp = fopen(filename.c_str(), "rb");
- if (fp == nullptr) {
- perror(filename.c_str());
- exit(1);
- }
- if (fseek(fp, frame.offset, SEEK_SET) == -1) {
- perror("fseek");
- exit(1);
- }
-
- string str;
- str.resize(frame.size);
- if (fread(&str[0], frame.size, 1, fp) != 1) {
- perror("fread");
- exit(1);
- }
-
- fclose(fp);
- return str;
-}
# All the other files.
srcs += ['ffmpeg_raii.cpp', 'main.cpp', 'player.cpp', 'httpd.cpp', 'mux.cpp', 'metacube2.cpp', 'video_stream.cpp', 'context.cpp', 'chroma_subsampler.cpp']
srcs += ['vaapi_jpeg_decoder.cpp', 'memcpy_interleaved.cpp', 'db.cpp', 'disk_space_estimator.cpp', 'ycbcr_converter.cpp', 'flags.cpp']
-srcs += ['mainwindow.cpp', 'jpeg_frame_view.cpp', 'clip_list.cpp']
+srcs += ['mainwindow.cpp', 'jpeg_frame_view.cpp', 'clip_list.cpp', 'frame_on_disk.cpp']
srcs += moc_files
srcs += proto_generated
// Preload the file from disk, so that the encoder thread does not get stalled.
// TODO: Consider sending it through the queue instead.
- (void)read_frame(frame);
+ (void)frame_reader.read_frame(frame);
QueuedFrame qf;
qf.local_pts = local_pts;
bool did_decode;
- shared_ptr<Frame> frame1 = decode_jpeg_with_cache(frame1_spec, DECODE_IF_NOT_IN_CACHE, &did_decode);
- shared_ptr<Frame> frame2 = decode_jpeg_with_cache(frame2_spec, DECODE_IF_NOT_IN_CACHE, &did_decode);
+ shared_ptr<Frame> frame1 = decode_jpeg_with_cache(frame1_spec, DECODE_IF_NOT_IN_CACHE, &frame_reader, &did_decode);
+ shared_ptr<Frame> frame2 = decode_jpeg_with_cache(frame2_spec, DECODE_IF_NOT_IN_CACHE, &frame_reader, &did_decode);
ycbcr_semiplanar_converter->prepare_chain_for_fade(frame1, frame2, fade_alpha)->render_to_fbo(resources->fade_fbo, 1280, 720);
for (size_t frame_no = 0; frame_no < 2; ++frame_no) {
FrameOnDisk frame_spec = frame_no == 1 ? frame2 : frame1;
bool did_decode;
- shared_ptr<Frame> frame = decode_jpeg_with_cache(frame_spec, DECODE_IF_NOT_IN_CACHE, &did_decode);
+ shared_ptr<Frame> frame = decode_jpeg_with_cache(frame_spec, DECODE_IF_NOT_IN_CACHE, &frame_reader, &did_decode);
ycbcr_converter->prepare_chain_for_conversion(frame)->render_to_fbo(resources->input_fbos[frame_no], 1280, 720);
}
// Now decode the image we are fading against.
bool did_decode;
- shared_ptr<Frame> frame2 = decode_jpeg_with_cache(secondary_frame, DECODE_IF_NOT_IN_CACHE, &did_decode);
+ shared_ptr<Frame> frame2 = decode_jpeg_with_cache(secondary_frame, DECODE_IF_NOT_IN_CACHE, &frame_reader, &did_decode);
// Then fade against it, putting it into the fade Y' and CbCr textures.
ycbcr_semiplanar_converter->prepare_chain_for_fade_from_texture(qf.output_tex, frame2, fade_alpha)->render_to_fbo(resources->fade_fbo, 1280, 720);
if (qf.type == QueuedFrame::ORIGINAL) {
// Send the JPEG frame on, unchanged.
- string jpeg = read_frame(qf.frame1);
+ string jpeg = frame_reader.read_frame(qf.frame1);
AVPacket pkt;
av_init_packet(&pkt);
pkt.stream_index = 0;
QueueSpotHolder &&queue_spot_holder);
private:
+ FrameReader frame_reader;
+
void encode_thread_func();
std::thread encode_thread;