From 135c2bb8104832d64e49d3bc4a954db3135bd0d6 Mon Sep 17 00:00:00 2001 From: Uze Date: Thu, 24 Sep 2015 21:28:27 -0400 Subject: [PATCH] -Added movie recording feature using ffmpeg piping --- tools/uzem/avr8.cpp | 68 +++++++++++++++++++++++++++++++++++++-------- tools/uzem/avr8.h | 9 ++++-- tools/uzem/uzem.cpp | 36 ++++++++++++++++++++---- 3 files changed, 95 insertions(+), 18 deletions(-) diff --git a/tools/uzem/avr8.cpp b/tools/uzem/avr8.cpp index 2f86ad17..966486d1 100644 --- a/tools/uzem/avr8.cpp +++ b/tools/uzem/avr8.cpp @@ -219,6 +219,9 @@ static u8 encode_delta(int d) u32 hsync_more_col; u32 hsync_less_col; +FILE* avconv_video = NULL; +FILE* avconv_audio = NULL; + void avr8::spi_calculateClock(){ // calculate the number of cycles before the write completes u16 spiClockDivider; @@ -250,6 +253,10 @@ void avr8::write_io(u8 addr,u8 value) SDL_LockAudio(); audioRing.push(value); SDL_UnlockAudio(); + + //Send audio byte to ffmpeg + if(recordMovie && avconv_audio) fwrite(&value, 1, 1, avconv_audio); + } } else if (addr == ports::PORTD) @@ -275,9 +282,11 @@ void avr8::write_io(u8 addr,u8 value) if(scanline_count >= 0){ current_cycle = left_edge; - current_scanline = (u32*)((u8*)screen->pixels + scanline_count * 2 * screen->pitch + inset); -/* + + + + /* if(hsyncHelp){ if(prev_scanline!=NULL && elapsedCycles > HSYNC_PERIOD){ @@ -323,7 +332,9 @@ void avr8::write_io(u8 addr,u8 value) { if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_Flip(screen); - //SDL_framerateDelay(&fpsmanager); + + //Send video frame to ffmpeg + if (recordMovie && avconv_video) fwrite(screen->pixels, 640*480*4, 1, avconv_video); SDL_Event event; while (singleStep? SDL_WaitEvent(&event) : SDL_PollEvent(&event)) @@ -360,6 +371,9 @@ void avr8::write_io(u8 addr,u8 value) buttons[0]=captureData[capturePtr]+(captureData[capturePtr+1]<<8); capturePtr+=2; captureSize-=2; + }else if(captureMode==CAPTURE_READ && captureSize==0){ + printf("Playback reached end of capture file.\n"); + shutdown(0); } @@ -1627,16 +1641,16 @@ bool avr8::init_gui() init_joysticks(); if (fullscreen) - screen = SDL_SetVideoMode(800,600,32,sdl_flags | SDL_FULLSCREEN); + screen = SDL_SetVideoMode(640,480,32,sdl_flags | SDL_FULLSCREEN); else - screen = SDL_SetVideoMode(630,448,32,sdl_flags); + screen = SDL_SetVideoMode(640,480,32,sdl_flags); if (!screen) { - fprintf(stderr, "Unable to set 630x448x32 video mode.\n"); + fprintf(stderr, "Unable to set 640x480x32 video mode.\n"); return false; } - else if (fullscreen) // Center in fullscreen - inset = ((600-448)/2) * screen->pitch + 4 * ((800-630)/2); + //else if (fullscreen) // Center in fullscreen + inset = ((480-448)/2) * screen->pitch + 4 * ((640-630)/2); if (SDL_MUSTLOCK(screen) && SDL_LockSurface(screen) < 0) return false; @@ -1690,8 +1704,20 @@ bool avr8::init_gui() hsync_more_col=SDL_MapRGB(screen->format, 255,0, 0); //red hsync_less_col=SDL_MapRGB(screen->format, 255,255, 0); //yellow - SDL_initFramerate(&fpsmanager); - SDL_setFramerate(&fpsmanager, 60); + if (recordMovie){ + + if (avconv_video == NULL) avconv_video = popen("ffmpeg -y -f rawvideo -s 640x480 -pix_fmt bgra -r 60 -i - -vf scale=-1:720 -sws_flags neighbor -an -b:v 1000k uzemtemp.mp4" , "w"); + if (avconv_video == NULL){ + fprintf(stderr, "Unable to init ffmpeg.\n"); + return false; + } + + avconv_audio = popen("ffmpeg -y -f u8 -ar 15734 -ac 1 -i - -acodec libmp3lame -ar 44.1k uzemtemp.mp3", "w"); + if(avconv_audio == NULL){ + fprintf(stderr, "Unable to init ffmpeg.\n"); + return false; + } + } //Set window icon SDL_Surface *slogo; @@ -2547,10 +2573,30 @@ void avr8::shutdown(int errcode){ } } - if(captureMode==CAPTURE_WRITE && captureFile!=NULL){ + if(captureFile!=NULL){ fclose(captureFile); } + //movie recording + if(recordMovie){ + if (avconv_video) pclose(avconv_video); + if (avconv_audio) pclose(avconv_audio); + + char mux[1024]; + strcpy(mux,"ffmpeg -y -i uzemtemp.mp4 -i uzemtemp.mp3 -vcodec copy -acodec copy -f mp4 "); + strcat(mux,romName); + strcat(mux,".mp4"); + + FILE* avconv_mux = popen(mux,"r"); + if (avconv_mux) { + pclose(avconv_mux); + unlink("uzemtemp.mp4"); + unlink("uzemtemp.mp3"); + }else{ + printf("Error with ffmpeg multiplexer."); + } + } + if (joystickFile) { FILE* f = fopen(joystickFile,"wb"); diff --git a/tools/uzem/avr8.h b/tools/uzem/avr8.h index 66b89781..ae970a72 100644 --- a/tools/uzem/avr8.h +++ b/tools/uzem/avr8.h @@ -279,7 +279,8 @@ struct avr8 { avr8() : /*Core*/ - pc(0), cycleCounter(0), watchdogTimer(0), prevPortB(0), prevWDR(0), eepromFile("eeprom.bin"),enableGdb(false),newTCCR1B(0),elapsedCyclesSleep(0),hsyncHelp(false), + pc(0), cycleCounter(0), watchdogTimer(0), prevPortB(0), prevWDR(0), eepromFile("eeprom.bin"),enableGdb(false), + newTCCR1B(0),elapsedCyclesSleep(0),hsyncHelp(false),recordMovie(false), /*Video*/ fullscreen(false),inset(0), @@ -291,7 +292,8 @@ struct avr8 joystickFile(0),pad_mode(SNES_PAD), new_input_mode(false), /*GDB*/ - singleStep(0), nextSingleStep(0), gdbBreakpointFound(false),gdbInvalidOpcode(false),gdbPort(1284),state(CPU_STOPPED),gdb(0), + singleStep(0), nextSingleStep(0), gdbBreakpointFound(false),gdbInvalidOpcode(false),gdbPort(1284), + state(CPU_STOPPED),gdb(0), /*Uzekeyboard*/ uzeKbState(0),uzeKbEnabled(false), @@ -311,6 +313,7 @@ struct avr8 memset(sram, 0, sizeof(sram)); memset(eeprom, 0, sizeof(eeprom)); memset(progmem,0,progSize); + memset(romName,0,sizeof(romName)); } /*Core*/ @@ -331,6 +334,8 @@ struct avr8 int randomSeed; const char* eepromFile; bool hsyncHelp; + bool recordMovie; + char romName[256]; struct { diff --git a/tools/uzem/uzem.cpp b/tools/uzem/uzem.cpp index 1c5bdb71..0d9e6f60 100644 --- a/tools/uzem/uzem.cpp +++ b/tools/uzem/uzem.cpp @@ -41,7 +41,7 @@ static const struct option longopts[] ={ { "mouse" , no_argument , NULL, 'm' }, { "2p" , no_argument , NULL, '2' }, { "img" , required_argument, NULL, 'g' }, - { "mbr" , no_argument , NULL, 'r' }, + { "record" , no_argument , NULL, 'r' }, { "eeprom" , required_argument, NULL, 'e' }, { "pgm" , required_argument, NULL, 'p' }, { "boot" , no_argument, NULL, 'b' }, @@ -81,6 +81,7 @@ void showHelp(char* programName){ printerr("\t--capture -c Captures controllers data to file.\n"); printerr("\t--loadcap -l Load and replays controllers data from file.\n"); printerr("\t--synchelp -z Displays and logs information to help troubleshooting HSYNC timing issues.\n"); + printerr("\t--record -r Record a movie in mp4/720p(60fps) format. (ffmpeg executable must be in the same directory as uzem or system path)\n"); } int ends_with(const char* name, const char* extension, size_t length) @@ -146,7 +147,7 @@ int main(int argc,char **argv) uzebox.pad_mode = avr8::SNES_PAD2; break; case 'r': - //TODO: implement MBR emulation option + uzebox.recordMovie=true; break; case 's': uzebox.SDpath = optarg; @@ -245,10 +246,35 @@ int main(int argc,char **argv) } } + //get rom name without extension + char *pfile = heximage + strlen(heximage); + for (;; pfile--) + { + if ((*pfile == '\\') || (*pfile == '/') || pfile==heximage) + { + if(pfile!=heximage)pfile++; //skip the slash character + for(int i=0;i<256;i++){ + if(*pfile=='.') break; + uzebox.romName[i]=*pfile; + pfile++; + } + break; + } + } - //get rom name without extension to build - //the capture file name + + //build the capture file name + int len=strlen(uzebox.romName); + char capfname[len+4]; + strcpy(capfname,uzebox.romName); + capfname[len+0]='.'; + capfname[len+1]='c'; + capfname[len+2]='a'; + capfname[len+3]='p'; + capfname[len+4]=0; + + /* char capfname[256]; char *pfile = heximage + strlen(heximage); for (;; pfile--) @@ -272,7 +298,7 @@ int main(int argc,char **argv) break; } } - + */ if(uzebox.captureMode==CAPTURE_READ) {