├── .gitignore
├── CMakeLists.txt
├── README.md
├── components
└── esp_lvgl_simple_player
│ ├── CMakeLists.txt
│ ├── LICENSE
│ ├── README.md
│ ├── idf_component.yml
│ ├── include
│ └── esp_lvgl_simple_player.h
│ ├── priv_include
│ └── media_src_storage.h
│ └── src
│ ├── esp_lvgl_simple_player.c
│ └── media_src_storage.c
├── doc
└── demo.jpg
├── main
├── CMakeLists.txt
├── cinema_main.c
├── idf_component.yml
├── images
│ ├── filesystem
│ │ ├── breaking_news.png
│ │ ├── esp_logo.png
│ │ └── esp_text.png
│ ├── lvgl8
│ │ ├── esp_logo.c
│ │ └── esp_text.c
│ └── lvgl9
│ │ ├── breaking_news.c
│ │ ├── esp_logo.c
│ │ └── esp_text.c
├── media_src_storage.c
└── media_src_storage.h
├── partitions.csv
└── sdkconfig.defaults
/.gitignore:
--------------------------------------------------------------------------------
1 | build*
2 | sdkconfig
3 | sdkconfig.old
4 | managed_components
5 | dependencies.lock
6 | .vscode
7 | doxygen_output/**
8 | dist
9 | __pycache__
10 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # For more information about build system see
2 | # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
3 | # The following five lines of boilerplate have to be in your project's
4 | # CMakeLists in this exact order for cmake to work correctly
5 | cmake_minimum_required(VERSION 3.5)
6 |
7 | #set(COMPONENTS main) # "Trim" the build. Include the minimal set of components; main and anything it depends on.
8 | include($ENV{IDF_PATH}/tools/cmake/project.cmake)
9 | project(display)
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple LVGL Player
2 |
3 | This is a display + LVGL9 graphics library example.
4 | Play mjpeg wideo from SD card.
5 |
6 |
7 |
8 | ## Supported boards
9 |
10 | Select board in `menuconfig`: Component config → Board Support Package(ESP32-P4) → Display → Select LCD type
11 | - LCD 1280x800 - ili9881c
12 | - LCD 7-inch 1024x600 - ek79007
13 |
14 | ## Build a flash
15 |
16 | ```
17 | idf.py -p COMx flash monitor
18 | ```
19 |
20 | ## Usage
21 |
22 | Save `*.mjpeg` video files into `root` on SD card. You can select video file and play it.
23 |
24 | ## USB
25 |
26 | - You can connect USB mouse to control it by mouse cursor. For disable USB mouse set `APP_SUPPORT_USB_MOUSE` to zero.
27 | - USB keyboard is not working right now.
28 |
29 | ## Create M-JPEG video
30 |
31 | Create video without audio:
32 | ```
33 | .\ffmpeg.exe -i input_video.mp4 -vcodec mjpeg -q:v 2 -vf "scale=800:450" -an output_video.mjpeg
34 | ```
35 |
--------------------------------------------------------------------------------
/components/esp_lvgl_simple_player/CMakeLists.txt:
--------------------------------------------------------------------------------
1 |
2 | idf_component_register(
3 | SRCS "src/esp_lvgl_simple_player.c" "src/media_src_storage.c"
4 | INCLUDE_DIRS "include"
5 | PRIV_INCLUDE_DIRS "priv_include"
6 | REQUIRES esp_driver_jpeg
7 | )
8 |
--------------------------------------------------------------------------------
/components/esp_lvgl_simple_player/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/components/esp_lvgl_simple_player/README.md:
--------------------------------------------------------------------------------
1 | # Simple LVGL Player
2 |
3 | This component can play M-JPEG video on ESP32P4 board with LVGL9 objects.
4 |
5 | ## Usage
6 |
7 | Create LVGL simple player object:
8 | ```
9 | /* Create player */
10 | esp_lvgl_simple_player_cfg_t player_cfg = {
11 | .file = "/sdcard/video.mjpeg",
12 | .screen = lv_screen_active(),
13 | .screen_width = BSP_LCD_V_RES,
14 | .screen_height = (BSP_LCD_H_RES),
15 | .buff_size = 540*960,
16 | .flags = {
17 | .hide_controls = false, /* Show/hide control buttons */
18 | .hide_slider = false, /* Show/hide indication slider */
19 | .hide_status = false, /* Hide status icons in video (paused, stopped) */
20 |
21 | .auto_width = false, /* Set automatic width by video size */
22 | .auto_height = true, /* Set automatic height by video size */
23 | }
24 | };
25 | lv_obj_t * player = esp_lvgl_simple_player_create(&player_cfg);
26 | /* Align to center */
27 | lv_obj_center(player);
28 | ```
29 |
30 | Change playing file (stop played file):
31 | ```
32 | esp_lvgl_simple_player_change_file("/sdcard/video1.mjpeg");
33 | ```
34 |
35 | Control video:
36 | ```
37 | esp_lvgl_simple_player_play();
38 | esp_lvgl_simple_player_pause();
39 | esp_lvgl_simple_player_stop();
40 | ```
41 |
42 | ## How to create M-JPEG video
43 |
44 | Create video without audio:
45 | ```
46 | .\ffmpeg.exe -i input_video.mp4 -vcodec mjpeg -q:v 2 -vf "scale=800:450" -an output_video.mjpeg
47 | ```
48 |
--------------------------------------------------------------------------------
/components/esp_lvgl_simple_player/idf_component.yml:
--------------------------------------------------------------------------------
1 | version: "1.0.0"
2 | description: ESP LVGL Simple Player
3 |
4 | targets:
5 | - esp32p4
6 |
7 | dependencies:
8 | esp_lvgl_port: "*"
9 |
10 |
--------------------------------------------------------------------------------
/components/esp_lvgl_simple_player/include/esp_lvgl_simple_player.h:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | #pragma once
8 | #include "lvgl.h"
9 |
10 | #ifdef __cplusplus
11 | extern "C" {
12 | #endif
13 |
14 | /**
15 | * @brief Player states
16 | */
17 | typedef enum
18 | {
19 | PLAYER_STATE_PLAYING,
20 | PLAYER_STATE_PAUSED,
21 | PLAYER_STATE_STOPPED,
22 | } player_state_t;
23 |
24 | /**
25 | * @brief Player configuration structure
26 | */
27 | typedef struct {
28 | char *file; /* File path to play */
29 | lv_obj_t *screen; /* LVGL screen to put the player */
30 | uint32_t buff_size; /* Size of the buffer for one video frame */
31 | uint32_t screen_width; /* Width of the video player object */
32 | uint32_t screen_height; /* Height of the video player object */
33 | struct {
34 | unsigned int hide_controls: 1; /* Hide control buttons */
35 | unsigned int hide_slider: 1; /* Hide indication slider */
36 | unsigned int hide_status: 1; /* Hide status icons in video (paused, stopped) */
37 |
38 | unsigned int auto_width: 1; /* Set automatic width by video size */
39 | unsigned int auto_height: 1; /* Set automatic height by video size */
40 | } flags;
41 | } esp_lvgl_simple_player_cfg_t;
42 |
43 | /**
44 | * @brief Create Player
45 | *
46 | * This function initializes video decoder (JPEG, ...), creates LVGL objects and starts handling task.
47 | *
48 | * @return
49 | * - ESP_OK On success
50 | */
51 | lv_obj_t * esp_lvgl_simple_player_create(esp_lvgl_simple_player_cfg_t * params);
52 |
53 | /**
54 | * @brief Get player state
55 | */
56 | player_state_t esp_lvgl_simple_player_get_state(void);
57 |
58 | /**
59 | * @brief Hide player controls
60 | */
61 | void esp_lvgl_simple_player_hide_controls(bool hide);
62 |
63 | /**
64 | * @brief Change file for playing
65 | */
66 | void esp_lvgl_simple_player_change_file(char *file);
67 |
68 | /**
69 | * @brief Play player
70 | */
71 | void esp_lvgl_simple_player_play(void);
72 |
73 | /**
74 | * @brief Pause player
75 | */
76 | void esp_lvgl_simple_player_pause(void);
77 |
78 | /**
79 | * @brief Stop player
80 | */
81 | void esp_lvgl_simple_player_stop(void);
82 |
83 | /**
84 | * @brief Set repeat playing
85 | */
86 | void esp_lvgl_simple_player_repeat(bool repeat);
87 |
88 | /**
89 | * @brief Delete Player
90 | *
91 | * @return
92 | * - ESP_OK On success
93 | */
94 | esp_err_t esp_lvgl_simple_player_del(void);
95 |
96 | #ifdef __cplusplus
97 | }
98 | #endif
--------------------------------------------------------------------------------
/components/esp_lvgl_simple_player/priv_include/media_src_storage.h:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | #pragma once
8 |
9 | #ifdef __cplusplus
10 | extern "C" {
11 | #endif
12 |
13 | typedef struct {
14 | void *sub_src; /*!< Sub source to keep media source extra data */
15 | } media_src_t;
16 |
17 | int media_src_storage_open(media_src_t *src);
18 | int media_src_storage_connect(media_src_t *src, char *uri);
19 | int media_src_storage_disconnect(media_src_t *src);
20 | int media_src_storage_read(media_src_t *src, void *data, size_t len);
21 | int media_src_storage_seek(media_src_t *src, uint64_t position);
22 | int media_src_storage_get_position(media_src_t *src, uint64_t *position);
23 | int media_src_storage_get_size(media_src_t *src, uint64_t *size);
24 | int media_src_storage_close(media_src_t *src);
25 |
26 | #ifdef __cplusplus
27 | }
28 | #endif
29 |
--------------------------------------------------------------------------------
/components/esp_lvgl_simple_player/src/esp_lvgl_simple_player.c:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | #include
8 | #include "esp_log.h"
9 | #include "esp_check.h"
10 | #include "esp_heap_caps.h"
11 | #include "freertos/FreeRTOS.h"
12 | #include "freertos/task.h"
13 | #include "driver/jpeg_decode.h"
14 | #include "esp_lvgl_port.h"
15 | #include "media_src_storage.h"
16 | #include "esp_lvgl_simple_player.h"
17 |
18 | #define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
19 |
20 | static const char *TAG = "PLAYER";
21 | static const uint16_t EOI = 0xd9ff; /* End of image */
22 |
23 | typedef struct
24 | {
25 | char *file_path;
26 | media_src_t file;
27 | uint64_t filesize;
28 | jpeg_decoder_handle_t jpeg;
29 |
30 | uint32_t screen_width; /* Width of the video player object */
31 | uint32_t screen_height; /* Height of the video player object */
32 | uint32_t video_width; /* Maximum width of the video */
33 | uint32_t video_height; /* Maximum height of the video */
34 |
35 | player_state_t state;
36 | bool loop;
37 | bool hide_controls;
38 | bool hide_slider;
39 | bool hide_status;
40 | bool auto_width;
41 | bool auto_height;
42 |
43 | /* Buffers */
44 | uint8_t *in_buff;
45 | uint32_t in_buff_size;
46 | uint8_t *out_buff;
47 | uint32_t out_buff_size;
48 |
49 | /* LVGL objects */
50 | lv_obj_t *main;
51 | lv_obj_t *canvas;
52 | lv_obj_t *slider;
53 | lv_obj_t *btn_play;
54 | lv_obj_t *btn_pause;
55 | lv_obj_t *btn_stop;
56 | lv_obj_t *btn_repeat;
57 | lv_obj_t *img_pause;
58 | lv_obj_t *img_stop;
59 | lv_obj_t *controls;
60 | } player_ctx_t;
61 |
62 | static player_ctx_t player_ctx;
63 |
64 |
65 | static const jpeg_decode_cfg_t jpeg_decode_cfg = {
66 | .output_format = JPEG_DECODE_OUT_FORMAT_RGB565,
67 | .rgb_order = JPEG_DEC_RGB_ELEMENT_ORDER_BGR,
68 | };
69 |
70 |
71 |
72 | static void play_event_cb(lv_event_t *e)
73 | {
74 | lv_event_code_t code = lv_event_get_code(e);
75 |
76 | if (code == LV_EVENT_CLICKED) {
77 | esp_lvgl_simple_player_play();
78 | }
79 | }
80 |
81 | static void stop_event_cb(lv_event_t *e)
82 | {
83 | lv_event_code_t code = lv_event_get_code(e);
84 |
85 | if (code == LV_EVENT_CLICKED) {
86 | esp_lvgl_simple_player_stop();
87 | }
88 | }
89 |
90 | static void pause_event_cb(lv_event_t *e)
91 | {
92 | lv_event_code_t code = lv_event_get_code(e);
93 |
94 | if (code == LV_EVENT_CLICKED) {
95 | if(player_ctx.state == PLAYER_STATE_PAUSED)
96 | esp_lvgl_simple_player_play();
97 | else
98 | esp_lvgl_simple_player_pause();
99 | }
100 | }
101 |
102 | static void repeat_event_cb(lv_event_t *e)
103 | {
104 | lv_event_code_t code = lv_event_get_code(e);
105 | lv_obj_t *obj = lv_event_get_target(e);
106 |
107 | if (code == LV_EVENT_VALUE_CHANGED) {
108 | bool loop = lv_obj_get_state(obj) & LV_STATE_CHECKED ? true : false;
109 | esp_lvgl_simple_player_repeat(loop);
110 | }
111 | }
112 |
113 | static lv_obj_t * create_lvgl_objects(lv_obj_t * screen)
114 | {
115 | /* Create LVGL objects */
116 | lvgl_port_lock(0);
117 |
118 | /* Rows */
119 | lv_obj_t *cont_col = lv_obj_create(screen);
120 | lv_obj_set_size(cont_col, player_ctx.screen_width, player_ctx.screen_height);
121 | lv_obj_set_flex_flow(cont_col, LV_FLEX_FLOW_COLUMN);
122 | lv_obj_set_style_pad_all(cont_col, 0, 0);
123 | lv_obj_set_flex_align(cont_col, LV_FLEX_ALIGN_END, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
124 | lv_obj_set_style_bg_color(cont_col, lv_color_black(), 0);
125 | lv_obj_remove_flag(cont_col, LV_OBJ_FLAG_SCROLLABLE);
126 | player_ctx.main = cont_col;
127 |
128 | /* Video canvas */
129 | player_ctx.canvas = lv_canvas_create(cont_col);
130 | lv_obj_add_event_cb(player_ctx.canvas, pause_event_cb, LV_EVENT_CLICKED, NULL);
131 |
132 | /*Create a slider in the center of the display*/
133 | lv_obj_t * slider = lv_slider_create(cont_col);
134 | lv_obj_set_size(slider, player_ctx.screen_width, 5);
135 | lv_obj_add_state(slider, LV_STATE_DISABLED);
136 | lv_obj_set_style_opa(slider, LV_OPA_TRANSP, LV_PART_KNOB);
137 | player_ctx.slider = slider;
138 |
139 | /* Buttons */
140 | lv_obj_t *cont_row = lv_obj_create(cont_col);
141 | lv_obj_set_size(cont_row, player_ctx.screen_width - 20, 80);
142 | lv_obj_set_flex_flow(cont_row, LV_FLEX_FLOW_ROW);
143 | lv_obj_set_style_pad_top(cont_row, 2, 0);
144 | lv_obj_set_style_pad_bottom(cont_row, 2, 0);
145 | lv_obj_set_flex_align(cont_row, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
146 | lv_obj_set_style_bg_color(cont_row, lv_color_black(), 0);
147 | lv_obj_set_style_border_width(cont_row, 0, 0);
148 | player_ctx.controls = cont_row;
149 |
150 | /* Play button */
151 | lv_obj_t * play_btn = lv_btn_create(cont_row);
152 | lv_obj_t * label = lv_label_create(play_btn);
153 | lv_label_set_text_static(label, LV_SYMBOL_PLAY);
154 | lv_obj_add_event_cb(play_btn, play_event_cb, LV_EVENT_CLICKED, NULL);
155 | player_ctx.btn_play = play_btn;
156 |
157 | /* Pause button */
158 | lv_obj_t * pause_btn = lv_btn_create(cont_row);
159 | label = lv_label_create(pause_btn);
160 | lv_label_set_text_static(label, LV_SYMBOL_PAUSE);
161 | lv_obj_add_event_cb(pause_btn, pause_event_cb, LV_EVENT_CLICKED, NULL);
162 | player_ctx.btn_pause = pause_btn;
163 |
164 | /* Stop button */
165 | lv_obj_t * stop_btn = lv_btn_create(cont_row);
166 | label = lv_label_create(stop_btn);
167 | lv_label_set_text_static(label, LV_SYMBOL_STOP);
168 | lv_obj_add_event_cb(stop_btn, stop_event_cb, LV_EVENT_CLICKED, NULL);
169 | player_ctx.btn_stop = stop_btn;
170 |
171 | /* Repeat button */
172 | lv_obj_t * repeat_btn = lv_btn_create(cont_row);
173 | lv_obj_add_flag(repeat_btn, LV_OBJ_FLAG_CHECKABLE);
174 | label = lv_label_create(repeat_btn);
175 | lv_label_set_text_static(label, LV_SYMBOL_REFRESH);
176 | lv_obj_add_event_cb(repeat_btn, repeat_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
177 | player_ctx.btn_repeat = repeat_btn;
178 |
179 | /* Pause image */
180 | lv_obj_t * img_pause = lv_label_create(player_ctx.canvas);
181 | lv_obj_set_style_text_font(img_pause, &lv_font_montserrat_48, 0);
182 | lv_obj_set_style_text_color(img_pause, lv_color_white(), 0);
183 | lv_label_set_text_static(img_pause, LV_SYMBOL_PAUSE);
184 | lv_obj_center(img_pause);
185 | lv_obj_add_flag(img_pause, LV_OBJ_FLAG_HIDDEN);
186 | player_ctx.img_pause = img_pause;
187 |
188 | /* Stop image */
189 | lv_obj_t * img_stop = lv_label_create(player_ctx.canvas);
190 | lv_obj_set_style_text_font(img_stop, &lv_font_montserrat_48, 0);
191 | lv_obj_set_style_text_color(img_stop, lv_color_white(), 0);
192 | lv_label_set_text_static(img_stop, LV_SYMBOL_STOP);
193 | lv_obj_center(img_stop);
194 | lv_obj_add_flag(img_stop, LV_OBJ_FLAG_HIDDEN);
195 | player_ctx.img_stop = img_stop;
196 |
197 | /* Hide control buttons */
198 | if (player_ctx.hide_controls) {
199 | lv_obj_add_flag(cont_row, LV_OBJ_FLAG_HIDDEN);
200 | }
201 | /* Hide slider */
202 | if (player_ctx.hide_slider) {
203 | lv_obj_add_flag(slider, LV_OBJ_FLAG_HIDDEN);
204 | }
205 | /* Hide status icons */
206 | if (player_ctx.hide_status) {
207 | lv_obj_add_flag(img_pause, LV_OBJ_FLAG_HIDDEN);
208 | lv_obj_add_flag(img_stop, LV_OBJ_FLAG_HIDDEN);
209 | }
210 |
211 | lvgl_port_unlock();
212 |
213 | return cont_col;
214 | }
215 |
216 | static esp_err_t get_video_size(uint32_t * width, uint32_t * height)
217 | {
218 | esp_err_t err;
219 | jpeg_decode_picture_info_t header;
220 | assert(width && height);
221 |
222 | int size = media_src_storage_read(&player_ctx.file, player_ctx.in_buff, player_ctx.in_buff_size);
223 | if(size < 0)
224 | return ESP_ERR_INVALID_SIZE;
225 |
226 | err = jpeg_decoder_get_info(player_ctx.in_buff, size, &header);
227 |
228 | *width = header.width;
229 | *height = header.height;
230 |
231 | return err;
232 | }
233 |
234 | static esp_err_t video_decoder_init(void)
235 | {
236 | jpeg_decode_engine_cfg_t engine_cfg = {
237 | .intr_priority = 0,
238 | .timeout_ms = 50,
239 | };
240 | return jpeg_new_decoder_engine(&engine_cfg, &player_ctx.jpeg);
241 | }
242 |
243 | static void video_decoder_deinit(void)
244 | {
245 | if (player_ctx.jpeg) {
246 | jpeg_del_decoder_engine(player_ctx.jpeg);
247 | }
248 | }
249 |
250 | static uint8_t * video_decoder_malloc(uint32_t size, bool inbuff, uint32_t * outsize)
251 | {
252 | jpeg_decode_memory_alloc_cfg_t tx_mem_cfg = {
253 | .buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER,
254 | };
255 | jpeg_decode_memory_alloc_cfg_t rx_mem_cfg = {
256 | .buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER,
257 | };
258 | return (uint8_t *)jpeg_alloc_decoder_mem(size, (inbuff ? &tx_mem_cfg : &rx_mem_cfg), (size_t*)outsize);
259 | }
260 |
261 | static int video_decoder_decode(void)
262 | {
263 | esp_err_t err;
264 | uint32_t ret_size = 0;
265 | uint32_t jpeg_image_size = 0;
266 | uint32_t jpeg_image_size_aligned = 0;
267 |
268 | /* Search for EOI. */
269 | uint8_t * match = memmem(player_ctx.in_buff, player_ctx.in_buff_size, &EOI, 2);
270 | if(match)
271 | {
272 | jpeg_image_size = ((uint32_t)((match+2) - player_ctx.in_buff)); // move match by 2 for skip EOI
273 | jpeg_image_size_aligned = ALIGN_UP(jpeg_image_size, 16);
274 | assert(jpeg_image_size < player_ctx.in_buff_size);
275 | assert(jpeg_image_size_aligned < player_ctx.in_buff_size);
276 | }
277 |
278 | /* Decode JPEG */
279 | ret_size = player_ctx.out_buff_size;
280 | err = jpeg_decoder_process(player_ctx.jpeg, &jpeg_decode_cfg, player_ctx.in_buff, jpeg_image_size_aligned, player_ctx.out_buff, player_ctx.out_buff_size, &ret_size);
281 | if(err != ESP_OK)
282 | return -1;
283 |
284 | assert(ret_size < player_ctx.out_buff_size);
285 |
286 | return jpeg_image_size;
287 | }
288 |
289 | static void show_video_task(void *arg)
290 | {
291 | esp_err_t ret = ESP_OK;
292 | int read_size = 0;
293 | int processed = 0;
294 | uint32_t all_size = 0;
295 |
296 | /* Open file */
297 | ESP_LOGI(TAG, "Opening file %s ...", player_ctx.file_path);
298 | ESP_GOTO_ON_FALSE(media_src_storage_open(&player_ctx.file) == 0, ESP_ERR_NO_MEM, err, TAG, "Storage open failed");
299 | ESP_GOTO_ON_FALSE(media_src_storage_connect(&player_ctx.file, player_ctx.file_path) == 0, ESP_ERR_NO_MEM, err, TAG, "Storage connect failed");
300 |
301 | /* Get file size */
302 | ESP_GOTO_ON_FALSE(media_src_storage_get_size(&player_ctx.file, &player_ctx.filesize) == 0, ESP_ERR_NO_MEM, err, TAG, "Get file size failed");
303 |
304 | /* Create input buffer */
305 | player_ctx.in_buff = video_decoder_malloc(player_ctx.in_buff_size, true, &player_ctx.in_buff_size);
306 | ESP_GOTO_ON_FALSE(player_ctx.in_buff, ESP_ERR_NO_MEM, err, TAG, "Allocation in_buff failed");
307 |
308 | /* Init video decoder */
309 | ESP_GOTO_ON_ERROR(video_decoder_init(), err, TAG, "Initialize video decoder failed");
310 | /* Get video output size */
311 | uint32_t height = 0;
312 | uint32_t width = 0;
313 | ESP_GOTO_ON_ERROR(get_video_size(&width, &height), err, TAG, "Get video file size failed");
314 | width = ALIGN_UP(width, 16);
315 |
316 | ESP_LOGI(TAG, "Video size: %ld x %ld", width, height);
317 |
318 | /* Create output buffer */
319 | player_ctx.out_buff_size = width * height * 3;
320 | player_ctx.out_buff = video_decoder_malloc(player_ctx.out_buff_size, false, &player_ctx.out_buff_size);
321 | ESP_GOTO_ON_FALSE(player_ctx.out_buff, ESP_ERR_NO_MEM, err, TAG, "Allocation out_buff failed");
322 |
323 | lvgl_port_lock(0);
324 | /* Set buffer to LVGL canvas */
325 | lv_canvas_set_buffer(player_ctx.canvas, player_ctx.out_buff, width, height, LV_COLOR_FORMAT_RGB565);
326 | lv_obj_invalidate(player_ctx.canvas);
327 |
328 | if (player_ctx.auto_width || player_ctx.auto_height) {
329 | uint32_t h = (player_ctx.auto_height ? (height+120) : lv_obj_get_height(player_ctx.main));
330 | uint32_t w = (player_ctx.auto_width ? width : lv_obj_get_width(player_ctx.main));
331 | lv_obj_set_size(player_ctx.main, w, h);
332 | }
333 |
334 |
335 | lv_obj_remove_state(player_ctx.slider, LV_STATE_DISABLED);
336 | /* Enable/disable buttons */
337 | lv_obj_add_state(player_ctx.btn_play, LV_STATE_DISABLED);
338 | lv_obj_remove_state(player_ctx.btn_stop, LV_STATE_DISABLED);
339 | lv_obj_remove_state(player_ctx.btn_pause, LV_STATE_DISABLED);
340 | lv_obj_remove_state(player_ctx.btn_repeat, LV_STATE_DISABLED);
341 | /* Hide Stop button */
342 | lv_obj_add_flag(player_ctx.img_stop, LV_OBJ_FLAG_HIDDEN);
343 | /* Set slider range */
344 | lv_slider_set_range(player_ctx.slider, 0, 1000);
345 | lvgl_port_unlock();
346 |
347 | player_ctx.state = PLAYER_STATE_PLAYING;
348 |
349 | ESP_LOGI(TAG, "Video player initialized");
350 |
351 | media_src_storage_seek(&player_ctx.file, 0);
352 | while(player_ctx.state != PLAYER_STATE_STOPPED)
353 | {
354 | if (player_ctx.state == PLAYER_STATE_PAUSED) {
355 | lvgl_port_lock(0);
356 | lv_obj_remove_flag(player_ctx.img_pause, LV_OBJ_FLAG_HIDDEN);
357 | lvgl_port_unlock();
358 | vTaskDelay(pdMS_TO_TICKS(500));
359 | continue;
360 | }
361 |
362 | read_size = media_src_storage_read(&player_ctx.file, player_ctx.in_buff, player_ctx.in_buff_size);
363 | if (read_size <= 0) {
364 | ESP_LOGI(TAG, "Playing finished.");
365 | if (player_ctx.loop) {
366 | ESP_LOGI(TAG, "Playing loop enabled. Play again...");
367 | media_src_storage_seek(&player_ctx.file, 0);
368 | all_size = 0;
369 | continue;
370 | } else {
371 | esp_lvgl_simple_player_stop();
372 | continue;
373 | }
374 | }
375 |
376 | /* Decode one frame */
377 | processed = video_decoder_decode();
378 |
379 | /* Move in video file */
380 | if(processed > 0)
381 | {
382 | all_size += processed;
383 | media_src_storage_seek(&player_ctx.file, all_size);
384 | }
385 |
386 | lvgl_port_lock(0);
387 | /* Refresh video canvas object */
388 | lv_obj_invalidate(player_ctx.canvas);
389 | /* Set slider */
390 | lv_slider_set_value(player_ctx.slider, ((float)all_size/(float)player_ctx.filesize)*1000, LV_ANIM_ON);
391 | lvgl_port_unlock();
392 | }
393 |
394 |
395 | err:
396 | lvgl_port_lock(0);
397 | /* Show black on screen */
398 | memset(player_ctx.out_buff, 0, player_ctx.out_buff_size);
399 | if (player_ctx.auto_height) {
400 | lv_obj_set_height(player_ctx.main, 320);
401 | }
402 | lv_obj_invalidate(player_ctx.canvas);
403 | /* Set slider */
404 | lv_slider_set_value(player_ctx.slider, 0, LV_ANIM_ON);
405 | lvgl_port_unlock();
406 |
407 | /* Close storage */
408 | media_src_storage_disconnect(&player_ctx.file);
409 | media_src_storage_close(&player_ctx.file);
410 |
411 | /* Deinit video decoder */
412 | video_decoder_deinit();
413 |
414 | if (player_ctx.in_buff) {
415 | heap_caps_free(player_ctx.in_buff);
416 | player_ctx.in_buff = NULL;
417 | }
418 | if (player_ctx.out_buff) {
419 | heap_caps_free(player_ctx.out_buff);
420 | player_ctx.out_buff = NULL;
421 | player_ctx.out_buff_size = 0;
422 | }
423 |
424 | /* Close task */
425 | vTaskDelete( NULL );
426 | }
427 |
428 | lv_obj_t * esp_lvgl_simple_player_create(esp_lvgl_simple_player_cfg_t * params)
429 | {
430 | ESP_RETURN_ON_FALSE(params->file, NULL, TAG, "File path must be filled");
431 | ESP_RETURN_ON_FALSE(params->screen, NULL, TAG, "LVGL screen must be filled");
432 | ESP_RETURN_ON_FALSE(params->buff_size, NULL, TAG, "Size of the video frame buffer must be filled");
433 | ESP_RETURN_ON_FALSE(params->screen_width > 0 && params->screen_height > 0, NULL, TAG, "Object size must be filled");
434 |
435 | player_ctx.file_path = params->file;
436 | player_ctx.in_buff_size = params->buff_size;
437 | player_ctx.screen_width = params->screen_width;
438 | player_ctx.screen_height = params->screen_height;
439 | player_ctx.hide_controls = params->flags.hide_controls;
440 | player_ctx.hide_slider = params->flags.hide_slider;
441 | player_ctx.hide_status = params->flags.hide_status;
442 | player_ctx.auto_width = params->flags.auto_width;
443 | player_ctx.auto_height = params->flags.auto_height;
444 |
445 | /* Create LVGL objects */
446 | lv_obj_t * player_screen = create_lvgl_objects(params->screen);
447 |
448 | /* Default player state */
449 | esp_lvgl_simple_player_stop();
450 |
451 | return player_screen;
452 | }
453 |
454 | player_state_t esp_lvgl_simple_player_get_state(void)
455 | {
456 | return player_ctx.state;
457 | }
458 |
459 | void esp_lvgl_simple_player_hide_controls(bool hide)
460 | {
461 | if (hide) {
462 | lv_obj_add_flag(player_ctx.controls, LV_OBJ_FLAG_HIDDEN);
463 | } else {
464 | lv_obj_remove_flag(player_ctx.controls, LV_OBJ_FLAG_HIDDEN);
465 | }
466 | }
467 |
468 | void esp_lvgl_simple_player_change_file(char *file)
469 | {
470 | if (player_ctx.state != PLAYER_STATE_STOPPED) {
471 | ESP_LOGW(TAG, "Playing file can be changed only when video is stopped.");
472 | }
473 | player_ctx.file_path = file;
474 | }
475 |
476 | void esp_lvgl_simple_player_play(void)
477 | {
478 | if (player_ctx.state == PLAYER_STATE_STOPPED) {
479 | ESP_LOGI(TAG, "Player starting playing.");
480 | /* Create video task */
481 | xTaskCreate(show_video_task, "video task", 4096, NULL, 4, NULL);
482 | } else if(player_ctx.state == PLAYER_STATE_PAUSED) {
483 | esp_lvgl_simple_player_pause();
484 | }
485 | }
486 |
487 | void esp_lvgl_simple_player_pause(void)
488 | {
489 | lvgl_port_lock(0);
490 | if (player_ctx.state != PLAYER_STATE_PAUSED) {
491 | ESP_LOGI(TAG, "Player paused.");
492 | player_ctx.state = PLAYER_STATE_PAUSED;
493 |
494 | lv_obj_remove_state(player_ctx.btn_play, LV_STATE_DISABLED);
495 | lv_obj_remove_state(player_ctx.btn_stop, LV_STATE_DISABLED);
496 | lv_obj_remove_state(player_ctx.btn_pause, LV_STATE_DISABLED);
497 | lv_obj_remove_state(player_ctx.btn_repeat, LV_STATE_DISABLED);
498 | } else {
499 | ESP_LOGI(TAG, "Player resume playing.");
500 | player_ctx.state = PLAYER_STATE_PLAYING;
501 | lv_obj_add_flag(player_ctx.img_pause, LV_OBJ_FLAG_HIDDEN);
502 | lv_obj_add_state(player_ctx.btn_play, LV_STATE_DISABLED);
503 | }
504 | lvgl_port_unlock();
505 | }
506 |
507 | void esp_lvgl_simple_player_stop(void)
508 | {
509 | ESP_LOGI(TAG, "Player stopped.");
510 | player_ctx.state = PLAYER_STATE_STOPPED;
511 |
512 | lvgl_port_lock(0);
513 | lv_obj_remove_state(player_ctx.btn_play, LV_STATE_DISABLED);
514 | lv_obj_add_state(player_ctx.btn_stop, LV_STATE_DISABLED);
515 | lv_obj_add_state(player_ctx.btn_pause, LV_STATE_DISABLED);
516 | lv_obj_add_state(player_ctx.btn_repeat, LV_STATE_DISABLED);
517 |
518 | lv_obj_remove_flag(player_ctx.img_stop, LV_OBJ_FLAG_HIDDEN);
519 | lvgl_port_unlock();
520 | }
521 |
522 | void esp_lvgl_simple_player_repeat(bool repeat)
523 | {
524 | ESP_LOGI(TAG, "Player repeat %s.", (repeat ? "enabled" : "disabled"));
525 | player_ctx.loop = repeat;
526 | }
--------------------------------------------------------------------------------
/components/esp_lvgl_simple_player/src/media_src_storage.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "esp_heap_caps.h"
7 | #include "media_src_storage.h"
8 |
9 | #define CACHE_SIZE (16*1024)
10 |
11 | #define USE_ALIGN_CACHE
12 |
13 | typedef struct {
14 | #ifdef USE_ALIGN_CACHE
15 | uint8_t* align_buffer;
16 | int readed;
17 | int filled;
18 | bool eof;
19 | int seek_pos;
20 | int align_pos;
21 | int buffer_pos;
22 | #endif
23 | FILE* fp;
24 | } storage_src_t;
25 |
26 | #define ALIGN_TO(pos, align) (pos & (~((align)-1)))
27 |
28 | static void media_src_storage_flush(storage_src_t* m)
29 | {
30 | #ifdef USE_ALIGN_CACHE
31 | m->filled = m->readed = 0;
32 | m->eof = false;
33 | m->align_pos = m->seek_pos = 0;
34 | m->buffer_pos = 0;
35 | #endif
36 | }
37 |
38 | #ifdef USE_ALIGN_CACHE
39 | static int cache_data(storage_src_t* m, void *data, int n) {
40 | int sent = 0;
41 | if (m->filled > m->readed) {
42 | sent = m->filled - m->readed;
43 | if (m->align_pos < m->seek_pos) {
44 | int need_skip = m->seek_pos - m->align_pos;
45 | if (need_skip > sent) {
46 | m->readed += sent;
47 | sent = 0;
48 | m->align_pos += sent;
49 | } else {
50 | sent -= need_skip;
51 | m->readed += need_skip;
52 | m->align_pos += need_skip;
53 | //printf("Skip data for align finished\n");
54 | }
55 | }
56 | if (sent > n) {
57 | sent = n;
58 | }
59 | if (sent) {
60 | memcpy(data, m->align_buffer + m->readed, sent);
61 | m->readed += sent;
62 | }
63 | }
64 | if (m->readed >= m->filled) {
65 | m->buffer_pos += m->filled;
66 | m->readed = m->filled = 0;
67 | }
68 | if (m->filled == 0) {
69 | int n = read(fileno(m->fp), m->align_buffer, CACHE_SIZE);
70 | //int n = fread(m->align_buffer, 1, CACHE_SIZE, m->fp);
71 | if (n < 0) {
72 | return n;
73 | }
74 | if (n < CACHE_SIZE) {
75 | m->eof = true;
76 | }
77 | m->filled = n;
78 | }
79 | return sent;
80 |
81 | }
82 |
83 | static int read_from_cache(storage_src_t* m, void *data, size_t len) {
84 | int read = 0;
85 | while (len > 0) {
86 | if (m->eof && m->filled == 0) {
87 | break;
88 | }
89 | int n = cache_data(m, data, len);
90 | if (n < 0) {
91 | return n;
92 | }
93 | data += n;
94 | len -= n;
95 | read += n;
96 | }
97 | return read;
98 | }
99 | #endif
100 |
101 | int media_src_storage_open(media_src_t *src)
102 | {
103 | storage_src_t* m = calloc(1, sizeof(storage_src_t));
104 | if (m == NULL) {
105 | return -1;
106 | }
107 | #ifdef USE_ALIGN_CACHE
108 | m->align_buffer = heap_caps_aligned_alloc(64, CACHE_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
109 | if (m->align_buffer == NULL) {
110 | free(m);
111 | return -1;
112 | }
113 | #endif
114 | src->sub_src = m;
115 | return 0;
116 | }
117 |
118 | int media_src_storage_connect(media_src_t *src, char *uri)
119 | {
120 | storage_src_t* m = (storage_src_t*)src->sub_src;
121 | if (m->fp) {
122 | fclose(m->fp);
123 | m->fp = NULL;
124 | }
125 | media_src_storage_flush(m);
126 | m->fp = fopen(uri, "rb");
127 | if (m->fp) {
128 | return 0;
129 | }
130 | return -1;
131 | }
132 |
133 | int media_src_storage_disconnect(media_src_t *src)
134 | {
135 | storage_src_t* m = (storage_src_t*)src->sub_src;
136 | if (m->fp) {
137 | fclose(m->fp);
138 | m->fp = NULL;
139 | }
140 | media_src_storage_flush(m);
141 | return 0;
142 | }
143 |
144 | int media_src_storage_read(media_src_t *src, void *data, size_t len)
145 | {
146 | storage_src_t* m = (storage_src_t*)src->sub_src;
147 | if (m->fp) {
148 | #ifdef USE_ALIGN_CACHE
149 | return read_from_cache(m, data, len);
150 | #else
151 | return fread(data, 1, len, m->fp);
152 | #endif
153 | }
154 | return -1;
155 | }
156 |
157 | int media_src_storage_seek(media_src_t *src, uint64_t position)
158 | {
159 | storage_src_t* m = (storage_src_t*)src->sub_src;
160 | if (m->fp) {
161 | #ifdef USE_ALIGN_CACHE
162 | if (m->filled && position >= m->buffer_pos && position <= m->buffer_pos + m->filled) {
163 | // Still in cached memory
164 | m->readed = (position - m->buffer_pos);
165 | return 0;
166 | }
167 | #endif
168 | media_src_storage_flush(m);
169 | #ifdef USE_ALIGN_CACHE
170 | m->align_pos = (int) ALIGN_TO(position, 1024);
171 | m->seek_pos = (int)position;
172 | m->buffer_pos = m->align_pos;
173 | position = m->align_pos;
174 | #endif
175 | return fseek(m->fp, position, SEEK_SET);
176 | }
177 | return -1;
178 | }
179 |
180 | int media_src_storage_get_position(media_src_t *src, uint64_t *position)
181 | {
182 | storage_src_t* m = (storage_src_t*)src->sub_src;
183 | if (m->fp) {
184 | *position = ftell(m->fp);
185 | return 0;
186 | }
187 | return -1;
188 | }
189 |
190 | int media_src_storage_get_size(media_src_t *src, uint64_t *size)
191 | {
192 | storage_src_t* m = (storage_src_t*)src->sub_src;
193 | if (m->fp) {
194 | uint32_t old = ftell(m->fp);
195 | fseek(m->fp, 0, 2);
196 | long end = ftell(m->fp);
197 | fseek(m->fp, old, 0);
198 | *size = (end <= 0 ? 0 : (uint32_t) end);
199 | return 0;
200 | }
201 | return -1;
202 | }
203 |
204 | int media_src_storage_close(media_src_t *src)
205 | {
206 | storage_src_t* m = (storage_src_t*)src->sub_src;
207 | if (m->fp) {
208 | fclose((FILE *) m->fp);
209 | m->fp = NULL;
210 | }
211 | #ifdef USE_ALIGN_CACHE
212 | if (m->align_buffer) {
213 | free(m->align_buffer);
214 | }
215 | #endif
216 | free(m);
217 | return 0;
218 | }
219 |
--------------------------------------------------------------------------------
/doc/demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espzav/Simple-LVGL-Player/dcc3acd81962eee89bcb729f53bf290fd239e706/doc/demo.jpg
--------------------------------------------------------------------------------
/main/CMakeLists.txt:
--------------------------------------------------------------------------------
1 |
2 | set(IMAGE_SOURCES "images/lvgl9/breaking_news.c")
3 |
4 | idf_component_register(
5 | SRCS "cinema_main.c" ${IMAGE_SOURCES}
6 | INCLUDE_DIRS "."
7 | REQUIRES esp_lvgl_simple_player
8 | )
9 |
--------------------------------------------------------------------------------
/main/cinema_main.c:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: CC0-1.0
5 | */
6 |
7 | #include
8 | #include
9 | #include
10 | #include "esp_log.h"
11 | #include "bsp/esp-bsp.h"
12 | #include "esp_lvgl_simple_player.h"
13 |
14 | static const char *TAG = "APP";
15 | #define APP_SUPPORT_USB_MOUSE (1)
16 | #define APP_SUPPORT_USB_KEYBOARD (1)
17 | #define APP_SUPPORT_FILE_EXT ".mjpeg"
18 |
19 | // LVGL image declare
20 | LV_IMG_DECLARE(breaking_news)
21 |
22 | #define APP_VIDEO_FILE "01_P4_vertical_540x960.mjpeg"
23 | #define APP_VIDEO_FILE_PATH BSP_SD_MOUNT_POINT"/"APP_VIDEO_FILE
24 | #define APP_BREAKING_NEWS_TEXT "New ESP32P4 chip is here! This demo was made with ESP-BSP and LVGL port (with LVGL9). *** Demo can be downloaded here: https://github.com/espzav/Simple-LVGL-Player ***"
25 |
26 | static char file_path[50] = "";
27 | static lv_obj_t * img_breaking_news;
28 | static lv_obj_t * row_edit;
29 | static lv_obj_t * lbl_breaking_news;
30 | static int sel_file = 0;
31 |
32 | static char * app_get_video_files(char * buff, uint32_t size)
33 | {
34 | int i = 0;
35 | uint32_t len = 0;
36 | struct dirent *dir;
37 | DIR *d;
38 | char * file = NULL;
39 |
40 | /* Open directory */
41 | d = opendir(BSP_SD_MOUNT_POINT);
42 | if (d != NULL) {
43 | /* Show button in the list for file of directory (Note: Directories are not supported in SPIFFS) */
44 | while ((dir = readdir(d)) != NULL) {
45 | if (dir->d_type != DT_DIR) {
46 | if (strstr(dir->d_name, APP_SUPPORT_FILE_EXT) != NULL) {
47 | file = buff+len;
48 | len += snprintf(buff+len, size-len, "%s\n", dir->d_name);
49 | if (strcmp(dir->d_name, APP_VIDEO_FILE) == 0) {
50 | sel_file = i;
51 | }
52 | }
53 | }
54 | i++;
55 | }
56 | /* Remove last new line */
57 | buff[len-1] = 0;
58 |
59 | closedir(d);
60 | }
61 |
62 | /* Return last filename */
63 | return file;
64 | }
65 |
66 | static void file_changed(lv_event_t * e)
67 | {
68 | lv_event_code_t code = lv_event_get_code(e);
69 | lv_obj_t * obj = lv_event_get_target(e);
70 | if(code == LV_EVENT_VALUE_CHANGED) {
71 | uint32_t len = snprintf(file_path, sizeof(file_path), "%s/", BSP_SD_MOUNT_POINT);
72 | lv_dropdown_get_selected_str(obj, file_path+len, sizeof(file_path)-len);
73 | esp_lvgl_simple_player_change_file(file_path);
74 | esp_lvgl_simple_player_stop();
75 | }
76 | }
77 |
78 | static void breaking_news_changed(lv_event_t * e)
79 | {
80 | lv_event_code_t code = lv_event_get_code(e);
81 | lv_obj_t * obj = lv_event_get_target(e);
82 | if(code == LV_EVENT_VALUE_CHANGED) {
83 | if (img_breaking_news) {
84 | const bool state = (lv_obj_get_state(obj) & LV_STATE_CHECKED);
85 | if (state) {
86 | lv_obj_remove_flag(img_breaking_news, LV_OBJ_FLAG_HIDDEN);
87 | } else {
88 | lv_obj_add_flag(img_breaking_news, LV_OBJ_FLAG_HIDDEN);
89 | }
90 | }
91 | }
92 | }
93 |
94 | static void hide_controls_changed(lv_event_t * e)
95 | {
96 | lv_event_code_t code = lv_event_get_code(e);
97 | lv_obj_t * obj = lv_event_get_target(e);
98 | if(code == LV_EVENT_VALUE_CHANGED) {
99 | const bool state = (lv_obj_get_state(obj) & LV_STATE_CHECKED);
100 | esp_lvgl_simple_player_hide_controls(state);
101 | }
102 | }
103 |
104 | static void edit_event_cb(lv_event_t * e)
105 | {
106 | lv_event_code_t code = lv_event_get_code(e);
107 | if(code == LV_EVENT_CLICKED) {
108 | if (lv_obj_has_flag(row_edit, LV_OBJ_FLAG_HIDDEN)) {
109 | lv_obj_remove_flag(row_edit, LV_OBJ_FLAG_HIDDEN);
110 | } else {
111 | lv_obj_add_flag(row_edit, LV_OBJ_FLAG_HIDDEN);
112 | }
113 | }
114 | }
115 |
116 | static void save_event_cb(lv_event_t * e)
117 | {
118 | lv_event_code_t code = lv_event_get_code(e);
119 | lv_obj_t * ta = lv_event_get_user_data(e);
120 | if(code == LV_EVENT_CLICKED && ta) {
121 | const char * txt = lv_textarea_get_text(ta);
122 | lv_label_set_text(lbl_breaking_news, txt);
123 | }
124 | }
125 |
126 | static void app_show_ui(void)
127 | {
128 | /* Create LVGL objects */
129 | lvgl_port_lock(0);
130 |
131 | lv_obj_set_style_bg_color(lv_screen_active(), lv_color_black(), 0);
132 |
133 | /* Rows */
134 | lv_obj_t *cont_col = lv_obj_create(lv_screen_active());
135 | lv_obj_set_size(cont_col, BSP_LCD_H_RES, BSP_LCD_V_RES);
136 | lv_obj_set_flex_flow(cont_col, LV_FLEX_FLOW_COLUMN);
137 | lv_obj_set_style_pad_all(cont_col, 0, 0);
138 | lv_obj_set_flex_align(cont_col, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
139 | lv_obj_set_style_bg_color(cont_col, lv_color_black(), 0);
140 | lv_obj_set_style_border_width(cont_col, 0, 0);
141 |
142 | char files[300] = "";
143 | char * filename;
144 | filename = app_get_video_files(files, sizeof(files));
145 |
146 | lv_obj_t *cont_row = lv_obj_create(cont_col);
147 | lv_obj_set_size(cont_row, BSP_LCD_H_RES - 20, 80);
148 | lv_obj_set_flex_flow(cont_row, LV_FLEX_FLOW_ROW);
149 | lv_obj_set_style_pad_top(cont_row, 2, 0);
150 | lv_obj_set_style_pad_bottom(cont_row, 2, 0);
151 | lv_obj_set_flex_align(cont_row, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
152 | lv_obj_set_style_bg_color(cont_row, lv_color_black(), 0);
153 | lv_obj_set_style_border_width(cont_row, 0, 0);
154 |
155 | /* Dropdown files */
156 | lv_obj_t * dd = lv_dropdown_create(cont_row);
157 | lv_obj_set_width(dd, BSP_LCD_H_RES/3);
158 | lv_dropdown_set_options(dd, files);
159 | lv_obj_add_event_cb(dd, file_changed, LV_EVENT_VALUE_CHANGED, NULL);
160 | lv_dropdown_set_selected(dd, sel_file);
161 | lv_obj_set_style_pad_top(dd, 5, 0);
162 |
163 | /* Checkbox - hide controls */
164 | lv_obj_t * cb = lv_checkbox_create(cont_row);
165 | lv_checkbox_set_text(cb, "HIDE CONTROLS");
166 | lv_obj_set_style_text_color(cb, lv_color_white(), 0);
167 | lv_obj_add_event_cb(cb, hide_controls_changed, LV_EVENT_VALUE_CHANGED, NULL);
168 |
169 | /* Checkbox - breaking news */
170 | cb = lv_checkbox_create(cont_row);
171 | lv_checkbox_set_text(cb, "BREAKING NEWS");
172 | lv_obj_add_state(cb, LV_STATE_CHECKED);
173 | lv_obj_set_style_text_color(cb, lv_color_white(), 0);
174 | lv_obj_add_event_cb(cb, breaking_news_changed, LV_EVENT_VALUE_CHANGED, NULL);
175 |
176 | /* Edit button */
177 | lv_obj_t * btn_edit = lv_btn_create(cont_row);
178 | lv_obj_t * label = lv_label_create(btn_edit);
179 | lv_label_set_text_static(label, LV_SYMBOL_EDIT);
180 | lv_obj_add_event_cb(btn_edit, edit_event_cb, LV_EVENT_CLICKED, NULL);
181 |
182 | /* Edit ROW */
183 | row_edit = lv_obj_create(cont_col);
184 | lv_obj_set_size(row_edit, BSP_LCD_H_RES - 20, 80);
185 | lv_obj_set_flex_flow(row_edit, LV_FLEX_FLOW_ROW);
186 | lv_obj_set_style_pad_top(row_edit, 0, 0);
187 | lv_obj_set_style_pad_bottom(row_edit, 0, 0);
188 | lv_obj_set_flex_align(row_edit, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
189 | lv_obj_set_style_bg_color(row_edit, lv_color_black(), 0);
190 | lv_obj_set_style_border_width(row_edit, 0, 0);
191 | lv_obj_add_flag(row_edit, LV_OBJ_FLAG_HIDDEN);
192 |
193 | /* Text area */
194 | lv_obj_t * text_ta = lv_textarea_create(row_edit);
195 | lv_textarea_set_one_line(text_ta, true);
196 | lv_obj_set_width(text_ta, BSP_LCD_H_RES - 200);
197 | lv_textarea_set_text(text_ta, APP_BREAKING_NEWS_TEXT);
198 |
199 | /* Save button */
200 | lv_obj_t * btn_save = lv_btn_create(row_edit);
201 | label = lv_label_create(btn_save);
202 | lv_label_set_text_static(label, "SAVE");
203 | lv_obj_add_event_cb(btn_save, save_event_cb, LV_EVENT_CLICKED, text_ta);
204 |
205 | /* Create player */
206 | snprintf(file_path, sizeof(file_path), "%s/%s", BSP_SD_MOUNT_POINT, filename);
207 | esp_lvgl_simple_player_cfg_t player_cfg = {
208 | .file = file_path,
209 | .screen = cont_col,
210 | .screen_width = BSP_LCD_H_RES,
211 | .screen_height = (BSP_LCD_V_RES/2),
212 | .buff_size = 540*960,
213 | .flags = {
214 | .auto_height = true,
215 | }
216 | };
217 | esp_lvgl_simple_player_create(&player_cfg);
218 |
219 | /* Breaking news image */
220 | img_breaking_news = lv_img_create(lv_screen_active());
221 | lv_img_set_src(img_breaking_news, &breaking_news);
222 | lv_obj_align(img_breaking_news, LV_ALIGN_BOTTOM_MID, 0, 0);
223 |
224 | lbl_breaking_news = lv_label_create(img_breaking_news);
225 | lv_obj_set_width(lbl_breaking_news, BSP_LCD_H_RES - 155);
226 | lv_label_set_text(lbl_breaking_news, APP_BREAKING_NEWS_TEXT);
227 | lv_obj_set_style_text_font(lbl_breaking_news, &lv_font_montserrat_16, 0);
228 | lv_label_set_long_mode(lbl_breaking_news, LV_LABEL_LONG_SCROLL_CIRCULAR);
229 | lv_obj_align(lbl_breaking_news, LV_ALIGN_BOTTOM_RIGHT, -40, -25);
230 |
231 | /* Start playing */
232 | esp_lvgl_simple_player_play();
233 |
234 | lvgl_port_unlock();
235 | }
236 |
237 | void app_main(void)
238 | {
239 | /* Initialize SD card */
240 | bsp_sdcard_mount();
241 | /* Initialize display */
242 | bsp_display_cfg_t disp_cfg = {
243 | .lvgl_port_cfg = {
244 | .task_priority = 2,
245 | .task_stack = 4096,
246 | .task_affinity = 1,
247 | .timer_period_ms = 5,
248 | .task_max_sleep_ms = 1,
249 | },
250 | .buffer_size = BSP_LCD_DRAW_BUFF_SIZE,
251 | .flags = {
252 | #if CONFIG_BSP_LCD_COLOR_FORMAT_RGB888
253 | .buff_dma = false,
254 | #else
255 | .buff_dma = true,
256 | #endif
257 | }
258 | };
259 | lv_disp_t *display = bsp_display_start_with_config(&disp_cfg);
260 | bsp_display_backlight_on();
261 |
262 | #if (APP_SUPPORT_USB_MOUSE || APP_SUPPORT_USB_KEYBOARD)
263 | /* Initialize USB */
264 | bsp_usb_host_start(BSP_USB_HOST_POWER_MODE_USB_DEV, true);
265 | #endif
266 |
267 | #if APP_SUPPORT_USB_MOUSE
268 | /* Initialize mouse */
269 | const lvgl_port_hid_mouse_cfg_t mouse_cfg = {
270 | .disp = display,
271 | .sensitivity = 1,
272 | };
273 | lvgl_port_add_usb_hid_mouse_input(&mouse_cfg);
274 | #endif
275 |
276 |
277 | #if APP_SUPPORT_USB_KEYBOARD
278 | /* Initialize keyboard */
279 | const lvgl_port_hid_keyboard_cfg_t kb_cfg = {
280 | .disp = display,
281 | };
282 | lvgl_port_add_usb_hid_keyboard_input(&kb_cfg);
283 | #endif
284 |
285 | app_show_ui();
286 | }
287 |
--------------------------------------------------------------------------------
/main/idf_component.yml:
--------------------------------------------------------------------------------
1 | description: BSP Display Cinema example
2 |
3 | dependencies:
4 | usb_host_hid: "*"
5 | esp32_p4_function_ev_board: "*"
6 | esp_lvgl_simple_player: "*"
7 |
--------------------------------------------------------------------------------
/main/images/filesystem/breaking_news.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espzav/Simple-LVGL-Player/dcc3acd81962eee89bcb729f53bf290fd239e706/main/images/filesystem/breaking_news.png
--------------------------------------------------------------------------------
/main/images/filesystem/esp_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espzav/Simple-LVGL-Player/dcc3acd81962eee89bcb729f53bf290fd239e706/main/images/filesystem/esp_logo.png
--------------------------------------------------------------------------------
/main/images/filesystem/esp_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/espzav/Simple-LVGL-Player/dcc3acd81962eee89bcb729f53bf290fd239e706/main/images/filesystem/esp_text.png
--------------------------------------------------------------------------------
/main/media_src_storage.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "esp_heap_caps.h"
7 | #include "media_src_storage.h"
8 |
9 | #define CACHE_SIZE (16*1024)
10 |
11 | #define USE_ALIGN_CACHE
12 |
13 | typedef struct {
14 | #ifdef USE_ALIGN_CACHE
15 | uint8_t* align_buffer;
16 | int readed;
17 | int filled;
18 | bool eof;
19 | int seek_pos;
20 | int align_pos;
21 | int buffer_pos;
22 | #endif
23 | FILE* fp;
24 | } storage_src_t;
25 |
26 | #define ALIGN_TO(pos, align) (pos & (~((align)-1)))
27 |
28 | static void media_src_storage_flush(storage_src_t* m)
29 | {
30 | #ifdef USE_ALIGN_CACHE
31 | m->filled = m->readed = 0;
32 | m->eof = false;
33 | m->align_pos = m->seek_pos = 0;
34 | m->buffer_pos = 0;
35 | #endif
36 | }
37 |
38 | #ifdef USE_ALIGN_CACHE
39 | static int cache_data(storage_src_t* m, void *data, int n) {
40 | int sent = 0;
41 | if (m->filled > m->readed) {
42 | sent = m->filled - m->readed;
43 | if (m->align_pos < m->seek_pos) {
44 | int need_skip = m->seek_pos - m->align_pos;
45 | if (need_skip > sent) {
46 | m->readed += sent;
47 | sent = 0;
48 | m->align_pos += sent;
49 | } else {
50 | sent -= need_skip;
51 | m->readed += need_skip;
52 | m->align_pos += need_skip;
53 | //printf("Skip data for align finished\n");
54 | }
55 | }
56 | if (sent > n) {
57 | sent = n;
58 | }
59 | if (sent) {
60 | memcpy(data, m->align_buffer + m->readed, sent);
61 | m->readed += sent;
62 | }
63 | }
64 | if (m->readed >= m->filled) {
65 | m->buffer_pos += m->filled;
66 | m->readed = m->filled = 0;
67 | }
68 | if (m->filled == 0) {
69 | int n = read(fileno(m->fp), m->align_buffer, CACHE_SIZE);
70 | //int n = fread(m->align_buffer, 1, CACHE_SIZE, m->fp);
71 | if (n < 0) {
72 | return n;
73 | }
74 | if (n < CACHE_SIZE) {
75 | m->eof = true;
76 | }
77 | m->filled = n;
78 | }
79 | return sent;
80 |
81 | }
82 |
83 | static int read_from_cache(storage_src_t* m, void *data, size_t len) {
84 | int read = 0;
85 | while (len > 0) {
86 | if (m->eof && m->filled == 0) {
87 | break;
88 | }
89 | int n = cache_data(m, data, len);
90 | if (n < 0) {
91 | return n;
92 | }
93 | data += n;
94 | len -= n;
95 | read += n;
96 | }
97 | return read;
98 | }
99 | #endif
100 |
101 | int media_src_storage_open(media_src_t *src)
102 | {
103 | storage_src_t* m = calloc(1, sizeof(storage_src_t));
104 | if (m == NULL) {
105 | return -1;
106 | }
107 | #ifdef USE_ALIGN_CACHE
108 | m->align_buffer = heap_caps_aligned_alloc(64, CACHE_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
109 | if (m->align_buffer == NULL) {
110 | free(m);
111 | return -1;
112 | }
113 | #endif
114 | src->sub_src = m;
115 | return 0;
116 | }
117 |
118 | int media_src_storage_connect(media_src_t *src, char *uri)
119 | {
120 | storage_src_t* m = (storage_src_t*)src->sub_src;
121 | if (m->fp) {
122 | fclose(m->fp);
123 | m->fp = NULL;
124 | }
125 | media_src_storage_flush(m);
126 | m->fp = fopen(uri, "rb");
127 | if (m->fp) {
128 | return 0;
129 | }
130 | return -1;
131 | }
132 |
133 | int media_src_storage_disconnect(media_src_t *src)
134 | {
135 | storage_src_t* m = (storage_src_t*)src->sub_src;
136 | if (m->fp) {
137 | fclose(m->fp);
138 | m->fp = NULL;
139 | }
140 | media_src_storage_flush(m);
141 | return 0;
142 | }
143 |
144 | int media_src_storage_read(media_src_t *src, void *data, size_t len)
145 | {
146 | storage_src_t* m = (storage_src_t*)src->sub_src;
147 | if (m->fp) {
148 | #ifdef USE_ALIGN_CACHE
149 | return read_from_cache(m, data, len);
150 | #else
151 | return fread(data, 1, len, m->fp);
152 | #endif
153 | }
154 | return -1;
155 | }
156 |
157 | int media_src_storage_seek(media_src_t *src, uint64_t position)
158 | {
159 | storage_src_t* m = (storage_src_t*)src->sub_src;
160 | if (m->fp) {
161 | #ifdef USE_ALIGN_CACHE
162 | if (m->filled && position >= m->buffer_pos && position <= m->buffer_pos + m->filled) {
163 | // Still in cached memory
164 | m->readed = (position - m->buffer_pos);
165 | return 0;
166 | }
167 | #endif
168 | media_src_storage_flush(m);
169 | #ifdef USE_ALIGN_CACHE
170 | m->align_pos = (int) ALIGN_TO(position, 1024);
171 | m->seek_pos = (int)position;
172 | m->buffer_pos = m->align_pos;
173 | position = m->align_pos;
174 | #endif
175 | return fseek(m->fp, position, SEEK_SET);
176 | }
177 | return -1;
178 | }
179 |
180 | int media_src_storage_get_position(media_src_t *src, uint64_t *position)
181 | {
182 | storage_src_t* m = (storage_src_t*)src->sub_src;
183 | if (m->fp) {
184 | *position = ftell(m->fp);
185 | return 0;
186 | }
187 | return -1;
188 | }
189 |
190 | int media_src_storage_get_size(media_src_t *src, uint64_t *size)
191 | {
192 | storage_src_t* m = (storage_src_t*)src->sub_src;
193 | if (m->fp) {
194 | uint32_t old = ftell(m->fp);
195 | fseek(m->fp, 0, 2);
196 | long end = ftell(m->fp);
197 | fseek(m->fp, old, 0);
198 | *size = (end <= 0 ? 0 : (uint32_t) end);
199 | return 0;
200 | }
201 | return -1;
202 | }
203 |
204 | int media_src_storage_close(media_src_t *src)
205 | {
206 | storage_src_t* m = (storage_src_t*)src->sub_src;
207 | if (m->fp) {
208 | fclose((FILE *) m->fp);
209 | m->fp = NULL;
210 | }
211 | #ifdef USE_ALIGN_CACHE
212 | if (m->align_buffer) {
213 | free(m->align_buffer);
214 | }
215 | #endif
216 |
217 | free(m);
218 | return 0;
219 | }
220 |
--------------------------------------------------------------------------------
/main/media_src_storage.h:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3 | *
4 | * SPDX-License-Identifier: Apache-2.0
5 | */
6 |
7 | #pragma once
8 |
9 | #ifdef __cplusplus
10 | extern "C" {
11 | #endif
12 |
13 | typedef struct {
14 | void *sub_src; /*!< Sub source to keep media source extra data */
15 | } media_src_t;
16 |
17 | int media_src_storage_open(media_src_t *src);
18 | int media_src_storage_connect(media_src_t *src, char *uri);
19 | int media_src_storage_disconnect(media_src_t *src);
20 | int media_src_storage_read(media_src_t *src, void *data, size_t len);
21 | int media_src_storage_seek(media_src_t *src, uint64_t position);
22 | int media_src_storage_get_position(media_src_t *src, uint64_t *position);
23 | int media_src_storage_get_size(media_src_t *src, uint64_t *size);
24 | int media_src_storage_close(media_src_t *src);
25 |
26 | #ifdef __cplusplus
27 | }
28 | #endif
29 |
--------------------------------------------------------------------------------
/partitions.csv:
--------------------------------------------------------------------------------
1 | # Name, Type, SubType, Offset, Size
2 | nvs, data, nvs, 0x9000, 0x4000
3 | phy_init, data, phy, 0xd000, 0x1000
4 | factory, app, factory, 0x10000, 0x250000,
5 |
--------------------------------------------------------------------------------
/sdkconfig.defaults:
--------------------------------------------------------------------------------
1 | # This file was generated using idf.py save-defconfig. It can be edited manually.
2 | # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
3 | #
4 | CONFIG_IDF_TARGET="esp32p4"
5 | CONFIG_FATFS_LFN_HEAP=y
6 | CONFIG_LV_FONT_MONTSERRAT_16=y
7 | CONFIG_LV_FONT_MONTSERRAT_48=y
8 | CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
9 | CONFIG_PARTITION_TABLE_CUSTOM=y
10 |
11 | CONFIG_SPIRAM=y
12 | CONFIG_SPIRAM_MODE_HEX=y
13 | CONFIG_SPIRAM_SPEED_200M=y
14 | CONFIG_IDF_EXPERIMENTAL_FEATURES=y
15 |
16 | #PERFORMANCE OPTIMIZATION
17 | CONFIG_FREERTOS_HZ=1000
18 | CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
19 | CONFIG_COMPILER_OPTIMIZATION_PERF=y
20 | CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y
21 | CONFIG_LV_DISP_DEF_REFR_PERIOD=10
22 | CONFIG_LV_DEF_REFR_PERIOD=10
23 |
24 | ## LVGL8 ##
25 | CONFIG_LV_MEM_SIZE_KILOBYTES=48
26 | CONFIG_LV_USE_PERF_MONITOR=y
27 |
28 | ## LVGL9 ##
29 | CONFIG_LV_CONF_SKIP=y
30 |
31 | #CLIB default
32 | CONFIG_LV_USE_CLIB_MALLOC=y
33 | CONFIG_LV_USE_CLIB_SPRINTF=y
34 | CONFIG_LV_USE_CLIB_STRING=y
35 |
36 | # Performance monitor
37 | CONFIG_LV_USE_OBSERVER=y
38 | CONFIG_LV_USE_SYSMON=y
39 | CONFIG_LV_USE_PERF_MONITOR=y
40 |
41 |
42 | # CONFIG_LV_BUILD_EXAMPLES is not set
43 |
--------------------------------------------------------------------------------