Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 97 additions & 58 deletions esphome/components/media_proxy_control/media_proxy_control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "esphome/core/log.h"
#include "esphome/components/network/util.h"
#include <cstring>
#include <strings.h>
#include <algorithm>
#include <cstdio>
#include <cctype>
Expand Down Expand Up @@ -77,7 +78,26 @@ void MediaProxyOutput::set_hw(const std::string &hw) {
}

void MediaProxyOutput::set_format(const std::string &fmt) {
format_ = fmt;
uint8_t pixcfg;
if (strcasecmp(fmt.c_str(), "rgb888") == 0) {
pixcfg = ddp::DDP_PIXCFG_RGB888;
} else if (strcasecmp(fmt.c_str(), "rgb565le") == 0) {
pixcfg = ddp::DDP_PIXCFG_RGB565_LE;
} else if (strcasecmp(fmt.c_str(), "rgb565be") == 0) {
pixcfg = ddp::DDP_PIXCFG_RGB565_BE;
} else if (strcasecmp(fmt.c_str(), "rgbw") == 0) {
pixcfg = ddp::DDP_PIXCFG_RGBW;
} else if (strcasecmp(fmt.c_str(), "rgb565") == 0) {
#if defined(LV_COLOR_16_SWAP) && LV_COLOR_16_SWAP
pixcfg = ddp::DDP_PIXCFG_RGB565_BE;
#else
pixcfg = ddp::DDP_PIXCFG_RGB565_LE;
#endif
} else {
pixcfg = ddp::DDP_PIXCFG_RGB888; // fallback
}

format_pixcfg_ = pixcfg;
if (parent_) {
parent_->send_update(stream_id_);
}
Expand All @@ -99,13 +119,17 @@ static inline void append_json_str(std::string &dst, const char *key, const std:
dst += "\""; dst += key; dst += "\":\""; dst += val; dst += "\"";
}
static inline void append_json_int(std::string &dst, const char *key, long long v) {
dst += "\""; dst += key; dst += "\":"; dst += std::to_string(v);
char buf[64];
snprintf(buf, sizeof(buf), "\"%s\":%lld", key, v);
dst += buf;
}
static inline void append_json_float(std::string &dst, const char *key, double v) {
char buf[64]; snprintf(buf, sizeof(buf), "\"%s\":%.6f", key, v); dst += buf;
}
static inline void append_json_bool(std::string &dst, const char *key, bool v) {
dst += "\""; dst += key; dst += "\":"; dst += (v ? "true" : "false");
char buf[64];
snprintf(buf, sizeof(buf), "\"%s\":%s", key, v ? "true" : "false");
dst += buf;
}

// Task to cleanup websocket client without blocking main thread
Expand All @@ -131,8 +155,9 @@ static std::string build_stream_json_(const char *type,
const std::optional<bool> &loop,
const std::optional<std::string> &hw,
const std::optional<std::string> &fit) {
// Reserve: src (variable) + overhead for all JSON structure/fields
std::string json;
json.reserve(256);
json.reserve(src.length() + 256);
json += "{";
append_json_str(json, "type", type); json += ",";
append_json_int(json, "out", stream_id); json += ",";
Expand Down Expand Up @@ -166,48 +191,41 @@ static void log_stream_line_(const char *label,
const std::optional<bool> &loop,
const std::optional<std::string> &hw,
const std::optional<std::string> &fit) {
std::string pace_str = pace ? std::to_string(*pace) : "(unset)";
std::string ema_str = ema ? std::to_string(*ema) : "(unset)";
std::string expand_str = expand ? std::to_string(*expand) : "(unset)";
// Use stack buffers instead of heap-allocated strings to reduce memory usage
// Buffers persist through entire function scope, so c_str() usage is safe
char pace_buf[32], ema_buf[32], expand_buf[32];

if (pace) {
snprintf(pace_buf, sizeof(pace_buf), "%d", *pace);
} else {
snprintf(pace_buf, sizeof(pace_buf), "%s", "(unset)");
}

if (ema) {
snprintf(ema_buf, sizeof(ema_buf), "%.6f", *ema);
} else {
snprintf(ema_buf, sizeof(ema_buf), "%s", "(unset)");
}

if (expand) {
snprintf(expand_buf, sizeof(expand_buf), "%d", *expand);
} else {
snprintf(expand_buf, sizeof(expand_buf), "%s", "(unset)");
}

ESP_LOGI(TAG, "tx %s stream=%u size=%dx%d src=%s ddp_port=%u fmt=%s pixcfg=0x%02X "
"pace=%s ema=%s expand=%s loop=%s hw=%s fit=%s",
label,
(unsigned) stream_id, w, h, src.c_str(), (unsigned) ddp_port,
(fmt?fmt->c_str():"(unset)"), (unsigned) (pixcfg?*pixcfg:0),
pace_str.c_str(),
ema_str.c_str(),
expand_str.c_str(),
pace_buf,
ema_buf,
expand_buf,
(loop?(*loop?"true":"false"):"(unset)"),
(hw?hw->c_str():"(unset)"),
(fit?fit->c_str():"(unset)"));
}

// map format string -> ("fmt" field, pixcfg byte). For "rgb565" without endian,
// we borrow endianness from the sink's LVGL (preferred_ddp_pixcfg()).
static inline std::pair<std::string,uint8_t>
resolve_fmt_and_pixcfg_(const std::string &fmt_in, ddp::DdpComponent *ddp) {
std::string f = fmt_in;
for (auto &c : f) c = (char)tolower((unsigned char)c);
if (f == "rgb888")
return { "rgb888", ddp::DDP_PIXCFG_RGB888 };
if (f == "rgb565le")
return { "rgb565le", ddp::DDP_PIXCFG_RGB565_LE };
if (f == "rgb565be")
return { "rgb565be", ddp::DDP_PIXCFG_RGB565_BE };
if (f == "rgbw")
return { "rgbw", ddp::DDP_PIXCFG_RGBW };
if (f == "rgb565") {
#if defined(LV_COLOR_16_SWAP) && LV_COLOR_16_SWAP
return { "rgb565be", ddp::DDP_PIXCFG_RGB565_BE };
#else
return { "rgb565le", ddp::DDP_PIXCFG_RGB565_LE };
#endif
}
// fallback
return { "rgb888", ddp::DDP_PIXCFG_RGB888 };
}

// ------------- trampoline -------------
// NOTE: This runs on ESP-IDF event task, NOT the main ESPHome thread.
// Most operations should be deferred to main thread via set_timeout().
Expand Down Expand Up @@ -306,14 +324,25 @@ void MediaProxyControl::dump_config() {
std::string ema_str = output->ema_ ? std::to_string(*output->ema_) : "(unset)";
std::string expand_str = output->expand_ ? std::to_string(*output->expand_) : "(unset)";

const char* format_str = "(unset)";
if (output->format_pixcfg_) {
switch (*output->format_pixcfg_) {
case ddp::DDP_PIXCFG_RGB888: format_str = "rgb888"; break;
case ddp::DDP_PIXCFG_RGB565_LE: format_str = "rgb565le"; break;
case ddp::DDP_PIXCFG_RGB565_BE: format_str = "rgb565be"; break;
case ddp::DDP_PIXCFG_RGBW: format_str = "rgbw"; break;
default: format_str = "rgb888"; break;
}
}

ESP_LOGCONFIG(TAG, " src=%s pace=%s ema=%s expand=%s loop=%s hw=%s format=%s",
output->src_.c_str(),
pace_str.c_str(),
ema_str.c_str(),
expand_str.c_str(),
output->loop_ ? (*output->loop_ ? "true" : "false") : "(unset)",
output->hw_ ? output->hw_->c_str() : "(unset)",
output->format_ ? output->format_->c_str() : "(unset)");
format_str);
}
}

Expand Down Expand Up @@ -531,13 +560,10 @@ void MediaProxyControl::stop_stream(uint8_t stream_id) {
}

if (!client_ || !running_.load()) return;
std::string json;
json.reserve(48);
json += "{\"type\":\"stop_stream\",\"out\":";
json += std::to_string(stream_id);
json += "}";
char buf[64];
snprintf(buf, sizeof(buf), "{\"type\":\"stop_stream\",\"out\":%u}", (unsigned) stream_id);
ESP_LOGI(TAG, "tx stop_stream stream=%u", (unsigned) stream_id);
this->send_text(json);
this->send_text(buf);
}

void MediaProxyControl::send_update(uint8_t stream_id) {
Expand All @@ -552,18 +578,15 @@ void MediaProxyControl::send_update(uint8_t stream_id) {
std::string MediaProxyControl::json_escape_(const std::string &s) {
std::string o; o.reserve(s.size()+8);
for (char c: s) {
switch (c) {
case '\"': o += "\\\""; break;
case '\\': o += "\\\\"; break;
case '\b': o += "\\b"; break;
case '\f': o += "\\f"; break;
case '\n': o += "\\n"; break;
case '\r': o += "\\r"; break;
case '\t': o += "\\t"; break;
default:
if ((unsigned char)c < 0x20) { char buf[7]; snprintf(buf, sizeof(buf), "\\u%04x", (unsigned char)c); o += buf; }
else o += c;
}
if (c == '\"') o += "\\\"";
else if (c == '\\') o += "\\\\";
else if (c == '\n') o += "\\n";
else if (c == '\t') o += "\\t";
else if (c == '\r') o += "\\r";
else if (c == '\b') o += "\\b";
else if (c == '\f') o += "\\f";
else if ((unsigned char)c < 0x20) { char buf[7]; snprintf(buf, sizeof(buf), "\\u%04x", (unsigned char)c); o += buf; }
else o += c;
}
return o;
}
Expand Down Expand Up @@ -604,10 +627,26 @@ MediaProxyControl::StreamConfig MediaProxyControl::compute_stream_config_(uint8_
if (output && output->loop_) e.loop = *output->loop_;

// format → (fmt,pixcfg)
if (output && output->format_) {
auto rp = resolve_fmt_and_pixcfg_(*output->format_, ddp_);
e.fmt = rp.first;
e.pixcfg = rp.second;
if (output && output->format_pixcfg_) {
e.pixcfg = *output->format_pixcfg_;
// Convert enum → string for JSON
switch (*output->format_pixcfg_) {
case ddp::DDP_PIXCFG_RGB888:
e.fmt = "rgb888";
break;
case ddp::DDP_PIXCFG_RGB565_LE:
e.fmt = "rgb565le";
break;
case ddp::DDP_PIXCFG_RGB565_BE:
e.fmt = "rgb565be";
break;
case ddp::DDP_PIXCFG_RGBW:
e.fmt = "rgbw";
break;
default:
e.fmt = "rgb888";
break;
}
}

// fit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class MediaProxyOutput : public Component {
std::optional<int> expand_;
std::optional<bool> loop_;
std::optional<std::string> hw_;
std::optional<std::string> format_;
std::optional<uint8_t> format_pixcfg_;
std::optional<std::string> fit_;

friend class MediaProxyControl;
Expand Down