MRPT  2.0.2
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 "hwdrivers-precomp.h" // Precompiled headers
16 
17 #include <mrpt/config.h>
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 #if MRPT_HAS_FFMPEG
38 namespace mrpt::hwdrivers
39 {
40 // All context for ffmpeg:
41 struct TFFMPEGContext
42 {
43  AVFormatContext* pFormatCtx{nullptr};
44  int videoStream{0};
45  AVCodecParameters* pCodecPars{nullptr};
46  AVCodec* pCodec{nullptr};
47  AVCodecContext* pCodecCtx{nullptr};
48  AVFrame* pFrame{nullptr};
49  AVFrame* pFrameRGB{nullptr};
50  SwsContext* img_convert_ctx{nullptr};
51  std::vector<uint8_t> buffer;
52 };
53 } // namespace mrpt::hwdrivers
54 #endif
55 
57 {
58 #if MRPT_HAS_FFMPEG
59  TFFMPEGContext m_state;
60 #endif
61 };
62 
63 /* --------------------------------------------------------
64  Ctor
65  -------------------------------------------------------- */
67 #if MRPT_HAS_FFMPEG
68  : m_impl(mrpt::make_impl<CFFMPEG_InputStream::Impl>())
69 {
70 // av_register_all() not needed in ffmpeg >=4.0
71 #if LIBAVFORMAT_VERSION_MAJOR < 58
72  // Register all formats and codecs
73  av_register_all();
74 #endif
75 }
76 #else
77 {
78  THROW_EXCEPTION("MRPT has been compiled without FFMPEG libraries.");
79 }
80 #endif
81 
82 /* --------------------------------------------------------
83  Dtor
84  -------------------------------------------------------- */
86 {
87 #if MRPT_HAS_FFMPEG
88  // Close everything:
89  this->close();
90 #endif
91 }
92 
93 /* --------------------------------------------------------
94  isOpen
95  -------------------------------------------------------- */
97 {
98 #if MRPT_HAS_FFMPEG
99  const TFFMPEGContext* ctx = &m_impl->m_state;
100  return ctx->pFormatCtx != nullptr;
101 #else
102  return false;
103 #endif
104 }
105 
106 /* --------------------------------------------------------
107  openURL
108  -------------------------------------------------------- */
110  const std::string& url, bool grab_as_grayscale, bool verbose)
111 {
112 #if MRPT_HAS_FFMPEG
113  this->close(); // Close first
114 
115  TFFMPEGContext* ctx = &m_impl->m_state;
116 
117  this->m_url = url;
118  this->m_grab_as_grayscale = grab_as_grayscale;
119 
120  // Open video file
121  if (avformat_open_input(&ctx->pFormatCtx, url.c_str(), nullptr, nullptr) !=
122  0)
123  {
124  ctx->pFormatCtx = nullptr;
125  std::cerr << "[CFFMPEG_InputStream::openURL] Cannot open video: " << url
126  << std::endl;
127  return false;
128  }
129 
130  // Retrieve stream information
131  if (avformat_find_stream_info(ctx->pFormatCtx, nullptr) < 0)
132  {
133  std::cerr << "[CFFMPEG_InputStream::openURL] Couldn't find stream "
134  "information: "
135  << url << std::endl;
136  return false;
137  }
138 
139  // Dump information about file onto standard error
140  if (verbose)
141  {
142  av_dump_format(ctx->pFormatCtx, 0, url.c_str(), false);
143  }
144 
145  // Find the first video stream
146  ctx->videoStream = -1;
147  for (unsigned int i = 0; i < ctx->pFormatCtx->nb_streams; i++)
148  {
149  if (ctx->pFormatCtx->streams[i]->codecpar->codec_type ==
150  AVMEDIA_TYPE_VIDEO)
151  {
152  ctx->videoStream = (int)i;
153  break;
154  }
155  }
156  if (ctx->videoStream == -1)
157  {
158  std::cerr
159  << "[CFFMPEG_InputStream::openURL] Didn't find a video stream: "
160  << url << std::endl;
161  return false;
162  }
163 
164  // Get a pointer to the codec context for the video stream
165  ctx->pCodecPars = ctx->pFormatCtx->streams[ctx->videoStream]->codecpar;
166 
167  // Find the decoder for the video stream
168  ctx->pCodec = avcodec_find_decoder(ctx->pCodecPars->codec_id);
169  if (ctx->pCodec == nullptr)
170  {
171  std::cerr << "[CFFMPEG_InputStream::openURL] Codec not found: " << url
172  << std::endl;
173  return false;
174  }
175 
176  ctx->pCodecCtx = avcodec_alloc_context3(nullptr /*ctx->pCodec*/);
177  if (!ctx->pCodecCtx)
178  {
179  std::cerr << "[CFFMPEG_InputStream::openURL] Cannot alloc avcodec "
180  "context for: "
181  << url << std::endl;
182  return false;
183  }
184 
185  // Add stream parameters to context
186  if (avcodec_parameters_to_context(
187  ctx->pCodecCtx,
188  ctx->pFormatCtx->streams[ctx->videoStream]->codecpar))
189  {
190  std::cerr << "[CFFMPEG_InputStream::openURL] Failed "
191  "avcodec_parameters_to_context() for: "
192  << url << std::endl;
193  return false;
194  }
195 
196  // Make sure that Codecs are identical or avcodec_open2 fails.
197  ctx->pCodecCtx->codec_id = ctx->pCodec->id;
198 
199  // Open codec
200  if (avcodec_open2(ctx->pCodecCtx, ctx->pCodec, nullptr) < 0)
201  {
202  std::cerr
203  << "[CFFMPEG_InputStream::openURL] avcodec_open2() failed for: "
204  << url << std::endl;
205  return false;
206  }
207 
208  // Allocate video frame
209  ctx->pFrame = av_frame_alloc();
210  // Allocate an AVFrame structure
211  ctx->pFrameRGB = av_frame_alloc();
212 
213  if (ctx->pFrameRGB == nullptr || ctx->pFrame == nullptr)
214  {
215  std::cerr << "[CFFMPEG_InputStream::openURL] Could not alloc memory "
216  "for frame buffers: "
217  << url << std::endl;
218  return false;
219  }
220 
221  // Determine required buffer size and allocate buffer
222  int numBytes = av_image_get_buffer_size(
223  m_grab_as_grayscale ? AV_PIX_FMT_GRAY8 : AV_PIX_FMT_BGR24,
224  ctx->pCodecPars->width, ctx->pCodecPars->height, 1);
225  if (numBytes < 0)
226  {
227  std::cerr << "[CFFMPEG_InputStream::openURL] av_image_get_buffer_size "
228  "error code: "
229  << numBytes << std::endl;
230  return false;
231  }
232 
233  ctx->buffer.resize(numBytes);
234 
235  // Assign appropriate parts of buffer to image planes in pFrameRGB
236 
237  av_image_fill_arrays(
238  ctx->pFrameRGB->data, ctx->pFrameRGB->linesize, &ctx->buffer[0],
239  m_grab_as_grayscale ? AV_PIX_FMT_GRAY8 : AV_PIX_FMT_BGR24,
240  ctx->pCodecPars->width, ctx->pCodecPars->height, 1);
241 
242  return true; // OK.
243 #else
244  return false;
245 #endif
246 }
247 
248 /* --------------------------------------------------------
249  close
250  -------------------------------------------------------- */
252 {
253 #if MRPT_HAS_FFMPEG
254  if (!this->isOpen()) return;
255 
256  TFFMPEGContext* ctx = &m_impl->m_state;
257 
258  // Close the codec
259  if (ctx->pCodecCtx)
260  {
261  avcodec_close(ctx->pCodecCtx);
262  ctx->pCodecCtx = nullptr;
263  }
264 
265  // Close the video file
266  if (ctx->pFormatCtx)
267  {
268  avformat_close_input(&ctx->pFormatCtx);
269  ctx->pFormatCtx = nullptr;
270  }
271 
272  // Free frames memory:
273  ctx->buffer.clear();
274 
275  if (ctx->pFrameRGB)
276  {
277  av_frame_free(&ctx->pFrameRGB);
278  ctx->pFrameRGB = nullptr;
279  }
280  if (ctx->pFrame)
281  {
282  av_frame_free(&ctx->pFrame);
283  ctx->pFrame = nullptr;
284  }
285 
286  if (ctx->img_convert_ctx)
287  {
288  sws_freeContext(ctx->img_convert_ctx);
289  ctx->img_convert_ctx = nullptr;
290  }
291 
292 #endif
293 }
294 
295 /* --------------------------------------------------------
296  retrieveFrame
297  -------------------------------------------------------- */
299 {
300 #if MRPT_HAS_FFMPEG
301  if (!this->isOpen()) return false;
302 
303  TFFMPEGContext* ctx = &m_impl->m_state;
304 
305  AVPacket packet;
306 
307  while (av_read_frame(ctx->pFormatCtx, &packet) >= 0)
308  {
309  // Is this a packet from the video stream?
310  if (packet.stream_index == ctx->videoStream)
311  {
312  // Decode video frame
313  int ret = avcodec_send_packet(ctx->pCodecCtx, &packet);
314  if (ret < 0)
315  {
316  std::cerr
317  << "[CFFMPEG_InputStream] avcodec_send_packet error code="
318  << ret << std::endl;
319  return false;
320  }
321 
322  // while (ret >= 0)
323  ret = avcodec_receive_frame(ctx->pCodecCtx, ctx->pFrame);
324  if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
325  return false;
326  else if (ret < 0)
327  {
328  std::cerr << "[CFFMPEG_InputStream] avcodec_receive_frame "
329  "error code="
330  << ret << std::endl;
331  return false;
332  }
333 
334  // Convert the image from its native format to RGB:
335  ctx->img_convert_ctx = sws_getCachedContext(
336  ctx->img_convert_ctx, ctx->pCodecPars->width,
337  ctx->pCodecPars->height, ctx->pCodecCtx->pix_fmt,
338  ctx->pCodecPars->width, ctx->pCodecPars->height,
339  m_grab_as_grayscale ? // BGR vs. RGB for OpenCV
340  AV_PIX_FMT_GRAY8
341  : AV_PIX_FMT_BGR24,
342  SWS_BICUBIC, nullptr, nullptr, nullptr);
343 
344  sws_scale(
345  ctx->img_convert_ctx, ctx->pFrame->data, ctx->pFrame->linesize,
346  0, ctx->pCodecPars->height, ctx->pFrameRGB->data,
347  ctx->pFrameRGB->linesize);
348 
349  // std::cout << "[retrieveFrame] Generating image: " <<
350  // ctx->pCodecPars->width << "x" << ctx->pCodecPars->height
351  // << std::endl; std::cout << " linsize: " <<
352  // ctx->pFrameRGB->linesize[0] << std::endl;
353 
354  if (ctx->pFrameRGB->linesize[0] !=
355  ((m_grab_as_grayscale ? 1 : 3) * ctx->pCodecPars->width))
356  THROW_EXCEPTION("FIXME: linesize!=width case not handled yet.");
357 
358  out_img.loadFromMemoryBuffer(
359  ctx->pCodecPars->width, ctx->pCodecPars->height,
360  !m_grab_as_grayscale, // Color
361  ctx->pFrameRGB->data[0]);
362 
363  // Free the packet that was allocated by av_read_frame
364  av_packet_unref(&packet);
365  return true;
366  }
367 
368  // Free the packet that was allocated by av_read_frame
369  av_packet_unref(&packet);
370  }
371 
372  return false; // Error reading/ EOF
373 #else
374  return false;
375 #endif
376 }
377 
378 /* --------------------------------------------------------
379  getVideoFPS
380  -------------------------------------------------------- */
382 {
383 #if MRPT_HAS_FFMPEG
384  if (!this->isOpen()) return -1;
385 
386  const TFFMPEGContext* ctx = &m_impl->m_state;
387  if (!ctx) return -1;
388  if (!ctx->pCodecCtx) return -1;
389 
390  return static_cast<double>(ctx->pCodecCtx->time_base.den) /
391  ctx->pCodecCtx->time_base.num;
392 #else
393  return false;
394 #endif
395 }
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:365



Page generated by Doxygen 1.8.14 for MRPT 2.0.2 Git: 9b4fd2465 Mon May 4 16:59:08 2020 +0200 at lun may 4 17:26:07 CEST 2020