[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

__sam__
Messages : 7966
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ »

BM16: Rêve ou possibilité ?

Code : Tout sélectionner

#/bin/perl
#
# tst_bm16.pl - Conversion video en gif 15 couls 160x200.
#
# par Samuel DEVULDER, Sept 2015.
#

if(0) {
	# pour trouver les niveaux equi-repartis
	&init_magick;

	my(@px);
	for my $c (0, 100, 127, 142, 163, 179, 191, 203, 215, 223, 231, 239, 243, 247, 251, 255) {push(@px,$c,$c,$c);}
	my $img=&px2img(16,1, @px);
	my @p1 = $img->GetPixels(height=>1,normalize=>"True");
	$img->Set(colorspace=>$LINEAR_SPACE);
	my @p2 = $img->GetPixels(height=>1,normalize=>"True");
	for my $i (0..15) {
		print "$i ",$px[$i*3]," ",255*$p1[$i*3]," ",2*$p2[$i*3]," ",255*$p2[$i*3]," ", 2*$p2[$i*3],"\n";
	}
	for my $i (0..15) {
		print "$i ",$px[$i*3]," ",255*$p1[$i*3]," ",3*$p2[$i*3]," ",255*$p2[$i*3]," ", 3*$p2[$i*3],"\n";
	}
	
	# 0 163 215 255
	# 0 191 255

	exit;
}

# parametres
($w, $h) = (160, 100);
$fps     = 10;
$dither  = "bayer4";
$zigzag  = 1;

# fichier entree
$file    = $ARGV[0];
$name    = $file;
$file =~ s/\.[^\.]*$/.gif/;
exit if -e $file;
print "$name\n";

# dossier temporaire
mkdir "tmp";
unlink(<tmp/img*.bmp>);
open(OUT,"| ./ffmpeg -i - -v 0 -r $fps -s ${w}x${h} -an tmp/img%05d.bmp");
open(IN, "<$name");

&init_magick;
$gif = Image::Magick->new(size=>($w*2)."x".($h*2));

@mat = ([1]) if $dither eq "checker";
@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 = ([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]};
for my $a (@mat) {for my $b (@$a) {$b /= $mat_x*$mat_y+1.0;}}

$d4 = sub {
	my($v, $d) = @_;
	return ($v-173)/(255-173)>=$d ? 255 : 215 if $v>=173;
	return ($v-93)/(173-93)>=$d ? 215 : 163 if $v>=93;
	return $v/93>=$d ? 163 : 0;
};
$d3 = sub {
	my($v, $d) = @_;
	return ($v-133)/(255-133)>=$d ? 255 : 191 if $v>=133;
	return $v/133>=$d ? 191 : 0;
};		

$cpt = 1;
binmode(IN);
binmode(OUT);
while(1) {
	$name = sprintf("tmp/img%05d.bmp", $cpt);
	
	$expected_size = $h*(($w*3 + 3)&~3) + 54 if !$expected_size;
	if($expected_size != -s $name) {
		my $buf;
		my $read = read(IN,$buf,4096);
		last unless $read;
		syswrite OUT, $buf, $read;
	} else  {
		# image complete!
		print STDERR int($cpt++/$fps),"s\r";
		sleep(5) if ($cpt%100)==0; # on fait une pause régulière pour ne pas surchauffer le processeur
		
		# lecture de l'image
		my $img = Image::Magick->new();
		$img->Read($name);
		unlink $name;
		$img->Set(colorspace=>$LINEAR_SPACE);
		
		# on force la saturation (140%) pour avoir des couleurs plus franches
		$img->Modulate(saturation=>140);
		$img->Evaluate(operator=>'Multiply', value=>255/245);
		
		# trammage
		my @px = $img->GetPixels(height=>$h, normalize=>"True");
		undef $img;
		for my $c (@px) {$c = int($c*255);}
		
		# dither
		my @im = (0,0,0)x($w*$h*4);
		for my $y (0..$h-1) {
			for my $x (0..$w-1) {
				my(@p) = splice(@px, 0, 3);
				my($d) = $mat[$x%$mat_x][$y%$mat_y];
				my($p) = ($y*$w*2 + $x)*6;
				
				@p = ($d4->($p[0],$d),$d4->($p[1],$d),$d3->($p[2],$d));
				
				if($zigzag & $x) {
					@im[($p,$p+2,$p+3,$p+5)] = @p[(0,2,0,2)];
					$p += $w*6;
					@im[($p+1,$p+4)]   = @p[(1,1)];
				} else {
					@im[($p+1,$p+4)]   = @p[(1,1)];
					$p += $w*6;
					@im[($p,$p+2,$p+3,$p+5)] = @p[(0,2,0,2)];
				}
			}
		}
		$img = &px2img($w*2,$h*2, @im);
		$img->Write("tmp/toto.png");
		
		# ajout de l'image au gif animé
		$img->Set(dispose=>"None");
		$img->Set(delay=>int(100/$fps));
		push(@$gif, $img);
		undef $img;
		
		# pas plus de 3000 imgs (5mins)
		last if $cpt==3000;
	}
}
close(IN);
close(OUT);
unlink(<tmp/img*.bmp>);

# ecriture du fichier gif
$gif->Set(dispose=>"None");
$gif->Set(Layers=>"optimize-trans");
$gif->Set(delay=>int(100/$fps));
$gif->Write($file);

sub init_magick {
	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";
}

sub px2img {
	my($width,$height,@px) = @_;

	open(OUT2,">/tmp/.toto2.pnm");
	print OUT2 "P6\n$width $height\n255\n",pack('C*', @px),"\n";
	close(OUT2);
	my $img2 = Image::Magick->new();
	$img2->ReadImage("/tmp/.toto2.pnm");
	unlink "/tmp/.toto2.pnm";
  
	return $img2;
} 
ImageImageImageImage
:idea: 48 couls visibles sur thomson :shock:
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 : 17412
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel »

Les rêves les plus fous se réalisent parfois... En diminuant un peu le framerate et en optimisant la compression, je le sens bien :wink:
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7966
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ »

C'est en réfléchissant sur la meilleure façon d'afficher un max de couleur sur le BM16, que j'ai trouvé un codage permettant d'avoir 48 couleurs simultanées avec un écran de 160x100. C'est vraiment beaucoup de couleurs et mes premiers essais sur des images statiques sont très proche du photo réalisme, sans avoir besoin d'extraire une palette spécifique pour chaque image.

Par ailleurs, comme la résolution BR (112x76) en 4 couleurs sur Hector donne de bons résultats.. je me suis dit: pourquoi ne pas imaginer de la video 160x100 48couls sur thomson. Les maquettes en GIF sont vraiment très belles, bien meilleure que le mode TO7.

Ce mode soulève cependant des pbs conséquents. Il faut afficher 16ko et plus seulement 8ko. Ca veux dire une bande passance doublée. Si on fait 25imgs/sec, ca nous fait du 400ko/sec nécessaire. on est 4 fois au dessus de ce que le 6809 sait faire.

En toute logique le FPS devrait être réduit. J'ai expérimenté des gifs à 8fps. C'est saccadé. Les GIFs ci-dessus sont à 10fps et c'est très fluide à mes yeux. A cette vitesse, on a plus besoin que de 250ko/sec. C'est +/- la vitesse à base de push/pull de la solution théorique de Foolduplex, mais limite-limite.

Alors bien sur il y a la compression qui réduit la bande passante nécessaire. C'est vrai, mais avec autant de couleurs distinctes, les probabilités de pixels restant inchangés est bien moindre qu'avec le mode TO7. Il n'y aura pas beaucoup à gagner la dessus, sauf en introduisant l'entrelacement (lignes paires puis lignes impaires).

Reste la réduction de la zone affichée. Si on affiche que 70% des pixels, la bande passante est encore divisée par deux, et il ne faut plus que 125ko/sec. Avec la compression on tombe dans les 116ko/sec de la solution Arduino actuelle.

Reste une difficulté: il faut pouvoir taper dans l'écran forme et fond simultanément. En ASM il faut autour de 12 cycles pour commuter les pages efficacement avec un inc/dec si on fait ca à chaque doublet vidéo. On peu s'en sortir en ne travaillant que sur les machines de toute dernière génération: TO8 TO9+ et MO6 en remappant l'écran video en RAM ($A000 pour TO par ex.). A ce moment la RAMB se trouve à 8000 octets de distance par rapport à la RAMA. Au lieu de faire

Code : Tout sélectionner

  STA ,X
  DEC $C3
  STB ,X
  INC $C3
il faut faire

Code : Tout sélectionner

  STA ,X
  STB 8000,X
au lieu de "STD ,X" du mode TO7. On passe alors de 20 cycles à 12 cycles au lieu des 5 cycles du mode TO7 (7 cycles plus long). La routine son jouerait alors à 67 (vs 75 avec la 1ere solution) cycles au lieu de 60. Ca nous fait 14925Hz, ce qui reste encore honorable. Bon par contre ca modifie les timings au niveau de l'arduino, ce qui m'ennuie un peu.

Un "STA ,X; STB ,Y" serait idéal, mais il n'y a pas de ABY.
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 : 7966
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 ammélioré le rendu en ne fixant plus par avance les niveaux RVB, mais en les choisissant à partir des histogrammes:

Code : Tout sélectionner

#/bin/perl
#
# tst_bm16.pl - Conversion video en gif 15 couls 160x200.
#
# par Samuel DEVULDER, Sept 2015.
#

if(0) {
	# pour trouver les niveaux equi-repartis
	&init_magick;

	my(@px);
	for my $c (0, 100, 127, 142, 163, 179, 191, 203, 215, 223, 231, 239, 243, 247, 251, 255) {push(@px,$c,$c,$c);}
	my $img=&px2img(16,1, @px);
	my @p1 = $img->GetPixels(height=>1,normalize=>"True");
	$img->Set(colorspace=>$LINEAR_SPACE);
	my @p2 = $img->GetPixels(height=>1,normalize=>"True");
	for my $i (0..15) {
		print "$i ",$px[$i*3]," ",255*$p1[$i*3]," ",2*$p2[$i*3]," ",255*$p2[$i*3]," ", 2*$p2[$i*3],"\n";
	}
	for my $i (0..15) {
		print "$i ",$px[$i*3]," ",255*$p1[$i*3]," ",3*$p2[$i*3]," ",255*$p2[$i*3]," ", 3*$p2[$i*3],"\n";
	}
	
	# 0 163 215 255
	# 0 191 255

	exit;
}

# parametres
($w, $h) = (160, 100);
$fps     = 10;
$dither  = "vac4"; #"bayer4";
$zigzag  = 1;
$satur   = 140;
$max     = 245;

# fichier entree
$file    = $ARGV[0];
$name    = $file;
$file =~ s/\.[^\.]*$/.gif/;
mkdir "out";
$file =~ s/^.*[\/]/out\//;
exit if -e $file;
print "\n$name\n";

# dossier temporaire
mkdir "tmp";
unlink(<tmp/img*.bmp>);

open(OUT,"| ./ffmpeg -i - -v 0 -r $fps -s ${w}x${h} -an tmp/img%05d.bmp");
open(IN, "<$name");
binmode(OUT);
binmode(IN);

open(OUT2,"| ./ffmpeg -i - -v 0 -r 1 -s ${w}x${h} -an tmp/img%05d.bmp");
open(IN2, "<$name");
binmode(OUT2);
binmode(IN2);

&init_magick;

$gif = Image::Magick->new(size=>($w*2)."x".($h*2));

@mat = ([1]) if $dither eq "checker";
@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]};
for my $a (@mat) {for my $b (@$a) {$b /= $mat_x*$mat_y+1.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);
#($MID31, $MID21, $MID32) = (4,6,8);
#($MID31, $MID21, $MID32) = (2,4,8);
#($MID31, $MID21, $MID32) = (1,3,5);

if(1) {
	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;
	$time = 1;
	my $run = 1;
	while(1) {
		my $name = sprintf("tmp/img%05d.bmp", $time);
		$expected_size = $h*(($w*3 + 3)&~3) + 54 if !$expected_size;
		if($expected_size != -s $name) {
			last unless $run;
			my $buf;
			my $read = read(IN2,$buf,4096);
			if($read) {
				syswrite OUT2, $buf, $read;
			} else {
				$run = 0;
				close(OUT2);
			}
		} 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;

			# on force la saturation (140%) pour avoir des couleurs plus franches
			$img->Modulate(saturation=>$satur);
			$img->Evaluate(operator=>'Multiply', value=>255/$max);

				
			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(IN2);close(OUT2);
	
	
	# for my $i (0..15) {
		# print $tab1[$i],"  ",$tab2[$i],"\n";
	# }
	# 0 a b 15
	# 0  c  15
	my($tot,$acc);
	
	$tot = $acc = 0;
	for my $t (@tabR) {$tot += $t;}
	for(my $i=0; $i<16; ++$i) {
		$acc += $tabR[$i];
		if($acc>=$tot*.02) {
			$RED0 = $i;
			last;
		}
	}
	for(my $i=$RED0+1; $i<16; ++$i) {
		$acc += $tabR[$i];
		if($acc>=$tot*.33) {
			$RED1 = $i;
			last;
		}
	}
	for(my $i=$RED1+1; $i<16; ++$i) {
		$acc += $tabR[$i];
		if($acc>=$tot*.66) {
			$RED2 = $i;
			last;
		}
	}
	for(my $i=$RED2+1; $i<16; ++$i) {
		$acc += $tabR[$i];
		if($acc>=$tot*.98) {
			$RED3 = $i;
			last;
		}
	}

	$tot = $acc = 0;
	for my $t (@tabG) {$tot += $t;}
	for(my $i=0; $i<16; ++$i) {
		$acc += $tabG[$i];
		if($acc>=$tot*.02) {
			$GRN0 = $i;
			last;
		}
	}
	for(my $i=$GRN0+1; $i<16; ++$i) {
		$acc += $tabG[$i];
		if($acc>=$tot*.33) {
			$GRN1 = $i;
			last;
		}
	}
	for(my $i=$GRN1+1; $i<16; ++$i) {
		$acc += $tabG[$i];
		if($acc>=$tot*.66) {
			$GRN2 = $i;
			last;
		}
	}
	for(my $i=$GRN2+1; $i<16; ++$i) {
		$acc += $tabG[$i];
		if($acc>=$tot*.98) {
			$GRN3 = $i;
			last;
		}
	}
	
	$tot = $acc = 0;
	for my $t (@tabB) {$tot += $t;}
	for(my $i=0; $i<16; ++$i) {
		$acc += $tabB[$i];
		if($acc>=$tot*.02) {
			$BLU0 = $i;
			last;
		}
	}
	for(my $i=$BLU0+1; $i<16; ++$i) {
		$acc += $tabB[$i];
		if($acc>=$tot*.5) {
			$BLU1 = $i;
			last;
		}
	}
	for(my $i=$BLU1+1; $i<16; ++$i) {
		$acc += $tabB[$i];
		if($acc>=$tot*.98) {
			$BLU2 = $i;
			last;
		}
	}
	
	my $print = sub {
		my($pfx, @t) = @_;
		my($max, $tot) = (0,0);
		for my $v (@t) {$max = $v if $v>$max;$tot += $v;}
		for my $i (0..$#t) {print sprintf("%s%2d:%3d%% %s\n", $pfx, $i, int(100*$t[$i]/$tot), "X"x(int(50*$t[$i]/$max)));}
	};
	$print->("RED", @tabR);
	print "$RED0 $RED1 $RED2 $RED3\n";	

	$print->("GRN", @tabG);
	print "$GRN0 $GRN1 $GRN2 $GRN3\n";	

	$print->("BLU", @tabB);
	print "$BLU0 $BLU1 $BLU2\n";	

	unlink(<tmp/img*.bmp>);
}


$dR = sub {
	my($v, $d,$a,$b) = @_;
	
	($a,$b) = ($lin_pal[$RED2],$lin_pal[$RED3]);
	return ($v-$a)/($b-$a)>=$d ? $teo_pal[$RED3] : $teo_pal[$RED2] if $v>=$a;
	
	($a,$b) = ($lin_pal[$RED1],$a);
	return ($v-$a)/($b-$a)>=$d ? $teo_pal[$RED2] : $teo_pal[$RED1] if $v>=$a;
	
	($a,$b) = ($lin_pal[$RED0],$a);
	return ($v-$a)/($b-$a)>=$d ? $teo_pal[$RED1] : $teo_pal[$RED0];
};
$dG = sub {
	my($v, $d,$a,$b) = @_;
	
	($a,$b) = ($lin_pal[$GRN2],$lin_pal[$GRN3]);
	return ($v-$a)/($b-$a)>=$d ? $teo_pal[$GRN3] : $teo_pal[$GRN2] if $v>=$a;
	
	($a,$b) = ($lin_pal[$GRN1],$a);
	return ($v-$a)/($b-$a)>=$d ? $teo_pal[$GRN2] : $teo_pal[$GRN1] if $v>=$a;
	
	($a,$b) = ($lin_pal[$GRN0],$a);
	return ($v-$a)/($b-$a)>=$d ? $teo_pal[$GRN1] : $teo_pal[$GRN0];
};
$dB = sub {
	my($v, $d,$a,$b) = @_;
	
	($a,$b) = ($lin_pal[$BLU1],$lin_pal[$BLU2]);
	return ($v-$a)/($b-$a)>=$d ? $teo_pal[$BLU2] : $teo_pal[$BLU1] if $v>=$a;
	
	($a,$b) = ($lin_pal[$BLU0],$a);
	return ($v-$a)/($b-$a)>=$d ? $teo_pal[$BLU1] : $teo_pal[$BLU0];
};

$cpt = 1;$run = 1;
while(1) {
	$name = sprintf("tmp/img%05d.bmp", $cpt);
	
	$expected_size = $h*(($w*3 + 3)&~3) + 54 if !$expected_size;
	if($expected_size != -s $name) {
		last unless $run;
		my $buf;
		my $read = read(IN,$buf,65536);
		if($read) {
			syswrite OUT, $buf, $read;
		} else {
			$run = 0;
			close(OUT);
		}
		
	} else  {
		# image complete!
		print STDERR int($cpt++/$fps),"s (",int(100*($cpt-2)/($fps*$time)),"%)\r";
		sleep(5) if ($cpt%500)==0; # on fait une pause régulière pour ne pas surchauffer le processeur
		
		# lecture de l'image
		my $img = Image::Magick->new();
		$img->Read($name);
		unlink $name;
		$img->Set(colorspace=>$LINEAR_SPACE);
		
		# on force la saturation (140%) pour avoir des couleurs plus franches
		$img->Modulate(saturation=>$satur);
		$img->Evaluate(operator=>'Multiply', value=>255/$max);
		
		# trammage
		my @px = $img->GetPixels(height=>$h, normalize=>"True");
		undef $img;
		for my $c (@px) {$c = int($c*255);}
		
		# dither
		my @im = (0,0,0)x($w*$h*4);
		for my $y (0..$h-1) {
			for my $x (0..$w-1) {
				my(@p) = splice(@px, 0, 3);
				my($d) = $mat[$x%$mat_x][$y%$mat_y];
				my($p) = ($y*$w*2 + $x)*6;
				
				@p = ($dR->($p[0],$d),$dG->($p[1],$d),$dB->($p[2],$d));
				
				if($zigzag & $x) {
					@im[($p,$p+2,$p+3,$p+5)] = @p[(0,2,0,2)];
					$p += $w*6;
					@im[($p+1,$p+4)]   = @p[(1,1)];
				} else {
					@im[($p+1,$p+4)]   = @p[(1,1)];
					$p += $w*6;
					@im[($p,$p+2,$p+3,$p+5)] = @p[(0,2,0,2)];
				}
			}
		}
		$img = &px2img($w*2,$h*2, @im);
		$img->Write("tmp/toto.png");
		
		# ajout de l'image au gif animé
		$img->Set(dispose=>"None");
		$img->Set(delay=>int(100/$fps));
		push(@$gif, $img);
		undef $img;
		
		# pas plus de 3000 imgs (5mins)
		last if $cpt==3000 || !$run;
	}
}
close(IN);
close(OUT);

# ecriture du fichier gif
$gif->Set(dispose=>"None");
$gif->Set(Layers=>"optimize-trans");
$gif->Set(delay=>int(100/$fps));
$gif->Write($file);

sub init_magick {
	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";
}

sub px2img {
	my($width,$height,@px) = @_;

	open(OUT_2,">/tmp/.toto2.pnm");
	print OUT_2 "P6\n$width $height\n255\n",pack('C*', @px),"\n";
	close(OUT_2);
	my $img2 = Image::Magick->new();
	$img2->ReadImage("/tmp/.toto2.pnm");
	unlink "/tmp/.toto2.pnm";
  
	return $img2;
} 
Le résultat est encore meilleur. Le tramage est moins visible et on voit plus de détails (le château dans Touhou, ou la montagne que dessine le gars sur le pare-brise arrière de son SUV):
ImageImage
ImageImage
Allez d'autres encore :)
ImageImage
http://www.cjoint.com/doc/15_09/EIrrNeD ... ffects.gif
http://www.cjoint.com/doc/15_09/EIrrNZh ... 9rique.gif

Mais du coup se pose le problème de fournir au player une palette différente par fichier.

:idea: A ce sujet j'ai eu une idée: Pourquoi n'utiliserait-on pas les 73*6=438 octets videos du 1er bloc pour mettre en mémoire un prog 6809 que le player appelle. Dans ce code on peut par exemple programmer le mode video et la palette. Une fois l'initialisation faite, on retourne au player qui reprend la lecture classique.

Ok, mais la synchro avec l'arduino ne sera plus la même? non: pendant le clargement des 438 octets le player respecte les même timings et recopie la synchro audio (qui ne contient aucune donnée audio) classiquement. Les 60cycles sont respectés.

Oui mais pendant qu'on execute les 438 octets, l'arduino se desynchronise? En fait non: on aura lu un bloc complet, y compris le dernier octet son mais qu'on aura pas transmis. L'arduino sera donc en attente de synchro pendant qu'on execute le code téléchargé. Il suffira juste au player de placer l'octet son en retour du JSR. C'est parfait!

Une petite évolution est même possible pour charger plusieurs blocs de code si nécessaire, mais je pense que dans 438 octets on peut déjà faire pas mal de chose comme forcer le mode couleur ou N&B, changer de palette ou de mode video. Bref la classe! 8)

(j'ai vraiment hâte de tester ca en vrai)
Dernière modification par __sam__ le 17 sept. 2015 22:02, modifié 1 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 : 17412
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel »

Tout ceci semble très prometteur...
Reste à écrire le code Thomson pour jouer la vidéo. A priori rien d'impossible, mais il faut attendre le résultat pour juger.
Daniel
L'obstacle augmente mon ardeur.
Daniel
Messages : 17412
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel »

Nouvelle version du "croquis" Arduino : sdanim7.ino v2015.09.18 à http://dcmoto.free.fr/bricolage/sdanim7/index.html

J'ai découvert que la fonction SD_L2_Dir() ne fonctionne pas avec certaines cartes SD quand les interruptions sont désactivées. La fonction noInterrupts() a donc été déplacée après la lecture du répertoire et avant la lecture du fichier .sd. Après cette modification toutes les cartes testées ont bien fonctionné. Utilisez de préférence la nouvelle version.
Daniel
L'obstacle augmente mon ardeur.
Daniel
Messages : 17412
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel »

:idea: Utilisation du contrôleur CC90-232 pour communiquer avec l'Arduino.

Le contrôleur de communication CC90-232 contient un PIA 6821 vu aux adresses $A7E0/$E7E0 et suivantes.

Il est très facile de l'utiliser comme interface avec l'Arduino pour le streaming. Il a deux avantages :
- Le port manettes est libéré pour la souris, les manettes ou l'interface SDMOTO.
- L'arduino et le module pour carte microSD peuvent être logés dans le boîtier, c'est un périphérique complet tout intégré.

Etes-vous intéressés ?

Image
CC90-232.png
CC90-232.png (123.56 Kio) Consulté 4834 fois
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7966
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ »

Tu veux détourner le boitier série pour la transformer en liaison parallèle avec l'Arduino. Surprenant! Le fait d'avoir tout dans un seul boitier est un grand plus en effet. Par contre les CC90-232 sont rares.
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 : 17412
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel »

Le contrôleur CC90-232 est avant tout un port parallèle pour l'imprimante. La partie "liaison série" est en supplément, mais ce n'est pas une véritable interface RS232 car le protocole est géré par soft.

Dans la pratique, pour transformer le CC90-232 en interface Arduino, il faut enlever le deuxième circuit imprimé et le remplacer par l'Arduino et le module pour carte microSD. Il y a toutefois deux difficultés :

1) Pour les machines n'ayant qu'un connecteur d'extension (MO5, MO6, TO8), le contrôleur CC90-232 occupe ce port et on ne peut pas connecter un deuxième contrôleur. Donc pas le CS91-280, ni le SX90-018 nécessaire au MO5 pour la musique.

2) Le bit de synchronisation (en $A7E0/$E7E0) ne peut pas être écrit en même temps que les 6 bits de l'échantillon son (en $A7CD/$E7CD), ce qui ajoute 4 cycles supplémentaires entre chaque synchronisation.

Il est possible d'améliorer encore le CC90-232 en ajoutant un CNA au port A du 6821, à la place de l'interface série. On remplace ainsi le contrôleur SX90-018 sur MO5 et TO7, et on résout le deuxième problème. Reste l'incompatibilité avec le CS91-280 sur MO5, MO6, TO8.

La rareté des CC90-232 est très relative. Il est vrai que le système CC90 de Préhisto a contribué à la raréfaction. Si je le transforme en interface Arduino, on n'en trouvera plus sur le marché :wink:
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7966
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ »

Pour l'instant je trouve que la meilleur config est ton MO5 avec le CS91-280 en interne et le port musique&jeux externe.

sam (qui encode des concerts à 0.1x => 10-12h par concert.. misère. L'encodeur C serait utile).
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 : 17412
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel »

Oui, le contrôleur CS91-281 intégré au MO5 est une invention très pratique et très utile. Je l'apprécie beaucoup et je pourrais difficilement m'en passer. C'est ce que j'utilise pour tous les chargements de programmes et tous les transferts MO5 <--> PC.

Avec le TO8, j'utilise aussi un contrôleur CS91-280 et une interface SDMOTO pour charger le programme. Ensuite je déconnecte l'interface SDMOTO pour connecter l'Arduino, et enfin je lance le programme. C'est un peu moins pratique, mais ça évite l'utilisation d'une disquette.

L'encodeur en C, nous l'attendons tous avec impatience :wink:
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7966
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ »

Oui je suis sacrément rouillé avec le C. Faire des bibliothèques de support: allocation mémoire, bibliothèque de manip d'image, et assembler le tout avec une bonne gestion d'erreur en un executable natif me semble très.. fastidieux. C'est pas pour rien que je préfère à présent les langages de haut niveau qui ont tout cela intégré et permettent de se concentrer sur l'algorithme à proprement parler au lieu de se disperser dans les trucs administratifs autour.

Concernant l'out-of-memory que fait le script sur les vidéos de plus d'1h, j'ai posté un bug sur la mailing-list perl-magick: http://www.imagemagick.org/discourse-se ... =7&t=28384

J'ai eu une réponse. Il est possible que ce soit ma version d'image-magick qui est trop vieille. C'est pas idiot car sur une autre machine avec un cygwin plus moderne, je n'ai pas observer la fuite mémoire. Bon je ne vais pas stopper l'encryption en cours pour autant: j'en suis à 59mins pour Brassens, et la (re)installation de cygwin est toujours une étape dangereuse où la nouvelle version marche moins bien que celle d'avant.
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
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 »

Daniel ton idée est très intéressantes :)

mais pourquoi ne pas carrément refaire un petit circuit imprimé avec tous ce qu'il faut ?

Ca permettrais à tous le monde de pouvoir le faire (à condition de fabriquer le circuit imprimé) ?
Phil.

www.6502man.com

To bit or not to bit.
1 or 0.
Daniel
Messages : 17412
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel »

J'y ai pensé aussi. Mais l'interface de l'Arduino avec le port manettes peut se construire sur une plaque d'essai, sans aucune soudure, pourtant personne n'a tenté l'expérience. Il est clair que le streaming sur Thomson n'intéresse pas les amateurs d'ordinateurs anciens, ni même les thomsonistes. Alors concevoir un nouveau périphérique et réaliser un circuit imprimé serait à mon avis une perte de temps.

Dans mes réflexions, j'ai aussi envisagé de transformer un contrôleur SX90-018 pour y ajouter l'Arduino et la carte SD. Mais ce contrôleur fonctionne seulement avec les MO5, TO7, TO7/70 et TO9. Pas avec les MO6, TO8, TO8D et TO9+. Ce n'est donc pas une bonne idée.
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7966
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ »

__sam__ a écrit :sam (qui encode des concerts à 0.1x => 10-12h par concert.. misère. L'encodeur C serait utile).
Je préfère de loin faire des trucs rapides. Tiens, entre hier et aujourd'hui sur TO8 j'ai obtenu 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-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
  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 (5+56 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 (45)
  PSHS  X,Y,A     (10)
  PULS  X,Y,A     (10)
  PSHS  X,Y,D     (11)
  PULS  X,Y,D     (11)
  BRN   SORTIE    (3)  

* rebouclage (7 cycles)
  BGE   SORTIE    (3) sortie?

*------------------------------------------------------
* lecture d'un bloc de 7 octets 8+(17+9)*2=60 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 = 9 cycles
PLAYa
  LDB   <$CC      (4) lecture octet video2
  STD   ,X        (5) affiche l'octet image

* 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 = 9 cycles
PLAYb
  LDB   <$CC      (4) lecture octet image
  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
qui donne 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,6A,E6,C0,BD
70 DATA E8,03,26,F9,CE,E7,C3,1F,30,1F
80 DATA 8B,CE,90,AB,8E,0C,60,A6,C8,D4
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 9D,0C,20,3F,96,CC,34,02,CC,90
130 DATA 6F,ED,C4,8E,9D,0E,5F,D7,DB,EC
140 DATA 81,D7,DA,97,DA,8C,9D,2E,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,32,35,32,34
180 DATA 36,35,36,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,ED
210 DATA 84,D6,CC,27,05,3A,96,CC,20,07
220 DATA 8E,A0,00,96,CC,20,00,D6,CC,ED
230 DATA 84
240 DATA 9000,**
Tout ca sa s'utilise avec le script

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.
#
##############################################################################

$file = $ARGV[0];

# params par defaut
mkdir("tmp");
$img_pattern = "tmp/img%05d.bmp";
($w, $h) = (160, 100); # 16:9
$cycles = 60; # cyles cpu par echantillon audio
$hz  = int(2000000/$cycles+.5)/2;
$fps = 10;
$interlace = 1; # 0=off, 1=simple
$audio_dither = 0;
$dither = "vac4";
$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 $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)
$name = $file; $name =~ s/\.[^\.]*$//; $name .= ".sd";
open(OUT, ">$name");
binmode(OUT);

# multiplicateur audio
@audio_cor = (8, 255);

# compteur image
$cpt = 1;

# ecran courant
@ecran = ((0) x 16384);

# position dans ecran
$pos = 16384;

# 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, 2); # depl
	push(@buf, splice(@pal,0,2));
	push(@buf, 2); # depl
	push(@buf, splice(@pal,0,2));
}
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>=16384+$start_pos) {
			++$realimg;
			push(@buf, 0, $ecran[0] ^= $cible[0], $ecran[1] ^= $cible[1]);
			$cible[0] = $cible[1] = 0;
			$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 % 16192);
		while($k<255 && !$cible[$p+$k]) {++$k;}
		$pos+=$k;
		if(($p+=$k)>=16192) {
			$k = $p = 0;
			$pos = int($pos/16192)*16192;
		}
		my($q) = $p+1;
		push(@buf, $k, $ecran[$p] ^= $cible[$p], $ecran[$q] ^= $cible[$q]);
		$cible[$p] = 0 if $p<16192;
		$cible[$q] = 0 if $q<16192;
	}
	return @buf;
}

sub tune_image {
	my($tmp) = @_;
	
	$tmp->Modulate(saturation=>140); # 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)x16192;
	$pset = sub {
		my($x,$y, $r,$g,$b) = @_;
		my($p)=$y*80 + ($x>>2);
		$p += 8192 unless $x & 2;
		my($a,$b) = ($g,$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]}
	@cible[16192..16194] = (255,255,255); # sentinelle
	
	# intrelacement
	if($interlace) {
		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);}
	}	
	
	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 = 1;
			while($k<255 && !$cible[$p+$k]) {++$k;}
			$p+=$k;
			$cible[$p+1]=0; # <== car on marche de 2 en deux
			++$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.98);
	($GRN0, $GRN1, $GRN2, $GRN3) = $f->("GRN", @tabR, 0.02, 0.33, 0.66, 0.98);
	($BLU0, $BLU1, $BLU2)        = $f->("GRN", @tabR, 0.02,    0.50,    0.98);
}

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";
	}
}
Comme on peut le voir on peut faire une version entrelacée ou non. En entrelacé les images sont plus grandes, mais on voit bien l'entrelacement des lignes lors des gros changements. La version non entrelacée ne montre que les bicolonnes paires/impaires.
entrelacé: http://www.cjoint.com/doc/15_09/EIuqMxe ... relace.zip
non-entrelacé: http://www.cjoint.com/doc/15_09/EIuq6on ... y-Head.zip
(Sur emul ca marche bien. Sur vraie machine il faut modifier le player pour qu'il récupère les noms de fichier).

Ah oui j'oubliais: la contrainte est de ne pas changer le sketch arduino.

Si on s'autorise à le changer, on peut éviter l'apparition des bi-colonnes. Mais je pense qu'il faut sans doute trouver un meilleur encodage. L'offset vaut souvent 2. C'est un peu du gaspillage. Si on le rends implicite, on augmente de 50% la bande passante vidéo: 6 données vidéos en 60 cycles, soit un écran complet (16ko) en 164ms, sensiblement 6fps sans compression. Hum.. bof.

[EDIT 21 sept] d'autres vidéos: http://dl.free.fr/fYWTAFT06
[EDIT 23 sept] toutes les vidéos: http://dl.free.fr/fFF1jLA5O
Dernière modification par __sam__ le 23 sept. 2015 08:58, modifié 4 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
Répondre