[Thomson] Vidéo avec son en streaming

Cette catégorie traite de développements récents destinés à nos vieilles machines, applications, jeux ou démos... Amis programmeurs, c'est ici que vous pourrez enfin devenir célèbres!

Modérateurs : Papy.G, fneck, Carl

Avatar de l’utilisateur
jice
Messages : 214
Inscription : 21 avr. 2014 15:08
Localisation : Madrid

Re: [Thomson] Vidéo avec son en streaming

Message par jice »

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. :D
MO5 - MO5 Platini - TO7 - TO7/70 - TO8 - TO9+
Daniel
Messages : 17410
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] Vidéo avec son en streaming

Message par Daniel »

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.

ImageImage
Daniel
L'obstacle augmente mon ardeur.
Avatar de l’utilisateur
6502man
Messages : 12312
Inscription : 12 avr. 2007 22:46
Localisation : VAR
Contact :

Re: [Thomson] Vidéo avec son en streaming

Message par 6502man »

très bonne idée le support zif :D :wink:
Phil.

www.6502man.com

To bit or not to bit.
1 or 0.
__sam__
Messages : 7964
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] Vidéo avec son en streaming

Message par __sam__ »

J'ai essayé d'écrire la RAMA et la RAMB de façon contiguë avec le code ASM suivant:

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,**
combiné à l'encodeur

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";
	}
}
Le 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:
  • 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 de

    Code : 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)
    soit 4+4+4+3+4+4+5+4+4+9=45 cycles au lieu de 60 8)
    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).
[EDIT] lien sur les vidéos ajouté.
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
Daniel
Messages : 17410
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] Vidéo avec son en streaming

Message par Daniel »

__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 ?
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.

:idea: 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.
Avatar de l’utilisateur
yo_fr
Messages : 1337
Inscription : 13 août 2009 18:24
Localisation : 78...
Contact :

Re: [Thomson] Vidéo avec son en streaming

Message par yo_fr »

Et l'idée de passer en 16 bits ? tu n'as pas assez de port sur MO pour ça ?
__sam__
Messages : 7964
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] Vidéo avec son en streaming

Message par __sam__ »

Daniel a écrit : :idea: 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é ?
Oui, c'est ce qui est fait ici avc un groupe de n=2. Un groupe de 3 est possible

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
mais
  • 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.
Alors on peut se dire qu'il n'y a qu'à prendre 4 octets fond/forme: (<son> <depl> <forme1> <forme2> <forme3> <forme4> <fond1> <fond2> <fond3> <fond4>), ce qui laisse 2 octets en fin de bloc c'est vrai, mais
  • 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.
Le truc qui aiderait serait l'équivalent d'une addition non signée sur Y et d'avoir Y qui pointe sur la partie haute de la RAM. On peut faire "CLRA; LEAY D,Y", mais rien qu'à lui seul le LEA prend 8 cycles, ce qui n'est pas mieux que "STD 8192,X".

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
__sam__
Messages : 7964
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] Vidéo avec son en streaming

Message par __sam__ »

yo_fr a écrit :Et l'idée de passer en 16 bits ? tu n'as pas assez de port sur MO pour ça ?
Oui les PIA sont des interfaces 8 bits, et les directions des ports joystick arrivent toutes sur le même PIA.
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
Daniel
Messages : 17410
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] Vidéo avec son en streaming

Message par Daniel »

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.
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7964
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] Vidéo avec son en streaming

Message par __sam__ »

Note:
  • 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.
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/oYqHEbBaG

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
__sam__
Messages : 7964
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] Vidéo avec son en streaming

Message par __sam__ »

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

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
__sam__
Messages : 7964
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] Vidéo avec son en streaming

Message par __sam__ »

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.
:idea: Il y a une idée dans ce que tu suggères.

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)
Cel a fait 4+4+(4+3+4+4+5)*2=48 cycles pour 7 octets au lieu de 60. Le son dépasse 20khz! En fin de bloc on compare X avec $E000 si ca dépasse on le reset à $C000 (RAM haute), sinon s'il dépasse $C000 on reset à$A000 (RAM basse). Enfin on échange X et Y. Ouais, c'est pas mal.

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
__sam__
Messages : 7964
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] Vidéo avec son en streaming

Message par __sam__ »

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 :D
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
Daniel
Messages : 17410
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] Vidéo avec son en streaming

Message par Daniel »

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.
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7964
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] Vidéo avec son en streaming

Message par __sam__ »

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é 8)

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
en basic:

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,**
Il y a deux idées importantes dans ce nouveau player:
  • 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.
Avec cette stratégie on traite 1 octet son et 6 octets vidéos en 48µs, soit 20.83khz audio, et un streaming à 146ko/sec. C'est vraiment bien plus qu'auparavant (16khz, et 116ko/sec), et c'est ca qui m'a posé soucis car le code du retour au début d'écran en fin de bloc dépasse très facilement les 48µs ou tombe à 47µs sans possibilité de pouvoir ajouter seulement +1 cycle sans revoir l'ensemble de l'algo. Mais bon, j'ai fini par trouver une façon de calculer qui fasse exactement 48cycles, et ca c'est cool. 8)

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";
	}
}
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. :cry:

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
Répondre