Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 

231 řádky
5.7 KiB

  1. //
  2. // Lol Engine
  3. //
  4. // Copyright © 2010—2020 Sam Hocevar <sam@hocevar.net>
  5. //
  6. // Lol Engine is free software. It comes without any warranty, to
  7. // the extent permitted by applicable law. You can redistribute it
  8. // and/or modify it under the terms of the Do What the Fuck You Want
  9. // to Public License, Version 2, as published by the WTFPL Task Force.
  10. // See http://www.wtfpl.net/ for more details.
  11. //
  12. #include <lol/engine-internal.h>
  13. #include <lol/image/movie.h>
  14. #if LOL_USE_FFMPEG
  15. extern "C"
  16. {
  17. # include <libavutil/avassert.h>
  18. # include <libavutil/channel_layout.h>
  19. # include <libavutil/mathematics.h>
  20. # include <libavutil/timestamp.h>
  21. # include <libavformat/avformat.h>
  22. # include <libswscale/swscale.h>
  23. # include <libswresample/swresample.h>
  24. }
  25. #endif
  26. namespace lol
  27. {
  28. #if LOL_USE_FFMPEG
  29. #define ERROR_TO_STRING(errnum) (error2string((errnum)).c_str())
  30. static std::string error2string(int errnum)
  31. {
  32. char tmp[AV_ERROR_MAX_STRING_SIZE];
  33. av_strerror(errnum, tmp, AV_ERROR_MAX_STRING_SIZE);
  34. return std::string(tmp);
  35. }
  36. /*static void ffmpeg_logger(void *ptr, int level, const char *fmt, va_list vl)
  37. {
  38. // FIXME: use lol::msg::debug
  39. UNUSED(ptr, level);
  40. vfprintf(stderr, fmt, vl);
  41. }*/
  42. #endif
  43. movie::movie(ivec2 size)
  44. : m_avformat(nullptr),
  45. m_avcodec(nullptr),
  46. m_stream(nullptr),
  47. m_frame(nullptr),
  48. m_size(size),
  49. m_index(0)
  50. {
  51. #if LOL_USE_FFMPEG
  52. m_frame = av_frame_alloc();
  53. ASSERT(m_frame);
  54. m_frame->format = AV_PIX_FMT_RGB8; // 3:3:2 packed for GIF
  55. m_frame->width = m_size.x;
  56. m_frame->height = m_size.y;
  57. int ret = av_frame_get_buffer(m_frame, 32);
  58. ASSERT(ret >= 0);
  59. #endif
  60. }
  61. bool movie::open_file(std::string const &filename)
  62. {
  63. #if LOL_USE_FFMPEG
  64. /* Third argument specifies format */
  65. avformat_alloc_output_context2(&m_avformat, nullptr, "gif", filename.c_str());
  66. if (!m_avformat)
  67. {
  68. msg::debug("could not create output context");
  69. return false;
  70. }
  71. if (!open_codec())
  72. return false;
  73. if (!(m_avformat->oformat->flags & AVFMT_NOFILE))
  74. {
  75. int ret = avio_open(&m_avformat->pb, filename.c_str(), AVIO_FLAG_WRITE);
  76. if (ret < 0)
  77. {
  78. msg::error("could not open '%s': %s\n", filename.c_str(), ERROR_TO_STRING(ret));
  79. return false;
  80. }
  81. }
  82. int ret = avformat_write_header(m_avformat, nullptr);
  83. if (ret < 0)
  84. {
  85. msg::error("could not write header: %s\n", ERROR_TO_STRING(ret));
  86. return false;
  87. }
  88. return true;
  89. #else
  90. UNUSED(filename);
  91. return false;
  92. #endif
  93. }
  94. bool movie::push_image(image &im)
  95. {
  96. #if LOL_USE_FFMPEG
  97. // Make sure the encoder does not hold a reference on our
  98. // frame (GIF does that in order to compress using deltas).
  99. if (av_frame_make_writable(m_frame) < 0)
  100. return false;
  101. // Convert image to 3:3:2. TODO: add some dithering
  102. u8vec3 *data = im.lock<PixelFormat::RGB_8>();
  103. for (int n = 0; n < im.size().x * im.size().y; ++n)
  104. m_frame->data[0][n] = (data[n].r & 0xe0) | ((data[n].g & 0xe0) >> 3) | (data[n].b >> 6);
  105. im.unlock(data);
  106. m_frame->pts = m_index++;
  107. int ret = avcodec_send_frame(m_avcodec, m_frame);
  108. if (ret < 0)
  109. {
  110. msg::error("cannot send video frame: %s\n", ERROR_TO_STRING(ret));
  111. return false;
  112. }
  113. while (ret >= 0)
  114. {
  115. AVPacket pkt;
  116. memset(&pkt, 0, sizeof(pkt));
  117. av_init_packet(&pkt);
  118. ret = avcodec_receive_packet(m_avcodec, &pkt);
  119. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  120. return true;
  121. if (ret < 0)
  122. {
  123. msg::error("cannot retrieve video packet: %s\n", ERROR_TO_STRING(ret));
  124. return false;
  125. }
  126. pkt.stream_index = m_stream->index;
  127. ret = av_interleaved_write_frame(m_avformat, &pkt);
  128. if (ret < 0)
  129. {
  130. msg::error("cannot write video frame: %s\n", ERROR_TO_STRING(ret));
  131. return false;
  132. }
  133. }
  134. #else
  135. UNUSED(im);
  136. #endif
  137. return true;
  138. }
  139. void movie::close()
  140. {
  141. #if LOL_USE_FFMPEG
  142. // this must be done before m_avcodec is freed
  143. av_write_trailer(m_avformat);
  144. avcodec_free_context(&m_avcodec);
  145. av_frame_free(&m_frame);
  146. if (!(m_avformat->oformat->flags & AVFMT_NOFILE))
  147. avio_closep(&m_avformat->pb);
  148. avformat_free_context(m_avformat);
  149. #endif
  150. }
  151. bool movie::open_codec()
  152. {
  153. #if LOL_USE_FFMPEG
  154. AVCodec *codec = avcodec_find_encoder(m_avformat->oformat->video_codec);
  155. if (!codec)
  156. {
  157. msg::error("no encoder found for %s\n", avcodec_get_name(m_avformat->oformat->video_codec));
  158. return false;
  159. }
  160. m_stream = avformat_new_stream(m_avformat, nullptr);
  161. if (!m_stream)
  162. {
  163. msg::error("cannot allocate stream\n");
  164. return false;
  165. }
  166. m_stream->id = 0; // first (and only?) stream
  167. m_stream->time_base = AVRational{ 1, 30 }; // 30 fps
  168. m_avcodec = avcodec_alloc_context3(codec);
  169. if (!m_avcodec)
  170. {
  171. msg::error("cannot allocate encoding context\n");
  172. return false;
  173. }
  174. m_avcodec->codec_id = m_avformat->oformat->video_codec;
  175. m_avcodec->width = m_frame->width;
  176. m_avcodec->height = m_frame->height;
  177. m_avcodec->pix_fmt = AVPixelFormat(m_frame->format);
  178. m_avcodec->time_base = m_stream->time_base;
  179. if (m_avformat->oformat->flags & AVFMT_GLOBALHEADER)
  180. m_avcodec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  181. int ret = avcodec_open2(m_avcodec, codec, nullptr);
  182. if (ret < 0)
  183. {
  184. msg::error("cannot open video codec: %s\n", ERROR_TO_STRING(ret));
  185. return false;
  186. }
  187. ret = avcodec_parameters_from_context(m_stream->codecpar, m_avcodec);
  188. if (ret < 0)
  189. {
  190. msg::error("cannot copy stream parameters\n");
  191. return false;
  192. }
  193. #endif
  194. return true;
  195. }
  196. } // namespace lol