Windows & Esp32基于 libjpeg-9e 编解码库的视频播放器
Posted _npc_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows & Esp32基于 libjpeg-9e 编解码库的视频播放器相关的知识,希望对你有一定的参考价值。
目录
🔍 如理解有误,望不吝指正。
🌎 仓库:https://gitee.com/npc-gitee/libjpeg_avi_player.git 🚀
一、音视频基础
编码的主要目的是减小文件大小,从而减小存储空间与传输带宽,从某种意义上讲就是一种压缩行为,像通过Zip软件对文件进行打包压缩行为类似。在压缩过程中,分为有损压缩和无损压缩。
- 有损压缩:丢弃不敏感信息。
- 无损压缩:保留全部信息。
💡 对于音频数据,按照扬声器的底层工作逻辑,通过改变电压达到改变振动频率,从而发出不同声音。PCM
格式作为一种非编码格式,可通过将 PCM
的数据经过DAC
即可输出音频信号。对于 MP3
等经过编码的音频文件,需要通过解码后,将其转换为PCM数据后,然后输出音频信号。
💡 对于图像数据,按照显示器的底层工作逻辑,显示器由多个像素组成,一个像素由三个发光源(RGB
)组合成不同的颜色。RGB
数据格式写入 GRAM
中即可显示图像。RGB
只是图像显示的一种颜色空间,还有YUV
、HSV
等颜色空间,颜色空间对应的是一个数学概念叫做向量空间,一个非线性相关的向量组,就可以当成线性空间一个基,可通过基来描述空间中任何一个向量,颜色空间与向量空间是一个与之类似的场景。 🚀 这些颜色空间之间可以相互转换,比如 YUV
转 RGB
,RGB
转 YUV
。
对于 jpeg 等经过编码的图像文件,需要经过解码后,转换为相应的颜色空间的数据,然后依据显示器的显示原理,将其转换为 RGB 颜色空间,完成显示任务。
💡 对于视频数据,本质上是多帧图像和音频数据的集合,图像的颜色空间的概念与图像中一样。多张图片在短时间内切换,给人呈现出一种动画的效果。图像的编码针对于帧内的数据进行,而视频的编码除了在帧内完成编码外,还会在帧间完成编码工作。图像所做的是 空间冗余,视频所做的是 空间冗余 和 时间冗余。
参考:
[1]: 音视频基础知识、YUV、H264 🚀
1.1、图像编码
bmp
格式没有压缩像素格式,存储在文件中时先有文件头、再图像头、后面就都是像素数据了,上下颠倒存储。bmp格式也是可以压缩,bmp格式也可以有颜色板。JPEG
是一种静止图像的压缩标准,属于有损压缩,它是一种标准的帧内压缩编码方式。jpeg 编码是通过将RGB
数据转换为YUV
的色彩空间,然后进行 压缩 的。M-JPEG
源于JPEG压缩技术,是一种简单的帧内JPEG压缩,但是由于这种压缩本身技术限制, 无法做到大比例压缩。png
是一种无损压缩格式。gif
可以保存多帧图像。gif中有个参数可以控制图片变化的快慢。
所谓颜色看板,就是在文件中创建一个颜色索引,图中像素用到的某个颜色则文件中存储的为索引值,而不是之前的 RGB 数值。
1.2、视频编码
MPEG
是压缩运动图像及其伴音的视音频编码标准,它采用了帧间压缩,仅存储连续帧之间有差别的地方 ,从而达到较大的压缩比。H264
视频帧编码和jpeg的编码逻辑一样,空间冗余,在其基础上添加了 IPB 帧的逻辑,在帧与帧之间做差运算,时间冗余。
视频文件格式: windows设置后缀名的目的是让相应的应用程序来打开相应的文件。可以随意更改后缀名,不会更改文件的内部数据格式。
视频封装格式: 一种存储视频信息的容器。视频封装格式不同,也不会影响视频数据,主要是一种对视频数据的组合。视频封装格式与视频文件格式一一对应。
视频编码方式: 对多帧图像数据进行压缩。
参考:
[1]: 图像和视频的主要格式与编码格式 🚀
[2]: H264系列(7):H.264与MPEG4区别 🚀
[3]: JPEG编码和H264 🚀
[4]: 视频编码与封装方式(整合) 🚀
1.3、AVI 文件结构
AVI 其音频数据采用 16
位线性 PCM
格式(未压缩),而视频数据,则采用MJPG
编码方式。
MJPG
是 MJPEG
的缩写,还可以文件格式扩展名。
MJPEG
不像 MPEG
,不使用帧间编码。
MJPEG
的工作是将 RGB 格式的影像转换成 YCrCB 格式,目的是为了减少档案大小,一般约可减少 1/3 ~ 1/2 左右。
MJPEG
是视频,就是由系列 jpg 图片组成的视频。
AVI 视频封装格式,采用的是 RIFF
文件结构方式。构造 RIFF 文件的基本单元叫做数据块(Chunk),每个数据块至少包含 3
个部分,
- 4字节的数据块标记(又称数据块的ID,Chunk ID)
- 数据块大小
- 4 字节的形式类型或者列表类型( ID)(RIFF块和LIST块独有)
- 数据
整个 RIFF 文件可以看成一个数据块,其数据 ID 为 RIFF,称为 RIFF 块。一个 RIFF 文件中只允许存在一个 RIFF 块
。RIFF 块中包含一系列的子块,其中有一种子块的 ID 为 “LIST”,称为 LIST 块
。LIST 块包含一系列的子块,但是 LIST 块外的其他所有子块都不能再包含子块。
RIFF 块和 LIST 块比普通的数据块多了一个 形式类型(Form Type) 和 列表类型(List Type) 的数据域。 AVI 的 RIFF 块的形式类型是 AVI
(注意:‘AVI ’,是带了一个空格的),它一般包含 3 个子块:
- 信息块(HeaderList):一个 ID 为
“hdrl”
的 LIST 块,定义 AVI 文件的数据格式。 - 数据块(MovieList): 一个 ID 为
“movi”
的 LIST 块,定义 AVI 文件的音视频序列数据格式。 - 索引块(Index Chunk):一个 ID 为
“idxl”
的子块,定义 “movi” LIST 块的索引数据。
AVI 音视频文件的二进制内容可通过 WinHex
软件查看:
将上述数据按 AVI 文件结构划分:
AVI 文件结构图:
- avih 块,用于记录 AVI 的全局信息,比如数据流的数量,视频图像的宽度和高度等信息,
AVIH_HEADER
为数据保存结构。 - strl 子列表,文件中有多少种数据流(即前面的 Streams),就有多少个 strl 子列表。
- 每个 strl 中,至少包含一个
strh(Stream Header)
和一个strf(Stream Formate)
,可选strn(Stream Name)。 - strf 的数据类型与strh相关,如果strh为视频流,那么 strf 对应视频流的结构保存信息。
- strl 子列表出现的顺序与后面数据块中的编号对应,假设 strl 子列表第一个是视频流,那么后面数据块视频帧的编号为
00dc
,第二个是音频流,那么编号为01wb
。
- 每个 strl 中,至少包含一个
- 数据块,图像数据和音频数据交错存储在数据块中,
##dc/##wb
后面接当前一帧数据的大小值(不包含标准类型值和数据大小值,所以偏移需要加8),如果这个值为奇数,则加1,在读数据的时候一般一次性读完一帧,方便解码。 - 索引块, 为 AVI 文件中每一个媒体数据块进行索引,并且记录它们在文件中的偏移(可能相对于‘movi’列表,也可能相对于 AVI 文件开头)。
二、TF卡基础
控制器对 SD 卡进行读写通信操作一般有两种通信接口可选,一种是 SPI 接口,另外一种就是 SDIO 接口。
根据容量,SD 卡可划分为SDSC(<2GB)
、SDHC(2~32GB)
、SDXC(32GB~2TB)
。
当前 SD 协议提供的 SD 卡规范版本最新是 8.0 版本(2020年),但是有些芯片(如stm32f4xx系列)控制器只支持 SD 卡规范版本 2.0,即只支持标准容量 SD 和高容量 SDHC 标准卡,不支持超大容量 SDXC 标准卡。
一张 SD 卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器 5 个部分。
- 存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;
- 电源检测单元保证 SD 卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;
- 卡及接口控制单元控制 SD 卡的运行状态,它包括有 8 个寄存器;
- 接口驱动器控制 SD 卡引脚的输入输出。
转载自:[野火]STM32 库开发实战指南
三、Windows上播放音视频
libjpeg
是一个完全用 C 语言编写的库,包含功能有 JPEG 解码、JPEG 编码 和其它的JPEG功能的实现,这里使用的版本为 jpeg-9e
。
开发平台:vs2019
3.1、在 Windows 下使用 vs2019 编译 libjpeg 库
win10
系统下没有这个文件,win7
系统可能会有。这里是在网站上下的文件。
win32.mdk
:https://gitee.com/guangpengz/ms-sdk?_from=gitee_search 🚀
cmd
切换目录:Windows-cmd切换目录
1、下载 libjpeg
,这里使用 jpegsr9e.zip
,官网:http://www.ijg.org/ 🚀
2、解压源码
3、进入解压后的目录,找到 makefile.vc
文件,修改下面语句:
!include <win32.mak>
所在行,并将 win32.mak
替换为实际位置, 我这边修改后的值为,修改为:
!include <C:\\Users\\user\\Desktop\\X\\ms-sdk-master\\win32.mak>
5、生成 sln
工程文件:在电脑的 “开始” 菜单中,找到 vs2019
的命令行工具,并打开,这里是:
x86 Native Tools Command Prompt for VS 2019
6、切换到 jpegsr9e
目录下,然后输入下面命令:
NMAKE /f makefile.vc setup-v16
7、打开 jpeg.sln
文件,然后在 vs2019
中,选择 Win32
,之后点击生成解决方案,最后就会在 jpegsr9e
文件夹下多一个 Release
文件夹,里面有 jpeg.lib
静态库。
参考:
[1]: 在Windows下使用vs2019编译libjpeg库(静态库与动态库) 🚀
[2]: Windows10下利用VS2022编译JpegLib 🚀
3.2、创建 libjpeg 解码项目
创建项目过程同上一篇博客 🚀 的创建方式相似,这里创建项目的名称为:libjpeg_for_windows
1、头文件路径添加
libjpeg_for_windows 右键 ——> 属性 ——> C/C++ ——> 常规 ——> 附加库目录中输入
.\\lib\\jpeg-9e
——> 确定
2、库文件路径添加
libjpeg_for_windows 右键 ——> 属性 ——> 链接器 ——> 常规 ——> 附加包含目录中输入
.\\lib\\jpeg-9e\\Release\\Win32
——> 确定
3、附加依赖项添加
libjpeg_for_windows 右键 ——> 属性 ——> 链接器 ——> 输入 ——> 附加依赖项:点击右边向下箭头’ v ’ ——> 编辑 ——> 输入:jpeg.lib (之前编译的静态库) ——> 确定 ——> 应用 ——> 确定。
将 jpegsr9e
文件夹下的 example.c
添加到项目中,生成解决方案。这时候就会出现这些报错:
LNK2001 无法解析的外部符号 _image_buffer
LNK2001 无法解析的外部符号 _image_height
LNK2001 无法解析的外部符号 _image_width
LNK2001 无法解析的外部符号 _main
LNK2001 无法解析的外部符号 _put_scanline_someplace
这些符号都是没有定义的,需要通过外部链接,所以这里直接在 example.c
文件中定义就可以了。添加代码如下:
JSAMPLE* image_buffer;
int image_height;
int image_width;
int put_scanline_someplace(JSAMPROW buffer, int row_stride)
/* 根据需要自己完善 */
int main()
/* 根据需要自己完善 */
jpeglib.h
头文件依赖于stdio.h
和string.h
头文件
错误:C4996 fopen(‘fscanf’、strcmp):This function or variable may be unsafe
解决:在程序最前面加#define _CRT_SECURE_NO_WARNINGS
,编译还是报错,所以这里通过 右键项目 —> 预处理器 —> 预处理器定义 —> 点击 ‘v’ —> 编辑 —> 添加 _CRT_SECURE_NO_WARNINGS —> 确定,应用。
参考:
[1]: LIBJPEG 安装编译,读取jpeg图像数据 🚀
3.3、libjpeg 中 example.c 功能解析
/* example1.c */
#include <stdio.h>
#include "jpeglib.h"
#include <setjmp.h>
JSAMPLE* image_buffer; /* Points to large array of R,G,B-order data */
int image_height; /* Number of rows in image */
int image_width; /* Number of columns in image */
/* 这里主要测试解码,所以这段代码注释了 */
//GLOBAL(void) write_JPEG_file(char *filename, int quality)
//...
struct my_error_mgr
struct jpeg_error_mgr pub; /* "public" fields */
jmp_buf setjmp_buffer; /* for return to caller */
;
typedef struct my_error_mgr* my_error_ptr;
METHODDEF(void)
my_error_exit(j_common_ptr cinfo)
/* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
my_error_ptr myerr = (my_error_ptr)cinfo->err;
/* Always display the message. */
/* We could postpone this until after returning, if we chose. */
(*cinfo->err->output_message) (cinfo);
/* Return control to the setjmp point */
longjmp(myerr->setjmp_buffer, 1);
GLOBAL(int) read_JPEG_file(char* filename)
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
FILE* infile; /* source file */
JSAMPARRAY buffer; /* Output row buffer */ // unsigned char ** buffer;
int row_stride; /* physical row width in output buffer */
if ((infile = fopen(filename, "rb")) == NULL)
fprintf(stderr, "can't open %s\\n", filename);
return 0;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
if (setjmp(jerr.setjmp_buffer))
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return 0;
/* Now we can initialize the JPEG decompression object. */
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
(void)jpeg_read_header(&cinfo, TRUE);
printf("image_width = %d\\n", cinfo.image_width);
printf("image_height = %d\\n", cinfo.image_height);
printf("num_components = %d\\n", cinfo.num_components);
printf("output_width = %d\\n", cinfo.output_width);
printf("output_components = %d\\n", cinfo.output_components);
cinfo.out_color_space = JCS_RGB; // 以 RGB 为结果输出
(void)jpeg_start_decompress(&cinfo);
row_stride = cinfo.output_width * cinfo.output_components;
printf("output_width2 = %d\\n", cinfo.output_width);
printf("output_components2 = %d\\n", cinfo.output_components);
// 计算buffer大小并申请相应空间
buffer = (*cinfo.mem->alloc_sarray)
((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
while (cinfo.output_scanline < cinfo.output_height)
(void)jpeg_read_scanlines(&cinfo, buffer, 1);
put_scanline_someplace(buffer[0], row_stride);
(void)jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return 1;
int put_scanline_someplace(JSAMPROW buffer, int row_stride)
int i = 0;
for (; i < row_stride; i++)
printf("0x%x ", buffer[i]);
printf("\\nend\\n");
int main()
read_JPEG_file("C:\\\\Users\\\\xxx\\\\Desktop\\\\pic.jpg");
return 0;
这里对 example.c
文件的框架没有改动,只是在程序中加入一些打印函数,测试输出结果。
这里加入了一条语句:
cinfo.out_color_space = JCS_RGB; // 以 RGB 为结果输出
程序的目的:
- 1、查看程序是否能正确获得图片信息。
- 2、row_stride 变量 = 图像宽度 x RGB(3字节)。
- 3、jpeg_read_scanlines 函数是一行一行解码,且解码后的 RGB 数据放到 buffer 中(第三个参数为1,设定一次解码行数)。
这里通过 PS
软件构造了一张 6x6
的红色图片。(如何构造请看下文 👀)
从打印的结果可知,正好是 6 行 6 列,且每个像素的 RGB 为(0xFE, 0x0,0x0),如果将数据发送给屏幕,那么就能显示一张完整的图片了。
参考:
[1]: libjpeg库的简单使用,rgb565与rgb888互转,以及色块的寻找 🚀
3.4、SDL2 库配置与使用
对于 RGB
数据在 Windows 系统显示方面,这里采用 SDL2
开源库。
SDL2
安装教程参考:VS2019配置SDL2库 🚀
开发基本流程:
- 【初始化SDL系统】
SDL_int()
- 【创建窗口】
SDL_CreateWindows()
- 【创建渲染器】
SDL_CreateRenderer()
- 【渲染器中创建一个材质】
SDL_CreateTexture()
- 【申请待显示RGB图像空间】
- 【为图像数据赋值】
- 【将内存中的RGB数据写入材质】
SDL_UpdateTexture()
- 【清理渲染区(清理屏幕)】
SDL_RenderClear()
- 【设定渲染的目标区域】
- 【复制材质到渲染器对象】
SDL_RenderCopy()
- 【执行渲染操作】
SDL_RenderPresent()
- 【销毁渲染器】
SDL_DestroyRenderer()
- 【销毁窗口】
SDL_DestroyWindow()
- 【退出SDL系统】
SDL_Quit()
#include <stdio.h>
#include "jpeglib.h"
#include <setjmp.h>
#include <iostream>
#include <SDL.h>
using namespace std;
#pragma comment(lib, "SDL2.lib")
// SDL 库里面定义了一个宏 main, 这里给取消掉,否则会与main 函数名冲突,导致编译错误
#undef main
#define __DEBUG__ 0
JSAMPLE* image_buffer; /* Points to large array of R,G,B-order data */
int image_height; /* Number of rows in image */
int image_width; /* Number of columns in image */
int Windows_Width = 0;
int Windows_Height = 0;
SDL_Window* screen = NULL;
SDL_Renderer* render = NULL;
SDL_Texture* texture = NULL;
unsigned char* r = NULL;
int put_scanline_someplace(JSAMPROW buffer, int row_stride);
int Image_Init(void);
int Image_Display(void);
//GLOBAL(void) write_JPEG_file(char* filename, int quality)
//...
struct my_error_mgr
struct jpeg_error_mgr pub; /* "public" fields */
jmp_buf setjmp_buffer; /* for return to caller */
;
typedef struct my_error_mgr* my_error_ptr;
METHODDEF(void)
my_error_exit(j_common_ptr cinfo)
my_error_ptr myerr = (my_error_ptr)cinfo->err;
(*cinfo->err->output_message) (cinfo);
longjmp(myerr->setjmp_buffer, 1);
GLOBAL(int)
read_JPEG_file(char* filename)
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
FILE* infile; /* source file */
JSAMPARRAY buffer; /* Output row buffer */ // unsigned char ** buffer;
int row_stride; /* physical row width in output buffer */
if ((infile = fopen(filename, "rb")) == NULL)
fprintf(stderr, "can't open %s\\n", filename);
return 0;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
/* Establish the setjmp return context for my_error_exit to use. */
if (setjmp(jerr.setjmp_buffer))
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return 0;
/* Now we can initialize the JPEG decompression object. */
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
(void)jpeg_read_header(&cinfo, TRUE);
#if __DEBUG__
printf("image_width = %d\\n", cinfo.image_width);
printf("image_height = %d\\n", cinfo.image_height);
printf("num_components = %d\\n", cinfo.num_components);
printf("output_width = %d\\n", cinfo.output_width);
printf("output_components = %d\\n", cinfo.output_components);
#endif
cinfo.out_color_space = JCS_RGB;
// 显示初始化
// 定义图像的宽高
Windows_Width = cinfo.image_width;
Windows_Height = cinfo.image_height;
// 显示初始化
if (Image_Init() != 0)
printf("Image Init error\\n");
return -1;
// 准备一幅w*h的红色RGB图像数据
shared_ptr<unsigned char> rgb(new unsigned char[Windows_Width * Windows_Height * 4]); // 乘以4是因为像素格式已指定为ARGB888,单个像素点占4字节
r = rgb.get();
(void)jpeg_start_decompress(&cinfo);
row_stride = cinfo.output_width * cinfo.output_components;
#if __DEBUG__
printf("output_width2 = %d\\n", cinfo.output_width);
printf("output_components2 = %d\\n", cinfo.output_components);
#endif
// 计算buffer大小并申请相应空间
buffer = (*cinfo.mem->alloc_sarray)
((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
int j = 0;
int lineR = 0; // 每一行R分量的起始位置
while (cinfo.output_scanline < cinfo.output_height)
int i = 0;
(void)jpeg_read_scanlines(&cinfo, buffer, 1);
/* Assume put_scanline_someplace wants a pointer and sample count. */
//put_scanline_someplace(buffer[0], row_stride);
// 为上述图像数据赋值
for (int k = 0; k < Windows_Width * 4; k += 4)
r[lineR + k] = buffer[0][i+2]; // B
r[lineR + k + 1] = buffer[0][i+1]; // G
r[lineR + k + 2] = buffer[0][i]; // R
r[ESP32基于Arduino框架下U8g2驱动I2C OLED 时间显示
ESP32基于Arduino框架下U8g2驱动I2C OLED时间显示
-
📺演示:
-
✨本案例采用1.3寸OLED屏幕,同时保留0.96屏幕接口函数,只要u8g2库支持的屏幕都可以,需要到\\U8g2\\src\\U8g2lib.h
中匹配自己的屏幕函数接口。
📑I2C引脚接线说明
SDA-----> 21
SCL----->22
🛠所需库
🎉 所需库都可以在IDE管理库当中下载到。
U8g2库
Ticker库
📝驱动代码
#include <Arduino.h>
#include <U8g2lib.h>//包含u8g2头文件,用于OLED显示
//#include <ESP8266WiFi.h>//包含ESP8266头文件
#include <WiFi.h>
#include <Ticker.h> //调用Ticker.h库
#define NTP1 "ntp1.aliyun.com"
#define NTP2 "ntp2.aliyun.com"
#define NTP3 "ntp3.aliyun.com"
#ifndef STASSID
#define STASSID "MERCURY_D268G"
#define STAPSK "pba5ayzk"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
#define U8X8_HAVE_HW_I2C
//U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // 1.3寸,默认引脚
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/22, /* data=*/21); // 1.3寸,指定引脚
//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);//0.96寸,默认引脚
//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/22, /* data=*/21); //0.96寸,指定引脚
#ifdef U8X8_HAVE_HW_I2C //定义OLED连接方式为I2C
#include <Wire.h>
#endif
const String WDAY_NAMES[] = "Sun","Mon", "Tue", "Wed", "Thu", "Fri", "Sat"; //星期
void printLocalTime()
struct tm timeInfo;//声明一个结构体
char buff[16];
if (!getLocalTime(&timeInfo))
Serial.println("Failed to obtain time");
u8g2.setCursor(0,9);
u8g2.print("NTP time failure");
return;
// Serial.println(&timeinfo, "%F %T %a"); // 格式化输出,串口显示
String date = WDAY_NAMES[timeInfo.tm_wday];
u8g2.clearBuffer();
sprintf_P(buff, PSTR("%04d-%02d-%02d %s"), timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, WDAY_NAMES[timeInfo.tm_wday].c_str());
u8g2.setFont(u8g2_font_wqy14_t_gb2312a);
u8g2.setCursor(9 , 20 ); //年份居中显示
u8g2.println(buff);//显示出年份
//Serial.println(buff);
sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);//时间
u8g2.setFont(u8g2_font_fur17_tn);
u8g2.drawStr(19 , 45 , buff);
u8g2.sendBuffer();
Ticker timer1(printLocalTime, 1000);
void setup()
// put your setup code here, to run once:
Serial.begin(115200);
u8g2.begin(); //初始化OLED
WiFi.mode(WIFI_STA); //设置ESP32工作模式为无线终端模式
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED)
delay(500);
Serial.print(".");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
configTime(8 * 3600, 0, NTP1, NTP2,NTP3);
u8g2.setPowerSave(0);// 关省电模式
timer1.start();
void loop()
timer1.update();
以上是关于Windows & Esp32基于 libjpeg-9e 编解码库的视频播放器的主要内容,如果未能解决你的问题,请参考以下文章
ESP32之 ESP-IDF + Clion 开发环境搭建—— Windows版
ESP8266/ESP32 网络温控器监控 Web服务器-基于温度控制输出
基于Windows下离线安装当前最新Arduino ESP32 SDK(2.0.7)固件开发包
ESP32-C3学习笔记:ESP32 C3 IIC总线驱动光照强度传感器(基于ESP-IDF Eclipse)