[Thomson] Vidéo avec son en streaming

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

Modérateurs : Carl, Papy.G, fneck

Répondre
__sam__
Messages : 4455
Enregistré le : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ » 13 juil. 2015 01:35

Daniel a écrit :Exceptionnel, prodigieux, incroyable, sensationnel (...)
De rien, n'en jetez plus :lol: Mais bon tu es le seul à avoir commenté. Tu crois qu'on a largué tout le monde avec nos expérimentations Thomson? Le fil est peut-être devenu trop long pour ceux qui voudraient le suivre depuis le départ.

:idea: Pour ceux qui auraient lu jusqu'ici: manifestez vous! N'hésitez pas à commenter ou poser des questions même si ca vous parait idiot. Rien qu'indiquer une vidéo qu'il vous plairait de voir convertie serait utile, parce que question vidéos libres de droits je ne trouve pas grand chose de bien.
Je vous conseille quand même de la visualiser avec dcmoto, c'est mieux que la vidéo Youtube :wink:
Absolument. Youtube c'est pour voir à quel point il est simple d'utiliser DCMOTO avec l'émulation de l'interface "arduino".
Peut-on avoir le fichier .sd de la navette spatiale ?
Oui il est ici: http://www.cjoint.com/doc/15_07/EGmxFxU ... D-1080p.7z
(demain je posterais probablement le code du script de conversion.)
Reste la dernière étape, passer de l'émulation à la vraie machine, ce sera la cerise sur le gâteau ! (...) il devrait être assez facile de respecter la cadence. Au moins en théorie, la pratique réservera peut-être des surprises, mais j'ai fait mienne la devise des Ducs de Gadagne : "L'obstacle augmente mon ardeur".
J'ai hâte de savoir si les fichiers SD que je crée sont ok avec la vraie interface arduino.

Quelques remarques par rapport au débit atteint:

1) comme on lit 7 octets en 60cycles, ca nous fait un débit de lecture de 116.67ko/sec (environ 400mo/heure) ce qui est vraiment proche des 120ko/sec max avec l'audio seul. Avec un débit pareil, si on devait utiliser la ram d'un TO8 gonflé on aurait à peine 4-5secs de vidéo. La carte SD nous apporte une grande quantité de données à faible cout. Une carte SD de 16Go nous ferait de l'ordre de 40h d'audio/vidéo. Largement de quoi passer le pont pluvieux du 14juillet devant un Thomson. :roll:

2) On récupère 4 octets audio tous les 60 cycles cpu. Ce qui veut dire que pour remplir un écran 16:9 de 320x180=7200 octets, il nous faut exactement 108 000 cycles cpu (108ms). Ca signifie que si tout l'écran changeait à chaque image, on pourrait au maximum faire du 1/108ms soit 9fps. En pratique tous les octets ne changent pas d'une image sur l'autre, et l'on oscille entre 13fps (David Guetta) et 40-60fps (démos amiga/cpc/zx) en passant par 20fps pour les films si j'ai bonne mémoire (tout ca en plein écran bien entendu).

3) A 60fps, l'encodage est vraiment trop lent (plus de 24h pour un film), aussi je limite pour le moment à 25-30fps ce qui me permet d'encoder en 0.15x-0.2x (il faut 5 sec de temps réel pour encoder 1 seconde de vidéo).
Samuel.
A500 Vampire V2+, A1200(030@50mhz/fpu/64mb/cf 8go),
GVP530 (MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.

Avatar du membre
yo_fr
Messages : 1329
Enregistré le : 13 août 2009 18:24
Localisation : 78...
Contact :

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

Message par yo_fr » 13 juil. 2015 09:37

__sam__ a écrit : Tu crois qu'on a largué tout le monde avec nos expérimentations Thomson? Le fil est peut-être devenu trop long pour ceux qui voudraient le suivre depuis le départ.
Tiens, on parle de moi (et je suppose que je ne suis pas le seul !).
En fait hier déjà je voulais vous poser une simple question : comment ça marche ?
OK la carte SD est lue (sera lue serait plus judicieux vu que vous en êtes au stade dcmoto, mais voyons loin, courage Daniel !) par la platine Arduino, ça c'est ok;
Un octet est envoyé au MO (ou TO) par l'Arduino,en // en utilisant les ports joysticks, ok;
Je n'ai plus le compte mais 1 sur 4 est pour l'audio sur 6 bits avec 2 bits de contrôles, ok;
maintenant la vidéo : Vous encodez à l'aide de ffmpeg avec la résolution / palette du MO. ça je pense comprendre, mais le flux d'octets vidéo est composé comment ? il y a une @ suivi d'une couleur ? Bref quel est la structure du fichier produit ? (je ne lis pas non plus assez bien le 6809, désolé et je ne connais pas les arcanes de ffmpeg non plus).

Je pense (et espère) ne pas être le seul à avoir ces questions...
Evidemment ces questions ont un lien avec la bidouille que je viens de terminer... :wink:

PS : Bravo les champions ! c'est du beau boulot !

Avatar du membre
Carl
Modérateur
Messages : 10435
Enregistré le : 08 avr. 2007 13:21
Localisation : http://www.doledujura.fr
Contact :

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

Message par Carl » 13 juil. 2015 11:21

Bravo !
Je pense peut être tester ça en vrai un de ces jours avec la futur interface SDAduino :wink:
Carl

Daniel
Messages : 11349
Enregistré le : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel » 13 juil. 2015 14:14

S'il y a des amateurs, je proposerai peut-être un jour de fabriquer un lot d'interfaces Arduino pour Thomson. Je ferai l'annonce à la section Bricolage. Mais il est encore trop tôt, il faut d'abord écrire et tester le sketch Arduino.
Daniel
L'obstacle augmente mon ardeur.

__sam__
Messages : 4455
Enregistré le : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ » 13 juil. 2015 17:07

yo_fr a écrit :En fait hier déjà je voulais vous poser une simple question : comment ça marche ?
Excellente question!

En effet, pour que ca marche il y a des trucs qui sont évidents pour les Thomsonistes, mais obscurs pour tout le reste du monde. Il est donc temps de faire un peu de pédagogie.

Le premier truc à comprendre est la façon dont les TO et les MO organisent la mémoire. C'est le brevet Thomson n°FR2477745A1, mais on retrouve l'équivalent sur Oric (qui utilise que 6 bits des octets). Attention ce n'est pas le mode avancé des TO8 ou MO6, mais le mode de base des TO7 et même T9000, dit mode 40 colonnes.

Tout d'abord signalons que ce mode est un mode graphique dans lequel les pixels sont accessibles indiviuellement. Comme le mode fait 320x200 pixels, il est organisé en 40 colonnes de 200 lignes, d'où le nom "40 colonnes". A l'intersection d'une colonne et d'une ligne on trouve un octet. Avec 40 colonnes et 200 lignes cela nous fait exactement 8000 octets, qui sont accessibles dans une zone de 8192 octets en mémoire (il y a 192 octets non visibles à l'écran). Cette zone mémoire est située à differentes adresses sur TO ($4000-$5F40) et sur les MO ($0000-$1F40). Pour le player, qui marche sur ces deux plateformes, cela signifie qu'il faut d'abord détecter si on est en TO ou MO et adapter les adresses vidéo en conséquence. Une astuce utilisée par Daniel est que dans la zone video des MO se trouve une ROM coté TO. Donc si on change le contenu, sur MO il aura changé, et sur sur TO il n'aura pas changé. C'est ce qui est fait au début du player.

Je disais donc qu'à l'intersection d'une colonne et d'une ligne se trouve un octet. Chacun des bits de cet octet représente un pixel à l'écran. Le bit numéro i (MSB) à l'intersection de la colonne c, ligne l représente le pixel de coordonnées x=c*8+(7-i), y=l. Ce bit peut avoir deux valeurs 0 ou 1. L'idée du brevet Thomson est qu'au lieu de dire 0=éteint=noir 1=allumé=blanc, ce qui nous aurait donné un écran monochrome, est d'associer deux couleurs pour ces bits 0 et 1. Un bit à 0 s'affiche dans la couleur dite fond, et s'il est à 1 il s'affiche à la couleur dite forme. Ce jeu de couleur n'est valable que pour les 8 pixels à l'intersection de la colonne et de la ligne donnée. Il y a 16 (8 seulement sur TO7 et T9000) couleurs de fond/forme possible (palette fixe thomson débutant par NOIR ROUGE VERT JAUNE BLEU VIOLET CYAN BLANC), donc un couple peut être représenté par un octet (16*16=256). Chacun des 8000 octets de fond/forme sont définies par une autre mémoire vidéo de 8000 octets situées aux mêmes adresses que les octets définissant les pixels via un jeu de banque mémoire. Cette mémoire est dite mémoire COULEUR alors que la première mémoire mémoire définissant les pixels est la mémoire dite FORME.

Donc en résumé sur Thomson une image couleur est définie par deux fois 8000 octets via la mémoire video FORME et COULEUR. Chaque octet défini 8 pixels chacun pouvant prendre l'une des deux couleurs de la palette de 16 couleurs de base. Cette organisation est astucieuse car elle permet de faire des images à la fois très colorées et très précises (voir: http://www.logicielsmoto.com/phpBB/view ... ?f=3&t=383). Mais cela vient avec un cout. Celui de devoir faire une commutation de banque mémoire pour lire ou même changer la couleur d'un pixel. C'est pour cette raison que les routines d'affichage Thomson (ligne, point, rectangle et même texte) sont d'une lenteur désespérante.

C'est aussi à cause de cela que le player d'origine de Daniel est monochrome. Il fixe le couple forme/fond de tout les octets à blanc/noir par la commande basic SCREEN, puis ne travaille plus que sur la mémoire vidéo FORME, ce qui est bien plus rapide que de faire des commutations de banque mémoire pour chaque groupe de 8 pixels.

D'ailleurs fondamentalement le player, même dans sa dernière version, est toujours un player monochrome. Il ne fait qu'écrire dans la mémoire FORME. Très bien, mais comment ca se fait qu'on voit de la couleur ? Ici il y a une astuce très intéressante: Au lieu d'initialiser la mémoire COULEUR du noir/blanc uniforme, celle-ci est initialisée de sorte que chaque ligne présente une couleur uniforme différente de la ligne au dessus et au dessous. La ligne numéro 1 est initialisée avec le couple rouge/noir, la ligne numéro 2 l'est avec le couple vert/noir, la numéro 3 avec le couple bleu/noir, la numéro 4 à nouveau avec rouge/noir, la 5 avec vert/noir la 6 avec bleu/noir. Bref toutes les 3 lignes on retrouve le même couplet forme/fond(noir). Très bien, mais qu'est ce qu'on peut faire avec une telle initialisation? Et bien on peut afficher jusqu'à 8 couleurs virtuelles. En effet, si j'allume un bit d'un octet sur la ligne des rouge et aucun sur les lignes vert et bleu qui suivent, j'obtiens un pixel rouge à l'écran. Si j'allume le même bit sur la ligne des verts j'ai rouge+vert=un pixel jaune à l'écran. Idem pour chacune des lignes: on peut en fait allumer ou éteindre chacune des composantes rouge, verte ou bleue d'un gros pixel étalé sur 3 lignes, ce qui nous fait bel et bien 8 couleurs indépendantes alors qu'on ne fait qu'écrire dans la mémoire FORME. C'est cette astuce qui est utilisée dans la démo "oh la belle bleue!" ( :arrow: un must à regarder le 14 juillet). C'es aussi un mode utilisé par Dbug de Defence-Force sur oric dans Baritoric.


Alors c'est bien on a 8 couleurs, mais on a réduit la résolution verticale d'un facteur 3. On a plus 200 pixels de faut, mais 200/3=66.6 pixels de haut. Aie mince zut: on a maintenant des pixels au format 1:3. Ils sont tout fins horizontalement et tout gros verticalement. C'est là où le traitement fait par le script perl s'occupe de ce soucis. En fait c'est tout simple, au lieu de travailler à la résolution 320x66 ce qui donnerait effectivement de très gros pixels verticaux, on travaille en 320x200, mais on ne prend que le rouge de la 1ere ligne, le vert de la 2eme, et le bleu de la 3eme (puis le rouge de la 4eme, le vert de la 5e, etc ca reboucle). Le résultat est étonnant: on obtient encore de la couleur tout en préservant la résolution de 200 en hauteur. C'est le même principe qui est utilisé dans la techno ClearType de Microsoft pour augmenter la résolution horizontale des écrans, sauf que nous on le fait verticalement.
Image
Ainsi d'une résolution 320x66 on repasse en 320x(3*66)=320x200. Quelques artefacts peuvent apparaitre si une ligne de couleur non uniforme d'un pixel de haut apparait là où l'on ignore cette couleur. La ligne apparait alors discontinue. Ca arrive, mais c'est rare donc on fait avec. (J'ai l'idée d'un filtre spécifique pour ce cas là, mais il est trop gourmand en temps CPU)

Oui mais bon, avec ca on a que 8 couleurs. C'est mieux que du monochrome, mais on ne peut pas voir de teintes intermédiaires. En théorie non, mais en pratique oui en utilisant la technique du tramage qui permet de reproduire toutes les couleurs que l'on veut à partir du moment où l'on sait en afficher 8 indépendante. Or les 8 couleurs indépendantes, c'est justement ce que le truc ci-dessus permet de faire. La théorie du tramage est très bien connue et décrite. Je n'entrerait pas dans les détails. Il faut juste réaliser qu'ici il a fallu innover pour faire apparaitre un tramage qui soit régulier alors que le pavé de base n'est pas une puissance de deux. L'astuce est d'ignorer la matrice de bayer classique et d'utiliser un générateur de matrice "[url=chrome-extension://encfpfilknmenlmjemepncnlbbjlabkc/http://home.comcast.net/~ulichney/CV/pa ... luster.pdf]void-and-cluster[/url]" (je me suis inspiré de celle de la libcaca) pour produire un pavé trois fois plus large que haut (9x3). Ce pavé est alors étalé verticalement en répétant les lignes 3 fois, ce qui nous fait une matrice régulière 9x9. A noter que le void-and-cluster produit un bruit dit "bleu" qui est plus agréable à l'œil que le bruit blanc produit par les méthodes de dither classique type dispersion d'erreur. L'autre avantage est que les pixels allumés gardent leur position d'une image à l'autre ce qui fera gagner en terme de compression.

Ok, le cas de la couleur est réglé: en fait on travaille en monochrome, mais un prétraitement est fait dans la mémoire COULEUR pour que des couleurs apparaissent. Bien évidemment, ce pré-traitement est aussi effectué dans le convertisseur pour que les données issues du stream soient en accord (sur la ligne de rouge on doit bien recevoir du rouge et pas la composante verte ou bleue). Mais comment est encodé une image ?

En fait dès le début une image n'est pas envoyée de façon brute. En effet, une image monochrome est composée de 8000 octets. Si on veut avoir 25 images par seconde cela nous fait 25*8000 = 200ko/secondes. Or le player n'est pas capable d'aller aussi vite. Il faut donc réduire l'image, la compresser.

Ce que l'on comprend vite quand on fait de l'animation, c'est que seulement une partie de l'écran a changé entre deux images et qu'au lieu d'envoyer tout un ecran au complet il suffit juste d'envoyer la partie qui a vraiment changée. C'est ce qu'a utilisé Daniel dans son premier player et dont le principe est resté jusqu'à présent. L'idée est de faire précéder un (ou des) octet(s) devant être changés d'un octet de déplacement pour passer par dessus n octets inchangés. Ainsi on réalise une compression qui est la base du player. Le player maintient un pointeur permanent sur la zone mémoire devant être modifié. Il lit le flux, déplace ce pointeur et y inscrit les nouvelles données vidéo. Régulièrement il envoie du son dans l'interface "musique et jeux" du thomson pour produire le son.

Le format actuel du player est basé sur un groupe de 7 octets qui se décompose en 1 octet de son et d'administration suivi de deux triplets videos.

L'octet son a le format suivant en bit: <encore?> <clock> <nnnnnn>. Les 6 bits de poids faibles <nnnnnn> correspondent à la valeur à envoyer au convertisseur numérique/analogique de l'interface "musique et jeux" du thomson. Le bit <clock> est un bit alternant entre 0 et 1 tous les 7 octets lu et servant à la synchro avec l'arduino. Enfin le bit <encore?> est à 1 tant qu'il y a des données à lire.

Le triplet vidéo est bâti comme ca en octets: <déplacement:8bits> <donnée:16bits> et s'interprète comme suit: si le déplacement n'est pas nul on incrémente le pointeur vidéo de cette valeur. S'il est nul, on place le pointeur video en début de mémoire vidéo. Enfin dans les deux cas on inscrit la donnée 16 bit dans la mémoire FORME.

Un triplet vidéo fourni donc 2 octets vidéo FORME consécutifs, et par bloc de 7 octets on en traite 4 à deux positions distinctes de l'écran.

C'est très bien, mais attends 3*7=511 ca ne remplie pas les 512 octets d'un secteur de carte SD. Que fait t'on du dernier octet qui traine? Le truc ici est de dire qu'on a juste la partie audio, sans aucun triplet vidéo. Les cycles correspondant à la gestion des deux triplets vidéo est utilisé pour lire le checksum et attendre la synchro "début de block" avec la carte SD du coté de l'arduino. Finalement le découpage en 7 octets tombe très bien. Au niveau de l'optimisation on profite du temps d'attente pour tester la fin de fichier (bit <encore?>) uniquement sur ce dernier octet. Cela permet d'aller encore plus vite pour les données à l'intérieur d'un bloc de carte SD.

Le décodage de 7 octets ne prends que 60 cycles, soit environ 8 cycles par octet. C'est très peu pour le 6809, preuve que le code est très optimisé. Songez qu'un simple échange de deux registres sur le 6809 prend lui aussi 8 cycles. Au final un bloc de carte SD (512 octets) est traité en (73+1)*60 = 4440 cycles processeur (4.44ms), ce qui signifie que l'Arduino doit lire la carte SD à la vitesse de 115315 octets/seconde ce qui est une vraie prouesse dans la mesure ou les API standard de lecture de carte SD plafonnent à quelque dizaines de ko/sec.

Voilà j'espère avoir fait le tour du sujet. Si je n'ai pas été assez clair et que certains points vous semblent encore obscurs, n'hésitez pas à demander, j'essayerais de mieux expliquer.

C'est pas facile ce sujet traitre de beaucoup de points différent qui m'ont tous intéressés à un moment donnée ou à un autre sur thomson: conversion d'image, conversion audio, optimisation asm, et je ne parle même pas de la partie matériel avec l'arduino de Daniel. Quelque part ce sujet regroupe tout l'état de l'art de ce tout ce qu'il s'est fait de mieux sur thomson jusqu'à présent. Simply state-of-the-art comme ils disent sur Commodore :D

http://www.cjoint.com/doc/15_07/EGnqVq4 ... ga-Demo.7z (Vrai fichier SD, bien mieux que youtube)

[EDIT] replacement de la video par un autre qui est peut-être mieux (ancienne video: https://www.youtube.com/watch?v=eI-eVj3iCgA)
Modifié en dernier par __sam__ le 14 juil. 2015 19:41, modifié 2 fois.
Samuel.
A500 Vampire V2+, A1200(030@50mhz/fpu/64mb/cf 8go),
GVP530 (MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.

Avatar du membre
yo_fr
Messages : 1329
Enregistré le : 13 août 2009 18:24
Localisation : 78...
Contact :

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

Message par yo_fr » 13 juil. 2015 19:30

Parfait pour la description !
Alors une question : comment encodez vous la vidéo ? le format très particulier, l'encodage avec déplacement etc... :?:

__sam__
Messages : 4455
Enregistré le : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ » 13 juil. 2015 20:08

L'encodage est fait par un script "perl" maison que j'ai promis de fournir aujourd'hui. Dont acte

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

$file = $ARGV[0];

# params par defaut
mkdir("tmp");
$img_pattern = "tmp/img%05d.bmp";
($w, $h) = (320, 180); # 16:9
$cycles = 60; # cyles cpu par echantillon audio
$hz  = int(2000000/$cycles+.5)/2;
$fps = 10;
$interlace = 0; # 0=off, 1=simple, 2=smart
$coul = 1;
$audio_dither = 0;

# recherche la taille de l'image
($x,$y, $aspect_ratio) = (320,200,"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) = (266,200,"4:3");
		}
	}
}
close(IN);
$h = int(($w=320)*$y/$x);
$w = int(($h=200)*$x/$y) if $h>200;

print $file," : ${x}x${y} ($aspect_ratio) -> ${w}x${h}\n";

# AUDIO: unsigned 8bits, mono, 16125hz
open(AUDIO, "./ffmpeg -i \"$file\" -v 0 -f u8 -ac 1 -ar ".int(2*$hz)." -acodec pcm_u8 - |");
binmode(AUDIO);

# tuyau vers ffmpeg pour images
open(FFMPEG,'| (read line; $line)');#  -vf format=gray
binmode(FFMPEG);

# fichier video (entree)
open(IN, "<$file");
binmode(IN);

# determination du zoom optimal
$zoom = &guess_zoom($w, $h);
$fps = int($fps * ($zoom>1?$zoom:1));
$w = int($w*($zoom>1?1:$zoom));
$h = int($h*($zoom>1?1:$zoom));
print sprintf("zoom = %.2g -> %dx%d @ %dfps\n", $zoom, $w, $h, $fps);
$fps = 25 if $fps>25;
$cmd = "./ffmpeg -i - -v 0 -r $fps -s ${w}x${h} -an $img_pattern\n";
syswrite(FFMPEG, $cmd, length($cmd));

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

# fichier sd (sortie)
$name = $file; $name =~ s/\.[^\.]*$//; $name .= ".sd";
open(OUT, ">$name");
binmode(OUT);

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

# compteur image
$cpt = 1;

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

# position dans ecran
$pos = 8000;

# compression
$time = 0; $start = time; $realimg = 0; $start_pos=0; $pause = 60;
$cycles_per_img = 1000000/$fps;
$current_cycle = $cycles_per_img; $clk = 0;
$first_line = 40*((200-$h)>>1)-255;
@buf = ();
for($running = 0x80; 1; $current_cycle += $cycles) {
	push(@buf, &echantillon_audio());
	if($#buf<511) {
		push(@buf, &triplets_video());
	} else {
		# write
		print OUT pack('C*', @buf);
		@buf = ();
		# fin fichier
		last unless $running;
	}
}

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

# Lit 2 échantillons audio de FFMPEG et le converti en échantillon audio pour le flux SD
sub echantillon_audio {
	if(!@AUDIO) {
		my($buf);
		$running = 0 if !read(AUDIO,$buf,8192);
		push(@AUDIO, unpack('C*', $buf));
	}
	my $v = (shift(@AUDIO)+shift(@AUDIO))/2;
	# volume auto
	$audio_cor[1] = $v if $v<$audio_cor[1];
	$v-=$audio_cor[1];
	$audio_cor[0] = 255/$v if $v*$audio_cor[0]>255;
	$v *= $audio_cor[0];
	# dither audio
	$v += int(rand(3)) if $audio_dither; 
	$v=255 if $v>255;
	# sortie audio
	return ($v>>2) | $running | ($clk ^= 0x40);
}

# retourne 2 triplets video
sub triplets_video {
	my($num, @buf) = 2;
	if($current_cycle>=$cycles_per_img) {
		$current_cycle-=$cycles_per_img;
		
		&next_image(*IN,*FFMPEG,$w,$h);

		# image complète ?
		if($pos>=8000+$start_pos) {
			++$realimg;
			push(@buf, 0, $ecran[0] ^= $cible[0], $ecran[1] ^= $cible[1]);
			$cible[0] = $cible[1] = 0;
			$pos = 0; $num = 1;
		} 
		
		$start_pos = $pos;
	
		# infos à l'écran
		if($cpt%$fps == 0) {
			++$time;
			my($d) = time-$start+.0001;
			print STDERR sprintf("%d:%02d:%02d (%.2gx) v=1:%.3g a=(x%+d)*%.1g        \r",
				int($time/3600), int($time/60)%60, $time%60,
				int(100*$time/$d)/100, $realimg/($cpt-1), -$audio_cor[1], $audio_cor[0]);
			# pour ne pas trop faire chauffer le CPU
			if($d>$pause) {$pause = $d+60;sleep(10);}
		}
	}
	for (1..$num) {
		my $p = $pos % 8000;
		my($k) = $p<$first_line?255:1;
		while($k<255 && !$cible[$p+$k]) {++$k;}
		$pos+=$k;
		if(($p+=$k)>=8000) {
			$k = $p = 0;
			$pos = int($pos/8000)*8000;
		}
		my($q) = $p+1;
		push(@buf, $k, $ecran[$p] ^= $cible[$p], $ecran[$q] ^= $cible[$q]);
		$cible[$p] = 0 if $p<8000;
		$cible[$q] = 0 if $q<8000;
	}
	return @buf;
}

# 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;
	} 
	
	# chargement image-magick la 1ere fois
	if(!$_magick) {
		$_magick = 1;
		
		my($home) = "tmp";
		$ENV{'HOME'} = $home;
		mkdir($home);
		mkdir("$home/.magick");
		open(THR, ">$home/.magick/thresholds.xml");
		
		if($coul) {
			print THR <<EOF;
		<thresholds>
			<threshold map="sd">
                                <description>void and cluster 28 niveaux pour couleur</description>
				<levels width="9" height="9" divisor="28">
					7 19 14 6 24 12 18 2 25
					7 19 14 6 24 12 18 2 25
					7 19 14 6 24 12 18 2 25

					21 3 23 11 17 4 26 9 13
					21 3 23 11 17 4 26 9 13
					21 3 23 11 17 4 26 9 13
									
					16 10 27 1 20 8 15 22 5
					16 10 27 1 20 8 15 22 5
					16 10 27 1 20 8 15 22 5
				</levels>
			</threshold>
		</thresholds>
EOF
		
		} else {
			print THR <<EOF;
		<thresholds>
			<threshold map="sd">
				<description>void and cluster 65 niveaux</description>
				<levels width="8" height="8" divisor="65">
					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
				</levels>
			</threshold>
		</thresholds>
EOF
		}		
		close(THR);
		
		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";
	}
	
	# lecture image
	my $tmp = Image::Magick->new();
	my $z = $tmp->Read($name);
	#print STDERR $z if $z;
	return 0 if $z; # si erreur => fin fichier
	unlink $name;
	

	# dither
	$tmp->Set(depth=>16);
	$tmp->Set(colorspace=>$LINEAR_SPACE);
	$tmp->Set(dither=>"FloydSteinberg");
	
	# plus de couleur
	$tmp->Modulate(saturation=>120);

	if($phase==1) {
		@stat = (0)x256 unless $#stat>=0;
		my(@p) = $tmp->GetPixels(map=>"RGB", height=>$h, width=>$w, normalize=>"True");
		for my $p (@p) {++$stat[int(255*$p)];}
	} 
	if($phase==2) {
		my $tot = -$stat[0];
		for my $p (@stat) {$tot += $p;}

		my $thr = ($tot*4)/100;
		$lvl_max=$#stat;
		for(my $t=0; ($t+=$stat[$lvl_max])<$thr;  --$lvl_max) {}
		$lvl_min=1;$thr/=1;
		for(my $t=0; ($t+=$stat[$lvl_min])<$thr;  ++$lvl_min) {}
		print "re-level: $lvl_min->0 $lvl_max->255\n";
		$phase = $lvl_max>0 && $lvl_max<255 ? 3 : 4;
	} 
	if($phase==3) {
		$tmp->Evaluate(operator=>'Subtract', value=>$lvl_min/255);
		$tmp->Evaluate(operator=>'Multiply', value=>255/($lvl_max-$lvl_min));
		#$tmp->Evaluate(operator=>'Multiply', value=>255/$lvl_max);
	}
	# $phase==4 rien
	
	
	if(!$coul) {
		# mieux ?
		$tmp->Set(colorspace=>"gray");
		#$tmp->OrderedDither(threshold=>"o8x8,2");
		#$tmp->OrderedDither(threshold=>"h8x8a,2");
		$tmp->OrderedDither(threshold=>"sd,2");
	} else {
		$tmp->OrderedDither(threshold=>"sd,2");
	}
	
	# centrage dans image 320x20
	my $img = Image::Magick->new(size=>"320x200");
	$img->Read("canvas:black");
	$img->Composite(image=>$tmp,compose=>"Over",
	                x=>(320-$w)>>1,y=>(200-$h)>>1);
			
	$img->Draw(stroke=>"White", primitive=>"line", points=>"0,199 ".int(319*($cpt-1)/($fps*$total_sec)).",199") if defined($total_sec);
	
	# remplissage variable globale @cible
	my(@p) = $img->GetPixels(map=>"RGB", height=>200, width=>320, normalize=>"True");
	
	my($start) = (200-$h)>>2;
	my($stop)  = 200*960-3;
	
	@cible = (0)x(40*$start);
	if(!$coul) {
		for(my $i=$start*960-3; $i<$stop;) {
			my $v = 0;
			for my $j (0..7) {$v <<= 1; $v |= 1 if $p[$i+=3]>.5;}
			push(@cible, $v ^ $ecran[1+$#cible]);		
		}
	} else {
		for my $y ($start..199) {
			my $i = $y*960 - 3 + ($y%3);
			for my $x (0..39) {
				my $v = 0;
				for my $j (0..7) {$v <<= 1; $v |= 1 if $p[$i+=3]>.5;}
				push(@cible, $v ^ $ecran[1+$#cible]);		
			}
		}
	}
	push(@cible, 1,1); # semaphore fin ecran
	
	my $f = sub {
		my($y) = @_;
		splice(@cible, $y*40, 40, (0)x40);
	};
	$f = sub {
		my($y) = @_;
		
		if(!@cnt) {
			for my $i (0..255) {
				$cnt[$i] = 0;
				my $t = $i;
				while($t) {++$cnt[$i]; $t ^= $t&-$t;}
			}
		}
		$y *= 40; my($c) = 0;
		for my $i ($y..$y+39) {$c += $cnt[$cible[$i]];}
		splice(@cible, $y, 40, (0)x40) if $c<40*$w/320;
	} if $interlace==2;
	
	if($interlace && !$coul) {
		for(my $y=$cpt&1; $y<200; $y+=2) {$f->($y);}
	}	
	if($interlace && $coul && ($cpt&1)) {
		for(my $y=1; $y<200; $y+=3) {$f->($y);}
	}
	if($interlace && $coul && !($cpt&1)) {
		for(my $y=0; $y<200; $y+=3) {$f->($y);}
		for(my $y=2; $y<200; $y+=3) {$f->($y);}
	}
	
	return 1;
}

sub guess_zoom {
	my($w, $h) = @_;
	my $BUFFERSIZE = 7*int(($hz+$fps-1)/$fps);
	
	# tuyau vers ffmpeg pour images
	open(GOUT,"| ./ffmpeg -i - -v 0 -r 1 -s ${w}x${h} -an $img_pattern");#  -vf format=gray
	binmode(GOUT);

	# fichier video (entree)
	open(GIN, "<$file");
	binmode(GIN);
	unlink(<tmp/img*.bmp>);
	
	@ecran = ((0)x8000, 1);
	local $cpt = 1;
	my $size = 0;
	$phase = 1; 
	while(&next_image(\*GIN, \*GOUT,$w,$h)) {
		for(my $p = 0; $p<8000;) {
			my $k = 1;
			while($k<255 && !$cible[$p+$k]) {++$k;}
			$p+=$k;
			$cible[$p+1]=0; # <== car on marche de 2 en deux
			++$size if (($size&511)%7)==0; # audio
			$size += 3; # video
		}
		# audio + goto 0
		$size += 4;
		print STDERR sprintf("avg %dx%d frame length = %d bytes (%d%%) @ %ds                 \r", $w, $h, int($size/($cpt-1)), int(100*$size/(($cpt-1)*$BUFFERSIZE)), $cpt-1);
	}
	$phase = 2;
	
	$total_sec = $cpt-1;
	unlink(<tmp/img*.bmp>);
	print STDERR "\n";
	my $len_per_img = ($size/($cpt-1)) * (1.05); # 5% extra
	close(GIN);
	close(GOUT);
	my $zoom = $BUFFERSIZE/$len_per_img;
	return $zoom>=1?$zoom:sqrt($zoom);
}
Quelques explications:

En fait, le décodage video est fait par ffmpeg auquel on demande de produire des images BMP de la vidéo à une certaine vitesse (paramètre "-r" de cet outil), par exemple 10 fois par secondes. On se retrouve alors avec un lot monstrueux d'image couleur, 10 images représentant 1 seconde de video par exemple (en pratique on fait en sorte de détruire les images après conversion ce qui fait qu'il n'y a jamais plus d'une image sur disk via la routine "next_image"). Le script lit alors l'image, effectue le tramage "spécial", ajoute la barre de progression. On obtient alors une image FORME à envoyer sur thomson via le fichier SD. Il maintient un buffer de la dernière image FORME envoyée sur thomson et calcule la différence avec la nouvelle image FORME. A partir du pointeur courant dans la différence il compte le nombre d'octets identiques consécutifs (bornée à 255), et une fois une différence trouvée, il produit (via la routine "triplets_video") le triplet "déplacement:8bits + données:16bis". Le fait qu'on envoie 16 octets de données alors que seul le premier octet change fait gagner un déplacement car la plupart du temps l'octet qui suit un changement est lui même aussi changé. Enfin, si le pointeur dépasse la fin d'écran un déplacement nul est utilisé ce qui fait revenir le pointeur en début d'image. Voilà en gros pour l'encodage.

En parallèle de tout cela, FFMPEG est aussi lancé pour nous produire le son à la fréquence 16.67khz correspondant à la fréquence audio du player (on a donc deux processus FFMPEG qui tournent: l'un pour le son, l'autre pour l'image). Tous les deux "triplets_video" appelés, on récupère un nouvel échantillon audio auquel on ajoute le bit de fin de fichier et l'horloge (routine "echantillon_audio"). On assemble cet octet audio avec les deux triplets video et on obtient un bloc de 7 octets que constitue le format video. A noter tous le 73 blocs de 7 octets on ne calcule pas les triplets vidéo et on ne fait que sortir un seul octet pour compléter le bloc de 512 octets d'un secteur SD. C'est ce que la formule (position & 511)%7==0 donne: Elle vaut vrai de en position 0, 7, 14, bref de sept en sept, jusqu'à 511 (=7*73) mais elle vaut aussi vrai pour position==512. C'est assez merveilleux: on calcule le cas spécial du dernier octet d'un secteur SD avec une formule toute simple et générale. (En général moins il y a de cas particuliers, plus proche de la meilleure solution on se trouve.)

Dans la description ci-dessus j'ai un peu simplifié ce que fait le script car si on se contente de cela le résultat est médiocre.

Par exemple pour l'audio on demande à FFMPEG de travailler à 2x la vitesse attendue, et au lieu de lire un seul échantillon on en lit deux, duquel on tire la moyenne. Cette moyenne est moins bruitée que l'échantillon brut. On constate aussi que d'une vidéo à l'autre les niveaux ne sont pas du tout les mêmes. Ils sont même parfois trop petit par rapport aux niveaux thomson (style: ca va e 0 à 32/255 sur PC, qui lorsque c'est converti sur thomson nous fait qu'un seul bit à l'arrivé). Aussi le script effectue un traitement de renormalisation adaptatif des niveaux pour utiliser au mieux les 6 bits dont dispose le convertisseur numérique/analogique de l'interface musique et jeux (tableau "audio_cor"). En fait il maintient 2 valeurs, l'un est un offset correspondant à la valeur minimale des échantillons lus jusque là et l'autre un facteur multiplicatif (initialement 8 ). Pour chaque échantillon audio lu, s'il est plus petit que le min, on met à jour le min. On remplace ensuite l'échantillon par (échantillon-min)*facteur_mult. Si cet échantillon adapté dépasse 255 c'est que le facteur multiplicatif utilisé jusque là est trop fort. On le réduit alors de sorte que la formule donne un échantillon ne dépassant pas 255. Ainsi au fil du flux audio, le coef multiplicatif diminue pour que tous les échantillons occument bien l'ensemble des valeurs. On voit la formule correcrice résultant sur la litgne de commande: a=(x-71)*2. "a" 'est l'échantillon audio traité, "x" c'est le brut, 71 c'est l'offset, et 2 c'est le coef multiplicatif (plus il est gros, plus le son de l'ensemble de la vidéo était faible).

Pour la vidéo on a aussi un problème de niveaux, aussi le script effectue une analyse dans une première passe sur l'ensemble des images. Il calcule un histogramme sur toutes les images de toutes les niveaux rencontrés. Il sature alors vers le max 4% des niveaux, et vers le min 2% des niveaux. Bref il étire les couleurs pour bien occuper l'ensemble des valeurs que le tramage permet de faire (sinon on peut n'utiliser que 25% de tous les niveaux disponibles).

Pendant cette première passe on mesure aussi combien il faudrait d'octets moyens pour encoder une image. Il adapte alors le FPS ou le ZOOM pour que le nombre d'octets par image soit compatible avec le FPS demandé, quitte à augmenter le FPS si le nombre d'octet est faible ou réduire l'image si le nombre d'octet est gros.

Voilà pour une explication rapide du script de conversion video->sd.

Si on avait eu une telle technique en 2011 (et oui 4 ans déjà), on aurait pu rivaliser avec le CPC à la forever party (bon ok, ca aurait un gros plagiat qui occupe 70Mo alors que la démo CPC tient sur diskette)

(http://dl.free.fr/nh146kJqn)

Bon je ne sais pas ce que vous faites ce soir, mais moi je regarde ARTE :mrgreen:

(http://www.cjoint.com/data/EGnstfgPyMr_ ... 1993-HQ.7z)

Punaise je le crois pas (bis): même les musiques des démos amiga sont sous (c) http://www.audiam.com/. Les rapaces cherchent de l'argent partout. Bientôt quand on va pisser dehors sur un arbre en sifflotant un air qu'aura reconnu notre smartphone, google va nous demander de payer là aussi une licence. Il y a de l'open-source en musique ou est-ce définitivement un monde envahit d'extorqueurs de fonds pour qui l'argent n'a pas d'odeur? Nan mais c'est n'importe quoi ces histoires de (c). Imaginez que les matheux posent des (c) sur les tables d'additions ou sur la règle de trois pendant qu'on y est...

[EDIT] Là aussi j'ai changé les video (anciennes: https://www.youtube.com/watch?v=fIpodnH98HA, et https://www.youtube.com/watch?v=fSpImT6F06w)
Modifié en dernier par __sam__ le 15 juil. 2015 14:46, modifié 1 fois.
Samuel.
A500 Vampire V2+, A1200(030@50mhz/fpu/64mb/cf 8go),
GVP530 (MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.

Fool-DupleX
Messages : 1064
Enregistré le : 06 avr. 2009 12:07

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

Message par Fool-DupleX » 14 juil. 2015 09:55

Je suis réellement impressionné par le résultat. La matrice de tramage utilisée est vraiment excellente, n'en change plus. le prétraitement des niveaux apporte un plus indéniable.

Etape suivante : supprimer l'arduino ... :wink: et gagner au passage encore de la bande passante ; je vois encore deux possibilités successives d'en gagner, sur les transferts d'octets purs, pas sur le codec, qui me semble effectivement proche de la perfection ...

Par contre, je me pose une question depuis le debut. C'est tres visible sur les video de Simon's cat. il y a plein d'endroits dans ces video ou l'image (le decor par exemple) ne change pas formellement d'une frame a l'autre, mais le codage mpeg d'origine genere des artefacts qui font qu'un pixel par ci par la change. Ne pourrait-on pas pretraiter cela avec un critere du genre "si un seul pixel change dans un voisinage de nxn, on ignore ce changement" pour supprimer l'encodage de tous les changements qui n'en sont pas ?

Edit: comment vous faites la comparaison entre les frames ? Si vous faites un simple diff pour trouver les parties qui changent, y'a peut-être moyen de changer ca en rajoutant un modele perceptif (a base du fameux flou gaussien) + seuillage pour trouver les zones qui changent vraiment. Je vienx de me mater la SOTA sur l'emu et par exemple sur le dragon fixe au debut, on voit bien les pixels rouges qui clignotent pour rien pendant plusieurs secondes.

Fool-DupleX
Messages : 1064
Enregistré le : 06 avr. 2009 12:07

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

Message par Fool-DupleX » 14 juil. 2015 11:01

Je me reponds a moi-même, en fait c'est plutot un diff gaussien ou une detection de pixels seuls des frames *apres codage* qu'il faudrait faire et encore ce n'est pas aussi simple, a cause du tramage. peut-être en mixant ca avec une metrique de changement global ? Genre si un pixel change et qu'il n'y a en a que 5 dans toute l'image tramée qui change, ce sont probablement des artefacts ? a voir ...

__sam__
Messages : 4455
Enregistré le : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ » 14 juil. 2015 11:12

La comparaison d'image n'est pas faite sur l'image 24bits, mais sur la conversion "mémoire FORME" de cette dernière, c'est à dire après l'étape de discrétisation/tramage. Du coup le pixel légèrement différent produit par l'encodeur mpeg se trouve être masqué par cette opération de discrétisation. On effectue d'une pierre deux coup.

A une époque j'avais imaginé, pour gagner en bande passante, compter les pixels différents de la mémoire forme et s'il y en a peu, considérer que l'image est inchangé. Mais le seuillage n'est pas facile à mettre au point. Et puis il y a assez de bande passante finalement pour ne pas s'occuper des faibles changements. Donc j'ai laissé tomber l'idée.

Autre idée que j'avais imaginée (et qui est toujours présente dans le script de conversion) faire de l'entrelacé. En N&B les trames paires ne changent que les lignes paires, et les impaires les lignes... impaires (ca va yen a qui suivent :wink: ). En Couleur on change pour les images paires les lignes vertes (les plus lumineuses) et pour les images impaires les lignes rouges+bleu (dont l'intensité totale équivaut à celle du vert). En N&B ca marche très bien, par contre en couleur quand il y a de gros changements, on voit apparaitre une image fantôme lors des changement brutaux (le vert de l'image précédente + le rouge-bleu de la nouvelle). C'est pas heureux. Cet effet n'est plus visible au delà de 45-50hz, mais si on peut encoder un clip à 50hz, on a pas besoin de faire de l'entrelacé. Bref: une fausse bonne-idée.

Un truc qui pourrait améliorer encore un peu la compression est l'octet déplacement. C'est un déplacement octet alors qu'on écrit 2 octets à l'arrivée. Le déplacement d'un octet fait descendre d'environ 6 lignes maximum. Si un changement a lieu plutôt vers le bas de l'écran (disons ligne 100) alors que le pointeur est en haut, il faut 15 triplets vidéo de déplacement. On pourrait le réduire à 7 (donc 2x la bande passante pour ces cas là) en disant que le déplacement se fait en mots de 16 bits. Il suffit de faire deux ABX consécutifs, ce qui ralentirait le player de 3cycles ==> 63cycles au lieu de 60 (5% plus lent). Avoir une solution qui ralenti tout le temps pour des cas relativement marginaux me gêne un peu.

Autre idée bien plus puissante (et à laquelle je crois plus), est d'utiliser les 2 octets qui suivent le déplacement nul (=retour début écran) comme un offset. On irait plus en début d'écran, mais à début+16bits. Avec ca on atteint n'importe quelle portion d'écran avec un seul triplet. La modif de l'encodeur serait simple (on vire la limite à 255 et si on dépasse 255 on fait un goto). Cela peut se faire sans faire perdre de cycles au player. Reste à voir si ca vaut le coup, car l'intérêt du goto est surtout pour les portion fixe d'image, et maintenant qu'on est en plein-écran, il n'y a pas beaucoup de grosses zones inchangées.

Il faudrait expérimenter... (m'enfin le script actuel est pas mal, sauf pour ce qui est de la vitesse)
Samuel.
A500 Vampire V2+, A1200(030@50mhz/fpu/64mb/cf 8go),
GVP530 (MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.

Avatar du membre
6502man
Messages : 9348
Enregistré le : 12 avr. 2007 22:46
Localisation : VAR
Contact :

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

Message par 6502man » 14 juil. 2015 18:43

J'ai malheureusement que très peu de temps libre mais je suis vos explications avec intérêts :D

Il y a des idées excellentes (couleurs), et je vois que le niveaux et très élevés :D
Modifié en dernier par 6502man le 14 juil. 2015 18:56, modifié 1 fois.
Phil.

www.6502man.com

To bit or not to bit.
1 or 0.

Avatar du membre
yo_fr
Messages : 1329
Enregistré le : 13 août 2009 18:24
Localisation : 78...
Contact :

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

Message par yo_fr » 14 juil. 2015 18:55

Bon je commence à regarder de près ton script, y a du boulot là dedans !!!

j'ai voulu le lancer, mais ne connaissant pas tes outils (le Perl, mais je pense que l'on peu s'y faire) et autre, je me suis donc vite arrêté à l'utilisation des ffmpeg et image magick...avec plein d'erreurs !

Bravo encore pour ce travail et merci des explications !

__sam__
Messages : 4455
Enregistré le : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ » 14 juil. 2015 19:32

J'ai dans mes projets de faire un exe qui fait la même chose que ce que fait le script mais de façon autonome (il embarquerait ffmpeg dans un gros tableau de byte et irait créer un exe dans %TMP%).. Je pensais le faire pendant le pont, mais... grosse flemme sur le canapé (là je regrade cosmos1999 sur ParisPremière... une heure où je suis normalement au travail).

Et puis me remettre au C me motive pas beaucoup: c'est toujours un bordel la compilation avec les versions de bibliothèques, les cochonneries d'OS interdisant plus ou moins les trucs (par exemple sur mon w8 les exe natifs produits par GCC ne peuvent pas se débugger ni s'executer pour des histoires obscures de bibliothèques pas trouvées (ca pue le UAC), etc... ). En fait j'attendais un nouveau PC win7 jeudi dernier pour faire ca, mais celui qui est arrivé vendu pour neuf sous garanti avait un disk dans l'état "Prudence" sous CrystalDisk. Obligé de le retourner à l'envoyeur, ce qui tombe mal avec le pont. Bref... gros découragement. :?

Mais sitôt que la motivation revient, ca devrait pouvoir se faire.

En attendant, sur C64 avec le REU ils ont ca

et sur thomson on a ca

(Je crois qu'on manque singulièrement de couleurs, sinon niveau FPS on tient la route(!) bien entendu :lol: )
Samuel.
A500 Vampire V2+, A1200(030@50mhz/fpu/64mb/cf 8go),
GVP530 (MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.

Daniel
Messages : 11349
Enregistré le : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

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

Message par Daniel » 30 juil. 2015 13:55

Des nouvelles du croquis Arduino.
rappel : dans le vocabulaire Arduino, un croquis (en anglais : sketch) est un programme.

Le croquis est écrit, sauf que les temporisations ne sont pas encore déterminées. Il est très difficile de trouver les valeurs exactes par approximations successives, car en cas de désynchronisation on ne peut pas savoir s'il faut mettre plus ou moins. Alors j'ai commandé un analyseur logique, qui devrait être livré dans 3 ou 4 semaines. Il me permettra de voir exactement ce qui passe sur les lignes de communication, donc de déterminer scientifiquement les temporisations.

Ci-dessous la boucle principale du sketch Arduino :

Code : Tout sélectionner

 uint32_t count = 0;                  // compteur d'octets

 while(count < AudioFileInfo.Size)    // tant qu'il reste des octets dans le fichier
 {
  // attente octet 0xfe de debut de bloc
  while(SD_L0_SpiRecvByte() != 0xfe); 

  //traitement d'un bloc de 512 octets lu sur la carte SD 
  // 36 boucles completes de 14 octets plus
  // 1 boucle partielle de 8 octets (36*14 + 8 = 512)
  i = 37; //compteur de boucles

  while(1)  //boucle d'envoi des 512 octets des données d'un secteur
  { 
   PORTD = SD_L0_SpiRecvByte();   // echantillon de son impair
   while(digitalRead(8) == LOW);  // attente acknowledge Thomson

   PORTD = SD_L0_SpiRecvByte();   // octet video 1
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 2
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 3
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 4
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 5
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 6
   delayMicroseconds(4);          // temporisation

   PORTD = SD_L0_SpiRecvByte();   // echantillon de son pair
   while(digitalRead(8) == HIGH); // attente acknowledge Thomson
   if(--i == 0) break;            // fin du bloc atteinte 

   PORTD = SD_L0_SpiRecvByte();   // octet video 1
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 2
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 3
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 4
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 5
   delayMicroseconds(4);          // temporisation
   PORTD = SD_L0_SpiRecvByte();   // octet video 6
   delayMicroseconds(4);          // temporisation
  } 
  SD_L0_SpiRecvByte();            // premier octet CRC
  SD_L0_SpiRecvByte();            // deuxieme octet CRC
  count += 512;                   // 512 echantillons par boucle 
 }
}
Daniel
L'obstacle augmente mon ardeur.

__sam__
Messages : 4455
Enregistré le : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

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

Message par __sam__ » 30 juil. 2015 14:29

Hum tu pourrais te caler sur les temps du dernier player ASM.
(+0(son), +8, +10, +7, +9, +10, +7, +9)x73, +4 (son, dernier octet de bloc SD), +56(attente crc etc)
Done le plus petit délai est 4µs. Or ca tombe bien, la doc de delayMicroscronds indique
This function works very accurately in the range 3 microseconds and up. We cannot assure that delayMicroseconds will perform precisely for smaller delay-times.
Donc on est dans la zone où le délai est très précis.

Si le +4 du dernier octet de bloc est trop court, il est très facile de le décaler un peu.

Bref: si ca se trouve "en aveugle" avec les timings théoriques ca marchera du 1er coup.

sam (<= l'optimiste)
Samuel.
A500 Vampire V2+, A1200(030@50mhz/fpu/64mb/cf 8go),
GVP530 (MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.

Répondre