MRPT  2.0.4
CFFMPEG_InputStream.cpp
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | https://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2020, Individual contributors, see AUTHORS file |
6  | See: https://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See: https://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 
10 #if defined(__GNUC__) // Needed for ffmpeg headers. Only allowed here when not
11 // using precomp. headers
12 #define __STDC_CONSTANT_MACROS // Needed for having "UINT64_C" and so
13 #endif
14 
15 #include <mrpt/config.h>
16 
17 #include "hwdrivers-precomp.h" // Precompiled headers
18 
19 #if MRPT_HAS_FFMPEG
20 extern "C"
21 {
22 #define _MSC_STDINT_H_ // We already have pstdint.h in MRPT
23 #include <libavcodec/avcodec.h>
24 #include <libavformat/avformat.h>
25 #include <libavutil/imgutils.h>
26 #include <libswscale/swscale.h>
27 }
28 #endif
29 
31 
32 using namespace mrpt;
33 using namespace mrpt::hwdrivers;
34 
35 // JLBC: This file takes portions of code from the example
36 // "avcodec_sample.0.4.9.cpp"
37 //
38 // Minimum ffmpeg libs versions we want to support:
39 // Ubuntu 16.04 LTS: avcodec 56.60.100, avutil 54.31.100, avformat 56.40.101
40 // Ubuntu 20.04 LTS: avcodec 58.54.100, avutil 56.31.100, avformat 58.29.100
41 //
42 #if MRPT_HAS_FFMPEG
43 namespace mrpt::hwdrivers
44 {
45 // All context for ffmpeg:
46 struct TFFMPEGContext
47 {
48  AVFormatContext* pFormatCtx{nullptr};
49  int videoStream{0};
50 #if LIBAVFORMAT_VERSION_MAJOR >= 58
51  AVCodecParameters* pCodecPars{nullptr};
52 #endif
53  AVCodec* pCodec{nullptr};
54  AVCodecContext* pCodecCtx{nullptr};
55  AVFrame* pFrame{nullptr};
56  AVFrame* pFrameRGB{nullptr};
57  SwsContext* img_convert_ctx{nullptr};
58  std::vector<uint8_t> buffer;
59 };
60 } // namespace mrpt::hwdrivers
61 #endif
62 
64 {
65 #if MRPT_HAS_FFMPEG
66  TFFMPEGContext m_state;
67 #endif
68 };
69 
70 /* --------------------------------------------------------
71  Ctor
72  -------------------------------------------------------- */
74 #if MRPT_HAS_FFMPEG
75  : m_impl(mrpt::make_impl<CFFMPEG_InputStream::Impl>())
76 {
77 // av_register_all() not needed in ffmpeg >=4.0
78 #if LIBAVFORMAT_VERSION_MAJOR < 58
79  // Register all formats and codecs
80  av_register_all();
81 #endif
82 }
83 #else
84 {
85  THROW_EXCEPTION("MRPT has been compiled without FFMPEG libraries.");
86 }
87 #endif
88 
89 /* --------------------------------------------------------
90  Dtor
91  -------------------------------------------------------- */
93 {
94 #if MRPT_HAS_FFMPEG
95  // Close everything:
96  this->close();
97 #endif
98 }
99 
100 /* --------------------------------------------------------
101  isOpen
102  -------------------------------------------------------- */
104 {
105 #if MRPT_HAS_FFMPEG
106  const TFFMPEGContext* ctx = &m_impl->m_state;
107  return ctx->pFormatCtx != nullptr;
108 #else
109  return false;
110 #endif
111 }
112 
113 /* --------------------------------------------------------
114  openURL
115  -------------------------------------------------------- */
117  const std::string& url, bool grab_as_grayscale, bool verbose)
118 {
119 #if MRPT_HAS_FFMPEG
120  this->close(); // Close first
121 
122  TFFMPEGContext* ctx = &m_impl->m_state;
123 
124  this->m_url = url;
125  this->m_grab_as_grayscale = grab_as_grayscale;
126 
127  // Open video file
128  if (avformat_open_input(&ctx->pFormatCtx, url.c_str(), nullptr, nullptr) !=
129  0)
130  {
131  ctx->pFormatCtx = nullptr;
132  std::cerr << "[CFFMPEG_InputStream::openURL] Cannot open video: " << url
133  << std::endl;
134  return false;
135  }
136 
137  // Retrieve stream information
138  if (avformat_find_stream_info(ctx->pFormatCtx, nullptr) < 0)
139  {
140  std::cerr << "[CFFMPEG_InputStream::openURL] Couldn't find stream "
141  "information: "
142  << url << std::endl;
143  return false;
144  }
145 
146  // Dump information about file onto standard error
147  if (verbose)
148  {
149  av_dump_format(ctx->pFormatCtx, 0, url.c_str(), false);
150  }
151 
152  // Find the first video stream
153  ctx->videoStream = -1;
154  for (unsigned int i = 0; i < ctx->pFormatCtx->nb_streams; i++)
155  {
156 #if LIBAVFORMAT_VERSION_MAJOR >= 58
157  auto codecType = ctx->pFormatCtx->streams[i]->codecpar->codec_type;
158 #else
159  auto codecType = ctx->pFormatCtx->streams[i]->codec->codec_type;
160 #endif
161  if (codecType == AVMEDIA_TYPE_VIDEO)
162  {
163  ctx->videoStream = (int)i;
164  break;
165  }
166  }
167  if (ctx->videoStream == -1)
168  {
169  std::cerr
170  << "[CFFMPEG_InputStream::openURL] Didn't find a video stream: "
171  << url << std::endl;
172  return false;
173  }
174 
175  // Get a pointer to the codec context for the video stream
176 #if LIBAVFORMAT_VERSION_MAJOR >= 58
177  ctx->pCodecPars = ctx->pFormatCtx->streams[ctx->videoStream]->codecpar;
178  // Find the decoder for the video stream
179  ctx->pCodec = avcodec_find_decoder(ctx->pCodecPars->codec_id);
180 #else
181  ctx->pCodecCtx = ctx->pFormatCtx->streams[ctx->videoStream]->codec;
182  // Find the decoder for the video stream
183  ctx->pCodec = avcodec_find_decoder(ctx->pCodecCtx->codec_id);
184 #endif
185  if (ctx->pCodec == nullptr)
186  {
187  std::cerr << "[CFFMPEG_InputStream::openURL] Codec not found: " << url
188  << std::endl;
189  return false;
190  }
191 
192 #if LIBAVFORMAT_VERSION_MAJOR >= 58
193  ctx->pCodecCtx = avcodec_alloc_context3(nullptr /*ctx->pCodec*/);
194  if (!ctx->pCodecCtx)
195  {
196  std::cerr << "[CFFMPEG_InputStream::openURL] Cannot alloc avcodec "
197  "context for: "
198  << url << std::endl;
199  return false;
200  }
201 
202  // Add stream parameters to context
203  if (avcodec_parameters_to_context(
204  ctx->pCodecCtx,
205  ctx->pFormatCtx->streams[ctx->videoStream]->codecpar))
206  {
207  std::cerr << "[CFFMPEG_InputStream::openURL] Failed "
208  "avcodec_parameters_to_context() for: "
209  << url << std::endl;
210  return false;
211  }
212 
213  // Make sure that Codecs are identical or avcodec_open2 fails.
214  ctx->pCodecCtx->codec_id = ctx->pCodec->id;
215 #endif
216 
217  // Open codec
218  if (avcodec_open2(ctx->pCodecCtx, ctx->pCodec, nullptr) < 0)
219  {
220  std::cerr
221  << "[CFFMPEG_InputStream::openURL] avcodec_open2() failed for: "
222  << url << std::endl;
223  return false;
224  }
225 
226  // Allocate video frame
227  ctx->pFrame = av_frame_alloc();
228  // Allocate an AVFrame structure
229  ctx->pFrameRGB = av_frame_alloc();
230 
231  if (ctx->pFrameRGB == nullptr || ctx->pFrame == nullptr)
232  {
233  std::cerr << "[CFFMPEG_InputStream::openURL] Could not alloc memory "
234  "for frame buffers: "
235  << url << std::endl;
236  return false;
237  }
238 
239  // Determine required buffer size and allocate buffer
240 #if LIBAVFORMAT_VERSION_MAJOR >= 58
241  const auto width = ctx->pCodecPars->width, height = ctx->pCodecPars->height;
242 #else
243  const auto width = ctx->pCodecCtx->width, height = ctx->pCodecCtx->height;
244 #endif
245  int numBytes = av_image_get_buffer_size(
246  m_grab_as_grayscale ? AV_PIX_FMT_GRAY8 : AV_PIX_FMT_BGR24, width,
247  height, 1);
248  if (numBytes < 0)
249  {
250  std::cerr << "[CFFMPEG_InputStream::openURL] av_image_get_buffer_size "
251  "error code: "
252  << numBytes << std::endl;
253  return false;
254  }
255 
256  ctx->buffer.resize(numBytes);
257 
258  // Assign appropriate parts of buffer to image planes in pFrameRGB
259 
260  av_image_fill_arrays(
261  ctx->pFrameRGB->data, ctx->pFrameRGB->linesize, &ctx->buffer[0],
262  m_grab_as_grayscale ? AV_PIX_FMT_GRAY8 : AV_PIX_FMT_BGR24, width,
263  height, 1);
264 
265  return true; // OK.
266 #else
267  return false;
268 #endif
269 }
270 
271 /* --------------------------------------------------------
272  close
273  -------------------------------------------------------- */
275 {
276 #if MRPT_HAS_FFMPEG
277  if (!this->isOpen()) return;
278 
279  TFFMPEGContext* ctx = &m_impl->m_state;
280 
281  // Close the codec
282  if (ctx->pCodecCtx)
283  {
284  avcodec_close(ctx->pCodecCtx);
285  ctx->pCodecCtx = nullptr;
286  }
287 
288  // Close the video file
289  if (ctx->pFormatCtx)
290  {
291  avformat_close_input(&ctx->pFormatCtx);
292  ctx->pFormatCtx = nullptr;
293  }
294 
295  // Free frames memory:
296  ctx->buffer.clear();
297 
298  if (ctx->pFrameRGB)
299  {
300  av_frame_free(&ctx->pFrameRGB);
301  ctx->pFrameRGB = nullptr;
302  }
303  if (ctx->pFrame)
304  {
305  av_frame_free(&ctx->pFrame);
306  ctx->pFrame = nullptr;
307  }
308 
309  if (ctx->img_convert_ctx)
310  {
311  sws_freeContext(ctx->img_convert_ctx);
312  ctx->img_convert_ctx = nullptr;
313  }
314 
315 #endif
316 }
317 
318 /* --------------------------------------------------------
319  retrieveFrame
320  -------------------------------------------------------- */
322 {
323 #if MRPT_HAS_FFMPEG
324  if (!this->isOpen()) return false;
325 
326  TFFMPEGContext* ctx = &m_impl->m_state;
327 
328  AVPacket packet;
329 
330 #if LIBAVFORMAT_VERSION_MAJOR < 58
331  int frameFinished;
332 #endif
333 
334 #if LIBAVFORMAT_VERSION_MAJOR >= 58
335  const auto width = ctx->pCodecPars->width, height = ctx->pCodecPars->height;
336 #else
337  const auto width = ctx->pCodecCtx->width, height = ctx->pCodecCtx->height;
338 #endif
339 
340  while (av_read_frame(ctx->pFormatCtx, &packet) >= 0)
341  {
342  // Is this a packet from the video stream?
343  if (packet.stream_index != ctx->videoStream)
344  {
345  av_packet_unref(&packet);
346  continue;
347  }
348 
349  // Decode video frame
350 #if LIBAVFORMAT_VERSION_MAJOR >= 58
351  int ret = avcodec_send_packet(ctx->pCodecCtx, &packet);
352  if (ret < 0)
353  {
354  std::cerr << "[CFFMPEG_InputStream] avcodec_send_packet error code="
355  << ret << std::endl;
356  return false;
357  }
358  // while (ret >= 0)
359  ret = avcodec_receive_frame(ctx->pCodecCtx, ctx->pFrame);
360  if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
361  return false;
362  else if (ret < 0)
363  {
364  std::cerr << "[CFFMPEG_InputStream] avcodec_receive_frame "
365  "error code="
366  << ret << std::endl;
367  return false;
368  }
369 
370 #else
371  avcodec_decode_video2(
372  ctx->pCodecCtx, ctx->pFrame, &frameFinished, &packet);
373  if (!frameFinished)
374  {
375  // Free the packet that was allocated by av_read_frame
376  av_packet_unref(&packet);
377  continue;
378  }
379 #endif
380  // Convert the image from its native format to RGB:
381  ctx->img_convert_ctx = sws_getCachedContext(
382  ctx->img_convert_ctx, width, height, ctx->pCodecCtx->pix_fmt, width,
383  height,
384  m_grab_as_grayscale ? // BGR vs. RGB for OpenCV
385  AV_PIX_FMT_GRAY8
386  : AV_PIX_FMT_BGR24,
387  SWS_BICUBIC, nullptr, nullptr, nullptr);
388 
389  sws_scale(
390  ctx->img_convert_ctx, ctx->pFrame->data, ctx->pFrame->linesize, 0,
391  height, ctx->pFrameRGB->data, ctx->pFrameRGB->linesize);
392 
393  // std::cout << "[retrieveFrame] Generating image: " <<
394  // ctx->pCodecPars->width << "x" << ctx->pCodecPars->height
395  // << std::endl; std::cout << " linsize: " <<
396  // ctx->pFrameRGB->linesize[0] << std::endl;
397 
398  if (ctx->pFrameRGB->linesize[0] !=
399  ((m_grab_as_grayscale ? 1 : 3) * width))
400  THROW_EXCEPTION("FIXME: linesize!=width case not handled yet.");
401 
402  out_img.loadFromMemoryBuffer(
403  width, height, !m_grab_as_grayscale, ctx->pFrameRGB->data[0]);
404 
405  // Free the packet that was allocated by av_read_frame
406  av_packet_unref(&packet);
407  return true;
408 
409  // Free the packet that was allocated by av_read_frame
410  av_packet_unref(&packet);
411  }
412 
413  return false; // Error reading/ EOF
414 #else
415  return false;
416 #endif
417 }
418 
419 /* --------------------------------------------------------
420  getVideoFPS
421  -------------------------------------------------------- */
423 {
424 #if MRPT_HAS_FFMPEG
425  if (!this->isOpen()) return -1;
426 
427  const TFFMPEGContext* ctx = &m_impl->m_state;
428  if (!ctx) return -1;
429  if (!ctx->pCodecCtx) return -1;
430 
431  return static_cast<double>(ctx->pCodecCtx->time_base.den) /
432  ctx->pCodecCtx->time_base.num;
433 #else
434  return false;
435 #endif
436 }
bool retrieveFrame(mrpt::img::CImage &out_img)
Get the next frame from the video stream.
#define THROW_EXCEPTION(msg)
Definition: exceptions.h:67
Contains classes for various device interfaces.
bool isOpen() const
Return whether the video source was open correctly.
bool openURL(const std::string &url, bool grab_as_grayscale=false, bool verbose=false)
Open a video file or a video stream (rtsp://) This can be used to open local video files (eg...
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
CFFMPEG_InputStream()
Default constructor, does not open any video source at startup.
void close()
Close the video stream (this is called automatically at destruction).
double getVideoFPS() const
Get the frame-per-second (FPS) of the video source, or "-1" if the video is not open.
A class for storing images as grayscale or RGB bitmaps.
Definition: img/CImage.h:148
void loadFromMemoryBuffer(unsigned int width, unsigned int height, bool color, unsigned char *rawpixels, bool swapRedBlue=false)
Reads the image from raw pixels buffer in memory.
Definition: CImage.cpp:364



Page generated by Doxygen 1.8.14 for MRPT 2.0.4 Git: 33de1d0ad Sat Jun 20 11:02:42 2020 +0200 at sáb jun 20 17:35:17 CEST 2020