選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

256 行
6.6 KiB

  1. //
  2. // Lol Engine — GIF encoding sample
  3. //
  4. // Copyright © 2016 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. #if HAVE_CONFIG_H
  13. # include "config.h"
  14. #endif
  15. #include <lol/engine.h>
  16. #include "loldebug.h"
  17. extern "C" {
  18. #include <libavutil/avassert.h>
  19. #include <libavutil/channel_layout.h>
  20. #include <libavutil/mathematics.h>
  21. #include <libavutil/timestamp.h>
  22. #include <libavformat/avformat.h>
  23. #include <libswscale/swscale.h>
  24. #include <libswresample/swresample.h>
  25. }
  26. using namespace lol;
  27. class gif_encoder
  28. {
  29. public:
  30. gif_encoder(ivec2 size)
  31. : m_avformat(nullptr),
  32. m_avcodec(nullptr),
  33. m_stream(nullptr),
  34. m_frame(nullptr),
  35. m_size(size),
  36. m_index(0)
  37. {
  38. av_register_all();
  39. av_log_set_callback(ffmpeg_logger);
  40. m_frame = av_frame_alloc();
  41. ASSERT(m_frame);
  42. m_frame->format = AV_PIX_FMT_RGB8; // 3:3:2 packed for GIF
  43. m_frame->width = m_size.x;
  44. m_frame->height = m_size.y;
  45. int ret = av_frame_get_buffer(m_frame, 32);
  46. ASSERT(ret >= 0);
  47. }
  48. bool open_file(char const *filename)
  49. {
  50. /* Third argument specifies format */
  51. avformat_alloc_output_context2(&m_avformat, nullptr, "gif", filename);
  52. if (!m_avformat)
  53. {
  54. msg::debug("could not create output context");
  55. return false;
  56. }
  57. if (!open_codec())
  58. return false;
  59. if (!(m_avformat->oformat->flags & AVFMT_NOFILE))
  60. {
  61. int ret = avio_open(&m_avformat->pb, filename, AVIO_FLAG_WRITE);
  62. if (ret < 0)
  63. {
  64. msg::error("could not open '%s': %s\n", filename, error2string(ret).C());
  65. return false;
  66. }
  67. }
  68. int ret = avformat_write_header(m_avformat, nullptr);
  69. if (ret < 0)
  70. {
  71. msg::error("could not write header: %s\n", error2string(ret).C());
  72. return false;
  73. }
  74. return true;
  75. }
  76. bool push_image(image &im)
  77. {
  78. // Make sure the encoder does not hold a reference on our
  79. // frame (GIF does that in order to compress using deltas).
  80. if (av_frame_make_writable(m_frame) < 0)
  81. return false;
  82. // Convert image to 3:3:2. TODO: add some dithering
  83. u8vec3 *data = im.lock<PixelFormat::RGB_8>();
  84. for (int n = 0; n < im.size().x * im.size().y; ++n)
  85. m_frame->data[0][n] = (data[n].r & 0xe0) | ((data[n].g & 0xe0) >> 3) | (data[n].b >> 6);
  86. im.unlock(data);
  87. m_frame->pts = m_index++;
  88. AVPacket pkt;
  89. memset(&pkt, 0, sizeof(pkt));
  90. av_init_packet(&pkt);
  91. // XXX: is got_packet necessary?
  92. int got_packet = 0;
  93. int ret = avcodec_encode_video2(m_avcodec, &pkt, m_frame, &got_packet);
  94. if (ret < 0)
  95. {
  96. msg::error("cannot encode video frame: %s\n", error2string(ret).C());
  97. return false;
  98. }
  99. if (got_packet)
  100. {
  101. pkt.stream_index = m_stream->index;
  102. ret = av_interleaved_write_frame(m_avformat, &pkt);
  103. if (ret < 0)
  104. {
  105. msg::error("cannot write video frame: %s\n", error2string(ret).C());
  106. return false;
  107. }
  108. }
  109. return true;
  110. }
  111. void close()
  112. {
  113. // this must be done before m_avcodec is freed
  114. av_write_trailer(m_avformat);
  115. avcodec_free_context(&m_avcodec);
  116. av_frame_free(&m_frame);
  117. if (!(m_avformat->oformat->flags & AVFMT_NOFILE))
  118. avio_closep(&m_avformat->pb);
  119. avformat_free_context(m_avformat);
  120. }
  121. private:
  122. bool open_codec()
  123. {
  124. AVCodec *codec = avcodec_find_encoder(m_avformat->oformat->video_codec);
  125. if (!codec)
  126. {
  127. msg::error("no encoder found for %s\n", avcodec_get_name(m_avformat->oformat->video_codec));
  128. return false;
  129. }
  130. m_stream = avformat_new_stream(m_avformat, nullptr);
  131. if (!m_stream)
  132. {
  133. msg::error("cannot allocate stream\n");
  134. return false;
  135. }
  136. m_stream->id = 0; // first (and only?) stream
  137. m_stream->time_base = AVRational{ 1, 30 }; // 30 fps
  138. m_avcodec = avcodec_alloc_context3(codec);
  139. if (!m_avcodec)
  140. {
  141. msg::error("cannot allocate encoding context\n");
  142. return false;
  143. }
  144. m_avcodec->codec_id = m_avformat->oformat->video_codec;
  145. m_avcodec->width = m_frame->width;
  146. m_avcodec->height = m_frame->height;
  147. m_avcodec->pix_fmt = AVPixelFormat(m_frame->format);
  148. m_avcodec->time_base = m_stream->time_base;
  149. if (m_avformat->oformat->flags & AVFMT_GLOBALHEADER)
  150. m_avcodec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  151. int ret = avcodec_open2(m_avcodec, codec, nullptr);
  152. if (ret < 0)
  153. {
  154. msg::error("cannot open video codec: %s\n", error2string(ret).C());
  155. return false;
  156. }
  157. ret = avcodec_parameters_from_context(m_stream->codecpar, m_avcodec);
  158. if (ret < 0)
  159. {
  160. msg::error("cannot copy stream parameters\n");
  161. return false;
  162. }
  163. return true;
  164. }
  165. static String error2string(int errnum)
  166. {
  167. char tmp[AV_ERROR_MAX_STRING_SIZE];
  168. av_strerror(errnum, tmp, AV_ERROR_MAX_STRING_SIZE);
  169. return String(tmp);
  170. }
  171. static void ffmpeg_logger(void *ptr, int level, const char *fmt, va_list vl)
  172. {
  173. // FIXME: use lol::msg::debug
  174. UNUSED(ptr, level);
  175. vfprintf(stderr, fmt, vl);
  176. }
  177. private:
  178. AVFormatContext *m_avformat;
  179. AVCodecContext *m_avcodec;
  180. AVStream *m_stream;
  181. AVFrame *m_frame;
  182. ivec2 m_size;
  183. int m_index;
  184. };
  185. int main(int argc, char **argv)
  186. {
  187. UNUSED(argc, argv);
  188. ivec2 size(256, 256);
  189. gif_encoder enc(size);
  190. if (!enc.open_file("16_movie.gif"))
  191. return EXIT_FAILURE;
  192. for (int i = 0; i < 256; ++i)
  193. {
  194. image im(size);
  195. array2d<u8vec3> &data = im.lock2d<PixelFormat::RGB_8>();
  196. for (int y = 0; y < size.y; ++y)
  197. for (int x = 0; x < size.x; ++x)
  198. {
  199. data[x][y].r = x * i / 2;
  200. data[x][y].g = x / 4 * 4 * y / 16 + i;
  201. data[x][y].b = y + i;
  202. }
  203. im.unlock2d(data);
  204. if (!enc.push_image(im))
  205. break;
  206. }
  207. enc.close();
  208. return 0;
  209. }