Linux (gtk): Add preliminary AV recording with x264 and flac.

This commit is contained in:
alvinwong 2014-02-17 04:02:42 +00:00
parent a15e6cee10
commit 6f91e947c3
9 changed files with 511 additions and 1 deletions

View File

@ -10,6 +10,10 @@ pixmap_DATA = DeSmuME.xpm
EXTRA_DIST = DeSmuME.xpm desmume.desktop
bin_PROGRAMS = desmume
desmume_SOURCES = \
avout.h \
avout_pipe_base.cpp avout_pipe_base.h \
avout_x264.cpp avout_x264.h \
avout_flac.cpp avout_flac.h \
desmume.h desmume.cpp \
dTool.h dToolsList.cpp \
tools/ioregsView.cpp tools/ioregsView.h \

32
desmume/src/gtk/avout.h Normal file
View File

@ -0,0 +1,32 @@
/*
Copyright (C) 2014 DeSmuME team
This file 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.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AVOUT_H_
#define _AVOUT_H_
#include "types.h"
class AVOut {
public:
virtual bool begin(const char* fname) { return false; }
virtual void end() {}
virtual bool isRecording() { return false; }
virtual void updateAudio(void* soundData, int soundLen) {}
virtual void updateVideo(const u16* buffer) {}
};
#endif

View File

@ -0,0 +1,47 @@
/*
Copyright (C) 2014 DeSmuME team
This file 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.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <cstring>
#include "avout_flac.h"
AVOutFlac::AVOutFlac() {
const char* const args[] = {
"flac",
"-f",
"-o", this->filename,
"--endian=little",
"--channels=2",
"--bps=16",
"--sample-rate=44100",
"--sign=signed",
"--force-raw-format",
"-",
NULL
};
memcpy(this->args, args, sizeof(args));
}
const char* const* AVOutFlac::getArgv(const char* fname) {
if (strlen(fname) >= sizeof(this->filename)) {
return NULL;
}
strncpy(this->filename, fname, sizeof(this->filename));
return this->args;
}

View File

@ -0,0 +1,34 @@
/*
Copyright (C) 2014 DeSmuME team
This file 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.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AVOUT_FLAC_H_
#define _AVOUT_FLAC_H_
#include "avout_pipe_base.h"
class AVOutFlac : public AVOutPipeBase {
public:
AVOutFlac();
protected:
Type type() { return TYPE_AUDIO; }
const char* const* getArgv(const char* fname);
private:
char filename[1024];
const char* args[12];
};
#endif

View File

@ -0,0 +1,102 @@
/*
Copyright (C) 2014 DeSmuME team
This file 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.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#include <unistd.h>
#include <cerrno>
#include "types.h"
#include "SPU.h"
#include "avout_pipe_base.h"
static inline int writeAll(int fd, const void* buf, size_t count) {
ssize_t written = 0, writtenTotal = 0;
do {
written = write(fd, ((u8*)buf) + writtenTotal, count - writtenTotal);
} while (written >= 0 && (writtenTotal += written) < count);
return written;
}
bool AVOutPipeBase::begin(const char* fname) {
if (this->recording) {
return false;
}
const char* const* args = this->getArgv(fname);
if (args == NULL) {
return false;
}
int pipefd[2];
if (pipe(pipefd) < 0) {
fprintf(stderr, "Fail to open pipe\n");
return false;
}
pid_t pid = fork();
if (pid == 0) {
close(pipefd[1]);
dup2(pipefd[0], STDIN_FILENO);
execvp(args[0], (char* const*)args);
fprintf(stderr, "Fail to start %s: %d %s\n", args[0], errno, strerror(errno));
_exit(1);
}
close(pipefd[0]);
this->pipe_fd = pipefd[1];
this->recording = true;
return true;
}
void AVOutPipeBase::end() {
if (this->recording) {
close(this->pipe_fd);
this->recording = false;
}
}
bool AVOutPipeBase::isRecording() {
return this->recording;
}
void AVOutPipeBase::updateAudio(void* soundData, int soundLen) {
if(!this->recording || this->type() != TYPE_AUDIO) {
return;
}
if (writeAll(this->pipe_fd, soundData, soundLen * 2 * 2) == -1) {
fprintf(stderr, "Error on writing audio: %d %s\n", errno, strerror(errno));
this->end();
}
}
void AVOutPipeBase::updateVideo(const u16* buffer) {
if(!this->recording || this->type() != TYPE_VIDEO) {
return;
}
u8 rgb[256 * 384 * 3];
u8* cur = rgb;
for (int i = 0; i < 256 * 384; i++) {
u16 gpu_pixel = buffer[i];
*cur = ((gpu_pixel >> 0) & 0x1f) << 3;
cur++;
*cur = ((gpu_pixel >> 5) & 0x1f) << 3;
cur++;
*cur = ((gpu_pixel >> 10) & 0x1f) << 3;
cur++;
}
if (write(this->pipe_fd, rgb, 256 * 384 * 3) == -1) {
fprintf(stderr, "Error on writing video: %d %s\n", errno, strerror(errno));
this->end();
}
}

View File

@ -0,0 +1,39 @@
/*
Copyright (C) 2014 DeSmuME team
This file 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.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AVOUT_PIPE_BASE_H_
#define _AVOUT_PIPE_BASE_H_
#include "avout.h"
class AVOutPipeBase : public AVOut {
public:
bool begin(const char* fname);
void end();
bool isRecording();
void updateAudio(void* soundData, int soundLen);
void updateVideo(const u16* buffer);
protected:
enum Type { TYPE_AUDIO, TYPE_VIDEO };
virtual Type type() = 0;
virtual const char* const* getArgv(const char* fname) = 0;
private:
bool recording;
int pipe_fd;
};
#endif

View File

@ -0,0 +1,47 @@
/*
Copyright (C) 2014 DeSmuME team
This file 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.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <cstring>
#include "avout_x264.h"
AVOutX264::AVOutX264() {
const char* const args[] = {
"x264",
"--qp", "0",
"--demuxer", "raw",
"--input-csp", "rgb",
"--input-depth", "8",
"--input-res", "256x384",
"--fps", "60",
"--output-csp", "i444",
"-o", this->filename,
"-",
NULL
};
memcpy(this->args, args, sizeof(args));
}
const char* const* AVOutX264::getArgv(const char* fname) {
if (strlen(fname) >= sizeof(this->filename)) {
return NULL;
}
strncpy(this->filename, fname, sizeof(this->filename));
return this->args;
}

View File

@ -0,0 +1,34 @@
/*
Copyright (C) 2014 DeSmuME team
This file 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.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AVOUT_X264_H_
#define _AVOUT_X264_H_
#include "avout_pipe_base.h"
class AVOutX264 : public AVOutPipeBase {
public:
AVOutX264();
protected:
Type type() { return TYPE_VIDEO; }
const char* const* getArgv(const char* fname);
private:
char filename[1024];
const char* args[19];
};
#endif

View File

@ -51,6 +51,9 @@
#include "cheatsGTK.h"
#include "GPU_osd.h"
#include "avout_x264.h"
#include "avout_flac.h"
#include "commandline.h"
#include "slot2.h"
@ -106,6 +109,12 @@ enum {
gboolean EmuLoop(gpointer data);
static AVOutX264 avout_x264;
static AVOutFlac avout_flac;
static void RecordAV_x264();
static void RecordAV_flac();
static void RecordAV_stop();
static void DoQuit();
static void RecordMovieDialog();
static void PlayMovieDialog();
@ -186,6 +195,12 @@ static const char *ui_description =
" <menuitem action='recordmovie'/>"
" <menuitem action='playmovie'/>"
" <menuitem action='stopmovie'/>"
" <separator/>"
" <menu action='RecordAVMenu'>"
" <menuitem action='record_x264'/>"
" <menuitem action='record_flac'/>"
" <menuitem action='record_stop'/>"
" </menu>"
" <menuitem action='printscreen'/>"
" <separator/>"
" <menuitem action='quit'/>"
@ -341,6 +356,10 @@ static const GtkActionEntry action_entries[] = {
{ "recordmovie", NULL, "Record movie _to ...", NULL, NULL, RecordMovieDialog },
{ "playmovie", NULL, "Play movie _from ...", NULL, NULL, PlayMovieDialog },
{ "stopmovie", NULL, "Stop movie", NULL, NULL, StopMovie },
{ "RecordAVMenu", NULL, "Record _video/audio" },
{ "record_x264", "gtk-media-record", "Record lossless H._264 (video only)...", NULL, NULL, RecordAV_x264 },
{ "record_flac", "gtk-media-record", "Record _flac (audio only)...", NULL, NULL, RecordAV_flac },
{ "record_stop", "gtk-media-stop", "_Stop recording", NULL, NULL, RecordAV_stop },
{ "SavestateMenu", NULL, "_Save state" },
{ "LoadstateMenu", NULL, "_Load state" },
#ifdef DESMUME_GTK_FIRMWARE_BROKEN
@ -1063,6 +1082,138 @@ static void SaveStateDialog()
gtk_widget_destroy(pFileSelection);
}
static void RecordAV_x264()
{
GtkFileFilter *pFilter_mkv, *pFilter_mp4, *pFilter_any;
GtkWidget *pFileSelection;
GtkWidget *pParent;
gchar *sPath;
if (desmume_running())
Pause();
pParent = GTK_WIDGET(pWindow);
pFilter_mkv = gtk_file_filter_new();
gtk_file_filter_add_pattern(pFilter_mkv, "*.mkv");
gtk_file_filter_set_name(pFilter_mkv, "Matroska (.mkv)");
pFilter_mp4 = gtk_file_filter_new();
gtk_file_filter_add_pattern(pFilter_mp4, "*.mp4");
gtk_file_filter_set_name(pFilter_mp4, "MP4 (.mp4)");
pFilter_any = gtk_file_filter_new();
gtk_file_filter_add_pattern(pFilter_any, "*");
gtk_file_filter_set_name(pFilter_any, "All files");
/* Creating the selection window */
pFileSelection = gtk_file_chooser_dialog_new("Save video...",
GTK_WINDOW(pParent),
GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_OK,
NULL);
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (pFileSelection), TRUE);
/* Only the dialog window is accepting events: */
gtk_window_set_modal(GTK_WINDOW(pFileSelection), TRUE);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(pFileSelection), pFilter_mkv);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(pFileSelection), pFilter_mp4);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(pFileSelection), pFilter_any);
/* Showing the window */
switch(gtk_dialog_run(GTK_DIALOG(pFileSelection))) {
case GTK_RESPONSE_OK:
sPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(pFileSelection));
if(avout_x264.begin(sPath)) {
gtk_action_set_sensitive(gtk_action_group_get_action(action_group, "record_x264"), FALSE);
} else {
GtkWidget *pDialog = gtk_message_dialog_new(GTK_WINDOW(pFileSelection),
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"Unable to record video to:\n%s", sPath);
gtk_dialog_run(GTK_DIALOG(pDialog));
gtk_widget_destroy(pDialog);
}
g_free(sPath);
break;
default:
break;
}
gtk_widget_destroy(pFileSelection);
}
static void RecordAV_flac()
{
GtkFileFilter *pFilter_flac, *pFilter_any;
GtkWidget *pFileSelection;
GtkWidget *pParent;
gchar *sPath;
if (desmume_running())
Pause();
pParent = GTK_WIDGET(pWindow);
pFilter_flac = gtk_file_filter_new();
gtk_file_filter_add_pattern(pFilter_flac, "*.flac");
gtk_file_filter_set_name(pFilter_flac, "FLAC file (.flac)");
pFilter_any = gtk_file_filter_new();
gtk_file_filter_add_pattern(pFilter_any, "*");
gtk_file_filter_set_name(pFilter_any, "All files");
/* Creating the selection window */
pFileSelection = gtk_file_chooser_dialog_new("Save audio...",
GTK_WINDOW(pParent),
GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_OK,
NULL);
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (pFileSelection), TRUE);
/* Only the dialog window is accepting events: */
gtk_window_set_modal(GTK_WINDOW(pFileSelection), TRUE);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(pFileSelection), pFilter_flac);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(pFileSelection), pFilter_any);
/* Showing the window */
switch(gtk_dialog_run(GTK_DIALOG(pFileSelection))) {
case GTK_RESPONSE_OK:
sPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(pFileSelection));
if(avout_flac.begin(sPath)) {
gtk_action_set_sensitive(gtk_action_group_get_action(action_group, "record_flac"), FALSE);
} else {
GtkWidget *pDialog = gtk_message_dialog_new(GTK_WINDOW(pFileSelection),
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"Unable to record audio to:\n%s", sPath);
gtk_dialog_run(GTK_DIALOG(pDialog));
gtk_widget_destroy(pDialog);
}
g_free(sPath);
break;
default:
break;
}
gtk_widget_destroy(pFileSelection);
}
static void RecordAV_stop() {
avout_x264.end();
avout_flac.end();
gtk_action_set_sensitive(gtk_action_group_get_action(action_group, "record_x264"), TRUE);
gtk_action_set_sensitive(gtk_action_group_get_action(action_group, "record_flac"), TRUE);
}
static void OpenNdsDialog()
{
GtkFileFilter *pFilter_nds, *pFilter_dsgba, *pFilter_any;
@ -1181,8 +1332,12 @@ static void OpenRecent(GtkRecentChooser *chooser, gpointer user_data)
static void Reset()
{
bool shouldBeRunning = desmume_running();
Pause();
NDS_Reset();
Launch();
if (shouldBeRunning) {
Launch();
}
}
@ -2035,6 +2190,18 @@ public:
while (gtk_events_pending())
gtk_main_iteration();
}
// HUD uses this to show pause state
virtual bool EMU_IsEmulationPaused() { return !desmume_running(); }
virtual bool AVI_IsRecording()
{
return avout_x264.isRecording() || avout_flac.isRecording();
}
virtual void AVI_SoundUpdate(void* soundData, int soundLen) {
avout_flac.updateAudio(soundData, soundLen);
}
};
static void DoQuit()
@ -2138,6 +2305,7 @@ gboolean EmuLoop(gpointer data)
_updateDTools();
gtk_widget_queue_draw( pDrawingArea );
osd->clear();
avout_x264.updateVideo((u16*)GPU_screen);
if (gtk_fps_limiter_disabled || keys_latch & KEYMASK_(KEY_BOOST - 1)) {
if (autoframeskip) {
@ -2747,6 +2915,9 @@ common_gtk_main( class configured_features *my_config)
/* Main loop */
gtk_main();
avout_x264.end();
avout_flac.end();
desmume_free();
#if defined(HAVE_LIBOSMESA)