Browse Source

doc: add a GIF exporting program (experimental for now)

legacy
Sam Hocevar 8 years ago
parent
commit
ed703e6a56
2 changed files with 234 additions and 1 deletions
  1. +1
    -0
      .gitignore
  2. +233
    -1
      doc/tutorial/16_movie.cpp

+ 1
- 0
.gitignore View File

@@ -102,6 +102,7 @@ doc/tutorial/13_shader_builder
doc/tutorial/14_lol_lua
doc/tutorial/15_lolimgui
doc/tutorial/16_movie
doc/tutorial/16_movie.gif
tools/make-font
# Our data
doc/doxygen.cfg


+ 233
- 1
doc/tutorial/16_movie.cpp View File

@@ -1,5 +1,5 @@
//
// Lol Engine — Movie tutorial
// Lol Engine — GIF encoding sample
//
// Copyright © 2016 Sam Hocevar <sam@hocevar.net>
//
@@ -15,11 +15,243 @@
#endif

#include <lol/engine.h>
#include "loldebug.h"

extern "C" {
#include <libavutil/avassert.h>
#include <libavutil/channel_layout.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}

using namespace lol;

class gif_encoder
{
public:
gif_encoder(ivec2 size)
: m_avformat(nullptr),
m_avcodec(nullptr),
m_stream(nullptr),
m_frame(nullptr),
m_size(size),
m_index(0)
{
av_register_all();
av_log_set_callback(ffmpeg_logger);

m_frame = av_frame_alloc();
ASSERT(m_frame);

m_frame->format = AV_PIX_FMT_RGB8; // 3:3:2 packed for GIF
m_frame->width = m_size.x;
m_frame->height = m_size.y;

int ret = av_frame_get_buffer(m_frame, 32);
ASSERT(ret >= 0);
}

bool open_file(char const *filename)
{
avformat_alloc_output_context2(&m_avformat, nullptr, nullptr, filename);
if (!m_avformat)
{
msg::debug("could not deduce output format from file extension %s: using GIF\n", filename);
avformat_alloc_output_context2(&m_avformat, nullptr, "gif", filename);

if (!m_avformat)
return false;
}

if (!open_codec())
return false;

if (!(m_avformat->oformat->flags & AVFMT_NOFILE))
{
int ret = avio_open(&m_avformat->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0)
{
msg::error("could not open '%s': %s\n", filename, error2string(ret).C());
return false;
}
}

int ret = avformat_write_header(m_avformat, nullptr);
if (ret < 0)
{
msg::error("could not write header: %s\n", error2string(ret).C());
return false;
}

return true;
}

bool push_image(Image &im)
{
// Make sure the encoder does not hold a reference on our
// frame (GIF does that in order to compress using deltas).
if (av_frame_make_writable(m_frame) < 0)
return false;

// Convert image to 3:3:2. TODO: add some dithering
u8vec3 *data = im.Lock<PixelFormat::RGB_8>();
for (int n = 0; n < im.GetSize().x * im.GetSize().y; ++n)
m_frame->data[0][n] = (data[n].r & 0xe0) | ((data[n].g & 0xe0) >> 3) | (data[n].b >> 6);
im.Unlock(data);

m_frame->pts = m_index++;

AVPacket pkt;
memset(&pkt, 0, sizeof(pkt));
av_init_packet(&pkt);

// XXX: is got_packet necessary?
int got_packet = 0;
int ret = avcodec_encode_video2(m_avcodec, &pkt, m_frame, &got_packet);
if (ret < 0)
{
msg::error("cannot encode video frame: %s\n", error2string(ret).C());
return false;
}

if (got_packet)
{
pkt.stream_index = m_stream->index;

ret = av_interleaved_write_frame(m_avformat, &pkt);
if (ret < 0)
{
msg::error("cannot write video frame: %s\n", error2string(ret).C());
return false;
}
}

return true;
}

void close()
{
// this must be done before m_avcodec is freed
av_write_trailer(m_avformat);

avcodec_free_context(&m_avcodec);
av_frame_free(&m_frame);

if (!(m_avformat->oformat->flags & AVFMT_NOFILE))
avio_closep(&m_avformat->pb);

avformat_free_context(m_avformat);
}

private:
bool open_codec()
{
AVCodec *codec = avcodec_find_encoder(m_avformat->oformat->video_codec);
if (!codec)
{
msg::error("no encoder found for %s\n", avcodec_get_name(m_avformat->oformat->video_codec));
return false;
}

m_stream = avformat_new_stream(m_avformat, nullptr);
if (!m_stream)
{
msg::error("cannot allocate stream\n");
return false;
}
m_stream->id = 0; // first (and only?) stream
m_stream->time_base = AVRational{ 1, 30 }; // 30 fps

m_avcodec = avcodec_alloc_context3(codec);
if (!m_avcodec)
{
msg::error("cannot allocate encoding context\n");
return false;
}

m_avcodec->codec_id = m_avformat->oformat->video_codec;
m_avcodec->width = m_frame->width;
m_avcodec->height = m_frame->height;
m_avcodec->pix_fmt = AVPixelFormat(m_frame->format);
m_avcodec->time_base = m_stream->time_base;

if (m_avformat->oformat->flags & AVFMT_GLOBALHEADER)
m_avcodec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

int ret = avcodec_open2(m_avcodec, codec, nullptr);
if (ret < 0)
{
msg::error("cannot open video codec: %s\n", error2string(ret).C());
return false;
}

ret = avcodec_parameters_from_context(m_stream->codecpar, m_avcodec);
if (ret < 0)
{
msg::error("cannot copy stream parameters\n");
return false;
}

return true;
}

static String error2string(int errnum)
{
char tmp[AV_ERROR_MAX_STRING_SIZE];
av_strerror(errnum, tmp, AV_ERROR_MAX_STRING_SIZE);
return String(tmp);
}

static void ffmpeg_logger(void *ptr, int level, const char *fmt, va_list vl)
{
// FIXME: use lol::msg::debug
UNUSED(ptr, level);
vfprintf(stderr, fmt, vl);
}

private:
AVFormatContext *m_avformat;
AVCodecContext *m_avcodec;
AVStream *m_stream;
AVFrame *m_frame;
ivec2 m_size;
int m_index;
};


int main(int argc, char **argv)
{
UNUSED(argc, argv);

ivec2 size(256, 256);

gif_encoder enc(size);
if (!enc.open_file("16_movie.gif"))
return EXIT_FAILURE;

for (int i = 0; i < 256; ++i)
{
Image im(size);

array2d<u8vec3> &data = im.Lock2D<PixelFormat::RGB_8>();
for (int y = 0; y < size.y; ++y)
for (int x = 0; x < size.x; ++x)
{
data[x][y].r = x * i / 2;
data[x][y].g = x / 4 * 4 * y / 16 + i;
data[x][y].b = y + i;
}
im.Unlock2D(data);

if (!enc.push_image(im))
break;
}

enc.close();

return 0;
}


Loading…
Cancel
Save