[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
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 »

Pas mal l'évolution :!:

:( c'est plus joli que sur Hector et ses 4 couleurs.
__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__ »

Mais sur Hector c'est joli aussi :!: Si on veut faire du true-color, on peut essayer le mode avec une couleur par ligne. Sur ton fil je vais voir à essayer un truc un de ces quatres (il y a un début de solution dans un code désactivé par if(0) dans le script :roll:)
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 »

Le code du player est complété par le choix du fichier dans le répertoire de la carte SD, avec la pause permettant de réinitialiser l'Arduino. Pour contrôler j'ai testé avec dcmoto, le code fonctionne bien. Reste à le faire tourner sur TO8 avec l'Arduino, et ce n'est pas le plus simple. Il va falloir, encore une fois, beaucoup de persévérance...

J'ai appelé le programme SDANIM16 en référence au bitmap16 (SDANIM7 étant la version précédente en mode standard Thomson).
Lancement de l'exécutable binaire par

Code : Tout sélectionner

CLEAR,&H8FFF:LOADM"SDANIM16",,R 

Code : Tout sélectionner

/**************************************************\
*                S D A N I M 1 6                   * 
*    (c) 2015 - Samuel Devulder, Daniel Coulom     *
*           http://dcmoto.free.fr/                 *
*           http://forum.system-cfg.com/           *
*--------------------------------------------------*
* Ce code est distribue gratuitement dans l'espoir *
* qu'il sera utile, mais sans aucune  garantie  et *
* sans  engager  la  responsabilité  de  l'auteur. *
* Vous  pouvez  l' utiliser,  le  modifier  et  le *
* diffuser librement, en conservant cette  licence *
* et les références de l'auteur dans   toutes  les *
* copies. L'exploitation commerciale est interdite.*
\**************************************************/

* Ce programme affiche une vidéo avec son à 20833 Hz.
* Les données sont lues sur carte SD par un Arduino
* et envoyées à l'ordinateur Thomson par les ports
* manettes à la vitesse de 145833 octets par seconde.
* Le fichier à jouer est choisi dans le répertoire
* de la carte SD.

/**************************************************\
*                Version 2015.10.10                *
\**************************************************/
* Historique
* 2015.10.10 premiere version issue de sdanim7

*------------------------------------------------------
* DEBUT DU PROGRAMME
*------------------------------------------------------
  ORG   $9000 

*------------------------------------------------------
* INITIALISATIONS EN FONCTION DE L'ORDINATEUR TO ou MO
*------------------------------------------------------
INIT
  PSHS  U,Y,X,DP,B,A,CC  empile les registres
  ORCC  #$50      masque les interruptions
INIT0
  LDX   #$1F40    adresse pour test RAM ou ROM
  LDB   ,X        lecture adresse X
  COM   ,X        tente de modifier adresse X
  CMPB  ,X        test modification adresse X
  BEQ   INIT1     pas de difference -> TO
  COM   ,X        retablissement adresse X
  LDU   #$A7C0    port A du PIA systeme
  LDX   #$0000    adresse dans ecran
  BRA   INIT2     suite des initialisations
INIT1
  LDA   #$E0      bleu/noir TO       
  STA   COUL      remplace bleu/noir MO 
  LDA   #$D0      vert/noir TO      
  STA   COUL+1    remplace vert/noir MO
  LDA   #$C8      rouge/noir TO       
  STA   COUL+2    remplace rouge/noir MO
  LDA   #$02      branchement routine TO
  STA   PUTC+1    remplace branchement routine MO 
  STA   GETC+1    remplace branchement routine MO
* STA   KTST+1    remplace branchement routine MO
  LDU   #$E7C3    port A du PIA systeme
  LDX   #$4000    adresse dans ecran
INIT2
* STX   NEWPICa+1 adresse debut ecran
* STX   NEWPICb+1 adresse debut ecran
* STX   COLOR3+1  adresse debut ecran
  TFR   U,D       extraction base $E7/$A7
  TFR   A,DP      initialisation DP

*------------------------------------------------------
* INITIALISATION 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
  LDB   #$40      clear b0-b5, set b6, clear b7  
  STB   <$CD      set bit synchro

*------------------------------------------------------
* Message accueil
*------------------------------------------------------
DEBUT
  LDX   #MESS1    adresse message a afficher
  BSR   PUTS      affichage du message 
DEBUT1  
  BSR   GETC      lecture du clavier 
  CMPB  #$02      test touche STOP
  LBEQ  SORTIE    sortie du programme
  CMPB  #$20      test touche ESPACE
  BEQ   DIR0      affichage du repertoire
  CMPB  #$0D      test touche ENTREE (dcmoto)
  BEQ   DIR4      utilisation fichier charge
  BRA   DEBUT1    nouvelle saisie de touche

*------------------------------------------------------
* Lecture et affichage de la directory
*------------------------------------------------------
DIR0
  BSR   GETC      lecture du clavier 
  BNE   DIR0      attente buffer vide
DIR1
  LSL   <$CD      clear bit synchro (attente caractere) 
DIR2
  LDB   <$CC      lecture caractere
  BMI   DIR2      attente b7=0 (caractere pret)
  BEQ   INIT0     fin repertoire -> recommencer
  LSR   <$CD      set bit synchro (caractere recu)
  BSR   PUTC      affichage caractere
  CMPB  #$0A      nouvelle ligne
  BNE   DIR1      lecture caractere suivant
DIR3
  BSR   GETC      lecture du clavier
  CMPB  #$02      test touche STOP
  LBEQ  SORTIE    sortie du programme
  CMPB  #$0D      test touche ENTREE
  BEQ   DIR4      traiter le fichier choisi
  CMPB  #$20      test touche ESPACE
  BNE   DIR3      attente touche valide
  LSL   <$CD      clear bit synchro (touche lue) 
  LSR   <$CD      set bit synchro (touche ESPACE)
  BRA   DIR0      lecture fichier suivant
DIR4
  LSL   <$CD      clear bit synchro (touche lue) 
  
*------------------------------------------------------
* INITIALISATION DES COULEURs
*------------------------------------------------------
COLOR 
  LDX   #MESS3    adresse message a afficher
  BSR   PUTS      affichage du message 
  LBRA  COPIE

*------------------------------------------------------
* AFFICHAGE CHAINE POINTEE PAR X
*------------------------------------------------------
PUTS
  PSHS  B         empilage B
PUTS1
  LDB   ,X+       caractere de la chaine
  CMPB  #$04      test fin de chaine
  BEQ   PUTS9     retour
  BSR   PUTC      affichage caractere
  BRA   PUTS1     caractere suivant
PUTS9  
  PULS  B,PC      retour 
  
*------------------------------------------------------
* AFFICHAGE DU CARACTERE B A LA POSITION COURANTE
*------------------------------------------------------
PUTC   BRA   PUTCMO    instruction modifiee pour TO 
PUTCMO FDB   $3F82     affichage caractere MO
PUTCTO JMP   $E803     affichage caractere TO 

*------------------------------------------------------
* LECTURE DU CLAVIER
*------------------------------------------------------
GETC   BRA   GETCMO    instruction modifiee pour TO
GETCMO FDB   $3F8A     lecture clavier MO
GETCTO JMP   $E806     lecture clavier TO 

*------------------------------------------------------
* SCRUTATION RAPIDE DU CLAVIER
*------------------------------------------------------
*KTST   BRA   KTSTMO    instruction modifiee pour TO
*KTSTMO FDB   $3F8C     scrutation rapide clavier MO
*KTSTTO JMP   $E809     scrutation rapide clavier TO 

*------------------------------------------------------
* MESSAGES
*------------------------------------------------------
MESS1
  FDB   $1B40     Ecriture noire 
  FDB   $1B57     Fond blanc
  FDB   $1B67     Cadre blanc
  FCB   $0C       Effacement de la fenetre
  FCC   "==================="
  FDB   $0D0A     CR-LF
  FCC   "SDANIM16 2015.10.10"
  FDB   $0D0A     CR-LF
  FCC   "==================="
  FDB   $0D0A     CR-LF
  FDB   $0D0A     CR-LF
  FCC   "ATTENTION: Dans l'emulateur dcmoto,"
  FDB   $0D0A     CR-LF
  FCC   "tapez uniquement la touche ENTREE."
  FDB   $0D0A     CR-LF
  FCC   "Rechargez le fichier pour le rejouer."
  FDB   $0D0A     CR-LF 
  FDB   $0D0A     CR-LF 
  FCC   "Avec le materiel Thomson, initialisez"
  FDB   $0D0A     CR-LF
  FCC   "l'Arduino, sinon resultat imprevisible."
  FDB   $0D0A     CR-LF
  FCC   "- ESPACE pour lister le repertoire"
  FDB   $0D0A     CR-LF
  FCC   "- ENTREE pour choisir le fichier"
  FDB   $0D0A     CR-LF
  FCC   "- STOP pour sortir du programme."
  FDB   $0D0A     CR-LF
  FDB   $0D0A     CR-LF 
  FCB   $04       Fin de message
MESS3
  FDB   $1B47     Ecriture blanche 
  FDB   $1B5E     Fond couleur 14
  FDB   $1B60     Cadre noir
  FCB   $0C       Effacement de la fenetre
  FCB   $04       Fin de message
COUL  
  FCB   $40       bleu/noir  ($E0 pour TO)    
  FCB   $20       vert/noir  ($D0 pour TO)    
  FCB   $10       rouge/noir ($C8 pour TO)       

*------------------------------------------------------
* Mise en place du block play 73 fois
*------------------------------------------------------
COPIE
  LDU   #FINPLAY7 fin block PLAY 7
  LDX   #72*(FINPLAY7-PLAY7)
COPIE1
  LDA   <(PLAY7-FINPLAY7),U
  STA   ,U+
  LEAX  -1,X
  BNE   COPIE1

*------------------------------------------------------
* mise en place "JMP FINBLOC"
*------------------------------------------------------
  LDA   #$7E      "JMP"
  STA   ,U+
  LDD   #FINBLOC2
  STD   ,U
     
*------------------------------------------------------
* Chargement donnees et execution du player
*------------------------------------------------------
* LDX   #DATA-2
  LDX   #FINPLAY7+72*(FINPLAY7-PLAY7)+1
  BRA   PLAY7
     
*------------------------------------------------------
* Fin de bloc
*------------------------------------------------------
FINBLOC2
  LDA   <$CC      (4) lecture echantillon son avec B6 alternant
  PSHS  A
  LDD   #FINBLOC  fin bloc normal
  STD  ,U
* recup palette
* LDX   #DATA
  LDX   #FINPLAY7+72*(FINPLAY7-PLAY7)+3
  CLRB
  STB   <$DB
PALETTE
  LDD   ,X++
  STB   <$DA
  STA   <$DA
* CMPX   #DATA+16*2
  CMPX  #FINPLAY7+72*(FINPLAY7-PLAY7)+35
  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     
 
*------------------------------------------------------
* RETOUR AU BASIC
*------------------------------------------------------
SORTIE 
  PULS  CC,A,B,DP,X,Y,U,PC   

*------------------------------------------------------
* 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   

* modulo du pointeur 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?

*------------------------------------------------------
* JOUE LA VIDEO ET LA MUSIQUE
* Boucle de 48 cycles = 20833 Hz
* 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

  END
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__ »

De mon coté j'ai réglé le problème du déphasage des flux avec l'encodeur suivant qui est conçu pour garder les flux RAMA et RAMB complètement synchrones. Effet de bord sympathique de ce nouvel encodeur: il est plus rapide qu'avant! :D

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.
#
# 10/10/2015 - Modifications pour garder les flux RAMA/RAMB synchrones.
#
# 11/10/2015 - Bugfix les 48µs de l'octet de fin de bloc était oublié entrainant une
#                      desynchro image/son d'environ 1sec au bout de 4 mins (et 1/10sec 
#                      suffit à voir que quelque chose cloche entre le son et l'image).
#
##############################################################################

$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 8002);
@buf = (0)x1024;
$pos = $num_oct = 0;
for($running = 0x80; $running;) {
   &video();
   &audio();
   # write
   print OUT pack('C*', @buf);
}

# nettoyage et fermetue flux
print STDERR "\n";
unlink(<tmp/img*.bmp>);
close(OUT);
close(IN);
close(FFMPEG);
close(AUDIO);

# compresse un buffer de 2x512 octets avec les infos video
sub video {
	# nouvelle image?
	if($current_cycle>=$cycles_per_img) {
		$current_cycle-=$cycles_per_img;
		#print "pos1=$pos1 pos2=$pos2 num=$num_oct\n";
		++$realimg if $num_oct>=8000; $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);}
		}
	}
	my($k,$a,$b,$p,$qos);
	for(my $i=1; $i<512; $i+=7) {
		$current_cycle += 2*$cycles;

		for my $j ($i, $i+3) {
			# recherche du nombre d'octets inchangés
			$k=255;
			for $p ($pos..$pos+255) { if($cible[$p]) {$k=$p-$pos;last;}}
			$pos+=$k; $num_oct+=$k;

			# mise a jour ecran
			$qos = $pos+1;
			$a = ($ecran[$pos] ^= $cible[$pos]);   
			$b = ($ecran[$qos] ^= $cible[$qos]);
			$cible[$pos] = 0 if $pos<8000;
			$cible[$qos] = 0 if $qos<8000;

			# encodage buffer des flux RAMA et RAMB en même temps
			splice(@buf, $j,     3, $k, $a>>8,  $b>>8);
			splice(@buf, $j+512, 3, $k, $a&255, $b&255);   
		}
	}
   
   # en fin de bloc: retour ecran si besoin
   $pos = 0 if $pos>=8000;

   # dernier octet de bloc
   $current_cycle += 2*$cycles;
}

# compresse un buffer de 2x512 octets avec les infos video
sub audio {
   for(my $i=0; $i<1024; $i+=$i==511?1:7) {
      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
      $buf[$i] = ($v>>2) | $running | ($clk ^= 0x40);
   }
}

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;

   $tmp->Set(depth=>16);
   $tmp->Set(colorspace=>$LINEAR_SPACE);
   &tune_image($tmp);
   
   my $t = Image::Magick->new(size=>"160x100");
   $t->Read("xc:black");
   $t->Composite(image=>$tmp, compose=>"Over", x=>(160-$w)>>1, y=>(100-$h)>>1);
   undef $tmp; $tmp = $t;
 
   # dither
   my @px = $tmp->GetPixels(height=>100, 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)x8002;
   $pset = sub {
      my($x,$y, $r,$g,$b) = @_;
      my($p)=$y*80 + ($x>>2);
      my($a,$b) = ($g,$r+$b==0?0:$r*3+$b+4);
      ($a,$b)=($a<<4,$b<<4) unless $x&1;
      ($a,$b)=($a<<8,$b<<8) unless $x&2;
      ($a,$b)=($b,$a) if $zigzag && ($x&1);
      $cible[$p]    |= $a;
      $cible[$p+40] |= $b;
   } unless $pset;
   
   for my $y (0..99) {
      for my $x (0..159) {
         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,8001)] = (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)x8002);
   local $cpt = 1;
   my $size = 0;
   while(&next_image($IN,$OUT,$w,$h)) {
      for(my $p = 0; $p<8000;) {
         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(2*$size/($cpt-1)), int(200*$size/(($cpt-1)*$BUFFERSIZE)), $cpt-1);
   }
   $size *= 2; # RAMA + RAMB
   
   $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);
   ($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";
   }
}
Alors en théorie les effets peignes liés au déphasage (c.a.d avec des dents en haut et en bas d'une zone) sont éliminés, et c'est ce que j'observe sur ce fichier:

Mais (oui le fameux "mais"), sur émulateur je vois quand même des dents, mais pas plus d'une par image (appelons ca un "mono peigne"). Dans la théorie c'est possible en effet: le bloc RAMA est séparé du bloc RAMB par 3.5ms (le temps de décoder le bloc RAMA). Si pendant cette faible durée, le rayon cathodique est au niveau de la zone écran qui est décodée on voit apparaitre les colonnes de la RAMA alors que la RAMB de la même zone n'est pas encore là. Ca fait un peigne en effet. Mais d'une part il est rare (les 3ms de décoage d'un bloc représentent 1/7 d'un rafraichissement écran), et au pire il ne dure que le temps d'une VBL soit 20ms qui est bien plus courte que la rémanence écran, si bien qu'on ne devrait pas le percevoir. Or je trouve qu'on le voit souvent lors des changements à gros contrastes.

Daniel: quelle est la fréquence de rafraichissement de l'écran dans l'émulateur ? Je soupçonne ce coup ci un artefact lié à l'émulation. En effet si tu mets un point d'arrêt en $9070 qui est le début du décodage d'un bloc, je constate qu'à l'écran il y a systématiquement un peigne. Pire: on peut voir le peigne apparaitre en haut de l'écran, alors que l'indication "ligne" du débuggeur indique une ligne en bas de l'écran. On dirait vraiment que l'émulateur rafraichit immédiatement une zone écran modifiée sans tenir compte du numéro de ligne du gate-array d'affichage.
mono-peigne.gif
mono-peigne.gif (62.15 Kio) Consulté 5939 fois
Est-ce que c'est possible? Ca montrerait une limite de l'émulation. Vivement qu'on puisse tester sur machine réel pour voir si ce "mono peigne" est aussi perceptible que sur émulateur.

S'il l'est, je pense qu'il faudra augmenter la fréquence d'échange RAMA/RAMB. Comme on ne peut pas aller plus vite pour décoder un bloc, il faudra le fractionner en sous-morceaux. La formule 512=(9*7+1)*8 s'avèrera utile à ce moment là, mais j'aimerais bien qu'il ne faille pas avoir recours à elle car le code arduino sera plus compliqué (tous les 9 blocs de 7 octets il ne faut lire que l'octet son et ne pas envoyer les 6 octets vidéos, et lors du dernier octet son du bloc il faut lire les crc et attendre le nouveau bloc). Cela dit, avec cette formule, la hauteur des dents du peigne seront 8 fois moins haute, donc 8 fois moins visibles.

Par ailleurs les 20khz audio sont vraiment du gaspillage. J'aimerais bien troquer 5khz audio pour 5khz vidéo. La formule qui permet cela revient à utiliser des blocs de 10 octets: 1 son et 3*3 octets vidéos. Un bloc se décomposera alors en 51 fois 10 octets, et deux octets terminaux. Si on emploie ces deux octets terminaux en octet son, cela revient à attendre l'équalent de deux trammes de 10 octets. C'est long! Je pense que la bonne stratégie est de mettre le son que su un seul des deux octets et garder l'autre pour plus tard (je pense par exemple pour indiquer si le prochain bloc est RAMA ou RAMB).

[EDIT]Arghh.. ya un bug dans l'encodeur: au bout de 5 mins le son et l'image ne collent plus! Zut, je ne vois pas d'où ca sort :evil:
[EDIT] TROUVE! (et corrigé). J'avais oublié de compter les 48µs de l'octet de fin de bloc. Au bout de 2000 images (4 mins), on a accumulé 1 sec de décalage!
Dernière modification par __sam__ le 11 oct. 2015 12:38, 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 : 17410
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel »

Dans dcmoto, l'image de l'écran en mémoire est mise à jour après chaque instruction. Le nombre de pixels modifiés est fonction du nombre de cycles de l'instruction. Un segment horizontal est dessiné en mémoire (mais pas à l'écran).

En fonctionnement normal, l'affichage se fait en une seule fois pendant la VBL, plus précisément après la 312e ligne de balayage du moniteur, au moment où le faisceau remonte à la première ligne. C'est pour des raisons de performances, car afficher les pixels un par un en suivant le spot prendrait trop de temps. Il y a 50 affichages par seconde si la vitesse du processeur est réglée à 1MHz.

En mode mise au point le principe est différent, pour permettre de visualiser précisément la mémoire vidéo Thomson. A chaque pause (en mode pas à pas, saut de subroutine ou arrêt sur breakpoint), l'écran en mémoire est affiché. En mode pas à pas on le voit donc se construire segment par segment, en fonction de la position du spot. L'image précédente n'étant pas effacée, il peut y avoir des choses bizarres à l'interface entre la nouvelle image et l'ancienne.

[Edit]
Rectification : contrairement à ce que j'ai écrit ci-dessus, en mode debug tout l'écran est recalculé à chaque affichage, indépendamment de la position du spot. J'ai fait la confusion car dans les premières versions de dcmoto ce n'était pas le cas. La modification a été faite en version 2010.10.22.
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__ »

Bon du coup on devrait voir les peignes sur machine réelle. On verra à l'usage si c'est gênant. En effet le découpage actuel des blocs me plait bien. Je vais voir à modifier le player pour le permettre de marcher sur MO6 et TO9 (et permettre des trucs sympa sur TO7 et MO5
Image
ou encore avec un autre motif de tramage:
Image
Comme quoi le mode TO7 peut aussi être très coloré).
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
Fool-DupleX
Messages : 2338
Inscription : 06 avr. 2009 12:07

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

Message par Fool-DupleX »

__sam__ a écrit :PS: "Call on me" me fascine depuis que je l'ai vu tourner sur Amstrad avec un OS multitache:
Sauf qu'elle ne tourne pas ici sur un Amstrad CPC mais sur un T-Rex (Machine 100% compatible Amstrad CPC à base de FPGA, avec un CPU Z80 qui tourne à 24 MHz ... entre autres améliorations ...)

Ca tourne aussi sur un vrai amstrad, à condition de rajouter une extension maison pour un disque dur, mais à peu près 6 fois plus lentement.
__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__ »

Petit "up" sur ce sujet car je viens de faire une bonne ammélioration de l'algo de colorisation pour TO7.

Pour rappel, l'algo TO que j'ai utilisé ci-dessus effectue un dithering classique en 8 couleurs sans contraintes, puis par groupe de 8 pixels hoizontaux il ne conserve que les deux seules couleurs les plus fréquentes. Ca marche pas mal, mais on voit apparaitre des artefacts quand un mélange de plus de deux couleurs sont nécéssaires pour obtenir la teinte d'une zone.

Par exemple sur cette image
Peter_Gab_eye2.png
Peter_Gab_eye2.png (21 Kio) Consulté 5659 fois
on voit des différences ente l'oeil du haut (avec contraintes TO7) et celui du bas (sans contraintes):
  • globalement l'image est plus jaune, pas assez rouge
  • La partie gauche de la paupière tend vers un verdatre jaune
  • on voit un motif hyper découpé sur les frontières d'octets dans la partie droite. C'est super moche!
Peter_Gab_eye3.png
Peter_Gab_eye3.png (22.38 Kio) Consulté 5659 fois
Ces defauts sont liés à l'aspect statique du motif de tramage: la couleur jaune/verte est lié au fait qu'il faudrait plus de pixels rouges allumés à ces endroits. Hélas il n'y a que 2 couleurs par groupe de 8 pixels. Donc l'algo fait l'impasse sur ces pixels rouges minoritaires. Idem la ligne en dessous: le tramage sans contrainte contient des pixels rouges, mais ils ne sont pas assez nombreux pour être selectionnés. Résultat: on a des à-plats étendus avec un manque de rouge. C'est subtil, mais c'est suffisant pour que notre vue détecte un truc anormal dans l'image qui est franchement moche du coup.

On a pas ce problème dans mes algo de diffusion d'erreur car cette absence de rouge est reporté sur les pixels voisins de façon cumulative. Auto-magiquement ces algos corrigent l'absence d'une couleur par un reforcement dans la ligne en dessous. C'est cela la grande force de ces algorithmes. Pardonnent assez bien et corrigent les erreurs. Hélas la diffusion d'erreur n'est pas bonne quand on veut faire des images animées car pour otpimiser la bande passante il faut changer le minimum de chose entre deux images.

Cepenant j'ai eu une intuition pour mélanger le dithering positionnel (statique) et la propagation d'erreur. Et là le résultat est nettement meilleur: un manque de rouge sur un pixel trammé est reporté à 80% sur l'image source, mais sur le pixel immediatement en dessous. Donc quand l'algo passe à la ligne suivante, le rouge est un peu plus prononcé sur ce pixel modifié. Résultat: statistiquement la proba d'avoir un rouge dans cette zone augmente, et ainsi l'algo aura tendence à corriger le manque du pixel au dessus.

Ca marche bien. Voyez l'image en haut à droite ci-dessous. Elle est très très similaire à celle en bas à gauche qui ne contient aucune contrainte et a jusqu'à 5 ou 7 couleurs différentes par groupe de 8 pixels.
Peter_Gab_eye.png
Peter_Gab_eye.png (64.58 Kio) Consulté 5659 fois
En pratique on peut répartir cette absence de rouge non pas seulement sur le pixel en dessous, mais aussi sur se deux voisins de la même ligne, mais ce n'est pas obligatoire. Les résultats sont très bons sans cela.

Pour ceux que ca interresse, mon code expérimental est ici: http://pastebin.com/5HdarJQy

Avec la matrice de dither que j'ai trouvée (mix bayer et clustered), on a des résultats visuels sensiblement de même qualité que ce que le TO8 peut faire en 160x200. Ca donne vraiment envie de faire un player permettant de changer les plans mémoire forme et couleurs de l'image :D

(images animées à venir...)
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__ »

En haut à gauche: la version TO7 avec l'algo d'origine (on voit quelques défauts de couleurs), et à droite avec l'algo modifié (les défauts sont beaucoup moindre). Pour la comparaison on trouve en 2ème ligne: à gauche un dithering 8 couleurs sans contraintes et à droite la vidéo d'origine.
Image
Image
(image accessibles pendant 1 semaine)
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 »

Comme quoi à force de persévérance on arrive toujours à améliorer les algorithmes. Le progrès est visible quand on compare le nouveau à l'ancien, mais il faut quand même être spécialiste pour les discerner sans voir les deux simultanément.

Il faudra refaire une démo avec une vidéo bien choisie (et pas trop grosse), pour que je puisse l'ajouter au site dcmoto.
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__ »

La qualité de ce mode là est carrément au dessus de ce qu'on obtient avec les lignes séparées R/V/B.

Il faudra retravailler le player pour l'adapter au TO7 avec commutation forme/couleur. Il y a aussi l'effet "peigne" qui est génant, mais je pense qu'en découpant un secteur FD ainsi 512 = 17*(3*10) + 2, c'est à dire 17 fois 3 séquences de 10 octets (1 son + 9 image), on doit obtenir un truc très souple et rapide. Il y a 7*3+1 bits dispo régulièrements répartis dans le format. De quoi changer forme/fond régulièrement (Le dernier bit du dernier octet sert lui à indiquer si c'est le dernier bloc du fichier). Bon je n'explique pas forcément très bien, mais je suis confiant: il y aura moyen d'avoir un player qui utilise toute la bande passante au mieux. 8)
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
Fool-DupleX
Messages : 2338
Inscription : 06 avr. 2009 12:07

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

Message par Fool-DupleX »

Une fois de plus, tu m'as bluffé. Le gain est incroyable en terme de fidelité couleur. J'ai dû regarder la vidéo image par image pour me convaincre qu'il y a effectivement des contraintes dans la version TO7 ! :o

Je pense que la méthode est au point. Reste à coder ça côté Thomson ... ca va pas être de la tarte.
__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__ »

Fool-DupleX a écrit :Reste à coder ça côté Thomson ... ca va pas être de la tarte.
Oui d'autant plus que la formule 512 = 51*10 + 2 n'est pas idéale car à chaque lecture de 10 octets (1son + 9video) il faut commuter forme/fond ce qui prend un certain temps. Creusons cela.

La lecture des 10 octets d'une trame se fait comme suit:

Code : Tout sélectionner

* 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

* lecture octets dep3 + video5 + video6 = 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
soit 68 cycles auquels il faut ajouter la commutation forme/fond et l'échange de X avec Y (pointeurs respectifs dans RAMA/RAMB)

Code : Tout sélectionner

  LDA   #1        (2)
  EORA <$C3       (4)
  STA  <$C3       (4) Commut forme/fond
  EXG  X,Y        (8) Echange pointeurs videos
on est donc à 68+18=86 cycles par octet son, soit 11.6khz au niveau audio. C'est acceptable car de l'autre coté on met à jour 6 octets videos dans le même temps.

La lecture d'un bloc de 512 coute 51*86 + 2*86 = 4.6ms et fournit 51*6=308 octets videos. Donc pour raffraichir chacun des 16000 octets de l'écran il faut 52 blocs, soit 239ms, 4 fps :( C'est pas glorieux. En pratique il faut espérer que l'on ne rafraichit pas l'ensemble de l'écran à chaque image mais disons un peu plus de la moitié ce qui pemettrait d'avoisiner les 10 fps video avec un son à 11khz. Passable.

Bon, il y aura sans doute quelque chose de sympa a exploiter quand même car si on travaille sur un écran 70% plus petit (224 x 140 au lieu de 320x200) on a quand même une "grosse" image, mais naturellement il n'y a plus que 7840 octets à mettre à jour dans le pire des cas (au lieu de 16000), soit 26 blocs = 118ms = 8.5fps garantis.
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
Fool-DupleX
Messages : 2338
Inscription : 06 avr. 2009 12:07

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

Message par Fool-DupleX »

N'y aurait-il pas moyen d'entrelacer les données d'une autre manière, de sorte que toutes les données de formes soient mise à jour pour une image, puis toutes les données de fond et ainsi économiser les commutations incessantes ? Certes, si le raffraichissement prend plus d'une frame (donc plus lent que 25 images/s, et ca le sera), on risque d'avoir des effets psychédéliques à l'écran (couleur de l'image precedente sur la forme de l'image suivante).
__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__ »

Précédemment je faisais une commutation forme/fond à chaque fin de bloc. On traitait donc 73*4=292 octets de l'écran video avant de faire une commutation. Cela prend 48*73=3.5ms entre 2 commutations. C'était largement moins qu'une frame. Cependant il apparait un motif en forme de peigne à l'écran dans la zone où l'on est en train d'écrire.
Image

Pourquoi ce peigne ? Imaginons que l'on vienne juste d'écrire les 292 octets en RAMA, cela fait 7 lignes où une colonne sur deux n'est pas en accord avec l'autre. Vient alors l'écriture des 292 octets aux mêmes addresse en RAMB. Cela prend 3.5ms, or le balayage d'une ligne écran prennant 64µs, on touve que pendant ces 3.5ms, l'écran est descendu de 3.5ms/64µs = 55 lignes! (un peu plus d'1/4 écran), et on a donc perdu la vitesse par rapport au rayon et on verra les dents du peigne pendant tout le reste de la trame vidéo (20ms). Mais ca veut dire qu'à l'image suivante on aura plus le peigne? A cet endroit là: oui, mais globalement non car dans les 20ms pour faire un cycle on aura encore avancé en RAMA et on aura à nouveau un peigne un peu plus bas. Ca n'en fini pas. On observe facilement 2 peignes dans deux endroits séparés de 50 lignes et quelques.

Seule solution que j'imagine: augmenter la fréquence de commutation RAMA/RAMB. Avec 86µs, on est un peu plus long qu'un balayage de ligne, mais pas trop loin quand même, ce qui fait que les dents du peigne ne feront pas plus qu'une ligne. Presque imperceptible j'espère.
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