[Thomson] Vidéo avec son en streaming
Modérateurs : Papy.G, fneck, Carl
Re: [Thomson] Vidéo avec son en streaming
oui, les puristes... m'enfin ils ont vu la puissance de calcul embarquée sur le moindre disque dur ? sans parler des SSD ?
chacun d'entre eux a plusieurs cores ARM, il y a même un mec qui a installé Linux sur son disque dur...
du coup l'arduino pour la carte SD je vois ça comme un disque dur pour le MO5 et ça me paraît tout à fait normal.
chacun d'entre eux a plusieurs cores ARM, il y a même un mec qui a installé Linux sur son disque dur...
du coup l'arduino pour la carte SD je vois ça comme un disque dur pour le MO5 et ça me paraît tout à fait normal.
MO5 - MO5 Platini - TO7 - TO7/70 - TO8 - TO9+
Re: [Thomson] Vidéo avec son en streaming
Nouvelle version de SDANIM7 à http://dcmoto.free.fr/bricolage/sdanim7/index.html
Elle corrige quelques anomalies dans l'affichage du répertoire de la carte pour choisir le fichier à lancer.
Il y a aussi la photo d'un autre modèle d'interface avec un support ZIF à 24 broches pour l'Arduino. On peut ainsi avoir plusieurs Arduino avec différents programmes et les échanger très rapidement, ou encore déconnecter facilement l'Arduino pour le reprogrammer.
Elle corrige quelques anomalies dans l'affichage du répertoire de la carte pour choisir le fichier à lancer.
Il y a aussi la photo d'un autre modèle d'interface avec un support ZIF à 24 broches pour l'Arduino. On peut ainsi avoir plusieurs Arduino avec différents programmes et les échanger très rapidement, ou encore déconnecter facilement l'Arduino pour le reprogrammer.
Daniel
L'obstacle augmente mon ardeur.
L'obstacle augmente mon ardeur.
Re: [Thomson] Vidéo avec son en streaming
très bonne idée le support zif
-
- Messages : 7964
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
J'ai essayé d'écrire la RAMA et la RAMB de façon contiguë avec le code ASM suivant:
combiné à l'encodeurLe player tourne alors avec 74 cycles (+14 par rapport au player précédent) pour 4 octets videos, 1 son et 2 déplacements, ce qui fait que le son tourne à 13.5khz. Pour lui tout c'est toujours très bien, mais les 74 cycles pour 4 octets vidéo font que les changements massifs prennent vraiment beaucoup de temps. Si on doit mettre à jour 1/3 de l'écran, il faut (74/4)*(16000/3), soit environ 100ms (10fps). Donc à 10fps on ne peut pas changer plus qu'un tiers de l'écran. C'est pas beaucoup. Si on inverse le rapport, ca signifie qu'il faut réduire la surface affichable à 1/3 du plein écran si on veut tenir le fps. Ca nous fait du 92x57 au lieu de 160x100. C'est vraiment tout petit.
Pour s'en sortir, j'ai fait passer l'encodeur en mode entrelacé, ce qui permet d'afficher du 130x81 à 10fps, ce qui est mieux. Hélas, à 10fps, on voit quand même beaucoup l'entrelacement. Pour tester par vous même: http://dl.free.fr/sa8e2aXDp
Je pense que le le fait que dans ce mode on travaille moralement en mode octet en vidéo ralenti beaucoup les performances du player qui est environ 23% plus lent alors qu'il y a deux fois plus de données à mettre à jour. Les 8 cycles "STB 8192,X" sont particulièrement malheureux. Il vaudrait mieux faire un STD 8192,X (9 cycles) qui double la bande passante en écriture sans prendre beaucoup plus de cycles. Si on pousse l'idée, cela revient à changer les trames de 7 octets pour ne faire que 6 octets. Les 512 octets d'un bloc se décomposent en (<son> <deplacement> <forme1> <forme2> <fond1> <fond2>)x85 + <son1> <son2>.
La présence des deux octets pour compléter le bloc n'est pas fondamentalement dérangeant. En revanche la lecture <fond1> <fond2> se fait par deux accès consécutifs à <$CC sans instructions entre les deux. C'est trop rapide pour l'arduino je pense. Daniel, il faudrait ajouter combien de délais entre deux accès en lecture rapprochée ?
Notes:
Code : Tout sélectionner
ORG $9000
*------------------------------------------------------
* Point d'entree
*------------------------------------------------------
INI
PSHS U,Y,X,DP,B,A,CC empile les registres
ORCC #$50 masque les interruptions
LDU #txt
TXT
LDB ,U+
JSR $E803
BNE TXT
* init
LDU #$E7C3 port A du PIA systeme
TFR U,D
TFR A,DP
* Mise en place du block play 73 fois
LDU #FINPLAY7 fin block PLAY 7
LDX #72*(FINPLAY7-PLAY7)
RECOPIE
LDA <(PLAY7-FINPLAY7),U
STA ,U+
LEAX -1,X
BNE RECOPIE
* mise en place "JMP FINBLOC"
LDA #$7E "JMP"
STA ,U+
LDD #FINBLOC2
STD ,U
* init ports d'entree/sortie
CLRA A=$00
CLRB B=$00
STD <$CE selectionne DDRA et DDRB
LDB #$7F B=$7F
STD <$CC PA b0-7 en entree, PB b0-6 en sortie
ORA #$04 set b2
STA <$CE selectionne PORTA
STA <$CF selectionne PORTB
*------------------------------------------------------
* Chargement donnees
*------------------------------------------------------
LDX #DATA-1
BRA PLAY7
FINBLOC2
LDA <$CC (4) lecture echantillon son avec B6 alternant
PSHS A
LDD #FINBLOC fin bloc normal
STD ,U
* recup palette
LDX #DATA
CLRB
STB <$DB
PALETTE
LDD ,X++
STB <$DA
STA <$DA
cmpx #DATA+16*2
BNE PALETTE
* ecran visible en $A000
LDD #$9000
STA <$E7
STB <$E5
LDX #$A000 pointeur ecran
PULS A
BRA FINBLOC+2 (4) joue le son et acknowledge
*------------------------------------------------------
* JOUE LA VIDEO ET LA MUSIQUE
* Boucle de 60 cycles = 16666.67 Hz
*------------------------------------------------------
* BRA PLAY7
*------------------------------------------------------
* RETOUR AU BASIC
*------------------------------------------------------
SORTIE
PULS CC,A,B,DP,X,Y,U,PC
* retour a l'assembleur
* SWI
txt
fcb 27,$60
fcb 27,$5e
fcb 0
* fin de bloc 512: son + attente (4(JMP)+8+59+3=74 cycles)
FINBLOC
* echantillon son (8 cycles)
LDA <$CC (4) lecture echantillon son avec B6 alternant
STA <$CD (4) joue le son et acknowledge
* compensation, lecture inter-bloc (59)
PSHS X,Y,D,U,DP (14)
PULS X,Y,D,U,DP (14)
PSHS X,Y,D,U,DP (14)
PULS X,Y,D,U,DP (14)
BRN SORTIE (3)
* rebouclage (7 cycles)
BGE SORTIE (3) sortie?
*------------------------------------------------------
* lecture d'un bloc de 7 octets 8+(17+16)*2=74 cycles
*
* <son> <depA> <vidA1> <vidA2> <depB> <vidB1> <vidB2>
*------------------------------------------------------
PLAY7
* echantillon son (8 cycles)
LDA <$CC (4) lecture echantillon son avec B6 alternant
STA <$CD (4) joue le son et acknowledge
* lecture octet depl + video1 = 17 cycles
LDB <$CC (4) lecture octet deplacement
BEQ NEWPICa (3) nouvelle image
ABX (3) ajout de l'increment
LDA <$CC (4) lecture octet video1
BRA PLAYa (3) octet video
* retour debut ecran + video1 = 10 cycle
NEWPICa
LDX #$A000 (3) retour debut ecran
LDA <$CC (4) lecture octet video1
BRA PLAYa (3) poursuite image
* sortie video = 16 cycles
PLAYa
LDB <$CC (4) lecture octet video2
* 5 -> 12
STA ,X (4) affiche l'octet image
STB 8192,X (8)
* lecture octet depl + video3 = 17 cycles
LDB <$CC (4) lecture octet deplacement
BEQ NEWPICb (3) nouvelle image
ABX (3) ajout de l'increment
LDA <$CC (4) lecture octet video3
BRA PLAYb (3) octet video
* retour debut ecran + video3 = 10 cycle
NEWPICb
LDX #$A000 (3) retour debut ecran
LDA <$CC (4) lecture octet video3
BRA PLAYb (3) poursuite image
* sortie video = 16 cycles
PLAYb
LDB <$CC (4) lecture octet image
STA ,X (4) affiche l'octet image
STB 8192,X (8)
FINPLAY7
RMB 72*(FINPLAY7-PLAY7)
* envoi fin bloc 512
* JMP FINBLOC (4)
RMB 3
DATA
RMB 256
END INI
Code : Tout sélectionner
10 CLEAR,&H8FFF
30 READ A$:IF LEN(A$)=4 THEN A=VAL("&H"+A$):GOTO 30
40 IF A$="**" THEN EXEC A ELSE POKE A,VAL("&H"+A$):A=A+1:GOTO 30
50 DATA 9000
60 DATA 34,7F,1A,50,CE,90,6A,E6,C0,BD
70 DATA E8,03,26,F9,CE,E7,C3,1F,30,1F
80 DATA 8B,CE,90,B3,8E,0E,A0,A6,C8,CC
90 DATA A7,C0,30,1F,26,F7,86,7E,A7,C0
100 DATA CC,90,40,ED,C4,4F,5F,DD,CE,C6
110 DATA 7F,DD,CC,8A,04,97,CE,97,CF,8E
120 DATA 9F,55,20,3F,96,CC,34,02,CC,90
130 DATA 6F,ED,C4,8E,9F,56,5F,D7,DB,EC
140 DATA 81,D7,DA,97,DA,8C,9F,76,26,F5
150 DATA CC,90,00,97,E7,D7,E5,8E,A0,00
160 DATA 35,02,20,09,35,FF,1B,60,1B,5E
170 DATA 00,96,CC,97,CD,34,7E,35,7E,34
180 DATA 7E,35,7E,21,EB,2C,E9,96,CC,97
190 DATA CD,D6,CC,27,05,3A,96,CC,20,07
200 DATA 8E,A0,00,96,CC,20,00,D6,CC,A7
210 DATA 84,E7,89,20,00,D6,CC,27,05,3A
220 DATA 96,CC,20,07,8E,A0,00,96,CC,20
230 DATA 00,D6,CC,A7,84,E7,89,20,00
240 DATA 9000,**
Code : Tout sélectionner
#/bin/perl
##############################################################################
# Conversion de fichier video en fichier SD fonctionnant avec le
# player SDANIM3 de Daniel Coulom pour THOMSON modifié pour le mode
# "2 octets"
#
# (http://forum.system-cfg.com/viewtopic.php?p=104928#p104928)
#
# par Samuel Devulder.
#
# Historique:
# ===========
# 09/06/2015 - version initiale. Portage semi direct du code C.
#
# 10/06/2015 - utilisation de la matrice vac-8 qui donne une image plus
# fine que le h4x4a
# - fps et zoom revu pour avoir une vitesse et une qualité
# très correcte (10fps et zoom 70%)
# - optimisation de la vitesse de compression:
# - les bords noirs supérieur et inférieurs de l'écran
# sont ignores
# - la boucle while cherchant les différences ne lit à
# présent qu'un seul des deux tablraux puisque la lecture
# nous fournit en cible le delta avec l'image actuelle.
# du coup la vitesse d'encodage passe de x0.4 à x1.
# - meilleure synchro video quand une image est compressée en
# moins de BUFFSIZE octets: on retourne simplement en haut
# de l'écran sans re-encoder la même image de sorte qu'au
# changement d'écran on redémarre en haut de la nouvelle image.
# On ne voit quasiment plus des demi-images et la vidéo est
# très fluide
# - mise en place d'une correction sonnore auto-adaptative.
# Donne d'excellents resultats pour Dire Straits (sultan of
# swing est fort et clair avec une bonne dynamique à présent).
# - stockage des images temporaires dans un sous-dossier tmp/
# pour ne opas polluer le répertoire courant.
#
# 12/06/2015 - mise en place d'un algo pour calculer le meilleur zoom
# ou framerate. Dans un 1er temps la vidéo est échantillonnée
# à 1 image /sec (pour aller vite) avec un zoom de 1. Puis
# compressée sans limite de taille de buffer. A la fin de la
# vidéo on obtient le nombre d'octets moyen par image (LEN_BY_IMG).
#
# si LEN_BY_IMG est inférieur au BUFFERSIZE associé au framerate
# choisi (FPS), ca veut dire que le framerate est un peu petit
# et que l'on gaspille des octets puisque BUFFERSIZE n'est pas
# complètement rempli. On peut alors augmenter le framerate de
# la même proportion d'écart entre BUFFERSIZE et LEN_BY_IMG:
# FPS <- FPS*(BUFFERSIZE/LEN_BY_IMG)
# C'est ce qu'il se passe avec les video facilement compressibles
# l'outil va en profiter pour augmenter le FPS afin d'occuper
# tout le buffer alloué à une image. Cependant le cas le plus
# fréquent est le suivant:
#
# Si LEN_BY_IMG est supérieur à BUFFERSIZE, c'est que ce dernier
# est trop petit pour contenir une image compressée. On peut alors
# jouer sur le facteur de zoom pour que l'image compressée tienne
# dans BUFFERSIZE. En première approximation si on réduit l'image
# d'un facteur r<1, le nombre de pixel à compresser est réduit
# d'un facteur r*r. La taille compressée est elle aussi sensiblement
# réduite du même facteur (imaginez diviser une image par 2, il y a
# 4x fois moins d'information à compresser, et la compression sera
# aussi 4x plus petite). Du coup cela veut dire que r*r peut valoir
# LEN_BY_IMG/BUFFERSIZE plus une petite marge (que je fixe à 15%).
# Cela signifie que l'on peut utiliser un zoom de
# SQRT(BUFFERSIZE/(LEN_BY_IMG + 15%))
# pour avoisiner le taux d'occupation idéal de BUFFERSIZE.
#
# - mise en place d'un contraste sigmoidal qui tasse les intensités
# sombre et lumineuses afin d'avoir des image très contrastée qui
# apparaissent dès lors un peu moins bruitées. Cependant je
# les trouve alors moins riche au niveau des dégradés.
#
# 27/06/2015 - Adapation au player "2octets", retour au ordered-dither,
# detection du bon espace RGB linéaire.
#
# 30/06/2015 - Ajout d'un algo de re-normalisation des niveau RVB pour eviter
# d'avoir un film globalement trop sombre. Le contraste et les
# couleurs sont grandement amméliorés.
#
# 03/07/2015 - Adaptation au protocole "sans acquitement" permettant d'avoir
# une bande passante video 3x par rapport à l'audio.
#
# 11/07/2015 - Adaptation au protocole "par block" avec un débit de 60 cycles
# pour un échantillon audio et 2 triplets video.
#
# 18/09/2015 - Debut d'adapation pour le mode BM16.
#
# 26/09/2015 - Modif pour encoder RAMA/RAMB ensemble.
#
##############################################################################
$file = $ARGV[0];
$name = $file; $name =~ s/\.[^\.]*$//; $name .= ".sd";
exit if -e $name;
# params par defaut
mkdir("tmp");
$img_pattern = "tmp/img%05d.bmp";
($w, $h) = (160, 100); # 16:9
$cycles = 60+(12-5)*2; # cyles cpu par echantillon audio
$hz = int(2000000/$cycles+.5)/2;
$fps = 10;
$interlace = 1; # 0=off, 1=simple
$audio_dither = 0;
$dither = "bayer4";
$zigzag = 1;
# recherche la taille de l'image
($x,$y, $aspect_ratio) = (160,100,"16:9");
open(IN, "./ffmpeg -i \"$file\" 2>&1 |");
while(<IN>) {
if(/, (\d+)x(\d+)/) {
($x,$y) = ($1, $2);
# 4:3
if(abs($x - 4/3*$y) < abs($x - 16/9*$y)) {
($w,$h,$aspect_ratio) = (133,100,"4:3");
}
}
}
close(IN);
$h = int(($w=160)*$y/$x);
$w = int(($h=100)*$x/$y) if $h>100;
print "\n",$file," : ${x}x${y} ($aspect_ratio) -> ${w}x${h}\n";
# AUDIO: unsigned 8bits, mono, 16125hz
open(AUDIO, "./ffmpeg -i \"$file\" -v 0 -f u8 -ac 1 -ar ".int(2*$hz)." -acodec pcm_u8 - |");
binmode(AUDIO);
# tuyau vers ffmpeg pour images
open(FFMPEG,'| (read line; $line)');# -vf format=gray
binmode(FFMPEG);
# fichier video (entree)
open(IN, "<$file");
binmode(IN);
# détermination des parametres de dither
open(DITHER_O,"| ./ffmpeg -i - -v 0 -r 1 -s ${w}x${h} -an $img_pattern");
open(DITHER_I, "<$file");
binmode(DITHER_I);
binmode(DITHER_O);
# guess zoom
open(GUESS_O,"| ./ffmpeg -i - -v 0 -r 1 -s ${w}x${h} -an $img_pattern");
open(GUESS_I, "<$file");
binmode(GUESS_I);
binmode(GUESS_O);
# init image magick
&magick_init;
# parametres de dither
&dither_init(\*DITHER_I, \*DITHER_O, $dither, $w, $h);
# determination du zoom optimal
$zoom = &guess_zoom(\*GUESS_I, \*GUESS_O, $w, $h); close(GUESS_I); close(GUESS_O);
$fps = int($fps * ($zoom>1?$zoom:1));
$w = int($w*($zoom>1?1:$zoom));
$h = int($h*($zoom>1?1:$zoom));
print sprintf("zoom = %.2g -> %dx%d @ %dfps\n", $zoom, $w, $h, $fps);
$fps = 25 if $fps>25;
$cmd = "./ffmpeg -i - -v 0 -r $fps -s ${w}x${h} -an $img_pattern\n";
syswrite(FFMPEG, $cmd, length($cmd));
# nettoyage
unlink(<tmp/img*.bmp>);
# fichier sd (sortie)
open(OUT, ">$name");
binmode(OUT);
# multiplicateur audio
@audio_cor = (8, 255);
# compteur image
$cpt = 1;
# ecran courant
@ecran = ((0) x 8000);
# position dans ecran
$pos = 8000;
# 1er bloc donnée: palette
push(@pal, 0, $GRN0*16, 0, $GRN1*16, 0,$GRN2*16, 0,$GRN3*16);
for my $r ($RED0, $RED1, $RED2, $RED3) {
for my $b ($BLU0, $BLU1, $BLU2) {
push(@pal, $b, $r);
}
}
while($#pal<291) {push(@pal, 0);}
# compression
$time = 0; $start = time; $realimg = 0; $start_pos=0; $pause = 60;
$cycles_per_img = 1000000/$fps;
$current_cycle = $cycles_per_img; $clk = 0;
# 1er bloc donnees
@buf = ();
while(1) {
push(@buf, 0x80 | ($clk ^= 0x40)); # son
last if $#buf==511;
push(@buf, 1); # depl
push(@buf, splice(@pal,0,1), 0);
push(@buf, 1); # depl
push(@buf, splice(@pal,0,1), 0);
}
print OUT pack('C*', @buf); @buf = ();
for($running = 0x80; 1; $current_cycle += $cycles) {
push(@buf, &echantillon_audio());
if($#buf<511) {
push(@buf, &triplets_video());
} else {
# write
print OUT pack('C*', @buf); @buf = ();
# fin fichier
last unless $running;
}
}
# nettoyage et fermetue flux
print STDERR "\n";
unlink(<tmp/img*.bmp>);
close(OUT);
close(IN);
close(FFMPEG);
close(AUDIO);
# Lit 2 échantillons audio de FFMPEG et le converti en échantillon audio pour le flux SD
sub echantillon_audio {
if(!@AUDIO) {
my($buf);
$running = 0 if !read(AUDIO,$buf,8192);
push(@AUDIO, unpack('C*', $buf));
}
my $v = (shift(@AUDIO)+shift(@AUDIO))/2;
# volume auto
$audio_cor[1] = $v if $v<$audio_cor[1];
$v-=$audio_cor[1];
$audio_cor[0] = 255/$v if $v*$audio_cor[0]>255;
$v *= $audio_cor[0];
# dither audio
$v += int(rand(3)) if $audio_dither;
$v=255 if $v>255;
# sortie audio
return ($v>>2) | $running | ($clk ^= 0x40);
}
# retourne 2 triplets video
sub triplets_video {
my($num, @buf) = 2;
if($current_cycle>=$cycles_per_img) {
$current_cycle-=$cycles_per_img;
&next_image(\*IN,\*FFMPEG,$w,$h);
# image complète ?
if($pos>=8000+$start_pos) {
++$realimg;
$ecran[0] ^= $cible[0]; $cible[0] = 0;
push(@buf, 0, $ecran[0]>>8, $ecran[0]&255);
$pos = 0; $num = 1;
}
$start_pos = $pos;
# infos à l'écran
if($cpt%$fps == 0) {
++$time;
my($d) = time-$start+.0001;
print STDERR sprintf("%d:%02d:%02d (%.2gx) v=1:%.3g a=(x%+d)*%.1g \r",
int($time/3600), int($time/60)%60, $time%60,
int(100*$time/$d)/100, $realimg/($cpt-1), -$audio_cor[1], $audio_cor[0]);
# pour ne pas trop faire chauffer le CPU
if($d>$pause) {$pause = $d+60;sleep(10);}
}
}
for (1..$num) {
my($k,$p) = (0, $pos % 8000);
while($k<255 && !$cible[$p+$k]) {++$k;}
$pos+=$k;
if(($p+=$k)>=8000) {
$k = $p = 0;
$pos = int($pos/8000)*8000;
}
$ecran[$p] ^= $cible[$p]; $cible[$p] = 0;
push(@buf, $k, $ecran[$p]>>8, $ecran[$p]&255);
}
return @buf;
}
sub tune_image {
my($tmp) = @_;
$tmp->Modulate(saturation=>120); # un peu plus de couleurs
$tmp->Evaluate(operator=>'Multiply', value=>255/245);
}
# Lit l'image suivante. Retourne 1 si ok, 0 si fin fichier
sub next_image {
my($IN, $OUT, $w, $h) = @_;
# nom du fichier BMP
my $name = sprintf($img_pattern, $cpt++);
# taille fichier BMP
my $expected_size = $h*(($w*3 + 3)&~3) + 54; # couleur
#print "$w, $h, $expected_size\n";
# on nourrit ffmpeg jusqu'a obtenir un fichier BMP fini
while($expected_size != -s $name) {
my $buf;
my $read = read($IN,$buf,8192);
last unless $read;
syswrite $OUT, $buf, $read;
}
# lecture image
my $tmp = Image::Magick->new();
my $z = $tmp->Read($name);
#print STDERR $z if $z;
return 0 if $z; # si erreur => fin fichier
unlink $name;
# dither
$tmp->Set(depth=>16);
$tmp->Set(colorspace=>$LINEAR_SPACE);
&tune_image($tmp);
my @px = $tmp->GetPixels(height=>$h, normalize=>"True"); undef $tmp;
for my $c (@px) {$c = int($c*255);}
$dR = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dR{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$RED2],$lin_pal[$RED3]);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? 3 : 2) if $v>=$a;
($a,$b) = ($lin_pal[$RED1],$a);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? 2 : 1) if $v>=$a;
($a,$b) = ($lin_pal[$RED0],$a);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? 1 : 0);
} unless $dR;
$dG = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dG{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$GRN2],$lin_pal[$GRN3]);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 3 : 2) if $v>=$a;
($a,$b) = ($lin_pal[$GRN1],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 2 : 1) if $v>=$a;
($a,$b) = ($lin_pal[$GRN0],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 1 : 0);
} unless $dG;
$dB = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dB{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$BLU1],$lin_pal[$BLU2]);
return $glb_dB{$k}=(($v-$a)/($b-$a)>=$d ? 2 : 1) if $v>=$a;
($a,$b) = ($lin_pal[$BLU0],$a);
return $glb_dB{$k}=(($v-$a)/($b-$a)>=$d ? 1 : 0);
} unless $dB;
@cible = (0)x8000;
$pset = sub {
my($x,$y, $r,$g,$b) = @_;
my($p)=$y*80 + ($x>>2);
my($a,$b) = ($g,$r*3+$b+4);
($a,$b)=($a<<4,$b<<4) unless $x&1;
($a,$b)=($a<<8,$b<<8) if $x&2;
($a,$b)=($b,$a) if $zigzag && ($x&1);
$cible[$p] |= $a;
$cible[$p+40] |= $b;
} unless $pset;
for my $y (0..$h-1) {
for my $x (0..$w-1) {
my($d) = $mat[$x%$mat_x][$y%$mat_y];
my(@p) = splice(@px, 0, 3);
$pset->($x,$y, $dR->($p[0],$d),$dG->($p[1],$d),$dB->($p[2],$d));
}
}
# compute delta
for my $i (0..$#cible) {$cible[$i]^=$ecran[$i]}
@cible[8000..8002] = (255,255,255); # sentinelle
# intrelacement
if($interlace==1) {
my $f = sub {
my($y) = @_;
splice(@cible, $y*40, 40, (0)x40);
#splice(@cible, $y*40+8192, 40, (0)x40);
};
for(my $y=$cpt&1; $y<200; $y+=2) {$f->($y);}
}
if($interlace==2) {
my $f = sub {
my($y) = @_;
my($cpt) = 0;
for my $x (0..39) {
my($c) = $cible[$y*40+$x];
++$cpt if $c&0x000f;
++$cpt if $c&0x00f0;
++$cpt if $c&0x0f00;
++$cpt if $c&0xf000;
}
splice(@cible, $y*40, 40, (0)x40) if $cpt<32;
};
for(my $y=$cpt%2; $y<200; $y+=3) {$f->($y);}
}
return 1;
}
sub guess_zoom {
my($IN, $OUT, $w, $h) = @_;
my $BUFFERSIZE = 7*int(($hz+$fps-1)/$fps);
unlink(<tmp/img*.bmp>);
@ecran = ((0)x16384);
local $cpt = 1;
my $size = 0;
while(&next_image($IN,$OUT,$w,$h)) {
for(my $p = 0; $p<8000;) {
my $k = 1;
while($k<255 && !$cible[$p+$k]) {++$k;}
$p+=$k;
++$size if (($size&511)%7)==0; # audio
$size += 3; # video
}
# audio + goto 0
$size += 4;
print STDERR sprintf("avg %dx%d frame length = %d bytes (%d%%) @ %ds \r", $w, $h, int($size/($cpt-1)), int(100*$size/(($cpt-1)*$BUFFERSIZE)), $cpt-1);
}
$total_sec = $cpt-1;
unlink(<tmp/img*.bmp>);
print STDERR "\n";
my $len_per_img = ($size/($cpt-1)) * (1.05); # 5% extra
close($IN);
close($OUT);
my $zoom = $BUFFERSIZE/$len_per_img;
return $zoom>=1?$zoom:sqrt($zoom);
}
sub dither_init {
my($in, $out, $dither, $w, $h) = @_;
my $expected_size = $h*(($w*3 + 3)&~3) + 54;
# param dither
@mat = ([1]);
@mat = ([1,3],
[4,2]) if $dither eq "bayer2";
@mat = ([7,8,2],
[6,9,4],
[3,5,1]) if $dither eq "sam3";
@mat = ([3,7,4],
[6,1,9],
[2,8,5]) if $dither eq "3x3";
@mat = ([6, 9, 7, 12],
[14, 3, 15, 1],
[8, 11, 5, 10],
[16, 2, 13, 4]) if $dither eq "vac4";
@mat = ([1,9,3,11],
[13,5,15,7],
[4,12,2,10],
[16,8,14,6]) if $dither eq "bayer4";
@mat = ([21,2,16,11,6],
[9,23,5,18,14],
[19,12,7,24,1],
[22,3,17,13,8],
[15,10,25,4,20]) if $dither eq "vac5";
@mat = ([35,57,19,55,7,51,4,21],
[29,6,41,27,37,17,59,45],
[61,15,53,12,62,25,33,9],
[23,39,31,49,2,47,13,43],
[3,52,8,22,36,58,20,56],
[38,18,60,46,30,5,42,28],
[63,26,34,11,64,16,54,10],
[14,48,1,44,24,40,32,50]) if $dither eq "vac8";
$mat_x = 1+$#mat;
$mat_y = 1+$#{$mat[0]};
@teo_pal = (0,100,127,142,163,179,191,203,215,223,231,239,243,247,251,255);
@lin_pal = (0, 33, 54, 69, 93,115,133,152,173,188,204,220,229,237,246,255);
my @pc2teo = ();
for my $i (1..$#teo_pal) {
my($a,$b,$c) = ($teo_pal[$i-1],($teo_pal[$i-1]+$teo_pal[$i])>>1,$teo_pal[$i]);
for my $j ($a..$b) {$pc2teo[$j] = $i-1;}
for my $j ($b+1..$c) {$pc2teo[$j] = $i;}
}
my @tabR = (0)x16;
my @tabG = (0)x16;
my @tabB = (0)x16;
my $time = 1;
my $run = 1;
while(1) {
my $name = sprintf($img_pattern, $time);
if($expected_size != -s $name) {
last unless $run;
my $buf;
my $read = read($in,$buf,4096);
if($read) {
syswrite $out, $buf, $read;
} else {
$run = 0;
close($out);
}
} else {
# image complete!
print STDERR $time++,"s\r";
# lecture de l'image
my $img = Image::Magick->new();
my $x = $img->Read($name); die "$x, stopped $!" if $x;
unlink $name;
&tune_image($img);
if(!defined $pal4096) {
my @px;
for my $r (0..15) {
for my $g (0..15) {
for my $b (0..15) {
push(@px, $teo_pal[$r], $teo_pal[$g], $teo_pal[$b]);
}
}
}
$pal4096 = &px2img(256,16, @px);
}
$img->Remap(image=>$pal4096, dither=>"true", "dither-method"=>"Floyd-Steinberg");
# trammage
my @px = $img->GetPixels(height=>$h, normalize=>"True");
undef $img;
for(my $i=0; $i<$#px; $i+=3) {
++$tabR[$pc2teo[int($px[$i+0]*255)]];
++$tabG[$pc2teo[int($px[$i+1]*255)]];
++$tabB[$pc2teo[int($px[$i+2]*255)]];
}
}
}
close($in);close($out);
unlink(<tmp/img*.bmp>);
my $f = sub {
my($name, @t) = @_;
my($tot, $acc, $max, @r);
my(@tab) = splice(@t,0,16);
for my $t (@tab) {$max = $t if $t>$max; $tot += $t;}
$r[0] = -1;
for my $t (@t) {
for(my $i=$r[$#r]+1; $i<16; ++$i) {
$acc += $tab[$i];
if($acc>=$t*$tot) {
push(@r, $i);
last;
}
}
}
shift @r;
for my $i (0..$#tab) {print sprintf("%s%2d:%3d%% %s\n", $name, $i, int(100*$tab[$i]/$tot), "X"x(int(50*$tab[$i]/$max)));}
print join(' ', @r),"\n";
return @r;
};
($RED0, $RED1, $RED2, $RED3) = $f->("RED", @tabR, 0.02, 0.33, 0.66, 0.95);
($GRN0, $GRN1, $GRN2, $GRN3) = $f->("GRN", @tabR, 0.02, 0.33, 0.66, 0.95);
($BLU0, $BLU1, $BLU2) = $f->("GRN", @tabR, 0.02, 0.50, 0.95);
}
sub px2img {
my($width,$height,@px) = @_;
open(PX2IMG,">/tmp/.toto2.pnm");
print PX2IMG "P6\n$width $height\n255\n",pack('C*', @px),"\n";
close(PX2IMG);
my $img2 = Image::Magick->new();
$img2->ReadImage("/tmp/.toto2.pnm");
unlink "/tmp/.toto2.pnm";
return $img2;
}
sub magick_init {
if(!$_magick) {
$_magick = 1;
eval 'use Image::Magick;';
# determination de l'espace RGB lineaire
my $img = Image::Magick->new(size=>"256x1", depth=>16);
$img->Read('gradient:black-white');
$img->Set(colorspace=>'RGB');
#$img->Set(colorspace=>"Gray") unless $coul;
my @px1 = $img->GetPixel(x=>128, y=>0);
$img->Read('gradient:black-white');
$img->Set(colorspace=>'sRGB');
#$img->Set(colorspace=>"Gray") unless $coul;
my @px2 = $img->GetPixel(x=>128, y=>0);
my $d1 = $px1[0]-0.5; $d1=-$d1 if $d1<0;
my $d2 = $px2[0]-0.5; $d2=-$d2 if $d2<0;
$LINEAR_SPACE = $d1>=$d2 ? "RGB" : "sRGB";
#print $px1[0], " ",$px2[0]," $LINEAR_SPACE\n";
}
}
Pour s'en sortir, j'ai fait passer l'encodeur en mode entrelacé, ce qui permet d'afficher du 130x81 à 10fps, ce qui est mieux. Hélas, à 10fps, on voit quand même beaucoup l'entrelacement. Pour tester par vous même: http://dl.free.fr/sa8e2aXDp
Je pense que le le fait que dans ce mode on travaille moralement en mode octet en vidéo ralenti beaucoup les performances du player qui est environ 23% plus lent alors qu'il y a deux fois plus de données à mettre à jour. Les 8 cycles "STB 8192,X" sont particulièrement malheureux. Il vaudrait mieux faire un STD 8192,X (9 cycles) qui double la bande passante en écriture sans prendre beaucoup plus de cycles. Si on pousse l'idée, cela revient à changer les trames de 7 octets pour ne faire que 6 octets. Les 512 octets d'un bloc se décomposent en (<son> <deplacement> <forme1> <forme2> <fond1> <fond2>)x85 + <son1> <son2>.
La présence des deux octets pour compléter le bloc n'est pas fondamentalement dérangeant. En revanche la lecture <fond1> <fond2> se fait par deux accès consécutifs à <$CC sans instructions entre les deux. C'est trop rapide pour l'arduino je pense. Daniel, il faudrait ajouter combien de délais entre deux accès en lecture rapprochée ?
Notes:
- rahhh... si on pouvait récupérer des mots de 16bits, le format ci-dessus serait quasiment optimal.
- l'encodeur bug un peu. Certaines images sont trashées. C'est rare, mais ca arrive, et je ne vois pas à quoi c'est du. Mais c'est important: ce défaut n'est pas le plus gros soucis pour l'instant.
- Il y a une particularité du format dont je me dit qu'elle pourrait être exploitée, mais je ne sais pas encore comment.
Cette particularité est que lorsqu'on a eu <déplacement>=0, alors on est pas prêt d'avoir un autre <déplacement>=0 avant d'avoir parcouru tout l'écran, c'est à dire avant longtemps. Du coup les tests/sauts sur déplacement=0 suivants sont inutiles. On peut gagner 6cycles sur chaque bloc avec ca. C'est pas beaucoup (10%), mais c'est toujours bienvenu.
Si on ne testais le <déplacement>=0 qu'en début de bloc, il faut s'autoriser à déborder de 85x1 ou x2 octets en fin d'écran. C'est tout à fait possible car il y a 182 octets non visibles. On a de la marge!
Oui il y a un truc à exploiter en n'autorisant les retour au début qu'en fin de bloc. Par contre ca va compliquer l'encodeur. Il faudra aussi prévoir dans le player de sauvegarder cette zone utilisée par le système en ram. Bon c'est du détail.
Fondamentalement le player se réduirait à une suite desoit 4+4+4+3+4+4+5+4+4+9=45 cycles au lieu de 60Code : Tout sélectionner
LDA <$CC (4) <son> STA <$CD (4) ack + son LDB <$CC (4) depl ABX (3) LDA <$CC (4) <forme1> LDB <$CC (4) <forme2> STD ,X (5) LDA <$CC (4) <fond1> LDB <$CC (4) <fond2> STD 8192,X (9)
Le test en retour d'écran pourrait se faire sur l'octet <son1> de fin de bloc dont le MSB est inutilisé (c'est le MSB de <son2> qui indique la fin de fichier).
Oui je suis sur qu'il y a moyen d'être super rapide. Par contre les LDA/LDB <$CC consécutifs m'inquiètent. S'il faut insérer 3 cycles entre les deux, on passe à 51 cycles ce qui reste quand même bien, mais j'aurais préféré un player à 45 cycles (son à 22khz).
Dernière modification par __sam__ le 27 sept. 2015 14:57, modifié 3 fois.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Re: [Thomson] Vidéo avec son en streaming
D'après l'analyseur logique, la lecture de la carte SD prend 2,5µs + ou - 0,5µs. Donc je crois que c'est possible, il faudrait essayer pour le confirmer.__sam__ a écrit :En revanche la lecture <fond1> <fond2> se fait par deux accès consécutifs à <$CC sans instructions entre les deux. C'est trop rapide pour l'arduino je pense. Daniel, il faudrait ajouter combien de délais entre deux accès en lecture rapprochée ?
Pourquoi afficher en même temps les deux octets en mémoire couleur et en mémoire forme ? Pour chaque groupe de n octets consécutifs, on gagnerait du temps en affichant à la suite n octets en mémoire couleur, puis n octets en mémoire forme. Par contre il faudrait peut-être changer le mode de compression pour plus d'efficacité ?
Daniel
L'obstacle augmente mon ardeur.
L'obstacle augmente mon ardeur.
Re: [Thomson] Vidéo avec son en streaming
Et l'idée de passer en 16 bits ? tu n'as pas assez de port sur MO pour ça ?
-
- Messages : 7964
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
Oui, c'est ce qui est fait ici avc un groupe de n=2. Un groupe de 3 est possibleDaniel a écrit : Pourquoi afficher en même temps les deux octets en mémoire couleur et en mémoire forme ? Pour chaque groupe de n octets consécutifs, on gagnerait du temps en affichant à la suite n octets en mémoire couleur, puis n octets en mémoire forme. Par contre il faudrait peut-être changer le mode de compression pour plus d'efficacité ?
Code : Tout sélectionner
LDA <$CC
LDB <$CC
STD ,X
LDA <$CC
STA 2,X
LDA <$CC
LDB <$CC
STD 8192,X
LDA <$CC
STA 8194,X
- le STA 8194,X prends 8 cycles pour un seul octet. A lui seul il grève les perfs
- un trame du flux fait 8 octets (son depl forme1 forme2 forme3 fond1 fond2 fond3), qui ne laisse pas là place à un octet son en fin bloc pour l'octet son de synchro.
- on modifie à l'écran une zone de 8 pixels consécutifs. Là dedans peut-être 1 ou 2 auraient du changer. Je pense que c'est moins efficace
- rapporté à l'octet, le temps moyen d'écriture (lecture en $CC et déplacement compris) est sensiblement le même que la version avec un seul STD.
Pour l'instant je pense que la version avec un seul STD est un bon compromis. Concernant le débordement d'écran, en fait c'est encore plus simple: une fois arrivé en bas de l'écran, il suffit d'utiliser <depl>=0, et au final on déborde au plus de seulement 2 octets!
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
-
- Messages : 7964
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
Oui les PIA sont des interfaces 8 bits, et les directions des ports joystick arrivent toutes sur le même PIA.yo_fr a écrit :Et l'idée de passer en 16 bits ? tu n'as pas assez de port sur MO pour ça ?
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Re: [Thomson] Vidéo avec son en streaming
J'ai eu l'idée d'une transmission 16 bits, car c'est ce que je faisais lors des essais avec la carte compactflash. Pour transmettre 16 bits il faudrait une interface spéciale avec un 6821 dédié, et un arduino avec plus d'entrées/sorties que le Pro Mini. C'est tout à fait possible, mais c'est la certitude que personne n'utilisera le système. Les utilisateurs hésitent déjà à souder deux connecteurs DB9 et à acheter un arduino à 1,36€, alors c'est sûr qu'il ne construiront pas une interface avec un 6821 et les circuits logiques de décodage d'adresse qui vont avec.
Il faut encore réfléchir. Huit octets au lieu de 7, c'est possible : on peut avoir des groupes d'un échantillon son + 7 octets vidéo, et dans le dernier groupe du secteur mettre un échantillon son suivi de 7 octets de remplissage non utilisés. On doit pouvoir aussi imaginer des systèmes de codage complètement différents, avec par exemple un secteur de carte SD contenant uniquement des informations pour la ram vidéo "basse" et le secteur suivant des informations pour la rom vidéo "haute". C'est facile de rêver, certainement plus difficile à faire.
Il faut encore réfléchir. Huit octets au lieu de 7, c'est possible : on peut avoir des groupes d'un échantillon son + 7 octets vidéo, et dans le dernier groupe du secteur mettre un échantillon son suivi de 7 octets de remplissage non utilisés. On doit pouvoir aussi imaginer des systèmes de codage complètement différents, avec par exemple un secteur de carte SD contenant uniquement des informations pour la ram vidéo "basse" et le secteur suivant des informations pour la rom vidéo "haute". C'est facile de rêver, certainement plus difficile à faire.
Daniel
L'obstacle augmente mon ardeur.
L'obstacle augmente mon ardeur.
-
- Messages : 7964
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
C'est rare, mais pas tant que ca quand on utilise pas le mode "zigzag" sur les démos amiga. Voyez par vous même la catastrophe: http://dl.free.fr/oYqHEbBaGNote:
- l'encodeur bug un peu. Certaines images sont trashées. C'est rare, mais ca arrive, et je ne vois pas à quoi c'est du. Mais c'est important: ce défaut n'est pas le plus gros soucis pour l'instant.
Vraiment je ne m'explique pas d'où ca sort. J'ai même modifié l'encodeur pour qu'il joue les commandes de déplacement par lui-même pour voir si le pointeur sortait de la zone vidéo. Rien! C'est très déroutant.
Le plus paradoxal est que jimpower ou shadow of the beast n'ont pas le bug. Leur écran est plus petit. Je ne voie que ca qui joue (en mode zigzag l'écran est aussi plus petit). Les couleurs sont d'ailleurs superbes en plus pour ces jeux.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
-
- Messages : 7964
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
Bon j'ai trouvé la cause. C'était vraiment très subtil: la recherche de différence débutait à l'offset 0, et il fallait commencer à 1 sinon il se passe un truc bizarre en cas de changement d'image à l'offset 0: cet octet est indiqué comme changé (nouvelle image), ce qui nous fait un déplacement de 0 pour ce changement, c'est à dire un retour au début écran alors qu'on aurait du rester sur place. Ceci crée un retour prématuré au début, provoquant les scories visibles, et qui ne dépassent jamais la fin d'écran. Tout s'explique.
J'ai profité de ce correctif pour aussi améliorer le choix des couleurs
J'ai profité de ce correctif pour aussi améliorer le choix des couleurs
Code : Tout sélectionner
#/bin/perl
##############################################################################
# Conversion de fichier video en fichier SD fonctionnant avec le
# player SDANIM3 de Daniel Coulom pour THOMSON modifié pour le mode
# "2 octets"
#
# (http://forum.system-cfg.com/viewtopic.php?p=104928#p104928)
#
# par Samuel Devulder.
#
# Historique:
# ===========
# 09/06/2015 - version initiale. Portage semi direct du code C.
#
# 10/06/2015 - utilisation de la matrice vac-8 qui donne une image plus
# fine que le h4x4a
# - fps et zoom revu pour avoir une vitesse et une qualité
# très correcte (10fps et zoom 70%)
# - optimisation de la vitesse de compression:
# - les bords noirs supérieur et inférieurs de l'écran
# sont ignores
# - la boucle while cherchant les différences ne lit à
# présent qu'un seul des deux tablraux puisque la lecture
# nous fournit en cible le delta avec l'image actuelle.
# du coup la vitesse d'encodage passe de x0.4 à x1.
# - meilleure synchro video quand une image est compressée en
# moins de BUFFSIZE octets: on retourne simplement en haut
# de l'écran sans re-encoder la même image de sorte qu'au
# changement d'écran on redémarre en haut de la nouvelle image.
# On ne voit quasiment plus des demi-images et la vidéo est
# très fluide
# - mise en place d'une correction sonnore auto-adaptative.
# Donne d'excellents resultats pour Dire Straits (sultan of
# swing est fort et clair avec une bonne dynamique à présent).
# - stockage des images temporaires dans un sous-dossier tmp/
# pour ne opas polluer le répertoire courant.
#
# 12/06/2015 - mise en place d'un algo pour calculer le meilleur zoom
# ou framerate. Dans un 1er temps la vidéo est échantillonnée
# à 1 image /sec (pour aller vite) avec un zoom de 1. Puis
# compressée sans limite de taille de buffer. A la fin de la
# vidéo on obtient le nombre d'octets moyen par image (LEN_BY_IMG).
#
# si LEN_BY_IMG est inférieur au BUFFERSIZE associé au framerate
# choisi (FPS), ca veut dire que le framerate est un peu petit
# et que l'on gaspille des octets puisque BUFFERSIZE n'est pas
# complètement rempli. On peut alors augmenter le framerate de
# la même proportion d'écart entre BUFFERSIZE et LEN_BY_IMG:
# FPS <- FPS*(BUFFERSIZE/LEN_BY_IMG)
# C'est ce qu'il se passe avec les video facilement compressibles
# l'outil va en profiter pour augmenter le FPS afin d'occuper
# tout le buffer alloué à une image. Cependant le cas le plus
# fréquent est le suivant:
#
# Si LEN_BY_IMG est supérieur à BUFFERSIZE, c'est que ce dernier
# est trop petit pour contenir une image compressée. On peut alors
# jouer sur le facteur de zoom pour que l'image compressée tienne
# dans BUFFERSIZE. En première approximation si on réduit l'image
# d'un facteur r<1, le nombre de pixel à compresser est réduit
# d'un facteur r*r. La taille compressée est elle aussi sensiblement
# réduite du même facteur (imaginez diviser une image par 2, il y a
# 4x fois moins d'information à compresser, et la compression sera
# aussi 4x plus petite). Du coup cela veut dire que r*r peut valoir
# LEN_BY_IMG/BUFFERSIZE plus une petite marge (que je fixe à 15%).
# Cela signifie que l'on peut utiliser un zoom de
# SQRT(BUFFERSIZE/(LEN_BY_IMG + 15%))
# pour avoisiner le taux d'occupation idéal de BUFFERSIZE.
#
# - mise en place d'un contraste sigmoidal qui tasse les intensités
# sombre et lumineuses afin d'avoir des image très contrastée qui
# apparaissent dès lors un peu moins bruitées. Cependant je
# les trouve alors moins riche au niveau des dégradés.
#
# 27/06/2015 - Adapation au player "2octets", retour au ordered-dither,
# detection du bon espace RGB linéaire.
#
# 30/06/2015 - Ajout d'un algo de re-normalisation des niveau RVB pour eviter
# d'avoir un film globalement trop sombre. Le contraste et les
# couleurs sont grandement amméliorés.
#
# 03/07/2015 - Adaptation au protocole "sans acquitement" permettant d'avoir
# une bande passante video 3x par rapport à l'audio.
#
# 11/07/2015 - Adaptation au protocole "par block" avec un débit de 60 cycles
# pour un échantillon audio et 2 triplets video.
#
# 18/09/2015 - Debut d'adapation pour le mode BM16.
#
# 26/09/2015 - Modif pour encoder RAMA/RAMB ensemble.
#
# 28/09/2015 - Bugfix des images trashees qaund zigzag=0
# Ammeliroation du choix des couleurs.
#
##############################################################################
$file = $ARGV[0];
$name = $file; $name =~ s/\.[^\.]*$//; $name .= ".sd";
exit if -e $name;
# params par defaut
mkdir("tmp");
$img_pattern = "tmp/img%05d.bmp";
($w, $h) = (160, 100); # 16:9
$cycles = 60+(12-5)*2; # cyles cpu par echantillon audio
$hz = int(2000000/$cycles+.5)/2;
$fps = 10;
$interlace = 0; # 0=off, 1=simple
$audio_dither = 0;
$dither = "bayer4";
$zigzag = 0;
# recherche la taille de l'image
($x,$y, $aspect_ratio) = (160,100,"16:9");
open(IN, "./ffmpeg -i \"$file\" 2>&1 |");
while(<IN>) {
if(/, (\d+)x(\d+)/) {
($x,$y) = ($1, $2);
# 4:3
if(abs($x - 4/3*$y) < abs($x - 16/9*$y)) {
($w,$h,$aspect_ratio) = (133,100,"4:3");
}
}
}
close(IN);
$h = int(($w=160)*$y/$x);
$w = int(($h=100)*$x/$y) if $h>100;
print "\n",$file," : ${x}x${y} ($aspect_ratio) -> ${w}x${h}\n";
# AUDIO: unsigned 8bits, mono, 16125hz
open(AUDIO, "./ffmpeg -i \"$file\" -v 0 -f u8 -ac 1 -ar ".int(2*$hz)." -acodec pcm_u8 - |");
binmode(AUDIO);
# tuyau vers ffmpeg pour images
open(FFMPEG,'| (read line; $line)');# -vf format=gray
binmode(FFMPEG);
# fichier video (entree)
open(IN, "<$file");
binmode(IN);
# détermination des parametres de dither
open(DITHER_O,"| ./ffmpeg -i - -v 0 -r 1 -s ${w}x${h} -an $img_pattern");
open(DITHER_I, "<$file");
binmode(DITHER_I);
binmode(DITHER_O);
# guess zoom
open(GUESS_O,"| ./ffmpeg -i - -v 0 -r 1 -s ${w}x${h} -an $img_pattern");
open(GUESS_I, "<$file");
binmode(GUESS_I);
binmode(GUESS_O);
# init image magick
&magick_init;
# parametres de dither
&dither_init(\*DITHER_I, \*DITHER_O, $dither, $w, $h);
# determination du zoom optimal
$zoom = &guess_zoom(\*GUESS_I, \*GUESS_O, $w, $h); close(GUESS_I); close(GUESS_O);
$fps = int($fps * ($zoom>1?$zoom:1));
$w = int($w*($zoom>1?1:$zoom));
$h = int($h*($zoom>1?1:$zoom));
print sprintf("zoom = %.2g -> %dx%d @ %dfps\n", $zoom, $w, $h, $fps);
$fps = 25 if $fps>25;
$cmd = "./ffmpeg -i - -v 0 -r $fps -s ${w}x${h} -an $img_pattern\n";
syswrite(FFMPEG, $cmd, length($cmd));
# nettoyage
unlink(<tmp/img*.bmp>);
# fichier sd (sortie)
open(OUT, ">$name");
binmode(OUT);
# multiplicateur audio
@audio_cor = (8, 255);
# compteur image
$cpt = 1;
# ecran courant
@ecran = ((0) x 8000);
# position dans ecran
$pos = 8000;
# 1er bloc donnée: palette
push(@pal, 0, $GRN0*16, 0, $GRN1*16, 0,$GRN2*16, 0,$GRN3*16);
for my $r ($RED0, $RED1, $RED2, $RED3) {
for my $b ($BLU0, $BLU1, $BLU2) {
push(@pal, $b, $r);
}
}
while($#pal<291) {push(@pal, 0);}
# compression
$time = 0; $start = time; $realimg = 0; $start_pos=0; $pause = 60;
$cycles_per_img = 1000000/$fps;
$current_cycle = $cycles_per_img; $clk = 0;
# 1er bloc donnees
@buf = ();
while(1) {
push(@buf, 0x80 | ($clk ^= 0x40)); # son
last if $#buf==511;
push(@buf, 1); # depl
push(@buf, splice(@pal,0,1), 0);
push(@buf, 1); # depl
push(@buf, splice(@pal,0,1), 0);
}
print OUT pack('C*', @buf); @buf = ();
for($running = 0x80; 1; $current_cycle += $cycles) {
push(@buf, &echantillon_audio());
if($#buf<511) {
push(@buf, &triplets_video());
} else {
# write
print OUT pack('C*', @buf); @buf = ();
# fin fichier
last unless $running;
}
}
# nettoyage et fermetue flux
print STDERR "\n";
unlink(<tmp/img*.bmp>);
close(OUT);
close(IN);
close(FFMPEG);
close(AUDIO);
# Lit 2 échantillons audio de FFMPEG et le converti en échantillon audio pour le flux SD
sub echantillon_audio {
if(!@AUDIO) {
my($buf);
$running = 0 if !read(AUDIO,$buf,8192);
push(@AUDIO, unpack('C*', $buf));
}
my $v = (shift(@AUDIO)+shift(@AUDIO))/2;
# volume auto
$audio_cor[1] = $v if $v<$audio_cor[1];
$v-=$audio_cor[1];
$audio_cor[0] = 255/$v if $v*$audio_cor[0]>255;
$v *= $audio_cor[0];
# dither audio
$v += int(rand(3)) if $audio_dither;
$v=255 if $v>255;
# sortie audio
return ($v>>2) | $running | ($clk ^= 0x40);
}
# retourne 2 triplets video
sub triplets_video {
my($num, @buf) = 2;
my($size) = 80*$h;
if($current_cycle>=$cycles_per_img) {
$current_cycle-=$cycles_per_img;
&next_image(\*IN,\*FFMPEG,$w,$h);
# image complète ?
if($pos>=8000+$start_pos) {
++$realimg;
$ecran[0] ^= $cible[0]; $cible[0] = 0;
push(@buf, 0, $ecran[0]>>8, $ecran[0]&255);
$pos = 0; $num = 1;
}
$start_pos = $pos;
# infos à l'écran
if($cpt%$fps == 0) {
++$time;
my($d) = time-$start+.0001;
print STDERR sprintf("%d:%02d:%02d (%.2gx) v=1:%.3g a=(x%+d)*%.1g \r",
int($time/3600), int($time/60)%60, $time%60,
int(100*$time/$d)/100, $realimg/($cpt-1), -$audio_cor[1], $audio_cor[0]);
# pour ne pas trop faire chauffer le CPU
if($d>$pause) {$pause = $d+60;sleep(10);}
}
}
for (1..$num) {
my($k,$p) = (1, $pos % 8000);
while($k<255 && !$cible[$p+$k]) {++$k;}
$pos+=$k;
if(($p+=$k)>=$size) {
$k = $p = 0;
$pos = int(($pos+7999)/8000)*8000;
}
$ecran[$p] ^= $cible[$p]; $cible[$p] = 0;
push(@buf, $k, $ecran[$p]>>8, $ecran[$p]&255);
}
return @buf;
}
sub tune_image {
my($tmp) = @_;
$tmp->Modulate(saturation=>120); # un peu plus de couleurs
$tmp->Evaluate(operator=>'Multiply', value=>255/245);
}
# Lit l'image suivante. Retourne 1 si ok, 0 si fin fichier
sub next_image {
my($IN, $OUT, $w, $h) = @_;
# nom du fichier BMP
my $name = sprintf($img_pattern, $cpt++);
# taille fichier BMP
my $expected_size = $h*(($w*3 + 3)&~3) + 54; # couleur
#print "$w, $h, $expected_size\n";
# on nourrit ffmpeg jusqu'a obtenir un fichier BMP fini
while($expected_size != -s $name) {
my $buf;
my $read = read($IN,$buf,8192);
last unless $read;
syswrite $OUT, $buf, $read;
}
# lecture image
my $tmp = Image::Magick->new();
my $z = $tmp->Read($name);
#print STDERR $z if $z;
return 0 if $z; # si erreur => fin fichier
unlink $name;
# dither
$tmp->Set(depth=>16);
$tmp->Set(colorspace=>$LINEAR_SPACE);
&tune_image($tmp);
my @px = $tmp->GetPixels(height=>$h, normalize=>"True"); undef $tmp;
for my $c (@px) {$c = int($c*255);}
$dR = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dR{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$RED2],$lin_pal[$RED3]);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? 3 : 2) if $v>=$a;
($a,$b) = ($lin_pal[$RED1],$a);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? 2 : 1) if $v>=$a;
($a,$b) = ($lin_pal[$RED0],$a);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? 1 : 0);
} unless $dR;
$dG = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dG{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$GRN2],$lin_pal[$GRN3]);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 3 : 2) if $v>=$a;
($a,$b) = ($lin_pal[$GRN1],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 2 : 1) if $v>=$a;
($a,$b) = ($lin_pal[$GRN0],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 1 : 0);
} unless $dG;
$dB = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dB{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$BLU1],$lin_pal[$BLU2]);
return $glb_dB{$k}=(($v-$a)/($b-$a)>=$d ? 2 : 1) if $v>=$a;
($a,$b) = ($lin_pal[$BLU0],$a);
return $glb_dB{$k}=(($v-$a)/($b-$a)>=$d ? 1 : 0);
} unless $dB;
@cible = (0)x8000;
$pset = sub {
my($x,$y, $r,$g,$b) = @_;
my($p)=$y*80 + ($x>>2);
my($a,$b) = ($g,$r*3+$b+4);
($a,$b)=($a<<4,$b<<4) unless $x&1;
($a,$b)=($a<<8,$b<<8) if $x&2;
($a,$b)=($b,$a) if $zigzag && ($x&1);
$cible[$p] |= $a;
$cible[$p+40] |= $b;
} unless $pset;
for my $y (0..$h-1) {
for my $x (0..$w-1) {
my($d) = $mat[$x%$mat_x][$y%$mat_y];
my(@p) = splice(@px, 0, 3);
$pset->($x,$y, $dR->($p[0],$d),$dG->($p[1],$d),$dB->($p[2],$d));
}
}
# compute delta
for my $i (0..$#cible) {$cible[$i]^=$ecran[$i]}
@cible[8000..8002] = (255,255,255); # sentinelle
# intrelacement
if($interlace==1) {
my $f = sub {
my($y) = @_;
splice(@cible, $y*40, 40, (0)x40);
#splice(@cible, $y*40+8192, 40, (0)x40);
};
for(my $y=$cpt&1; $y<200; $y+=2) {$f->($y);}
}
if($interlace==2) {
my $f = sub {
my($y) = @_;
my($cpt) = 0;
for my $x (0..39) {
my($c) = $cible[$y*40+$x];
++$cpt if $c&0x000f;
++$cpt if $c&0x00f0;
++$cpt if $c&0x0f00;
++$cpt if $c&0xf000;
}
splice(@cible, $y*40, 40, (0)x40) if $cpt<32;
};
for(my $y=$cpt%2; $y<200; $y+=3) {$f->($y);}
}
return 1;
}
sub guess_zoom {
my($IN, $OUT, $w, $h) = @_;
my $BUFFERSIZE = 7*int(($hz+$fps-1)/$fps);
unlink(<tmp/img*.bmp>);
@ecran = ((0)x16384);
local $cpt = 1;
my $size = 0;
while(&next_image($IN,$OUT,$w,$h)) {
for(my $p = 0; $p<8000;) {
my $k = 1;
while($k<255 && !$cible[$p+$k]) {++$k;}
$p+=$k;
++$size if (($size&511)%7)==0; # audio
$size += 3; # video
}
# audio + goto 0
$size += 4;
print STDERR sprintf("avg %dx%d frame length = %d bytes (%d%%) @ %ds \r", $w, $h, int($size/($cpt-1)), int(100*$size/(($cpt-1)*$BUFFERSIZE)), $cpt-1);
}
$total_sec = $cpt-1;
unlink(<tmp/img*.bmp>);
print STDERR "\n";
my $len_per_img = ($size/($cpt-1)) * (1.05); # 5% extra
close($IN);
close($OUT);
my $zoom = $BUFFERSIZE/$len_per_img;
return $zoom>=1?$zoom:sqrt($zoom);
}
sub dither_init {
my($in, $out, $dither, $w, $h) = @_;
my $expected_size = $h*(($w*3 + 3)&~3) + 54;
# param dither
@mat = ([1]);
@mat = ([1,3],
[4,2]) if $dither eq "bayer2";
@mat = ([7,8,2],
[6,9,4],
[3,5,1]) if $dither eq "sam3";
@mat = ([3,7,4],
[6,1,9],
[2,8,5]) if $dither eq "3x3";
@mat = ([6, 9, 7, 12],
[14, 3, 15, 1],
[8, 11, 5, 10],
[16, 2, 13, 4]) if $dither eq "vac4";
@mat = ([1,9,3,11],
[13,5,15,7],
[4,12,2,10],
[16,8,14,6]) if $dither eq "bayer4";
@mat = ([21,2,16,11,6],
[9,23,5,18,14],
[19,12,7,24,1],
[22,3,17,13,8],
[15,10,25,4,20]) if $dither eq "vac5";
@mat = ([35,57,19,55,7,51,4,21],
[29,6,41,27,37,17,59,45],
[61,15,53,12,62,25,33,9],
[23,39,31,49,2,47,13,43],
[3,52,8,22,36,58,20,56],
[38,18,60,46,30,5,42,28],
[63,26,34,11,64,16,54,10],
[14,48,1,44,24,40,32,50]) if $dither eq "vac8";
$mat_x = 1+$#mat;
$mat_y = 1+$#{$mat[0]};
@teo_pal = (0,100,127,142,163,179,191,203,215,223,231,239,243,247,251,255);
@lin_pal = (0, 33, 54, 69, 93,115,133,152,173,188,204,220,229,237,246,255);
my @pc2teo = ();
for my $i (1..$#teo_pal) {
my($a,$b,$c) = ($teo_pal[$i-1],($teo_pal[$i-1]+$teo_pal[$i])>>1,$teo_pal[$i]);
for my $j ($a..$b) {$pc2teo[$j] = $i-1;}
for my $j ($b+1..$c) {$pc2teo[$j] = $i;}
}
my @tabR = (0)x16;
my @tabG = (0)x16;
my @tabB = (0)x16;
my $time = 1;
my $run = 1;
while(1) {
my $name = sprintf($img_pattern, $time);
if($expected_size != -s $name) {
last unless $run;
my $buf;
my $read = read($in,$buf,4096);
if($read) {
syswrite $out, $buf, $read;
} else {
$run = 0;
close($out);
}
} else {
# image complete!
print STDERR $time++,"s\r";
# lecture de l'image
my $img = Image::Magick->new();
my $x = $img->Read($name); die "$x, stopped $!" if $x;
unlink $name;
&tune_image($img);
if(!defined $pal4096) {
my @px;
for my $r (0..15) {
for my $g (0..15) {
for my $b (0..15) {
push(@px, $teo_pal[$r], $teo_pal[$g], $teo_pal[$b]);
}
}
}
$pal4096 = &px2img(256,16, @px);
}
$img->Remap(image=>$pal4096, dither=>"true", "dither-method"=>"Floyd-Steinberg");
# trammage
my @px = $img->GetPixels(height=>$h, normalize=>"True");
undef $img;
for(my $i=0; $i<$#px; $i+=3) {
++$tabR[$pc2teo[int($px[$i+0]*255)]];
++$tabG[$pc2teo[int($px[$i+1]*255)]];
++$tabB[$pc2teo[int($px[$i+2]*255)]];
}
}
}
close($in);close($out);
unlink(<tmp/img*.bmp>);
my $f = sub {
my($name, @t) = @_;
my($tot, $acc, $max, @r);
my(@tab) = splice(@t,0,16);
for my $t (@tab) {$max = $t if $t>$max; $tot += $t;}
$r[0] = -1; my($z) = $tot;
for my $t (@t) {
for(my $i=$r[$#r]+1; $i<16; ++$i) {
$acc += $tab[$i];
if($acc>=$t*$z) {
$z-=$acc; $acc=0;
push(@r, $i);
last;
}
}
}
shift @r;
if($#r!=$#t) {
my($deb,$fin)=($r[0], $r[$#r]);
$z = $tot;
for my $i (0..$deb) {$z -= $tab[$i];}
for my $i ($fin..15) {$z -= $tab[$i];}
@r = ($deb);
for my $t (@t[1..$#t-1]) {
for(my $i=$r[$#r]+1; $i<16; ++$i) {
$acc += $tab[$i];
if($acc>=$t*$z) {
$z-=$acc; $acc=0;
push(@r, $i);
last;
}
}
}
push(@r, $fin);
@r = sort { $a <=> $b } @r;
}
for my $i (0..$#tab) {print sprintf("%s%2d:%3d%% %s\n", $name, $i, int(100*$tab[$i]/$tot), "X"x(int(50*$tab[$i]/$max)));}
print join(' ', @r),"\n";
return @r;
};
# ($RED0, $RED1, $RED2, $RED3) = $f->("RED", @tabR, 0.02, 0.33, 0.66, 0.95);
# ($GRN0, $GRN1, $GRN2, $GRN3) = $f->("GRN", @tabR, 0.02, 0.33, 0.66, 0.95);
# ($BLU0, $BLU1, $BLU2) = $f->("GRN", @tabR, 0.02, 0.50, 0.95);
($RED0, $RED1, $RED2, $RED3) = $f->("RED", @tabR, 0.02, 0.33, 0.50, 0.95);
($GRN0, $GRN1, $GRN2, $GRN3) = $f->("GRN", @tabG, 0.02, 0.33, 0.50, 0.95);
($BLU0, $BLU1, $BLU2) = $f->("BLU", @tabB, 0.02, 0.50, 0.95);
}
sub px2img {
my($width,$height,@px) = @_;
open(PX2IMG,">/tmp/.toto2.pnm");
print PX2IMG "P6\n$width $height\n255\n",pack('C*', @px),"\n";
close(PX2IMG);
my $img2 = Image::Magick->new();
$img2->ReadImage("/tmp/.toto2.pnm");
unlink "/tmp/.toto2.pnm";
return $img2;
}
sub magick_init {
if(!$_magick) {
$_magick = 1;
eval 'use Image::Magick;';
# determination de l'espace RGB lineaire
my $img = Image::Magick->new(size=>"256x1", depth=>16);
$img->Read('gradient:black-white');
$img->Set(colorspace=>'RGB');
#$img->Set(colorspace=>"Gray") unless $coul;
my @px1 = $img->GetPixel(x=>128, y=>0);
$img->Read('gradient:black-white');
$img->Set(colorspace=>'sRGB');
#$img->Set(colorspace=>"Gray") unless $coul;
my @px2 = $img->GetPixel(x=>128, y=>0);
my $d1 = $px1[0]-0.5; $d1=-$d1 if $d1<0;
my $d2 = $px2[0]-0.5; $d2=-$d2 if $d2<0;
$LINEAR_SPACE = $d1>=$d2 ? "RGB" : "sRGB";
#print $px1[0], " ",$px2[0]," $LINEAR_SPACE\n";
}
}
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
-
- Messages : 7964
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
Il y a une idée dans ce que tu suggères.Daniel a écrit :On doit pouvoir aussi imaginer des systèmes de codage complètement différents, avec par exemple un secteur de carte SD contenant uniquement des informations pour la ram vidéo "basse" et le secteur suivant des informations pour la rom vidéo "haute". C'est facile de rêver, certainement plus difficile à faire.
Supposons que X pointe sur la RAM basse, et Y sur la RAM haute, et que dans le traitement de l'interbloc coté TO (45 cycles), on fasse un "EXG X,Y" (8 cycles) on a a peu près l'idée que tu indiques, mais sans aucune complexité. Tout se passe comme si on décodait RAMA et RAMB dans deux flux entrelacés.
Si le temps de décodage d'un bloc est très faible pour que l'alternance RAMA/RAMB à l'écran ne se voit pas, ca serait une solution super élégante. Un bloc se décode en 73*60µs=4.38ms, ce qui est bien moins que la VBL (20ms). Donc le décodage du bloc RAMA suivi par celui du bloc RAMB devrait être quasi invisible à l'écran. C'est très prometteur, et en outre puisqu'on déplace le truc couteux en interbloc, ca serait parfaitement adaptable aux TO/MO aussi (un XOR sur le PIA système doit manger 12 ou quelques cycles. Il y a de la place dans la temporisation).
Pour l'encodeur, cela revient à compresser deux flux. C'est plus compliqué mais pas insurmontable.
Par ailleurs je pense que l'idée de déplacer le test du déplacement nul à la fin de bloc est aussi intéressant parce que le décodage d'un bloc s'en trouve grandement accéléré:
Code : Tout sélectionner
LDA <$CC (4)
STA <$CB (4) ack + son
LDB <$CC (4) depl
ABX (3)
LDA <$CC (4)
LDB <$CC (4)
STD ,X (5)
LDB <$CC (4) depl
ABX (3)
LDA <$CC (4)
LDB <$CC (4)
STD ,X (5)
Hum.. bon dans un 1er temps, voyons si déjà avec la version à 60µs ca marche ou pas. Ca a l'avantage de garder les timings arduinos intacts.
(il faudrait que je vous montre la qualité des images avec le mode BM16. C'est vraiment bon!)
[EDIT] voilà en 5fps: http://dl.free.fr/rtFdgJtlT, à voir sur TO8 émulé avec le player disponible dans le code BASIC de http://forum.system-cfg.com/viewtopic.p ... c6#p109198. Un facteur x2 dans la vitesse du player serait formidable, mais même à 5fps, c'est déjà pas mal. La qualité des couleurs est sans commune mesure avec le player pour MO5/TO7.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
-
- Messages : 7964
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
Arf je suis passé à coté d'une optimisation importante du nombre de couleurs
Je suis parti de 3*4+4=16, ce qui fait qu'avec 16 couleurs de base on affiche 3*4*4=48 couls visible sans tramage (4 verts, 4 rouges, et 3 bleus). Or parmi les 16 couleurs produites, on a deux fois du noir. Si on remplace l'un des noirs par une 5eme couleur on a le découpage suivant: 3 bleus, 4 rouges, 5 verts, soit 3*4*5=60 couls visibles simultanément sur un écran TO8! (j'ai attribué le nombre de couleurs par composantes à leur sensibilité relatives pour l'œil humain).
Avec une palette e 60 couls, les images sont encore plus belles
Je suis parti de 3*4+4=16, ce qui fait qu'avec 16 couleurs de base on affiche 3*4*4=48 couls visible sans tramage (4 verts, 4 rouges, et 3 bleus). Or parmi les 16 couleurs produites, on a deux fois du noir. Si on remplace l'un des noirs par une 5eme couleur on a le découpage suivant: 3 bleus, 4 rouges, 5 verts, soit 3*4*5=60 couls visibles simultanément sur un écran TO8! (j'ai attribué le nombre de couleurs par composantes à leur sensibilité relatives pour l'œil humain).
Avec une palette e 60 couls, les images sont encore plus belles
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Re: [Thomson] Vidéo avec son en streaming
Tu es arrivé à un point où il reste encore probablement quelques optimisations à faire pour gagner deux ou trois cycles, mais une vitesse multipliée par deux je n'y crois pas. Alors, quoi qu'on fasse, il y aura toujours un compromis à faire entre le nombre de couleurs, la définition et la vitesse.
Les derniers essais en bitmap16 48 couleurs 5fps sont excellents pour les vidéos pas trop animées, et en réel progrès par rapport aux versions en mode standard Thomson. Par contre, dans les clips très rythmés, avec beaucoup de changements de plan et des travellings rapides, il est plus difficile d'accepter les 5fps. Alors que faut-il faire ? A part overclocker le TO8, ou remplacer le 6809 par un 6309, il n'y a pas trop de solutions. Ou alors accepter une image réduite.
Il restera aussi à explorer d'autres modes, en particulier le 640x200 monochrome. Pour certaines images une meilleure définition est parfois préférable à la couleur.
Les derniers essais en bitmap16 48 couleurs 5fps sont excellents pour les vidéos pas trop animées, et en réel progrès par rapport aux versions en mode standard Thomson. Par contre, dans les clips très rythmés, avec beaucoup de changements de plan et des travellings rapides, il est plus difficile d'accepter les 5fps. Alors que faut-il faire ? A part overclocker le TO8, ou remplacer le 6809 par un 6309, il n'y a pas trop de solutions. Ou alors accepter une image réduite.
Il restera aussi à explorer d'autres modes, en particulier le 640x200 monochrome. Pour certaines images une meilleure définition est parfois préférable à la couleur.
Daniel
L'obstacle augmente mon ardeur.
L'obstacle augmente mon ardeur.
-
- Messages : 7964
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
J'ai été pas mal silencieux sur ce sujet ces derniers temps. C'est que j'ai pas mal galéré dans la mise au point du player TO8 suivant, mais j'y suis arrivé en basic:
Il y a deux idées importantes dans ce nouveau player:
On a donc un player rapide, reste à faire l'encodage des vidéos. Là aussi j'ai pas mal ramé parce qu'il fallait pas mal chambouler le script, qui finalement est bien plus simple qu'avant.
Et les résultats? Ben ca encode... suspens...
Mais on peut quand même réfléchir: le débit est effectivement meilleur. On passe de 60cycles à 48cycles, mais cela n'est que 20% plus rapide. 20% de débit en plus permet de faire passer 20% de pixel en plus, ce qui revient à avoir une image sqrt(1.2)=1.09 fois plus large qu'avec le player 60µs. 10% en largeur c'est pas bézef Déjà qu'on avait du mal à dépasser un zoom de 50% à 60µs, là ca va passer à un zoom de 55%. On ne verra pas le changement.
Reste que la latence RAMA/RAMB n'est plus que de 3.5ms au lieu de 96. Est-ce que ca va se voir? ... mystère ... Il faut attendre la fin des encodages...
Code : Tout sélectionner
ORG $9000
*------------------------------------------------------
* Point d'entree
*------------------------------------------------------
INI
PSHS U,Y,X,DP,B,A,CC empile les registres
ORCC #$50 masque les interruptions
LDU #txt
TXT
LDB ,U+
JSR $E803
BNE TXT
* init
LDA #$E7 registres systemes
TFR A,DP
* Mise en place du block play 73 fois
LDU #FINPLAY7 fin block PLAY 7
LDX #72*(FINPLAY7-PLAY7)
RECOPIE
LDA <(PLAY7-FINPLAY7),U
STA ,U+
LEAX -1,X
BNE RECOPIE
* mise en place "JMP FINBLOC"
LDA #$7E "JMP"
STA ,U+
LDD #FINBLOC2
STD ,U
* init ports d'entree/sortie
CLRA A=$00
CLRB B=$00
STD <$CE selectionne DDRA et DDRB
LDB #$7F B=$7F
STD <$CC PA b0-7 en entree, PB b0-6 en sortie
ORA #$04 set b2
STA <$CE selectionne PORTA
STA <$CF selectionne PORTB
*------------------------------------------------------
* Chargement donnees
*------------------------------------------------------
LDX #DATA-2
BRA PLAY7
FINBLOC2
LDA <$CC (4) lecture echantillon son avec B6 alternant
PSHS A
LDD #FINBLOC fin bloc normal
STD ,U
* recup palette
LDX #DATA
CLRB
STB <$DB
PALETTE
LDD ,X++
STB <$DA
STA <$DA
CMPX #DATA+16*2
BNE PALETTE
* ecran visible en $A000
LDD #$9000
STA <$E7
STB <$E5
LDX #$A000 pointeur ecran
LEAY 8192,X
PULS A
BRA FINBLOC+2 (4) joue le son et acknowledge
*------------------------------------------------------
* JOUE LA VIDEO ET LA MUSIQUE
* Boucle de 60 cycles = 16666.67 Hz
*------------------------------------------------------
* BRA PLAY7
*------------------------------------------------------
* RETOUR AU BASIC
*------------------------------------------------------
SORTIE
PULS CC,A,B,DP,X,Y,U,PC
* retour a l'assembleur
* SWI
txt
fcb 27,$60
fcb 27,$5e
fcb 0
* fin de bloc 512: son + attente (4(JMP)+8+20+8+8=48 cycles)
FINBLOC
* echantillon son (8 cycles)
LDA <$CC (4) lecture echantillon son avec B6 alternant
STA <$CD (4) joue le son et acknowledge
* modulo du poineur ecran (20 cycles)
CMPX #$A000+8000 (4)
BNE FINBL1 (3)
LDX #$A000 (3)
NOP (2)
NOP (2)
BRA FINBL3 (3)
FINBL1
CMPX #$C000+8000 (4)
BNE FINBL2 (3)
LDX #$C000 (3)
BRA FINBL4 (3)
FINBL2
BRA FINBL3 (3)
FINBL3
BRA FINBL4 (3)
FINBL4
* permutation (8)
EXG X,Y (8)
* fin fichier (8)
BRN * (3)
TSTA (2)
BGE SORTIE (3) sortie?
*------------------------------------------------------
* lecture d'un bloc de 7 octets 8+(17+16)*2=48 cycles
*
* <son> <depA> <vidA1> <vidA2> <depB> <vidB1> <vidB2>
*------------------------------------------------------
PLAY7
* echantillon son (8 cycles)
LDA <$CC (4) lecture echantillon son avec B6 alternant
STA <$CD (4) joue le son et acknowledge
* lecture octets depl + video1 + video2 = 20 cycles
LDB <$CC (4) lecture octet deplacement
ABX (3) ajout de l'increment
LDA <$CC (4) lecture octet video1
LDB <$CC (4) lecture octet video2
STD ,X (5) affiche l'octet image
* lecture octets dep2 + video3 + video4 = 20 cycles
LDB <$CC (4) lecture octet deplacement
ABX (3) ajout de l'increment
LDA <$CC (4) lecture octet video1
LDB <$CC (4) lecture octet video2
STD ,X (5) affiche l'octet image
FINPLAY7
RMB 72*(FINPLAY7-PLAY7)
* envoi fin bloc 512
* JMP FINBLOC (4)
RMB 3
DATA
RMB 256
END INI
Code : Tout sélectionner
10 CLEAR,&H8FFF
30 READ A$:IF LEN(A$)=4 THEN A=VAL("&H"+A$):GOTO 30
40 IF A$="**" THEN EXEC A ELSE POKE A,VAL("&H"+A$):A=A+1:GOTO 30
50 DATA 9000
60 DATA 34,7F,1A,50,CE,90,6B,E6,C0,BD
70 DATA E8,03,26,F9,86,E7,1F,8B,CE,90
80 DATA AB,8E,06,30,A6,C8,EA,A7,C0,30
90 DATA 1F,26,F7,86,7E,A7,C0,CC,90,3D
100 DATA ED,C4,4F,5F,DD,CE,C6,7F,DD,CC
110 DATA 8A,04,97,CE,97,CF,8E,96,DC,20
120 DATA 58,96,CC,34,02,CC,90,70,ED,C4
130 DATA 8E,96,DE,5F,D7,DB,EC,81,D7,DA
140 DATA 97,DA,8C,96,FE,26,F5,CC,90,00
150 DATA 97,E7,D7,E5,8E,A0,00,31,89,20
160 DATA 00,35,02,20,09,35,FF,1B,60,1B
170 DATA 5E,00,96,CC,97,CD,8C,BF,40,26
180 DATA 07,8E,A0,00,12,12,20,0C,8C,DF
190 DATA 40,26,05,8E,C0,00,20,04,20,00
200 DATA 20,00,1E,12,21,FE,4D,2C,D4,96
210 DATA CC,97,CD,D6,CC,3A,96,CC,D6,CC
220 DATA ED,84,D6,CC,3A,96,CC,D6,CC,ED
230 DATA 84
240 DATA 9000,**
- Ce coup ci le décodage ne fait plus de test sur l'octet de déplacement à 0, et le traite comme une avancée de 0 octets. Le test de retour au début écran est repoussé à la fin de bloc.
- On gère deux pointeurs vidéos (les registres X et Y), chacun pointant respectivement sur la RAMA et l'autre sur la RAMB. En fin de bloc on les échange, ce qui fait qu'il ne faut plus attendre de décoder les 8000 octets de la RAMA (96ms dans le pire des cas) avant de décoder la RAMB. La latence entre RAMA et RAMB est maintenant de 3.5ms, ce qui est nettement mieux.
On a donc un player rapide, reste à faire l'encodage des vidéos. Là aussi j'ai pas mal ramé parce qu'il fallait pas mal chambouler le script, qui finalement est bien plus simple qu'avant.
Code : Tout sélectionner
#/bin/perl
##############################################################################
# Conversion de fichier video en fichier SD fonctionnant avec le
# player SDANIM3 de Daniel Coulom pour THOMSON modifié pour le mode
# "2 octets"
#
# (http://forum.system-cfg.com/viewtopic.php?p=104928#p104928)
#
# par Samuel Devulder.
#
# Historique:
# ===========
# 09/06/2015 - version initiale. Portage semi direct du code C.
#
# 10/06/2015 - utilisation de la matrice vac-8 qui donne une image plus
# fine que le h4x4a
# - fps et zoom revu pour avoir une vitesse et une qualité
# très correcte (10fps et zoom 70%)
# - optimisation de la vitesse de compression:
# - les bords noirs supérieur et inférieurs de l'écran
# sont ignores
# - la boucle while cherchant les différences ne lit à
# présent qu'un seul des deux tablraux puisque la lecture
# nous fournit en cible le delta avec l'image actuelle.
# du coup la vitesse d'encodage passe de x0.4 à x1.
# - meilleure synchro video quand une image est compressée en
# moins de BUFFSIZE octets: on retourne simplement en haut
# de l'écran sans re-encoder la même image de sorte qu'au
# changement d'écran on redémarre en haut de la nouvelle image.
# On ne voit quasiment plus des demi-images et la vidéo est
# très fluide
# - mise en place d'une correction sonnore auto-adaptative.
# Donne d'excellents resultats pour Dire Straits (sultan of
# swing est fort et clair avec une bonne dynamique à présent).
# - stockage des images temporaires dans un sous-dossier tmp/
# pour ne opas polluer le répertoire courant.
#
# 12/06/2015 - mise en place d'un algo pour calculer le meilleur zoom
# ou framerate. Dans un 1er temps la vidéo est échantillonnée
# à 1 image /sec (pour aller vite) avec un zoom de 1. Puis
# compressée sans limite de taille de buffer. A la fin de la
# vidéo on obtient le nombre d'octets moyen par image (LEN_BY_IMG).
#
# si LEN_BY_IMG est inférieur au BUFFERSIZE associé au framerate
# choisi (FPS), ca veut dire que le framerate est un peu petit
# et que l'on gaspille des octets puisque BUFFERSIZE n'est pas
# complètement rempli. On peut alors augmenter le framerate de
# la même proportion d'écart entre BUFFERSIZE et LEN_BY_IMG:
# FPS <- FPS*(BUFFERSIZE/LEN_BY_IMG)
# C'est ce qu'il se passe avec les video facilement compressibles
# l'outil va en profiter pour augmenter le FPS afin d'occuper
# tout le buffer alloué à une image. Cependant le cas le plus
# fréquent est le suivant:
#
# Si LEN_BY_IMG est supérieur à BUFFERSIZE, c'est que ce dernier
# est trop petit pour contenir une image compressée. On peut alors
# jouer sur le facteur de zoom pour que l'image compressée tienne
# dans BUFFERSIZE. En première approximation si on réduit l'image
# d'un facteur r<1, le nombre de pixel à compresser est réduit
# d'un facteur r*r. La taille compressée est elle aussi sensiblement
# réduite du même facteur (imaginez diviser une image par 2, il y a
# 4x fois moins d'information à compresser, et la compression sera
# aussi 4x plus petite). Du coup cela veut dire que r*r peut valoir
# LEN_BY_IMG/BUFFERSIZE plus une petite marge (que je fixe à 15%).
# Cela signifie que l'on peut utiliser un zoom de
# SQRT(BUFFERSIZE/(LEN_BY_IMG + 15%))
# pour avoisiner le taux d'occupation idéal de BUFFERSIZE.
#
# - mise en place d'un contraste sigmoidal qui tasse les intensités
# sombre et lumineuses afin d'avoir des image très contrastée qui
# apparaissent dès lors un peu moins bruitées. Cependant je
# les trouve alors moins riche au niveau des dégradés.
#
# 27/06/2015 - Adapation au player "2octets", retour au ordered-dither,
# detection du bon espace RGB linéaire.
#
# 30/06/2015 - Ajout d'un algo de re-normalisation des niveau RVB pour eviter
# d'avoir un film globalement trop sombre. Le contraste et les
# couleurs sont grandement amméliorés.
#
# 03/07/2015 - Adaptation au protocole "sans acquitement" permettant d'avoir
# une bande passante video 3x par rapport à l'audio.
#
# 11/07/2015 - Adaptation au protocole "par block" avec un débit de 60 cycles
# pour un échantillon audio et 2 triplets video.
#
# 18/09/2015 - Debut d'adapation pour le mode BM16.
#
# 26/09/2015 - Modif pour encoder RAMA/RAMB ensemble.
#
# 28/09/2015 - Bugfix des images trashees qaund zigzag=0
# Ammeliroation du choix des couleurs.
#
# 08/10/2015 - Adaptation au player 48µs.
#
##############################################################################
$file = $ARGV[0];
$name = $file; $name =~ s/\.[^\.]*$//; $name .= ".sd";
exit if -e $name;
# params par defaut
mkdir("tmp");
$img_pattern = "tmp/img%05d.bmp";
($w, $h) = (160, 100); # 16:9
$cycles = 48; # cyles cpu par echantillon audio
$hz = int(2000000/$cycles+.5)/2;
$fps = 8;
$interlace = 0; # 0=off, 1=simple
$audio_dither = 1;
$dither = "bayer4";
$zigzag = 0;
# recherche la taille de l'image
($x,$y, $aspect_ratio) = (160,100,"16:9");
open(IN, "./ffmpeg -i \"$file\" 2>&1 |");
while(<IN>) {
if(/, (\d+)x(\d+)/) {
($x,$y) = ($1, $2);
# 4:3
if(abs($x - 4/3*$y) < abs($x - 16/9*$y)) {
($w,$h,$aspect_ratio) = (133,100,"4:3");
}
}
}
close(IN);
$h = int(($w=160)*$y/$x);
$w = int(($h=100)*$x/$y) if $h>100;
print "\n",$file," : ${x}x${y} ($aspect_ratio) -> ${w}x${h}\n";
# AUDIO: unsigned 8bits, mono, 16125hz
open(AUDIO, "./ffmpeg -i \"$file\" -v 0 -f u8 -ac 1 -ar ".int(2*$hz)." -acodec pcm_u8 - |");
binmode(AUDIO);
# tuyau vers ffmpeg pour images
open(FFMPEG,'| (read line; $line)');# -vf format=gray
binmode(FFMPEG);
# fichier video (entree)
open(IN, "<$file");
binmode(IN);
# détermination des parametres de dither
open(DITHER_O,"| ./ffmpeg -i - -v 0 -r 1 -s ${w}x${h} -an $img_pattern");
open(DITHER_I, "<$file");
binmode(DITHER_I);
binmode(DITHER_O);
# guess zoom
open(GUESS_O,"| ./ffmpeg -i - -v 0 -r 1 -s ${w}x${h} -an $img_pattern");
open(GUESS_I, "<$file");
binmode(GUESS_I);
binmode(GUESS_O);
# init image magick
&magick_init;
# parametres de dither
&dither_init(\*DITHER_I, \*DITHER_O, $dither, $w, $h);
# determination du zoom optimal
$zoom = $zoom<0?-$zoom:&guess_zoom(\*GUESS_I, \*GUESS_O, $w, $h);
close(GUESS_I); close(GUESS_O);
$fps = int($fps * ($zoom>1?$zoom:1));
$t = 1;
$w = int($w*($zoom>$t?1:$zoom/$t));
$h = int($h*($zoom>$t?1:$zoom/$t));
print sprintf("zoom = %.2g -> %dx%d @ %dfps\n", $zoom, $w, $h, $fps);
$fps = 25 if $fps>25;
$cmd = "./ffmpeg -i - -v 0 -r $fps -s ${w}x${h} -an $img_pattern\n";
syswrite(FFMPEG, $cmd, length($cmd));
# nettoyage
unlink(<tmp/img*.bmp>);
# fichier sd (sortie)
open(OUT, ">$name");
binmode(OUT);
# multiplicateur audio
@audio_cor = (8, 255);
# compteur image
$cpt = 1;
# 1er bloc donnée: palette
push(@pal, 0, $GRN0*16, 0, $GRN1*16, 0,$GRN2*16, 0,$GRN3*16, 0, $GRN4*16);
for my $r ($RED0, $RED1, $RED2, $RED3) {
for my $b ($BLU0, $BLU1, $BLU2) {
push(@pal, $b, $r) unless $b==$BLU0 && $r==$RED0;
}
}
while($#pal<292) {push(@pal, 0);}
# compression
$time = 0; $start = time; $realimg = 0; $pause = 60;
$cycles_per_img = 1000000/$fps;
$current_cycle = $cycles_per_img; $clk = 0;
# 1er bloc donnees
@buf = ();
while(1) {
push(@buf, 0x80 | ($clk ^= 0x40)); # son
last if $#buf==511;
push(@buf, 2); # depl
push(@buf, splice(@pal,0,2));
push(@buf, 2); # depl
push(@buf, splice(@pal,0,2) );
}
print OUT pack('C*', @buf); @buf = ();
# compression
@ecran = ((0) x 16384);
$pos1 = 0; $pos2 = 8192; $num_oct = 0;
for($running = 0x80; 1; $current_cycle += $cycles) {
push(@buf, &echantillon_audio());
if($#buf<511) {
push(@buf, &triplets_video());
} else {
# write
print OUT pack('C*', @buf); @buf = ();
# fin fichier
last unless $running;
# ecran terminé
$pos1 -= 8000 if ($pos1&8191)==8000;
($pos1,$pos2) = ($pos2,$pos1);
}
}
# nettoyage et fermetue flux
print STDERR "\n";
unlink(<tmp/img*.bmp>);
close(OUT);
close(IN);
close(FFMPEG);
close(AUDIO);
# Lit 2 échantillons audio de FFMPEG et le converti en échantillon audio pour le flux SD
sub echantillon_audio {
if(!@AUDIO) {
my($buf);
$running = 0 if !read(AUDIO,$buf,8192);
push(@AUDIO, unpack('C*', $buf));
}
my $v = (shift(@AUDIO)+shift(@AUDIO))/2;
# volume auto
$audio_cor[1] = $v if $v<$audio_cor[1];
$v-=$audio_cor[1];
$audio_cor[0] = 255/$v if $v*$audio_cor[0]>255;
$v *= $audio_cor[0];
# dither audio
$v += int(rand(3)) if $audio_dither;
$v=255 if $v>255;
# sortie audio
return ($v>>2) | $running | ($clk ^= 0x40);
}
# retourne 2 triplets video
sub triplets_video {
my(@buf);
if($current_cycle>=$cycles_per_img) {
$current_cycle-=$cycles_per_img;
#print "pos1=$pos1 pos2=$pos2 num=$num_oct\n";
++$realimg if $num_oct>=16000; $num_oct = 0;
&next_image(\*IN,\*FFMPEG,$w,$h);
# infos à l'écran
if($cpt%$fps == 0) {
++$time;
my($d) = time-$start+.0001;
print STDERR sprintf("%d:%02d:%02d (%.2gx) v=1:%.3g a=(x%+d)*%.1g \r",
int($time/3600), int($time/60)%60, $time%60,
int(100*$time/$d)/100, $realimg/($cpt-1), -$audio_cor[1], $audio_cor[0]);
# pour ne pas trop faire chauffer le CPU
if($d>$pause) {$pause = $d+60;sleep(10);}
}
}
for (1..2) {
my $k = 0;
while($k<255 && !$cible[$pos1+$k]) {++$k;}
$pos1+=$k; $num_oct+=$k;
push(@buf, $k, $ecran[$pos1] ^= $cible[$pos1], $ecran[$pos1+1] ^= $cible[$pos1+1]);
$k = $pos1&8191;
$cible[$pos1]=0 if $k<8000;
$cible[$pos1+1]=0 if $k+1<8000;
}
return @buf;
}
sub tune_image {
my($tmp) = @_;
$tmp->Modulate(saturation=>120); # un peu plus de couleurs
$tmp->Evaluate(operator=>'Multiply', value=>255/245);
}
# Lit l'image suivante. Retourne 1 si ok, 0 si fin fichier
sub next_image {
my($IN, $OUT, $w, $h) = @_;
# nom du fichier BMP
my $name = sprintf($img_pattern, $cpt++);
# taille fichier BMP
my $expected_size = $h*(($w*3 + 3)&~3) + 54; # couleur
#print "$w, $h, $expected_size\n";
# on nourrit ffmpeg jusqu'a obtenir un fichier BMP fini
while($expected_size != -s $name) {
my $buf;
my $read = read($IN,$buf,8192);
last unless $read;
syswrite $OUT, $buf, $read;
}
# lecture image
my $tmp = Image::Magick->new();
my $z = $tmp->Read($name);
#print STDERR $z if $z;
return 0 if $z; # si erreur => fin fichier
unlink $name;
# dither
$tmp->Set(depth=>16);
$tmp->Set(colorspace=>$LINEAR_SPACE);
&tune_image($tmp);
my @px = $tmp->GetPixels(height=>$h, normalize=>"True"); undef $tmp;
for my $c (@px) {$c = int($c*255);}
$dR = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dR{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$RED2],$lin_pal[$RED3]);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? 3 : 2) if $v>=$a;
($a,$b) = ($lin_pal[$RED1],$a);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? 2 : 1) if $v>=$a;
($a,$b) = ($lin_pal[$RED0],$a);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? 1 : 0);
} unless $dR;
$dG = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dG{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$GRN3],$lin_pal[$GRN4]);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 4 : 3) if $v>=$a;
($a,$b) = ($lin_pal[$GRN2],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 3 : 2) if $v>=$a;
($a,$b) = ($lin_pal[$GRN1],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 2 : 1) if $v>=$a;
($a,$b) = ($lin_pal[$GRN0],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? 1 : 0);
} unless $dG;
$dB = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dB{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$BLU1],$lin_pal[$BLU2]);
return $glb_dB{$k}=(($v-$a)/($b-$a)>=$d ? 2 : 1) if $v>=$a;
($a,$b) = ($lin_pal[$BLU0],$a);
return $glb_dB{$k}=(($v-$a)/($b-$a)>=$d ? 1 : 0);
} unless $dB;
@cible = (0)x16384;
$pset = sub {
my($x,$y, $r,$g,$b) = @_;
my($p)=$y*80 + ($x>>2) + ($x&2?8192:0);
my($a,$b) = ($g,$r+$b==0?0:$r*3+$b+4);
($a,$b)=($a<<4,$b<<4) unless $x&1;
($a,$b)=($b,$a) if $zigzag && ($x&1);
$cible[$p] |= $a;
$cible[$p+40] |= $b;
} unless $pset;
for my $y (0..$h-1) {
for my $x (0..$w-1) {
my($d) = $mat[$x%$mat_x][$y%$mat_y];
my(@p) = splice(@px, 0, 3);
$pset->($x,$y, $dR->($p[0],$d),$dG->($p[1],$d),$dB->($p[2],$d));
}
}
# compute delta
for my $i (0..$#cible) {$cible[$i]^=$ecran[$i]}
#sentinel
@cible[(8000,16192)] = (255,255);
# intrelacement
if($interlace==1) {
my $f = sub {
my($y) = @_;
splice(@cible, $y*40, 40, (0)x40);
#splice(@cible, $y*40+8192, 40, (0)x40);
};
for(my $y=$cpt&1; $y<200; $y+=2) {$f->($y);}
}
if($interlace==2) {
my $f = sub {
my($y) = @_;
my($cpt) = 0;
for my $x (0..39) {
my($c) = $cible[$y*40+$x];
++$cpt if $c&0x000f;
++$cpt if $c&0x00f0;
++$cpt if $c&0x0f00;
++$cpt if $c&0xf000;
}
splice(@cible, $y*40, 40, (0)x40) if $cpt<32;
};
for(my $y=$cpt%2; $y<200; $y+=3) {$f->($y);}
}
return 1;
}
sub guess_zoom {
my($IN, $OUT, $w, $h) = @_;
my $BUFFERSIZE = 7*int(($hz+$fps-1)/$fps);
unlink(<tmp/img*.bmp>);
@ecran = ((0)x16384);
local $cpt = 1;
my $size = 0;
while(&next_image($IN,$OUT,$w,$h)) {
for(my $p = 0; $p<16192;) {
my $k = 0;
while($k<255 && !$cible[$p+$k]) {++$k;}
$p+=$k;
$ecran[$p]^=$cible[$p];$ecran[$p+1]^=$cible[$p+1];
$cible[$p]=$cible[$p+1]=0;
++$size if (($size&511)%7)==0; # audio
$size += 3; # video
}
print STDERR sprintf("avg %dx%d frame length = %d bytes (%d%%) @ %ds \r", $w, $h, int($size/($cpt-1)), int(100*$size/(($cpt-1)*$BUFFERSIZE)), $cpt-1);
}
$total_sec = $cpt-1;
unlink(<tmp/img*.bmp>);
print STDERR "\n";
my $len_per_img = $size/($cpt-1);
close($IN);
close($OUT);
my $zoom = $BUFFERSIZE/$len_per_img;
return $zoom>=1?$zoom:sqrt($zoom);
}
sub dither_init {
my($in, $out, $dither, $w, $h) = @_;
my $expected_size = $h*(($w*3 + 3)&~3) + 54;
# param dither
@mat = ([1]);
@mat = ([1,3],
[4,2]) if $dither eq "bayer2";
@mat = ([7,8,2],
[6,9,4],
[3,5,1]) if $dither eq "sam3";
@mat = ([3,7,4],
[6,1,9],
[2,8,5]) if $dither eq "3x3";
@mat = ([6, 9, 7, 12],
[14, 3, 15, 1],
[8, 11, 5, 10],
[16, 2, 13, 4]) if $dither eq "vac4";
@mat = ([1,9,3,11],
[13,5,15,7],
[4,12,2,10],
[16,8,14,6]) if $dither eq "bayer4";
@mat = ([21,2,16,11,6],
[9,23,5,18,14],
[19,12,7,24,1],
[22,3,17,13,8],
[15,10,25,4,20]) if $dither eq "vac5";
@mat = ([35,57,19,55,7,51,4,21],
[29,6,41,27,37,17,59,45],
[61,15,53,12,62,25,33,9],
[23,39,31,49,2,47,13,43],
[3,52,8,22,36,58,20,56],
[38,18,60,46,30,5,42,28],
[63,26,34,11,64,16,54,10],
[14,48,1,44,24,40,32,50]) if $dither eq "vac8";
$mat_x = 1+$#mat;
$mat_y = 1+$#{$mat[0]};
@teo_pal = (0,100,127,142,163,179,191,203,215,223,231,239,243,247,251,255);
@lin_pal = (0, 33, 54, 69, 93,115,133,152,173,188,204,220,229,237,246,255);
my @pc2teo = ();
for my $i (1..$#teo_pal) {
my($a,$b,$c) = ($teo_pal[$i-1],($teo_pal[$i-1]+$teo_pal[$i])>>1,$teo_pal[$i]);
for my $j ($a..$b) {$pc2teo[$j] = $i-1;}
for my $j ($b+1..$c) {$pc2teo[$j] = $i;}
}
my @tabR = (0)x16;
my @tabG = (0)x16;
my @tabB = (0)x16;
my $time = 1;
my $run = 1;
while(1) {
my $name = sprintf($img_pattern, $time);
if($expected_size != -s $name) {
last unless $run;
my $buf;
my $read = read($in,$buf,4096);
if($read) {
syswrite $out, $buf, $read;
} else {
$run = 0;
close($out);
}
} else {
# image complete!
print STDERR $time++,"s\r";
# lecture de l'image
my $img = Image::Magick->new();
my $x = $img->Read($name); die "$x, stopped $!" if $x;
unlink $name;
&tune_image($img);
if(!defined $pal4096) {
my @px;
for my $r (0..15) {
for my $g (0..15) {
for my $b (0..15) {
push(@px, $teo_pal[$r], $teo_pal[$g], $teo_pal[$b]);
}
}
}
$pal4096 = &px2img(256,16, @px);
}
#$img->Remap(image=>$pal4096, dither=>"true", "dither-method"=>"Floyd-Steinberg");
# trammage
my @px = $img->GetPixels(height=>$h, normalize=>"True");
undef $img;
for(my $i=0; $i<$#px; $i+=3) {
++$tabR[$pc2teo[int($px[$i+0]*255)]];
++$tabG[$pc2teo[int($px[$i+1]*255)]];
++$tabB[$pc2teo[int($px[$i+2]*255)]];
}
}
}
close($in);close($out);
unlink(<tmp/img*.bmp>);
my $f = sub {
my($name, @t) = @_;
my($tot, $acc, $max, @r);
my(@tab) = splice(@t,0,16);
for my $t (@tab) {$max = $t if $t>$max; $tot += $t;}
$r[0] = -1; my($z) = $tot;
for my $t (@t) {
for(my $i=$r[$#r]+1; $i<16; ++$i) {
$acc += $tab[$i];
if($acc>=$t*$z) {
$z-=$acc; $acc=0;
push(@r, $i);
last;
}
}
}
shift @r;
if($#r!=$#t) {
my($deb,$fin)=($r[0], $r[$#r]);
$z = $tot;
for my $i (0..$deb) {$z -= $tab[$i];}
for my $i ($fin..15) {$z -= $tab[$i];}
@r = ($deb);
for my $t (@t[1..$#t-1]) {
for(my $i=$r[$#r]+1; $i<16; ++$i) {
$acc += $tab[$i];
if($acc>=$t*$z) {
$z-=$acc; $acc=0;
push(@r, $i);
last;
}
}
}
push(@r, $fin);
@r = sort { $a <=> $b } @r;
}
for my $i (0..$#tab) {print sprintf("%s%2d:%3d%% %s\n", $name, $i, int(100*$tab[$i]/$tot), "X"x(int(50*$tab[$i]/$max)));}
print join(' ', @r),"\n";
return @r;
};
# ($RED0, $RED1, $RED2, $RED3) = $f->("RED", @tabR, 0.02, 0.33, 0.66, 0.95);
# ($GRN0, $GRN1, $GRN2, $GRN3) = $f->("GRN", @tabR, 0.02, 0.33, 0.66, 0.95);
# ($BLU0, $BLU1, $BLU2) = $f->("GRN", @tabR, 0.02, 0.50, 0.95);
($GRN0, $GRN1, $GRN2, $GRN3,$GRN4) = $f->("GRN", @tabG, 0.02, 0.20, 0.20, 0.50, 0.85);
($RED0, $RED1, $RED2, $RED3) = $f->("RED", @tabR, 0.02, 0.20, 0.50, 0.85);
($BLU0, $BLU1, $BLU2) = $f->("BLU", @tabB, 0.02, 0.50, 0.85);
}
sub px2img {
my($width,$height,@px) = @_;
open(PX2IMG,">/tmp/.toto2.pnm");
print PX2IMG "P6\n$width $height\n255\n",pack('C*', @px),"\n";
close(PX2IMG);
my $img2 = Image::Magick->new();
$img2->ReadImage("/tmp/.toto2.pnm");
unlink "/tmp/.toto2.pnm";
return $img2;
}
sub magick_init {
if(!$_magick) {
$_magick = 1;
eval 'use Image::Magick;';
# determination de l'espace RGB lineaire
my $img = Image::Magick->new(size=>"256x1", depth=>16);
$img->Read('gradient:black-white');
$img->Set(colorspace=>'RGB');
#$img->Set(colorspace=>"Gray") unless $coul;
my @px1 = $img->GetPixel(x=>128, y=>0);
$img->Read('gradient:black-white');
$img->Set(colorspace=>'sRGB');
#$img->Set(colorspace=>"Gray") unless $coul;
my @px2 = $img->GetPixel(x=>128, y=>0);
my $d1 = $px1[0]-0.5; $d1=-$d1 if $d1<0;
my $d2 = $px2[0]-0.5; $d2=-$d2 if $d2<0;
$LINEAR_SPACE = $d1>=$d2 ? "RGB" : "sRGB";
#print $px1[0], " ",$px2[0]," $LINEAR_SPACE\n";
}
}
Mais on peut quand même réfléchir: le débit est effectivement meilleur. On passe de 60cycles à 48cycles, mais cela n'est que 20% plus rapide. 20% de débit en plus permet de faire passer 20% de pixel en plus, ce qui revient à avoir une image sqrt(1.2)=1.09 fois plus large qu'avec le player 60µs. 10% en largeur c'est pas bézef Déjà qu'on avait du mal à dépasser un zoom de 50% à 60µs, là ca va passer à un zoom de 55%. On ne verra pas le changement.
Reste que la latence RAMA/RAMB n'est plus que de 3.5ms au lieu de 96. Est-ce que ca va se voir? ... mystère ... Il faut attendre la fin des encodages...
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos