arduino-audio-tools
Loading...
Searching...
No Matches
OutputTinyGPU.h
Go to the documentation of this file.
1#pragma once
2#include <TinyGPU.h> // https://github.com/pschatzmann/TinyGPU
3#include <TinyGPU/DisplayDriver.h> // ILI9341Driver
4#include <TinyGPU/Boards.h> // LCDBoard
5
8
9namespace audio_tools {
10
30class OutputTinyGPU : public VideoOutput {
31 public:
40 OutputTinyGPU(DisplayDriver<RGB565>& driver, int pinBacklight,
41 int bandHeight = 40)
42 : tftDriver(driver),
43 kPinBacklight(pinBacklight),
44 kBandHeight(bandHeight) {}
45
52 OutputTinyGPU(LCDBoard& board, int bandHeight = 40)
53 : tftDriver(board.display()),
54 kPinBacklight(-1),
55 kBandHeight(bandHeight),
56 p_board(&board) {}
57
58 bool begin() {
59 if (p_board != nullptr) {
60 if (!p_board->begin()) return false;
61 } else {
62 if (kPinBacklight >= 0) {
65 }
66 tftDriver.begin();
67 }
69
70 return true;
71 }
72
80 void setRotation(DisplayRotation rotation) { tftDriver.setRotation(rotation); }
81
83 void setVideoInfoSource(VideoInfoSource& source) override { p_info = &source; }
84
86 void setVideoInfo(VideoInfo info) { info_ = info; }
87
95 void setScaleToFit(bool enable) { scale_to_fit = enable; }
96
110 void setScaleSingleBuffer(bool enable) { scale_single_buffer = enable; }
111
119 void setSkipRender(bool skip) override { skip_render = skip; }
120
121 size_t write(const uint8_t* data, size_t len) override {
122 auto start = millis();
123 if (p_info != nullptr) {
125 }
126
127 if (info_.width == 0 || info_.height == 0) {
128 LOGE("OutputTinyGPU: invalid video size: %d x %d", (int)info_.width,
129 (int)info_.height);
130 return 0;
131 }
132
133 bool isI420 = info_.format == VideoFormat::I420;
134 // I420: one byte/pixel Y plane, plus two quarter-size (w/2 x h/2)
135 // U/V planes - the standard w*h*3/2 packed-planar size.
136 size_t needed = isI420 ? (size_t)info_.width * info_.height * 3 / 2
137 : (size_t)info_.width * info_.height * sizeof(RGB565);
138 if (len < needed) {
139 LOGE("OutputTinyGPU: frame too small: %d < %d bytes", (int)len,
140 (int)needed);
141 return 0;
142 }
143
144 if (skip_render) {
145 return len;
146 }
147
148 if (isI420) {
149 // Unlike the RGB565 path below, this always goes row-by-row (even
150 // when scale_to_fit is off) - I420's planar layout isn't a raw byte
151 // pattern the driver can view directly the way packed RGB565 is, so
152 // conversion is unavoidable regardless of whether scaling also
153 // happens in the same pass.
155 write_time_ms = millis() - start;
156 return len;
157 }
158
159 if (scale_to_fit && (info_.width != (int)tftDriver.width() ||
160 info_.height != (int)tftDriver.height())) {
162 write_time_ms = millis() - start;
163 return len;
164 }
165
166 // View directly over the caller's buffer - no allocation, no copy.
167 SurfaceWithExternalBuffer<RGB565> frame;
168 frame.setExternalBuffer(const_cast<uint8_t*>(data), len);
169 frame.resize(info_.width, info_.height);
170 tftDriver.writeData(frame, 0, 0);
171 write_time_ms = millis() - start;
172
173 return len;
174 }
175
182 void writeScaled(const uint8_t* data, int srcW, int srcH) {
183 int width = (int)tftDriver.width();
184 int height = (int)tftDriver.height();
185 updateScaleLuts(srcW, srcH, width, height);
186 const RGB565* src = (const RGB565*)data;
187 const int* xLut = scale_x_lut_.data();
188 const int* yLut = scale_y_lut_.data();
189
191 size_t pixels = (size_t)width * height;
192 if (scale_frame_buf_.size() != pixels) scale_frame_buf_.resize(pixels);
193 for (int y = 0; y < height; y++) {
194 const RGB565* srcRow = src + (size_t)yLut[y] * srcW;
195 RGB565* dstRow = scale_frame_buf_.data() + (size_t)y * width;
196 for (int x = 0; x < width; x++) {
197 dstRow[x] = srcRow[xLut[x]];
198 }
199 }
200 SurfaceWithExternalBuffer<RGB565> frame;
201 frame.setExternalBuffer((uint8_t*)scale_frame_buf_.data(),
202 pixels * sizeof(RGB565));
203 frame.resize(width, height);
204 tftDriver.writeData(frame, 0, 0);
205 return;
206 }
207
208 // One row at a time: build one scaled row into scale_line_, then hand
209 // it to the driver as a 1-row-tall surface - one extra small SPI
210 // transaction per output row versus the single-buffer path above, but
211 // needs only a single row's worth of scratch RAM regardless of
212 // resolution.
213 if ((int)scale_line_.size() != width) scale_line_.resize(width);
214 for (int y = 0; y < height; y++) {
215 const RGB565* srcRow = src + (size_t)yLut[y] * srcW;
216 for (int x = 0; x < width; x++) {
217 scale_line_[x] = srcRow[xLut[x]];
218 }
219 SurfaceWithExternalBuffer<RGB565> row;
220 row.setExternalBuffer((uint8_t*)scale_line_.data(),
221 (size_t)width * sizeof(RGB565));
222 row.resize(width, 1);
223 tftDriver.writeData(row, 0, y);
224 }
225 }
226
232 void updateScaleLuts(int srcW, int srcH, int width, int height) {
233 if (srcW == scale_lut_src_w_ && srcH == scale_lut_src_h_ &&
234 width == scale_lut_dst_w_ && height == scale_lut_dst_h_) {
235 return;
236 }
237 scale_x_lut_.resize(width);
238 for (int x = 0; x < width; x++) {
239 scale_x_lut_[x] = (int)((int64_t)x * srcW / width);
240 }
241 scale_y_lut_.resize(height);
242 for (int y = 0; y < height; y++) {
243 scale_y_lut_[y] = (int)((int64_t)y * srcH / height);
244 }
245 scale_lut_src_w_ = srcW;
246 scale_lut_src_h_ = srcH;
247 scale_lut_dst_w_ = width;
248 scale_lut_dst_h_ = height;
249 }
250
260 void writeI420(const uint8_t* data, int srcW, int srcH) {
261 const uint8_t* yPlane = data;
262 const uint8_t* uPlane = yPlane + (size_t)srcW * srcH;
263 const uint8_t* vPlane = uPlane + (size_t)(srcW / 2) * (srcH / 2);
264 size_t srcPixels = (size_t)srcW * srcH;
265 if (i420_rgb_buf_.size() != srcPixels) i420_rgb_buf_.resize(srcPixels);
266 for (int y = 0; y < srcH; y++) {
267 const uint8_t* yRow = yPlane + (size_t)y * srcW;
268 const uint8_t* uRow = uPlane + (size_t)(y / 2) * (srcW / 2);
269 const uint8_t* vRow = vPlane + (size_t)(y / 2) * (srcW / 2);
270 RGB565* dstRow = i420_rgb_buf_.data() + (size_t)y * srcW;
271 for (int x = 0; x < srcW; x++) {
272 dstRow[x] = yuvToRgb565(yRow[x], uRow[x / 2], vRow[x / 2]);
273 }
274 }
275
276 if (scale_to_fit && (srcW != (int)tftDriver.width() ||
277 srcH != (int)tftDriver.height())) {
278 writeScaled((const uint8_t*)i420_rgb_buf_.data(), srcW, srcH);
279 return;
280 }
281 SurfaceWithExternalBuffer<RGB565> frame;
282 frame.setExternalBuffer((uint8_t*)i420_rgb_buf_.data(),
283 srcPixels * sizeof(RGB565));
284 frame.resize(srcW, srcH);
285 tftDriver.writeData(frame, 0, 0);
286 }
287
291 static RGB565 yuvToRgb565(uint8_t Y, uint8_t U, uint8_t V) {
292 int c = (int)Y - 16;
293 int d = (int)U - 128;
294 int e = (int)V - 128;
295 int r = (298 * c + 409 * e + 128) >> 8;
296 int g = (298 * c - 100 * d - 208 * e + 128) >> 8;
297 int b = (298 * c + 516 * d + 128) >> 8;
298 r = r < 0 ? 0 : (r > 255 ? 255 : r);
299 g = g < 0 ? 0 : (g > 255 ? 255 : g);
300 b = b < 0 ? 0 : (b > 255 ? 255 : b);
301 return RGB565((uint8_t)r, (uint8_t)g, (uint8_t)b);
302 }
303
304 void clearScreen() {
305 int width = (int)tftDriver.width();
306 int height = (int)tftDriver.height();
307 SurfaceRGB565 band(width, kBandHeight, FontRGB565);
308 band.begin();
309 band.clear(RGB565(0, 0, 0));
310 for (int y = 0; y < height; y += kBandHeight) {
311 tftDriver.writeData(band, 0, y);
312 }
313 band.end();
314 }
315
316 uint32_t getWriteTimeMs() const { return write_time_ms; }
317
318 protected:
323 DisplayDriver<RGB565>& tftDriver;
324 LCDBoard* p_board = nullptr;
325 bool scale_to_fit = false;
327 bool skip_render = false;
335
339 Vector<RGB565> scale_frame_buf_{0, DefaultESP32AllocatorRAM};
342 Vector<RGB565> scale_line_{0, DefaultESP32AllocatorRAM};
349 Vector<int> scale_x_lut_{0, DefaultESP32AllocatorRAM};
350 Vector<int> scale_y_lut_{0, DefaultESP32AllocatorRAM};
359 Vector<RGB565> i420_rgb_buf_{0, DefaultESP32AllocatorRAM};
360 uint32_t write_time_ms = 0;
361};
362
363} // namespace audio_tools
#define HIGH
Definition Arduino.h:46
#define OUTPUT
Definition Arduino.h:38
#define LOGE(...)
Definition AudioLoggerIDF.h:30
Bridges VideoDecoder's write() calls to a TinyGPU DisplayDriver (e.g. ILI9341Driver,...
Definition OutputTinyGPU.h:30
uint32_t write_time_ms
Definition OutputTinyGPU.h:360
OutputTinyGPU(LCDBoard &board, int bandHeight=40)
Definition OutputTinyGPU.h:52
VideoInfoSource * p_info
Definition OutputTinyGPU.h:321
Vector< int > scale_y_lut_
Definition OutputTinyGPU.h:350
int kBandHeight
Definition OutputTinyGPU.h:320
Vector< RGB565 > scale_frame_buf_
Definition OutputTinyGPU.h:339
VideoInfo info_
Definition OutputTinyGPU.h:322
int kPinBacklight
Definition OutputTinyGPU.h:319
int scale_lut_src_h_
Definition OutputTinyGPU.h:352
Vector< RGB565 > i420_rgb_buf_
Definition OutputTinyGPU.h:359
bool scale_single_buffer
Definition OutputTinyGPU.h:326
uint32_t getWriteTimeMs() const
Optional: returns the time (ms) spent in the last write() call.
Definition OutputTinyGPU.h:316
bool begin()
Definition OutputTinyGPU.h:58
void writeI420(const uint8_t *data, int srcW, int srcH)
Definition OutputTinyGPU.h:260
int scale_lut_src_w_
Definition OutputTinyGPU.h:351
size_t write(const uint8_t *data, size_t len) override
Definition OutputTinyGPU.h:121
LCDBoard * p_board
Definition OutputTinyGPU.h:324
bool skip_render
Definition OutputTinyGPU.h:327
int scale_lut_dst_w_
Definition OutputTinyGPU.h:353
OutputTinyGPU(DisplayDriver< RGB565 > &driver, int pinBacklight, int bandHeight=40)
Definition OutputTinyGPU.h:40
void setSkipRender(bool skip) override
Definition OutputTinyGPU.h:119
void clearScreen()
Definition OutputTinyGPU.h:304
Vector< int > scale_x_lut_
Definition OutputTinyGPU.h:349
void updateScaleLuts(int srcW, int srcH, int width, int height)
Definition OutputTinyGPU.h:232
int scale_lut_dst_h_
Definition OutputTinyGPU.h:354
void writeScaled(const uint8_t *data, int srcW, int srcH)
Definition OutputTinyGPU.h:182
void setScaleToFit(bool enable)
Definition OutputTinyGPU.h:95
bool scale_to_fit
Definition OutputTinyGPU.h:325
void setVideoInfo(VideoInfo info)
Defines the video format and dimensions - call before begin() if you.
Definition OutputTinyGPU.h:86
Vector< RGB565 > scale_line_
Definition OutputTinyGPU.h:342
DisplayDriver< RGB565 > & tftDriver
Definition OutputTinyGPU.h:323
static RGB565 yuvToRgb565(uint8_t Y, uint8_t U, uint8_t V)
Definition OutputTinyGPU.h:291
void setVideoInfoSource(VideoInfoSource &source) override
Defines the source of the video information (width, height, fps, format)
Definition OutputTinyGPU.h:83
void setRotation(DisplayRotation rotation)
Definition OutputTinyGPU.h:80
void setScaleSingleBuffer(bool enable)
Definition OutputTinyGPU.h:110
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
bool resize(size_t newSize, T value)
Definition Vector.h:266
T * data()
Definition Vector.h:316
int size()
Definition Vector.h:178
Provider of VideoInfo (width/height/fps/format) for whoever needs to size buffers/panel setup against...
Definition VideoOutput.h:92
virtual VideoInfo videoInfo()=0
Abstract class for video playback. This class is used to assemble a complete video frame in memory....
Definition VideoOutput.h:107
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
void digitalWrite(digital_pin_t pin, bool value)
Definition Arduino.h:317
void pinMode(digital_pin_t pin, int mode)
Definition Arduino.h:286
uint32_t millis()
Returns the milliseconds since the start.
Definition Arduino.h:260
Basic video information (width/height/codec/frame size), analogous to AudioInfo - common to both Demu...
Definition VideoOutput.h:49
uint16_t height
Frame height in pixels.
Definition VideoOutput.h:53
uint16_t width
Frame width in pixels.
Definition VideoOutput.h:51
VideoFormat format
Video codec - VideoFormat::UNKNOWN if not (yet) determined.
Definition VideoOutput.h:58