mirror of
https://github.com/0015/ThatProject.git
synced 2026-01-12 01:07:44 +03:00
Mini Lego TV
This commit is contained in:
249
ESP32_VideoPlayer/MiniLegoTV/MiniLegoTV.ino
Executable file
249
ESP32_VideoPlayer/MiniLegoTV/MiniLegoTV.ino
Executable file
@@ -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 <WiFi.h>
|
||||
#include <FS.h>
|
||||
#include <SD.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
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_GFX_Library.h>
|
||||
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.h>
|
||||
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();
|
||||
}
|
||||
23
ESP32_VideoPlayer/MiniLegoTV/config.h
Normal file
23
ESP32_VideoPlayer/MiniLegoTV/config.h
Normal file
@@ -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
|
||||
163
ESP32_VideoPlayer/MiniLegoTV/esp32_audio_task.h
Executable file
163
ESP32_VideoPlayer/MiniLegoTV/esp32_audio_task.h
Executable file
@@ -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);
|
||||
}
|
||||
300
ESP32_VideoPlayer/MiniLegoTV/mjpeg_decode_draw_task.h
Executable file
300
ESP32_VideoPlayer/MiniLegoTV/mjpeg_decode_draw_task.h
Executable file
@@ -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 <FS.h>
|
||||
#include <JPEGDEC.h>
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user