<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: AFCM</title>
    <description>The latest articles on Forem by AFCM (@afcm).</description>
    <link>https://forem.com/afcm</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F646012%2Fd4701761-1a3c-4de2-966a-65ffdad9df73.png</url>
      <title>Forem: AFCM</title>
      <link>https://forem.com/afcm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/afcm"/>
    <language>en</language>
    <item>
      <title>Displaying a video on a ESP32 powered SSD1306 OLED screen</title>
      <dc:creator>AFCM</dc:creator>
      <pubDate>Wed, 17 Apr 2024 11:43:05 +0000</pubDate>
      <link>https://forem.com/afcm/putting-a-video-on-a-esp32-powered-ssd1306-oled-screen-30ka</link>
      <guid>https://forem.com/afcm/putting-a-video-on-a-esp32-powered-ssd1306-oled-screen-30ka</guid>
      <description>&lt;p&gt;In this exemple, I will be using a SSD1306 OLED display driven by the ESP32-WROOM-32.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the video
&lt;/h2&gt;

&lt;p&gt;First, you need a video to display.&lt;/p&gt;

&lt;p&gt;In my case, I wanted to display a video available on YouTube, so I used the &lt;a href="https://nickvision.org/parabolic.html"&gt;Parabolic&lt;/a&gt; GUI for the &lt;a href="https://github.com/yt-dlp/yt-dlp"&gt;yt-dlp&lt;/a&gt; project, which allows downloading videos from YouTube and other video streaming services.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IMPORTANT:&lt;/p&gt;

&lt;p&gt;Please keep in mind you may not have the right to include a video in your project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want you can also use directly the &lt;code&gt;yt-dlp&lt;/code&gt; CLI tool after following the &lt;a href="https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#installation"&gt;official documentation&lt;/a&gt; to install it. This should get you a video file inside the folder you have run the command from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yt-dlp https://www.youtube.com/watch?v&lt;span class="o"&gt;=&lt;/span&gt;dQw4w9WgXcQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Of course, you can use a video from any other source you want.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting usable video data
&lt;/h2&gt;

&lt;p&gt;For the next step, we need to get the video as a sequence of images at a &lt;code&gt;128x64&lt;/code&gt; resolution.&lt;/p&gt;

&lt;p&gt;To do this, I will be using the &lt;a href="https://ffmpeg.org"&gt;FFmpeg&lt;/a&gt; media processing toolkit. You can install it on your system following the &lt;a href="https://ffmpeg.org/download.html"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can now run the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; ./My_Video_File.mp4 &lt;span class="nt"&gt;-s&lt;/span&gt; 128x64 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="nv"&gt;fps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15 &lt;span class="nt"&gt;-ss&lt;/span&gt; 0 &lt;span class="nt"&gt;-t&lt;/span&gt; 60 ./My_Video_File_Output/out%4d.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can explain the command line like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-s 128x64&lt;/code&gt; Specify the wanted resolution&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-vf fps=15&lt;/code&gt; Specify the targeted FPS. Usually, videos are at 25FPS or more, but for such a small screen and limited memory, 15FPS will be enough in most cases.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-ss 0&lt;/code&gt; Used to limit the amount of video extracted. Specify the start timestamp, can use several different formats, notably number of seconds or &lt;code&gt;HH:MM:SS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-t 60&lt;/code&gt; Used to limit the amount of video extracted. Specify the duration of the video taken after the start timestamp, can use several different formats, notably number of seconds or &lt;code&gt;HH:MM:SS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./My_Video_File_Output/out%4d.png&lt;/code&gt; Define where the PNG sequence will be. In that case, they will be named &lt;code&gt;outXXXX.png&lt;/code&gt; where &lt;code&gt;XXXX&lt;/code&gt; is the frame ID. It is very important you do not overflow the specified number of digits (here 4), or the frames will not be ordered in the next step.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Getting the generated C code
&lt;/h2&gt;

&lt;p&gt;To generate C code, I will be using the &lt;a href="https://javl.github.io/image2cpp"&gt;image2cpp&lt;/a&gt; website, which runs entirely in your browser without a server. You can also close the project source code and run it locally.&lt;/p&gt;

&lt;p&gt;In the first step, you can select all images in your sequence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CAUTION:&lt;/p&gt;

&lt;p&gt;Depending on how many images you have, crashes may happen.&lt;br&gt;
In my testing, Firefox crashed when I uploaded 6000 images. Chrome didn't crash, but on my Fedora 39 installation, the &lt;code&gt;xdg-desktop-portal&lt;/code&gt; process used by Chrome to select the files wasn't stopping after the files had been uploaded, while taking a huge amount of RAM, requiring me to kill the process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The image importing process is quite slow and uses a lot of RAM since it's done all at once, which is a downside of the project being web-based. Resizing the video to 128x64 must be done by FFmpeg, even if the website can resize images because it will take too much RAM if you use the website.&lt;/p&gt;

&lt;p&gt;After the importation finishes, you will go to the image settings step.&lt;/p&gt;

&lt;p&gt;You need to set the background color to white, but the most important setting is the dithering.&lt;/p&gt;

&lt;p&gt;IMO &lt;code&gt;Floyd-Steinberg&lt;/code&gt; was the best-looking method in my testing.&lt;/p&gt;

&lt;p&gt;You can then generate Arduino code that will look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;// 'outXXXX', 128x64px&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;epd_bitmap_outXXXX&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;PROGMEM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xaa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 1234)&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;epd_bitmap_allArray_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;epd_bitmap_allArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;epd_bitmap_outXXXX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;epd_bitmap_allArray&lt;/code&gt; array will be used to display the video, here it's important to not overflow the digits in the file names, or the images will not be ordered correctly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Displaying the video
&lt;/h2&gt;

&lt;p&gt;To display the video in a loop using the following libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.arduino.cc/reference/en/libraries/adafruit-gfx-library"&gt;Adafruit GFX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.arduino.cc/reference/en/libraries/adafruit-ssd1306"&gt;Adafruit SSD1306&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;SPI.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Adafruit_GFX.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Adafruit_SSD1306.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cp"&gt;#define OLED_RESET 0x3C
&lt;/span&gt;&lt;span class="n"&gt;Adafruit_SSD1306&lt;/span&gt; &lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Wire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Used to know which frame to display&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// initialize with the I2C addr 0x3D (for the 128x64)&lt;/span&gt;
  &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSD1306_SWITCHCAPVCC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OLED_RESET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clearDisplay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Make sure the display is cleared&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Clear the display&lt;/span&gt;
  &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clearDisplay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Draw the t frame from the array&lt;/span&gt;
  &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;epd_bitmap_allArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WHITE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Wait for 1000ms/15FPS&lt;/span&gt;
  &lt;span class="c1"&gt;// If you changed the number of FPS in FFmpeg output, you need to update this for the video to play in real-time&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Which frame is next, back to 0 at the end of the array &lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;epd_bitmap_allArray_LEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmv04m4m0x4ozgxqythd4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmv04m4m0x4ozgxqythd4.png" alt="IMG_20240201_180928" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq27ijv2r84rdl3njyk7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq27ijv2r84rdl3njyk7x.png" alt="ESP32 Cabling" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wokwi diagram:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



</description>
      <category>iot</category>
      <category>esp32</category>
      <category>arduino</category>
      <category>ffmpeg</category>
    </item>
  </channel>
</rss>
