#226 Le 10/12/2012, à 10:25
- Rolinh
Re : /* Topic des codeurs [8] */
@grim7reaper:
Je sais parfaitement que ce n'était pas une bonne idée (d'où le smiley).
Cependant, Ruby gère beaucoup mieux la récursion que Python (heureusement pour un langage qui se veut supporter le lambda calcul...).
D'ailleurs, un petit test pour se représenter la différence: calcul d'une suite de Fibonacci en récursif.
Python:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
def fibo(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibo(n-1) + fibo(n-2)
if __name__ == "__main__":
n = int(sys.argv.pop())
print(fibo(n))
Ruby:
#!/usr/bin/env ruby
def fibo(n)
n < 2 ? n : fibo(n-1) + fibo(n-2)
end
begin
n = ARGV.pop.to_i
puts fibo(n)
end
Ruby via le lambda calcul:
#!/usr/bin/env ruby
begin
fibo = lambda { |x| x < 2 ? x : fibo[x-1] + fibo[x-2] }
n = ARGV.pop.to_i
puts fibo[n]
end
Et finalement en C:
#include <stdio.h>
#include <stdlib.h>
unsigned long int
fibo(n)
{
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return fibo(n-1) + fibo(n-2);
}
int
main(int argc, char *argv[])
{
unsigned long int n = strtol(argv[1], NULL, 10);
(void)printf("%ld\n", fibo(n));
return EXIT_SUCCESS;
}
Et maintenant pour les résultats:
[robin@thor ~/test] % time ./fibo.py 42
267914296
./fibo.py 42 198.08s user 0.00s system 99% cpu 3:18.12 total
[robin@thor ~/test] % time ./fibo.rb 42
267914296
./fibo.rb 42 57.07s user 0.01s system 99% cpu 57.174 total
[robin@thor ~/test] % time ./fibo-lambda.rb 42
267914296
./fibo-lambda.rb 42 86.65s user 0.01s system 99% cpu 1:26.83 total
[robin@thor ~/test] % time ./fibo 42
267914296
./fibo 42 1.45s user 0.00s system 99% cpu 1.459 total
On a donc, pour fibo de 42, sur ma machine (Intel Core i7-2620M CPU @ 2.70GHz):
~198 secondes en Python
~57 secondes en Ruby
~87 secondes en Ruby via le lambda calcul (décevant pour le coup)
~1.5 secondes en C
Respectivement, Ruby se révèle environ 3.5X plus rapide que Python en standard et 2.3X en lambda calcul, et C 132X plus rapide.
Je suis bien conscient que ce petit test n'a aucune valeur scientifique mais ça permet de se faire une petite idée.
Hors ligne
#227 Le 10/12/2012, à 10:33
- grim7reaper
Re : /* Topic des codeurs [8] */
@grim7reaper:
Je sais parfaitement que ce n'était pas une bonne idée (d'où le smiley).
Je ne disais pas ce que c’était pas une bonne idée en général, juste que pour Python c’est pas une bonne idée ^^
Dernière modification par grim7reaper (Le 10/12/2012, à 10:35)
Hors ligne
#228 Le 10/12/2012, à 11:34
- The Uploader
Re : /* Topic des codeurs [8] */
Ça m'a l'air de fonctionner pas mal la récursivité en Ruby. (d'ailleurs je l'ai déjà utilisé en fait, mais c'est tellement rare que je m'en souvenais plus
)
Quant à la doc Ruby elle est souvent bien, sauf quand tu tombes sur un truc beaucoup trop squelettique : (exemple : http://www.ruby-doc.org/stdlib-1.9.3/li … &width=785 )
Ça me gênerait moins (enfin autant si je devais l'utiliser) qu'un truc obscur sur github, mais pour la doc officielle de la stdlib Ruby ça craint un peu quand même.
Dernière modification par The Uploader (Le 10/12/2012, à 11:46)
- Oldies PC : Intel Pentium 3 @ 800 Mhz sur CM ASUS P2B-F, GeForce 4 Ti4800 SE, Disque Dur Hitachi 160 Go, 512 Mo de RAM, 3DFX Voodoo 2, Sound Blaster 16 ISA PnP, Windows 98 SE / XP)
- Desktop : Intel Core i7 6700K @ 4 GHz sur CM ASUS Z170-P, GeForce GTX 1070, SSD Samsung 850 EVO 1 To, 16 Go de RAM, Disque Dur Seagate Barracuda 3 To, Windows 10
Hors ligne
#229 Le 10/12/2012, à 15:02
- Blabla404
Re : /* Topic des codeurs [8] */
Je viens de commencer Rosalind, pour le moment j'ai fait les 10 premiers j'attends d'arriver aux plus durs mais pour le moment c'est interessant de voir le coté bio.
Fait amusant ma copine m'a vu faire ces problèmes et elle voudrait que je lui montre comment ça marche (typiquement elle peut être amenée à faire des trucs semblable pour sa thèse). Du coup après une brève recherche je vais lui faire faire du python (pour le moment j'ai tout codé en C++) apparemment c'est pas mal comme language il y a une grosse communauté et c'est très utilisé en bioinfo donc si elle doit vraiment en faire y a une chance que ce soit du python. Seulement je n'ai jamais fait de python mais c'est une excellente raison de m'y mettre (c'était sur ma todo list depuis longtemps). Vu qu'il y a beaucoup de Pythonistes ici, vous pensez quoi de l'intro sur rosalind, du byte of python et de ce cours qui a l'avantage d'être orienté bio-info:
http://www.dsimb.inserm.fr/~fuchs/python/
Et du coup question bonus, Rosalind recommande python2 il y a une vraie raison pour ça ? Ça me parait idiot d'apprendre une ancienne version surtout quand on a le choix. Il y a des arguments qui sont vraiment en faveur de python2 ?
Dernière modification par redoxx (Le 10/12/2012, à 15:12)
Hors ligne
#230 Le 10/12/2012, à 15:29
- grim7reaper
Re : /* Topic des codeurs [8] */
Fait amusant ma copine m'a vu faire ces problèmes et elle voudrait que je lui montre comment ça marche (typiquement elle peut être amenée à faire des trucs semblable pour sa thèse). Du coup après une brève recherche je vais lui faire faire du python (pour le moment j'ai tout codé en C++) apparemment c'est pas mal comme language il y a une grosse communauté et c'est très utilisé en bioinfo donc si elle doit vraiment en faire y a une chance que ce soit du python. Seulement je n'ai jamais fait de python mais c'est une excellente raison de m'y mettre (c'était sur ma todo list depuis longtemps).
Clairement c‘est le bon choix.
Il y a quelques années je t’aurais dis Perl sans hésiter, maintenant c‘est Python qui a pris la relève.
Vu qu'il y a beaucoup de Pythonistes ici, vous pensez quoi de l'intro sur rosalind, du byte of python et de ce cours qui a l'avantage d'être orienté bio-info:
http://www.dsimb.inserm.fr/~fuchs/python/
L’intro est une intro, c’est pas mal mais très léger.
Perso, je conseille Dive into Python 3 (il y a aussi une version pour Python 2)
Après un rapide survol, le cours orienté bioinfo ne semble guère orienté bioinfo (si ce n’est le chapitre 16)
Et du coup question bonus, Rosalind recommande python2 il y a une vraie raison pour ça ?
Ça me parait idiot d'apprendre une ancienne version surtout quand on a le choix. Il y a des arguments qui sont vraiment en faveur de python2 ?
Certaines bibliothèques ne sont pas encore dispo‘ pour Python 3, mais ça évolue et c‘est beaucoup moins gênant qu’il y a quelques années.
Pour ce qui es de BioPython :
Note we do not intend to support Python 3.0, please use Python 3.1 or later. Python 3 support is still incomplete (and we do not yet officially support using Biopython with Python 3), but the majority of modules are largely functional. In order to be able to run Biopython with Python 3 it is first converted using the 2to3 script. This is done automatically by the setup.py script. Use python3 setup.py build etc (see installation notes below).
Donc je pense que ça se tente.
Au pire, si elle doit absolument utiliser Python 2, si elle a appris le 3 ça ne lui fera pas grand-chose à retoucher
Perso, je fais Rosalind en Python 3 sans souci pour le moment (49 problèmes résolus).
Hors ligne
#231 Le 10/12/2012, à 15:41
- Mindiell
Re : /* Topic des codeurs [8] */
grim7reaper a répondu pour moi : je dis +1 pour Python3 (dernière version). J'ai pas fait autant de problèmes, mais je n'ai utilisé, pour le moment, que 2 lib externes tout à fait compatibles.
Hors ligne
#232 Le 10/12/2012, à 17:02
- Blabla404
Re : /* Topic des codeurs [8] */
Merci de vos retours, donc ce sera python3 et ce sera l'occasion de m'y mettre aussi.
Effectivement le cours que j'ai indiqué n'est pas vraiment spécifique si ce n'est qu'il est donné à un public de biologistes donc la présentation est peut-être plus adaptée. J'avais entendu parler du dive into python je ne sais pas si j'ai confondu avec le byte of python, mais je vais suivre to conseil et utiliser cette référence au début.
Je ne pensais pas avoir des réponses si vite, donc j'ai fait l'intro proposée par Rosalind. Mais les exemples ne marchent pas sur python3 (typiquement print n'a pas la même syntaxe en python2 et python3). Du peu que j'en ai vu c'est agréable même si ça me fait un peu peur de ne pas voir comment ça marche dernière.
Par exemple
for i in range(1,1000000):
On alloue vraiment une liste d'un Mo ? Je ne pense pas mais en l'écrivant j'ai eu un peu peur.
J'ai une autre question probablement idiote comment vous gérez la lecture d'entrée. En C++ j'envoyais le fichier sur l'entrée standard que je lisais via cin, mais en python je ne vois pas comment faire aussi simple. La lecture d'autres solutions ne m'aide pas vraiment sur ce point vous avez de conseils ?
Hors ligne
#233 Le 10/12/2012, à 17:16
- grim7reaper
Re : /* Topic des codeurs [8] */
Mais les exemples ne marchent pas sur python3 (typiquement print n'a pas la même syntaxe en python2 et python3).
Oui, c’est une des différences. print est devenu une fonction.
Du peu que j'en ai vu c'est agréable même si ça me fait un peu peur de ne pas voir comment ça marche dernière.
Par exemple
for i in range(1,1000000):
On alloue vraiment une liste d'un Mo ? Je ne pense pas mais en l'écrivant j'ai eu un peu peur.
En Python 2, oui je crois bien (mais à vérifier, je ne suis pas allé voir le code de l’interpréteur).
En Python 3, non : ça renvoie un itérateur et les éléments sont généré à la volée, un à un (une sorte d’évaluation paresseuse pour ceux qui connaissent). Pour avoir la même en chose en Python 2 il faut utiliser xrange
J'ai une autre question probablement idiote comment vous gérez la lecture d'entrée. En C++ j'envoyais le fichier sur l'entrée standard que je lisais via cin, mais en python je ne vois pas comment faire aussi simple. La lecture d'autres solutions ne m'aide pas vraiment sur ce point vous avez de conseils ?
Pour de l’entrée utilisateur, input.
Sinon sys.stdin à une méthode read et readline il me semble.
Ou tout simplement :
#!/usr/bin/env python3
from sys import stdin
for line in stdin:
print(line, end='')
Qui affiche tout ce qu’il reçoit en entrée.
Dernière modification par grim7reaper (Le 10/12/2012, à 17:20)
Hors ligne
#234 Le 10/12/2012, à 20:44
- Mindiell
Re : /* Topic des codeurs [8] */
Pour la lecture, input, comme grim7reaper.
Pour les fichiers, j'utilise with open tout simplement :
with open(path, 'r') as f:
for line in f.read().splitlines():
# traitement de la ligne
L'avantage : gestion des exceptions, de la fermeture du fichier, etc...
Hors ligne
#235 Le 11/12/2012, à 01:38
- The Uploader
Re : /* Topic des codeurs [8] */
Mott555 is interested in some sloppy/ugly code or strange workarounds or code comments during the development of Windows 95, like "anything TheDailyWTF-worthy."
I discovered that opening a particular program churned the hard drive a lot when you opened it. I decided to hook up the debugger to see what the problem was. What I discovered was code that went roughly like this, in pseudo-code:
int TryToCallFunctionX(a, b, c)
{
for each file in (SystemDirectory,
WindowsDirectory,
ProgramFilesDirectory(RecursiveSearch),
KitchenSink,
Uncle.GetKitchenSink)
{
hInstance = LoadLibrary(file);
fn = GetProcAddress(hInstance, "FunctionX");
if (fn != nullptr) {
int result = fn(a,b,c);
FreeLibrary(hInstance);
return result;
}
fn = GetProcAddress(hInstance, "__imp__FunctionX");
if (fn != nullptr) {
int result = fn(a,b,c);
FreeLibrary(hInstance);
return result;
}
fn = GetProcAddress(hInstance, "FunctionX@12");
if (fn != nullptr) {
int result = fn(a,b,c);
FreeLibrary(hInstance);
return result;
}
fn = GetProcAddress(hInstance, "__imp__FunctionX@12");
if (fn != nullptr) {
int result = fn(a,b,c);
FreeLibrary(hInstance);
return result;
}
FreeLibrary(hInstance);
}
return 0;
}The code enumerated every file in the system directory, Windows directory, Program Files directory, and possibly also the kitchen sink and their uncle's kitchen sink. It tries to load each one as a library, and sees if it has an export called FunctionX. For good measure, it also tries __imp__FunctionX, FunctionX@12, and __imp__FunctionX@12. If it finds any match, it calls the function.
As it happens, every single call to GetProcAddress failed. The function they were trying to call was an internal function in the window manager that wasn't exported. I guess they figured, "Hm, I can't find it in user32. Maybe it moved to some other DLL," and went through every DLL they could think of.
I called out this rather dubious programming technique, and word got back to the development team for that program. They came back and admitted, "Yeah, we were hoping to call that function, but couldn't find it, and the code you found is stuff we added during debugging. We have no intention of actually shipping that code."
Well, yeah, but still, what possesses you to try such a crazy technique, even if only for debugging?
http://blogs.msdn.com/b/oldnewthing/arc … 73882.aspx
Bon maintenant j'ai une petite histoire.
J'en ai parlé avant de temps en temps dans les fils précédents, et pour faire court DOSBox a une fonction intégrée pour capturer des vidéos (AVI, ZMBV 24 bits @ 70 FPS, PCM stéréo). Le problème est que tout ça est executé dans le même fil d'exécution que tout le reste de l'émulateur... Et ça a tendance évidemment à faire tout ramer, même sur un jeu 2D, même sur un Core2Duo. Ça fonctionne mais ça tourne quasi obligatoirement au ralenti vu que tout tourne sur un seul core.
Sur une machine avec plus d'un core, c'est dommage de n'utiliser qu'une seul core pour le coup. L'émulation de DOSBox ne peut pas être threadé (difficile à faire, gain pas forcément évident, et l'émulation risque d'en pâtir...), mais rien n'interdit de mettre la capture vidéo dans un thread à part.
Heureusement, sur les forums VOGONS, un patch existe depuis 2009.... Et il est assez bugé.
Ça se passe dans src/hardware/hardware.cpp. Voici l'original :
/*
* Copyright (C) 2002-2010 The DOSBox Team
*
* 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/* $Id: hardware.cpp,v 1.23 2009-10-11 18:09:22 qbix79 Exp $ */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "dosbox.h"
#include "hardware.h"
#include "setup.h"
#include "support.h"
#include "mem.h"
#include "mapper.h"
#include "pic.h"
#include "render.h"
#include "cross.h"
#if (C_SSHOT)
#include <png.h>
#include "../libs/zmbv/zmbv.cpp"
#endif
static std::string capturedir;
extern const char* RunningProgram;
Bitu CaptureState;
#define WAVE_BUF 16*1024
#define MIDI_BUF 4*1024
#define AVI_HEADER_SIZE 500
static struct {
struct {
FILE * handle;
Bit16s buf[WAVE_BUF][2];
Bitu used;
Bit32u length;
Bit32u freq;
} wave;
struct {
FILE * handle;
Bit8u buffer[MIDI_BUF];
Bitu used,done;
Bit32u last;
} midi;
struct {
Bitu rowlen;
} image;
#if (C_SSHOT)
struct {
FILE *handle;
Bitu frames;
Bit16s audiobuf[WAVE_BUF][2];
Bitu audioused;
Bitu audiorate;
Bitu audiowritten;
VideoCodec *codec;
Bitu width, height, bpp;
Bitu written;
float fps;
int bufSize;
void *buf;
Bit8u *index;
Bitu indexsize, indexused;
} video;
#endif
} capture;
FILE * OpenCaptureFile(const char * type,const char * ext) {
if(capturedir.empty()) {
LOG_MSG("Please specify a capture directory");
return 0;
}
Bitu last=0;
char file_start[16];
dir_information * dir;
/* Find a filename to open */
dir = open_directory(capturedir.c_str());
if (!dir) {
//Try creating it first
Cross::CreateDir(capturedir);
dir=open_directory(capturedir.c_str());
if(!dir) {
LOG_MSG("Can't open dir %s for capturing %s",capturedir.c_str(),type);
return 0;
}
}
strcpy(file_start,RunningProgram);
lowcase(file_start);
strcat(file_start,"_");
bool is_directory;
char tempname[CROSS_LEN];
bool testRead = read_directory_first(dir, tempname, is_directory );
for ( ; testRead; testRead = read_directory_next(dir, tempname, is_directory) ) {
char * test=strstr(tempname,ext);
if (!test || strlen(test)!=strlen(ext))
continue;
*test=0;
if (strncasecmp(tempname,file_start,strlen(file_start))!=0) continue;
Bitu num=atoi(&tempname[strlen(file_start)]);
if (num>=last) last=num+1;
}
close_directory( dir );
char file_name[CROSS_LEN];
sprintf(file_name,"%s%c%s%03d%s",capturedir.c_str(),CROSS_FILESPLIT,file_start,last,ext);
/* Open the actual file */
FILE * handle=fopen(file_name,"wb");
if (handle) {
LOG_MSG("Capturing %s to %s",type,file_name);
} else {
LOG_MSG("Failed to open %s for capturing %s",file_name,type);
}
return handle;
}
#if (C_SSHOT)
static void CAPTURE_AddAviChunk(const char * tag, Bit32u size, void * data, Bit32u flags) {
Bit8u chunk[8];Bit8u *index;Bit32u pos, writesize;
chunk[0] = tag[0];chunk[1] = tag[1];chunk[2] = tag[2];chunk[3] = tag[3];
host_writed(&chunk[4], size);
/* Write the actual data */
fwrite(chunk,1,8,capture.video.handle);
writesize = (size+1)&~1;
fwrite(data,1,writesize,capture.video.handle);
pos = capture.video.written + 4;
capture.video.written += writesize + 8;
if (capture.video.indexused + 16 >= capture.video.indexsize ) {
capture.video.index = (Bit8u*)realloc( capture.video.index, capture.video.indexsize + 16 * 4096);
if (!capture.video.index)
E_Exit("Ran out of memory during AVI capturing");
capture.video.indexsize += 16*4096;
}
index = capture.video.index+capture.video.indexused;
capture.video.indexused += 16;
index[0] = tag[0];
index[1] = tag[1];
index[2] = tag[2];
index[3] = tag[3];
host_writed(index+4, flags);
host_writed(index+8, pos);
host_writed(index+12, size);
}
#endif
#if (C_SSHOT)
static void CAPTURE_VideoHeader() {
Bit8u avi_header[AVI_HEADER_SIZE];
Bitu main_list;
Bitu header_pos=0;
Bitu save_pos=ftell(capture.video.handle);
#define AVIOUT4(_S_) memcpy(&avi_header[header_pos],_S_,4);header_pos+=4;
#define AVIOUTw(_S_) host_writew(&avi_header[header_pos], _S_);header_pos+=2;
#define AVIOUTd(_S_) host_writed(&avi_header[header_pos], _S_);header_pos+=4;
/* Try and write an avi header */
AVIOUT4("RIFF"); // Riff header
AVIOUTd(AVI_HEADER_SIZE + capture.video.written - 8 + capture.video.indexused);
AVIOUT4("AVI ");
AVIOUT4("LIST"); // List header
main_list = header_pos;
AVIOUTd(0); // TODO size of list
AVIOUT4("hdrl");
AVIOUT4("avih");
AVIOUTd(56); /* # of bytes to follow */
AVIOUTd((Bit32u)(1000000 / capture.video.fps)); /* Microseconds per frame */
AVIOUTd(0);
AVIOUTd(0); /* PaddingGranularity (whatever that might be) */
AVIOUTd(0x110); /* Flags,0x10 has index, 0x100 interleaved */
AVIOUTd(capture.video.frames); /* TotalFrames */
AVIOUTd(0); /* InitialFrames */
AVIOUTd(2); /* Stream count */
AVIOUTd(0); /* SuggestedBufferSize */
AVIOUTd(capture.video.width); /* Width */
AVIOUTd(capture.video.height); /* Height */
AVIOUTd(0); /* TimeScale: Unit used to measure time */
AVIOUTd(0); /* DataRate: Data rate of playback */
AVIOUTd(0); /* StartTime: Starting time of AVI data */
AVIOUTd(0); /* DataLength: Size of AVI data chunk */
/* Video stream list */
AVIOUT4("LIST");
AVIOUTd(4 + 8 + 56 + 8 + 40); /* Size of the list */
AVIOUT4("strl");
/* video stream header */
AVIOUT4("strh");
AVIOUTd(56); /* # of bytes to follow */
AVIOUT4("vids"); /* Type */
AVIOUT4(CODEC_4CC); /* Handler */
AVIOUTd(0); /* Flags */
AVIOUTd(0); /* Reserved, MS says: wPriority, wLanguage */
AVIOUTd(0); /* InitialFrames */
AVIOUTd(1000000); /* Scale */
AVIOUTd((Bit32u)(1000000 * capture.video.fps)); /* Rate: Rate/Scale == samples/second */
AVIOUTd(0); /* Start */
AVIOUTd(capture.video.frames); /* Length */
AVIOUTd(0); /* SuggestedBufferSize */
AVIOUTd(~0); /* Quality */
AVIOUTd(0); /* SampleSize */
AVIOUTd(0); /* Frame */
AVIOUTd(0); /* Frame */
/* The video stream format */
AVIOUT4("strf");
AVIOUTd(40); /* # of bytes to follow */
AVIOUTd(40); /* Size */
AVIOUTd(capture.video.width); /* Width */
AVIOUTd(capture.video.height); /* Height */
// OUTSHRT(1); OUTSHRT(24); /* Planes, Count */
AVIOUTd(0);
AVIOUT4(CODEC_4CC); /* Compression */
AVIOUTd(capture.video.width * capture.video.height*4); /* SizeImage (in bytes?) */
AVIOUTd(0); /* XPelsPerMeter */
AVIOUTd(0); /* YPelsPerMeter */
AVIOUTd(0); /* ClrUsed: Number of colors used */
AVIOUTd(0); /* ClrImportant: Number of colors important */
/* Audio stream list */
AVIOUT4("LIST");
AVIOUTd(4 + 8 + 56 + 8 + 16); /* Length of list in bytes */
AVIOUT4("strl");
/* The audio stream header */
AVIOUT4("strh");
AVIOUTd(56); /* # of bytes to follow */
AVIOUT4("auds");
AVIOUTd(0); /* Format (Optionally) */
AVIOUTd(0); /* Flags */
AVIOUTd(0); /* Reserved, MS says: wPriority, wLanguage */
AVIOUTd(0); /* InitialFrames */
AVIOUTd(4); /* Scale */
AVIOUTd(capture.video.audiorate*4); /* Rate, actual rate is scale/rate */
AVIOUTd(0); /* Start */
if (!capture.video.audiorate)
capture.video.audiorate = 1;
AVIOUTd(capture.video.audiowritten/4); /* Length */
AVIOUTd(0); /* SuggestedBufferSize */
AVIOUTd(~0); /* Quality */
AVIOUTd(4); /* SampleSize */
AVIOUTd(0); /* Frame */
AVIOUTd(0); /* Frame */
/* The audio stream format */
AVIOUT4("strf");
AVIOUTd(16); /* # of bytes to follow */
AVIOUTw(1); /* Format, WAVE_ZMBV_FORMAT_PCM */
AVIOUTw(2); /* Number of channels */
AVIOUTd(capture.video.audiorate); /* SamplesPerSec */
AVIOUTd(capture.video.audiorate*4); /* AvgBytesPerSec*/
AVIOUTw(4); /* BlockAlign */
AVIOUTw(16); /* BitsPerSample */
int nmain = header_pos - main_list - 4;
/* Finish stream list, i.e. put number of bytes in the list to proper pos */
int njunk = AVI_HEADER_SIZE - 8 - 12 - header_pos;
AVIOUT4("JUNK");
AVIOUTd(njunk);
/* Fix the size of the main list */
header_pos = main_list;
AVIOUTd(nmain);
header_pos = AVI_HEADER_SIZE - 12;
AVIOUT4("LIST");
AVIOUTd(capture.video.written+4); /* Length of list in bytes */
AVIOUT4("movi");
/* First add the index table to the end */
memcpy(capture.video.index, "idx1", 4);
host_writed( capture.video.index+4, capture.video.indexused - 8 );
fwrite( capture.video.index, 1, capture.video.indexused, capture.video.handle);
fseek(capture.video.handle, 0, SEEK_SET);
fwrite(&avi_header, 1, AVI_HEADER_SIZE, capture.video.handle);
fseek(capture.video.handle, save_pos, SEEK_SET);
}
static void CAPTURE_VideoEvent(bool pressed) {
if (!pressed)
return;
if (CaptureState & CAPTURE_VIDEO) {
/* Close the video */
CaptureState &= ~CAPTURE_VIDEO;
LOG_MSG("Stopped capturing video.");
/* Adds AVI header to the file */
CAPTURE_VideoHeader();
fclose( capture.video.handle );
free( capture.video.index );
free( capture.video.buf );
delete capture.video.codec;
capture.video.handle = 0;
} else {
CaptureState |= CAPTURE_VIDEO;
}
}
#endif
void CAPTURE_AddImage(Bitu width, Bitu height, Bitu bpp, Bitu pitch, Bitu flags, float fps, Bit8u * data, Bit8u * pal) {
#if (C_SSHOT)
Bitu i;
Bit8u doubleRow[SCALER_MAXWIDTH*4];
Bitu countWidth = width;
if (flags & CAPTURE_FLAG_DBLH)
height *= 2;
if (flags & CAPTURE_FLAG_DBLW)
width *= 2;
if (height > SCALER_MAXHEIGHT)
return;
if (width > SCALER_MAXWIDTH)
return;
if (CaptureState & CAPTURE_IMAGE) {
png_structp png_ptr;
png_infop info_ptr;
png_color palette[256];
CaptureState &= ~CAPTURE_IMAGE;
/* Open the actual file */
FILE * fp=OpenCaptureFile("Screenshot",".png");
if (!fp) goto skip_shot;
/* First try to alloacte the png structures */
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,NULL, NULL);
if (!png_ptr) goto skip_shot;
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr,(png_infopp)NULL);
goto skip_shot;
}
/* Finalize the initing of png library */
png_init_io(png_ptr, fp);
png_set_compression_level(png_ptr,Z_BEST_COMPRESSION);
/* set other zlib parameters */
png_set_compression_mem_level(png_ptr, 8);
png_set_compression_strategy(png_ptr,Z_DEFAULT_STRATEGY);
png_set_compression_window_bits(png_ptr, 15);
png_set_compression_method(png_ptr, 8);
png_set_compression_buffer_size(png_ptr, 8192);
if (bpp==8) {
png_set_IHDR(png_ptr, info_ptr, width, height,
8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
for (i=0;i<256;i++) {
palette[i].red=pal[i*4+0];
palette[i].green=pal[i*4+1];
palette[i].blue=pal[i*4+2];
}
png_set_PLTE(png_ptr, info_ptr, palette,256);
} else {
png_set_bgr( png_ptr );
png_set_IHDR(png_ptr, info_ptr, width, height,
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
}
png_write_info(png_ptr, info_ptr);
for (i=0;i<height;i++) {
void *rowPointer;
void *srcLine;
if (flags & CAPTURE_FLAG_DBLH)
srcLine=(data+(i >> 1)*pitch);
else
srcLine=(data+(i >> 0)*pitch);
rowPointer=srcLine;
switch (bpp) {
case 8:
if (flags & CAPTURE_FLAG_DBLW) {
for (Bitu x=0;x<countWidth;x++)
doubleRow[x*2+0] =
doubleRow[x*2+1] = ((Bit8u *)srcLine)[x];
rowPointer = doubleRow;
}
break;
case 15:
if (flags & CAPTURE_FLAG_DBLW) {
for (Bitu x=0;x<countWidth;x++) {
Bitu pixel = ((Bit16u *)srcLine)[x];
doubleRow[x*6+0] = doubleRow[x*6+3] = ((pixel& 0x001f) * 0x21) >> 2;
doubleRow[x*6+1] = doubleRow[x*6+4] = ((pixel& 0x03e0) * 0x21) >> 7;
doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0x7c00) * 0x21) >> 12;
}
} else {
for (Bitu x=0;x<countWidth;x++) {
Bitu pixel = ((Bit16u *)srcLine)[x];
doubleRow[x*3+0] = ((pixel& 0x001f) * 0x21) >> 2;
doubleRow[x*3+1] = ((pixel& 0x03e0) * 0x21) >> 7;
doubleRow[x*3+2] = ((pixel& 0x7c00) * 0x21) >> 12;
}
}
rowPointer = doubleRow;
break;
case 16:
if (flags & CAPTURE_FLAG_DBLW) {
for (Bitu x=0;x<countWidth;x++) {
Bitu pixel = ((Bit16u *)srcLine)[x];
doubleRow[x*6+0] = doubleRow[x*6+3] = ((pixel& 0x001f) * 0x21) >> 2;
doubleRow[x*6+1] = doubleRow[x*6+4] = ((pixel& 0x07e0) * 0x41) >> 9;
doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0xf800) * 0x21) >> 13;
}
} else {
for (Bitu x=0;x<countWidth;x++) {
Bitu pixel = ((Bit16u *)srcLine)[x];
doubleRow[x*3+0] = ((pixel& 0x001f) * 0x21) >> 2;
doubleRow[x*3+1] = ((pixel& 0x07e0) * 0x41) >> 9;
doubleRow[x*3+2] = ((pixel& 0xf800) * 0x21) >> 13;
}
}
rowPointer = doubleRow;
break;
case 32:
if (flags & CAPTURE_FLAG_DBLW) {
for (Bitu x=0;x<countWidth;x++) {
doubleRow[x*6+0] = doubleRow[x*6+3] = ((Bit8u *)srcLine)[x*4+0];
doubleRow[x*6+1] = doubleRow[x*6+4] = ((Bit8u *)srcLine)[x*4+1];
doubleRow[x*6+2] = doubleRow[x*6+5] = ((Bit8u *)srcLine)[x*4+2];
}
} else {
for (Bitu x=0;x<countWidth;x++) {
doubleRow[x*3+0] = ((Bit8u *)srcLine)[x*4+0];
doubleRow[x*3+1] = ((Bit8u *)srcLine)[x*4+1];
doubleRow[x*3+2] = ((Bit8u *)srcLine)[x*4+2];
}
}
rowPointer = doubleRow;
break;
}
png_write_row(png_ptr, (png_bytep)rowPointer);
}
/* Finish writing */
png_write_end(png_ptr, 0);
/*Destroy PNG structs*/
png_destroy_write_struct(&png_ptr, &info_ptr);
/*close file*/
fclose(fp);
}
skip_shot:
if (CaptureState & CAPTURE_VIDEO) {
zmbv_format_t format;
/* Disable capturing if any of the test fails */
if (capture.video.handle && (
capture.video.width != width ||
capture.video.height != height ||
capture.video.bpp != bpp ||
capture.video.fps != fps))
{
CAPTURE_VideoEvent(true);
}
CaptureState &= ~CAPTURE_VIDEO;
switch (bpp) {
case 8:format = ZMBV_FORMAT_8BPP;break;
case 15:format = ZMBV_FORMAT_15BPP;break;
case 16:format = ZMBV_FORMAT_16BPP;break;
case 32:format = ZMBV_FORMAT_32BPP;break;
default:
goto skip_video;
}
if (!capture.video.handle) {
capture.video.handle = OpenCaptureFile("Video",".avi");
if (!capture.video.handle)
goto skip_video;
capture.video.codec = new VideoCodec();
if (!capture.video.codec)
goto skip_video;
if (!capture.video.codec->SetupCompress( width, height))
goto skip_video;
capture.video.bufSize = capture.video.codec->NeededSize(width, height, format);
capture.video.buf = malloc( capture.video.bufSize );
if (!capture.video.buf)
goto skip_video;
capture.video.index = (Bit8u*)malloc( 16*4096 );
if (!capture.video.buf)
goto skip_video;
capture.video.indexsize = 16*4096;
capture.video.indexused = 8;
capture.video.width = width;
capture.video.height = height;
capture.video.bpp = bpp;
capture.video.fps = fps;
for (i=0;i<AVI_HEADER_SIZE;i++)
fputc(0,capture.video.handle);
capture.video.frames = 0;
capture.video.written = 0;
capture.video.audioused = 0;
capture.video.audiowritten = 0;
}
int codecFlags;
if (capture.video.frames % 300 == 0)
codecFlags = 1;
else codecFlags = 0;
if (!capture.video.codec->PrepareCompressFrame( codecFlags, format, (char *)pal, capture.video.buf, capture.video.bufSize))
goto skip_video;
for (i=0;i<height;i++) {
void * rowPointer;
if (flags & CAPTURE_FLAG_DBLW) {
void *srcLine;
Bitu x;
Bitu countWidth = width >> 1;
if (flags & CAPTURE_FLAG_DBLH)
srcLine=(data+(i >> 1)*pitch);
else
srcLine=(data+(i >> 0)*pitch);
switch ( bpp) {
case 8:
for (x=0;x<countWidth;x++)
((Bit8u *)doubleRow)[x*2+0] =
((Bit8u *)doubleRow)[x*2+1] = ((Bit8u *)srcLine)[x];
break;
case 15:
case 16:
for (x=0;x<countWidth;x++)
((Bit16u *)doubleRow)[x*2+0] =
((Bit16u *)doubleRow)[x*2+1] = ((Bit16u *)srcLine)[x];
break;
case 32:
for (x=0;x<countWidth;x++)
((Bit32u *)doubleRow)[x*2+0] =
((Bit32u *)doubleRow)[x*2+1] = ((Bit32u *)srcLine)[x];
break;
}
rowPointer=doubleRow;
} else {
if (flags & CAPTURE_FLAG_DBLH)
rowPointer=(data+(i >> 1)*pitch);
else
rowPointer=(data+(i >> 0)*pitch);
}
capture.video.codec->CompressLines( 1, &rowPointer );
}
int written = capture.video.codec->FinishCompressFrame();
if (written < 0)
goto skip_video;
CAPTURE_AddAviChunk( "00dc", written, capture.video.buf, codecFlags & 1 ? 0x10 : 0x0);
capture.video.frames++;
// LOG_MSG("Frame %d video %d audio %d",capture.video.frames, written, capture.video.audioused *4 );
if ( capture.video.audioused ) {
CAPTURE_AddAviChunk( "01wb", capture.video.audioused * 4, capture.video.audiobuf, 0);
capture.video.audiowritten = capture.video.audioused*4;
capture.video.audioused = 0;
}
/* Adds AVI header to the file */
CAPTURE_VideoHeader();
/* Everything went okay, set flag again for next frame */
CaptureState |= CAPTURE_VIDEO;
}
skip_video:
#endif
return;
}
#if (C_SSHOT)
static void CAPTURE_ScreenShotEvent(bool pressed) {
if (!pressed)
return;
CaptureState |= CAPTURE_IMAGE;
}
#endif
/* WAV capturing */
static Bit8u wavheader[]={
'R','I','F','F', 0x0,0x0,0x0,0x0, /* Bit32u Riff Chunk ID / Bit32u riff size */
'W','A','V','E', 'f','m','t',' ', /* Bit32u Riff Format / Bit32u fmt chunk id */
0x10,0x0,0x0,0x0, 0x1,0x0,0x2,0x0, /* Bit32u fmt size / Bit16u encoding/ Bit16u channels */
0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0, /* Bit32u freq / Bit32u byterate */
0x4,0x0,0x10,0x0, 'd','a','t','a', /* Bit16u byte-block / Bit16u bits / Bit16u data chunk id */
0x0,0x0,0x0,0x0, /* Bit32u data size */
};
void CAPTURE_AddWave(Bit32u freq, Bit32u len, Bit16s * data) {
#if (C_SSHOT)
if (CaptureState & CAPTURE_VIDEO) {
Bitu left = WAVE_BUF - capture.video.audioused;
if (left > len)
left = len;
memcpy( &capture.video.audiobuf[capture.video.audioused], data, left*4);
capture.video.audioused += left;
capture.video.audiorate = freq;
}
#endif
if (CaptureState & CAPTURE_WAVE) {
if (!capture.wave.handle) {
capture.wave.handle=OpenCaptureFile("Wave Output",".wav");
if (!capture.wave.handle) {
CaptureState &= ~CAPTURE_WAVE;
return;
}
capture.wave.length = 0;
capture.wave.used = 0;
capture.wave.freq = freq;
fwrite(wavheader,1,sizeof(wavheader),capture.wave.handle);
}
Bit16s * read = data;
while (len > 0 ) {
Bitu left = WAVE_BUF - capture.wave.used;
if (!left) {
fwrite(capture.wave.buf,1,4*WAVE_BUF,capture.wave.handle);
capture.wave.length += 4*WAVE_BUF;
capture.wave.used = 0;
left = WAVE_BUF;
}
if (left > len)
left = len;
memcpy( &capture.wave.buf[capture.wave.used], read, left*4);
capture.wave.used += left;
read += left*2;
len -= left;
}
}
}
static void CAPTURE_WaveEvent(bool pressed) {
if (!pressed)
return;
/* Check for previously opened wave file */
if (capture.wave.handle) {
LOG_MSG("Stopped capturing wave output.");
/* Write last piece of audio in buffer */
fwrite(capture.wave.buf,1,capture.wave.used*4,capture.wave.handle);
capture.wave.length+=capture.wave.used*4;
/* Fill in the header with useful information */
host_writed(&wavheader[0x04],capture.wave.length+sizeof(wavheader)-8);
host_writed(&wavheader[0x18],capture.wave.freq);
host_writed(&wavheader[0x1C],capture.wave.freq*4);
host_writed(&wavheader[0x28],capture.wave.length);
fseek(capture.wave.handle,0,0);
fwrite(wavheader,1,sizeof(wavheader),capture.wave.handle);
fclose(capture.wave.handle);
capture.wave.handle=0;
CaptureState |= CAPTURE_WAVE;
}
CaptureState ^= CAPTURE_WAVE;
}
/* MIDI capturing */
static Bit8u midi_header[]={
'M','T','h','d', /* Bit32u, Header Chunk */
0x0,0x0,0x0,0x6, /* Bit32u, Chunk Length */
0x0,0x0, /* Bit16u, Format, 0=single track */
0x0,0x1, /* Bit16u, Track Count, 1 track */
0x01,0xf4, /* Bit16u, Timing, 2 beats/second with 500 frames */
'M','T','r','k', /* Bit32u, Track Chunk */
0x0,0x0,0x0,0x0, /* Bit32u, Chunk Length */
//Track data
};
static void RawMidiAdd(Bit8u data) {
capture.midi.buffer[capture.midi.used++]=data;
if (capture.midi.used >= MIDI_BUF ) {
capture.midi.done += capture.midi.used;
fwrite(capture.midi.buffer,1,MIDI_BUF,capture.midi.handle);
capture.midi.used = 0;
}
}
static void RawMidiAddNumber(Bit32u val) {
if (val & 0xfe00000) RawMidiAdd((Bit8u)(0x80|((val >> 21) & 0x7f)));
if (val & 0xfffc000) RawMidiAdd((Bit8u)(0x80|((val >> 14) & 0x7f)));
if (val & 0xfffff80) RawMidiAdd((Bit8u)(0x80|((val >> 7) & 0x7f)));
RawMidiAdd((Bit8u)(val & 0x7f));
}
void CAPTURE_AddMidi(bool sysex, Bitu len, Bit8u * data) {
if (!capture.midi.handle) {
capture.midi.handle=OpenCaptureFile("Raw Midi",".mid");
if (!capture.midi.handle) {
return;
}
fwrite(midi_header,1,sizeof(midi_header),capture.midi.handle);
capture.midi.last=PIC_Ticks;
}
Bit32u delta=PIC_Ticks-capture.midi.last;
capture.midi.last=PIC_Ticks;
RawMidiAddNumber(delta);
if (sysex) {
RawMidiAdd( 0xf0 );
RawMidiAddNumber( len );
}
for (Bitu i=0;i<len;i++)
RawMidiAdd(data[i]);
}
static void CAPTURE_MidiEvent(bool pressed) {
if (!pressed)
return;
/* Check for previously opened wave file */
if (capture.midi.handle) {
LOG_MSG("Stopping raw midi saving and finalizing file.");
//Delta time
RawMidiAdd(0x00);
//End of track event
RawMidiAdd(0xff);
RawMidiAdd(0x2F);
RawMidiAdd(0x00);
/* clear out the final data in the buffer if any */
fwrite(capture.midi.buffer,1,capture.midi.used,capture.midi.handle);
capture.midi.done+=capture.midi.used;
fseek(capture.midi.handle,18, SEEK_SET);
Bit8u size[4];
size[0]=(Bit8u)(capture.midi.done >> 24);
size[1]=(Bit8u)(capture.midi.done >> 16);
size[2]=(Bit8u)(capture.midi.done >> 8);
size[3]=(Bit8u)(capture.midi.done >> 0);
fwrite(&size,1,4,capture.midi.handle);
fclose(capture.midi.handle);
capture.midi.handle=0;
CaptureState &= ~CAPTURE_MIDI;
return;
}
CaptureState ^= CAPTURE_MIDI;
if (CaptureState & CAPTURE_MIDI) {
LOG_MSG("Preparing for raw midi capture, will start with first data.");
capture.midi.used=0;
capture.midi.done=0;
capture.midi.handle=0;
} else {
LOG_MSG("Stopped capturing raw midi before any data arrived.");
}
}
class HARDWARE:public Module_base{
public:
HARDWARE(Section* configuration):Module_base(configuration){
Section_prop * section = static_cast<Section_prop *>(configuration);
Prop_path* proppath= section->Get_path("captures");
capturedir = proppath->realpath;
CaptureState = 0;
MAPPER_AddHandler(CAPTURE_WaveEvent,MK_f6,MMOD1,"recwave","Rec Wave");
MAPPER_AddHandler(CAPTURE_MidiEvent,MK_f8,MMOD1|MMOD2,"caprawmidi","Cap MIDI");
#if (C_SSHOT)
MAPPER_AddHandler(CAPTURE_ScreenShotEvent,MK_f5,MMOD1,"scrshot","Screenshot");
MAPPER_AddHandler(CAPTURE_VideoEvent,MK_f5,MMOD1|MMOD2,"video","Video");
#endif
}
~HARDWARE(){
if (capture.wave.handle) CAPTURE_WaveEvent(true);
if (capture.midi.handle) CAPTURE_MidiEvent(true);
}
};
static HARDWARE* test;
void HARDWARE_Destroy(Section * sec) {
delete test;
}
void HARDWARE_Init(Section * sec) {
test = new HARDWARE(sec);
sec->AddDestroyFunction(&HARDWARE_Destroy,true);
}
La capture vidéo se passe dans CAPTURE_AddImage, section "skip_shot:" (oui la même fonction s'occupe des screenshots en PNG et de la capture vidéo... vive les labels et les gotos et les "return;"... Ou pas !)
Le patch change énormément tout ça, utilise principalement un SDL_Thread, et une queue :
/*
* Copyright (C) 2002-2009 The DOSBox Team
*
* 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/* $Id: hardware.cpp,v 1.23 2009/10/11 18:09:22 qbix79 Exp $ */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "dosbox.h"
#include "hardware.h"
#include "setup.h"
#include "support.h"
#include "mem.h"
#include "mapper.h"
#include "pic.h"
#include "render.h"
#include "cross.h"
#if (C_SSHOT)
#define C_THREADED_CAPTURE 1
#include <png.h>
#include "../libs/zmbv/zmbv.cpp"
#if (C_THREADED_CAPTURE)
#define MAX_QUEUE_SIZE 400
#include "SDL.h"
#include <queue>
SDL_Thread *video_thread;
#endif
#endif
static std::string capturedir;
extern const char* RunningProgram;
Bitu CaptureState;
#define WAVE_BUF 16*1024
#define MIDI_BUF 4*1024
#define AVI_HEADER_SIZE 500
#if (C_SSHOT)
typedef struct {
Bit16s *audiobuf;
Bitu audioused;
Bit8u *videobuf;
Bit8u *pal;
} video_chunk_t;
typedef struct {
FILE *handle;
Bitu frames;
Bit16s audiobuf[WAVE_BUF][2];
Bitu audioused;
Bitu audiorate;
Bitu audiowritten;
VideoCodec *codec;
Bitu width, height, bpp;
Bitu written;
float fps;
int bufSize;
void *buf;
Bit8u *index;
Bitu indexsize, indexused;
Bitu flags;
Bitu pitch;
zmbv_format_t format;
bool thread_running;
#if (C_THREADED_CAPTURE)
std::queue<video_chunk_t> q;
#endif
} video_capture_t;
#endif
static struct {
struct {
FILE * handle;
Bit16s buf[WAVE_BUF][2];
Bitu used;
Bit32u length;
Bit32u freq;
} wave;
struct {
FILE * handle;
Bit8u buffer[MIDI_BUF];
Bitu used,done;
Bit32u last;
} midi;
struct {
Bitu rowlen;
} image;
#if (C_SSHOT)
video_capture_t video;
#endif
} capture;
FILE * OpenCaptureFile(const char * type,const char * ext) {
if(capturedir.empty()) {
LOG_MSG("Please specify a capture directory");
return 0;
}
Bitu last=0;
char file_start[16];
dir_information * dir;
/* Find a filename to open */
dir = open_directory(capturedir.c_str());
if (!dir) {
//Try creating it first
Cross::CreateDir(capturedir);
dir=open_directory(capturedir.c_str());
if(!dir) {
LOG_MSG("Can't open dir %s for capturing %s",capturedir.c_str(),type);
return 0;
}
}
strcpy(file_start,RunningProgram);
lowcase(file_start);
strcat(file_start,"_");
bool is_directory;
char tempname[CROSS_LEN];
bool testRead = read_directory_first(dir, tempname, is_directory );
for ( ; testRead; testRead = read_directory_next(dir, tempname, is_directory) ) {
char * test=strstr(tempname,ext);
if (!test || strlen(test)!=strlen(ext))
continue;
*test=0;
if (strncasecmp(tempname,file_start,strlen(file_start))!=0) continue;
Bitu num=atoi(&tempname[strlen(file_start)]);
if (num>=last) last=num+1;
}
close_directory( dir );
char file_name[CROSS_LEN];
sprintf(file_name,"%s%c%s%03d%s",capturedir.c_str(),CROSS_FILESPLIT,file_start,last,ext);
/* Open the actual file */
FILE * handle=fopen(file_name,"wb");
if (handle) {
LOG_MSG("Capturing %s to %s",type,file_name);
} else {
LOG_MSG("Failed to open %s for capturing %s",file_name,type);
}
return handle;
}
#if (C_SSHOT)
static void CAPTURE_AddAviChunk(const char * tag, Bit32u size, void * data, Bit32u flags) {
Bit8u chunk[8];Bit8u *index;Bit32u pos, writesize;
chunk[0] = tag[0];chunk[1] = tag[1];chunk[2] = tag[2];chunk[3] = tag[3];
host_writed(&chunk[4], size);
/* Write the actual data */
fwrite(chunk,1,8,capture.video.handle);
writesize = (size+1)&~1;
fwrite(data,1,writesize,capture.video.handle);
pos = capture.video.written + 4;
capture.video.written += writesize + 8;
if (capture.video.indexused + 16 >= capture.video.indexsize ) {
capture.video.index = (Bit8u*)realloc( capture.video.index, capture.video.indexsize + 16 * 4096);
if (!capture.video.index)
E_Exit("Ran out of memory during AVI capturing");
capture.video.indexsize += 16*4096;
}
index = capture.video.index+capture.video.indexused;
capture.video.indexused += 16;
index[0] = tag[0];
index[1] = tag[1];
index[2] = tag[2];
index[3] = tag[3];
host_writed(index+4, flags);
host_writed(index+8, pos);
host_writed(index+12, size);
}
static void CAPTURE_VideoHeader() {
Bit8u avi_header[AVI_HEADER_SIZE];
Bitu main_list;
Bitu header_pos=0;
Bitu save_pos=ftell(capture.video.handle);
#define AVIOUT4(_S_) memcpy(&avi_header[header_pos],_S_,4);header_pos+=4;
#define AVIOUTw(_S_) host_writew(&avi_header[header_pos], _S_);header_pos+=2;
#define AVIOUTd(_S_) host_writed(&avi_header[header_pos], _S_);header_pos+=4;
/* Try and write an avi header */
AVIOUT4("RIFF"); // Riff header
AVIOUTd(AVI_HEADER_SIZE + capture.video.written - 8 + capture.video.indexused);
AVIOUT4("AVI ");
AVIOUT4("LIST"); // List header
main_list = header_pos;
AVIOUTd(0); // TODO size of list
AVIOUT4("hdrl");
AVIOUT4("avih");
AVIOUTd(56); /* # of bytes to follow */
AVIOUTd((Bit32u)(1000000 / capture.video.fps)); /* Microseconds per frame */
AVIOUTd(0);
AVIOUTd(0); /* PaddingGranularity (whatever that might be) */
AVIOUTd(0x110); /* Flags,0x10 has index, 0x100 interleaved */
AVIOUTd(capture.video.frames); /* TotalFrames */
AVIOUTd(0); /* InitialFrames */
AVIOUTd(2); /* Stream count */
AVIOUTd(0); /* SuggestedBufferSize */
AVIOUTd(capture.video.width); /* Width */
AVIOUTd(capture.video.height); /* Height */
AVIOUTd(0); /* TimeScale: Unit used to measure time */
AVIOUTd(0); /* DataRate: Data rate of playback */
AVIOUTd(0); /* StartTime: Starting time of AVI data */
AVIOUTd(0); /* DataLength: Size of AVI data chunk */
/* Video stream list */
AVIOUT4("LIST");
AVIOUTd(4 + 8 + 56 + 8 + 40); /* Size of the list */
AVIOUT4("strl");
/* video stream header */
AVIOUT4("strh");
AVIOUTd(56); /* # of bytes to follow */
AVIOUT4("vids"); /* Type */
AVIOUT4(CODEC_4CC); /* Handler */
AVIOUTd(0); /* Flags */
AVIOUTd(0); /* Reserved, MS says: wPriority, wLanguage */
AVIOUTd(0); /* InitialFrames */
AVIOUTd(1000000); /* Scale */
AVIOUTd((Bit32u)(1000000 * capture.video.fps)); /* Rate: Rate/Scale == samples/second */
AVIOUTd(0); /* Start */
AVIOUTd(capture.video.frames); /* Length */
AVIOUTd(0); /* SuggestedBufferSize */
AVIOUTd(~0); /* Quality */
AVIOUTd(0); /* SampleSize */
AVIOUTd(0); /* Frame */
AVIOUTd(0); /* Frame */
/* The video stream format */
AVIOUT4("strf");
AVIOUTd(40); /* # of bytes to follow */
AVIOUTd(40); /* Size */
AVIOUTd(capture.video.width); /* Width */
AVIOUTd(capture.video.height); /* Height */
// OUTSHRT(1); OUTSHRT(24); /* Planes, Count */
AVIOUTd(0);
AVIOUT4(CODEC_4CC); /* Compression */
AVIOUTd(capture.video.width * capture.video.height*4); /* SizeImage (in bytes?) */
AVIOUTd(0); /* XPelsPerMeter */
AVIOUTd(0); /* YPelsPerMeter */
AVIOUTd(0); /* ClrUsed: Number of colors used */
AVIOUTd(0); /* ClrImportant: Number of colors important */
/* Audio stream list */
AVIOUT4("LIST");
AVIOUTd(4 + 8 + 56 + 8 + 16); /* Length of list in bytes */
AVIOUT4("strl");
/* The audio stream header */
AVIOUT4("strh");
AVIOUTd(56); /* # of bytes to follow */
AVIOUT4("auds");
AVIOUTd(0); /* Format (Optionally) */
AVIOUTd(0); /* Flags */
AVIOUTd(0); /* Reserved, MS says: wPriority, wLanguage */
AVIOUTd(0); /* InitialFrames */
AVIOUTd(4); /* Scale */
AVIOUTd(capture.video.audiorate*4); /* Rate, actual rate is scale/rate */
AVIOUTd(0); /* Start */
if (!capture.video.audiorate)
capture.video.audiorate = 1;
AVIOUTd(capture.video.audiowritten/4); /* Length */
AVIOUTd(0); /* SuggestedBufferSize */
AVIOUTd(~0); /* Quality */
AVIOUTd(4); /* SampleSize */
AVIOUTd(0); /* Frame */
AVIOUTd(0); /* Frame */
/* The audio stream format */
AVIOUT4("strf");
AVIOUTd(16); /* # of bytes to follow */
AVIOUTw(1); /* Format, WAVE_ZMBV_FORMAT_PCM */
AVIOUTw(2); /* Number of channels */
AVIOUTd(capture.video.audiorate); /* SamplesPerSec */
AVIOUTd(capture.video.audiorate*4); /* AvgBytesPerSec*/
AVIOUTw(4); /* BlockAlign */
AVIOUTw(16); /* BitsPerSample */
int nmain = header_pos - main_list - 4;
/* Finish stream list, i.e. put number of bytes in the list to proper pos */
int njunk = AVI_HEADER_SIZE - 8 - 12 - header_pos;
AVIOUT4("JUNK");
AVIOUTd(njunk);
/* Fix the size of the main list */
header_pos = main_list;
AVIOUTd(nmain);
header_pos = AVI_HEADER_SIZE - 12;
AVIOUT4("LIST");
AVIOUTd(capture.video.written+4); /* Length of list in bytes */
AVIOUT4("movi");
/* First add the index table to the end */
memcpy(capture.video.index, "idx1", 4);
host_writed( capture.video.index+4, capture.video.indexused - 8 );
fwrite( capture.video.index, 1, capture.video.indexused, capture.video.handle);
fseek(capture.video.handle, 0, SEEK_SET);
fwrite(&avi_header, 1, AVI_HEADER_SIZE, capture.video.handle);
fseek(capture.video.handle, save_pos, SEEK_SET);
}
static void CAPTURE_VideoEvent(bool pressed) {
if (!pressed)
return;
if (CaptureState & CAPTURE_VIDEO) {
/* Close the video */
CaptureState &= ~CAPTURE_VIDEO;
#if (C_THREADED_CAPTURE)
/* Waits for thread to finish */
if (capture.video.thread_running) {
LOG_MSG("Finalizing video. Please wait...");
SDL_WaitThread(video_thread,NULL);
}
#endif
/* Adds AVI header to the file */
CAPTURE_VideoHeader();
/* Close video file and free memory */
fclose( capture.video.handle );
free( capture.video.index );
free( capture.video.buf );
delete capture.video.codec;
capture.video.handle = 0;
LOG_MSG("Stopped capturing video.");
} else {
if (!capture.video.thread_running)
CaptureState |= CAPTURE_VIDEO;
}
}
int CAPTURE_VideoCompressFrame(video_capture_t *videohandle, video_chunk_t chunk) {
Bit8u doubleRow[SCALER_MAXWIDTH*4];
Bitu countWidth = videohandle->width;
int codecFlags;
if (videohandle->frames % 300 == 0)
codecFlags = 1;
else codecFlags = 0;
if (!videohandle->codec->PrepareCompressFrame( codecFlags, videohandle->format, (char *)chunk.pal, videohandle->buf, videohandle->bufSize))
{
CAPTURE_VideoEvent(TRUE);
return 1;
}
for (Bit32u i=0;i<videohandle->height;i++) {
void * rowPointer;
if (videohandle->flags & CAPTURE_FLAG_DBLW) {
void *srcLine;
Bitu x;
Bitu countWidth = videohandle->width >> 1;
if (videohandle->flags & CAPTURE_FLAG_DBLH)
srcLine=(chunk.videobuf+(i >> 1)*videohandle->pitch);
else
srcLine=(chunk.videobuf+(i >> 0)*videohandle->pitch);
switch ( videohandle->bpp) {
case 8:
for (x=0;x<countWidth;x++)
((Bit8u *)doubleRow)[x*2+0] =
((Bit8u *)doubleRow)[x*2+1] = ((Bit8u *)srcLine)[x];
break;
case 15:
case 16:
for (x=0;x<countWidth;x++)
((Bit16u *)doubleRow)[x*2+0] =
((Bit16u *)doubleRow)[x*2+1] = ((Bit16u *)srcLine)[x];
break;
case 32:
for (x=0;x<countWidth;x++)
((Bit32u *)doubleRow)[x*2+0] =
((Bit32u *)doubleRow)[x*2+1] = ((Bit32u *)srcLine)[x];
break;
}
rowPointer=doubleRow;
} else {
if (videohandle->flags & CAPTURE_FLAG_DBLH)
rowPointer=(chunk.videobuf+(i >> 1)*videohandle->pitch);
else
rowPointer=(chunk.videobuf+(i >> 0)*videohandle->pitch);
}
videohandle->codec->CompressLines( 1, &rowPointer );
}
int written = videohandle->codec->FinishCompressFrame();
if (written < 0) {
CAPTURE_VideoEvent(TRUE);
return 1;
}
CAPTURE_AddAviChunk( "00dc", written, videohandle->buf, codecFlags & 1 ? 0x10 : 0x0);
videohandle->frames++;
// LOG_MSG("Frame %d video %d audio %d",videohandle->frames, written, videohandle->audioused *4 );
if ( chunk.audioused ) {
CAPTURE_AddAviChunk( "01wb", chunk.audioused * 4, chunk.audiobuf, 0);
videohandle->audiowritten = chunk.audioused*4;
}
/* Adds AVI header to the file */
CAPTURE_VideoHeader();
return 0;
}
#if (C_THREADED_CAPTURE)
int CAPTURE_VideoThread(void *videohandleptr) {
video_capture_t *videohandle=(video_capture_t*)videohandleptr;
videohandle->thread_running = true;
int rc = 0;
/* Process queue */
while (!videohandle->q.empty()) {
// LOG_MSG("queue size %d",videohandle->q.size());
/* Process a block and write it to disk */
if (!rc) rc = CAPTURE_VideoCompressFrame(videohandle,videohandle->q.front());
else CaptureState &= ~CAPTURE_VIDEO;
free(videohandle->q.front().videobuf);
free(videohandle->q.front().audiobuf);
free(videohandle->q.front().pal);
/* Delete chunk from queue */
videohandle->q.pop();
}
videohandle->thread_running = false;
return rc;
}
#endif // C_THREADED_CAPTURE
#endif // C_SSHOT
void CAPTURE_AddImage(Bitu width, Bitu height, Bitu bpp, Bitu pitch, Bitu flags, float fps, Bit8u * data, Bit8u * pal) {
#if (C_SSHOT)
Bitu i;
Bit8u doubleRow[SCALER_MAXWIDTH*4];
Bitu countWidth = width;
if (flags & CAPTURE_FLAG_DBLH)
height *= 2;
if (flags & CAPTURE_FLAG_DBLW)
width *= 2;
if (height > SCALER_MAXHEIGHT)
return;
if (width > SCALER_MAXWIDTH)
return;
if (CaptureState & CAPTURE_IMAGE) {
png_structp png_ptr;
png_infop info_ptr;
png_color palette[256];
CaptureState &= ~CAPTURE_IMAGE;
/* Open the actual file */
FILE * fp=OpenCaptureFile("Screenshot",".png");
if (!fp) goto skip_shot;
/* First try to alloacte the png structures */
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,NULL, NULL);
if (!png_ptr) goto skip_shot;
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr,(png_infopp)NULL);
goto skip_shot;
}
/* Finalize the initing of png library */
png_init_io(png_ptr, fp);
png_set_compression_level(png_ptr,Z_BEST_COMPRESSION);
/* set other zlib parameters */
png_set_compression_mem_level(png_ptr, 8);
png_set_compression_strategy(png_ptr,Z_DEFAULT_STRATEGY);
png_set_compression_window_bits(png_ptr, 15);
png_set_compression_method(png_ptr, 8);
png_set_compression_buffer_size(png_ptr, 8192);
if (bpp==8) {
png_set_IHDR(png_ptr, info_ptr, width, height,
8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
for (i=0;i<256;i++) {
palette[i].red=pal[i*4+0];
palette[i].green=pal[i*4+1];
palette[i].blue=pal[i*4+2];
}
png_set_PLTE(png_ptr, info_ptr, palette,256);
} else {
png_set_bgr( png_ptr );
png_set_IHDR(png_ptr, info_ptr, width, height,
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
}
png_write_info(png_ptr, info_ptr);
for (i=0;i<height;i++) {
void *rowPointer;
void *srcLine;
if (flags & CAPTURE_FLAG_DBLH)
srcLine=(data+(i >> 1)*pitch);
else
srcLine=(data+(i >> 0)*pitch);
rowPointer=srcLine;
switch (bpp) {
case 8:
if (flags & CAPTURE_FLAG_DBLW) {
for (Bitu x=0;x<countWidth;x++)
doubleRow[x*2+0] =
doubleRow[x*2+1] = ((Bit8u *)srcLine)[x];
rowPointer = doubleRow;
}
break;
case 15:
if (flags & CAPTURE_FLAG_DBLW) {
for (Bitu x=0;x<countWidth;x++) {
Bitu pixel = ((Bit16u *)srcLine)[x];
doubleRow[x*6+0] = doubleRow[x*6+3] = ((pixel& 0x001f) * 0x21) >> 2;
doubleRow[x*6+1] = doubleRow[x*6+4] = ((pixel& 0x03e0) * 0x21) >> 7;
doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0x7c00) * 0x21) >> 12;
}
} else {
for (Bitu x=0;x<countWidth;x++) {
Bitu pixel = ((Bit16u *)srcLine)[x];
doubleRow[x*3+0] = ((pixel& 0x001f) * 0x21) >> 2;
doubleRow[x*3+1] = ((pixel& 0x03e0) * 0x21) >> 7;
doubleRow[x*3+2] = ((pixel& 0x7c00) * 0x21) >> 12;
}
}
rowPointer = doubleRow;
break;
case 16:
if (flags & CAPTURE_FLAG_DBLW) {
for (Bitu x=0;x<countWidth;x++) {
Bitu pixel = ((Bit16u *)srcLine)[x];
doubleRow[x*6+0] = doubleRow[x*6+3] = ((pixel& 0x001f) * 0x21) >> 2;
doubleRow[x*6+1] = doubleRow[x*6+4] = ((pixel& 0x07e0) * 0x41) >> 9;
doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0xf800) * 0x21) >> 13;
}
} else {
for (Bitu x=0;x<countWidth;x++) {
Bitu pixel = ((Bit16u *)srcLine)[x];
doubleRow[x*3+0] = ((pixel& 0x001f) * 0x21) >> 2;
doubleRow[x*3+1] = ((pixel& 0x07e0) * 0x41) >> 9;
doubleRow[x*3+2] = ((pixel& 0xf800) * 0x21) >> 13;
}
}
rowPointer = doubleRow;
break;
case 32:
if (flags & CAPTURE_FLAG_DBLW) {
for (Bitu x=0;x<countWidth;x++) {
doubleRow[x*6+0] = doubleRow[x*6+3] = ((Bit8u *)srcLine)[x*4+0];
doubleRow[x*6+1] = doubleRow[x*6+4] = ((Bit8u *)srcLine)[x*4+1];
doubleRow[x*6+2] = doubleRow[x*6+5] = ((Bit8u *)srcLine)[x*4+2];
}
} else {
for (Bitu x=0;x<countWidth;x++) {
doubleRow[x*3+0] = ((Bit8u *)srcLine)[x*4+0];
doubleRow[x*3+1] = ((Bit8u *)srcLine)[x*4+1];
doubleRow[x*3+2] = ((Bit8u *)srcLine)[x*4+2];
}
}
rowPointer = doubleRow;
break;
}
png_write_row(png_ptr, (png_bytep)rowPointer);
}
/* Finish writing */
png_write_end(png_ptr, 0);
/*Destroy PNG structs*/
png_destroy_write_struct(&png_ptr, &info_ptr);
/*close file*/
fclose(fp);
}
skip_shot:
if (CaptureState & CAPTURE_VIDEO) {
/* Restarts capturing if any of the test fails */
if (capture.video.handle && (
capture.video.width != width ||
capture.video.height != height ||
capture.video.bpp != bpp ||
capture.video.fps != fps)) {
/* Ends current capturing */
CAPTURE_VideoEvent(true);
/* Toggle video capture event */
CaptureState |= CAPTURE_VIDEO;
}
/* Choose video format */
switch (bpp) {
case 8:capture.video.format = ZMBV_FORMAT_8BPP;break;
case 15:capture.video.format = ZMBV_FORMAT_15BPP;break;
case 16:capture.video.format = ZMBV_FORMAT_16BPP;break;
case 32:capture.video.format = ZMBV_FORMAT_32BPP;break;
default:
CAPTURE_VideoEvent(TRUE);
return;
}
/* Initialize video handle */
if (!capture.video.handle) {
capture.video.handle = OpenCaptureFile("Video",".avi");
if (!capture.video.handle) {
CAPTURE_VideoEvent(TRUE);
return;
}
capture.video.codec = new VideoCodec();
if (!capture.video.codec) {
CAPTURE_VideoEvent(TRUE);
return;
}
if (!capture.video.codec->SetupCompress( width, height)) {
CAPTURE_VideoEvent(TRUE);
return;
}
capture.video.bufSize = capture.video.codec->NeededSize(width, height, capture.video.format);
capture.video.buf = malloc( capture.video.bufSize );
if (!capture.video.buf) {
CAPTURE_VideoEvent(TRUE);
return;
}
capture.video.index = (Bit8u*)malloc( 16*4096 );
if (!capture.video.index) {
CAPTURE_VideoEvent(TRUE);
return;
}
capture.video.indexsize = 16*4096;
capture.video.indexused = 8;
capture.video.width = width;
capture.video.height = height;
capture.video.pitch = pitch;
capture.video.flags = flags;
capture.video.bpp = bpp;
capture.video.fps = fps;
for (int i=0;i<AVI_HEADER_SIZE;i++)
fputc(0,capture.video.handle);
capture.video.frames = 0;
capture.video.written = 0;
capture.video.audioused = 0;
capture.video.audiowritten = 0;
capture.video.thread_running = false;
}
video_chunk_t chunk;
#if (C_THREADED_CAPTURE)
/* Create video chunk buffers */
chunk.videobuf = (Bit8u *)malloc(capture.video.height*capture.video.pitch);
if (!chunk.videobuf) {
CAPTURE_VideoEvent(true);
return;
}
chunk.audiobuf = (Bit16s *)malloc(capture.video.audioused*4);
if (!chunk.audiobuf) {
CAPTURE_VideoEvent(true);
return;
}
chunk.pal = (Bit8u *)malloc(256*4);
if (!chunk.pal) {
CAPTURE_VideoEvent(true);
return;
}
/* Copy data to chunk */
memcpy(chunk.videobuf,data,capture.video.height*capture.video.pitch);
memcpy(chunk.audiobuf,&capture.video.audiobuf, capture.video.audioused*4);
memcpy(chunk.pal,pal,256*4);
chunk.audioused = capture.video.audioused;
capture.video.audioused = 0;
/* Push avi chunk to queue */
capture.video.q.push(chunk);
/* If queue exceeds size limit, wait for capture thread to empty queue */
if (capture.video.q.size()>MAX_QUEUE_SIZE) {
LOG_MSG("Writing video to disk. Please wait...");
SDL_WaitThread(video_thread,NULL);
}
/* If thread is not already running, start it */
if (!capture.video.thread_running)
video_thread = SDL_CreateThread(CAPTURE_VideoThread, (void*)&capture.video);
#else
/* Compress frame directly (no thread) */
chunk.videobuf = data;
chunk.audiobuf = (Bit16s*)&capture.video.audiobuf;
chunk.audioused = capture.video.audioused;
chunk.pal = pal;
capture.video.audioused = 0;
CAPTURE_VideoCompressFrame(&capture.video,chunk);
#endif
}
#endif
return;
}
#if (C_SSHOT)
static void CAPTURE_ScreenShotEvent(bool pressed) {
if (!pressed)
return;
CaptureState |= CAPTURE_IMAGE;
}
#endif
/* WAV capturing */
static Bit8u wavheader[]={
'R','I','F','F', 0x0,0x0,0x0,0x0, /* Bit32u Riff Chunk ID / Bit32u riff size */
'W','A','V','E', 'f','m','t',' ', /* Bit32u Riff Format / Bit32u fmt chunk id */
0x10,0x0,0x0,0x0, 0x1,0x0,0x2,0x0, /* Bit32u fmt size / Bit16u encoding/ Bit16u channels */
0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0, /* Bit32u freq / Bit32u byterate */
0x4,0x0,0x10,0x0, 'd','a','t','a', /* Bit16u byte-block / Bit16u bits / Bit16u data chunk id */
0x0,0x0,0x0,0x0, /* Bit32u data size */
};
void CAPTURE_AddWave(Bit32u freq, Bit32u len, Bit16s * data) {
#if (C_SSHOT)
if (CaptureState & CAPTURE_VIDEO) {
Bitu left = WAVE_BUF - capture.video.audioused;
if (left > len)
left = len;
memcpy( &capture.video.audiobuf[capture.video.audioused], data, left*4);
capture.video.audioused += left;
capture.video.audiorate = freq;
}
#endif
if (CaptureState & CAPTURE_WAVE) {
if (!capture.wave.handle) {
capture.wave.handle=OpenCaptureFile("Wave Output",".wav");
if (!capture.wave.handle) {
CaptureState &= ~CAPTURE_WAVE;
return;
}
capture.wave.length = 0;
capture.wave.used = 0;
capture.wave.freq = freq;
fwrite(wavheader,1,sizeof(wavheader),capture.wave.handle);
}
Bit16s * read = data;
while (len > 0 ) {
Bitu left = WAVE_BUF - capture.wave.used;
if (!left) {
fwrite(capture.wave.buf,1,4*WAVE_BUF,capture.wave.handle);
capture.wave.length += 4*WAVE_BUF;
capture.wave.used = 0;
left = WAVE_BUF;
}
if (left > len)
left = len;
memcpy( &capture.wave.buf[capture.wave.used], read, left*4);
capture.wave.used += left;
read += left*2;
len -= left;
}
}
}
static void CAPTURE_WaveEvent(bool pressed) {
if (!pressed)
return;
/* Check for previously opened wave file */
if (capture.wave.handle) {
LOG_MSG("Stopped capturing wave output.");
/* Write last piece of audio in buffer */
fwrite(capture.wave.buf,1,capture.wave.used*4,capture.wave.handle);
capture.wave.length+=capture.wave.used*4;
/* Fill in the header with useful information */
host_writed(&wavheader[0x04],capture.wave.length+sizeof(wavheader)-8);
host_writed(&wavheader[0x18],capture.wave.freq);
host_writed(&wavheader[0x1C],capture.wave.freq*4);
host_writed(&wavheader[0x28],capture.wave.length);
fseek(capture.wave.handle,0,0);
fwrite(wavheader,1,sizeof(wavheader),capture.wave.handle);
fclose(capture.wave.handle);
capture.wave.handle=0;
CaptureState |= CAPTURE_WAVE;
}
CaptureState ^= CAPTURE_WAVE;
}
/* MIDI capturing */
static Bit8u midi_header[]={
'M','T','h','d', /* Bit32u, Header Chunk */
0x0,0x0,0x0,0x6, /* Bit32u, Chunk Length */
0x0,0x0, /* Bit16u, Format, 0=single track */
0x0,0x1, /* Bit16u, Track Count, 1 track */
0x01,0xf4, /* Bit16u, Timing, 2 beats/second with 500 frames */
'M','T','r','k', /* Bit32u, Track Chunk */
0x0,0x0,0x0,0x0, /* Bit32u, Chunk Length */
//Track data
};
static void RawMidiAdd(Bit8u data) {
capture.midi.buffer[capture.midi.used++]=data;
if (capture.midi.used >= MIDI_BUF ) {
capture.midi.done += capture.midi.used;
fwrite(capture.midi.buffer,1,MIDI_BUF,capture.midi.handle);
capture.midi.used = 0;
}
}
static void RawMidiAddNumber(Bit32u val) {
if (val & 0xfe00000) RawMidiAdd((Bit8u)(0x80|((val >> 21) & 0x7f)));
if (val & 0xfffc000) RawMidiAdd((Bit8u)(0x80|((val >> 14) & 0x7f)));
if (val & 0xfffff80) RawMidiAdd((Bit8u)(0x80|((val >> 7) & 0x7f)));
RawMidiAdd((Bit8u)(val & 0x7f));
}
void CAPTURE_AddMidi(bool sysex, Bitu len, Bit8u * data) {
if (!capture.midi.handle) {
capture.midi.handle=OpenCaptureFile("Raw Midi",".mid");
if (!capture.midi.handle) {
return;
}
fwrite(midi_header,1,sizeof(midi_header),capture.midi.handle);
capture.midi.last=PIC_Ticks;
}
Bit32u delta=PIC_Ticks-capture.midi.last;
capture.midi.last=PIC_Ticks;
RawMidiAddNumber(delta);
if (sysex) {
RawMidiAdd( 0xf0 );
RawMidiAddNumber( len );
}
for (Bitu i=0;i<len;i++)
RawMidiAdd(data[i]);
}
static void CAPTURE_MidiEvent(bool pressed) {
if (!pressed)
return;
/* Check for previously opened wave file */
if (capture.midi.handle) {
LOG_MSG("Stopping raw midi saving and finalizing file.");
//Delta time
RawMidiAdd(0x00);
//End of track event
RawMidiAdd(0xff);
RawMidiAdd(0x2F);
RawMidiAdd(0x00);
/* clear out the final data in the buffer if any */
fwrite(capture.midi.buffer,1,capture.midi.used,capture.midi.handle);
capture.midi.done+=capture.midi.used;
fseek(capture.midi.handle,18, SEEK_SET);
Bit8u size[4];
size[0]=(Bit8u)(capture.midi.done >> 24);
size[1]=(Bit8u)(capture.midi.done >> 16);
size[2]=(Bit8u)(capture.midi.done >> 8);
size[3]=(Bit8u)(capture.midi.done >> 0);
fwrite(&size,1,4,capture.midi.handle);
fclose(capture.midi.handle);
capture.midi.handle=0;
CaptureState &= ~CAPTURE_MIDI;
return;
}
CaptureState ^= CAPTURE_MIDI;
if (CaptureState & CAPTURE_MIDI) {
LOG_MSG("Preparing for raw midi capture, will start with first data.");
capture.midi.used=0;
capture.midi.done=0;
capture.midi.handle=0;
} else {
LOG_MSG("Stopped capturing raw midi before any data arrived.");
}
}
class HARDWARE:public Module_base{
public:
HARDWARE(Section* configuration):Module_base(configuration){
Section_prop * section = static_cast<Section_prop *>(configuration);
Prop_path* proppath= section->Get_path("captures");
capturedir = proppath->realpath;
CaptureState = 0;
MAPPER_AddHandler(CAPTURE_WaveEvent,MK_f6,MMOD1,"recwave","Rec Wave");
MAPPER_AddHandler(CAPTURE_MidiEvent,MK_f8,MMOD1|MMOD2,"caprawmidi","Cap MIDI");
#if (C_SSHOT)
MAPPER_AddHandler(CAPTURE_ScreenShotEvent,MK_f5,MMOD1,"scrshot","Screenshot");
MAPPER_AddHandler(CAPTURE_VideoEvent,MK_f5,MMOD1|MMOD2,"video","Video");
#endif
}
~HARDWARE(){
if (capture.wave.handle) CAPTURE_WaveEvent(true);
if (capture.midi.handle) CAPTURE_MidiEvent(true);
}
};
static HARDWARE* test;
void HARDWARE_Destroy(Section * sec) {
delete test;
}
void HARDWARE_Init(Section * sec) {
test = new HARDWARE(sec);
sec->AddDestroyFunction(&HARDWARE_Destroy,true);
}
(c'est la dernière version, un peu différente de celle que j'avais).
Là aussi, la section skip_shot: de la fonction CAPTURE_AddImage est le fil directeur. Elle appelle le thread et remplie la queue, essentiellement.
Bon alors premier problème à la compilation :
TRUE is undefined
(sur 9 lignes)
Bon ça c'est facile, DOSBox est écrit en C++ (du moins le patch utilise la stdlib, notamment std::queue), même si c'est souvent mélangé avec du C, donc j'ai le droit de remplacer TRUE par true (ou 1, mais bon...).
Ensuite, ça plante au bout de 5 minutes. Maintenant, j'avais déjà modifié un patch très similaire pour limiter ça à 10 FPS (DOSBox enregistre à 70 FPS des jeux DOS... C'est un peu overkill) - d'une manière assez crade d'ailleurs - tout en essayant de résoudre ce problème. Ça a tellement mis le plantage à plus tard que je croyais que c'était réglé.. Et puis j'utilise la capture vidéo de DOSBox une fois par an. Voilà pourquoi j'y reviens aujourd'hui : c'est pas réglé du tout.
Pourquoi ça plante ? Parce que le thread vidéo meurt dès que la queue est vide, et un nouveau est créé dès que la queue n'est plus vide... Ça donne énormement de threads créés à chaque seconde sur mon core2Duo, et une race condition qui arrive tôt ou tard !
Voici pour plus de clarté le code de la fonction du thread (déjà dans le code ci-dessus, mais y'a une tonne de code autour qui s'occupe des screenshots en PNG, de la capture de la musique MIDI, et d'autres trucs qui n'ont rien à voir) :
#if (C_THREADED_CAPTURE)
int CAPTURE_VideoThread(void *videohandleptr) {
video_capture_t *videohandle=(video_capture_t*)videohandleptr;
videohandle->thread_running = true;
int rc = 0;
/* Process queue */
while (!videohandle->q.empty()) {
// LOG_MSG("queue size %d",videohandle->q.size());
/* Process a block and write it to disk */
if (!rc) rc = CAPTURE_VideoCompressFrame(videohandle,videohandle->q.front());
else CaptureState &= ~CAPTURE_VIDEO;
free(videohandle->q.front().videobuf);
free(videohandle->q.front().audiobuf);
free(videohandle->q.front().pal);
/* Delete chunk from queue */
videohandle->q.pop();
}
videohandle->thread_running = false;
return rc;
}
En fait, y'a trois symptômes :
1) La création/fermeture d'autant de threads à la seconde fait que ça rame énormément (du coup la queue est souvent pleine, ce qui stop l'émulation le temps d'un SDL_WaitThread), ce qui entraîne...
- une belle fuite mémoire (on attend 10 Go en taille virtuelle du processus au bout de quelques secondes dans HTOP)
- une race condition tôt ou tard sur la file qui fait que ça plante (le thread machin commence son boulot... oups ! la queue est en fait vide ! Ou un "double free" arrive -> PAF! segfault)
Pour régler ça, le videohandle utilisé par le thread devient une variable globale, le thread ne meurt pas tant qu'on doit capturer la vidéo (CaptureState & CAPTURE_VIDEO est vrai). et on créé le thread dans CAPTURE_AddImage -> section skip_shot uniquement si le videohandle est NULL (au lieu d'utiliser "!capture.video.thread_running". Ceci dit j'pense que ce dernier changement est peut-être superflu, je ne sais plus trop pourquoi je l'ai fait...) :
video_capture_t *videohandle = NULL;
int CAPTURE_VideoThread(void *videohandleptr) {
videohandle=(video_capture_t*)videohandleptr;
videohandle->thread_running = true;
int rc = 0;
/* Process queue */
while (CaptureState & CAPTURE_VIDEO) {
while (!videohandle->q.empty()) {
// LOG_MSG("queue size %d",videohandle->q.size());
/* Process a block and write it to disk */
if (!rc) rc = CAPTURE_VideoCompressFrame(videohandle,videohandle->q.front());
else CaptureState &= ~CAPTURE_VIDEO;
free(videohandle->q.front().videobuf);
free(videohandle->q.front().audiobuf);
free(videohandle->q.front().pal);
/* Delete chunk from queue */
videohandle->q.pop();
}
}
videohandle->thread_running = false;
return rc;
}
/* If thread is not already running, start it */
if (!videohandle)
video_thread = SDL_CreateThread(CAPTURE_VideoThread, (void*)&capture.video);
Bon maintenant, c'est bien il n'y a plus de fuite mémoire et c'est stable et rapide (un seul thread créé... et gardé tant que CaptureState & CAPTURE_VIDEO est vrai). Ça a occasionné deux problèmes, mais ils ont été assez faciles à résoudre :
commit 6d7b7d2232a7e35bd66be5078b7d86dbd08d01c6
Author: Maximilien Noal <mnoal@epsi.fr>
Date: Mon Dec 10 13:15:44 2012 +0100
FIXED: Can now record several videos
Signed-off-by: Maximilien Noal <mnoal@epsi.fr>
diff --git a/hardware.cpp b/hardware.cpp
index a4d9d57..b4253aa 100644
--- a/hardware.cpp
+++ b/hardware.cpp
@@ -445,6 +444,7 @@ int CAPTURE_VideoThread(void *videohandleptr) {
}
videohandle->thread_running = false;
+ videohandle = NULL;
return rc;
}
(le second fichier vidéo était bien créé, sauf qu'il était vide...)
commit d60962397cc689dc5b802ff2455505df4d6c2090
Author: Maximilien Noal <mnoal@epsi.fr>
Date: Mon Dec 10 19:34:37 2012 +0100
FIXED: SDL_Wait never returned when the queue was full
replaced by a SDL_Delay while the queue is full.
Signed-off-by: Maximilien Noal <mnoal@epsi.fr>
diff --git a/hardware.cpp b/hardware.cpp
index b4253aa..4195862 100644
--- a/hardware.cpp
+++ b/hardware.cpp
@@ -18,6 +18,7 @@
/* $Id: hardware.cpp,v 1.23 2009/10/11 18:09:22 qbix79 Exp $ */
+
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
@@ -699,7 +700,10 @@ skip_shot:
/* If queue exceeds size limit, wait for capture thread to empty queue */
if (capture.video.q.size()>MAX_QUEUE_SIZE) {
LOG_MSG("Writing video to disk. Please wait...");
- SDL_WaitThread(video_thread,NULL);
+ while(capture.video.q.size()>MAX_QUEUE_SIZE) {
+ SDL_Delay(1);
+ }
}
/* If thread is not already running, start it */
(bon ça j'ai eu du mal à le tester vu que maintenant la queue n'est quasiment jamais supérieur à un élément... et aussi comme un con j'ai d'abord essayé sleep avant d'utiliser SDL_Delay qui a l'avantage de fonctionner partout sans faire des #ifdef)
Et maintenant... je peux enregistrer des vidéos pendant des heures sans que ça plante. Ça ne rame même pas dans Quake (du coup je n'ai pas remis ma modif pour limiter ça à 10 FPS, et tant mieux !).
Créer plein de threads à la seconde, ça peut vraiment tuer les perfs... en plus de tout faire bugger.
-----------------------------------------
(bon sinon pour ceux qui voulaient lire mon mémoire, y'a pas de problème pour le publier en fait, je le met à disposition demain)
Dernière modification par The Uploader (Le 11/12/2012, à 02:16)
- Oldies PC : Intel Pentium 3 @ 800 Mhz sur CM ASUS P2B-F, GeForce 4 Ti4800 SE, Disque Dur Hitachi 160 Go, 512 Mo de RAM, 3DFX Voodoo 2, Sound Blaster 16 ISA PnP, Windows 98 SE / XP)
- Desktop : Intel Core i7 6700K @ 4 GHz sur CM ASUS Z170-P, GeForce GTX 1070, SSD Samsung 850 EVO 1 To, 16 Go de RAM, Disque Dur Seagate Barracuda 3 To, Windows 10
Hors ligne
#236 Le 11/12/2012, à 03:05
- Pylades
Re : /* Topic des codeurs [8] */
Tiens, moi aussi j’ai chopé de la Vitamine. ^^
@ The Uploader : eh bien, beau travail !
“Any if-statement is a goto. As are all structured loops.
“And sometimes structure is good. When it’s good, you should use it.
“And sometimes structure is _bad_, and gets into the way, and using a goto is just much clearer.”
Linus Torvalds – 12 janvier 2003
Hors ligne
#237 Le 11/12/2012, à 06:13
- Pylades
Re : /* Topic des codeurs [8] */
Bon, il va falloir que je me penche sur NWCK et LCSQ, seuls douze problèmes me sont encore accessibles sans ces deux-là.
Dernière modification par Πυλάδης (Le 11/12/2012, à 06:13)
“Any if-statement is a goto. As are all structured loops.
“And sometimes structure is good. When it’s good, you should use it.
“And sometimes structure is _bad_, and gets into the way, and using a goto is just much clearer.”
Linus Torvalds – 12 janvier 2003
Hors ligne
#238 Le 11/12/2012, à 06:54
- grim7reaper
Re : /* Topic des codeurs [8] */
Idem pour moi, mais avec LIA.
Les proba’ ça m’a toujours fait chier de toutes façons…
Hors ligne
#239 Le 11/12/2012, à 07:15
- Pylades
Re : /* Topic des codeurs [8] */
Je me fais troller par Python… ><"
Premier échec parce qu’il formate les petits float avec un exposant, puis un second échec sur un dataset un poil plus violent, parce qu’il ne gère pas les trop petits float.
J’hésite à contourner ce problème où jouer à la loterie pour avoir un dataset pas trop violent…
Édit :
>>> math.log10(-2.0633788316110735e-17)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: math domain error
Mais merde, quoi !
Dernière modification par Πυλάδης (Le 11/12/2012, à 07:18)
“Any if-statement is a goto. As are all structured loops.
“And sometimes structure is good. When it’s good, you should use it.
“And sometimes structure is _bad_, and gets into the way, and using a goto is just much clearer.”
Linus Torvalds – 12 janvier 2003
Hors ligne
#240 Le 11/12/2012, à 07:26
- Pylades
Re : /* Topic des codeurs [8] */
OK, ça passe avec 40, 42, 44 ou 47 ; mais pas 41, 43, 45, 46, 48, 49 ou 50.
Fuck yeah!
Édit : bon, j’ai un peu touché au code, ça semble passer pour tout, maintenant (je n’ai pas compris).
On va voir si Rosalind est d’accord…
Édit : ben ouais, c’est passé.
Dernière modification par Πυλάδης (Le 11/12/2012, à 07:39)
“Any if-statement is a goto. As are all structured loops.
“And sometimes structure is good. When it’s good, you should use it.
“And sometimes structure is _bad_, and gets into the way, and using a goto is just much clearer.”
Linus Torvalds – 12 janvier 2003
Hors ligne
#241 Le 11/12/2012, à 08:57
- grim7reaper
Re : /* Topic des codeurs [8] */
Πυλάδης en train de coder
J’espère que tu vas au moins essayer de comprendre après coup.
Sinon c’est moche…
Moi j’arrête de résoudre pour le moment, du moins tant que j’ai pas fait un parser super classe pour Newick (c’est déjà bien propre, mais je peux mieux faire et parfois je suis un peu perfectionniste sur les bords )
Dernière modification par grim7reaper (Le 11/12/2012, à 08:58)
Hors ligne
#242 Le 11/12/2012, à 09:17
- Rolinh
Re : /* Topic des codeurs [8] */
Les proba’ ça m’a toujours fait chier de toutes façons…
Bah on est deux en tout cas.
@TheUploader: gg
Hors ligne
#243 Le 11/12/2012, à 10:02
- Mindiell
Re : /* Topic des codeurs [8] */
Èdp'uq t(p' joledèp avp .lvu zpèpu p' .dp h :lvu epodpé xlnnp'è
hum, j'ai oublié que je tapais en bépo. Désolé !
Donc, j'en profite pour vous demander comment vous feriez pour faire communiquer deux processus python ensemble ?
Sachant qu'ils peuvent être sur deux machines différentes et qu'en fait, ils se partagent une queue. Le premier écrit dedans et le deuxième vient y lire.
Je souhaite aussi que ça fonctionne en évènementiel.
PS : dur dur de taper en bépo...
Hors ligne
#244 Le 11/12/2012, à 10:27
- The Uploader
Re : /* Topic des codeurs [8] */
En Ruby, j'aurais utilisé stdlib::DRb, en Python je sais pas.
- Oldies PC : Intel Pentium 3 @ 800 Mhz sur CM ASUS P2B-F, GeForce 4 Ti4800 SE, Disque Dur Hitachi 160 Go, 512 Mo de RAM, 3DFX Voodoo 2, Sound Blaster 16 ISA PnP, Windows 98 SE / XP)
- Desktop : Intel Core i7 6700K @ 4 GHz sur CM ASUS Z170-P, GeForce GTX 1070, SSD Samsung 850 EVO 1 To, 16 Go de RAM, Disque Dur Seagate Barracuda 3 To, Windows 10
Hors ligne
#245 Le 11/12/2012, à 10:37
- Mindiell
Re : /* Topic des codeurs [8] */
Oui, il existe Pyro en python, par exemple.
Hors ligne
#246 Le 11/12/2012, à 11:08
- Blabla404
Re : /* Topic des codeurs [8] */
Donc si je veux lire deux entiers sur l'entrée standard je peux faire ça.
line = input().split()
a = int(l[0])
b = int(l[1])
print(a+b)
Ou il y a mieux ? Là si il y a des retours à la ligne après le premier entier ça ne marche pas mais si l'entrée n'est pas trop mal formatée ça va.
Hors ligne
#247 Le 11/12/2012, à 12:00
- Mindiell
Re : /* Topic des codeurs [8] */
Oui, tout dépend toujours de comment tu considères le formatage de l'entrée.
Tu peux aussi faire ça :
print(sum(int(input().split())))
Mais ça devient moins lisible, forcément
J'avoue que de pouvoir passer tout en paramètre et tout réutiliser est un truc assez sympa de python :
Là, en utilisant sum, je viens de donner la possibilité à ton programme de rentrer 0 à n arguments sans modifier le code.
Dernière modification par Mindiell (Le 11/12/2012, à 12:01)
Hors ligne
#248 Le 11/12/2012, à 12:22
- Blabla404
Re : /* Topic des codeurs [8] */
Merci, en fait la somme était là pour vérifier que a et b étaient bien utilisés comme des entiers. Mais pouvoir factoriser le int est sympa en effet. Bon je vais refaire les premiers problèmes en python et je verrai s'il y a des choses qui me gênent.
Hors ligne
#249 Le 11/12/2012, à 12:35
- Mindiell
Re : /* Topic des codeurs [8] */
Sinon, moi je mets ça dans des fichiers. Et j'ai deux fonctions :
- Chargement d'une liste
Qui me permet, depuis un fichier, de charger une liste de chaines (proteines ou ADN ou ARN, etc...)
- Chargement d'un fichier FASTA
Qui me permet, depuis un fichier ua format FASTA de remonter un dico de chaines (le nom en clef et la chaine en valeur)
Comme ça je fais mes tests facilement, et quand je veux résoudre un problème, j'ouvre le fichier donné, et je copie-colle dans mon fichier. C'est rapide
Hors ligne
#250 Le 11/12/2012, à 12:43
- The Uploader
Re : /* Topic des codeurs [8] */
- Oldies PC : Intel Pentium 3 @ 800 Mhz sur CM ASUS P2B-F, GeForce 4 Ti4800 SE, Disque Dur Hitachi 160 Go, 512 Mo de RAM, 3DFX Voodoo 2, Sound Blaster 16 ISA PnP, Windows 98 SE / XP)
- Desktop : Intel Core i7 6700K @ 4 GHz sur CM ASUS Z170-P, GeForce GTX 1070, SSD Samsung 850 EVO 1 To, 16 Go de RAM, Disque Dur Seagate Barracuda 3 To, Windows 10
Hors ligne