+ vector<iovec> ret;
+ size_t max_iovecs = std::min<size_t>(data.size(), IOV_MAX);
+ for (size_t i = 0; i < max_iovecs && bytes_wanted > 0; ++i) {
+ if (data[i].data.iov_len <= bytes_wanted) {
+ // Consume the entire iovec.
+ ret.push_back(data[i].data);
+ bytes_wanted -= data[i].data.iov_len;
+ } else {
+ // Take only parts of this iovec.
+ iovec iov;
+ iov.iov_base = data[i].data.iov_base;
+ iov.iov_len = bytes_wanted;
+ ret.push_back(iov);
+ bytes_wanted = 0;
+ }
+ }
+ return ret;
+}
+
+// Return a new set of iovecs that contains all of <data> except the first <bytes_wanted> bytes.
+vector<Stream::DataElement> remove_iovecs(const vector<Stream::DataElement> &data, size_t bytes_wanted)
+{
+ vector<Stream::DataElement> ret;
+ size_t i;
+ for (i = 0; i < data.size() && bytes_wanted > 0; ++i) {
+ if (data[i].data.iov_len <= bytes_wanted) {
+ // Consume the entire iovec.
+ bytes_wanted -= data[i].data.iov_len;
+ } else {
+ // Take only parts of this iovec.
+ Stream::DataElement data_element;
+ data_element.data.iov_base = reinterpret_cast<char *>(data[i].data.iov_base) + bytes_wanted;
+ data_element.data.iov_len = data[i].data.iov_len - bytes_wanted;
+ data_element.suitable_for_stream_start = NOT_SUITABLE_FOR_STREAM_START;
+ ret.push_back(data_element);
+ bytes_wanted = 0;
+ }
+ }
+
+ // Add the rest of the iovecs unchanged.
+ ret.insert(ret.end(), data.begin() + i, data.end());
+ return ret;
+}
+
+void Stream::add_data_raw(const vector<DataElement> &orig_data)
+{
+ vector<DataElement> data = orig_data;
+ while (!data.empty()) {
+ size_t pos = bytes_received % backlog_size;
+
+ // Collect as many iovecs as we can before we hit the point
+ // where the circular buffer wraps around.
+ vector<iovec> to_write = collect_iovecs(data, backlog_size - pos);
+ ssize_t ret;
+ do {
+ ret = pwritev(data_fd, to_write.data(), to_write.size(), pos);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret == -1) {
+ log_perror("pwritev");
+ // Dazed and confused, but trying to continue...
+ return;
+ }
+ bytes_received += ret;
+
+ // Remove the data that was actually written from the set of iovecs.
+ data = remove_iovecs(data, ret);
+ }
+}
+
+void Stream::remove_obsolete_starting_points()
+{
+ // We could do a binary search here (std::lower_bound), but it seems
+ // overkill for removing what's probably only a few points.
+ while (!suitable_starting_points.empty() &&
+ bytes_received - suitable_starting_points[0] > backlog_size) {
+ suitable_starting_points.pop_front();
+ }
+}
+
+void Stream::add_data_deferred(const char *data, size_t bytes, StreamStartSuitability suitable_for_stream_start)
+{
+ MutexLock lock(&queued_data_mutex);
+ assert(suitable_for_stream_start == SUITABLE_FOR_STREAM_START ||
+ suitable_for_stream_start == NOT_SUITABLE_FOR_STREAM_START);
+
+ DataElement data_element;
+ data_element.suitable_for_stream_start = suitable_for_stream_start;
+
+ if (encoding == Stream::STREAM_ENCODING_METACUBE) {
+ // Add a Metacube block header before the data.
+ metacube2_block_header hdr;
+ memcpy(hdr.sync, METACUBE2_SYNC, sizeof(hdr.sync));
+ hdr.size = htonl(bytes);
+ hdr.flags = htons(0);
+ if (suitable_for_stream_start == NOT_SUITABLE_FOR_STREAM_START) {
+ hdr.flags |= htons(METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START);
+ }
+ hdr.csum = htons(metacube2_compute_crc(&hdr));
+
+ data_element.data.iov_base = new char[bytes + sizeof(hdr)];
+ data_element.data.iov_len = bytes + sizeof(hdr);
+
+ memcpy(data_element.data.iov_base, &hdr, sizeof(hdr));
+ memcpy(reinterpret_cast<char *>(data_element.data.iov_base) + sizeof(hdr), data, bytes);
+
+ queued_data.push_back(data_element);
+ } else if (encoding == Stream::STREAM_ENCODING_RAW) {
+ // Just add the data itself.
+ data_element.data.iov_base = new char[bytes];
+ memcpy(data_element.data.iov_base, data, bytes);
+ data_element.data.iov_len = bytes;
+
+ queued_data.push_back(data_element);
+ } else {
+ assert(false);
+ }
+}
+
+void Stream::process_queued_data()
+{
+ std::vector<DataElement> queued_data_copy;
+
+ // Hold the lock for as short as possible, since add_data_raw() can possibly
+ // write to disk, which might disturb the input thread.
+ {
+ MutexLock lock(&queued_data_mutex);
+ if (queued_data.empty()) {
+ return;
+ }
+
+ swap(queued_data, queued_data_copy);
+ }
+
+ // Add suitable starting points for the stream, if the queued data
+ // contains such starting points. Note that we drop starting points
+ // if they're less than 10 kB apart, so that we don't get a huge
+ // amount of them for e.g. each and every MPEG-TS 188-byte cell.
+ // The 10 kB value is somewhat arbitrary, but at least it should make
+ // the RAM cost of saving the position ~0.1% (or less) of the actual
+ // data, and 10 kB is a very fine granularity in most streams.
+ static const int minimum_start_point_distance = 10240;
+ size_t byte_position = bytes_received;
+ for (size_t i = 0; i < queued_data_copy.size(); ++i) {
+ if (queued_data_copy[i].suitable_for_stream_start == SUITABLE_FOR_STREAM_START) {
+ size_t num_points = suitable_starting_points.size();
+ if (num_points >= 2 &&
+ suitable_starting_points[num_points - 1] - suitable_starting_points[num_points - 2] < minimum_start_point_distance) {
+ // p[n-1] - p[n-2] < 10 kB, so drop p[n-1].
+ suitable_starting_points.pop_back();
+ }
+ suitable_starting_points.push_back(byte_position);
+ }
+ byte_position += queued_data_copy[i].data.iov_len;
+ }
+
+ add_data_raw(queued_data_copy);
+ remove_obsolete_starting_points();
+ for (size_t i = 0; i < queued_data_copy.size(); ++i) {
+ char *data = reinterpret_cast<char *>(queued_data_copy[i].data.iov_base);
+ delete[] data;
+ }
+
+ // We have more data, so wake up all clients.