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;
}
예제 #3
0
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);

}
예제 #4
0
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;
}
예제 #5
0
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;
	}

}
예제 #6
0
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
}