diff --git a/desmume/src/gtk/Makefile.am b/desmume/src/gtk/Makefile.am
index 4bfda77d7..09a8cead0 100644
--- a/desmume/src/gtk/Makefile.am
+++ b/desmume/src/gtk/Makefile.am
@@ -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 \
diff --git a/desmume/src/gtk/avout.h b/desmume/src/gtk/avout.h
new file mode 100644
index 000000000..8966856ea
--- /dev/null
+++ b/desmume/src/gtk/avout.h
@@ -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 .
+*/
+
+#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
diff --git a/desmume/src/gtk/avout_flac.cpp b/desmume/src/gtk/avout_flac.cpp
new file mode 100644
index 000000000..eb32fdbbc
--- /dev/null
+++ b/desmume/src/gtk/avout_flac.cpp
@@ -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 .
+*/
+
+#include
+#include
+
+#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;
+}
+
diff --git a/desmume/src/gtk/avout_flac.h b/desmume/src/gtk/avout_flac.h
new file mode 100644
index 000000000..818d81665
--- /dev/null
+++ b/desmume/src/gtk/avout_flac.h
@@ -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 .
+*/
+
+#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
diff --git a/desmume/src/gtk/avout_pipe_base.cpp b/desmume/src/gtk/avout_pipe_base.cpp
new file mode 100644
index 000000000..fa4f5d431
--- /dev/null
+++ b/desmume/src/gtk/avout_pipe_base.cpp
@@ -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 .
+*/
+
+#include
+#include
+
+#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();
+ }
+}
+
diff --git a/desmume/src/gtk/avout_pipe_base.h b/desmume/src/gtk/avout_pipe_base.h
new file mode 100644
index 000000000..3f1c0edd8
--- /dev/null
+++ b/desmume/src/gtk/avout_pipe_base.h
@@ -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 .
+*/
+
+#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
diff --git a/desmume/src/gtk/avout_x264.cpp b/desmume/src/gtk/avout_x264.cpp
new file mode 100644
index 000000000..1996c0f8a
--- /dev/null
+++ b/desmume/src/gtk/avout_x264.cpp
@@ -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 .
+*/
+
+#include
+#include
+
+#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;
+}
+
diff --git a/desmume/src/gtk/avout_x264.h b/desmume/src/gtk/avout_x264.h
new file mode 100644
index 000000000..24125a0f9
--- /dev/null
+++ b/desmume/src/gtk/avout_x264.h
@@ -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 .
+*/
+
+#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
diff --git a/desmume/src/gtk/main.cpp b/desmume/src/gtk/main.cpp
index 1b921febf..b5634d443 100644
--- a/desmume/src/gtk/main.cpp
+++ b/desmume/src/gtk/main.cpp
@@ -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 =
" "
" "
" "
+" "
+" "
" "
" "
" "
@@ -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)