We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
miniVideoPlayer.ino
/* * require libraries: * https://github.com/lovyan03/LovyanGFX.git * https://github.com/earlephilhower/ESP8266Audio.git * https://github.com/bitbank2/JPEGDEC.git */ #define FPS 20 #define MJPEG_BUFFER_SIZE (480 * 270 * 2 / 10) #include <WiFi.h> #include <FS.h> #include <SD_MMC.h> #include <driver/i2s.h> #define SD_CS 41 #define SDMMC_CMD 40 #define SDMMC_CLK 39 #define SDMMC_D0 38 #define I2C_SDA 6 #define I2C_SCL 5 #define TP_INT 7 #define TP_RST -1 #include "LovyanGFX_Driver.h" LGFX tft; //MP3音频 #include <AudioFileSourceFS.h> #include <AudioGeneratorMP3.h> #include <AudioOutputI2S.h> static AudioGeneratorMP3 *mp3 = NULL; static AudioFileSourceFS *aFile = NULL; static AudioOutputI2S *out = NULL; //MJPEG视频 #include "MjpegClass.h" static MjpegClass mjpeg; static unsigned long total_show_video = 0; //总节目视频 int noFiles = 0; // SD卡根目录下的媒体文件数量 String videoFilename; String audioFilename; int fileNo = 1; // 首先播放哪个(视频、音频)文件对的变量 bool buttonPressed = false; // 文件更改按钮按下变量 bool fullPlaythrough = true; unsigned long lastDebounceTime = 0; unsigned long debounceDelay = 50; File root; void IRAM_ATTR incrFileNo() { if ((millis() - lastDebounceTime) > debounceDelay) { fileNo += 1; if (fileNo > noFiles) //循环一圈 { fileNo = 0; } buttonPressed = true; lastDebounceTime = millis(); fullPlaythrough = false; Serial.print("Button Pressed! Current Debounce Time: "); Serial.println(lastDebounceTime); } } void setup() { WiFi.mode(WIFI_OFF); Serial.begin(115200); pinMode(TP_INT, INPUT_PULLUP); attachInterrupt(TP_INT, incrFileNo, RISING); // 初始化音频 out = new AudioOutputI2S(0, 1, 128); mp3 = new AudioGeneratorMP3(); aFile = new AudioFileSourceFS(SD_MMC); // 初始化视频 tft.init(); tft.initDMA(); tft.startWrite(); tft.fillScreen(TFT_BLACK); SD_MMC.setPins(SDMMC_CLK, SDMMC_CMD, SDMMC_D0); if (!SD_MMC.begin("/root", true)) /* 1-bit SD bus mode */ { Serial.println("ERROR: File system mount failed!"); tft.println("ERROR: File system mount failed!"); } else { root = SD_MMC.open("/"); noFiles = getNoFiles(root); Serial.print("Found "); Serial.print(noFiles); Serial.println(" in root directory!"); Serial.println("Starting playback!"); } } // 是音视频对 bool isAudioVideoPair(File const &entry, File dir) { String name = entry.path(); if (!name.endsWith(".mjpeg")) return false; if (SD_MMC.exists(name.substring(0, name.length() - 6) + ".mp3")) return true; return false; } // 获取无文件 int getNoFiles(File dir) { while (true) { File entry = dir.openNextFile(); if (!entry) { // no more files break; } if (entry.isDirectory() || !isAudioVideoPair(entry, dir)) { // Skip file if in subfolder entry.close(); // Close folder entry } else { entry.close(); ++noFiles; } } return noFiles; } //获取文件名 void getFilenames(File dir, int fileNo) { int fileCounter = 0; while (true) { File entry = dir.openNextFile(); if (!entry) { // 没有更多文件 break; } if (entry.isDirectory() || !isAudioVideoPair(entry, dir)) { // 如果文件位于子文件夹中则跳过文件 entry.close(); // 关闭文件夹目录 } else //有效的音频/视频对 { ++fileCounter; if (fileCounter == fileNo) { videoFilename = entry.path(); audioFilename = videoFilename.substring(0, videoFilename.length() - 6) + ".mp3"; Serial.print("Loading video: "); Serial.println(videoFilename); Serial.print("Loading audio: "); Serial.println(audioFilename); } entry.close(); } } } //播放视频 void playVideo(String videoFilename, String audioFilename) { int next_frame = 0; int skipped_frames = 0; unsigned long total_play_audio = 0; unsigned long total_read_video = 0; unsigned long total_decode_video = 0; unsigned long start_ms, curr_ms, next_frame_ms; int brightPWM = 0; Serial.println("In playVideo() loop!"); if (mp3 && mp3->isRunning()) mp3->stop(); if (!aFile->open(audioFilename.c_str())) Serial.println(F("Failed to open audio file")); Serial.println("Created aFile!"); File vFile = SD_MMC.open(videoFilename); Serial.println("Created vFile!"); uint8_t *mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE); if (!mjpeg_buf) { Serial.println(F("mjpeg_buf malloc failed!")); } else { // init Video mjpeg.setup(&vFile, mjpeg_buf, drawMCU, false, true); // MJPEG SETUP -> bool setup(Stream *input, uint8_t *mjpeg_buf, JPEG_DRAW_CALLBACK *pfnDraw, bool enableMultiTask, bool useBigEndian) } if (!vFile || vFile.isDirectory()) { Serial.println(("ERROR: Failed to open " + videoFilename + ".mjpeg file for reading")); tft.println(("ERROR: Failed to open " + videoFilename + ".mjpeg file for reading")); } else { // init audio if (!mp3->begin(aFile, out)) Serial.println(F("Failed to start audio!")); start_ms = millis(); curr_ms = start_ms; next_frame_ms = start_ms + (++next_frame * 1000 / FPS); while (vFile.available() && buttonPressed == false) { // Read video mjpeg.readMjpegBuf(); total_read_video += millis() - curr_ms; curr_ms = millis(); if (millis() < next_frame_ms) // check show frame or skip frame { // Play video mjpeg.drawJpg(); total_decode_video += millis() - curr_ms; } else { ++skipped_frames; Serial.println(F("Skip frame")); } curr_ms = millis(); // Play audio if (mp3->isRunning() && !mp3->loop()) { mp3->stop(); } total_play_audio += millis() - curr_ms; while (millis() < next_frame_ms) { vTaskDelay(1); } curr_ms = millis(); next_frame_ms = start_ms + (++next_frame * 1000 / FPS); } if (fullPlaythrough == false) { mp3->stop(); } buttonPressed = false; // reset buttonPressed boolean int time_used = millis() - start_ms; int total_frames = next_frame - 1; Serial.println(F("MP3 audio MJPEG video end")); vFile.close(); aFile->close(); } if (mjpeg_buf) { free(mjpeg_buf); } } // pixel drawing callback static int drawMCU(JPEGDRAW *pDraw) { unsigned long s = millis(); if (tft.getStartCount() > 0) { tft.endWrite(); } tft.pushImageDMA(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight, (lgfx::swap565_t *)pDraw->pPixels); total_show_video += millis() - s; return 1; } void loop() { root = SD_MMC.open("/"); Serial.print("fileNo: "); Serial.println(fileNo); getFilenames(root, fileNo);//获取文件名 playVideo(videoFilename, audioFilename);//播放视频 if (fullPlaythrough == true) // 检查 fullPlaythrough 布尔值以避免 fileNo 的双重增量 { fileNo = fileNo + 1; // 增加 fileNo 以播放下一个视频 if (fileNo > noFiles) // 如果超出文件数,则重置计数器 { fileNo = 1; } } else // 重置 fullPlaythrough 布尔值 { fullPlaythrough = true; } }
LovyanGFX.h
#include <LovyanGFX.hpp> class LGFX : public lgfx::LGFX_Device { lgfx::Panel_ST7796 _panel_instance; lgfx::Bus_Parallel8 _bus_instance; lgfx::Light_PWM _light_instance; public: LGFX(void) { { auto cfg = _bus_instance.config(); cfg.port = 0; cfg.freq_write = 40000000; cfg.pin_wr = 47; // WR を接続しているピン番号 cfg.pin_rd = -1; // RD を接続しているピン番号 cfg.pin_rs = 0; // RS(D/C)を接続しているピン番号 cfg.pin_d0 = 9; // D0を接続しているピン番号 cfg.pin_d1 = 46; // D1を接続しているピン番号 cfg.pin_d2 = 3; // D2を接続しているピン番号 cfg.pin_d3 = 8; // D3を接続しているピン番号 cfg.pin_d4 = 18; // D4を接続しているピン番号 cfg.pin_d5 = 17; // D5を接続しているピン番号 cfg.pin_d6 = 16; // D6を接続しているピン番号 cfg.pin_d7 = 15; // D7を接続しているピン番号 _bus_instance.config(cfg); // 設定値をバスに反映します。 _panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。 } { // 表示パネル制御の設定を行います。 auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。 cfg.pin_cs = -1; // CSが接続されているピン番号 (-1 = disable) cfg.pin_rst = 4; // RSTが接続されているピン番号 (-1 = disable) cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable) // ※ 以下の設定値はパネル毎に一般的な初期値が設定さ BUSYが接続されているピン番号 (-1 = disable)れていますので、不明な項目はコメントアウトして試してみてください。 cfg.memory_width = 320; // ドライバICがサポートしている最大の幅 cfg.memory_height = 480; // ドライバICがサポートしている最大の高さ cfg.panel_width = 320; // 実際に表示可能な幅 cfg.panel_height = 480; // 実際に表示可能な高さ cfg.offset_x = 0; // パネルのX方向オフセット量 cfg.offset_y = 0; // パネルのY方向オフセット量 cfg.offset_rotation = 1; //值在旋转方向的偏移0~7(4~7是倒置的) cfg.dummy_read_pixel = 8; // 在读取像素之前读取的虚拟位数 cfg.dummy_read_bits = 1; // 读取像素以外的数据之前的虚拟读取位数 cfg.readable = false; // 如果可以读取数据,则设置为 true cfg.invert = true; // 如果面板的明暗反转,则设置为 true cfg.rgb_order = false; // 如果面板的红色和蓝色被交换,则设置为 true cfg.dlen_16bit = false; // 对于以 16 位单位发送数据长度的面板,设置为 true cfg.bus_shared = false; // 如果总线与 SD 卡共享,则设置为 true(使用 drawJpgFile 等执行总线控制) _panel_instance.config(cfg); } { // バックライト制御の設定を行います。(必要なければ削除) auto cfg = _light_instance.config(); // バックライト設定用の構造体を取得します。 cfg.pin_bl = 45; // バックライトが接続されているピン番号 cfg.invert = false; // バックライトの輝度を反転させる場合 true cfg.freq = 44100; // バックライトのPWM周波数 cfg.pwm_channel = 1; // 使用するPWMのチャンネル番号 _light_instance.config(cfg); _panel_instance.setLight(&_light_instance); // バックライトをパネルにセットします。 } setPanel(&_panel_instance); // 使用するパネルをセットします。 } };
Serial port print information
ESP-ROM:esp32s3-20210327 Build:Mar 27 2021 rst:0x15 (USB_UART_CHIP_RESET),boot:0xa (SPI_FAST_FLASH_BOOT) Saved PC:0x4209347e SPIWP:0xee mode:DIO, clock div:1 load:0x3fce3808,len:0x44c load:0x403c9700,len:0xbe4 load:0x403cc700,len:0x2a68 entry 0x403c98d4 Found 3 in root directory! Starting playback! fileNo: 1 Loading video: /02.mjpeg Loading audio: /02.mp3 In playVideo() loop! Created aFile! Created vFile! Failed to start audio!
The text was updated successfully, but these errors were encountered:
Files in SD card
ffmpeg
ffmpeg -i input.mp4 -ar 44100 -ac 1 -ab 32k -filter:a loudnorm -filter:a "volume=-5dB" output.mp3
ffmpeg -i input.mp4 -vf "fps=20,scale=-1:272:flags=lanczos,crop=480:in_h:(in_w-480)/2:0" -q:v 9 output.mjpeg
Sorry, something went wrong.
#define I2S_DOUT 37 #define I2S_BCLK 36 #define I2S_LRC 35 // 初始化音频 out = new AudioOutputI2S(I2S_NUM_0); out->SetGain(0.5); //设置音量0~1 out->SetRate(44100); //采样率 out->SetChannels(1);//1单声道,2立体声 out->SetBitsPerSample(16); //指定每个样本的位数,通常为8或16 out->SetPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); mp3 = new AudioGeneratorMP3(); aFile = new AudioFileSourceFS(SD_MMC);
ESP-ROM:esp32s3-20210327 Build:Mar 27 2021 rst:0x15 (USB_UART_CHIP_RESET),boot:0x8 (SPI_FAST_FLASH_BOOT) Saved PC:0x420934d2 SPIWP:0xee mode:DIO, clock div:1 load:0x3fce3808,len:0x44c load:0x403c9700,len:0xbe4 load:0x403cc700,len:0x2a68 entry 0x403c98d4 Found 6 in root directory! Starting playback! fileNo: 1 Loading video: /02.mjpeg Loading audio: /02.mp3 In playVideo() loop! Created aFile! Created vFile! Skip frame Skip frame Skip frame Skip frame Skip frame Skip frame Skip frame Skip frame Skip frame Skip frame Skip frame Skip frame Skip frame
No branches or pull requests
The text was updated successfully, but these errors were encountered: