Browse Source

movie: replace the old Movie class with a GIF encoder.

legacy
Sam Hocevar 7 years ago
parent
commit
c6374c7e07
3 changed files with 199 additions and 242 deletions
  1. +7
    -210
      doc/tutorial/16_movie.cpp
  2. +169
    -18
      src/image/movie.cpp
  3. +23
    -14
      src/lol/image/movie.h

+ 7
- 210
doc/tutorial/16_movie.cpp View File

@@ -17,224 +17,21 @@
#include <lol/engine.h> #include <lol/engine.h>
#include "loldebug.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)
{
/* Third argument specifies format */
avformat_alloc_output_context2(&m_avformat, nullptr, "gif", filename);
if (!m_avformat)
{
msg::debug("could not create output context");
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.size().x * im.size().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) int main(int argc, char **argv)
{ {
UNUSED(argc, argv); UNUSED(argc, argv);


ivec2 size(256, 256);
lol::ivec2 size(256, 256);


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


for (int i = 0; i < 256; ++i) for (int i = 0; i < 256; ++i)
{ {
image im(size);
lol::image im(size);


array2d<u8vec3> &data = im.lock2d<PixelFormat::RGB_8>();
lol::array2d<lol::u8vec3> &data = im.lock2d<lol::PixelFormat::RGB_8>();
for (int y = 0; y < size.y; ++y) for (int y = 0; y < size.y; ++y)
for (int x = 0; x < size.x; ++x) for (int x = 0; x < size.x; ++x)
{ {
@@ -244,11 +41,11 @@ int main(int argc, char **argv)
} }
im.unlock2d(data); im.unlock2d(data);


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


enc.close();
movie.close();


return 0; return 0;
} }


+ 169
- 18
src/image/movie.cpp View File

@@ -1,7 +1,7 @@
// //
// Lol Engine // Lol Engine
// //
// Copyright © 2010—2016 Sam Hocevar <sam@hocevar.net>
// Copyright © 2010—2017 Sam Hocevar <sam@hocevar.net>
// //
// Lol Engine is free software. It comes without any warranty, to // Lol Engine is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it // the extent permitted by applicable law. You can redistribute it
@@ -15,7 +15,13 @@
#if LOL_USE_FFMPEG #if LOL_USE_FFMPEG
extern "C" extern "C"
{ {
# include <libavutil/avassert.h>
# include <libavutil/channel_layout.h>
# include <libavutil/mathematics.h>
# include <libavutil/timestamp.h>
# include <libavformat/avformat.h> # include <libavformat/avformat.h>
# include <libswscale/swscale.h>
# include <libswresample/swresample.h>
} }
#endif #endif


@@ -23,49 +29,194 @@ namespace lol
{ {


#if LOL_USE_FFMPEG #if LOL_USE_FFMPEG
static bool g_ready = false;
#define ERROR_TO_STRING(errnum) (error2string((errnum)).C())
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);
}
#endif #endif


class MovieData
movie::movie(ivec2 size)
: m_avformat(nullptr),
m_avcodec(nullptr),
m_stream(nullptr),
m_frame(nullptr),
m_size(size),
m_index(0)
{ {
#if LOL_USE_FFMPEG #if LOL_USE_FFMPEG
av_register_all();
//av_log_set_callback(ffmpeg_logger);


#endif
m_frame = av_frame_alloc();
ASSERT(m_frame);


friend class Movie;
};
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;


/*
* Public Movie class
*/
int ret = av_frame_get_buffer(m_frame, 32);
ASSERT(ret >= 0);
#endif
}


Movie::Movie(String const &name, ivec2 size, float fps)
: m_data(new MovieData())
bool movie::open_file(char const *filename)
{ {
#if LOL_USE_FFMPEG #if LOL_USE_FFMPEG
if (!g_ready)
/* Third argument specifies format */
avformat_alloc_output_context2(&m_avformat, nullptr, "gif", filename);
if (!m_avformat)
{
msg::debug("could not create output context");
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, ERROR_TO_STRING(ret));
return false;
}
}

int ret = avformat_write_header(m_avformat, nullptr);
if (ret < 0)
{ {
g_ready = true;
av_register_all();
msg::error("could not write header: %s\n", ERROR_TO_STRING(ret));
return false;
} }
#endif #endif

return true;
} }


Movie::~Movie()
bool movie::push_image(image &im)
{ {
#if LOL_USE_FFMPEG #if LOL_USE_FFMPEG
// 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.size().x * im.size().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", ERROR_TO_STRING(ret));
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", ERROR_TO_STRING(ret));
return false;
}
}
#endif #endif


delete m_data;
return true;
}

void movie::close()
{
#if LOL_USE_FFMPEG
// 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);
#endif
} }


void Movie::Feed(image const &image)
bool movie::open_codec()
{ {
#if LOL_USE_FFMPEG #if LOL_USE_FFMPEG
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", ERROR_TO_STRING(ret));
return false;
}

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

return true;
} }


} /* namespace lol */
} // namespace lol



+ 23
- 14
src/lol/image/movie.h View File

@@ -1,7 +1,7 @@
// //
// Lol Engine // Lol Engine
// //
// Copyright © 2010—2015 Sam Hocevar <sam@hocevar.net>
// Copyright © 2010—2017 Sam Hocevar <sam@hocevar.net>
// //
// Lol Engine is free software. It comes without any warranty, to // Lol Engine is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it // the extent permitted by applicable law. You can redistribute it
@@ -13,32 +13,41 @@
#pragma once #pragma once


// //
// The Movie class
// The movie class
// --------------- // ---------------
// //


#include <lol/image/movie.h>
#include <lol/image/pixel.h>
#include <lol/image/image.h>

extern "C" struct AVFormatContext;
extern "C" struct AVCodecContext;
extern "C" struct AVStream;
extern "C" struct AVFrame;


namespace lol namespace lol
{ {


class Movie
class movie
{ {
public: public:
Movie(String const &name, ivec2 size, float fps);
movie(ivec2 size);


/* TODO: Rule of three */
#if 0
Movie(Movie const &other);
Movie & operator =(Movie other);
#endif
~Movie();
bool open_file(char const *filename);
bool push_image(image &im);
void close();


void Feed(image const &image);
private:
bool open_codec();


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


} /* namespace lol */
} // namespace lol



Loading…
Cancel
Save