ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
QVideoEncoder.cpp
Go to the documentation of this file.
1 #include "QVideoEncoder.h"
2 
3 #ifdef _MSC_VER
4 #pragma warning(push)
5 #pragma warning(disable : 4996)
6 #endif
7 
8 // FFmpeg
9 extern "C" {
10 #include "libavcodec/avcodec.h"
11 #include "libavformat/avformat.h"
12 #include "libavformat/avio.h"
13 #include "libavutil/avstring.h"
14 #include "libavutil/error.h"
15 #include "libavutil/frame.h"
16 #include "libavutil/imgutils.h"
17 #include "libavutil/mathematics.h"
18 #include "libavutil/opt.h"
19 #include "libavutil/rational.h"
20 #include "libswscale/swscale.h"
21 }
22 
23 #if LIBAVCODEC_VERSION_MAJOR < 59
24 #define cloudViewer_ff_const59
25 #else
26 #define cloudViewer_ff_const59 const
27 #endif
28 
29 // system
30 #include <assert.h>
31 
33  AVFormatContext *formatContext;
34  AVCodecContext *codecContext;
35  AVStream *videoStream;
36  AVFrame *frame;
37  SwsContext *swsContext;
38 
40  : formatContext(0),
41  codecContext(0),
42  videoStream(0),
43  frame(0),
44  swsContext(0) {}
45 };
46 
48  int width,
49  int height,
50  unsigned bitrate,
51  int gop,
52  int fps)
53  : m_filename(filename),
54  m_width(width),
55  m_height(height),
56  m_bitrate(bitrate),
57  m_gop(gop),
58  m_fps(fps),
59  m_isOpen(false),
60  m_ff(new FFmpegStuffEnc) {}
61 
63  close();
64 
65  if (m_ff) {
66  delete m_ff;
67  m_ff = 0;
68  }
69 }
70 
72  return (m_width % 8) == 0 && (m_height % 8) == 0;
73 }
74 
76  assert(!m_ff->frame);
77 
78  m_ff->frame = av_frame_alloc();
79  if (!m_ff->frame) {
80  return false;
81  }
82 
83  m_ff->frame->format = m_ff->codecContext->pix_fmt;
84  m_ff->frame->width = m_ff->codecContext->width;
85  m_ff->frame->height = m_ff->codecContext->height;
86  m_ff->frame->pts = 0;
87 
88  /* allocate the buffers for the frame data */
89  int ret = av_frame_get_buffer(m_ff->frame, 32);
90  if (ret < 0) {
91  fprintf(stderr, "Could not allocate frame data.\n");
92  return false;
93  }
94 
95  return true;
96 }
97 
99  if (m_ff->frame) {
100  av_frame_free(&m_ff->frame);
101  m_ff->frame = 0;
102  }
103 }
104 
105 #if LIBAVCODEC_VERSION_MAJOR < 59
107  std::vector<OutputFormat> &formats,
108  bool ignoreIfNoFileExtension /*=true*/) {
109  try {
110  // register all out formats for avoiding missing out format.
111  av_register_all();
112 
113  // list of all output formats
114  cloudViewer_ff_const59 AVOutputFormat *prev = nullptr;
115  while (true) {
116  AVOutputFormat *format = av_oformat_next(prev);
117  if (format) {
118  // potentially skip the output formats without any extension (=
119  // test formats mostly)
120  if (format->video_codec != AV_CODEC_ID_NONE &&
121  (!ignoreIfNoFileExtension ||
122  (format->extensions && format->extensions[0] != 0))) {
123  OutputFormat f;
124  {
125  f.shortName = format->name;
126  f.longName = format->long_name;
127  f.extensions = format->extensions;
128  }
129  formats.push_back(f);
130  }
131  prev = format;
132  } else {
133  break;
134  }
135  }
136  } catch (const std::bad_alloc &) {
137  // not enough memory
138  return false;
139  }
140 
141  return true;
142 }
143 #else
145  std::vector<OutputFormat> &formats,
146  bool ignoreIfNoFileExtension /*=true*/) {
147  try {
148  // list of all output formats
149  void *ofmt_opaque = nullptr;
150  while (true) {
151  cloudViewer_ff_const59 AVOutputFormat *format =
152  av_muxer_iterate(&ofmt_opaque);
153  if (format) {
154  // potentially skip the output formats without any extension (=
155  // test formats mostly)
156  if (format->video_codec != AV_CODEC_ID_NONE &&
157  (!ignoreIfNoFileExtension ||
158  (format->extensions && format->extensions[0] != 0))) {
159  OutputFormat f;
160  {
161  f.shortName = format->name;
162  f.longName = format->long_name;
163  f.extensions = format->extensions;
164  }
165  formats.push_back(f);
166  }
167  } else {
168  break;
169  }
170  }
171  } catch (const std::bad_alloc &) {
172  // not enough memory
173  return false;
174  }
175 
176  return true;
177 }
178 #endif
179 
180 bool QVideoEncoder::open(QString formatShortName, QStringList &errors) {
181  if (m_isOpen) {
182  // the stream is already opened!
183  return false;
184  }
185 
186  if (!isSizeValid()) {
187  errors << "Invalid video size";
188  return false;
189  }
190 
191  cloudViewer_ff_const59 AVOutputFormat *outputFormat = nullptr;
192  if (!formatShortName.isEmpty()) {
193  outputFormat =
194  av_guess_format(qPrintable(formatShortName), nullptr, nullptr);
195  if (!outputFormat) {
196  errors << "Could not find output format from short name: " +
197  formatShortName;
198  }
199  }
200 
201  // find the output format
202  avformat_alloc_output_context2(
203  &m_ff->formatContext, outputFormat, nullptr,
204  outputFormat ? qPrintable(m_filename) : nullptr);
205  if (!m_ff->formatContext) {
206  if (!outputFormat) {
207  errors << "Could not deduce output format from file extension: "
208  "using MPEG";
209 
210  avformat_alloc_output_context2(&m_ff->formatContext, nullptr,
211  "mpeg", qPrintable(m_filename));
212  if (!m_ff->formatContext) {
213  errors << "Codec not found";
214  return false;
215  }
216  } else {
217  errors << "Failed to initialize the output context with the "
218  "specified output format: " +
219  formatShortName;
220  return false;
221  }
222  }
223 
224  // get the codec
225  AVCodecID codec_id = m_ff->formatContext->oformat->video_codec;
226  // codec_id = AV_CODEC_ID_MPEG1VIDEO;
227  // codec_id = AV_CODEC_ID_H264;
228  cloudViewer_ff_const59 AVCodec *pCodec = avcodec_find_encoder(codec_id);
229  if (!pCodec) {
230  errors << "Could not load the codec";
231  return false;
232  }
233  m_ff->codecContext = avcodec_alloc_context3(pCodec);
234 
235  /* put sample parameters */
236  m_ff->codecContext->bit_rate = m_bitrate;
237  /* resolution must be a multiple of two */
238  m_ff->codecContext->width = m_width;
239  m_ff->codecContext->height = m_height;
240  /* frames per second */
241  m_ff->codecContext->time_base.num = 1;
242  m_ff->codecContext->time_base.den = m_fps;
243  m_ff->codecContext->gop_size = m_gop;
244  m_ff->codecContext->max_b_frames = 1;
245  m_ff->codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
246 
247  // DGM: doesn't really change anything. We only get different warnings if we
248  // enabled this :( m_ff->codecContext->rc_buffer_size = m_bitrate * 2;
249  // m_ff->codecContext->rc_max_rate = m_bitrate;
250 
251  if (codec_id == AV_CODEC_ID_H264) {
252  av_opt_set(m_ff->codecContext->priv_data, "preset", "slow", 0);
253  } else if (codec_id == AV_CODEC_ID_MPEG1VIDEO) {
254  /* Needed to avoid using macroblocks in which some coeffs overflow.
255  * This does not happen with normal video, it just happens here as
256  * the motion of the chroma plane does not match the luma plane. */
257  m_ff->codecContext->mb_decision = 2;
258  }
259 
260  // some formats want stream headers to be separate
261  if (m_ff->formatContext->oformat->flags & AVFMT_GLOBALHEADER) {
262  m_ff->codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
263  }
264 
265  // Add the video stream
266  m_ff->videoStream = avformat_new_stream(m_ff->formatContext, pCodec);
267  if (!m_ff->videoStream) {
268  errors << "Failed to allocate the output stream";
269  return false;
270  }
271  m_ff->videoStream->id = m_ff->formatContext->nb_streams - 1;
272  avcodec_parameters_from_context(m_ff->videoStream->codecpar,
273  m_ff->codecContext);
274  m_ff->videoStream->time_base.num = 1;
275  m_ff->videoStream->time_base.den = m_fps;
276 
277  // av_dump_format(m_ff->formatContext, 0, fileName.toStdString().c_str(),
278  // 1);
279 
280  // open the codec
281  if (avcodec_open2(m_ff->codecContext, pCodec, 0) < 0) {
282  errors << "Could not open the codec";
283  return false;
284  }
285 
286  // Allocate the YUV frame
287  if (!initFrame()) {
288  errors << "Could not init the internal frame";
289  return false;
290  }
291 
292  if (avio_open(&m_ff->formatContext->pb, qPrintable(m_filename),
293  AVIO_FLAG_WRITE) < 0) {
294  errors << QString("Could not open '%1'").arg(m_filename);
295  return false;
296  }
297 
298  int err = avformat_write_header(m_ff->formatContext, nullptr);
299 
300  if (err != 0) {
301  errors << QString("Could not write header for '%1'").arg(m_filename);
302  return false;
303  }
304 
305  m_isOpen = true;
306 
307  return true;
308 }
309 
310 static int write_frame(FFmpegStuffEnc *ff, AVPacket *pkt) {
311  if (!ff) {
312  assert(ff);
313  return 0;
314  }
315 
316  // if (ff->codecContext->coded_frame->key_frame)
317  //{
318  // pkt->flags |= AV_PKT_FLAG_KEY;
319  // }
320 
321  // rescale output packet timestamp values from codec to stream timebase
322  av_packet_rescale_ts(pkt, ff->codecContext->time_base,
323  ff->videoStream->time_base);
324  pkt->stream_index = ff->videoStream->index;
325 
326  // write the compressed frame to the media file
327  return av_interleaved_write_frame(ff->formatContext, pkt);
328 }
329 
331  if (!m_isOpen) {
332  return false;
333  }
334 
335  int ret = avcodec_send_frame(m_ff->codecContext, 0);
336 
337  // delayed frames?
338  while (ret >= 0) {
339 #if LIBAVCODEC_VERSION_MAJOR >= 58
340  AVPacket *pkt = av_packet_alloc();
341  if (!pkt) {
342  break;
343  }
344  ret = avcodec_receive_packet(m_ff->codecContext, pkt);
345  if (ret < 0) {
346  av_packet_free(&pkt);
347  break;
348  }
349  write_frame(m_ff, pkt);
350  av_packet_free(&pkt);
351 #else
352  AVPacket pkt;
353  memset(&pkt, 0, sizeof(AVPacket));
354  av_init_packet(&pkt);
355  ret = avcodec_receive_packet(m_ff->codecContext, &pkt);
356  if (ret < 0) {
357  break;
358  }
359  write_frame(m_ff, &pkt);
360  av_packet_unref(&pkt);
361 #endif
362  }
363 
364  av_write_trailer(m_ff->formatContext);
365 
366  // close the codec
367 #if LIBAVCODEC_VERSION_MAJOR >= 59
368  avcodec_free_context(&m_ff->codecContext);
369 #else
370  avcodec_close(m_ff->codecContext);
371 #endif
372 
373  // free the streams and other data
374  freeFrame();
375  for (unsigned i = 0; i < m_ff->formatContext->nb_streams; i++) {
376  av_freep(&m_ff->formatContext->streams[i]);
377  }
378 
379  // close the file
380  avio_close(m_ff->formatContext->pb);
381 
382  // free the stream
383 #if LIBAVFORMAT_VERSION_MAJOR >= 59
384  avformat_free_context(m_ff->formatContext);
385 #else
386  av_free(m_ff->formatContext);
387 #endif
388 
389  m_isOpen = false;
390 
391  return true;
392 }
393 
395  int frameIndex,
396  QString *errorString /*=nullptr*/) {
397  if (!isOpen()) {
398  if (errorString) *errorString = "Stream is not opened";
399  return false;
400  }
401 
402  // SWS conversion
403  if (!convertImage_sws(image, errorString)) {
404  return false;
405  }
406 
407  // encode the image
408  {
409  // compute correct timestamp based on the input frame index
410  // int timestamp = ((m_ff->codecContext->time_base.num * 90000) /
411  // m_ff->codecContext->time_base.den) * frameIndex;
412  m_ff->frame->pts = frameIndex /*timestamp*/;
413 
414  int ret = avcodec_send_frame(m_ff->codecContext, m_ff->frame);
415  if (ret < 0) {
416  char errorStr[AV_ERROR_MAX_STRING_SIZE] = {0};
417  av_make_error_string(errorStr, AV_ERROR_MAX_STRING_SIZE, ret);
418  if (errorString)
419  *errorString =
420  QString("Error encoding video frame: %1").arg(errorStr);
421  return false;
422  }
423 
424  while (ret >= 0) {
425 #if LIBAVCODEC_VERSION_MAJOR >= 58
426  AVPacket *pkt = av_packet_alloc();
427  if (!pkt) {
428  if (errorString)
429  *errorString = "Failed to allocate packet";
430  return false;
431  }
432  ret = avcodec_receive_packet(m_ff->codecContext, pkt);
433  if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
434  av_packet_free(&pkt);
435  break;
436  } else if (ret < 0) {
437  char errorStr[AV_ERROR_MAX_STRING_SIZE] = {0};
438  av_make_error_string(errorStr, AV_ERROR_MAX_STRING_SIZE, ret);
439  if (errorString)
440  *errorString = QString("Error receiving video frame: %1")
441  .arg(errorStr);
442  av_packet_free(&pkt);
443  return false;
444  }
445 
446  int wRet = write_frame(m_ff, pkt);
447  if (wRet < 0) {
448  char errorStr[AV_ERROR_MAX_STRING_SIZE] = {0};
449  av_make_error_string(errorStr, AV_ERROR_MAX_STRING_SIZE, wRet);
450  if (errorString)
451  *errorString =
452  QString("Error while writing video frame: %1")
453  .arg(errorStr);
454  av_packet_free(&pkt);
455  return false;
456  }
457  av_packet_free(&pkt);
458 #else
459  AVPacket pkt;
460  memset(&pkt, 0, sizeof(AVPacket));
461  av_init_packet(&pkt);
462  ret = avcodec_receive_packet(m_ff->codecContext, &pkt);
463  if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
464  break;
465  } else if (ret < 0) {
466  char errorStr[AV_ERROR_MAX_STRING_SIZE] = {0};
467  av_make_error_string(errorStr, AV_ERROR_MAX_STRING_SIZE, ret);
468  if (errorString)
469  *errorString = QString("Error receiving video frame: %1")
470  .arg(errorStr);
471  return false;
472  }
473 
474  int wRet = write_frame(m_ff, &pkt);
475  if (wRet < 0) {
476  char errorStr[AV_ERROR_MAX_STRING_SIZE] = {0};
477  av_make_error_string(errorStr, AV_ERROR_MAX_STRING_SIZE, wRet);
478  if (errorString)
479  *errorString =
480  QString("Error while writing video frame: %1")
481  .arg(errorStr);
482  return false;
483  }
484  av_packet_unref(&pkt);
485 #endif
486  }
487  }
488 
489  return true;
490 }
491 
493  QString *errorString /*=nullptr*/) {
494  // Check if the image matches the size
495  if (image.width() != m_width || image.height() != m_height) {
496  if (errorString) *errorString = "Wrong image size";
497  return false;
498  }
499 
500  QImage::Format format = image.format();
501  if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 &&
502  format != QImage::Format_ARGB32_Premultiplied) {
503  if (errorString) *errorString = "Wrong image format";
504  return false;
505  }
506 
507  // Check if context can be reused, otherwise reallocate a new one
508  m_ff->swsContext = sws_getCachedContext(m_ff->swsContext, m_width, m_height,
509  AV_PIX_FMT_BGRA, m_width, m_height,
510  AV_PIX_FMT_YUV420P, SWS_BICUBIC,
511  nullptr, nullptr, nullptr);
512 
513  if (m_ff->swsContext == nullptr) {
514  if (errorString)
515  *errorString = "[SWS] Cannot initialize the conversion context";
516  return false;
517  }
518 
519  int num_bytes =
520  av_image_get_buffer_size(AV_PIX_FMT_BGRA, m_width, m_height, 1);
521 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
522  if (num_bytes != static_cast<int>(image.sizeInBytes())) {
523 #else
524  if (num_bytes != image.byteCount()) {
525 #endif
526  if (errorString) *errorString = "[SWS] Number of bytes mismatch";
527  return false;
528  }
529 
530  const uint8_t *srcSlice[3] = {
531  static_cast<const uint8_t *>(image.constBits()), 0, 0};
532 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
533  int srcStride[3] = {static_cast<int>(image.bytesPerLine()), 0, 0};
534 #else
535  int srcStride[3] = {image.bytesPerLine(), 0, 0};
536 #endif
537 
538  sws_scale(m_ff->swsContext, srcSlice, srcStride, 0, m_height,
539  m_ff->frame->data, m_ff->frame->linesize);
540 
541  return true;
542 }
543 
544 #ifdef _MSC_VER
545 #pragma warning(pop)
546 #endif
std::string filename
std::shared_ptr< core::Tensor > image
filament::Texture::InternalFormat format
int width
int height
#define cloudViewer_ff_const59
static int write_frame(FFmpegStuffEnc *ff, AVPacket *pkt)
virtual bool encodeImage(const QImage &image, int frameIndex, QString *errorString=nullptr)
Adds an image to the stream.
bool isSizeValid()
Returns whether the image size is valid.
virtual bool close()
Closes the file.
bool convertImage_sws(const QImage &image, QString *errorString=nullptr)
Convert the QImage to the internal YUV format.
bool open(QString formatShortName, QStringList &errors)
Creates an (empty) video/anmiation file.
virtual ~QVideoEncoder()
bool isOpen() const
Returns whether the file is opened or not.
Definition: QVideoEncoder.h:43
static bool GetSupportedOutputFormats(std::vector< OutputFormat > &formats, bool ignoreIfNoFileExtension=true)
Returns the list of supported output formats.
FFmpegStuffEnc * m_ff
FFmpeg variables.
Definition: QVideoEncoder.h:75
QString m_filename
Definition: QVideoEncoder.h:66
QVideoEncoder(QString filename, int width, int height, unsigned bitrate, int gop=12, int fps=25)
Default constructor.
unsigned m_bitrate
Definition: QVideoEncoder.h:69
AVFormatContext * formatContext
AVCodecContext * codecContext
AVStream * videoStream
SwsContext * swsContext