[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/esp-cpp/camera-streamer/main/main/main.cpp [Back]  [Original]

#include "sdkconfig.h"

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#include 
#include 
#include 
#include 
#include 

#include "cli.hpp"
#include "heap_monitor.hpp"
#include "mjpeg_packetizer.hpp"
#include "nvs.hpp"
#include "rtsp_server.hpp"
#include "task.hpp"
#include "task_monitor.hpp"
#include "wifi_sta.hpp"
#include "wifi_sta_menu.hpp"

#include "esp_camera.h"

#if CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
#include 
#include 

#include "generic_packetizer.hpp"
#include "xiao-esp32s3-sense.hpp"
#else
#include "esp32-timer-cam.hpp"
#endif

using namespace std::chrono_literals;

#if CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
using Board = espp::XiaoEsp32S3Sense;
static constexpr const char *board_name = "Seeed Studio XIAO ESP32S3 Sense";
static constexpr const char *mdns_hostname_prefix = "xiao-camera-streamer";
static constexpr const char *mdns_instance_name = "XIAO Camera Streamer";
#else
using Board = espp::EspTimerCam;
static constexpr const char *board_name = "M5Stack ESP32 TimerCam";
static constexpr const char *mdns_hostname_prefix = "camera-streamer";
static constexpr const char *mdns_instance_name = "Camera Streamer";
#endif

static espp::Logger logger({.tag = "Camera Streamer", .level = espp::Logger::Verbosity::INFO});

namespace {
constexpr auto idle_capture_poll_period = 250ms;
constexpr auto dma_pressure_backoff = 250ms;
constexpr auto camera_capture_error_backoff = 100ms;
constexpr auto target_video_period = 100ms;
constexpr size_t min_dma_free_bytes_for_streaming = 12 * 1024;
constexpr size_t min_dma_largest_block_for_streaming = 4 * 1024;

constexpr int video_track_id = 0;
constexpr char rtsp_path[] = "mjpeg/1";

#if CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
constexpr auto audio_capture_timeout = 50ms;
constexpr int audio_track_id = 1;
constexpr uint32_t microphone_sample_rate_hz =
    espp::XiaoEsp32S3Sense::microphone_default_sample_rate_hz();
constexpr size_t microphone_frame_samples = microphone_sample_rate_hz / 50; // 20 ms
constexpr size_t microphone_frame_bytes = microphone_frame_samples * sizeof(int16_t);
#endif

// Wait until `deadline`, returning true if the task was asked to stop while waiting.
template 
bool wait_until_or_stop(std::mutex &m, std::condition_variable &cv, bool &task_notified,
                        const std::chrono::time_point &deadline) {
  std::unique_lock lk(m);
  auto stop_requested = cv.wait_until(lk, deadline, [&task_notified] { return task_notified; });
  task_notified = false;
  return stop_requested;
}
} // namespace

std::recursive_mutex server_mutex;
std::unique_ptr camera_task;
std::unique_ptr memory_monitor_task;
std::unique_ptr task_monitor;
std::shared_ptr rtsp_server;
bool mdns_started{false};
std::atomic video_frames_streamed{0};

#if CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
std::unique_ptr audio_task;
i2s_chan_handle_t microphone_rx_channel{nullptr};
std::atomic audio_frames_streamed{0};
#endif

esp_err_t initialize_camera(void);
bool start_rtsp_server(std::string_view server_address, int server_port);
void stop_streaming(void);
bool camera_task_fn(std::mutex &m, std::condition_variable &cv, bool &task_notified);
bool memory_monitor_task_fn(std::mutex &m, std::condition_variable &cv, bool &task_notified);

#if CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
esp_err_t initialize_microphone(void);
void deinitialize_microphone(void);
bool initialize_and_test_sdcard(void);
bool audio_task_fn(std::mutex &m, std::condition_variable &cv, bool &task_notified);
#endif

extern "C" void app_main(void) {
  logger.info("Bootup");
  logger.info("Board: {}", board_name);

#if CONFIG_ESP32_WIFI_NVS_ENABLED
  std::error_code ec;
  // initialize NVS, needed for WiFi
  espp::Nvs nvs;
  nvs.init(ec);
#endif

  static constexpr float disconnected_led_breathing_period = 1.0f;
  static constexpr float connected_led_breathing_period = 3.5f;

  auto &board = Board::get();

#if !CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
  // initialize RTC (TimerCam has an onboard BM8563)
  if (!board.initialize_rtc()) {
    logger.error("Could not initialize RTC");
    return;
  }
#endif

  // initialize LED
  if (!board.initialize_led(disconnected_led_breathing_period)) {
    logger.error("Could not initialize LED");
    return;
  }
  board.start_led_breathing();

#if CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
  // initialize the (optional) microSD card; continues on failure
  initialize_and_test_sdcard();
#endif

  // initialize camera
  logger.info("Initializing camera");
  auto err = initialize_camera();
  if (err != ESP_OK) {
    logger.error("Could not initialize camera: {} '{}'", err, esp_err_to_name(err));
    return;
  }

#if CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
  // initialize microphone (XIAO ESP32S3 Sense has an onboard PDM microphone)
  logger.info("Initializing microphone");
  err = initialize_microphone();
  if (err != ESP_OK) {
    logger.error("Could not initialize microphone: {} '{}'", err, esp_err_to_name(err));
    deinitialize_microphone();
    return;
  }
#endif

  logger.info("Starting memory monitors");
  task_monitor = std::make_unique(espp::TaskMonitor::Config{.period = 30s});
  memory_monitor_task = espp::Task::make_unique(espp::Task::Config{
      .callback = memory_monitor_task_fn,
      .task_config =
          {
              .name = "Memory Monitor",
              .stack_size_bytes = 6 * 1024,
          },
      .log_level = espp::Logger::Verbosity::WARN,
  });
  memory_monitor_task->start();

  // initialize WiFi
  logger.info("Initializing WiFi");
  espp::WifiSta wifi_sta(
      {.ssid = CONFIG_ESP_WIFI_SSID,
       .password = CONFIG_ESP_WIFI_PASSWORD,
       .num_connect_retries = CONFIG_ESP_MAXIMUM_RETRY,
       .on_connected =
           []() { Board::get().set_led_breathing_period(connected_led_breathing_period); },
       .on_disconnected =
           []() {
             Board::get().set_led_breathing_period(disconnected_led_breathing_period);
             logger.info("WiFi disconnected, stopping RTSP streaming");
             stop_streaming();
           },
       .on_got_ip =
           [](ip_event_got_ip_t *eventdata) {
             auto server_address = fmt::format("{}.{}.{}.{}", IP2STR(&eventdata->ip_info.ip));
             logger.info("got IP: {}", server_address);
             // start from a clean slate in case a previous session is still up
             stop_streaming();

             if (esp_wifi_set_ps(WIFI_PS_NONE) != ESP_OK) {
               logger.warn(
                   "Could not disable WiFi power save; RTSP streaming may hit TX backpressure");
             } else {
               logger.info("Disabled WiFi power save for RTSP streaming");
             }

             if (!start_rtsp_server(server_address, CONFIG_RTSP_SERVER_PORT)) {
               logger.error("RTSP server failed to start, not starting capture tasks");
               return;
             }

             logger.info("Creating camera task");
             auto new_camera_task = espp::Task::make_unique(espp::Task::Config{
                 .callback = camera_task_fn,
                 .task_config = {.name = "Camera Task", .priority = 10},
                 .log_level = espp::Logger::Verbosity::WARN,
             });
             if (!new_camera_task->start()) {
               logger.error("Could not start camera task");
               stop_streaming();
               return;
             }

#if CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
             logger.info("Creating audio task");
             auto new_audio_task = espp::Task::make_unique(espp::Task::Config{
                 .callback = audio_task_fn,
                 .task_config =
                     {
                         .name = "Audio Task",
                         .stack_size_bytes = 6 * 1024,
                         .priority = 9,
                     },
                 .log_level = espp::Logger::Verbosity::WARN,
             });
             if (!new_audio_task->start()) {
               logger.error("Could not start audio task");
               new_camera_task.reset();
               stop_streaming();
               return;
             }
#endif

             std::lock_guard lock(server_mutex);
             camera_task = std::move(new_camera_task);
#if CONFIG_CAMERA_STREAMER_BOARD_XIAO_ESP32S3_SENSE
             audio_task = std::move(new_audio_task);
#endif
           }});

  espp::WifiStaMenu sta_menu(wifi_sta);
  auto root_menu = sta_menu.get();
  root_menu->Insert(
      "memory",
      [](std::ostream &out) {
        out 

Web Proxy Viewer  |  New URL  |  Original Page