Πώς και γιατί δημιουργήσαμε το Whitewater, έναν κωδικοποιητή και πρόγραμμα αναπαραγωγής βίντεο ανοιχτού κώδικα για τον ιστότοπό μας

Το πρόβλημα με το βίντεο για κινητά

Στις αρχές του 2015 ξεκινήσαμε την ανάπτυξη ενός επανασχεδιασμού για τον ιστότοπο του This Also. Καθώς το έργο εξελίχθηκε, έγινε σαφές ότι το βίντεο θα ήταν ένα σημαντικό συστατικό της γλώσσας σχεδιασμού. Είχαμε ένα μεγάλο βίντεο στο παρασκήνιο στην αρχική σελίδα και στο υποσέλιδο, περισσότερα μεγάλα βίντεο κοντά στην κορυφή κάθε σελίδας έργου και πολλά μικρότερα βίντεο στριμωγμένα στις περισσότερες από τις άλλες σελίδες. Όλα αυτά προορίζονταν είτε για αυτόματη αναπαραγωγή είτε για αναπαραγωγή και παύση λόγω κάποιας σεναριακής συμπεριφοράς. Κανένα από αυτά δεν απαιτούσε διεπαφή χρήστη ή άμεση αλληλεπίδραση.

Αυτές οι απαιτήσεις ήταν ήδη περίπλοκες σε επιτραπέζιους υπολογιστές, αλλά στο κινητό ήταν ένας εφιάλτης. Οι κοινές λειτουργίες βίντεο HTML5, όπως η προφόρτωση και η αυτόματη αναπαραγωγή, λείπουν εντελώς σε ορισμένα προγράμματα περιήγησης. Τα API δέσμης ενεργειών είναι περιορισμένα σε σύγκριση με αυτά που είναι διαθέσιμα στην επιφάνεια εργασίας. Το χειρότερο από όλα, το Safari στο iPhone (το πιο δημοφιλές πρόγραμμα περιήγησης για κινητά που επισκέπτεστε τον ιστότοπό μας) δεν επιτρέπει την αναπαραγωγή βίντεο ενσωματωμένη καθόλου (Σημείωση: Αυτός ο περιορισμός αίρεται στο iOS 10, το οποίο έχει οριστεί να κυκλοφορήσει αργότερα αυτό το φθινόπωρο).

Τα GIF δεν ήταν η ιδανική λύση και η απαίτηση από τους χρήστες να ανοίξουν ένα βίντεο σε πλήρη οθόνη δεν είχε νόημα για τα είδη βίντεο που χρησιμοποιούσαμε. Ψάξαμε για μια λύση και ανακαλύψαμε ότι άλλοι άνθρωποι είχαν ήδη επινοήσει κάποιες έξυπνες λύσεις.

Η Apple το έκανε. Το ίδιο και μια χούφτα ειδησεογραφικές τοποθεσίες και άλλα πρακτορεία. Αλλά κανένας από αυτούς (που μπορούσαμε να βρούμε) δεν δημοσίευσε τις λύσεις του ούτε τις έκανε διαθέσιμες στην κοινότητα ως σύνολο.

Έτσι φτιάξαμε τα δικά μας

Σήμερα κυκλοφορούμε το Whitewater Mobile Video. Αποτελείται από έναν κωδικοποιητή γραμμένο σε Python και έναν παίκτη γραμμένο σε Javascript. Και τα δύο είναι δωρεάν στη χρήση και ανοιχτού κώδικα.

Οι οδηγίες για τον τρόπο χρήσης τους είναι λεπτομερείς αλλού, οπότε αυτό που θα κάνουμε εδώ είναι να εξηγήσουμε πώς λειτουργούν ώστε να μπορείτε να αποφασίσετε εάν το Whitewater είναι κατάλληλο για τα δικά σας έργα.

Παραποίηση βίντεο 101

Τα βασικά για την προσομοίωση ενός βίντεο στο πρόγραμμα περιήγησης είναι αρκετά απλά. Εάν σχεδιάζετε μια ακολουθία εικόνων μέσα σε μια ετικέτα ‹καμβάς› αρκετά γρήγορα, αυτό είναι ένα βίντεο. Αλλά αυτό σημαίνει τη δημιουργία αυτών των στοιχείων εικόνας εκ των προτέρων. θα μπορούσατε να αποθηκεύσετε κάθε καρέ ως ξεχωριστή εικόνα, αλλά αυτό δεν είναι ούτε πρακτικό ούτε αποτελεσματικό για βίντεο μεγάλης διάρκειας ή υψηλής ανάλυσης. Για να καταλήξουμε σε μια πιο αποτελεσματική μέθοδο για τη δημιουργία αυτών των εικόνων, υπήρχαν τρεις βασικοί παράγοντες που εξισορροπήσαμε:

  1. Συνολικό μέγεθος αρχείου όλων των στοιχείων σε συνδυασμό, που αποτελεί σημαντικό παράγοντα για κινητές συσκευές με περιορισμένη μνήμη RAM
  2. Ο αριθμός αιτημάτων HTTP που απαιτείται για τη φόρτωση όλων των απαιτούμενων στοιχείων, γεγονός που μπορεί να έχει τεράστιο αντίκτυπο στην απόδοση μιας σελίδας καθώς φορτώνεται
  3. Το ποσό της υπολογιστικής πολυπλοκότητας στο πρόγραμμα περιήγησης που απαιτείται για την επεξεργασία και την αναδημιουργία κάθε καρέ βίντεο

Η Whitewater ευνοεί τη βελτιστοποίηση του 1 & 2 έναντι του 3. Η πολυπλοκότητα του προγράμματος αναπαραγωγής μπορεί να προκαλέσει κάποια επιβράδυνση σε πιο αδύναμες συσκευές, αλλά η φόρτωση μεγάλου μεγέθους ή/και πολλών στοιχείων είναι πιο πιθανή αιτία σφαλμάτων του προγράμματος περιήγησης. Ως αποτέλεσμα, η κωδικοποίηση - και επομένως η επανασυναρμολόγηση - ήταν αναγκαστικά πιο περίπλοκη.

Λοιπόν, πώς λειτουργεί η κωδικοποίηση;

Η δουλειά του κωδικοποιητή είναι διπλή: 1: Αποθηκεύει τα οπτικά στοιχεία που απαιτούνται για την επανασυναρμολόγηση του βίντεο και 2: παρέχει ένα σύνολο οδηγιών για να γίνει αυτό. Επειδή θέλουμε να ελαχιστοποιήσουμε τα μεγέθη των αρχείων και τον αριθμό των στοιχείων, στόχος μας είναι να αποκόψουμε όσο το δυνατόν περισσότερες διπλότυπες οπτικές πληροφορίες και στη συνέχεια να συμπυκνώσουμε ό,τι απομένει. Ο κωδικοποιητής το επιτυγχάνει συγκρίνοντας κάθε πλαίσιο με το πλαίσιο που προηγείται και αποθηκεύοντας μόνο τα μέρη που έχουν αλλάξει.

Στο πρώτο καρέ, ο κωδικοποιητής αποθηκεύει ολόκληρη την εικόνα ως first.jpg. Αυτή είναι η μόνη φορά που ο κωδικοποιητής το κάνει αυτό, αλλά είναι σημαντικό γιατί δίνει στον παίκτη ένα σημείο εκκίνησης για να σχεδιάσει κάθε επόμενο καρέ από πάνω.

Για τα υπόλοιπα καρέ, ο κωδικοποιητής παίρνει τόσο το τρέχον όσο και το προηγούμενο πλαίσιο και τα χωρίζει σε πλέγματα μπλοκ 8x8 pixel. Ο κωδικοποιητής περνά σε κάθε σειρά, μπλοκ προς μπλοκ, συγκρίνοντας το τρέχον μπλοκ με το αντίστοιχο από το προηγούμενο πλαίσιο.

Συγκρίνει αυτά τα μπλοκ για να καθορίσει όχι μόνο αν είναι διαφορετικά, αλλά σε ποιο βαθμό είναι διαφορετικά. Αυτό μας δίνει ευελιξία να ορίσουμε όρια επιτρεπόμενης μεταβλητότητας για να λάβουμε υπόψη πράγματα όπως κόκκους φιλμ ή μικρά τεχνουργήματα συμπίεσης που υπάρχουν στο βίντεο πηγής. Η μέθοδος που χρησιμοποιείται για τη λήψη αυτής της τιμής είναι να βρείτε τη διαφορά μεταξύ δύο μπλοκ και, στη συνέχεια, να υπολογίσετε το Root-Mean-Square (RMS) από το ιστόγραμμα της εικόνας που προκύπτει.

Ο καθορισμός υψηλότερου ορίου RMS ισοδυναμεί με περισσότερη επιείκεια, η οποία μειώνει τον αριθμό των μπλοκ που αποθηκεύονται. Αυτό μπορεί να μειώσει το συνολικό μέγεθος του αρχείου, αλλά πιθανώς εις βάρος της ποιότητας του βίντεο. Ένα χαμηλότερο όριο θα έχει το αντίθετο αποτέλεσμα. Η ρύθμιση του ορίου στο μηδέν λέει στον κωδικοποιητή να αποθηκεύσει κάθε μπλοκ (κάτι που είναι γενικά κακή ιδέα).

Εάν η τιμή RMS είναι πάνω από το όριο μας, ο κωδικοποιητής αποθηκεύει αυτό το μπλοκ σε μια εικόνα που ονομάζεται diffmap. Τα Diffmaps είναι μια σειρά από εικόνες που αποθηκεύουν τα μπλοκ που χρειάζεται ο παίκτης. Οι Diffmaps γεμίζουν από αριστερά προς τα δεξιά, από πάνω προς τα κάτω. Κάθε diffmap για ένα μόνο βίντεο έχει το ίδιο προκαθορισμένο μέγεθος. Όταν κάποιος γεμίσει, αποθηκεύεται και δημιουργείται ένας νέος, κενός diffmap για να φιλοξενήσει περισσότερα μπλοκ.

Η προεπιλεγμένη μορφή εξόδου είναι JPEG. Επειδή η συμπίεση JPEG λειτουργεί σε μπλοκ pixel 8x8, η αποθήκευση των δικών μας μπλοκ στις ίδιες διαστάσεις αποτρέπει τη διάχυση τεχνουργημάτων συμπίεσης JPEG σε άσχετα μέρη του βίντεο. Τόσο η μορφή όσο και το μέγεθος μπλοκ μπορούν να αλλάξουν στις ρυθμίσεις του κωδικοποιητή, αλλά συνιστάται να διατηρείται η προεπιλογή των 8 pixel κατά τη χρήση JPEG.

Υπάρχει ένα ακόμη σημαντικό πράγμα που συμβαίνει όταν τα ζεύγη μπλοκ καθορίζονται διαφορετικά: Αυξάνεται ένας εσωτερικός διαδοχικός μετρητής. Εάν υπάρχουν διαδοχικά διαφορετικά μπλοκ το ένα δίπλα στο άλλο σε ένα πλαίσιο, είναι χρήσιμο να γνωρίζουμε πόσα. Γνωρίζοντας ότι υπάρχουν επτά μπλοκ στη σειρά που πρέπει να αντιγραφούν στον ‹καμβά›, μας επιτρέπει να μειώσουμε τον αριθμό των ενεργειών αντιγραφής-επικόλλησης κατά έξι.

Ο μετρητής συνεχίζει να αυξάνεται μέχρι…

  1. Η διαδοχική αλυσίδα σπάει από ένα ζεύγος μπλοκ που δεν είναι διαφορετικό,
  2. Ο κωδικοποιητής φτάνει στο τέλος της τρέχουσας σειράς στο πλαίσιο βίντεο,
  3. Η τρέχουσα σειρά του diffmap που χρησιμοποιείται γεμίζει ή
  4. Ολόκληρος ο diffmap γεμίζει.

Σε όλες αυτές τις περιπτώσεις, ο μετρητής μηδενίζεται και αποθηκεύονται κάποια μεταδεδομένα τα οποία θα καθοδηγήσουν το πρόγραμμα αναπαραγωγής για το πώς να συναρμολογήσει ξανά ένα βίντεο από τα diffmaps. Αυτά τα μεταδεδομένα είναι μια συμβολοσειρά 5 χαρακτήρων που αντιπροσωπεύει δύο πράγματα: την τοποθεσία σε ένα πλαίσιο από την οποία προέρχεται μια ομάδα μπλοκ και πόσα διαδοχικά μπλοκ περιέχει αυτή η ομάδα.

Όταν ο κωδικοποιητής έσπασε κάθε πλαίσιο σε ένα πλέγμα, αρίθμησε κάθε κελί διαδοχικά. Η τοποθεσία που αποθηκεύουμε είναι ο αριθμός κελιού από το πρώτο μπλοκ μιας διαδοχικής ομάδας μπλοκ, που έχει μετατραπεί στη βάση 64 και συμπληρώνεται με 3 ψηφία σε μήκος. Ο αριθμός των διαδοχικών μπλοκ μετατρέπεται ομοίως στη βάση 64 και στη συνέχεια συμπληρώνεται σε 2 ψηφία. Και οι δύο μετατρέπονται σε χορδές και στη συνέχεια συνενώνονται για να δημιουργήσουν μία χορδή μήκους 5 χαρακτήρων. Αφού ο κωδικοποιητής ολοκληρώσει ένα ολόκληρο πλαίσιο, καθένα από αυτά συνενώνεται σε μια μεγάλη συμβολοσειρά και προσαρτάται σε έναν πίνακα που ονομάζεται "πλαίσια".

Αυτή η διαδικασία συνεχίζεται μέχρι το τελικό καρέ. Μόλις δημιουργηθούν όλες οι εικόνες, ο κωδικοποιητής αποθηκεύει τα μεταδεδομένα σε ένα αρχείο JSON που ονομάζεται manifest.json. Η συστοιχία καρέ γίνεται μέρος αυτού, μαζί με πληροφορίες σχετικά με το βίντεο προέλευσης (διαστάσεις, FPS, αριθμός καρέ), τον αριθμό των diffmaps που δημιουργήθηκαν και τη μορφή τους, το μέγεθος του μπλοκ που χρησιμοποιείται, τις διαστάσεις του diffmap και την έκδοση του κωδικοποιητή που χρησιμοποιείται.

{
    "sourceGrid": 256, 
    "version": 1, 
    "framesPerSecond": 30.0, 
    "format": "JPEG", 
    "frames": […], 
    "blockSize": 8, 
    "videoHeight": 720, 
    "videoWidth": 720, 
    "frameCount": 120, 
    "imagesRequired": 1
}

Η τελική έξοδος είναι ένας κατάλογος αρχείων. Κατά την προετοιμασία ενός βίντεο με τη συσκευή αναπαραγωγής, δείχνετε αυτόν τον φάκελο ως πηγή βίντεο.

/name_of_video
  ├── diff_001.jpg
  ├── diff_002.jpg
  ├── diff_003.jpg
  ├── first.jpg
  └── manifest.json

Τα αποτελέσματα μπορεί να διαφέρουν

Τώρα που καταλαβαίνουμε την ίδια τη διαδικασία, κατανοούμε καλύτερα γιατί ίσως θέλουμε να ενημερώσουμε ορισμένες από τις ρυθμίσεις κωδικοποιητή. Μπορεί να θέλετε να μειώσετε τον αριθμό των στοιχείων αυξάνοντας τις διαστάσεις των χαρτών diffmaps ή αυξάνοντας το όριο RMS. Ή μπορεί να διαπιστώσετε ότι το βίντεό σας χρησιμοποιεί μόνο ένα diffmap και δεν το γεμίζει πλήρως, επομένως μπορεί να είναι επιθυμητή η μείωση των διαστάσεων. Ανάλογα με το είδος του βίντεο που κωδικοποιείτε, μπορεί ακόμη και να διαπιστώσετε ότι η χρήση GIF ή PNG ως τύπου αρχείου εξόδου εξοικονομεί χώρο σε JPEG.

Το πρόγραμμα αναπαραγωγής βίντεο

Μπορείτε να δημιουργήσετε ένα βίντεο Whitewater στον ιστότοπό σας αρχικοποιώντας μια παρουσία της κλάσης Javascript Whitewater() με μια διαδρομή προς έναν φάκελο βίντεο που δημιουργήθηκε από τον κωδικοποιητή. Η φάση προφόρτωσης ξεκινά και το πρόγραμμα αναπαραγωγής κάνει ένα αίτημα για manifest.json ώστε να μπορεί να επεξεργαστεί τις πληροφορίες που χρειάζονται για να συνεχίσει. Αυτό φορτώνει όλες τις απαραίτητες εικόνες και χρησιμοποιεί το first.jpg για να σχεδιάσει το πρώτο πλαίσιο στην ετικέτα ‹canvas›. Μόλις γίνει αυτό, το πρόγραμμα αναπαραγωγής αποστέλλει ένα συμβάν DOM "whitewaterload» στον ‹καμβά› για να δείξει ότι το βίντεο είναι έτοιμο για αναπαραγωγή.

Από εδώ, εναπόκειται στον χρήστη ή στον προγραμματιστή να εκτελέσει τη συνάρτηση play(). Αυτή η συνάρτηση χρησιμοποιεί requestAnimationFrame() για να χειριστεί τη λογική σχεδίασης κάθε καρέ και να διαχειριστεί το διάστημα μεταξύ των καρέ. Σε πολλές περιπτώσεις, το requestAnimationFrame ενεργοποιείται γρηγορότερα από ό,τι χρειαζόμαστε, επομένως το FPS χρησιμοποιείται για τον υπολογισμό του ελάχιστου χρόνου που πρέπει να περάσει πριν από τη σχεδίαση του επόμενου καρέ.

Ο παίκτης εργάζεται τοποθετώντας τις διαφορές του επόμενου καρέ πάνω από το προηγούμενο. Με αυτόν τον τρόπο, το βίντεο δημιουργείται από ένα επίπεδο κάθε φορά.

Για κάθε πλαίσιο, παίρνει ό,τι έχει αλλάξει και το βάφει ξανά πάνω από το προηγούμενο πλαίσιο. Με αυτόν τον τρόπο, το βίντεο δημιουργείται ένα επίπεδο κάθε φορά και χρειάζεται αυτό το πρώτο καρέ για να χρησιμεύσει ως βάση.

Για κάθε πλαίσιο, μία γραμμή του πίνακα πλαισίων από το manifest.json αποκωδικοποιείται στη βάση 10 και χρησιμοποιείται για να αρπάξει τα απαραίτητα μπλοκ από την τρέχουσα diffmap. Αντί να σχεδιάζονται αυτά τα μπλοκ απευθείας στον «καμβά», πρώτα προσυναρμολογούνται σε μια ενιαία εικόνα στη μνήμη και στη συνέχεια σχεδιάζονται. Με αυτόν τον τρόπο, κάθε πλαίσιο αποδίδεται ως σύνολο, αφαιρώντας την πιθανότητα ένα πλαίσιο να αποδίδεται μόνο μερικώς.

Το τελικό αποτέλεσμα

Στόχος μας για αυτό το έργο ήταν να αναπτύξουμε μια λύση για ορισμένους από τους περιορισμούς του βίντεο HTML5. Το σύστημα Whitewater παρέχει στους προγραμματιστές προφόρτωση, ενσωματωμένη αναπαραγωγή, ορισμένα συμβάντα DOM και ένα σύνολο API δέσμης ενεργειών που δεν εξαρτώνται από την ενέργεια του χρήστη για την ενεργοποίηση.

Θα πρέπει να σημειωθεί ότι το Whitewater δεν προορίζεται να αντικαταστήσει όλα τα βίντεο στο κινητό. Δεν υπάρχει υποστήριξη για ήχο. Βρήκαμε επίσης ότι σε πολλές περιπτώσεις το συνολικό μέγεθος αρχείου όλων των απαραίτητων στοιχείων θα είναι μεγαλύτερο από ένα κανονικό αρχείο βίντεο. Στην πραγματικότητα, για βίντεο μεγάλης διάρκειας, το μέγεθος του αρχείου μπορεί να είναι πολύ μεγάλο για να είναι χρήσιμο. Ωστόσο, στις περιπτώσεις στις οποίες πρέπει να ξεπεράσετε τους περιορισμούς των προγραμμάτων περιήγησης για κινητά και τα μειονεκτήματα του Whitewater δεν αποτελούν πρόβλημα, αυτό το σύστημα σας παρέχει μια επιλογή.

Τα σχόλια, τα αιτήματα για λειτουργίες και η αναφορά προβλημάτων είναι όλα ευπρόσδεκτα. Εάν βρείτε μια χρήση για το Whitewater στον δικό σας ιστότοπο, ενημερώστε μας. Θα θέλαμε να το δούμε σε χρήση.

Σύνδεσμοι έργου