aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2012-10-04 22:42:06 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2012-10-04 22:42:06 +0200
commit4bb5ce0be62671f7c5127d969c682d57e971679b (patch)
tree1ce0510c5a8cca37a4106f43f4c5de1162f01a35
parentd5dcc19a006f898f21a5b081180a132c9dc6e638 (diff)
downloadosc-graphics-4bb5ce0be62671f7c5127d969c682d57e971679b.tar.gz
rewritten recorder using ffmpeg libs (without SDL_ffmpeg)
* SDL_ffmpeg was broken and is hard to get * my implementation allows specifying an output codec. if the codec supports the screen's pixel format, no conversion is performed saving lots of performance (also beneficial for post-processing a recorded video)
-rw-r--r--chuck/OSCGraphics.ck10
-rw-r--r--configure.ac9
-rw-r--r--src/main.cpp1
-rw-r--r--src/recorder.cpp284
-rw-r--r--src/recorder.h20
5 files changed, 262 insertions, 62 deletions
diff --git a/chuck/OSCGraphics.ck b/chuck/OSCGraphics.ck
index 49a8003..4ca6be5 100644
--- a/chuck/OSCGraphics.ck
+++ b/chuck/OSCGraphics.ck
@@ -9,14 +9,20 @@ public class OSCGraphics {
}
fun static string
- record(string file)
+ record(string file, string codec)
{
- osc_send.startMsg("/recorder/start", "s");
+ osc_send.startMsg("/recorder/start", "ss");
file => osc_send.addString;
+ codec => osc_send.addString;
return file;
}
fun static string
+ record(string file)
+ {
+ return record(file, "");
+ }
+ fun static string
record()
{
osc_send.startMsg("/recorder/stop", "");
diff --git a/configure.ac b/configure.ac
index 4af841d..30d5341 100644
--- a/configure.ac
+++ b/configure.ac
@@ -78,11 +78,10 @@ AC_CHECK_HEADERS([SDL_ttf.h], , [
AC_MSG_ERROR([Required libSDL_ttf header missing!])
])
-AC_CHECK_LIB(SDL_ffmpeg, SDL_ffmpegOpen, , [
- AC_MSG_ERROR([Required libSDL_ffmpeg missing!])
-])
-AC_CHECK_HEADERS([SDL/SDL_ffmpeg.h libavcodec/avcodec.h], , [
- AC_MSG_ERROR([Required libSDL_ffmpeg header missing!])
+PKG_CHECK_MODULES(FFMPEG, [libavcodec libavformat libavutil libswscale], [
+ CFLAGS="$CFLAGS $FFMPEG_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $FFMPEG_CFLAGS"
+ LIBS="$LIBS $FFMPEG_LIBS"
])
PKG_CHECK_MODULES(LIBVLC, [libvlc >= 1.1.10], [
diff --git a/src/main.cpp b/src/main.cpp
index d353a52..a7475bb 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -2,6 +2,7 @@
#include "config.h"
#endif
+#define __STDC_CONSTANT_MACROS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/src/recorder.cpp b/src/recorder.cpp
index 9680079..e9622f3 100644
--- a/src/recorder.cpp
+++ b/src/recorder.cpp
@@ -8,10 +8,11 @@
#include <stdint.h>
#include <SDL.h>
-#include <SDL/SDL_ffmpeg.h>
extern "C" {
+#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
+#include <libswscale/swscale.h>
}
#include "osc_graphics.h"
@@ -19,37 +20,33 @@ extern "C" {
#include "recorder.h"
-/*
- * Macros
- */
-#define SDL_FFMPEGFREE_SAFE(FILE) do { \
- if (FILE) { \
- SDL_ffmpegFree(FILE); \
- FILE = NULL; \
- } \
-} while (0)
-
-#define SDL_FFMPEG_ERROR(FMT, ...) do { \
- fprintf(stderr, "%s(%d): " FMT ": %s\n", \
- __FILE__, __LINE__, ##__VA_ARGS__, \
- SDL_ffmpegGetError()); \
-} while (0)
-
extern "C" {
static int start_handler(const char *path, const char *types,
- lo_arg **argv, int argc,
- void *data, void *user_data);
+ lo_arg **argv, int argc,
+ void *data, void *user_data);
static int stop_handler(const char *path, const char *types,
lo_arg **argv, int argc,
- void *data, void *user_data);
+ void *data, void *user_data);
+
+}
+
+Recorder::Recorder() : Mutex(), ffmpeg(NULL), sws_context(NULL)
+{
+ static bool initialized = false;
+
+ if (!initialized) {
+ avcodec_register_all();
+ av_register_all();
+ initialized = true;
+ }
}
void
Recorder::register_methods()
{
- osc_server.add_method("s", start_handler, this, "/recorder/start");
+ osc_server.add_method("ss", start_handler, this, "/recorder/start");
osc_server.add_method("", stop_handler, this, "/recorder/stop");
}
@@ -59,39 +56,158 @@ start_handler(const char *path, const char *types,
{
Recorder *obj = (Recorder *)user_data;
- obj->start(&argv[0]->s);
+ obj->start(&argv[0]->s, &argv[1]->s);
return 0;
}
+static enum PixelFormat
+get_pix_fmt(SDL_PixelFormat *format)
+{
+ switch (format->BitsPerPixel) {
+ case 8:
+ return PIX_FMT_PAL8;
+ case 24:
+ return PIX_FMT_RGB24;
+ case 32:
+ return PIX_FMT_RGB32;
+ }
+
+ return PIX_FMT_NONE;
+}
+
void
-Recorder::start(const char *filename)
+Recorder::start(const char *filename, const char *codecname)
{
- SDL_ffmpegCodec codec = {
- -1, /* video codec based on file name */
- screen->w, screen->h,
- 1, config_framerate, /* framerate */
- 6000000, -1, -1,
- -1, 0, -1, -1, -1, -1 /* no audio */
- };
-
- if (!filename || !*filename) {
- stop();
+ AVCodec *videoCodec;
+
+ stop();
+
+ if (!filename || !*filename)
return;
- }
lock();
- SDL_FFMPEGFREE_SAFE(file);
+ ffmpeg = avformat_alloc_context();
+
+ ffmpeg->oformat = av_guess_format(NULL, filename, NULL);
+ if (!ffmpeg->oformat)
+ ffmpeg->oformat = av_guess_format("dvd", NULL, NULL);
+
+ ffmpeg->preload = (int)(0.5 * AV_TIME_BASE);
+ ffmpeg->max_delay = (int)(0.7 * AV_TIME_BASE);
+
+ if (url_fopen(&ffmpeg->pb, filename, URL_WRONLY) < 0) {
+ fprintf(stderr, "Could not open %s!\n", filename);
+ exit(EXIT_FAILURE);
+ }
+
+ stream = av_new_stream(ffmpeg, 0);
+ if (!stream) {
+ fprintf(stderr, "Could not stream!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ stream->codec = avcodec_alloc_context();
+
+ avcodec_get_context_defaults2(stream->codec, CODEC_TYPE_VIDEO);
+
+ stream->codec->codec_type = CODEC_TYPE_VIDEO;
+
+ stream->codec->bit_rate = 6000000;
+
+ /* resolution must be a multiple of two */
+ stream->codec->width = screen->w;
+ stream->codec->height = screen->h;
+
+ /* set time_base */
+ stream->codec->time_base.num = 1;
+ stream->codec->time_base.den = config_framerate;
+
+ /* emit one intra frame every twelve frames at most */
+ stream->codec->gop_size = 12;
+
+ switch (stream->codec->codec_id) {
+ case CODEC_ID_MPEG2VIDEO:
+ /* set mpeg2 codec parameters */
+ stream->codec->max_b_frames = 2;
+ break;
+ case CODEC_ID_MPEG1VIDEO:
+ /* set mpeg1 codec parameters */
+ /* needed to avoid using macroblocks in which some coeffs overflow
+ this doesnt happen with normal video, it just happens here as the
+ motion of the chroma plane doesnt match the luma plane */
+ stream->codec->mb_decision = 2;
+ break;
+ default:
+ break;
+ }
+
+ /* some formats want stream headers to be separate */
+ if (ffmpeg->oformat->flags & AVFMT_GLOBALHEADER)
+ stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
+
+ /* find the video encoder */
+ if (codecname && *codecname)
+ videoCodec = avcodec_find_encoder_by_name(codecname);
+ else
+ videoCodec = avcodec_find_encoder(ffmpeg->oformat->video_codec);
+ if (!videoCodec) {
+ /* FIXME */
+ exit(EXIT_FAILURE);
+ }
+ stream->codec->codec_id = videoCodec->id;
+
+ enum PixelFormat screen_fmt = get_pix_fmt(screen->format);
+
+ /* set pixel format */
+ stream->codec->pix_fmt = PIX_FMT_YUV420P;
+ for (const PixelFormat *fmt = videoCodec->pix_fmts; *fmt != -1; fmt++) {
+ if (*fmt == screen_fmt) {
+ stream->codec->pix_fmt = screen_fmt;
+ break;
+ }
+ }
- file = SDL_ffmpegCreate(filename);
- if (!file) {
- SDL_FFMPEG_ERROR("SDL_ffmpegCreate");
+ if (stream->codec->pix_fmt != screen_fmt) {
+ fprintf(stderr, "Warning: Pixel format conversion necessary!\n");
+
+ /* NOTE: sizes shouldn't differ */
+ sws_context = sws_getContext(screen->w, screen->h, screen_fmt,
+ stream->codec->width,
+ stream->codec->height,
+ stream->codec->pix_fmt,
+ SWS_BILINEAR, NULL, NULL, NULL);
+ }
+
+ /* open the codec */
+ if (avcodec_open(stream->codec, videoCodec) < 0) {
+ /* FIXME */
exit(EXIT_FAILURE);
}
- SDL_ffmpegAddVideoStream(file, codec);
- SDL_ffmpegSelectVideoStream(file, 0);
+ encodeFrame = avcodec_alloc_frame();
+
+ if (sws_context) {
+ int size = avpicture_get_size(stream->codec->pix_fmt,
+ stream->codec->width,
+ stream->codec->height) +
+ FF_INPUT_BUFFER_PADDING_SIZE;
+ avpicture_fill((AVPicture *)encodeFrame, new uint8_t[size],
+ stream->codec->pix_fmt,
+ stream->codec->width,
+ stream->codec->height);
+ }
+
+ encodeFrameBufferSize = stream->codec->width * stream->codec->height * 4 +
+ FF_INPUT_BUFFER_PADDING_SIZE;
+ encodeFrameBuffer = new uint8_t[encodeFrameBufferSize];
+
+ if (av_set_parameters(ffmpeg, 0) < 0)
+ fprintf(stderr, "could not set encoding parameters\n");
+
+ /* try to write a header */
+ av_write_header(ffmpeg);
start_time = SDL_GetTicks();
@@ -113,7 +229,31 @@ void
Recorder::stop()
{
lock();
- SDL_FFMPEGFREE_SAFE(file);
+
+ if (!ffmpeg) {
+ unlock();
+ return;
+ }
+
+ avcodec_flush_buffers(stream->codec);
+
+ av_write_trailer(ffmpeg);
+
+ av_free_packet(&pkt);
+ delete encodeFrameBuffer;
+ if (sws_context) {
+ sws_freeContext(sws_context);
+ sws_context = NULL;
+
+ delete encodeFrame->data[0];
+ }
+ av_free(encodeFrame);
+ avcodec_close(stream->codec);
+ av_free(stream);
+ url_fclose(ffmpeg->pb);
+ av_free(ffmpeg);
+ ffmpeg = NULL;
+
unlock();
}
@@ -122,14 +262,56 @@ Recorder::record(SDL_Surface *surf)
{
lock();
- if (file) {
- struct AVFrame *frame = file->videoStream->encodeFrame;
- int64_t pts = (SDL_GetTicks() - start_time)/FRAME_DELAY;
+ if (!ffmpeg) {
+ unlock();
+ return;
+ }
- if (pts > frame->pts) {
- frame->pts = pts;
- SDL_ffmpegAddVideoFrame(file, surf);
- }
+ int64_t pts = (SDL_GetTicks() - start_time)/FRAME_DELAY;
+ if (pts <= encodeFrame->pts) {
+ unlock();
+ return;
+ }
+ encodeFrame->pts = pts;
+
+ if (sws_context) {
+ const uint8_t *const data[] = {(uint8_t *)surf->pixels, NULL};
+ const int pitch[] = {surf->pitch, 0};
+
+ sws_scale(sws_context, data, pitch, 0, surf->h,
+ encodeFrame->data, encodeFrame->linesize);
+ } else {
+ encodeFrame->data[0] = (uint8_t *)surf->pixels;
+ encodeFrame->data[1] = NULL;
+ encodeFrame->linesize[0] = surf->pitch;
+ encodeFrame->linesize[1] = 0;
+ }
+
+ int out_size = avcodec_encode_video(stream->codec, encodeFrameBuffer,
+ encodeFrameBufferSize, encodeFrame);
+
+ if (out_size > 0) {
+ av_init_packet(&pkt);
+
+ /* set correct stream index for this packet */
+ pkt.stream_index = stream->index;
+ /* set keyframe flag if needed */
+ if (stream->codec->coded_frame->key_frame)
+ pkt.flags |= PKT_FLAG_KEY;
+ /* write encoded data into packet */
+ pkt.data = encodeFrameBuffer;
+ /* set the correct size of this packet */
+ pkt.size = out_size;
+ /* set the correct duration of this packet */
+ pkt.duration = AV_TIME_BASE / stream->time_base.den;
+
+ /* if needed info is available, write pts for this packet */
+ if (stream->codec->coded_frame->pts != AV_NOPTS_VALUE)
+ pkt.pts = av_rescale_q(stream->codec->coded_frame->pts,
+ stream->codec->time_base,
+ stream->time_base);
+
+ av_write_frame(ffmpeg, &pkt);
}
unlock();
@@ -137,8 +319,8 @@ Recorder::record(SDL_Surface *surf)
Recorder::~Recorder()
{
- osc_server.del_method("s", "/recorder/start");
- osc_server.del_method("", "/recorder/stop");
+ osc_server.del_method("ss", "/recorder/start");
+ osc_server.del_method("", "/recorder/stop");
- SDL_FFMPEGFREE_SAFE(file);
+ stop();
}
diff --git a/src/recorder.h b/src/recorder.h
index 62458ad..2ef0d47 100644
--- a/src/recorder.h
+++ b/src/recorder.h
@@ -2,21 +2,33 @@
#define __RECORDER_H
#include <SDL.h>
-#include <SDL/SDL_ffmpeg.h>
+
+extern "C" {
+#include <libavformat/avformat.h>
+#include <libavcodec/avcodec.h>
+#include <libswscale/swscale.h>
+}
#include "osc_graphics.h"
class Recorder : Mutex {
- SDL_ffmpegFile *file;
+ AVFormatContext *ffmpeg;
+ AVStream *stream;
+ SwsContext *sws_context;
+ AVFrame *encodeFrame;
+ int encodeFrameBufferSize;
+ uint8_t *encodeFrameBuffer;
+ AVPacket pkt;
+
Uint32 start_time;
public:
- Recorder() : Mutex(), file(NULL) {}
+ Recorder();
~Recorder();
void register_methods();
- void start(const char *filename);
+ void start(const char *filename, const char *codecname = NULL);
void stop();
void record(SDL_Surface *surf);