JeVoisBase  1.0
JeVois Smart Embedded Machine Vision Toolkit Base Modules
SaveVideo.C
Go to the documentation of this file.
1 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2016 by Laurent Itti, the University of Southern
4 // California (USC), and iLab at USC. See http://iLab.usc.edu and http://jevois.org for information about this project.
5 //
6 // This file is part of the JeVois Smart Embedded Machine Vision Toolkit. This program is free software; you can
7 // redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
8 // Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
10 // License for more details. You should have received a copy of the GNU General Public License along with this program;
11 // if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
12 //
13 // Contact information: Laurent Itti - 3641 Watt Way, HNB-07A - Los Angeles, CA 90089-2520 - USA.
14 // Tel: +1 213 740 3527 - itti@pollux.usc.edu - http://iLab.usc.edu - http://jevois.org
15 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
16 /*! \file */
17 
18 #include <jevois/Core/Module.H>
19 #include <jevois/Debug/Log.H>
22 
23 #include <opencv2/core/version.hpp>
24 
25 #include <opencv2/videoio.hpp> // for cv::VideoCapture
26 #include <opencv2/imgproc/imgproc.hpp>
27 
28 #include <future>
29 #include <linux/videodev2.h> // for v4l2 pixel types
30 #include <cstdlib> // for std::system()
31 #include <cstdio> // for snprintf()
32 #include <fstream>
33 
34 // icon by Madebyoliver in multimedia at flaticon
35 
36 static jevois::ParameterCategory const ParamCateg("Video Saving Options");
37 
38 #define PATHPREFIX "/jevois/data/savevideo/"
39 
40 //! Parameter \relates SaveVideo
41 JEVOIS_DECLARE_PARAMETER(filename, std::string, "Name of the video file to write. If path is not absolute, "
42  PATHPREFIX " will be prepended to it. Name should contain a printf-like directive for "
43  "one int argument, which will start at 0 and be incremented on each streamoff command.",
44  "video%06d.avi", ParamCateg);
45 
46 //! Parameter \relates SaveVideo
47 JEVOIS_DECLARE_PARAMETER(fourcc, std::string, "FourCC of the codec to use. The OpenCV VideoWriter doc is unclear "
48  "as to which codecs are supported. Presumably, the ffmpeg library is used inside OpenCV. "
49  "Hence any video encoder supported by ffmpeg should work. Tested codecs include: MJPG, "
50  "MP4V, AVC1. Make sure you also pick the right filename extension (e.g., .avi for MJPG, "
51  ".mp4 for MP4V, etc)",
52  "MJPG", boost::regex("^\\w{4}$"), ParamCateg);
53 
54 //! Parameter \relates SaveVideo
55 JEVOIS_DECLARE_PARAMETER(fps, double, "Video frames/sec as stored in the file and to be used during playback",
56  30.0, ParamCateg);
57 
58 //! Save captured camera frames into a video file
59 /*! Issue the command "start" to start saving video frames, and "stop" to stop saving. Successive start/stop commands
60  will increment the file number (%d argument in the 'filename' parameter. Before a file is written, we check whether
61  it already exists, and skip over it by incrementing the file number if so.
62 
63  This module works with any video resolution and pixel format supported by the camera sensor. Additional video
64  mappings are possible beyond the ones listed here.
65 
66  When using with no USB output (NONE output format), you should first issue a 'streamon' command to start video
67  streaming, then 'start'. The 'streamon' is not necessary when using with a USB video output, the host computer over
68  USB triggers video streaming when it starts grabbing frames from the JeVois camera.
69 
70  @author Laurent Itti
71 
72  @videomapping YUYV 320 240 60.0 YUYV 320 240 60.0 JeVois SaveVideo
73  @videomapping YUYV 320 240 30.0 YUYV 320 240 30.0 JeVois SaveVideo
74  @videomapping NONE 0 0 0 YUYV 320 240 60.0 JeVois SaveVideo
75  @videomapping NONE 0 0 0 YUYV 320 240 30.0 JeVois SaveVideo
76  @videomapping NONE 0 0 0 YUYV 176 144 120.0 JeVois SaveVideo
77  @email itti\@usc.edu
78  @address University of Southern California, HNB-07A, 3641 Watt Way, Los Angeles, CA 90089-2520, USA
79  @copyright Copyright (C) 2016 by Laurent Itti, iLab and the University of Southern California
80  @mainurl http://jevois.org
81  @supporturl http://jevois.org/doc
82  @otherurl http://iLab.usc.edu
83  @license GPL v3
84  @distribution Unrestricted
85  @restrictions None
86  \ingroup modules */
87 class SaveVideo : public jevois::Module,
88  public jevois::Parameter<filename, fourcc, fps>
89 {
90  public:
91  // ####################################################################################################
92  //! Constructor
93  // ####################################################################################################
94  SaveVideo(std::string const & instance) : jevois::Module(instance), itsBuf(1000), itsSaving(false),
95  itsFileNum(0), itsRunning(false)
96  { }
97 
98  // ####################################################################################################
99  //! Get started
100  // ####################################################################################################
101  void postInit() override
102  {
103  itsRunning.store(true);
104 
105  // Get our run() thread going, it is in charge of compresing and saving frames:
106  itsRunFut = std::async(std::launch::async, &SaveVideo::run, this);
107  }
108 
109  // ####################################################################################################
110  //! Get stopped
111  // ####################################################################################################
112  void postUninit() override
113  {
114  // Signal end of run:
115  itsRunning.store(false);
116 
117  // Push an empty frame into our buffer to signal the end of video to our thread:
118  itsBuf.push(cv::Mat());
119 
120  // Wait for the thread to complete:
121  LINFO("Waiting for writer thread to complete, " << itsBuf.filled_size() << " frames to go...");
122  try { itsRunFut.get(); } catch (...) { jevois::warnAndIgnoreException(); }
123  LINFO("Writer thread completed. Syncing disk...");
124  if (std::system("/bin/sync")) LERROR("Error syncing disk -- IGNORED");
125  LINFO("Video " << itsFilename << " saved.");
126  }
127 
128  // ####################################################################################################
129  //! Virtual destructor for safe inheritance
130  // ####################################################################################################
131  virtual ~SaveVideo()
132  { }
133 
134  // ####################################################################################################
135  //! Processing function, version that also shows a debug video output
136  // ####################################################################################################
137  void process(jevois::InputFrame && inframe, jevois::OutputFrame && outframe) override
138  {
139  // Wait for next available camera image:
140  jevois::RawImage inimg = inframe.get(true); unsigned int const w = inimg.width, h = inimg.height;
141  inimg.require("input", w, h, V4L2_PIX_FMT_YUYV); // accept any image size but require YUYV pixels
142 
143  if (itsSaving.load())
144  {
145  // Convert image to BGR and push to our writer thread:
146  if (itsBuf.filled_size() > 1000) LERROR("Image queue too large, video writer cannot keep up - DROPPING FRAME");
148  }
149 
150  // Copy the input image to output:
151  jevois::RawImage outimg = outframe.get();
152  outimg.require("output", w, h, V4L2_PIX_FMT_YUYV);
153 
154  jevois::rawimage::paste(inimg, outimg, 0, 0);
155 
156  // Let camera know we are done processing the raw YUV input image:
157  inframe.done();
158 
159  // Show some text messages:
160  std::string txt = "SaveVideo: "; if (itsSaving.load()) txt += "RECORDING"; else txt += "not recording";
161  jevois::rawimage::writeText(outimg, txt.c_str(), 3, 3, jevois::yuyv::White);
162  jevois::rawimage::writeText(outimg, itsFilename.c_str(), 3, h - 13, jevois::yuyv::White);
163 
164  // Send output frame over USB:
165  outframe.send();
166  }
167 
168  // ####################################################################################################
169  //! Processing function, version with no video output
170  // ####################################################################################################
171  void process(jevois::InputFrame && inframe) override
172  {
173  // Wait for next available camera image:
174  jevois::RawImage inimg = inframe.get(true);
175 
176  if (itsSaving.load())
177  {
178  // Convert image to BGR and push to our writer thread:
179  if (itsBuf.filled_size() > 1000) LERROR("Image queue too large, video writer cannot keep up - DROPPING FRAME");
181  }
182 
183  // Let camera know we are done processing the raw YUV input image:
184  inframe.done();
185  }
186 
187  // ####################################################################################################
188  //! Receive a string from a serial port which contains a user command
189  // ####################################################################################################
190  void parseSerial(std::string const & str, std::shared_ptr<jevois::UserInterface> s) override
191  {
192  if (str == "start")
193  {
194  itsSaving.store(true);
195  sendSerial("SAVESTART");
196  }
197  else if (str == "stop")
198  {
199  itsSaving.store(false);
200  sendSerial("SAVESTOP");
201 
202  // Push an empty frame into our buffer to signal the end of video to our thread:
203  itsBuf.push(cv::Mat());
204 
205  // Wait for the thread to empty our image buffer:
206  while (itsBuf.filled_size())
207  {
208  LINFO("Waiting for writer thread to complete, " << itsBuf.filled_size() << " frames to go...");
209  std::this_thread::sleep_for(std::chrono::milliseconds(200));
210  }
211  LINFO("Writer thread completed. Syncing disk...");
212  if (std::system("/bin/sync")) LERROR("Error syncing disk -- IGNORED");
213  LINFO("Video " << itsFilename << " saved.");
214  }
215  else throw std::runtime_error("Unsupported module command");
216  }
217 
218  // ####################################################################################################
219  //! Human-readable description of this Module's supported custom commands
220  // ####################################################################################################
221  void supportedCommands(std::ostream & os) override
222  {
223  os << "start - start saving video" << std::endl;
224  os << "stop - stop saving video and increment video file number" << std::endl;
225  }
226 
227  protected:
228  void run() // Runs in a thread
229  {
230  while (itsRunning.load())
231  {
232  // Create a VideoWriter here, since it has no close() function, this will ensure it gets destroyed and closes
233  // the movie once we stop the recording:
234  cv::VideoWriter writer;
235  int frame = 0;
236 
237  while (true)
238  {
239  // Get next frame from the buffer:
240  cv::Mat im = itsBuf.pop();
241 
242  // An empty image will be pushed when we are ready to close the video file:
243  if (im.empty()) break;
244 
245  // Start the encoder if it is not yet running:
246  if (writer.isOpened() == false)
247  {
248  // Parse the fourcc, regex in our param definition enforces 4 alphanumeric chars:
249  std::string const fcc = fourcc::get();
250  int const cvfcc = cv::VideoWriter::fourcc(fcc[0], fcc[1], fcc[2], fcc[3]);
251 
252  // Add path prefix if given filename is relative:
253  std::string fn = filename::get();
254  if (fn.empty()) LFATAL("Cannot save to an empty filename");
255  if (fn[0] != '/') fn = PATHPREFIX + fn;
256 
257  // Create directory just in case it does not exist:
258  std::string const cmd = "/bin/mkdir -p " + fn.substr(0, fn.rfind('/'));
259  if (std::system(cmd.c_str())) LERROR("Error running [" << cmd << "] -- IGNORED");
260 
261  // Fill in the file number; be nice and do not overwrite existing files:
262  while (true)
263  {
264  char tmp[2048];
265  std::snprintf(tmp, 2047, fn.c_str(), itsFileNum);
266  std::ifstream ifs(tmp);
267  if (ifs.is_open() == false) { itsFilename = tmp; break; }
268  ++itsFileNum;
269  }
270 
271  // Open the writer:
272  if (writer.open(itsFilename, cvfcc, fps::get(), im.size(), true) == false)
273  LFATAL("Failed to open video encoder for file [" << itsFilename << ']');
274 
275  sendSerial("SAVETO " + itsFilename);
276  }
277 
278  // Write the frame:
279  writer << im;
280 
281  // Report what is going on once in a while:
282  if ((++frame % 100) == 0) sendSerial("SAVEDNUM " + std::to_string(frame));
283  }
284 
285  // Our writer runs out of scope and closes the file here.
286  ++itsFileNum;
287  }
288  }
289 
290  std::future<void> itsRunFut;
292  std::atomic<bool> itsSaving;
294  std::atomic<bool> itsRunning;
295  std::string itsFilename;
296 };
297 
298 // Allow the module to be loaded as a shared object (.so) file:
Save captured camera frames into a video file.
Definition: SaveVideo.C:87
std::string warnAndIgnoreException()
size_t filled_size() const
friend friend class Module
void writeText(RawImage &img, std::string const &txt, int x, int y, unsigned int col, Font font=Font6x10)
void require(char const *info, unsigned int w, unsigned int h, unsigned int f) const
cv::Mat convertToCvBGR(RawImage const &src)
unsigned int height
void push(T const &val)
virtual ~SaveVideo()
Virtual destructor for safe inheritance.
Definition: SaveVideo.C:131
void postUninit() override
Get stopped.
Definition: SaveVideo.C:112
#define JEVOIS_DECLARE_PARAMETER(ParamName, ParamType,...)
Parameter.
void supportedCommands(std::ostream &os) override
Human-readable description of this Module's supported custom commands.
Definition: SaveVideo.C:221
void run()
Definition: SaveVideo.C:228
std::string itsFilename
Definition: SaveVideo.C:295
void parseSerial(std::string const &str, std::shared_ptr< jevois::UserInterface > s) override
Receive a string from a serial port which contains a user command.
Definition: SaveVideo.C:190
void postInit() override
Get started.
Definition: SaveVideo.C:101
#define LERROR(msg)
void process(jevois::InputFrame &&inframe) override
Processing function, version with no video output.
Definition: SaveVideo.C:171
JEVOIS_REGISTER_MODULE(SaveVideo)
int itsFileNum
Definition: SaveVideo.C:293
virtual void sendSerial(std::string const &str)
#define LFATAL(msg)
std::future< void > itsRunFut
Definition: SaveVideo.C:290
#define PATHPREFIX
Definition: SaveVideo.C:38
std::string to_string(T const &val)
jevois::BoundedBuffer< cv::Mat, jevois::BlockingBehavior::Block, jevois::BlockingBehavior::Block > itsBuf
Definition: SaveVideo.C:291
#define LINFO(msg)
SaveVideo(std::string const &instance)
Constructor.
Definition: SaveVideo.C:94
std::atomic< bool > itsRunning
Definition: SaveVideo.C:294
unsigned int width
void process(jevois::InputFrame &&inframe, jevois::OutputFrame &&outframe) override
Processing function, version that also shows a debug video output.
Definition: SaveVideo.C:137
std::atomic< bool > itsSaving
Definition: SaveVideo.C:292
void paste(RawImage const &src, RawImage &dest, int dx, int dy)