Revert "Refactor movie capture to use FFMPEG only"

pull/1112/head
Hleb Valoshka 2021-08-21 14:21:17 +04:00
parent 7b52f23c97
commit f731579681
10 changed files with 302 additions and 243 deletions

View File

@ -23,7 +23,8 @@ set(CELESTIA_SOURCES
if(ENABLE_FFMPEG)
list(APPEND CELESTIA_SOURCES
moviecapture.cpp
ffmpegcapture.cpp
ffmpegcapture.h
moviecapture.h
)
endif()

View File

@ -87,13 +87,6 @@ static const float MinimumFOV = degToRad(0.001f);
static float KeyRotationAccel = degToRad(120.0f);
static float MouseRotationSensitivity = degToRad(1.0f);
#ifdef USE_FFMPEG
enum CodecID
{
CEL_CODEC_ID_FFVHUFF = AV_CODEC_ID_FFVHUFF,
CEL_CODEC_ID_H264 = AV_CODEC_ID_H264,
};
#endif
static void warning(string s)
{
@ -176,10 +169,8 @@ CelestiaCore::CelestiaCore() :
CelestiaCore::~CelestiaCore()
{
#ifdef USE_FFMPEG
if (movieCapture != nullptr)
recordEnd();
#endif
delete timer;
delete renderer;
@ -816,7 +807,6 @@ void CelestiaCore::keyDown(int key, int modifiers)
case Key_F7:
sim->setTargetSpeed(1.0_ly);
break;
#ifdef USE_FFMPEG
case Key_F11:
if (movieCapture != nullptr)
{
@ -830,7 +820,6 @@ void CelestiaCore::keyDown(int key, int modifiers)
if (movieCapture != nullptr)
recordEnd();
break;
#endif
case Key_NumPad2:
case Key_NumPad4:
case Key_NumPad6:
@ -1926,7 +1915,6 @@ void CelestiaCore::tick()
// The time step is normally driven by the system clock; however, when
// recording a movie, we fix the time step the frame rate of the movie.
double dt = 0.0;
#ifdef USE_FFMPEG
if (movieCapture != nullptr && recording)
{
dt = 1.0 / movieCapture->getFrameRate();
@ -1935,9 +1923,6 @@ void CelestiaCore::tick()
{
dt = sysTime - lastTime;
}
#else
dt = sysTime - lastTime;
#endif
// Pause script execution
if (scriptState == ScriptPaused)
@ -2147,10 +2132,8 @@ void CelestiaCore::draw()
if (toggleAA)
renderer->enableMSAA();
#ifdef USE_FFMPEG
if (movieCapture != nullptr && recording)
movieCapture->captureFrame();
#endif
// Frame rate counter
nFrames++;
@ -3523,7 +3506,6 @@ void CelestiaCore::renderOverlay()
overlay->setFont(font);
}
#ifdef USE_FFMPEG
if (movieCapture != nullptr)
{
int movieWidth = movieCapture->getWidth();
@ -3571,7 +3553,6 @@ void CelestiaCore::renderOverlay()
overlay->restorePos();
}
#endif
if (editMode)
{
@ -4417,33 +4398,25 @@ void CelestiaCore::setOverlayElements(int _overlayElements)
overlayElements = _overlayElements;
}
#ifdef USE_FFMPEG
bool CelestiaCore::initMovieCapture(const fs::path &path, int width, int height,
float fps, int64_t bitrate, int codec)
void CelestiaCore::initMovieCapture(MovieCapture* mc)
{
if (movieCapture != nullptr)
return false;
movieCapture = make_unique<celestia::MovieCapture>(getRenderer());
movieCapture->setVideoCodec(static_cast<AVCodecID>(codec));
movieCapture->setBitRate(bitrate);
if (codec == CEL_CODEC_ID_H264)
movieCapture->setEncoderOptions(getConfig()->x264EncoderOptions);
else
movieCapture->setEncoderOptions(getConfig()->ffvhEncoderOptions);
return movieCapture->start(path, width, height, fps);
if (movieCapture == nullptr)
movieCapture = mc;
}
void CelestiaCore::recordBegin()
{
if (movieCapture != nullptr)
{
recording = true;
movieCapture->recordingStatus(true);
}
}
void CelestiaCore::recordPause()
{
recording = false;
if (movieCapture != nullptr) movieCapture->recordingStatus(false);
}
void CelestiaCore::recordEnd()
@ -4452,6 +4425,7 @@ void CelestiaCore::recordEnd()
{
recordPause();
movieCapture->end();
delete movieCapture;
movieCapture = nullptr;
}
}
@ -4465,7 +4439,6 @@ bool CelestiaCore::isRecording()
{
return recording;
}
#endif
void CelestiaCore::flash(const string& s, double duration)
{
@ -4779,43 +4752,3 @@ void CelestiaCore::setLogFile(const fs::path &fn)
fmt::fprintf(cerr, "Unable to open log file %s\n", fn.string());
}
}
#ifdef USE_FFMPEG
auto CelestiaCore::getSupportedMovieSizes() const
-> celestia::util::array_view<MovieSize>
{
static std::array<MovieSize, 8> MovieSizes =
{{
{ 320, 240 },
{ 640, 480 },
{ 720, 480 },
{ 720, 576 },
{ 1024, 768 },
{ 1280, 720 },
{ 1920, 1080 },
{ 3840, 2160 }
}};
return MovieSizes;
}
auto CelestiaCore::getSupportedMovieFramerates() const
-> celestia::util::array_view<float>
{
static std::array<float, 5> MovieFramerates =
{
15.0f, 24.0f, 25.0f, 29.97f, 30.0f
};
return MovieFramerates;
}
auto CelestiaCore::getSupportedMovieCodecs() const
-> celestia::util::array_view<MovieCodec>
{
static std::array<MovieCodec, 2> MovieCodecs =
{{
{ CEL_CODEC_ID_FFVHUFF, N_("Lossless") },
{ CEL_CODEC_ID_H264, N_("Lossy (H.264)") }
}};
return MovieCodecs;
}
#endif

View File

@ -12,7 +12,6 @@
#include <fstream>
#include <string>
#include <memory>
#include <celutil/filetype.h>
#include <celutil/timer.h>
#include <celutil/watcher.h>
@ -29,9 +28,7 @@
#include "configfile.h"
#include "favorites.h"
#include "destination.h"
#ifdef USE_FFMPEG
#include "moviecapture.h"
#endif
#include "view.h"
#ifdef CELX
#include <celscript/lua/celx.h>
@ -58,18 +55,6 @@ public:
virtual void update(const std::string&) = 0;
};
struct MovieSize
{
int width;
int height;
};
struct MovieCodec
{
int codecId;
const char *codecDescr;
};
class CelestiaCore // : public Watchable<CelestiaCore>
{
public:
@ -259,17 +244,12 @@ class CelestiaCore // : public Watchable<CelestiaCore>
void setTextEnterMode(int);
int getTextEnterMode() const;
#ifdef USE_FFMPEG
bool initMovieCapture(const fs::path &path, int width, int height, float fps, int64_t bitrate, int codec);
void initMovieCapture(MovieCapture*);
void recordBegin();
void recordPause();
void recordEnd();
bool isCaptureActive();
bool isRecording();
celestia::util::array_view<MovieSize> getSupportedMovieSizes() const;
celestia::util::array_view<float> getSupportedMovieFramerates() const;
celestia::util::array_view<MovieCodec> getSupportedMovieCodecs() const;
#endif
void runScript(const fs::path& filename, bool i18n = true);
void cancelScript();
@ -491,10 +471,8 @@ class CelestiaCore // : public Watchable<CelestiaCore>
bool shiftKeysPressed[KeyCount];
double KeyAccel{ 1.0 };
#ifdef USE_FFMPEG
std::unique_ptr<celestia::MovieCapture> movieCapture;
MovieCapture* movieCapture{ nullptr };
bool recording{ false };
#endif
Alerter* alerter{ nullptr };
std::vector<CelestiaWatcher*> watchers;

View File

@ -1,13 +1,10 @@
#define AVCODEC_DEBUG 0
#include <iostream>
#include <vector>
#include <fmt/format.h>
#include "ffmpegcapture.h"
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavutil/timestamp.h>
#include <libavutil/pixdesc.h>
#include <libavutil/opt.h>
@ -15,18 +12,17 @@ extern "C"
#include <libswscale/swscale.h>
}
#include <celengine/render.h>
#include "moviecapture.h"
#include <iostream>
#include <vector>
#include <fmt/format.h>
using namespace std;
namespace celestia
{
// a wrapper around a single output AVStream
class MovieCapturePrivate
class FFMPEGCapturePrivate
{
MovieCapturePrivate() = default;
~MovieCapturePrivate();
FFMPEGCapturePrivate() = default;
~FFMPEGCapturePrivate();
bool init(const fs::path& fn);
bool addStream(int w, int h, float fps);
@ -45,7 +41,7 @@ class MovieCapturePrivate
AVFrame *tmpfr { nullptr };
AVCodecContext *enc { nullptr };
AVFormatContext *oc { nullptr };
const AVCodec *vc { nullptr };
AVCodec *vc { nullptr };
AVPacket *pkt { nullptr };
SwsContext *swsc { nullptr };
@ -54,7 +50,7 @@ class MovieCapturePrivate
// pts of the next frame that will be generated
int64_t nextPts { 0 };
// requested bitrate
int64_t bitrate { 400000 };
int64_t bit_rate { 400000 };
AVCodecID vc_id { AV_CODEC_ID_FFVHUFF };
AVPixelFormat format { AV_PIX_FMT_NONE };
@ -69,22 +65,22 @@ class MovieCapturePrivate
#if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)) // ffmpeg < 4.0
static bool registered;
#endif
friend class MovieCapture;
friend class FFMPEGCapture;
};
#if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)) // ffmpeg < 4.0
bool MovieCapturePrivate::registered = false;
bool FFMPEGCapturePrivate::registered = false;
#endif
bool MovieCapturePrivate::init(const fs::path& filename)
bool FFMPEGCapturePrivate::init(const fs::path& filename)
{
this->filename = filename;
#if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)) // ffmpeg < 4.0
if (!MovieCapturePrivate::registered)
if (!FFMPEGCapturePrivate::registered)
{
av_register_all();
MovieCapturePrivate::registered = true;
FFMPEGCapturePrivate::registered = true;
}
#endif
@ -96,7 +92,7 @@ bool MovieCapturePrivate::init(const fs::path& filename)
return oc != nullptr;
}
bool MovieCapturePrivate::isSupportedPixelFormat(enum AVPixelFormat format) const
bool FFMPEGCapturePrivate::isSupportedPixelFormat(enum AVPixelFormat format) const
{
const enum AVPixelFormat *p = vc->pix_fmts;
if (p == nullptr)
@ -193,7 +189,7 @@ static void listEncoderParameters(const AVCodec *vc)
}
#endif
int MovieCapturePrivate::writePacket()
int FFMPEGCapturePrivate::writePacket()
{
// rescale output packet timestamp values from codec to stream timebase
av_packet_rescale_ts(pkt, enc->time_base, st->time_base);
@ -204,7 +200,7 @@ int MovieCapturePrivate::writePacket()
}
// add an output stream
bool MovieCapturePrivate::addStream(int width, int height, float fps)
bool FFMPEGCapturePrivate::addStream(int width, int height, float fps)
{
this->fps = fps;
@ -235,12 +231,13 @@ bool MovieCapturePrivate::addStream(int width, int height, float fps)
return false;
}
enc->codec_id = vc_id;
enc->bit_rate = bitrate;
enc->codec_id = oc->oformat->video_codec = vc_id;
enc->bit_rate = bit_rate;
#if 0
enc->rc_min_rate = ...;
enc->rc_max_rate = ...;
enc->bitrate_tolerance = 0;
enc->bit_rate_tolerance = 0;
#endif
// Resolution must be a multiple of two
enc->width = width;
@ -290,7 +287,7 @@ bool MovieCapturePrivate::addStream(int width, int height, float fps)
return true;
}
bool MovieCapturePrivate::start()
bool FFMPEGCapturePrivate::start()
{
// open the output file, if needed
if ((oc->oformat->flags & AVFMT_NOFILE) == 0)
@ -320,7 +317,7 @@ bool MovieCapturePrivate::start()
return true;
}
bool MovieCapturePrivate::openVideo()
bool FFMPEGCapturePrivate::openVideo()
{
AVDictionary *opts = nullptr;
const char *str = "";
@ -419,7 +416,7 @@ static void captureImage(AVFrame *pict, int width, int height, const Renderer *r
// encode one video frame and send it to the muxer
// return 1 when encoding is finished, 0 otherwise
bool MovieCapturePrivate::writeVideoFrame(bool finalize)
bool FFMPEGCapturePrivate::writeVideoFrame(bool finalize)
{
AVFrame *frame = finalize ? nullptr : this->frame;
const int bytesPerPixel = hasAlpha ? 4 : 3;
@ -451,9 +448,7 @@ bool MovieCapturePrivate::writeVideoFrame(bool finalize)
frame->pts = nextPts++;
}
#if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100))
av_init_packet(pkt);
#endif
// encode the image
if (avcodec_send_frame(enc, frame) < 0)
@ -485,7 +480,7 @@ bool MovieCapturePrivate::writeVideoFrame(bool finalize)
return true;
}
void MovieCapturePrivate::finish()
void FFMPEGCapturePrivate::finish()
{
writeVideoFrame(true);
@ -499,7 +494,7 @@ void MovieCapturePrivate::finish()
avio_closep(&oc->pb);
}
MovieCapturePrivate::~MovieCapturePrivate()
FFMPEGCapturePrivate::~FFMPEGCapturePrivate()
{
avcodec_free_context(&enc);
av_frame_free(&frame);
@ -509,40 +504,41 @@ MovieCapturePrivate::~MovieCapturePrivate()
av_packet_free(&pkt);
}
MovieCapture::MovieCapture(const Renderer *r) :
d(new MovieCapturePrivate)
FFMPEGCapture::FFMPEGCapture(const Renderer *r) :
MovieCapture(r),
d(new FFMPEGCapturePrivate)
{
d->renderer = r;
d->hasAlpha = r->getPreferredCaptureFormat() == PixelFormat::RGBA;
d->format = d->hasAlpha ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24;
}
MovieCapture::~MovieCapture()
FFMPEGCapture::~FFMPEGCapture()
{
delete d;
}
int MovieCapture::getFrameCount() const
int FFMPEGCapture::getFrameCount() const
{
return d->nextPts;
}
int MovieCapture::getWidth() const
int FFMPEGCapture::getWidth() const
{
return d->enc->width;
}
int MovieCapture::getHeight() const
int FFMPEGCapture::getHeight() const
{
return d->enc->height;
}
float MovieCapture::getFrameRate() const
float FFMPEGCapture::getFrameRate() const
{
return d->fps;
}
bool MovieCapture::start(const fs::path& filename, int width, int height, float fps)
bool FFMPEGCapture::start(const fs::path& filename, int width, int height, float fps)
{
if (!d->init(filename) ||
!d->addStream(width, height, fps) ||
@ -557,7 +553,7 @@ bool MovieCapture::start(const fs::path& filename, int width, int height, float
return true;
}
bool MovieCapture::end()
bool FFMPEGCapture::end()
{
if (!d->capturing)
return false;
@ -569,23 +565,22 @@ bool MovieCapture::end()
return true;
}
bool MovieCapture::captureFrame()
bool FFMPEGCapture::captureFrame()
{
return d->capturing && d->writeVideoFrame();
}
void MovieCapture::setVideoCodec(AVCodecID vc_id)
void FFMPEGCapture::setVideoCodec(AVCodecID vc_id)
{
d->vc_id = vc_id;
}
void MovieCapture::setBitRate(int64_t bitrate)
void FFMPEGCapture::setBitRate(int64_t bit_rate)
{
d->bitrate = bitrate;
d->bit_rate = bit_rate;
}
void MovieCapture::setEncoderOptions(const std::string &s)
void FFMPEGCapture::setEncoderOptions(const std::string &s)
{
d->vc_options = s;
}
}

View File

@ -0,0 +1,39 @@
#pragma once
#include "moviecapture.h"
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include <libavformat/avformat.h>
}
#include <celengine/hash.h>
class FFMPEGCapturePrivate;
class FFMPEGCapture : public MovieCapture
{
public:
FFMPEGCapture(const Renderer *r);
~FFMPEGCapture() override;
bool start(const fs::path&, int, int, float) override;
bool end() override;
bool captureFrame() override;
int getFrameCount() const override;
int getWidth() const override;
int getHeight() const override;
float getFrameRate() const override;
void setAspectRatio(int, int) override {};
void setQuality(float) override {};
void recordingStatus(bool) override {};
void setVideoCodec(AVCodecID);
void setBitRate(int64_t);
void setEncoderOptions(const std::string&);
private:
FFMPEGCapturePrivate *d{ nullptr };
};

View File

@ -30,6 +30,9 @@
#include <celcompat/charconv.h>
#include <celutil/filetype.h>
#include <celutil/gettext.h>
#ifdef USE_FFMPEG
#include <celestia/ffmpegcapture.h>
#endif
#include "actions.h"
#include "common.h"
@ -53,14 +56,41 @@ using namespace std;
static void openScript(const char* filename, AppData* app);
static void captureImage(const char* filename, AppData* app);
#ifdef USE_FFMPEG
static void captureMovie(const char* filename, int w, int h, float fps,
int codec, int64_t bitrate, AppData* app);
static void captureMovie(const char* filename, const int resolution[], float fps,
AVCodecID codec, float bitrate, AppData* app);
#endif
static void textInfoDialog(const char *txt, const char *title, AppData* app);
static void setRenderFlag(AppData* a, uint64_t flag, gboolean state);
static void setOrbitMask(AppData* a, int mask, gboolean state);
static void setLabelMode(AppData* a, int mode, gboolean state);
#ifdef USE_FFMPEG
static const int MovieSizes[][2] =
{
{ 160, 120 },
{ 320, 240 },
{ 640, 480 },
{ 720, 480 },
{ 720, 576 },
{ 1024, 768 },
{ 1280, 720 },
{ 1920, 1080 }
};
static const float MovieFramerates[] = { 15.0f, 23.976f, 24.0f, 25.0f, 29.97f, 30.0f, 60.0f };
struct MovieCodec
{
AVCodecID codecId;
const char *codecDesc;
};
static MovieCodec MovieCodecs[2] =
{
{ AV_CODEC_ID_FFVHUFF, N_("Lossless") },
{ AV_CODEC_ID_H264, N_("Lossy (H.264)") }
};
static void insert_text_event(GtkEditable *editable, const gchar *text, gint length, gint *position, gpointer data)
{
for (int i = 0; i < length; i++)
@ -72,6 +102,7 @@ static void insert_text_event(GtkEditable *editable, const gchar *text, gint len
}
}
}
#endif
/* File -> Copy URL */
void actionCopyURL(GtkAction*, AppData* app)
@ -237,12 +268,10 @@ void actionCaptureMovie(GtkAction*, AppData* app)
gtk_box_pack_start(GTK_BOX(hbox), rlabel, TRUE, TRUE, 0);
GtkWidget* vscombo = gtk_combo_box_text_new();
auto movieSizes = app->core->getSupportedMovieSizes();
for (const auto& size : movieSizes)
for (const auto& size : MovieSizes)
{
auto s = fmt::format("{} x {}", size.width, size.height);
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(vscombo),
s.c_str());
fmt::format("{} x {}", size[0], size[1]).c_str());
}
gtk_combo_box_set_active(GTK_COMBO_BOX(vscombo), 0);
gtk_box_pack_start(GTK_BOX(hbox), vscombo, FALSE, FALSE, 0);
@ -251,8 +280,7 @@ void actionCaptureMovie(GtkAction*, AppData* app)
gtk_box_pack_start(GTK_BOX(hbox), flabel, TRUE, TRUE, 0);
GtkWidget* frcombo = gtk_combo_box_text_new();
auto movieFramerates = app->core->getSupportedMovieFramerates();
for (float i : movieFramerates)
for (float i : MovieFramerates)
{
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(frcombo),
fmt::format("{:.3f}", i).c_str());
@ -264,11 +292,10 @@ void actionCaptureMovie(GtkAction*, AppData* app)
gtk_box_pack_start(GTK_BOX(hbox), vclabel, TRUE, TRUE, 0);
GtkWidget* vccombo = gtk_combo_box_text_new();
auto movieCodecs = app->core->getSupportedMovieCodecs();
for (const auto &mcodec : movieCodecs)
for (const auto &mcodec : MovieCodecs)
{
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(vccombo),
mcodec.codecDescr);
mcodec.codecDesc);
}
gtk_combo_box_set_active(GTK_COMBO_BOX(vccombo), 0);
gtk_box_pack_start(GTK_BOX(hbox), vccombo, FALSE, FALSE, 0);
@ -290,17 +317,17 @@ void actionCaptureMovie(GtkAction*, AppData* app)
int fridx = gtk_combo_box_get_active(GTK_COMBO_BOX(frcombo));
int vcidx = gtk_combo_box_get_active(GTK_COMBO_BOX(vccombo));
const gchar *brtext = gtk_entry_get_text(GTK_ENTRY(brentry));
const auto &dim = movieSizes[vsidx];
float fps = movieFramerates[fridx];
int codec = movieCodecs[vcidx].codecId;
int64_t bitrate = 400000;
const int *resolution = MovieSizes[vsidx];
float fps = MovieFramerates[fridx];
AVCodecID codec = MovieCodecs[vcidx].codecId;
float bitrate = 400000;
const gchar *last = &brtext[gtk_entry_get_text_length(GTK_ENTRY(brentry))];
std::from_chars(brtext, last, bitrate);
gtk_widget_destroy(fs);
for (int i=0; i < 10 && gtk_events_pending ();i++)
gtk_main_iteration ();
captureMovie(filename, dim.width, dim.height, fps, codec, bitrate, app);
captureMovie(filename, resolution, fps, codec, bitrate, app);
g_free(filename);
}
else
@ -1085,17 +1112,26 @@ static void captureImage(const char* filename, AppData* app)
/* Image capturing helper called by actionCaptureImage() */
#ifdef USE_FFMPEG
static void captureMovie(const char* filename,
int width, int height,
float fps, int codec,
int64_t bitrate, AppData* app)
static void captureMovie(const char* filename, const int resolution[], float fps,
AVCodecID codec, float bitrate, AppData* app)
{
bool ok = app->core->initMovieCapture(filename,
width, height,
fps, bitrate,
codec);
if (!ok)
auto* movieCapture = new FFMPEGCapture(app->renderer);
movieCapture->setVideoCodec(codec);
movieCapture->setBitRate(bitrate);
if (codec == AV_CODEC_ID_H264)
movieCapture->setEncoderOptions(app->core->getConfig()->x264EncoderOptions);
else
movieCapture->setEncoderOptions(app->core->getConfig()->ffvhEncoderOptions);
bool success = movieCapture->start(filename, resolution[0], resolution[1], fps);
if (success)
{
app->core->initMovieCapture(movieCapture);
}
else
{
delete movieCapture;
GtkWidget* errBox = gtk_message_dialog_new(GTK_WINDOW(app->mainWindow),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,

View File

@ -1,34 +1,44 @@
#pragma once
// moviecapture.h
//
// Copyright (C) 2001, Chris Laurel <claurel@shatters.net>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include <libavformat/avformat.h>
}
#ifndef _MOVIECAPTURE_H_
#define _MOVIECAPTURE_H_
#include <string>
#include <vector>
#include <celengine/render.h>
#include <celcompat/filesystem.h>
namespace celestia
{
class MovieCapturePrivate;
class MovieCapture
{
public:
MovieCapture(const Renderer *r);
~MovieCapture();
MovieCapture(const Renderer *r) : renderer(r) {};
virtual ~MovieCapture() {};
bool start(const fs::path&, int, int, float);
bool end();
bool captureFrame();
virtual bool start(const fs::path& filename,
int width, int height,
float fps) = 0;
virtual bool end() = 0;
virtual bool captureFrame() = 0;
int getFrameCount() const;
int getWidth() const;
int getHeight() const;
float getFrameRate() const;
virtual int getFrameCount() const = 0;
virtual int getWidth() const = 0;
virtual int getHeight() const = 0;
virtual float getFrameRate() const = 0;
void setVideoCodec(AVCodecID);
void setBitRate(int64_t);
void setEncoderOptions(const std::string&);
virtual void setAspectRatio(int aspectNumerator, int aspectDenominator) = 0;
virtual void setQuality(float) = 0;
virtual void recordingStatus(bool started) = 0; /* to update UI recording status indicator */
private:
MovieCapturePrivate *d{ nullptr };
protected:
const Renderer *renderer{ nullptr };
};
}
#endif // _MOVIECAPTURE_H_

View File

@ -11,6 +11,7 @@
// of the License, or (at your option) any later version.
//#include <ctime>
#include <memory>
#include <QStandardPaths>
@ -64,6 +65,10 @@
#include <celestia/url.h>
#include "qtbookmark.h"
#ifdef USE_FFMPEG
#include "celestia/ffmpegcapture.h"
#endif
#ifndef CONFIG_DATA_DIR
#define CONFIG_DATA_DIR "./"
#endif
@ -84,6 +89,22 @@ static const int CELESTIA_MAIN_WINDOW_VERSION = 12;
static int fps_to_ms(int fps) { return fps > 0 ? 1000 / fps : 0; }
static int ms_to_fps(int ms) { return ms > 0? 1000 / ms : 0; }
#ifdef USE_FFMPEG
static const int videoSizes[][2] =
{
{ 160, 120 },
{ 320, 240 },
{ 640, 480 },
{ 720, 480 },
{ 720, 576 },
{ 1024, 768 },
{ 1280, 720 },
{ 1920, 1080 }
};
static const float videoFrameRates[] = { 15.0f, 23.976f, 24.0f, 25.0f, 29.97f, 30.0f, 60.0f };
#endif
// Progress notifier class receives update messages from CelestiaCore
// at startup. This simple implementation just forwards messages on
// to the main Celestia window.
@ -644,27 +665,20 @@ void CelestiaAppWindow::slotCaptureVideo()
QComboBox* resolutionCombo = new QComboBox(&videoInfoDialog);
layout->addWidget(new QLabel(_("Resolution:"), &videoInfoDialog), 0, 0);
layout->addWidget(resolutionCombo, 0, 1);
auto videoSizes = m_appCore->getSupportedMovieSizes();
for (const auto& size : videoSizes)
{
int w = size.width;
int h = size.height;
resolutionCombo->addItem(QString(_("%1 x %2")).arg(w).arg(h), QSize(w, h));
}
resolutionCombo->addItem(QString(_("%1 x %2")).arg(size[0]).arg(size[1]), QSize(size[0], size[1]));
QComboBox* frameRateCombo = new QComboBox(&videoInfoDialog);
layout->addWidget(new QLabel(_("Frame rate:"), &videoInfoDialog), 1, 0);
layout->addWidget(frameRateCombo, 1, 1);
auto videoFrameRates = m_appCore->getSupportedMovieFramerates();
for (float i : videoFrameRates)
frameRateCombo->addItem(QString::number(i), i);
QComboBox* codecCombo = new QComboBox(&videoInfoDialog);
layout->addWidget(new QLabel(_("Video codec:"), &videoInfoDialog), 2, 0);
layout->addWidget(codecCombo, 2, 1);
auto videoCodecs = m_appCore->getSupportedMovieCodecs();
for (const auto &c : videoCodecs)
codecCombo->addItem(_(c.codecDescr), c.codecId);
codecCombo->addItem(_("Lossless"), AV_CODEC_ID_FFVHUFF);
codecCombo->addItem(_("Lossy (H.264)"), AV_CODEC_ID_H264);
QLineEdit* bitrateEdit = new QLineEdit("400000", &videoInfoDialog);
bitrateEdit->setInputMask("D000000000");
@ -682,12 +696,24 @@ void CelestiaAppWindow::slotCaptureVideo()
{
QSize videoSize = resolutionCombo->itemData(resolutionCombo->currentIndex()).toSize();
float frameRate = frameRateCombo->itemData(frameRateCombo->currentIndex()).toFloat();
int codec = codecCombo->itemData(codecCombo->currentIndex()).toInt();
int64_t bitrate = bitrateEdit->text().toLongLong();
AVCodecID vc = static_cast<AVCodecID>(codecCombo->itemData(codecCombo->currentIndex()).toInt());
int br = bitrateEdit->text().toLongLong();
m_appCore->initMovieCapture(saveAsName.toStdString(),
videoSize.width(), videoSize.height(),
frameRate, bitrate, codec);
auto *movieCapture = new FFMPEGCapture(m_appCore->getRenderer());
movieCapture->setVideoCodec(vc);
movieCapture->setBitRate(br);
if (vc == AV_CODEC_ID_H264)
movieCapture->setEncoderOptions(m_appCore->getConfig()->x264EncoderOptions);
else
movieCapture->setEncoderOptions(m_appCore->getConfig()->ffvhEncoderOptions);
bool ok = movieCapture->start(saveAsName.toStdString(),
videoSize.width(), videoSize.height(),
frameRate);
if (ok)
m_appCore->initMovieCapture(movieCapture);
else
delete movieCapture;
}
settings.beginGroup("Preferences");

View File

@ -488,9 +488,9 @@ FONT 8, "Segoe UI", 0, 0, 0
COMBOBOX IDC_COMBO_MOVIE_SIZE, 38, 2, 82, 93, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST, WS_EX_LEFT
RTEXT "Frame rate:", IDC_STATIC, 120, 4, 74, 8, SS_RIGHT, WS_EX_LEFT
COMBOBOX IDC_COMBO_MOVIE_FRAMERATE, 196, 2, 82, 90, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST, WS_EX_LEFT
LTEXT "Codec:", IDC_STATIC, 6, 20, 30, 8, SS_LEFT, WS_EX_LEFT
COMBOBOX IDC_COMBO_MOVIE_CODEC, 38, 18, 82, 90, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST, WS_EX_LEFT
RTEXT "Bitrate:", IDC_STATIC, 120, 20, 74, 8, SS_RIGHT, WS_EX_LEFT
LTEXT "Codec:", 0, 6, 20, 30, 8, SS_LEFT, WS_EX_LEFT
COMBOBOX IDC_COMBO_MOVIE_CODEC, 38, 18, 82, 30, CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT
RTEXT "Bitrate:", 0, 120, 20, 74, 8, SS_RIGHT, WS_EX_LEFT
EDITTEXT IDC_EDIT_MOVIE_BITRATE, 196, 18, 82, 14, ES_AUTOHSCROLL | ES_NUMBER, WS_EX_LEFT
}

View File

@ -22,10 +22,8 @@
#include <process.h>
#include <time.h>
#include <windows.h>
#include <windowsx.h> // GET_X_LPARAM, GET_Y_LPARAM
#include <commctrl.h>
#include <mmsystem.h>
#include <oleidl.h> // IDropTarget
#include <commdlg.h>
#include <shellapi.h>
@ -44,6 +42,7 @@
#include <celscript/legacy/cmdparser.h>
#include "celestia/celestiacore.h"
#include "celestia/ffmpegcapture.h"
#include "celestia/helper.h"
#include "celestia/scriptmenu.h"
#include "celestia/url.h"
@ -128,6 +127,31 @@ static POINT lastMouseMove;
class WinCursorHandler;
WinCursorHandler* cursorHandler = NULL;
static int MovieSizes[8][2] = {
{ 160, 120 },
{ 320, 240 },
{ 640, 480 },
{ 720, 480 },
{ 720, 576 },
{ 1024, 768 },
{ 1280, 720 },
{ 1920, 1080 }
};
static float MovieFramerates[5] = { 15.0f, 24.0f, 25.0f, 29.97f, 30.0f };
struct MovieCodec
{
AVCodecID codecId;
const char *codecDesc;
};
static MovieCodec MovieCodecs[2] =
{
{ AV_CODEC_ID_FFVHUFF, N_("Lossless") },
{ AV_CODEC_ID_H264, N_("Lossy (H.264)") }
};
static int movieSize = 1;
static int movieFramerate = 1;
static int movieCodec = 1;
@ -417,6 +441,31 @@ static void ShowLocalTime(CelestiaCore* appCore)
}
static bool BeginMovieCapture(const Renderer* renderer,
const std::string& filename,
int width, int height,
float framerate,
AVCodecID codec,
int64_t bitrate)
{
auto* movieCapture = new FFMPEGCapture(renderer);
movieCapture->setVideoCodec(codec);
movieCapture->setBitRate(bitrate);
if (vc == AV_CODEC_ID_H264)
movieCapture->setEncoderOptions(appCore->getConfig()->x264EncoderOptions);
else
movieCapture->setEncoderOptions(appCore->getConfig()->ffvhEncoderOptions);
bool success = movieCapture->start(filename, width, height, framerate);
if (success)
appCore->initMovieCapture(movieCapture);
else
delete movieCapture;
return success;
}
static bool CopyStateURLToClipboard()
{
BOOL b;
@ -628,7 +677,6 @@ BOOL APIENTRY GLInfoProc(HWND hDlg,
}
#ifdef USE_FFMPEG
UINT CALLBACK ChooseMovieParamsProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
@ -638,32 +686,33 @@ UINT CALLBACK ChooseMovieParamsProc(HWND hDlg, UINT message,
{
char buf[100];
HWND hwnd = GetDlgItem(hDlg, IDC_COMBO_MOVIE_SIZE);
int nSizes = sizeof MovieSizes / sizeof MovieSizes[0];
auto movieSizes = appCore->getSupportedMovieSizes();
for (auto const &s : movieSizes)
for (int i = 0; i < nSizes; i++)
{
sprintf(buf, _("%d x %d"), s.width, s.height);
sprintf(buf, _("%d x %d"), MovieSizes[i][0], MovieSizes[i][1]);
SendMessage(hwnd, CB_INSERTSTRING, -1,
reinterpret_cast<LPARAM>(buf));
}
SendMessage(hwnd, CB_SETCURSEL, movieSize, 0);
hwnd = GetDlgItem(hDlg, IDC_COMBO_MOVIE_FRAMERATE);
auto movieFramerates = appCore->getSupportedMovieFramerates();
for (float fps : movieFramerates)
int nFramerates = sizeof MovieFramerates / sizeof MovieFramerates[0];
for (int i = 0; i < nFramerates; i++)
{
sprintf(buf, "%.2f", fps);
sprintf(buf, "%.2f", MovieFramerates[i]);
SendMessage(hwnd, CB_INSERTSTRING, -1,
reinterpret_cast<LPARAM>(buf));
}
SendMessage(hwnd, CB_SETCURSEL, movieFramerate, 0);
hwnd = GetDlgItem(hDlg, IDC_COMBO_MOVIE_CODEC);
auto movieCodecs = appCore->getSupportedMovieCodecs();
for (auto &c : movieCodecs)
int nCodecs = sizeof MovieCodecs / sizeof MovieCodecs[0];
for (int i = 0; i < nCodecs; i++)
{
SendMessage(hwnd, CB_INSERTSTRING, -1,
reinterpret_cast<LPARAM>(_(c.codecDescr)));
SendMessage(hwnd, CB_INSERTSTRING,
reinterpret_cast<WPARAM>(MovieCodecs[i].codecId),
reinterpret_cast<LPARAM>(_(MovieCodecs[i].codecDesc)));
}
SendMessage(hwnd, CB_SETCURSEL, movieCodec, 0);
@ -705,15 +754,14 @@ UINT CALLBACK ChooseMovieParamsProc(HWND hDlg, UINT message,
movieCodec = item;
}
}
else if (LOWORD(wParam) == IDOK)
else if (LOWORD(wParam) == IDOK IDC_EDIT_MOVIE_BITRATE)
{
char buf[24], out[24];
wchar_t wbuff[48];
int wlen = 0;
int len = GetDlgItemText(hDlg, IDC_EDIT_MOVIE_BITRATE, buf, sizeof(buf));
if (len > 0)
{
wlen = MultiByteToWideChar(CP_ACP, 0, buf, -1, wbuff, sizeof(wbuff));
int wlen = MultiByteToWideChar(CP_ACP, 0, buf, -1, wbuff, sizeof(wbuff));
WideCharToMultiByte(CP_UTF8, 0, wbuff, wlen, out, sizeof(out), NULL, NULL);
}
@ -728,7 +776,6 @@ UINT CALLBACK ChooseMovieParamsProc(HWND hDlg, UINT message,
return FALSE;
}
#endif
BOOL APIENTRY FindObjectProc(HWND hDlg,
@ -2691,7 +2738,6 @@ static void HandleCaptureImage(HWND hWnd)
}
#ifdef USE_FFMPEG
static void HandleCaptureMovie(HWND hWnd)
{
// TODO: The menu item should be disable so that the user doesn't even
@ -2785,15 +2831,13 @@ static void HandleCaptureMovie(HWND hWnd)
}
else
{
auto movieSizes = appCore->getSupportedMovieSizes();
auto movieFramerates = appCore->getSupportedMovieFramerates();
auto movieCodecs = appCore->getSupportedMovieCodecs();
success = appCore->initMovieCapture(Ofn.lpstrFile,
movieSizes[movieSize].width,
movieSizes[movieSize].height,
movieFramerates[movieFramerate],
movieBitrate,
movieCodecs[movieCodec].codecId);
success = BeginMovieCapture(appCore->getRenderer(),
string(Ofn.lpstrFile),
MovieSizes[movieSize][0],
MovieSizes[movieSize][1],
MovieFramerates[movieFramerate],
MovieCodecs[movieCodec],
movieBitrate);
}
if (!success)
@ -2809,7 +2853,6 @@ static void HandleCaptureMovie(HWND hWnd)
}
}
}
#endif
static void HandleOpenScript(HWND hWnd, CelestiaCore* appCore)
@ -4268,11 +4311,9 @@ LRESULT CALLBACK MainWindowProc(HWND hWnd,
HandleCaptureImage(hWnd);
break;
#ifdef USE_FFMPEG
case ID_FILE_CAPTUREMOVIE:
HandleCaptureMovie(hWnd);
break;
#endif
case ID_FILE_EXIT:
SendMessage(hWnd, WM_CLOSE, 0, 0);