diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp index b223e6f9cca81fd1b357919761d79708e853576d..5e1413ddb660bea45dc6ab4f51f91bccbf78bc9d 100644 --- a/applications/gui/src/camera.cpp +++ b/applications/gui/src/camera.cpp @@ -230,6 +230,15 @@ void ftl::gui::Camera::showSettings() { } +void ftl::gui::Camera::setChannel(ftl::rgbd::channel_t c) { + channel_ = c; + switch (c) { + case ftl::rgbd::kChanRight: + case ftl::rgbd::kChanDepth: src_->setChannel(c); break; + default: src_->setChannel(ftl::rgbd::kChanNone); + } +} + const GLTexture &ftl::gui::Camera::thumbnail() { } @@ -255,11 +264,13 @@ const GLTexture &ftl::gui::Camera::captureFrame() { src_->grab(); src_->getFrames(rgb, depth); - if (!stats_ && depth.rows > 0) { - stats_ = new StatisticsImageNSamples(depth.size(), 25); + if (channel_ == ftl::rgbd::kChanDeviation) { + if (!stats_ && depth.rows > 0) { + stats_ = new StatisticsImageNSamples(depth.size(), 25); + } + + if (stats_ && depth.rows > 0) { stats_->update(depth); } } - - if (stats_ && depth.rows > 0) { stats_->update(depth); } cv::Mat tmp; @@ -282,6 +293,11 @@ const GLTexture &ftl::gui::Camera::captureFrame() { texture_.update(tmp); break; + case ftl::rgbd::kChanRight: + if (depth.rows == 0 || depth.type() != CV_8UC3) { break; } + texture_.update(depth); + break; + default: if (rgb.rows == 0) { break; } //imageSize = Vector2f(rgb.cols,rgb.rows); diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp index 590bdbceef2a7f781c2309549e13aa99350a2330..5284b28ebc17076615c5937142d7d340d94903ef 100644 --- a/applications/gui/src/camera.hpp +++ b/applications/gui/src/camera.hpp @@ -32,7 +32,7 @@ class Camera { void showPoseWindow(); void showSettings(); - void setChannel(ftl::rgbd::channel_t c) { channel_ = c; }; + void setChannel(ftl::rgbd::channel_t c); void togglePause(); void isPaused(); diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp index 84b45e9fc0cced3f03cb25f90855a4c18a2d1849..3d87de9e1771e320d4d21033de46af1d9545b4fa 100644 --- a/applications/gui/src/media_panel.cpp +++ b/applications/gui/src/media_panel.cpp @@ -109,6 +109,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), popbutton->setChevronIcon(ENTYPO_ICON_CHEVRON_SMALL_RIGHT); Popup *popup = popbutton->popup(); popup->setLayout(new GroupLayout()); + popup->setTheme(screen->toolbuttheme); popup->setAnchorHeight(100); button = new Button(popup, "Left"); @@ -121,9 +122,18 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), } }); - button = new Button(popup, "Depth"); - button->setFlags(Button::RadioButton); - button->setCallback([this]() { + right_button_ = new Button(popup, "Right"); + right_button_->setFlags(Button::RadioButton); + right_button_->setCallback([this]() { + ftl::gui::Camera *cam = screen_->activeCamera(); + if (cam) { + cam->setChannel(ftl::rgbd::kChanRight); + } + }); + + depth_button_ = new Button(popup, "Depth"); + depth_button_->setFlags(Button::RadioButton); + depth_button_->setCallback([this]() { ftl::gui::Camera *cam = screen_->activeCamera(); if (cam) { cam->setChannel(ftl::rgbd::kChanDepth); @@ -143,3 +153,15 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), MediaPanel::~MediaPanel() { } + +// Update button enabled status +void MediaPanel::cameraChanged() { + ftl::gui::Camera *cam = screen_->activeCamera(); + if (cam) { + if (cam->source()->hasCapabilities(ftl::rgbd::kCapStereo)) { + right_button_->setEnabled(true); + } else { + right_button_->setEnabled(false); + } + } +} diff --git a/applications/gui/src/media_panel.hpp b/applications/gui/src/media_panel.hpp index c65c48770fe4523e5c4847ca63a50a60f0af918d..d7f9aa9938ee51418629ee42781e738b535c38d0 100644 --- a/applications/gui/src/media_panel.hpp +++ b/applications/gui/src/media_panel.hpp @@ -18,10 +18,14 @@ class MediaPanel : public nanogui::Window { MediaPanel(ftl::gui::Screen *); ~MediaPanel(); + void cameraChanged(); + private: ftl::gui::Screen *screen_; bool paused_; ftl::rgbd::SnapshotStreamWriter *writer_; + nanogui::Button *right_button_; + nanogui::Button *depth_button_; }; } diff --git a/applications/gui/src/screen.cpp b/applications/gui/src/screen.cpp index 9f3d03bdfe99324fd2f30f15fbcd2a1ebd6e47dc..e451e48194bf0ac8c3bca4a28e3b6d8a3a5fede7 100644 --- a/applications/gui/src/screen.cpp +++ b/applications/gui/src/screen.cpp @@ -62,7 +62,7 @@ ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl setSize(Vector2i(1280,720)); - Theme *toolbuttheme = new Theme(*theme()); + toolbuttheme = new Theme(*theme()); toolbuttheme->mBorderDark = nanogui::Color(0,0); toolbuttheme->mBorderLight = nanogui::Color(0,0); toolbuttheme->mButtonGradientBotFocused = nanogui::Color(60,255); @@ -71,8 +71,9 @@ ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl toolbuttheme->mButtonGradientTopUnfocused = nanogui::Color(0,0); toolbuttheme->mButtonGradientTopPushed = nanogui::Color(60,180); toolbuttheme->mButtonGradientBotPushed = nanogui::Color(60,180); + toolbuttheme->mTextColor = nanogui::Color(0.9f,0.9f,0.9f,0.9f); - Theme *mediatheme = new Theme(*theme()); + mediatheme = new Theme(*theme()); mediatheme->mIconScale = 1.2f; mediatheme->mWindowDropShadowSize = 0; mediatheme->mWindowFillFocused = nanogui::Color(45, 150); @@ -255,11 +256,12 @@ void ftl::gui::Screen::setActiveCamera(ftl::gui::Camera *cam) { if (cam) { status_ = cam->source()->getURI(); mwindow_->setVisible(true); + mwindow_->cameraChanged(); swindow_->setVisible(false); } else { mwindow_->setVisible(false); swindow_->setVisible(true); - status_ = "No camera..."; + status_ = "[No camera]"; } } diff --git a/applications/gui/src/screen.hpp b/applications/gui/src/screen.hpp index 7746aa49ef4fe6b2332a7d68114fda90410c6c81..8bfce830a034e024ec42037dbc43539a62d50101 100644 --- a/applications/gui/src/screen.hpp +++ b/applications/gui/src/screen.hpp @@ -41,6 +41,8 @@ class Screen : public nanogui::Screen { nanogui::Theme *windowtheme; nanogui::Theme *specialtheme; + nanogui::Theme *mediatheme; + nanogui::Theme *toolbuttheme; private: ftl::gui::SourceWindow *swindow_; diff --git a/components/rgbd-sources/include/ftl/rgbd/source.hpp b/components/rgbd-sources/include/ftl/rgbd/source.hpp index 4fd875bc8cdc7166e4c5bb5a82bb7ba881701462..b084258bcd9d5a7365fd83136aec599d9701a406 100644 --- a/components/rgbd-sources/include/ftl/rgbd/source.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/source.hpp @@ -25,13 +25,14 @@ class SnapshotReader; typedef unsigned int channel_t; -static const channel_t kChanLeft = 1; -static const channel_t kChanDepth = 2; -static const channel_t kChanRight = 3; -static const channel_t kChanDisparity = 4; -static const channel_t kChanDeviation = 5; +static const channel_t kChanNone = 0; +static const channel_t kChanLeft = 0x0001; +static const channel_t kChanDepth = 0x0002; +static const channel_t kChanRight = 0x0004; +static const channel_t kChanDisparity = 0x0008; +static const channel_t kChanDeviation = 0x0010; -static const channel_t kChanOverlay1 = 100; +static const channel_t kChanOverlay1 = 0x1000; /** * RGBD Generic data source configurable entity. This class hides the @@ -69,6 +70,13 @@ class Source : public ftl::Configurable { */ bool isReady() { return (impl_) ? impl_->isReady() : false; } + /** + * Change the second channel source. + */ + bool setChannel(channel_t c); + + channel_t getChannel() const { return channel_; } + /** * Perform the hardware or virtual frame grab operation. */ @@ -180,6 +188,7 @@ class Source : public ftl::Configurable { SHARED_MUTEX mutex_; bool paused_; bool bullet_; + channel_t channel_; detail::Source *_createImplementation(); detail::Source *_createFileImpl(const ftl::URI &uri); diff --git a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp index 2ab3bbda4281d19837fd3924959ee193c8c5ed0f..30631b7f938033792a6303aab6f16649b4f30456 100644 --- a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp @@ -118,6 +118,9 @@ class Streamer : public ftl::Configurable { void _schedule(); void _swap(detail::StreamSource *); void _addClient(const std::string &source, int N, int rate, const ftl::UUID &peer, const std::string &dest); + void _encodeAndTransmit(detail::StreamSource *src, int chunk); + void _encodeChannel1(const cv::Mat &in, std::vector<unsigned char> &out, unsigned int b); + bool _encodeChannel2(const cv::Mat &in, std::vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b); }; } diff --git a/components/rgbd-sources/src/net.cpp b/components/rgbd-sources/src/net.cpp index bedac5b67b526118e89b96cbe21a9808b60befc8..a5cc73e08d0c441fecf9392c9d5d3f0170492783 100644 --- a/components/rgbd-sources/src/net.cpp +++ b/components/rgbd-sources/src/net.cpp @@ -57,6 +57,7 @@ NetSource::NetSource(ftl::rgbd::Source *host) gamma_ = host->value("gamma", 1.0f); temperature_ = host->value("temperature", 6500); + default_quality_ = host->value("quality", 0); host->on("gamma", [this,host](const ftl::config::Event&) { gamma_ = host->value("gamma", 1.0f); @@ -77,6 +78,10 @@ NetSource::NetSource(ftl::rgbd::Source *host) host_->getNet()->send(peer_, "update_cfg", host_->getURI() + "/baseline", host_->getConfig()["baseline"].dump()); }); + host->on("quality", [this,host](const ftl::config::Event&) { + default_quality_ = host->value("quality", 0); + }); + _updateURI(); h_ = host_->getNet()->onConnect([this](ftl::net::Peer *p) { @@ -101,7 +106,7 @@ void NetSource::_recvChunk(int frame, int chunk, bool delta, const vector<unsign // Decode in temporary buffers to prevent long locks cv::imdecode(jpg, cv::IMREAD_COLOR, &tmp_rgb); - cv::imdecode(d, cv::IMREAD_UNCHANGED, &tmp_depth); + if (d.size() > 0) cv::imdecode(d, cv::IMREAD_UNCHANGED, &tmp_depth); // Apply colour correction to chunk ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_); @@ -120,12 +125,25 @@ void NetSource::_recvChunk(int frame, int chunk, bool delta, const vector<unsign // Original size so just copy if (tmp_rgb.cols == chunkRGB.cols) { tmp_rgb.copyTo(chunkRGB); - tmp_depth.convertTo(chunkDepth, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f)); + if (!tmp_depth.empty() && tmp_depth.type() == CV_16U && chunkDepth.type() == CV_32F) { + tmp_depth.convertTo(chunkDepth, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f)); + } else if (!tmp_depth.empty() && tmp_depth.type() == CV_8UC3 && chunkDepth.type() == CV_8UC3) { + tmp_depth.copyTo(chunkDepth); + } else { + // Silent ignore? + } // Downsized so needs a scale up } else { cv::resize(tmp_rgb, chunkRGB, chunkRGB.size()); tmp_depth.convertTo(tmp_depth, CV_32FC1, 1.0f/1000.0f); - cv::resize(tmp_depth, chunkDepth, chunkDepth.size()); + if (!tmp_depth.empty() && tmp_depth.type() == CV_16U && chunkDepth.type() == CV_32F) { + tmp_depth.convertTo(tmp_depth, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f)); + cv::resize(tmp_depth, chunkDepth, chunkDepth.size()); + } else if (!tmp_depth.empty() && tmp_depth.type() == CV_8UC3 && chunkDepth.type() == CV_8UC3) { + cv::resize(tmp_depth, chunkDepth, chunkDepth.size()); + } else { + // Silent ignore? + } } if (chunk == 0) { @@ -150,6 +168,7 @@ void NetSource::setPose(const Eigen::Matrix4d &pose) { void NetSource::_updateURI() { UNIQUE_LOCK(mutex_,lk); active_ = false; + prev_chan_ = ftl::rgbd::kChanNone; auto uri = host_->get<string>("uri"); if (uri_.size() > 0) { @@ -206,9 +225,25 @@ bool NetSource::grab(int n, int b) { // Send k frames before end to prevent unwanted pause // Unless only a single frame is requested if ((N_ <= 2 && maxN_ > 1) || N_ == 0) { + const ftl::rgbd::channel_t chan = host_->getChannel(); + N_ = maxN_; - if (!host_->getNet()->send(peer_, "get_stream", *host_->get<string>("uri"), N_, minB_, host_->getNet()->id(), *host_->get<string>("uri"))) { + // Verify depth destination is of required type + if (chan == ftl::rgbd::kChanDepth && depth_.type() != CV_32F) { + depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_32FC1, 0.0f); + } else if (chan == ftl::rgbd::kChanRight && depth_.type() != CV_8UC3) { + depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_8UC3, cv::Scalar(0,0,0)); + } + + if (prev_chan_ != chan) { + host_->getNet()->send(peer_, "set_channel", *host_->get<string>("uri"), chan); + prev_chan_ = chan; + } + + if (!host_->getNet()->send(peer_, "get_stream", + *host_->get<string>("uri"), N_, minB_, + host_->getNet()->id(), *host_->get<string>("uri"))) { active_ = false; } diff --git a/components/rgbd-sources/src/net.hpp b/components/rgbd-sources/src/net.hpp index a962d880a973c1696728de808c30ffd9dae11b55..3f8a86df3001b49d1a1ac2d097667fc5ea175080 100644 --- a/components/rgbd-sources/src/net.hpp +++ b/components/rgbd-sources/src/net.hpp @@ -45,6 +45,8 @@ class NetSource : public detail::Source { int temperature_; int minB_; int maxN_; + int default_quality_; + ftl::rgbd::channel_t prev_chan_; bool _getCalibration(ftl::net::Universe &net, const ftl::UUID &peer, const std::string &src, ftl::rgbd::Camera &p); void _recv(const std::vector<unsigned char> &jpg, const std::vector<unsigned char> &d); diff --git a/components/rgbd-sources/src/source.cpp b/components/rgbd-sources/src/source.cpp index f810f38daf7e94ef889f51bb4fbc88d4b7102814..dbf23f203f987c35a0f0a2bf9b27bdc01d73a4a3 100644 --- a/components/rgbd-sources/src/source.cpp +++ b/components/rgbd-sources/src/source.cpp @@ -132,7 +132,6 @@ ftl::rgbd::detail::Source *Source::_createFileImpl(const ftl::URI &uri) { } ftl::rgbd::detail::Source *Source::_createNetImpl(const ftl::URI &uri) { - LOG(INFO) << "MAKE NET SOURCE"; return new NetSource(this); } @@ -203,6 +202,7 @@ capability_t Source::getCapabilities() const { void Source::reset() { UNIQUE_LOCK(mutex_,lk); + channel_ = kChanNone; if (impl_) delete impl_; impl_ = _createImplementation(); } @@ -233,3 +233,9 @@ bool Source::thumbnail(cv::Mat &t) { t = thumb_; return !thumb_.empty(); } + +bool Source::setChannel(ftl::rgbd::channel_t c) { + channel_ = c; + // TODO(Nick) Verify channel is supported by this source... + return true; +} diff --git a/components/rgbd-sources/src/stereovideo.cpp b/components/rgbd-sources/src/stereovideo.cpp index ad8fafa4371f03c52a37c6ed05c2e725bd317d4b..6e1c321a1eb91ef713c305ddca09b83914b78110 100644 --- a/components/rgbd-sources/src/stereovideo.cpp +++ b/components/rgbd-sources/src/stereovideo.cpp @@ -122,17 +122,33 @@ static void disparityToDepth(const cv::cuda::GpuMat &disparity, cv::cuda::GpuMat } bool StereoVideoSource::grab(int n, int b) { - lsrc_->get(left_, right_, stream_); - if (depth_tmp_.empty()) depth_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1); - if (disp_tmp_.empty()) disp_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1); - calib_->rectifyStereo(left_, right_, stream_); - disp_->compute(left_, right_, disp_tmp_, stream_); - disparityToDepth(disp_tmp_, depth_tmp_, calib_->getQ(), stream_); - left_.download(rgb_, stream_); - //rgb_ = lsrc_->cachedLeft(); - depth_tmp_.download(depth_, stream_); - - stream_.waitForCompletion(); + const ftl::rgbd::channel_t chan = host_->getChannel(); + + if (chan == ftl::rgbd::kChanDepth) { + lsrc_->get(left_, right_, stream_); + if (depth_tmp_.empty()) depth_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1); + if (disp_tmp_.empty()) disp_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1); + calib_->rectifyStereo(left_, right_, stream_); + disp_->compute(left_, right_, disp_tmp_, stream_); + disparityToDepth(disp_tmp_, depth_tmp_, calib_->getQ(), stream_); + left_.download(rgb_, stream_); + //rgb_ = lsrc_->cachedLeft(); + depth_tmp_.download(depth_, stream_); + + stream_.waitForCompletion(); + } else if (chan == ftl::rgbd::kChanRight) { + lsrc_->get(left_, right_, stream_); + calib_->rectifyStereo(left_, right_, stream_); + left_.download(rgb_, stream_); + right_.download(depth_, stream_); + stream_.waitForCompletion(); + } else { + lsrc_->get(left_, right_, stream_); + calib_->rectifyStereo(left_, right_, stream_); + //rgb_ = lsrc_->cachedLeft(); + left_.download(rgb_, stream_); + stream_.waitForCompletion(); + } return true; } diff --git a/components/rgbd-sources/src/streamer.cpp b/components/rgbd-sources/src/streamer.cpp index 6c656ec79d297567b30491b02aaec8948e390330..815701cf4facce74dc2ddf26925129ebdf58d071 100644 --- a/components/rgbd-sources/src/streamer.cpp +++ b/components/rgbd-sources/src/streamer.cpp @@ -91,6 +91,14 @@ Streamer::Streamer(nlohmann::json &config, Universe *net) _addClient(source, N, rate, peer, dest); }); + net->bind("set_channel", [this](const string &uri, unsigned int chan) { + SHARED_LOCK(mutex_,slk); + + if (sources_.find(uri) != sources_.end()) { + sources_[uri]->src->setChannel((ftl::rgbd::channel_t)chan); + } + }); + net->bind("sync_streams", [this](unsigned long long time) { // Calc timestamp delta }); @@ -292,6 +300,7 @@ void Streamer::_schedule() { // Grab job ftl::pool.push([this,src](int id) { //auto start = std::chrono::high_resolution_clock::now(); + try { src->src->grab(); } catch (std::exception &ex) { @@ -319,72 +328,12 @@ void Streamer::_schedule() { for (int i=0; i<kChunkCount; ++i) { // Add chunk job to thread pool ftl::pool.push([this,src](int id, int chunk) { - if (!src->rgb.empty() && !src->depth.empty()) { - bool delta = (chunk+src->frame) % 8 > 0; // Do XOR or not - int chunk_width = src->rgb.cols / kChunkDim; - int chunk_height = src->rgb.rows / kChunkDim; - - // Build chunk heads - int cx = (chunk % kChunkDim) * chunk_width; - int cy = (chunk / kChunkDim) * chunk_height; - cv::Rect roi(cx,cy,chunk_width,chunk_height); - vector<unsigned char> rgb_buf; - cv::Mat chunkRGB = src->rgb(roi); - cv::Mat chunkDepth = src->depth(roi); - //cv::Mat chunkDepthPrev = src->prev_depth(roi); - - cv::Mat d2, d3; - vector<unsigned char> d_buf; - chunkDepth.convertTo(d2, CV_16UC1, 1000); // 16*10); - //if (delta) d3 = (d2 * 2) - chunkDepthPrev; - //else d3 = d2; - //d2.copyTo(chunkDepthPrev); - - // For each allowed bitrate setting (0 = max quality) - for (unsigned int b=0; b<10; ++b) { - { - //SHARED_LOCK(src->mutex,lk); - if (src->clients[b].size() == 0) continue; - } - - // Max bitrate means no changes - if (b == 0) { - cv::imencode(".jpg", chunkRGB, rgb_buf); - vector<int> pngparams = {cv::IMWRITE_PNG_COMPRESSION, compress_level_}; // Default is 1 for fast, 9 = small but slow. - cv::imencode(".png", d2, d_buf, pngparams); - - // Otherwise must downscale and change compression params - // TODO(Nick) could reuse downscales - } else { - cv::Mat downrgb, downdepth; - cv::resize(chunkRGB, downrgb, cv::Size(bitrate_settings[b].width / kChunkDim, bitrate_settings[b].height / kChunkDim)); - cv::resize(d2, downdepth, cv::Size(bitrate_settings[b].width / kChunkDim, bitrate_settings[b].height / kChunkDim)); - vector<int> jpgparams = {cv::IMWRITE_JPEG_QUALITY, bitrate_settings[b].jpg_quality}; - cv::imencode(".jpg", downrgb, rgb_buf, jpgparams); - vector<int> pngparams = {cv::IMWRITE_PNG_COMPRESSION, bitrate_settings[b].png_compression}; // Default is 1 for fast, 9 = small but slow. - cv::imencode(".png", downdepth, d_buf, pngparams); - } - - //if (chunk == 0) LOG(INFO) << "Sending chunk " << chunk << " : size = " << (d_buf.size()+rgb_buf.size()) << "bytes"; - - // Lock to prevent clients being added / removed - SHARED_LOCK(src->mutex,lk); - auto c = src->clients[b].begin(); - while (c != src->clients[b].end()) { - try { - // TODO(Nick) Send pose and timestamp - if (!net_->send((*c).peerid, (*c).uri, 0, chunk, delta, rgb_buf, d_buf)) { - // Send failed so mark as client stream completed - (*c).txcount = (*c).txmax; - } else { - ++(*c).txcount; - } - } catch(...) { - (*c).txcount = (*c).txmax; - } - ++c; - } - } + try { + if (!src->rgb.empty() && (src->src->getChannel() == ftl::rgbd::kChanNone || !src->depth.empty())) { + _encodeAndTransmit(src, chunk); + } + } catch(...) { + LOG(ERROR) << "Encode Exception: " << chunk; } src->jobs--; @@ -397,6 +346,102 @@ void Streamer::_schedule() { } } +void Streamer::_encodeAndTransmit(StreamSource *src, int chunk) { + bool hasChan2 = (!src->depth.empty() && src->src->getChannel() != ftl::rgbd::kChanNone); + + bool delta = (chunk+src->frame) % 8 > 0; // Do XOR or not + int chunk_width = src->rgb.cols / kChunkDim; + int chunk_height = src->rgb.rows / kChunkDim; + + // Build chunk heads + int cx = (chunk % kChunkDim) * chunk_width; + int cy = (chunk / kChunkDim) * chunk_height; + cv::Rect roi(cx,cy,chunk_width,chunk_height); + vector<unsigned char> rgb_buf; + cv::Mat chunkRGB = src->rgb(roi); + cv::Mat chunkDepth; + //cv::Mat chunkDepthPrev = src->prev_depth(roi); + + cv::Mat d2, d3; + vector<unsigned char> d_buf; + + if (hasChan2) { + chunkDepth = src->depth(roi); + if (chunkDepth.type() == CV_32F) chunkDepth.convertTo(d2, CV_16UC1, 1000); // 16*10); + else d2 = chunkDepth; + //if (delta) d3 = (d2 * 2) - chunkDepthPrev; + //else d3 = d2; + //d2.copyTo(chunkDepthPrev); + } + + // For each allowed bitrate setting (0 = max quality) + for (unsigned int b=0; b<10; ++b) { + { + //SHARED_LOCK(src->mutex,lk); + if (src->clients[b].size() == 0) continue; + } + + // Max bitrate means no changes + if (b == 0) { + _encodeChannel1(chunkRGB, rgb_buf, b); + if (hasChan2) _encodeChannel2(d2, d_buf, src->src->getChannel(), b); + + // Otherwise must downscale and change compression params + // TODO:(Nick) could reuse downscales + } else { + cv::Mat downrgb, downdepth; + cv::resize(chunkRGB, downrgb, cv::Size(bitrate_settings[b].width / kChunkDim, bitrate_settings[b].height / kChunkDim)); + if (hasChan2) cv::resize(d2, downdepth, cv::Size(bitrate_settings[b].width / kChunkDim, bitrate_settings[b].height / kChunkDim)); + + _encodeChannel1(downrgb, rgb_buf, b); + if (hasChan2) _encodeChannel2(downdepth, d_buf, src->src->getChannel(), b); + } + + //if (chunk == 0) LOG(INFO) << "Sending chunk " << chunk << " : size = " << (d_buf.size()+rgb_buf.size()) << "bytes"; + + // Lock to prevent clients being added / removed + SHARED_LOCK(src->mutex,lk); + auto c = src->clients[b].begin(); + while (c != src->clients[b].end()) { + try { + // TODO:(Nick) Send pose and timestamp + if (!net_->send((*c).peerid, (*c).uri, 0, chunk, delta, rgb_buf, d_buf)) { + // Send failed so mark as client stream completed + (*c).txcount = (*c).txmax; + } else { + ++(*c).txcount; + } + } catch(...) { + (*c).txcount = (*c).txmax; + } + ++c; + } + } +} + +void Streamer::_encodeChannel1(const cv::Mat &in, vector<unsigned char> &out, unsigned int b) { + vector<int> jpgparams = {cv::IMWRITE_JPEG_QUALITY, bitrate_settings[b].jpg_quality}; + cv::imencode(".jpg", in, out, jpgparams); +} + +bool Streamer::_encodeChannel2(const cv::Mat &in, vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b) { + if (c == ftl::rgbd::kChanNone) return false; // NOTE: Should not happen + + if (c == ftl::rgbd::kChanDepth && in.type() == CV_16U && in.channels() == 1) { + vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, bitrate_settings[b].png_compression}; + cv::imencode(".png", in, out, params); + return true; + } else if (c == ftl::rgbd::kChanRight && in.type() == CV_8UC3) { + vector<int> params = {cv::IMWRITE_JPEG_QUALITY, bitrate_settings[b].jpg_quality}; + cv::imencode(".jpg", in, out, params); + return true; + } else { + LOG(ERROR) << "Bad channel configuration: channel=" << c << " imagetype=" << in.type(); + } + + return false; +} + Source *Streamer::get(const std::string &uri) { SHARED_LOCK(mutex_,slk); if (sources_.find(uri) != sources_.end()) return sources_[uri]->src;