diff --git a/ESP32_VideoPlayer/MiniLegoTV/MiniLegoTV.ino b/ESP32_VideoPlayer/MiniLegoTV/MiniLegoTV.ino new file mode 100755 index 0000000..28e44b5 --- /dev/null +++ b/ESP32_VideoPlayer/MiniLegoTV/MiniLegoTV.ino @@ -0,0 +1,249 @@ +///////////////////////////////////////////////////////////////// +/* + Mini Lego TV based on ESP32 + For More Information: https://youtu.be/2TOVohmUqOE + Created by Eric N. (ThatProject) +*/ +///////////////////////////////////////////////////////////////// +// This project is based on a Mini Retro TV. +// https://www.instructables.com/Mini-Retro-TV/ +///////////////////////////////////////////////////////////////// + +/*** + * Required libraries: + * https://github.com/moononournation/Arduino_GFX.git + * https://github.com/pschatzmann/arduino-libhelix.git + * https://github.com/bitbank2/JPEGDEC.git + */ + +/*** +* Added libraries: +* https://github.com/vshymanskyy/Preferences +* https://github.com/fbiego/CST816S +*/ + +#define FPS 30 +#define MJPEG_BUFFER_SIZE (288 * 240 * 2 / 8) +#define AUDIOASSIGNCORE 1 +#define DECODEASSIGNCORE 0 +#define DRAWASSIGNCORE 1 + +#include "config.h" +#include +#include +#include +#include + +Preferences preferences; +#define APP_NAME "video_player" +#define K_VIDEO_INDEX "video_index" +#define BASE_PATH "/Videos/" +#define AAC_FILENAME "/44100.aac" +#define MJPEG_FILENAME "/288_30fps.mjpeg" +#define VIDEO_COUNT 20 + +/* Arduino_GFX */ +#include +Arduino_DataBus *bus = new Arduino_ESP32SPI(DC, CS, SCK, MOSI, MISO, VSPI); +Arduino_GFX *gfx = new Arduino_ST7789(bus, RST, 1, true, ST7789_TFTWIDTH, ST7789_TFTHEIGHT, 0, 20, 0, 20); + +/* Audio */ +#include "esp32_audio_task.h" + +/* MJPEG Video */ +#include "mjpeg_decode_draw_task.h" + +/* Touch */ +#include +CST816S touch(TOUCH_SDA, TOUCH_SCL, TOUCH_RST, TOUCH_IRQ); + +/* Variables */ +static int next_frame = 0; +static int skipped_frames = 0; +static unsigned long start_ms, curr_ms, next_frame_ms; +static unsigned int video_idx = 1; + +// pixel drawing callback +static int drawMCU(JPEGDRAW *pDraw) { + unsigned long s = millis(); + gfx->draw16bitRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight); + total_show_video_ms += millis() - s; + return 1; +} /* drawMCU() */ + +void setup() { + disableCore0WDT(); + + WiFi.mode(WIFI_OFF); + Serial.begin(115200); + + // Init Display + gfx->begin(80000000); + gfx->fillScreen(BLACK); + pinMode(BLK, OUTPUT); + digitalWrite(BLK, HIGH); + + xTaskCreate( + touchTask, + "touchTask", + 2000, + NULL, + 1, + NULL); + + Serial.println("Init I2S"); + + esp_err_t ret_val = i2s_init(I2S_NUM_0, 44100, I2S_MCLK, I2S_SCLK, I2S_LRCLK, I2S_DOUT, I2S_DIN); + if (ret_val != ESP_OK) { + Serial.printf("i2s_init failed: %d\n", ret_val); + gfx->println("i2s_init failed"); + return; + } + i2s_zero_dma_buffer(I2S_NUM_0); + + Serial.println("Init FS"); + + SPIClass spi = SPIClass(HSPI); + spi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS); + if (!SD.begin(SD_CS, spi, 80000000)) { + Serial.println("ERROR: File system mount failed!"); + gfx->println("ERROR: File system mount failed!"); + return; + } + + preferences.begin(APP_NAME, false); + video_idx = preferences.getUInt(K_VIDEO_INDEX, 1); + Serial.printf("videoIndex: %d\n", video_idx); + + gfx->setCursor(20, 20); + gfx->setTextColor(GREEN); + gfx->setTextSize(6, 6, 0); + gfx->printf("CH %d", video_idx); + delay(1000); + + playVideoWithAudio(video_idx); +} + +void loop() { +} + +void touchTask(void *parameter) { + touch.begin(); + while (1) { + if (touch.available()) { + Serial.printf("x: %d, y: %d\n", touch.data.x, touch.data.y); + switch (touch.data.gestureID) { + case SWIPE_LEFT: + Serial.println("SWIPE_LEFT"); + break; + case SWIPE_RIGHT: + Serial.println("SWIPE_RIGHT"); + break; + case SWIPE_UP: + Serial.println("SWIPE_UP (RIGHT)"); + videoController(-1); + break; + case SWIPE_DOWN: + Serial.println("SWIPE_DOWN (LEFT)"); + videoController(1); + break; + case SINGLE_CLICK: + Serial.println("SINGLE_CLICK"); + break; + case DOUBLE_CLICK: + Serial.println("DOUBLE_CLICK"); + break; + case LONG_PRESS: + Serial.println("LONG_PRESS"); + break; + } + } + vTaskDelay(1000); + } +} + +void playVideoWithAudio(int channel) { + + char aFilePath[40]; + sprintf(aFilePath, "%s%d%s", BASE_PATH, channel, AAC_FILENAME); + + File aFile = SD.open(aFilePath); + if (!aFile || aFile.isDirectory()) { + Serial.printf("ERROR: Failed to open %s file for reading\n", aFilePath); + gfx->printf("ERROR: Failed to open %s file for reading\n", aFilePath); + return; + } + + char vFilePath[40]; + sprintf(vFilePath, "%s%d%s", BASE_PATH, channel, MJPEG_FILENAME); + + File vFile = SD.open(vFilePath); + if (!vFile || vFile.isDirectory()) { + Serial.printf("ERROR: Failed to open %s file for reading\n", vFilePath); + gfx->printf("ERROR: Failed to open %s file for reading\n", vFilePath); + return; + } + + Serial.println("Init video"); + + mjpeg_setup(&vFile, MJPEG_BUFFER_SIZE, drawMCU, false, DECODEASSIGNCORE, DRAWASSIGNCORE); + + Serial.println("Start play audio task"); + + BaseType_t ret = aac_player_task_start(&aFile, AUDIOASSIGNCORE); + + if (ret != pdPASS) { + Serial.printf("Audio player task start failed: %d\n", ret); + gfx->printf("Audio player task start failed: %d\n", ret); + } + + Serial.println("Start play video"); + + start_ms = millis(); + curr_ms = millis(); + next_frame_ms = start_ms + (++next_frame * 1000 / FPS / 2); + while (vFile.available() && mjpeg_read_frame()) // Read video + { + total_read_video_ms += millis() - curr_ms; + curr_ms = millis(); + + if (millis() < next_frame_ms) // check show frame or skip frame + { + // Play video + mjpeg_draw_frame(); + total_decode_video_ms += millis() - curr_ms; + curr_ms = millis(); + } else { + ++skipped_frames; + Serial.println("Skip frame"); + } + + while (millis() < next_frame_ms) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + + curr_ms = millis(); + next_frame_ms = start_ms + (++next_frame * 1000 / FPS); + } + int time_used = millis() - start_ms; + int total_frames = next_frame - 1; + Serial.println("AV end"); + vFile.close(); + aFile.close(); + + videoController(1); +} + +void videoController(int next) { + + video_idx += next; + if (video_idx <= 0) { + video_idx = VIDEO_COUNT; + } else if (video_idx > VIDEO_COUNT) { + video_idx = 1; + } + Serial.printf("video_idx : %d\n", video_idx); + preferences.putUInt(K_VIDEO_INDEX, video_idx); + preferences.end(); + esp_restart(); +} \ No newline at end of file diff --git a/ESP32_VideoPlayer/MiniLegoTV/config.h b/ESP32_VideoPlayer/MiniLegoTV/config.h new file mode 100644 index 0000000..47effcd --- /dev/null +++ b/ESP32_VideoPlayer/MiniLegoTV/config.h @@ -0,0 +1,23 @@ +#define CS 5 +#define SCK 18 +#define MOSI 23 +#define MISO -1 +#define DC 27 +#define RST 33 +#define BLK 22 + +#define I2S_MCLK -1 +#define I2S_SCLK 25 +#define I2S_LRCLK 26 +#define I2S_DOUT 32 +#define I2S_DIN -1 + +#define SD_SCK 14 +#define SD_MOSI 15 +#define SD_MISO 4 +#define SD_CS 13 + +#define TOUCH_SDA 17 +#define TOUCH_SCL 16 +#define TOUCH_RST 19 +#define TOUCH_IRQ 39 \ No newline at end of file diff --git a/ESP32_VideoPlayer/MiniLegoTV/esp32_audio_task.h b/ESP32_VideoPlayer/MiniLegoTV/esp32_audio_task.h new file mode 100755 index 0000000..0dfc6c2 --- /dev/null +++ b/ESP32_VideoPlayer/MiniLegoTV/esp32_audio_task.h @@ -0,0 +1,163 @@ +#include "driver/i2s.h" + +#include "AACDecoderHelix.h" +#include "MP3DecoderHelix.h" + +static unsigned long total_read_audio_ms = 0; +static unsigned long total_decode_audio_ms = 0; +static unsigned long total_play_audio_ms = 0; + +static i2s_port_t _i2s_num; +static esp_err_t i2s_init(i2s_port_t i2s_num, uint32_t sample_rate, + int mck_io_num, /*!< MCK in out pin. Note that ESP32 supports setting MCK on GPIO0/GPIO1/GPIO3 only*/ + int bck_io_num, /*!< BCK in out pin*/ + int ws_io_num, /*!< WS in out pin*/ + int data_out_num, /*!< DATA out pin*/ + int data_in_num /*!< DATA in pin*/ +) +{ + _i2s_num = i2s_num; + + esp_err_t ret_val = ESP_OK; + + i2s_config_t i2s_config; + i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); + i2s_config.sample_rate = sample_rate; + i2s_config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT; + i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; + i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S; + i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; + i2s_config.dma_buf_count = 8; + i2s_config.dma_buf_len = 160; + i2s_config.use_apll = false; + i2s_config.tx_desc_auto_clear = true; + i2s_config.fixed_mclk = 0; + i2s_config.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT; + i2s_config.bits_per_chan = I2S_BITS_PER_CHAN_16BIT; + + i2s_pin_config_t pin_config; + pin_config.mck_io_num = mck_io_num; + pin_config.bck_io_num = bck_io_num; + pin_config.ws_io_num = ws_io_num; + pin_config.data_out_num = data_out_num; + pin_config.data_in_num = data_in_num; + + ret_val |= i2s_driver_install(i2s_num, &i2s_config, 0, NULL); + ret_val |= i2s_set_pin(i2s_num, &pin_config); + + return ret_val; +} + +static int _samprate = 0; +static void aacAudioDataCallback(AACFrameInfo &info, int16_t *pwm_buffer, size_t len) +{ + unsigned long s = millis(); + if (_samprate != info.sampRateOut) + { + // log_i("bitRate: %d, nChans: %d, sampRateCore: %d, sampRateOut: %d, bitsPerSample: %d, outputSamps: %d, profile: %d, tnsUsed: %d, pnsUsed: %d", + // info.bitRate, info.nChans, info.sampRateCore, info.sampRateOut, info.bitsPerSample, info.outputSamps, info.profile, info.tnsUsed, info.pnsUsed); + i2s_set_clk(_i2s_num, info.sampRateOut /* sample_rate */, info.bitsPerSample /* bits_cfg */, (info.nChans == 2) ? I2S_CHANNEL_STEREO : I2S_CHANNEL_MONO /* channel */); + _samprate = info.sampRateOut; + } + size_t i2s_bytes_written = 0; + i2s_write(_i2s_num, pwm_buffer, len * 2, &i2s_bytes_written, portMAX_DELAY); + // log_i("len: %d, i2s_bytes_written: %d", len, i2s_bytes_written); + total_play_audio_ms += millis() - s; +} +static void mp3AudioDataCallback(MP3FrameInfo &info, int16_t *pwm_buffer, size_t len) +{ + unsigned long s = millis(); + if (_samprate != info.samprate) + { + log_i("bitrate: %d, nChans: %d, samprate: %d, bitsPerSample: %d, outputSamps: %d, layer: %d, version: %d", + info.bitrate, info.nChans, info.samprate, info.bitsPerSample, info.outputSamps, info.layer, info.version); + i2s_set_clk(_i2s_num, info.samprate /* sample_rate */, info.bitsPerSample /* bits_cfg */, (info.nChans == 2) ? I2S_CHANNEL_STEREO : I2S_CHANNEL_MONO /* channel */); + _samprate = info.samprate; + } + size_t i2s_bytes_written = 0; + i2s_write(_i2s_num, pwm_buffer, len * 2, &i2s_bytes_written, portMAX_DELAY); + // log_i("len: %d, i2s_bytes_written: %d", len, i2s_bytes_written); + total_play_audio_ms += millis() - s; +} + +static uint8_t _frame[MP3_MAX_FRAME_SIZE]; // MP3_MAX_FRAME_SIZE is smaller, so always use MP3_MAX_FRAME_SIZE + +static libhelix::AACDecoderHelix _aac(aacAudioDataCallback); +static void aac_player_task(void *pvParam) +{ + Stream *input = (Stream *)pvParam; + + int r, w; + unsigned long ms = millis(); + while (r = input->readBytes(_frame, MP3_MAX_FRAME_SIZE)) + { + total_read_audio_ms += millis() - ms; + ms = millis(); + + while (r > 0) + { + w = _aac.write(_frame, r); + // log_i("r: %d, w: %d\n", r, w); + r -= w; + } + total_decode_audio_ms += millis() - ms; + ms = millis(); + } + log_i("AAC stop."); + + vTaskDelete(NULL); +} + +static libhelix::MP3DecoderHelix _mp3(mp3AudioDataCallback); +static void mp3_player_task(void *pvParam) +{ + Stream *input = (Stream *)pvParam; + + int r, w; + unsigned long ms = millis(); + while (r = input->readBytes(_frame, MP3_MAX_FRAME_SIZE)) + { + total_read_audio_ms += millis() - ms; + ms = millis(); + + while (r > 0) + { + w = _mp3.write(_frame, r); + // log_i("r: %d, w: %d\n", r, w); + r -= w; + } + total_decode_audio_ms += millis() - ms; + ms = millis(); + } + log_i("MP3 stop."); + + vTaskDelete(NULL); +} + +static BaseType_t aac_player_task_start(Stream *input, BaseType_t audioAssignCore) +{ + _aac.begin(); + + return xTaskCreatePinnedToCore( + (TaskFunction_t)aac_player_task, + (const char *const)"AAC Player Task", + (const uint32_t)2000, + (void *const)input, + (UBaseType_t)configMAX_PRIORITIES - 1, + (TaskHandle_t *const)NULL, + (const BaseType_t)audioAssignCore); +} + +static BaseType_t mp3_player_task_start(Stream *input, BaseType_t audioAssignCore) +{ + _mp3.begin(); + + return xTaskCreatePinnedToCore( + (TaskFunction_t)mp3_player_task, + (const char *const)"MP3 Player Task", + (const uint32_t)2000, + (void *const)input, + (UBaseType_t)configMAX_PRIORITIES - 1, + (TaskHandle_t *const)NULL, + (const BaseType_t)audioAssignCore); +} \ No newline at end of file diff --git a/ESP32_VideoPlayer/MiniLegoTV/mjpeg_decode_draw_task.h b/ESP32_VideoPlayer/MiniLegoTV/mjpeg_decode_draw_task.h new file mode 100755 index 0000000..4de165f --- /dev/null +++ b/ESP32_VideoPlayer/MiniLegoTV/mjpeg_decode_draw_task.h @@ -0,0 +1,300 @@ +#define READ_BUFFER_SIZE 1024 +// #define MAXOUTPUTSIZE (MAX_BUFFERED_PIXELS / 16 / 16) +#define MAXOUTPUTSIZE (288 / 3 / 16) +#define NUMBER_OF_DECODE_BUFFER 3 +#define NUMBER_OF_DRAW_BUFFER 9 + +#include +#include + +typedef struct +{ + int32_t size; + uint8_t *buf; +} mjpegBuf; + +typedef struct +{ + xQueueHandle xqh; + JPEG_DRAW_CALLBACK *drawFunc; +} paramDrawTask; + +typedef struct +{ + xQueueHandle xqh; + mjpegBuf *mBuf; + JPEG_DRAW_CALLBACK *drawFunc; +} paramDecodeTask; + +static JPEGDRAW jpegdraws[NUMBER_OF_DRAW_BUFFER]; +static int _draw_queue_cnt = 0; +static JPEGDEC _jpegDec; +static xQueueHandle _xqh; +static bool _useBigEndian; + +static unsigned long total_read_video_ms = 0; +static unsigned long total_decode_video_ms = 0; +static unsigned long total_show_video_ms = 0; + +Stream *_input; + +int32_t _mjpegBufufSize; + +uint8_t *_read_buf; +int32_t _mjpeg_buf_offset = 0; + +TaskHandle_t _decodeTask; +TaskHandle_t _draw_task; +paramDecodeTask _pDecodeTask; +paramDrawTask _pDrawTask; +uint8_t *_mjpeg_buf; +uint8_t _mBufIdx = 0; + +int32_t _inputindex = 0; +int32_t _buf_read; +int32_t _remain = 0; +mjpegBuf _mjpegBufs[NUMBER_OF_DECODE_BUFFER]; + +static int queueDrawMCU(JPEGDRAW *pDraw) +{ + int len = pDraw->iWidth * pDraw->iHeight * 2; + JPEGDRAW *j = &jpegdraws[_draw_queue_cnt % NUMBER_OF_DRAW_BUFFER]; + j->x = pDraw->x; + j->y = pDraw->y; + j->iWidth = pDraw->iWidth; + j->iHeight = pDraw->iHeight; + memcpy(j->pPixels, pDraw->pPixels, len); + + // log_i("queueDrawMCU start."); + ++_draw_queue_cnt; + xQueueSend(_xqh, &j, portMAX_DELAY); + // log_i("queueDrawMCU end."); + + return 1; +} + +static void decode_task(void *arg) +{ + paramDecodeTask *p = (paramDecodeTask *)arg; + mjpegBuf *mBuf; + log_i("decode_task start."); + while (xQueueReceive(p->xqh, &mBuf, portMAX_DELAY)) + { + // log_i("mBuf->size: %d", mBuf->size); + // log_i("mBuf->buf start: %X %X, end: %X, %X.", mBuf->buf[0], mBuf->buf[1], mBuf->buf[mBuf->size - 2], mBuf->buf[mBuf->size - 1]); + unsigned long s = millis(); + + _jpegDec.openRAM(mBuf->buf, mBuf->size, p->drawFunc); + + // _jpegDec.setMaxOutputSize(MAXOUTPUTSIZE); + if (_useBigEndian) + { + _jpegDec.setPixelType(RGB565_BIG_ENDIAN); + } + _jpegDec.setMaxOutputSize(MAXOUTPUTSIZE); + _jpegDec.decode(0, 0, 0); + _jpegDec.close(); + + total_decode_video_ms += millis() - s; + } + vQueueDelete(p->xqh); + log_i("decode_task end."); + vTaskDelete(NULL); +} + +static void draw_task(void *arg) +{ + paramDrawTask *p = (paramDrawTask *)arg; + JPEGDRAW *pDraw; + log_i("draw_task start."); + while (xQueueReceive(p->xqh, &pDraw, portMAX_DELAY)) + { + // log_i("draw_task work start: x: %d, y: %d, iWidth: %d, iHeight: %d.", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); + p->drawFunc(pDraw); + // log_i("draw_task work end."); + } + vQueueDelete(p->xqh); + log_i("draw_task end."); + vTaskDelete(NULL); +} + +bool mjpeg_setup(Stream *input, int32_t mjpegBufufSize, JPEG_DRAW_CALLBACK *pfnDraw, + bool useBigEndian, BaseType_t decodeAssignCore, BaseType_t drawAssignCore) +{ + _input = input; + _mjpegBufufSize = mjpegBufufSize; + _useBigEndian = useBigEndian; + + for (int i = 0; i < NUMBER_OF_DECODE_BUFFER; ++i) + { + _mjpegBufs[i].buf = (uint8_t *)malloc(mjpegBufufSize); + if (_mjpegBufs[i].buf) + { + log_i("#%d decode buffer allocated.", i); + } + else + { + log_e("#%d decode buffer allocat failed.", i); + } + } + _mjpeg_buf = _mjpegBufs[_mBufIdx].buf; + + if (!_read_buf) + { + _read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE); + } + if (_read_buf) + { + log_i("Read buffer allocated."); + } + + _xqh = xQueueCreate(NUMBER_OF_DRAW_BUFFER, sizeof(JPEGDRAW)); + _pDrawTask.xqh = _xqh; + _pDrawTask.drawFunc = pfnDraw; + _pDecodeTask.xqh = xQueueCreate(NUMBER_OF_DECODE_BUFFER, sizeof(mjpegBuf)); + _pDecodeTask.drawFunc = queueDrawMCU; + + xTaskCreatePinnedToCore( + (TaskFunction_t)decode_task, + (const char *const)"MJPEG decode Task", + (const uint32_t)2000, + (void *const)&_pDecodeTask, + (UBaseType_t)configMAX_PRIORITIES - 1, + (TaskHandle_t *const)&_decodeTask, + (const BaseType_t)decodeAssignCore); + xTaskCreatePinnedToCore( + (TaskFunction_t)draw_task, + (const char *const)"MJPEG Draw Task", + (const uint32_t)2000, + (void *const)&_pDrawTask, + (UBaseType_t)configMAX_PRIORITIES - 1, + (TaskHandle_t *const)&_draw_task, + (const BaseType_t)drawAssignCore); + + for (int i = 0; i < NUMBER_OF_DRAW_BUFFER; i++) + { + if (!jpegdraws[i].pPixels) + { + jpegdraws[i].pPixels = (uint16_t *)heap_caps_malloc(MAXOUTPUTSIZE * 16 * 16 * 2, MALLOC_CAP_DMA); + } + if (jpegdraws[i].pPixels) + { + log_i("#%d draw buffer allocated.", i); + } + else + { + log_e("#%d draw buffer allocat failed.", i); + } + } + + return true; +} + +bool mjpeg_read_frame() +{ + if (_inputindex == 0) + { + _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); + _inputindex += _buf_read; + } + _mjpeg_buf_offset = 0; + int i = 0; + bool found_FFD8 = false; + while ((_buf_read > 0) && (!found_FFD8)) + { + i = 0; + while ((i < _buf_read) && (!found_FFD8)) + { + if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header + { + // log_i("Found FFD8 at: %d.", i); + found_FFD8 = true; + } + ++i; + } + if (found_FFD8) + { + --i; + } + else + { + _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); + } + } + uint8_t *_p = _read_buf + i; + _buf_read -= i; + bool found_FFD9 = false; + if (_buf_read > 0) + { + i = 3; + while ((_buf_read > 0) && (!found_FFD9)) + { + if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer + { + found_FFD9 = true; + } + else + { + while ((i < _buf_read) && (!found_FFD9)) + { + if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer + { + found_FFD9 = true; + ++i; + } + ++i; + } + } + + // log_i("i: %d", i); + memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i); + _mjpeg_buf_offset += i; + int32_t o = _buf_read - i; + if (o > 0) + { + // log_i("o: %d", o); + memcpy(_read_buf, _p + i, o); + _buf_read = _input->readBytes(_read_buf + o, READ_BUFFER_SIZE - o); + _p = _read_buf; + _inputindex += _buf_read; + _buf_read += o; + // log_i("_buf_read: %d", _buf_read); + } + else + { + _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); + _p = _read_buf; + _inputindex += _buf_read; + } + i = 0; + } + if (found_FFD9) + { + // log_i("Found FFD9 at: %d.", _mjpeg_buf_offset); + if (_mjpeg_buf_offset > _mjpegBufufSize) { + log_e("_mjpeg_buf_offset(%d) > _mjpegBufufSize (%d)", _mjpeg_buf_offset, _mjpegBufufSize); + } + return true; + } + } + + return false; +} + +bool mjpeg_draw_frame() +{ + mjpegBuf *mBuf = &_mjpegBufs[_mBufIdx]; + mBuf->size = _mjpeg_buf_offset; + // log_i("_mjpegBufs[%d].size: %d.", _mBufIdx, _mjpegBufs[_mBufIdx].size); + // log_i("_mjpegBufs[%d].buf start: %X %X, end: %X, %X.", _mjpegBufs, _mjpegBufs[_mBufId].buf[0], _mjpegBufs[_mBufIdx].buf[1], _mjpegBufs[_mBufIdx].buf[_mjpeg_buf_offset - 2], _mjpegBufs[_mBufIdx].buf[_mjpeg_buf_offset - 1]); + xQueueSend(_pDecodeTask.xqh, &mBuf, portMAX_DELAY); + ++_mBufIdx; + if (_mBufIdx >= NUMBER_OF_DECODE_BUFFER) + { + _mBufIdx = 0; + } + _mjpeg_buf = _mjpegBufs[_mBufIdx].buf; + // log_i("queue decode_task end"); + + return true; +}