void SSRVideoStreamReader::Init() { Logger::LogInfo("[SSRVideoStreamReader::Init] " + Logger::tr("Created video stream reader.")); // open main file m_fd_main = open(m_filename_main.c_str(), O_RDWR | O_CLOEXEC); if(m_fd_main == -1) { Logger::LogError("[SSRVideoStreamReader::Init] " + Logger::tr("Error: Can't open video stream file!")); throw SSRStreamException(); } // resize main file m_mmap_size_main = (sizeof(GLInjectHeader) + GLINJECT_RING_BUFFER_SIZE * sizeof(GLInjectFrameInfo) + m_page_size - 1) / m_page_size * m_page_size; if(ftruncate(m_fd_main, m_mmap_size_main) == -1) { Logger::LogError("[SSRVideoStreamReader::Init] " + Logger::tr("Error: Can't resize video stream file!")); throw SSRStreamException(); } // map main file m_mmap_ptr_main = mmap(NULL, m_mmap_size_main, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd_main, 0); if(m_mmap_ptr_main == MAP_FAILED) { Logger::LogError("[SSRVideoStreamReader::Init] " + Logger::tr("Error: Can't memory-map video stream file!")); throw SSRStreamException(); } // open frame files for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { FrameData &fd = m_frame_data[i]; fd.m_fd_frame = open(fd.m_filename_frame.c_str(), O_RDWR | O_CLOEXEC); if(fd.m_fd_frame == -1) { Logger::LogError("[SSRVideoStreamReader::Init] " + Logger::tr("Error: Can't open video frame file!")); throw SSRStreamException(); } } // initialize header GLInjectHeader *header = GetGLInjectHeader(); header->capture_flags = 0; header->capture_target_fps = 0; std::atomic_thread_fence(std::memory_order_release); // initialize frame counter std::atomic_thread_fence(std::memory_order_acquire); m_fps_last_timestamp = hrt_time_micro(); m_fps_last_counter = header->frame_counter; m_fps_current = 0.0; }
void* SSRVideoStreamReader::GetFrame(int64_t* timestamp, unsigned int* width, unsigned int* height, int* stride) { // make sure that the stream has been initialized GLInjectHeader *header = GetGLInjectHeader(); std::atomic_thread_fence(std::memory_order_acquire); if(header->identifier != GLINJECT_IDENTIFIER) return NULL; // make sure that at least one frame is available std::atomic_thread_fence(std::memory_order_acquire); unsigned int read_pos = header->ring_buffer_read_pos; unsigned int write_pos = header->ring_buffer_write_pos; if(read_pos == write_pos) return NULL; // read frame info GLInjectFrameInfo *frameinfo = GetGLInjectFrameInfo(read_pos % GLINJECT_RING_BUFFER_SIZE); std::atomic_thread_fence(std::memory_order_acquire); *timestamp = frameinfo->timestamp; *width = frameinfo->width; *height = frameinfo->height; *stride = frameinfo->stride; // verify the size (should never happen unless someone is messing with the files) if(*width < 2 || *height < 2) return NULL; if(*width > 10000 || *height > 10000) return NULL; if(abs(*stride) > 10000 * 4) return NULL; // read frame FrameData &fd = m_frame_data[read_pos % GLINJECT_RING_BUFFER_SIZE]; size_t required_size = (size_t) abs(*stride) * (size_t) *height; if(required_size > fd.m_mmap_size_frame) { // calculate new size required_size = (required_size + m_page_size - 1) / m_page_size * m_page_size; // unmap frame file if(fd.m_mmap_ptr_frame != MAP_FAILED) { munmap(fd.m_mmap_ptr_frame, fd.m_mmap_size_frame); fd.m_mmap_ptr_frame = MAP_FAILED; fd.m_mmap_size_frame = 0; } // check frame file size { struct stat statinfo; if(fstat(fd.m_fd_frame, &statinfo) == -1 || (size_t) statinfo.st_size < required_size) { Logger::LogError("[SSRVideoStreamReader::GetFrame] " + Logger::tr("Error: Size of video frame file is incorrect!")); throw SSRStreamException(); } required_size = statinfo.st_size / m_page_size * m_page_size; } // map frame file fd.m_mmap_ptr_frame = mmap(NULL, required_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd.m_fd_frame, 0); if(fd.m_mmap_ptr_frame == MAP_FAILED) { Logger::LogError("[SSRVideoStreamReader::GetFrame] " + Logger::tr("Error: Can't memory-map video frame file!")); throw SSRStreamException(); } fd.m_mmap_size_frame = required_size; } return fd.m_mmap_ptr_frame; }
void SSRVideoStreamWriter::Init() { GLINJECT_PRINT("[" << m_filename_main << "] Created video stream."); bool relax_permissions = false; { char *ssr_stream_relax_permissions = getenv("SSR_STREAM_RELAX_PERMISSIONS"); if(ssr_stream_relax_permissions != NULL && atoi(ssr_stream_relax_permissions) > 0) { GLINJECT_PRINT("Warning: Using relaxed file permissions, any user on this machine will be able to read or manipulate the stream!"); relax_permissions = true; } } // create channel directory (permissions may be wrong because of umask, fix this later) if(mkdir(m_channel_directory.c_str(), (relax_permissions)? 0777 : 0700) == -1) { if(errno != EEXIST) { // does the directory already exist? GLINJECT_PRINT("Error: Can't create channel directory!"); throw SSRStreamException(); } } // check ownership and permissions struct stat statinfo; if(lstat(m_channel_directory.c_str(), &statinfo) == -1) { GLINJECT_PRINT("Error: Can't stat channel directory!"); throw SSRStreamException(); } if(!S_ISDIR(statinfo.st_mode) || S_ISLNK(statinfo.st_mode)) { GLINJECT_PRINT("Error: Channel directory is not a regular directory!"); throw SSRStreamException(); } if(statinfo.st_uid == geteuid()) { if(chmod(m_channel_directory.c_str(), (relax_permissions)? 0777 : 0700) == -1) { GLINJECT_PRINT("Error: Can't set channel directory mode!"); throw SSRStreamException(); } } else { if(!relax_permissions) { GLINJECT_PRINT("Error: Channel directory is owned by a different user! " "Choose a different channel name, or enable relaxed file permissions to use it anyway."); throw SSRStreamException(); } } // open frame files for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { FrameData &fd = m_frame_data[i]; fd.m_fd_frame = open(fd.m_filename_frame.c_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, (relax_permissions)? 0666 : 0600); if(fd.m_fd_frame == -1) { GLINJECT_PRINT("Error: Can't open video frame file!"); throw SSRStreamException(); } if(fchmod(fd.m_fd_frame, (relax_permissions)? 0666 : 0600) == -1) { GLINJECT_PRINT("Error: Can't set video frame file mode!"); throw SSRStreamException(); } } // open main file m_fd_main = open(m_filename_main.c_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, (relax_permissions)? 0666 : 0600); if(m_fd_main == -1) { GLINJECT_PRINT("Error: Can't open video stream file!"); throw SSRStreamException(); } if(fchmod(m_fd_main, (relax_permissions)? 0666 : 0600) == -1) { GLINJECT_PRINT("Error: Can't set video stream file mode!"); throw SSRStreamException(); } // resize main file m_mmap_size_main = (sizeof(GLInjectHeader) + GLINJECT_RING_BUFFER_SIZE * sizeof(GLInjectFrameInfo) + m_page_size - 1) / m_page_size * m_page_size; if(ftruncate(m_fd_main, m_mmap_size_main) == -1) { GLINJECT_PRINT("Error: Can't resize video stream file!"); throw SSRStreamException(); } // map main file m_mmap_ptr_main = mmap(NULL, m_mmap_size_main, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd_main, 0); if(m_mmap_ptr_main == MAP_FAILED) { GLINJECT_PRINT("Error: Can't memory-map video stream file!"); throw SSRStreamException(); } // initialize header GLInjectHeader *header = GetGLInjectHeader(); header->identifier = 0; // will be set later header->ring_buffer_read_pos = 0; header->ring_buffer_write_pos = 0; header->current_width = m_width; header->current_height = m_height; header->frame_counter = 0; // initialize frame info for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { GLInjectFrameInfo *frameinfo = GetGLInjectFrameInfo(i); frameinfo->timestamp = 0; frameinfo->width = 0; frameinfo->height = 0; frameinfo->stride = 0; } // set the identifier to indicate that initialization is complete std::atomic_thread_fence(std::memory_order_release); header->identifier = GLINJECT_IDENTIFIER; std::atomic_thread_fence(std::memory_order_release); }
void* SSRVideoStreamWriter::NewFrame(unsigned int* flags) { // increment the frame counter GLInjectHeader *header = GetGLInjectHeader(); ++header->frame_counter; std::atomic_thread_fence(std::memory_order_release); // get capture parameters std::atomic_thread_fence(std::memory_order_acquire); *flags = header->capture_flags; if(!(*flags & GLINJECT_FLAG_CAPTURE_ENABLED)) return NULL; // check the timestamp and maybe limit the fps unsigned int target_fps = header->capture_target_fps; int64_t timestamp = hrt_time_micro(); if(target_fps > 0) { int64_t interval = 1000000 / target_fps; if(*flags & GLINJECT_FLAG_LIMIT_FPS) { if(timestamp < m_next_frame_time) { usleep(m_next_frame_time - timestamp); timestamp = hrt_time_micro(); } } else { if(timestamp < m_next_frame_time - interval) return NULL; } m_next_frame_time = std::max(m_next_frame_time + interval, timestamp); } // make sure that at least one frame is available unsigned int read_pos = header->ring_buffer_read_pos; unsigned int write_pos = header->ring_buffer_write_pos; unsigned int frames_used = positive_mod((int) write_pos - (int) read_pos, GLINJECT_RING_BUFFER_SIZE * 2); if(frames_used >= GLINJECT_RING_BUFFER_SIZE) return NULL; // write frame info GLInjectFrameInfo *frameinfo = GetGLInjectFrameInfo(write_pos % GLINJECT_RING_BUFFER_SIZE); frameinfo->timestamp = timestamp; frameinfo->width = m_width; frameinfo->height = m_height; frameinfo->stride = m_stride; // prepare the frame file FrameData &fd = m_frame_data[write_pos % GLINJECT_RING_BUFFER_SIZE]; size_t required_size = (size_t) abs(m_stride) * (size_t) m_height; if(required_size > fd.m_mmap_size_frame) { // calculate new size required_size = (required_size + required_size / 4 + m_page_size - 1) / m_page_size * m_page_size; // unmap frame file if(fd.m_mmap_ptr_frame != MAP_FAILED) { munmap(fd.m_mmap_ptr_frame, fd.m_mmap_size_frame); fd.m_mmap_ptr_frame = MAP_FAILED; fd.m_mmap_size_frame = 0; } // resize frame file if(ftruncate(fd.m_fd_frame, required_size) == -1) { GLINJECT_PRINT("Error: Can't resize video frame file!"); throw SSRStreamException(); } // map frame file fd.m_mmap_ptr_frame = mmap(NULL, required_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd.m_fd_frame, 0); if(fd.m_mmap_ptr_frame == MAP_FAILED) { GLINJECT_PRINT("Error: Can't memory-map video frame file!"); throw SSRStreamException(); } fd.m_mmap_size_frame = required_size; } return fd.m_mmap_ptr_frame; }
void SSRVideoStreamWatcher::Init() { // create channel directory if(mkdir(m_channel_directory.c_str(), (m_relax_permissions)? 0777 : 0700) == -1) { // does the directory exist? if(errno != EEXIST) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't create channel directory!")); throw SSRStreamException(); } // directory already exists, check ownership and permissions struct stat statinfo; if(lstat(m_channel_directory.c_str(), &statinfo) == -1) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't stat channel directory!")); throw SSRStreamException(); } if(!S_ISDIR(statinfo.st_mode) || S_ISLNK(statinfo.st_mode)) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Channel directory is not a regular directory!")); throw SSRStreamException(); } if(statinfo.st_uid == geteuid()) { if(chmod(m_channel_directory.c_str(), (m_relax_permissions)? 0777 : 0700) == -1) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't set channel directory mode!")); throw SSRStreamException(); } } else { if(!m_relax_permissions) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Channel directory is owned by a different user! " "Choose a different channel name, or enable relaxed file permissions to use it anyway.")); throw SSRStreamException(); } } } #ifdef __linux__ // initialize inotify m_fd_notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); if(m_fd_notify == -1) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't initialize inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } // watch channel directory if(inotify_add_watch(m_fd_notify, m_channel_directory.c_str(), IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO) == -1) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't watch channel directory!")); throw SSRStreamException(); } #endif // get all the files that existed already DIR *dir = NULL; try { // open directory dir = opendir(m_channel_directory.c_str()); if(dir == NULL) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't open channel directory!")); throw SSRStreamException(); } // get list of files for( ; ; ) { // get one file dirent *d = readdir(dir); if(d == NULL) break; // parse the name SSRVideoStream stream; if(!SSRVideoStreamParse(d->d_name, &stream)) continue; // add the stream Logger::LogInfo("[SSRVideoStreamWatcher::Init] " + Logger::tr("Added pre-existing stream %1.").arg(d->d_name)); m_streams.push_back(stream); } // close directory closedir(dir); dir = NULL; // sort by creation time std::sort(m_streams.begin(), m_streams.end()); } catch(...) { if(dir != NULL) { closedir(dir); dir = NULL; } throw; } }
void SSRVideoStreamWatcher::HandleChanges(AddCallback add_callback, RemoveCallback remove_callback, void* userdata) { #ifdef __linux__ // find out how much we can read int len; if(ioctl(m_fd_notify, FIONREAD, &len) == -1) { Logger::LogError("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Error: Can't get read length from inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } if(len > 0) { // read all the changes std::vector<char> buffer(len); if(read(m_fd_notify, buffer.data(), buffer.size()) != (ssize_t) buffer.size()) { Logger::LogError("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Error: Can't read from inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } // parse the changes size_t pos = 0; while(pos < buffer.size()) { // read the event structure if(buffer.size() - pos < sizeof(inotify_event)) { Logger::LogError("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Error: Received partial event from inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } inotify_event *event = (inotify_event*) (buffer.data() + pos); pos += sizeof(inotify_event); // ignore events with no name if(event->len == 0) continue; // read the name if(buffer.size() - pos < event->len) { Logger::LogError("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Error: Received partial name from inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } std::string name(buffer.data() + pos); // the name may be padded with null bytes that should be ignored pos += event->len; // parse the name SSRVideoStream stream; if(!SSRVideoStreamParse(name, &stream)) continue; // handle events if(event->mask & (IN_CREATE | IN_MOVED_TO)) { if(std::find(m_streams.begin(), m_streams.end(), stream) == m_streams.end()) { Logger::LogInfo("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Added stream %1.").arg(QString::fromStdString(stream.m_stream_name))); m_streams.push_back(stream); add_callback(stream, userdata); } } if(event->mask & (IN_DELETE | IN_MOVED_FROM)) { auto p = std::find(m_streams.begin(), m_streams.end(), stream); if(p != m_streams.end()) { Logger::LogInfo("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Removed stream %1.").arg(QString::fromStdString(stream.m_stream_name))); size_t index = p - m_streams.begin(); m_streams.erase(p); remove_callback(stream, index, userdata); } } } } // delete abandoned streams for(unsigned int j = m_streams.size(); j > 0; ) { --j; // does the process still exist? if(kill(m_streams[j].m_process_id, 0) == 0) continue; // if the process is dead, delete the files for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { std::string filename = m_channel_directory + "/videoframe" + NumToString(i) + "-" + m_streams[j].m_stream_name; unlink(filename.c_str()); } std::string filename = m_channel_directory + "/video-" + m_streams[j].m_stream_name; unlink(filename.c_str()); Logger::LogInfo("[SSRVideoStreamWatcher::Init] " + Logger::tr("Deleted abandoned stream %1.").arg(QString::fromStdString(m_streams[j].m_stream_name))); // remove the stream SSRVideoStream stream = m_streams[j]; m_streams.erase(m_streams.begin() + j); remove_callback(stream, j, userdata); } #endif }