[Thomson] Vidéo avec son en streaming
Modérateurs : Papy.G, fneck, Carl
Re: [Thomson] Vidéo avec son en streaming
Pas mal l'évolution
c'est plus joli que sur Hector et ses 4 couleurs.
c'est plus joli que sur Hector et ses 4 couleurs.
-
- Messages : 7967
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
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 )
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Re: [Thomson] Vidéo avec son en streaming
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
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.
L'obstacle augmente mon ardeur.
-
- Messages : 7967
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
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!
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. 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
[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!
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";
}
}
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. 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
[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
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Re: [Thomson] Vidéo avec son en streaming
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.
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.
L'obstacle augmente mon ardeur.
-
- Messages : 7967
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
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
ou encore avec un autre motif de tramage:
Comme quoi le mode TO7 peut aussi être très coloré).
ou encore avec un autre motif de tramage:
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
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
-
- Messages : 2341
- Inscription : 06 avr. 2009 12:07
Re: [Thomson] Vidéo avec son en streaming
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 ...)__sam__ a écrit :PS: "Call on me" me fascine depuis que je l'ai vu tourner sur Amstrad avec un OS multitache:
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.
-
- Messages : 7967
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
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 on voit des différences ente l'oeil du haut (avec contraintes TO7) et celui du bas (sans contraintes):
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. 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
(images animées à venir...)
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 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!
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. 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
(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
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
-
- Messages : 7967
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
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 accessibles pendant 1 semaine)
(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
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Re: [Thomson] Vidéo avec son en streaming
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.
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.
L'obstacle augmente mon ardeur.
-
- Messages : 7967
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
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.
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.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
-
- Messages : 2341
- Inscription : 06 avr. 2009 12:07
Re: [Thomson] Vidéo avec son en streaming
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 !
Je pense que la méthode est au point. Reste à coder ça côté Thomson ... ca va pas être de la tarte.
Je pense que la méthode est au point. Reste à coder ça côté Thomson ... ca va pas être de la tarte.
-
- Messages : 7967
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
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.Fool-DupleX a écrit :Reste à coder ça côté Thomson ... ca va pas être de la tarte.
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
Code : Tout sélectionner
LDA #1 (2)
EORA <$C3 (4)
STA <$C3 (4) Commut forme/fond
EXG X,Y (8) Echange pointeurs videos
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
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
-
- Messages : 2341
- Inscription : 06 avr. 2009 12:07
Re: [Thomson] Vidéo avec son en streaming
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).
-
- Messages : 7967
- Inscription : 18 sept. 2010 12:08
- Localisation : Brest et parfois les Flandres
Re: [Thomson] Vidéo avec son en streaming
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.
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.
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
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos