forked from 0ad/0ad
# Added basic video recording.
* Included FFmpeg library for video encoding. (LGPL, dynamically linked.) * Added 'record' button to cinematics - plays the selected track at a fixed framerate, and saves the output to a video file (preferably MPEG4). Slightly lacking in reliability and features (fixed at 640x480, no choice of codec, probably crashes horribly if any problems occur while encoding, etc). * Moved icon bitmaps out of mods directory. Added 'record' icon. This was SVN commit r4676.
This commit is contained in:
parent
6ed4ad6519
commit
2c71c22045
BIN
binaries/data/tools/atlas/buttons/next_s.bmp
Normal file
BIN
binaries/data/tools/atlas/buttons/next_s.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 822 B |
BIN
binaries/data/tools/atlas/buttons/pause_s.bmp
Normal file
BIN
binaries/data/tools/atlas/buttons/pause_s.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 822 B |
BIN
binaries/data/tools/atlas/buttons/play_s.bmp
Normal file
BIN
binaries/data/tools/atlas/buttons/play_s.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 822 B |
BIN
binaries/data/tools/atlas/buttons/previous_s.bmp
Normal file
BIN
binaries/data/tools/atlas/buttons/previous_s.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 822 B |
BIN
binaries/data/tools/atlas/buttons/record_s.bmp
Normal file
BIN
binaries/data/tools/atlas/buttons/record_s.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 822 B |
BIN
binaries/data/tools/atlas/buttons/stop_s.bmp
Normal file
BIN
binaries/data/tools/atlas/buttons/stop_s.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 822 B |
@ -62,6 +62,10 @@ extern_lib_defs = {
|
||||
win_names = { "ddraw", "dsound" },
|
||||
dbg_suffix = "",
|
||||
},
|
||||
ffmpeg = {
|
||||
win_names = { "avcodec-51", "avformat-51", "avutil-49" },
|
||||
dbg_suffix = "",
|
||||
},
|
||||
libjpg = {
|
||||
win_names = { "jpeg-6b" },
|
||||
unix_names = { "jpeg" },
|
||||
@ -95,8 +99,9 @@ extern_lib_defs = {
|
||||
tinsert(package.includepaths, libraries_dir.."wxwidgets/include/msvc")
|
||||
tinsert(package.includepaths, libraries_dir.."wxwidgets/include")
|
||||
tinsert(package.libpaths, libraries_dir.."wxwidgets/lib/vc_lib")
|
||||
package.config["Debug" ].links = { "wxmsw26ud_gl" }
|
||||
package.config["Release"].links = { "wxmsw26u_gl" }
|
||||
tinsert(package.config["Debug" ].links, "wxmsw26ud_gl")
|
||||
tinsert(package.config["Testing"].links, "wxmsw26ud_gl")
|
||||
tinsert(package.config["Release"].links, "wxmsw26u_gl")
|
||||
else
|
||||
tinsert(package.buildoptions, "`wx-config --cxxflags`")
|
||||
tinsert(package.linkoptions, "`wx-config --libs std,gl,ogl,media`")
|
||||
|
@ -477,7 +477,7 @@ local function setup_atlas_package(package_name, target_type, rel_source_dirs, r
|
||||
tinsert(package.defines, "_UNICODE")
|
||||
|
||||
-- Link to required libraries
|
||||
package.links = { "winmm", "comctl32", "rpcrt4" }
|
||||
package.links = { "winmm", "comctl32", "rpcrt4", "delayimp" }
|
||||
|
||||
-- required to use WinMain() on Windows, otherwise will default to main()
|
||||
tinsert(package.buildflags, "no-main")
|
||||
@ -525,6 +525,7 @@ function setup_atlas_packages()
|
||||
"CustomControls/Windows",
|
||||
"FileConverter",
|
||||
"General",
|
||||
"General/VideoRecorder",
|
||||
"Misc",
|
||||
"ScenarioEditor",
|
||||
"ScenarioEditor/Sections/Common",
|
||||
@ -543,8 +544,9 @@ function setup_atlas_packages()
|
||||
},{ -- extern_libs
|
||||
"boost",
|
||||
"devil",
|
||||
"xerces",
|
||||
"wxwidgets"
|
||||
"ffmpeg",
|
||||
"wxwidgets",
|
||||
"xerces"
|
||||
},{ -- extra_params
|
||||
pch = 1,
|
||||
extra_links = { "AtlasObject", "DatafileIO" },
|
||||
|
@ -247,7 +247,7 @@ void WriteBigScreenshot(const char* extension, int tiles)
|
||||
g_Renderer.Resize(tile_w, tile_h);
|
||||
SViewPort vp = { 0, 0, tile_w, tile_h };
|
||||
g_Game->GetView()->GetCamera()->SetViewPort(&vp);
|
||||
g_Game->GetView()->GetCamera()->SetProjection (CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
|
||||
g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
|
||||
}
|
||||
|
||||
// Temporarily move everything onto the front buffer, so the user can
|
||||
@ -290,7 +290,7 @@ void WriteBigScreenshot(const char* extension, int tiles)
|
||||
g_Renderer.Resize(g_xres, g_yres);
|
||||
SViewPort vp = { 0, 0, g_xres, g_yres };
|
||||
g_Game->GetView()->GetCamera()->SetViewPort(&vp);
|
||||
g_Game->GetView()->GetCamera()->SetProjection (1, 5000, DEGTORAD(20));
|
||||
g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
|
||||
|
||||
g_Game->GetView()->GetCamera()->SetProjectionTile(1, 0, 0);
|
||||
}
|
||||
|
391
source/tools/atlas/AtlasUI/General/VideoRecorder/FFmpeg.cpp
Normal file
391
source/tools/atlas/AtlasUI/General/VideoRecorder/FFmpeg.cpp
Normal file
@ -0,0 +1,391 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
/*
|
||||
|
||||
Code originally taken from ffmpeg's output_example.c
|
||||
This is all rather hacked together and unreliable. In particular, I should:
|
||||
* Change the fprintf/exit error handling so that it works in Atlas.
|
||||
* Send all the logging output to wx too.
|
||||
* Support variable bitrate (set qscale to 1 (best) .. 31 (worst), sameq)
|
||||
* See if other codecs (particularly lossless ones) could be made to work.
|
||||
(Currently it half assumes that it's passed a .mp4 filename.)
|
||||
* See if other compression parameters would give better quality/speed/etc.
|
||||
* Make the frame size variable.
|
||||
* Tidy everything up a bit.
|
||||
|
||||
Please complain if I forget to do those things.
|
||||
|
||||
*/
|
||||
|
||||
#include "FFmpeg.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(disable: 4100 4505 4510 4610)
|
||||
#endif
|
||||
|
||||
#include "ffmpeg/avformat.h"
|
||||
#include "ffmpeg/swscale.h"
|
||||
|
||||
struct VideoEncoderImpl
|
||||
{
|
||||
int framerate;
|
||||
int bitrate;
|
||||
float duration;
|
||||
|
||||
AVStream *video_st;
|
||||
AVFormatContext *oc;
|
||||
AVOutputFormat *fmt;
|
||||
|
||||
AVFrame *picture, *tmp_picture;
|
||||
uint8_t *video_outbuf;
|
||||
int frame_count, video_outbuf_size;
|
||||
|
||||
VideoEncoderImpl()
|
||||
{
|
||||
video_st = NULL;
|
||||
oc = NULL;
|
||||
fmt = NULL;
|
||||
|
||||
picture = NULL;
|
||||
tmp_picture = NULL;
|
||||
video_outbuf = NULL;
|
||||
frame_count = 0;
|
||||
video_outbuf_size = 0;
|
||||
}
|
||||
|
||||
AVStream *add_video_stream(AVFormatContext *oc, int codec_id)
|
||||
{
|
||||
AVCodecContext *c;
|
||||
AVStream *st;
|
||||
|
||||
st = av_new_stream(oc, 0);
|
||||
if (!st) {
|
||||
fprintf(stderr, "Could not alloc stream\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
c = st->codec;
|
||||
c->codec_id = (CodecID)codec_id;
|
||||
c->codec_type = CODEC_TYPE_VIDEO;
|
||||
|
||||
c->bit_rate = bitrate*1000;
|
||||
c->width = 640;
|
||||
c->height = 480;
|
||||
|
||||
c->time_base.den = framerate;
|
||||
c->time_base.num = 1;
|
||||
|
||||
c->pix_fmt = PIX_FMT_YUV420P;
|
||||
|
||||
// TODO: these compression parameters aren't necessarily any good
|
||||
|
||||
c->mb_decision = FF_MB_DECISION_RD;
|
||||
c->gop_size = 250;
|
||||
c->max_b_frames = 2;
|
||||
c->b_frame_strategy = 1;
|
||||
c->dia_size = 4;
|
||||
|
||||
c->me_cmp = c->me_sub_cmp= FF_CMP_SAD;
|
||||
c->mb_cmp= FF_CMP_SSE;
|
||||
|
||||
c->last_predictor_count = 3;
|
||||
|
||||
c->flags |= CODEC_FLAG_4MV | CODEC_FLAG_QP_RD;
|
||||
|
||||
|
||||
if(!strcmp(oc->oformat->name, "mp4") || !strcmp(oc->oformat->name, "mov") || !strcmp(oc->oformat->name, "3gp"))
|
||||
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
AVFrame *alloc_picture(int pix_fmt, int width, int height)
|
||||
{
|
||||
AVFrame *picture;
|
||||
uint8_t *picture_buf;
|
||||
int size;
|
||||
|
||||
picture = avcodec_alloc_frame();
|
||||
if (!picture)
|
||||
return NULL;
|
||||
size = avpicture_get_size(pix_fmt, width, height);
|
||||
picture_buf = (uint8_t*)av_malloc(size);
|
||||
if (!picture_buf) {
|
||||
av_free(picture);
|
||||
return NULL;
|
||||
}
|
||||
avpicture_fill((AVPicture *)picture, picture_buf,
|
||||
pix_fmt, width, height);
|
||||
return picture;
|
||||
}
|
||||
|
||||
void open_video(AVFormatContext *oc, AVStream *st)
|
||||
{
|
||||
AVCodec *codec;
|
||||
AVCodecContext *c;
|
||||
|
||||
c = st->codec;
|
||||
|
||||
/* find the video encoder */
|
||||
codec = avcodec_find_encoder(c->codec_id);
|
||||
if (!codec) {
|
||||
fprintf(stderr, "codec not found\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* open the codec */
|
||||
if (avcodec_open(c, codec) < 0) {
|
||||
fprintf(stderr, "could not open codec\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
video_outbuf = NULL;
|
||||
if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
|
||||
/* allocate output buffer */
|
||||
/* XXX: API change will be done */
|
||||
/* buffers passed into lav* can be allocated any way you prefer,
|
||||
as long as they're aligned enough for the architecture, and
|
||||
they're freed appropriately (such as using av_free for buffers
|
||||
allocated with av_malloc) */
|
||||
video_outbuf_size = bitrate*1000/framerate * 8;
|
||||
video_outbuf = (uint8_t*)av_malloc(video_outbuf_size);
|
||||
}
|
||||
|
||||
/* allocate the encoded raw picture */
|
||||
picture = alloc_picture(c->pix_fmt, c->width, c->height);
|
||||
if (!picture) {
|
||||
fprintf(stderr, "Could not allocate picture\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* if the output format is not YUV420P, then a temporary YUV420P
|
||||
picture is needed too. It is then converted to the required
|
||||
output format */
|
||||
tmp_picture = NULL;
|
||||
if (c->pix_fmt != PIX_FMT_RGB24) {
|
||||
tmp_picture = alloc_picture(PIX_FMT_RGB24, c->width, c->height);
|
||||
if (!tmp_picture) {
|
||||
fprintf(stderr, "Could not allocate temporary picture\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void copy_rgb_image(AVFrame *pict, int width, int height, const unsigned char* buffer)
|
||||
{
|
||||
for(int y=0;y<height;y++) {
|
||||
memcpy(&pict->data[0][y * pict->linesize[0]], buffer+y*width*3, width*3);
|
||||
}
|
||||
}
|
||||
|
||||
void write_video_frame(AVFormatContext *oc, AVStream *st, const unsigned char* buffer)
|
||||
{
|
||||
AVCodecContext *c;
|
||||
static struct SwsContext *img_convert_ctx;
|
||||
int out_size, ret;
|
||||
|
||||
c = st->codec;
|
||||
|
||||
if (!buffer || frame_count >= (int)(duration*framerate)) {
|
||||
/* no more frame to compress. The codec has a latency of a few
|
||||
frames if using B frames, so we get the last frames by
|
||||
passing the same picture again */
|
||||
} else {
|
||||
if (c->pix_fmt != PIX_FMT_RGB24) {
|
||||
/* as we only generate a YUV420P picture, we must convert it
|
||||
to the codec pixel format if needed */
|
||||
if (img_convert_ctx == NULL) {
|
||||
img_convert_ctx = sws_getContext(c->width, c->height,
|
||||
PIX_FMT_RGB24,
|
||||
c->width, c->height,
|
||||
c->pix_fmt,
|
||||
SWS_BICUBIC, NULL, NULL, NULL);
|
||||
if (img_convert_ctx == NULL) {
|
||||
fprintf(stderr, "Cannot initialize the conversion context\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
copy_rgb_image(tmp_picture, c->width, c->height, buffer);
|
||||
sws_scale(img_convert_ctx, tmp_picture->data, tmp_picture->linesize,
|
||||
0, c->height, picture->data, picture->linesize);
|
||||
} else {
|
||||
copy_rgb_image(picture, c->width, c->height, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (oc->oformat->flags & AVFMT_RAWPICTURE) {
|
||||
/* raw video case. The API will change slightly in the near
|
||||
futur for that */
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
|
||||
pkt.flags |= PKT_FLAG_KEY;
|
||||
pkt.stream_index= st->index;
|
||||
pkt.data= (uint8_t *)picture;
|
||||
pkt.size= sizeof(AVPicture);
|
||||
|
||||
ret = av_write_frame(oc, &pkt);
|
||||
} else {
|
||||
/* encode the image */
|
||||
out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture);
|
||||
/* if zero size, it means the image was buffered */
|
||||
if (out_size > 0) {
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
|
||||
pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);
|
||||
if(c->coded_frame->key_frame)
|
||||
pkt.flags |= PKT_FLAG_KEY;
|
||||
pkt.stream_index= st->index;
|
||||
pkt.data= video_outbuf;
|
||||
pkt.size= out_size;
|
||||
|
||||
/* write the compressed frame in the media file */
|
||||
ret = av_write_frame(oc, &pkt);
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error while writing video frame\n");
|
||||
exit(1);
|
||||
}
|
||||
frame_count++;
|
||||
}
|
||||
|
||||
void close_video(AVFormatContext *oc, AVStream *st)
|
||||
{
|
||||
avcodec_close(st->codec);
|
||||
av_free(picture->data[0]);
|
||||
av_free(picture);
|
||||
if (tmp_picture) {
|
||||
av_free(tmp_picture->data[0]);
|
||||
av_free(tmp_picture);
|
||||
}
|
||||
av_free(video_outbuf);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void log(void* v, int i, const char* format, va_list ap)
|
||||
{
|
||||
char buf[512];
|
||||
vsnprintf(buf, sizeof(buf), format, ap);
|
||||
buf[sizeof(buf)-1] = '\0';
|
||||
wxLogDebug(L"[%d] %hs", i, buf);
|
||||
}
|
||||
|
||||
VideoEncoder::VideoEncoder(const wxString& filenameStr, int framerate, int bitrate, float duration)
|
||||
: m(new VideoEncoderImpl)
|
||||
{
|
||||
wxCharBuffer filename = filenameStr.ToAscii();
|
||||
m->framerate = framerate;
|
||||
m->bitrate = bitrate;
|
||||
m->duration = duration;
|
||||
|
||||
/* initialize libavcodec, and register all codecs and formats */
|
||||
av_register_all();
|
||||
|
||||
av_log_set_callback(&log);
|
||||
|
||||
/* auto detect the output format from the name. default is mpeg. */
|
||||
m->fmt = guess_format(NULL, filename, NULL);
|
||||
if (!m->fmt) {
|
||||
printf("Could not deduce output format from file extension: using MPEG.\n");
|
||||
m->fmt = guess_format("mpeg", NULL, NULL);
|
||||
}
|
||||
if (!m->fmt) {
|
||||
fprintf(stderr, "Could not find suitable output format\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* allocate the output media context */
|
||||
m->oc = av_alloc_format_context();
|
||||
if (!m->oc) {
|
||||
fprintf(stderr, "Memory error\n");
|
||||
exit(1);
|
||||
}
|
||||
m->oc->oformat = m->fmt;
|
||||
strncpy(m->oc->filename, filename, sizeof(m->oc->filename));
|
||||
m->oc->filename[sizeof(m->oc->filename) - 1] = '\0';
|
||||
|
||||
/* add the audio and video streams using the default format codecs
|
||||
and initialize the codecs */
|
||||
m->video_st = NULL;
|
||||
if (m->fmt->video_codec != CODEC_ID_NONE) {
|
||||
m->video_st = m->add_video_stream(m->oc, m->fmt->video_codec);
|
||||
}
|
||||
|
||||
/* set the output parameters (must be done even if no
|
||||
parameters). */
|
||||
if (av_set_parameters(m->oc, NULL) < 0) {
|
||||
fprintf(stderr, "Invalid output format parameters\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dump_format(m->oc, 0, filename, 1);
|
||||
|
||||
/* now that all the parameters are set, we can open the audio and
|
||||
video codecs and allocate the necessary encode buffers */
|
||||
if (m->video_st)
|
||||
m->open_video(m->oc, m->video_st);
|
||||
|
||||
/* open the output file, if needed */
|
||||
if (!(m->fmt->flags & AVFMT_NOFILE)) {
|
||||
if (url_fopen(&m->oc->pb, filename, URL_WRONLY) < 0) {
|
||||
fprintf(stderr, "Could not open '%s'\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* write the stream header, if any */
|
||||
av_write_header(m->oc);
|
||||
|
||||
}
|
||||
|
||||
void VideoEncoder::Frame(const unsigned char* buffer)
|
||||
{
|
||||
double video_pts = (double)m->video_st->pts.val * m->video_st->time_base.num / m->video_st->time_base.den;
|
||||
if (video_pts >= m->duration)
|
||||
return;
|
||||
|
||||
m->write_video_frame(m->oc, m->video_st, buffer);
|
||||
}
|
||||
|
||||
VideoEncoder::~VideoEncoder()
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
double video_pts = (double)m->video_st->pts.val * m->video_st->time_base.num / m->video_st->time_base.den;
|
||||
if (video_pts >= m->duration)
|
||||
break;
|
||||
|
||||
m->write_video_frame(m->oc, m->video_st, NULL);
|
||||
}
|
||||
|
||||
|
||||
/* close each codec */
|
||||
if (m->video_st)
|
||||
m->close_video(m->oc, m->video_st);
|
||||
|
||||
/* write the trailer, if any */
|
||||
av_write_trailer(m->oc);
|
||||
|
||||
/* free the streams */
|
||||
for(int i = 0; i < m->oc->nb_streams; i++) {
|
||||
av_freep(&m->oc->streams[i]->codec);
|
||||
av_freep(&m->oc->streams[i]);
|
||||
}
|
||||
|
||||
if (!(m->fmt->flags & AVFMT_NOFILE)) {
|
||||
/* close the output file */
|
||||
url_fclose(&m->oc->pb);
|
||||
}
|
||||
|
||||
/* free the stream */
|
||||
av_free(m->oc);
|
||||
|
||||
|
||||
delete m;
|
||||
}
|
17
source/tools/atlas/AtlasUI/General/VideoRecorder/FFmpeg.h
Normal file
17
source/tools/atlas/AtlasUI/General/VideoRecorder/FFmpeg.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef FFMPEG_H__
|
||||
#define FFMPEG_H__
|
||||
|
||||
struct VideoEncoderImpl;
|
||||
|
||||
class VideoEncoder
|
||||
{
|
||||
public:
|
||||
VideoEncoder(const wxString& filename, int framerate, int bitrate, float duration);
|
||||
void Frame(const unsigned char* buffer);
|
||||
~VideoEncoder();
|
||||
|
||||
private:
|
||||
VideoEncoderImpl* m;
|
||||
};
|
||||
|
||||
#endif // FFMPEG_H__
|
@ -0,0 +1,187 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "VideoRecorder.h"
|
||||
|
||||
#include "FFmpeg.h"
|
||||
|
||||
#include "GameInterface/Messages.h"
|
||||
|
||||
using namespace AtlasMessage;
|
||||
|
||||
class RecorderDialog : public wxDialog
|
||||
{
|
||||
enum
|
||||
{
|
||||
ID_FILENAME,
|
||||
ID_FILECHOOSE,
|
||||
ID_FRAMERATE,
|
||||
ID_BITRATE,
|
||||
ID_DURATION,
|
||||
ID_FILESIZE
|
||||
};
|
||||
|
||||
public:
|
||||
RecorderDialog(wxWindow* parent, float duration)
|
||||
: wxDialog(parent, wxID_ANY, (wxString)_("Video recording options")),
|
||||
m_Duration(duration)
|
||||
{
|
||||
wxArrayString framerates;
|
||||
framerates.Add(_T("10"));
|
||||
framerates.Add(_T("15"));
|
||||
framerates.Add(_T("25"));
|
||||
framerates.Add(_T("30"));
|
||||
framerates.Add(_T("60"));
|
||||
wxString framerateDefault = _T("25");
|
||||
|
||||
wxArrayString bitrates;
|
||||
bitrates.Add(_T("500"));
|
||||
bitrates.Add(_T("800"));
|
||||
bitrates.Add(_T("1200"));
|
||||
bitrates.Add(_T("1600"));
|
||||
bitrates.Add(_T("3200"));
|
||||
wxString bitrateDefault = _T("1200");
|
||||
|
||||
|
||||
wxSizer* filenameSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
filenameSizer->Add(new wxTextCtrl(this, ID_FILENAME, _T("")), wxSizerFlags().Proportion(1));
|
||||
filenameSizer->Add(new wxButton(this, ID_FILECHOOSE, _("Browse...")));
|
||||
|
||||
wxGridSizer* gridSizer = new wxGridSizer(4);
|
||||
|
||||
gridSizer->Add(new wxStaticText(this, -1, _("Framerate (fps)")), wxSizerFlags().Right().Border(wxRIGHT, 4));
|
||||
gridSizer->Add(new wxComboBox(this, ID_FRAMERATE, framerateDefault, wxDefaultPosition, wxDefaultSize, framerates), wxSizerFlags().Proportion(1).Expand());
|
||||
// TODO: use a wxValidator to only allow digits
|
||||
|
||||
gridSizer->Add(new wxStaticText(this, -1, _("Duration (seconds):")), wxSizerFlags().Right().Border(wxLEFT, 4));
|
||||
gridSizer->Add(new wxStaticText(this, ID_DURATION, wxString::Format(_T("%.1f"), m_Duration)), wxSizerFlags().Expand().Border(wxLEFT, 4));
|
||||
|
||||
gridSizer->Add(new wxStaticText(this, -1, _("Bitrate (Kbit/s)")), wxSizerFlags().Right().Border(wxRIGHT, 4));
|
||||
gridSizer->Add(new wxComboBox(this, ID_BITRATE, bitrateDefault, wxDefaultPosition, wxDefaultSize, bitrates), wxSizerFlags().Proportion(1).Expand());
|
||||
|
||||
gridSizer->Add(new wxStaticText(this, -1, _("Estimated file size:")), wxSizerFlags().Right().Border(wxRIGHT, 4));
|
||||
gridSizer->Add(new wxStaticText(this, ID_FILESIZE, L"???"), wxSizerFlags().Expand().Border(wxLEFT, 4));
|
||||
|
||||
RecalculateSizes_();
|
||||
|
||||
wxSizer* gridSizerBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Compression options"));
|
||||
gridSizerBox->Add(gridSizer, wxSizerFlags().Border(wxALL, 10));
|
||||
|
||||
wxStdDialogButtonSizer* buttonSizer = new wxStdDialogButtonSizer();
|
||||
wxButton* okButton = new wxButton(this, wxID_OK, _("Save"));
|
||||
okButton->SetDefault();
|
||||
buttonSizer->AddButton(okButton);
|
||||
buttonSizer->AddButton(new wxButton(this, wxID_CANCEL, _("Cancel")));
|
||||
buttonSizer->Realize();
|
||||
|
||||
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
sizer->Add(filenameSizer, wxSizerFlags().Border(wxALL, 10).Expand());
|
||||
sizer->Add(gridSizerBox, wxSizerFlags().Border(wxALL, 10));
|
||||
sizer->Add(buttonSizer, wxSizerFlags().Border(wxALL, 10).Centre());
|
||||
|
||||
SetSizer(sizer);
|
||||
sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
// Outputs:
|
||||
wxString m_Filename;
|
||||
unsigned long m_Framerate;
|
||||
unsigned long m_Bitrate;
|
||||
|
||||
protected:
|
||||
|
||||
void OnOK(wxCommandEvent& event)
|
||||
{
|
||||
wxTextCtrl* filenameWin = wxDynamicCast(FindWindow(ID_FILENAME), wxTextCtrl);
|
||||
wxComboBox* framerateWin = wxDynamicCast(FindWindow(ID_FRAMERATE), wxComboBox);
|
||||
wxComboBox* bitrateWin = wxDynamicCast(FindWindow(ID_BITRATE), wxComboBox);
|
||||
|
||||
wxCHECK(filenameWin && framerateWin && bitrateWin, );
|
||||
|
||||
m_Filename = filenameWin->GetValue();
|
||||
if (m_Filename.IsEmpty())
|
||||
{
|
||||
wxLogError(_("No filename specified."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!framerateWin->GetValue().ToULong(&m_Framerate) ||
|
||||
!bitrateWin->GetValue().ToULong(&m_Bitrate))
|
||||
{
|
||||
wxLogError(_("Invalid framerate/bitrate."));
|
||||
return;
|
||||
}
|
||||
|
||||
wxDialog::OnOK(event);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void RecalculateSizes(wxCommandEvent& WXUNUSED(event)) { RecalculateSizes_(); }
|
||||
void RecalculateSizes_()
|
||||
{
|
||||
wxComboBox* framerateWin = wxDynamicCast(FindWindow(ID_FRAMERATE), wxComboBox);
|
||||
wxComboBox* bitrateWin = wxDynamicCast(FindWindow(ID_BITRATE), wxComboBox);
|
||||
wxStaticText* filesizeWin = wxDynamicCast(FindWindow(ID_FILESIZE), wxStaticText);
|
||||
wxCHECK(framerateWin && bitrateWin && filesizeWin, );
|
||||
unsigned long framerate = 0, bitrate = 0;
|
||||
if (!framerateWin->GetValue().ToULong(&framerate) || !bitrateWin->GetValue().ToULong(&bitrate))
|
||||
return;
|
||||
|
||||
int size = m_Duration * bitrate * 1000/8;
|
||||
wxString sizeStr;
|
||||
if (size < 1024*1024) sizeStr = wxString::Format(_T("~%.0f KiB"), (float)size/1024);
|
||||
else if (size < 1024*1024*100) sizeStr = wxString::Format(_T("~%.1f MiB"), (float)size/(1024*1024));
|
||||
else if (size < 1024*1024*1024) sizeStr = wxString::Format(_T("~%.0f MiB"), (float)size/(1024*1024));
|
||||
else sizeStr = wxString::Format(_T("~%.2f GiB"), (float)size/(1024*1024*1024));
|
||||
filesizeWin->SetTitle(sizeStr);
|
||||
}
|
||||
|
||||
void FileBrowse(wxCommandEvent& WXUNUSED(event))
|
||||
{
|
||||
wxTextCtrl* filenameWin = wxDynamicCast(FindWindow(ID_FILENAME), wxTextCtrl);
|
||||
wxCHECK(filenameWin, );
|
||||
|
||||
wxFileDialog dlg (this, wxFileSelectorPromptStr, filenameWin->GetValue(), filenameWin->GetValue(), _("MP4 files (*.mp4)|*.mp4"), wxSAVE);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
filenameWin->SetValue(dlg.GetPath());
|
||||
}
|
||||
|
||||
float m_Duration;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
BEGIN_EVENT_TABLE(RecorderDialog, wxDialog)
|
||||
EVT_COMBOBOX(ID_FRAMERATE, RecorderDialog::RecalculateSizes)
|
||||
EVT_COMBOBOX(ID_BITRATE, RecorderDialog::RecalculateSizes)
|
||||
EVT_TEXT(ID_FRAMERATE, RecorderDialog::RecalculateSizes)
|
||||
EVT_TEXT(ID_BITRATE, RecorderDialog::RecalculateSizes)
|
||||
EVT_BUTTON(ID_FILECHOOSE, RecorderDialog::FileBrowse)
|
||||
EVT_BUTTON(wxID_OK, RecorderDialog::OnOK)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
|
||||
void callback(const sCinemaRecordCB* data, void* cbdata)
|
||||
{
|
||||
VideoEncoder* enc = (VideoEncoder*)cbdata;
|
||||
enc->Frame(data->buffer);
|
||||
}
|
||||
|
||||
void VideoRecorder::RecordCinematic(wxWindow* window, const wxString& trackName, float duration)
|
||||
{
|
||||
RecorderDialog dlg(window, duration);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
wxStopWatch sw;
|
||||
|
||||
VideoEncoder venc (dlg.m_Filename, dlg.m_Framerate, dlg.m_Bitrate, duration);
|
||||
|
||||
qCinemaRecord qry(trackName.c_str(), dlg.m_Framerate, duration, Callback<sCinemaRecordCB>(&callback, (void*)&venc));
|
||||
qry.Post();
|
||||
|
||||
wxLogMessage(_("Finished recording (took %.1f seconds)\n"), sw.Time()/1000.f);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
#ifndef VIDEORECORDER_H__
|
||||
#define VIDEORECORDER_H__
|
||||
|
||||
class VideoRecorder
|
||||
{
|
||||
public:
|
||||
static void RecordCinematic(wxWindow* window, const wxString& trackName, float duration);
|
||||
};
|
||||
|
||||
#endif // VIDEORECORDER_H__
|
@ -9,6 +9,8 @@
|
||||
#include "ScenarioEditor/Tools/Common/Tools.h"
|
||||
#include "HighResTimer/HighResTimer.h"
|
||||
|
||||
#include "General/VideoRecorder/VideoRecorder.h"
|
||||
|
||||
#include "wx/spinctrl.h"
|
||||
#include "wx/filename.h"
|
||||
#include "wx/wfstream.h"
|
||||
@ -23,7 +25,7 @@ struct eCinemaButton
|
||||
{
|
||||
enum {
|
||||
previous/*, rewind, reverse*/, stop,
|
||||
play, pause, /*forward,*/ next };
|
||||
play, pause, /*forward,*/ next, record };
|
||||
};
|
||||
|
||||
float CinemaTextFloat(wxTextCtrl&, size_t, float, float, float);
|
||||
@ -925,6 +927,17 @@ public:
|
||||
|
||||
m_Parent->m_Playing = true;
|
||||
}
|
||||
void OnRecord(wxCommandEvent& WXUNUSED(event))
|
||||
{
|
||||
if ( m_Parent->m_SelectedTrack < 0 )
|
||||
return;
|
||||
m_Parent->m_SliderBox->m_Timer.Stop();
|
||||
m_Parent->m_SliderBox->PrepareTimers();
|
||||
|
||||
VideoRecorder::RecordCinematic(this,
|
||||
m_Parent->m_Tracks[m_Parent->m_SelectedTrack].name.c_str(),
|
||||
m_Parent->m_Tracks[m_Parent->m_SelectedTrack].duration);
|
||||
}
|
||||
void OnPause(wxCommandEvent& WXUNUSED(event))
|
||||
{
|
||||
if ( m_Parent->m_SelectedTrack < 0 )
|
||||
@ -980,6 +993,7 @@ BEGIN_EVENT_TABLE(CinemaButtonBox, wxPanel)
|
||||
EVT_BUTTON(eCinemaButton::pause, CinemaButtonBox::OnPause)
|
||||
// EVT_BUTTON(eCinemaButton::forward, CinemaButtonBox::OnForward)
|
||||
EVT_BUTTON(eCinemaButton::next, CinemaButtonBox::OnNext)
|
||||
EVT_BUTTON(eCinemaButton::record, CinemaButtonBox::OnRecord)
|
||||
END_EVENT_TABLE()
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -1126,7 +1140,7 @@ wxImage CinematicSidebar::LoadIcon(const wxString& filename)
|
||||
wxImage img (1, 1, true);
|
||||
|
||||
// Load the icon
|
||||
wxFileName iconPath (_T("mods/official/art/textures/ui/session/icons/single/atlas/"));
|
||||
wxFileName iconPath (_T("tools/atlas/buttons/"));
|
||||
iconPath.MakeAbsolute(Datafile::GetDataDirectory());
|
||||
iconPath.SetFullName(filename);
|
||||
wxFileInputStream fstr (iconPath.GetFullPath());
|
||||
@ -1162,6 +1176,9 @@ void CinematicSidebar::LoadIcons()
|
||||
wxBitmapButton* next = new wxBitmapButton(m_IconSizer,
|
||||
eCinemaButton::next, LoadIcon( _T("next_s.bmp") ));
|
||||
m_IconSizer->Add(next);
|
||||
wxBitmapButton* record = new wxBitmapButton(m_IconSizer,
|
||||
eCinemaButton::record, LoadIcon( _T("record_s.bmp") ));
|
||||
m_IconSizer->Add(record);
|
||||
}
|
||||
void CinematicSidebar::AddTrack(float x, float y, float z,
|
||||
std::wstring& name, int count)
|
||||
|
@ -94,13 +94,6 @@ private:
|
||||
CinemaInfoBox* m_InfoBox; // ^same^
|
||||
CinematicBottomBar* m_CinemaBottomBar;
|
||||
|
||||
struct eCinemaButton
|
||||
{
|
||||
enum {
|
||||
previous/*, rewind, reverse*/, stop,
|
||||
play, pause, /*forward,*/ next };
|
||||
};
|
||||
|
||||
wxImage LoadIcon(const wxString& filename);
|
||||
void LoadIcons();
|
||||
};
|
||||
|
@ -6,6 +6,13 @@
|
||||
#include "ps/GameSetup/Config.h"
|
||||
#include "ps/Util.h"
|
||||
|
||||
#include "ps/Game.h"
|
||||
#include "renderer/Renderer.h"
|
||||
#include "ps/GameSetup/GameSetup.h"
|
||||
#include "../GameLoop.h"
|
||||
|
||||
extern void (*Atlas_GLSwapBuffers)(void* context);
|
||||
|
||||
namespace AtlasMessage {
|
||||
|
||||
MESSAGEHANDLER(MessageTrace)
|
||||
@ -19,6 +26,57 @@ MESSAGEHANDLER(Screenshot)
|
||||
WriteBigScreenshot("bmp", msg->tiles);
|
||||
}
|
||||
|
||||
QUERYHANDLER(CinemaRecord)
|
||||
{
|
||||
CCinemaManager* manager = g_Game->GetView()->GetCinema();
|
||||
manager->SetCurrentTrack(*msg->track, false, false, false);
|
||||
|
||||
const int w = 640, h = 480;
|
||||
{
|
||||
g_Renderer.Resize(w, h);
|
||||
SViewPort vp = { 0, 0, w, h };
|
||||
g_Game->GetView()->GetCamera()->SetViewPort(&vp);
|
||||
g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
|
||||
}
|
||||
|
||||
unsigned char* img = new unsigned char [w*h*3];
|
||||
|
||||
int num_frames = msg->framerate * msg->duration;
|
||||
|
||||
for (int frame = 0; frame < num_frames; ++frame)
|
||||
{
|
||||
manager->MoveToPointAbsolute((float)frame/msg->framerate);
|
||||
Render();
|
||||
Atlas_GLSwapBuffers((void*)g_GameLoop->glContext);
|
||||
glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, img);
|
||||
|
||||
// Swap the rows around, else the image will be upside down
|
||||
char temp[w*3];
|
||||
for (int y = 0; y < h/2; ++y)
|
||||
{
|
||||
memcpy2(temp, &img[y*w*3], w*3);
|
||||
memcpy2(&img[y*w*3], &img[(h-1-y)*w*3], w*3);
|
||||
memcpy2(&img[(h-1-y)*w*3], temp, w*3);
|
||||
}
|
||||
|
||||
// Call the user-supplied function with this data, so they can
|
||||
// store it as a video
|
||||
sCinemaRecordCB cbdata = { img };
|
||||
msg->cb.Call(cbdata);
|
||||
}
|
||||
|
||||
delete[] img;
|
||||
|
||||
// Restore viewport
|
||||
{
|
||||
g_Renderer.Resize(g_xres, g_yres);
|
||||
SViewPort vp = { 0, 0, g_xres, g_yres };
|
||||
g_Game->GetView()->GetCamera()->SetViewPort(&vp);
|
||||
g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QUERYHANDLER(Ping)
|
||||
{
|
||||
UNUSED2(msg);
|
||||
|
@ -80,6 +80,20 @@ MESSAGE(Screenshot,
|
||||
((int, tiles)) // the final image will be (640*tiles)x(480*tiles)
|
||||
);
|
||||
|
||||
struct sCinemaRecordCB
|
||||
{
|
||||
unsigned char* buffer;
|
||||
};
|
||||
SHAREABLE_STRUCT(sCinemaRecordCB);
|
||||
|
||||
QUERY(CinemaRecord,
|
||||
((std::wstring, track))
|
||||
((int, framerate))
|
||||
((float, duration))
|
||||
((Callback<sCinemaRecordCB>, cb))
|
||||
,
|
||||
);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MESSAGE(Brush,
|
||||
|
@ -268,6 +268,29 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Shareable callbacks:
|
||||
// (TODO - this is probably not really safely shareable, due to unspecified calling conventions)
|
||||
template<typename T> struct Callback
|
||||
{
|
||||
Callback(void (*cb) (const T*, void*), void* cbdata) : cb(cb), cbdata(cbdata) {}
|
||||
void (*cb) (const T*, void*);
|
||||
void* cbdata;
|
||||
};
|
||||
|
||||
template<typename T> class Shareable<Callback<T> >
|
||||
{
|
||||
ASSERT_TYPE_IS_SHAREABLE(T);
|
||||
public:
|
||||
Shareable(Callback<T> cb) : cb(cb) {}
|
||||
Callback<T> cb;
|
||||
void Call(const T& data) const
|
||||
{
|
||||
cb.cb(&data, cb.cbdata);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#ifdef SHAREABLE_USED_NOMMGR
|
||||
|
Loading…
Reference in New Issue
Block a user