Advertisement
Guest User

Untitled

a guest
Jul 23rd, 2019
954
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 35.17 KB | None | 0 0
  1. /*
  2. * Copyright (c) 2016 - 2019, NVIDIA CORPORATION. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions
  6. * are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of NVIDIA CORPORATION nor the names of its
  13. * contributors may be used to endorse or promote products derived
  14. * from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  19. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  20. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  21. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  22. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  23. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  24. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  26. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. */
  28.  
  29. #include <Argus/Argus.h>
  30. #include <gst/gst.h>
  31. #include <stdlib.h>
  32. #include <unistd.h>
  33. #include "ArgusHelpers.h"
  34. #include "CommonOptions.h"
  35. #include "Error.h"
  36. #include "PreviewConsumer.h"
  37. #include <string>
  38. #include <queue>
  39.  
  40. namespace ArgusSamples
  41. {
  42.  
  43. // Globals.
  44. EGLDisplayHolder g_display;
  45. volatile sig_atomic_t shutdownSignalReceived = 0;
  46.  
  47. void ctrl_c_sig_handler(int signo);
  48. void ctrl_c_sig_handler(int signo)
  49. {
  50. shutdownSignalReceived = 1;
  51. }
  52.  
  53. /*******************************************************************************
  54. * Extended options class to add additional options specific to this sample.
  55. ******************************************************************************/
  56. class GstVideoEncodeSampleOptions : public CommonOptions
  57. {
  58. public:
  59. enum NoiseModeArg
  60. {
  61. NOISE_MODE_ARG_OFF = 1,
  62. NOISE_MODE_ARG_FAST = 2,
  63. NOISE_MODE_ARG_HIGH_QUALITY = 3
  64. };
  65.  
  66. GstVideoEncodeSampleOptions(const char *programName)
  67. : CommonOptions(programName,
  68. ArgusSamples::CommonOptions::Option_D_CameraDevice |
  69. ArgusSamples::CommonOptions::Option_M_SensorMode |
  70. ArgusSamples::CommonOptions::Option_R_WindowRect |
  71. ArgusSamples::CommonOptions::Option_T_CaptureTime)
  72. , m_enablePreview(true)
  73. , m_framerate(0)
  74. , m_bitrate(14000000)
  75. , m_useP016(false)
  76. , m_nrMode(NOISE_MODE_ARG_FAST)
  77. , m_useH264(false)
  78. , m_useWdr(false)
  79. , m_path("./")
  80. , m_filename("argus_gstvideoencode_out")
  81. , m_remoteAddress("")
  82. , m_remoteUsername("")
  83. , m_remotePath("~/")
  84. , m_keepLocalFiles(false)
  85. , m_partSize(0)
  86. {
  87. addOption(createValueOption
  88. ("preview", 'p', "0 or 1", "Enable preview window.", m_enablePreview, "1"));
  89. addOption(createValueOption
  90. ("framerate", 0, "RATE", "Video recording framerate. 0 indicates sensor mode maximum.",
  91. m_framerate));
  92. addOption(createValueOption
  93. ("bitrate", 0, "RATE", "Video recording bitrate.", m_bitrate));
  94. addOption(createValueOption
  95. ("usep016", 0, "FLAG", "Use P016 deep-color format (instead of NV12).", m_useP016));
  96. addOption(createValueOption
  97. ("nrmode", 'n', "MODE", "Noise reduction: 1=off, 2=fast, 3=best", m_nrMode));
  98. addOption(createValueOption
  99. ("useh264", 0, "FLAG", "Use H.264 video format (else H.265)", m_useH264));
  100. addOption(createValueOption
  101. ("usewdr", 'w', "0 or 1", "Force the use of a WDR SensorMode. This will use the first "
  102. "available WDR (DOL or PWL) mode, and overrides any explicit --mode setting.",
  103. m_useWdr, "1"));
  104. addOption(createValueOption
  105. ("path", 0, "PATH", "Output path (directory) for the output file(s). This path must be "
  106. "writable by the application, and files will be written to this location regardless"
  107. "of whether or not files are being sent to a remote server. See --keeplocal.",
  108. m_path));
  109. addOption(createValueOption
  110. ("file", 0, "FILE", "Filename for the output file(s). This will be prefixed to "
  111. "the part number and file type suffix (i.e. '.mkv') that will be appended by the app.",
  112. m_filename));
  113. addOption(createValueOption
  114. ("partsize", 0, "MBYTES", "Maximum size, in MBytes, of the output video files. When "
  115. "non-zero, the video output will be divided into multiple parts of this maximum size, "
  116. "identified by a part number appended to the filename. These parts are written as "
  117. "raw HEVC data (i.e. it is not written to an mp4 or mkv container).",
  118. m_partSize, "0"));
  119. addOption(createValueOption
  120. ("remoteaddress", 0, "ADDRESS", "Address (host name or IP address) for remote file writes.",
  121. m_remoteAddress));
  122. addOption(createValueOption
  123. ("remoteusername", 0, "USER", "Username for remote file writes. Note that no 'password' "
  124. "option is available; the expectation is that SSH passwordless login is configured "
  125. "for this user on the remote device.",
  126. m_remoteUsername));
  127. addOption(createValueOption
  128. ("remotepath", 0, "PATH", "Path on the remote server where the files will be copied.",
  129. m_remotePath));
  130. addOption(createValueOption
  131. ("keeplocal", 0, "FLAG", "When remote copies are enabled, this flag will specify "
  132. "whether or not the local copy should be deleted after being copied.",
  133. m_keepLocalFiles));
  134.  
  135. addDescription("\nThis sample demonstrates the use of a GStreamer consumer for encoding\n"
  136. "video streams from an Argus OutputStream.\n\n"
  137. "In order to support 24x7 use cases such as security camera recording,\n"
  138. "this sample can record indefinitely (specified with a capture time of 0),\n"
  139. "and can optionally write the output stream to multiple file parts using\n"
  140. "a maximum file size. These parts can then be optionally copied to a\n"
  141. "remote server, and then kept or deleted from the local device.\n"
  142. "For example, the following will indefinitely record video in 100MB chunks\n"
  143. "that will be written to the local path '/argusLocal/' before being copied\n"
  144. "to 192.168.1.1:/argusRemote/ using the 'argus' username. The local files\n"
  145. "will also be preserved using --keeplocal\n\n"
  146. " argus_gstvideoencode -t0 \\\n"
  147. " --partsize=100 \\\n"
  148. " --path=/argusLocal/ \\\n"
  149. " --remoteaddress=192.168.1.1 \\\n"
  150. " --remoteusername=argus \\\n"
  151. " --remotepath=/argusRemote/ \\\n"
  152. " --keeplocal\n\n");
  153. }
  154.  
  155. bool enablePreview() const { return m_enablePreview.get(); }
  156. uint32_t framerate() const { return m_framerate.get(); }
  157. uint32_t bitrate() const { return m_bitrate.get(); }
  158. bool useP016() const { return m_useP016.get(); }
  159. NoiseModeArg nrMode() const { return static_cast<NoiseModeArg>(m_nrMode.get()); }
  160. bool useH264() const { return m_useH264.get(); }
  161. bool useWdr() const { return m_useWdr.get(); }
  162. const std::string& path() const { return m_path.get(); }
  163. const std::string& filename() const { return m_filename.get(); }
  164. const std::string& address() const { return m_remoteAddress.get(); }
  165. const std::string& username() const { return m_remoteUsername.get(); }
  166. const std::string& remotePath() const { return m_remotePath.get(); }
  167. bool keepLocalFiles() const { return m_keepLocalFiles.get(); }
  168. uint64_t partSize() const { return m_partSize.get() * 1024 * 1024; }
  169. bool isRemoteDestination() const { return m_remoteAddress.get() != ""; }
  170.  
  171. protected:
  172. Value<bool> m_enablePreview;
  173. Value<uint32_t> m_framerate;
  174. Value<uint32_t> m_bitrate;
  175. Value<bool> m_useP016;
  176. Value<uint32_t> m_nrMode;
  177. Value<bool> m_useH264;
  178. Value<bool> m_useWdr;
  179. Value<std::string> m_path;
  180. Value<std::string> m_filename;
  181. Value<std::string> m_remoteAddress;
  182. Value<std::string> m_remoteUsername;
  183. Value<std::string> m_remotePath;
  184. Value<bool> m_keepLocalFiles;
  185. Value<uint32_t> m_partSize;
  186. };
  187.  
  188. /**
  189. * Utility thread to copy files to a remote server using scp.
  190. * This class is only used when the --remoteaddress option is provided for remote file writes.
  191. */
  192. class FileTransferThread : public Thread
  193. {
  194. public:
  195. FileTransferThread(const GstVideoEncodeSampleOptions& options)
  196. : m_options(options)
  197. {
  198. }
  199.  
  200. /**
  201. * Queues a file to be transfered to the remote destination.
  202. * If the keepLocalFiles option is false, the source file is deleted after the copy.
  203. */
  204. void sendFile(const std::string& fileName)
  205. {
  206. m_queue.push(fileName);
  207. }
  208.  
  209. private:
  210. virtual bool threadInitialize() { return true; }
  211. virtual bool threadShutdown() { return true; }
  212.  
  213. virtual bool threadExecute()
  214. {
  215. while (!m_doShutdown && m_queue.size() == 0)
  216. sleep(1);
  217.  
  218. if (m_queue.size() > 0)
  219. {
  220. // Copy file to server.
  221. const char* filename = m_queue.front().c_str();
  222. std::string cmd = std::string("scp ") + filename + " " + m_options.username() +
  223. "@" + m_options.address() + ":" + m_options.remotePath() +
  224. " > /dev/null";
  225. if (system(cmd.c_str()))
  226. {
  227. printf("Failed to copy %s to %s\n", filename, m_options.address().c_str());
  228. return false;
  229. }
  230. else
  231. {
  232. printf("Copied %s to %s:%s (source file is being %s)\n",
  233. filename, m_options.address().c_str(),
  234. m_options.remotePath().c_str(),
  235. (m_options.keepLocalFiles() ? "kept" : "deleted"));
  236. }
  237.  
  238. if (!m_options.keepLocalFiles())
  239. {
  240. // Delete file.
  241. cmd = std::string("rm ") + filename;
  242. if (system(cmd.c_str()))
  243. {
  244. printf("Failed to delete %s\n", filename);
  245. }
  246. }
  247.  
  248. m_queue.pop();
  249. }
  250.  
  251. return true;
  252. }
  253.  
  254. const GstVideoEncodeSampleOptions& m_options;
  255. std::queue< std::string > m_queue;
  256. };
  257.  
  258.  
  259. /**
  260. * Class to initialize and control GStreamer video encoding from an EGLStream.
  261. */
  262. class GstVideoEncoder
  263. {
  264. public:
  265. GstVideoEncoder(const GstVideoEncodeSampleOptions& options)
  266. : m_options(options)
  267. , m_state(GST_STATE_NULL)
  268. , m_pipeline(NULL)
  269. , m_videoEncoder(NULL)
  270. , m_fileTransferThread(NULL)
  271. , m_currentPartNumber(0)
  272. , m_currentPartFile(NULL)
  273. , m_totalBytesWritten(0)
  274. {
  275. }
  276.  
  277. ~GstVideoEncoder()
  278. {
  279. shutdown();
  280. }
  281.  
  282. /**
  283. * Initialize the GStreamer video encoder pipeline.
  284. * @param[in] eglStream The EGLStream to consume frames from.
  285. * @param[in] resolution The resolution of the video.
  286. * @param[in] framerate The framerate of the video (in frames per second).
  287. */
  288. bool initialize(EGLStreamKHR eglStream, Argus::Size2D<uint32_t> resolution, int32_t framerate)
  289. {
  290. // Only H265 supports resolutions > 4k.
  291. const uint32_t WIDTH_4K = 3840;
  292. if (resolution.width() > WIDTH_4K && m_options.useH264())
  293. {
  294. ORIGINATE_ERROR("Resolution > 4k requires encoder to be H265\n");
  295. }
  296.  
  297. // If we're outputting to a remote destination, allocate/start the FileTransferThread.
  298. if (m_options.isRemoteDestination())
  299. {
  300. m_fileTransferThread = new FileTransferThread(m_options);
  301. if (!m_fileTransferThread)
  302. ORIGINATE_ERROR("Failed to create FileTransferThread");
  303. PROPAGATE_ERROR(m_fileTransferThread->initialize());
  304. }
  305.  
  306. // Initialize GStreamer.
  307. gst_init(NULL, NULL);
  308.  
  309. // Create pipeline.
  310. m_pipeline = gst_pipeline_new("video_pipeline");
  311. if (!m_pipeline)
  312. ORIGINATE_ERROR("Failed to create video pipeline");
  313.  
  314. // Create EGLStream video source.
  315. GstElement *videoSource = gst_element_factory_make("nveglstreamsrc", NULL);
  316. if (!videoSource)
  317. ORIGINATE_ERROR("Failed to create EGLStream video source");
  318. if (!gst_bin_add(GST_BIN(m_pipeline), videoSource))
  319. {
  320. gst_object_unref(videoSource);
  321. ORIGINATE_ERROR("Failed to add video source to pipeline");
  322. }
  323. g_object_set(G_OBJECT(videoSource), "display", g_display.get(), NULL);
  324. g_object_set(G_OBJECT(videoSource), "eglstream", eglStream, NULL);
  325.  
  326. // Create queue.
  327. GstElement *queue = gst_element_factory_make("queue", NULL);
  328. if (!queue)
  329. ORIGINATE_ERROR("Failed to create queue");
  330. if (!gst_bin_add(GST_BIN(m_pipeline), queue))
  331. {
  332. gst_object_unref(queue);
  333. ORIGINATE_ERROR("Failed to add queue to pipeline");
  334. }
  335.  
  336. // Create encoder.
  337. const char* encoder = m_options.useH264() ? "omxh264enc" : "omxh265enc";
  338. m_videoEncoder = gst_element_factory_make(encoder, NULL);
  339. if (!m_videoEncoder)
  340. ORIGINATE_ERROR("Failed to create video encoder");
  341. if (!gst_bin_add(GST_BIN(m_pipeline), m_videoEncoder))
  342. {
  343. gst_object_unref(m_videoEncoder);
  344. ORIGINATE_ERROR("Failed to add video encoder to pipeline");
  345. }
  346. g_object_set(G_OBJECT(m_videoEncoder), "bitrate", m_options.bitrate(), NULL);
  347.  
  348. // If the video stream is going to be divided into parts then it will be output as a raw
  349. // video stream via the writeFileParts() callback, otherwise it is muxed to a container
  350. // format for file output (i.e. MKV or MP4).
  351. GstElement *encoderQueue = NULL;
  352. GstElement *fakeSink = NULL;
  353. GstElement *fileSink = NULL;
  354. GstElement *videoMuxer = NULL;
  355. if (m_options.partSize() > 0)
  356. {
  357. // Create encoder queue.
  358. encoderQueue = gst_element_factory_make("queue", NULL);
  359. if (!encoderQueue)
  360. ORIGINATE_ERROR("Failed to create encoder queue");
  361. if (!gst_bin_add(GST_BIN(m_pipeline), encoderQueue))
  362. {
  363. gst_object_unref(encoderQueue);
  364. ORIGINATE_ERROR("Failed to add encoder queue to pipeline");
  365. }
  366.  
  367. // Create fake sink to connect the file writing thread as the output destination.
  368. fakeSink = gst_element_factory_make("fakesink", NULL);
  369. if (!fakeSink)
  370. ORIGINATE_ERROR("Failed to create fake sink");
  371. if (!gst_bin_add(GST_BIN(m_pipeline), fakeSink))
  372. {
  373. gst_object_unref(fakeSink);
  374. ORIGINATE_ERROR("Failed to add fake sink to pipeline");
  375. }
  376. }
  377. else
  378. {
  379. // Create muxer.
  380. const char* muxer = (m_options.useH264() ? "qtmux" : "matroskamux");
  381. const char* suffix = (m_options.useH264() ? ".mp4" : ".mkv");
  382. videoMuxer = gst_element_factory_make(muxer, NULL);
  383. if (!videoMuxer)
  384. ORIGINATE_ERROR("Failed to create video muxer");
  385. if (!gst_bin_add(GST_BIN(m_pipeline), videoMuxer))
  386. {
  387. gst_object_unref(videoMuxer);
  388. ORIGINATE_ERROR("Failed to add video muxer to pipeline");
  389. }
  390.  
  391. // Create file sink as the final destination for the output file.
  392. fileSink = gst_element_factory_make("filesink", NULL);
  393. if (!fileSink)
  394. ORIGINATE_ERROR("Failed to create file sink");
  395. if (!gst_bin_add(GST_BIN(m_pipeline), fileSink))
  396. {
  397. gst_object_unref(fileSink);
  398. ORIGINATE_ERROR("Failed to add file sink to pipeline");
  399. }
  400. m_currentPartName = m_options.path() + m_options.filename() + suffix;
  401. printf("Writing output to %s\n", m_currentPartName.c_str());
  402. g_object_set(G_OBJECT(fileSink), "location", m_currentPartName.c_str(), NULL);
  403. }
  404.  
  405. // Create caps filter to describe EGLStream image format.
  406. GstCaps *caps = gst_caps_new_simple("video/x-raw",
  407. "format", G_TYPE_STRING, "NV12",
  408. "width", G_TYPE_INT, resolution.width(),
  409. "height", G_TYPE_INT, resolution.height(),
  410. "framerate", GST_TYPE_FRACTION, framerate, 1,
  411. NULL);
  412. if (!caps)
  413. ORIGINATE_ERROR("Failed to create caps");
  414. GstCapsFeatures *features = gst_caps_features_new("memory:NVMM", NULL);
  415. if (!features)
  416. {
  417. gst_caps_unref(caps);
  418. ORIGINATE_ERROR("Failed to create caps feature");
  419. }
  420. gst_caps_set_features(caps, 0, features);
  421.  
  422. // Link EGLStream source to queue via caps filter.
  423. if (!gst_element_link_filtered(videoSource, queue, caps))
  424. {
  425. gst_caps_unref(caps);
  426. ORIGINATE_ERROR("Failed to link EGLStream source to queue");
  427. }
  428. gst_caps_unref(caps);
  429.  
  430. // Link queue to encoder
  431. if (!gst_element_link(queue, m_videoEncoder))
  432. ORIGINATE_ERROR("Failed to link queue to encoder");
  433.  
  434. if (m_options.partSize() > 0)
  435. {
  436. printf("Writing parts with a maximum size of %lu bytes.\n", m_options.partSize());
  437.  
  438. // Link EGLStream encoder queue to h264 encoder queue via new encoder caps filter.
  439. const char* format = m_options.useH264() ? "video/x-h264" : "video/x-h265";
  440. GstCaps *encoderCaps = gst_caps_new_simple(
  441. format, "stream-format", G_TYPE_STRING, "byte-stream", NULL);
  442. if (!gst_element_link_filtered(m_videoEncoder, encoderQueue, encoderCaps))
  443. {
  444. gst_caps_unref(encoderCaps);
  445. ORIGINATE_ERROR("Failed to link m_videoEncoder & encoder caps source to queue");
  446. }
  447. gst_caps_unref(encoderCaps);
  448.  
  449. // Link encoder queue to fake sink.
  450. if (!gst_element_link(encoderQueue, fakeSink))
  451. ORIGINATE_ERROR("Failed to link encoder queue to fake sink");
  452.  
  453. // Set encoder buffer probe to connect the writeFileParts callback.
  454. GstPad *src_pad = gst_element_get_static_pad(m_videoEncoder, "src");
  455. gst_pad_add_probe(src_pad, GST_PAD_PROBE_TYPE_BUFFER,
  456. (GstPadProbeCallback)writeFilePartsStatic, this, NULL);
  457. gst_object_unref(src_pad);
  458. }
  459. else
  460. {
  461. // Link encoder to muxer pad.
  462. if (!gst_element_link_pads(m_videoEncoder, "src", videoMuxer, "video_%u"))
  463. ORIGINATE_ERROR("Failed to link encoder to muxer pad");
  464.  
  465. // Link muxer to sink.
  466. if (!gst_element_link(videoMuxer, fileSink))
  467. ORIGINATE_ERROR("Failed to link muxer to sink");
  468. }
  469.  
  470. return true;
  471. }
  472.  
  473. /**
  474. * Shutdown the GStreamer pipeline.
  475. */
  476. void shutdown()
  477. {
  478. if (m_state == GST_STATE_PLAYING)
  479. stopRecording();
  480.  
  481. if (m_pipeline)
  482. gst_object_unref(GST_OBJECT(m_pipeline));
  483. m_pipeline = NULL;
  484.  
  485. if (m_fileTransferThread)
  486. {
  487. m_fileTransferThread->sendFile(m_currentPartName);
  488. m_fileTransferThread->shutdown();
  489. delete m_fileTransferThread;
  490. m_fileTransferThread = NULL;
  491. }
  492. }
  493.  
  494. /**
  495. * Start recording video.
  496. */
  497. bool startRecording()
  498. {
  499. if (!m_pipeline || !m_videoEncoder)
  500. ORIGINATE_ERROR("Video encoder not initialized");
  501.  
  502. if (m_state != GST_STATE_NULL)
  503. ORIGINATE_ERROR("Video encoder already recording");
  504.  
  505. // Start the pipeline.
  506. if (gst_element_set_state(m_pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
  507. ORIGINATE_ERROR("Failed to start recording.");
  508.  
  509. time(&m_startTime);
  510. m_lastPrintTime = 0;
  511. m_state = GST_STATE_PLAYING;
  512. return true;
  513. }
  514.  
  515. /**
  516. * Stop recording video.
  517. */
  518. bool stopRecording()
  519. {
  520. if (!m_pipeline || !m_videoEncoder)
  521. ORIGINATE_ERROR("Video encoder not initialized");
  522.  
  523. if (m_state != GST_STATE_PLAYING)
  524. ORIGINATE_ERROR("Video encoder not recording");
  525.  
  526. // Send the end-of-stream event.
  527. GstPad *pad = gst_element_get_static_pad(m_videoEncoder, "sink");
  528. if (!pad)
  529. ORIGINATE_ERROR("Failed to get 'sink' pad");
  530. bool result = gst_pad_send_event(pad, gst_event_new_eos());
  531. gst_object_unref(pad);
  532. if (!result)
  533. ORIGINATE_ERROR("Failed to send end of stream event to encoder");
  534.  
  535. // Wait for the event to complete.
  536. GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(m_pipeline));
  537. if (!bus)
  538. ORIGINATE_ERROR("Failed to get bus");
  539. result = gst_bus_poll(bus, GST_MESSAGE_EOS, GST_CLOCK_TIME_NONE);
  540. gst_object_unref(bus);
  541. if (!result)
  542. ORIGINATE_ERROR("Failed to wait for the EOF event");
  543.  
  544. // Stop the pipeline.
  545. if (gst_element_set_state(m_pipeline, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE)
  546. ORIGINATE_ERROR("Failed to stop recording.");
  547.  
  548. m_state = GST_STATE_NULL;
  549. return true;
  550. }
  551.  
  552. static GstPadProbeReturn writeFilePartsStatic(
  553. GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
  554. {
  555. GstVideoEncoder *thiz = static_cast<GstVideoEncoder*>(user_data);
  556. return thiz->writeFileParts(pad, info);
  557. }
  558.  
  559. GstPadProbeReturn writeFileParts(GstPad *pad, GstPadProbeInfo *info)
  560. {
  561. GstMapInfo map;
  562. GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER(info);
  563. buffer = gst_buffer_make_writable(buffer);
  564. gst_buffer_map(buffer, &map, GST_MAP_WRITE);
  565.  
  566. // If the write will exceed the file size maximum, start a new part.
  567. int64_t writeSize = gst_buffer_get_size(buffer);
  568. if (m_currentPartFile)
  569. {
  570. int64_t partBytesWritten = ftell(m_currentPartFile);
  571. if ((uint64_t)(partBytesWritten + writeSize) > m_options.partSize())
  572. {
  573. fclose(m_currentPartFile);
  574. m_currentPartFile = NULL;
  575.  
  576. printf("Finished part %s (%ld bytes)\n",
  577. m_currentPartName.c_str(), partBytesWritten);
  578.  
  579. // If we have a file transfer thread, send the file to the remote destination.
  580. if (m_fileTransferThread)
  581. {
  582. m_fileTransferThread->sendFile(m_currentPartName);
  583. }
  584. }
  585. }
  586.  
  587. // Create new file part if needed.
  588. if (!m_currentPartFile)
  589. {
  590. std::string partString = std::to_string(m_currentPartNumber++);
  591. m_currentPartName = m_options.path() + m_options.filename() +
  592. std::string(6 - partString.size(), '0') + partString +
  593. (m_options.useH264() ? ".h264" : ".h265");
  594. m_currentPartFile = fopen(m_currentPartName.c_str(), "w");
  595. if (!m_currentPartFile)
  596. {
  597. printf("File open failed\n");
  598. return (GstPadProbeReturn)(0);
  599. }
  600. }
  601.  
  602. // Write to file.
  603. if (fwrite(map.data, writeSize, 1, m_currentPartFile) != 1)
  604. {
  605. fclose(m_currentPartFile);
  606. m_currentPartFile = NULL;
  607. printf("Write to file %s failed!\n", m_currentPartName.c_str());
  608. return (GstPadProbeReturn)(0);
  609. }
  610. else
  611. {
  612. m_totalBytesWritten += writeSize;
  613.  
  614. time_t now = time(0);
  615. if (difftime(now, m_lastPrintTime) >= 1)
  616. {
  617. uint64_t runtime = difftime(now, m_startTime);
  618. const uint32_t days = runtime / (60*60*24);
  619. runtime -= days * (60*60*24);
  620. const uint32_t hours = runtime / (60*60);
  621. runtime -= hours * (60*60);
  622. const uint32_t minutes = runtime / 60;
  623. runtime -= minutes * 60;
  624. const uint32_t seconds = runtime;
  625. printf("Recording: %u days, %02u:%02u:%02u, %ld MB written to %s, %lu MB total.\n",
  626. days, hours, minutes, seconds,
  627. (ftell(m_currentPartFile) / 1024 / 1024),
  628. m_currentPartName.c_str(),
  629. (m_totalBytesWritten / 1024 / 1024));
  630. m_lastPrintTime = now;
  631. }
  632. }
  633.  
  634. gst_buffer_unmap(buffer, &map);
  635. GST_PAD_PROBE_INFO_DATA(info) = buffer;
  636.  
  637. return GST_PAD_PROBE_OK;
  638. }
  639.  
  640. protected:
  641. const GstVideoEncodeSampleOptions& m_options;
  642.  
  643. GstState m_state;
  644. GstElement *m_pipeline;
  645. GstElement *m_videoEncoder;
  646.  
  647. FileTransferThread *m_fileTransferThread;
  648. uint32_t m_currentPartNumber;
  649. std::string m_currentPartName;
  650. FILE *m_currentPartFile;
  651. uint64_t m_totalBytesWritten;
  652. time_t m_startTime;
  653. time_t m_lastPrintTime;
  654. };
  655.  
  656. static bool execute(const GstVideoEncodeSampleOptions& options)
  657. {
  658. using namespace Argus;
  659.  
  660. // Initialize the preview window and EGL display.
  661. Window &window = Window::getInstance();
  662. if (options.enablePreview())
  663. {
  664. window.setWindowRect(options.windowRect());
  665. PROPAGATE_ERROR(g_display.initialize(window.getEGLNativeDisplay()));
  666. }
  667. else
  668. {
  669. PROPAGATE_ERROR(g_display.initialize(EGL_DEFAULT_DISPLAY));
  670. }
  671.  
  672. // Create CameraProvider.
  673. UniqueObj<CameraProvider> cameraProvider(CameraProvider::create());
  674. ICameraProvider *iCameraProvider = interface_cast<ICameraProvider>(cameraProvider);
  675. if (!iCameraProvider)
  676. ORIGINATE_ERROR("Failed to open CameraProvider");
  677. printf("Argus Version: %s\n", iCameraProvider->getVersion().c_str());
  678.  
  679. // Get the selected camera device and sensor mode.
  680. CameraDevice* cameraDevice = ArgusHelpers::getCameraDevice(
  681. cameraProvider.get(), options.cameraDeviceIndex());
  682. if (!cameraDevice)
  683. ORIGINATE_ERROR("Selected camera device is not available");
  684. SensorMode* sensorMode = options.useWdr()
  685. ? ArgusHelpers::getWdrSensorMode(cameraDevice)
  686. : ArgusHelpers::getSensorMode(cameraDevice, options.sensorModeIndex());
  687. ISensorMode *iSensorMode = interface_cast<ISensorMode>(sensorMode);
  688. if (!iSensorMode)
  689. ORIGINATE_ERROR("Selected sensor mode not available");
  690.  
  691. // Create CaptureSession.
  692. UniqueObj<CaptureSession> captureSession(iCameraProvider->createCaptureSession(cameraDevice));
  693. ICaptureSession *iSession = interface_cast<ICaptureSession>(captureSession);
  694. if (!iSession)
  695. ORIGINATE_ERROR("Failed to create CaptureSession");
  696.  
  697. // Set common output stream settings.
  698. UniqueObj<OutputStreamSettings> streamSettings(
  699. iSession->createOutputStreamSettings(STREAM_TYPE_EGL));
  700. IEGLOutputStreamSettings *iEGLStreamSettings =
  701. interface_cast<IEGLOutputStreamSettings>(streamSettings);
  702. if (!iEGLStreamSettings)
  703. ORIGINATE_ERROR("Failed to create OutputStreamSettings");
  704. if (options.useP016())
  705. iEGLStreamSettings->setPixelFormat(PIXEL_FMT_P016);
  706. else
  707. iEGLStreamSettings->setPixelFormat(PIXEL_FMT_YCbCr_420_888);
  708. iEGLStreamSettings->setEGLDisplay(g_display.get());
  709.  
  710. // Create video encoder stream using the sensor mode resolution.
  711. iEGLStreamSettings->setResolution(iSensorMode->getResolution());
  712. UniqueObj<OutputStream> videoStream(iSession->createOutputStream(streamSettings.get()));
  713. IEGLOutputStream *iVideoStream = interface_cast<IEGLOutputStream>(videoStream);
  714. if (!iVideoStream)
  715. ORIGINATE_ERROR("Failed to create video stream");
  716.  
  717. // Create preview stream.
  718. UniqueObj<OutputStream> previewStream;
  719. IEGLOutputStream *iPreviewStream = NULL;
  720. if (options.enablePreview())
  721. {
  722. iEGLStreamSettings->setResolution(Size2D<uint32_t>(options.windowRect().width(),
  723. options.windowRect().height()));
  724. previewStream.reset(iSession->createOutputStream(streamSettings.get()));
  725. iPreviewStream = interface_cast<IEGLOutputStream>(previewStream);
  726. if (!iPreviewStream)
  727. ORIGINATE_ERROR("Failed to create preview stream");
  728. }
  729.  
  730. // Create capture Request and enable the streams in the Request.
  731. UniqueObj<Request> request(iSession->createRequest(CAPTURE_INTENT_VIDEO_RECORD));
  732. IRequest *iRequest = interface_cast<IRequest>(request);
  733. if (!iRequest)
  734. ORIGINATE_ERROR("Failed to create Request");
  735. if (iRequest->enableOutputStream(videoStream.get()) != STATUS_OK)
  736. ORIGINATE_ERROR("Failed to enable video stream in Request");
  737. if (options.enablePreview())
  738. {
  739. if (iRequest->enableOutputStream(previewStream.get()) != STATUS_OK)
  740. ORIGINATE_ERROR("Failed to enable preview stream in Request");
  741. }
  742.  
  743. // Determine the framerate (0 indicates max framerate for sensor mode).
  744. uint32_t maxFramerate = (1000000000 / (iSensorMode->getFrameDurationRange().min() - 1));
  745. uint32_t minFramerate = (1000000000 / iSensorMode->getFrameDurationRange().max()) + 1;
  746. uint32_t framerate = (options.framerate() == 0) ? maxFramerate : options.framerate();
  747. if (framerate < minFramerate || framerate > maxFramerate)
  748. {
  749. ORIGINATE_ERROR("Requested framerate (%d) exceeds limits [%d, %d].\n",
  750. framerate, minFramerate, maxFramerate);
  751. }
  752.  
  753. // Set the sensor mode in the request.
  754. ISourceSettings *iSourceSettings = interface_cast<ISourceSettings>(request);
  755. if (!iSourceSettings)
  756. ORIGINATE_ERROR("Failed to get source settings request interface");
  757. iSourceSettings->setSensorMode(sensorMode);
  758. iSourceSettings->setFrameDurationRange(1000000000 / framerate);
  759.  
  760. // Set the denoise settings.
  761. IDenoiseSettings *denoiseSettings = interface_cast<IDenoiseSettings>(request);
  762. if (!denoiseSettings)
  763. ORIGINATE_ERROR("Failed to get DenoiseSettings interface");
  764. switch (options.nrMode())
  765. {
  766. case GstVideoEncodeSampleOptions::NOISE_MODE_ARG_OFF:
  767. denoiseSettings->setDenoiseMode(DENOISE_MODE_OFF);
  768. break;
  769.  
  770. default:
  771. case GstVideoEncodeSampleOptions::NOISE_MODE_ARG_FAST:
  772. denoiseSettings->setDenoiseMode(DENOISE_MODE_FAST);
  773. break;
  774.  
  775. case GstVideoEncodeSampleOptions::NOISE_MODE_ARG_HIGH_QUALITY:
  776. denoiseSettings->setDenoiseMode(DENOISE_MODE_HIGH_QUALITY);
  777. break;
  778. }
  779. denoiseSettings->setDenoiseStrength(1.0f);
  780.  
  781. // Initialize the GStreamer video encoder consumer.
  782. GstVideoEncoder gstVideoEncoder(options);
  783. if (!gstVideoEncoder.initialize(iVideoStream->getEGLStream(),
  784. iSensorMode->getResolution(),
  785. framerate))
  786. {
  787. ORIGINATE_ERROR("Failed to initialize GstVideoEncoder EGLStream consumer");
  788. }
  789. if (!gstVideoEncoder.startRecording())
  790. {
  791. ORIGINATE_ERROR("Failed to start video recording");
  792. }
  793.  
  794. // Initialize the preview consumer.
  795. PreviewConsumerThread previewConsumer(iPreviewStream ? iPreviewStream->getEGLDisplay() : NULL,
  796. iPreviewStream ? iPreviewStream->getEGLStream() : NULL);
  797. if (options.enablePreview())
  798. {
  799. PROPAGATE_ERROR(previewConsumer.initialize());
  800. PROPAGATE_ERROR(previewConsumer.waitRunning());
  801. }
  802.  
  803. // If the capture time is 0, register the signal handler to terminate the infinite captures.
  804. if (options.captureTime() == 0 && signal(SIGINT, ArgusSamples::ctrl_c_sig_handler) == SIG_ERR)
  805. ORIGINATE_ERROR("Failed to register signal handler");
  806.  
  807. // Start repeat capture requests.
  808. if (iSession->repeat(request.get()) != STATUS_OK)
  809. ORIGINATE_ERROR("Failed to start repeat capture requests");
  810.  
  811. // Wait for the requested amount of time while the repeat captures continue.
  812. // If the capture time is 0, we capture infinitely until a shutdown signal is received.
  813. uint32_t sleepTime = std::max(1u, options.captureTime());
  814. do
  815. {
  816. if (options.enablePreview())
  817. {
  818. PROPAGATE_ERROR(Window::getInstance().pollingSleep(sleepTime));
  819. }
  820. else
  821. {
  822. sleep(sleepTime);
  823. }
  824. } while (options.captureTime() == 0 && !shutdownSignalReceived);
  825.  
  826. // Stop the repeating captures.
  827. iSession->stopRepeat();
  828.  
  829. // Wait until all frames have completed before stopping recording.
  830. /// @todo: Not doing this may cause a deadlock.
  831. iSession->waitForIdle();
  832.  
  833. // Stop video recording.
  834. if (!gstVideoEncoder.stopRecording())
  835. ORIGINATE_ERROR("Failed to stop video recording");
  836. gstVideoEncoder.shutdown();
  837. videoStream.reset();
  838.  
  839. // Stop preview.
  840. if (options.enablePreview())
  841. {
  842. previewStream.reset();
  843. PROPAGATE_ERROR(previewConsumer.shutdown());
  844. }
  845.  
  846. // Shut down Argus.
  847. cameraProvider.reset();
  848.  
  849. // Shut down the window (destroys window's EGLSurface).
  850. window.shutdown();
  851.  
  852. // Cleanup the EGL display
  853. PROPAGATE_ERROR(g_display.cleanup());
  854. return true;
  855. }
  856.  
  857. }; // namespace ArgusSamples
  858.  
  859. int main(int argc, char** argv)
  860. {
  861. ArgusSamples::GstVideoEncodeSampleOptions options(basename(argv[0]));
  862. if (!options.parse(argc, argv))
  863. return EXIT_FAILURE;
  864. if (options.requestedExit())
  865. return EXIT_SUCCESS;
  866.  
  867. if (!ArgusSamples::execute(options))
  868. return EXIT_FAILURE;
  869.  
  870. printf("Done.\n");
  871.  
  872. return EXIT_SUCCESS;
  873. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement