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
3 changes: 2 additions & 1 deletion doc/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,13 @@ This document describes the fields that can be found within a configuration file
- **regX_text**: Text displayed in OSD region X.
- **regX_font**: Font used for text in OSD region X.
- **regX_opal**: Opacity of OSD region X.
- **regX_posx**: X position of OSD region X.
- **regX_posx**: X position of OSD region X; `-1` centers it on the main stream.
- **regX_posy**: Y position of OSD region X.
- **regX_size**: Size of the text or image in OSD region X.
- **regX_color**: Color of the text or image in OSD region X.
- **regX_outl**: Outline color of the text in OSD region X.
- **regX_thick**: Thickness of the text outline in OSD region X.
- **regX_bgcolor**: Background of the text in OSD region X, in the same 15-bit format as the colors with the top bit set to show it: `0x8000` is an opaque black box, `0` none (default: `0`). Through `/api/osd/X`, `bgcolor=#RGB`, `#RRGGBB` or `none`, other values leave it unchanged.

## JPEG section

Expand Down
6 changes: 4 additions & 2 deletions doc/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,11 @@ Configures text or image overlays by their ID (0-9 at the moment).
| GET | `color` | Font color (hex format, RGB555 format) |
| GET | `opal` | Opacity level (0-255) |
| GET | `pos` | Position on main stream \[x,y\] |
| GET | `posx` | X coordinate (write-only) |
| GET | `posx` | X coordinate, -1 to center (write-only) |
| GET | `posy` | Y coordinate (write-only) |
| GET | `outl` | Outline color (hex format, RGB555 format) |
| GET | `thick` | Outline thickness (0 to disable) |
| GET | `bgcolor` | Background box color (hex format), `none` for none |
| POST | `file` | Bitmap or PNG image to upload (replaces text) |

**Response**
Expand All @@ -258,7 +259,8 @@ Configures text or image overlays by their ID (0-9 at the moment).
"size": 15.0,
"text": "Backyard (%T)",
"outl": "#8000",
"thick": 0.0
"thick": 0.0,
"bgcolor": "none"
}
```

Expand Down
6 changes: 6 additions & 0 deletions doc/overlays.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ curl http://192.168.1.17/api/osd/1?color=%23FFFF&outl=%238000&thick=1.0
```
N.B. Hashtags have to be espaced with %23 in curl URL syntaxes

A box behind the text keeps it readable over bright scenes, and a negative X centers a region:
```
curl "http://192.168.1.17/api/osd/1?text=Backyard&bgcolor=%23000000&posx=-1&posy=0"
curl http://192.168.1.17/api/osd/1?bgcolor=none
```

Empty strings are used to clear the regions:
```
curl http://192.168.1.17/api/osd/1?text=
Expand Down
8 changes: 6 additions & 2 deletions src/app_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ int app_config_save(void) {
fprintf(file, " reg%d_size: %.1f\n", i, osds[i].size);
fprintf(file, " reg%d_color: %#04x\n", i, osds[i].color);
fprintf(file, " reg%d_outl: %#04x\n", i, osds[i].outl);
fprintf(file, " reg%d_bgcolor: %#04x\n", i, osds[i].bgcolor);
fprintf(file, " reg%d_thick: %.1f\n", i, osds[i].thick);
}

Expand Down Expand Up @@ -380,6 +381,7 @@ enum ConfigError app_config_parse(void) {

parse_bool(&ini, "mdns", "enable", &app_config.mdns_enable);

region_defaults();
parse_bool(&ini, "osd", "enable", &app_config.osd_enable);
if (app_config.osd_enable) {
for (char i = 0; i < MAX_OSD; i++) {
Expand All @@ -395,7 +397,7 @@ enum ConfigError app_config_parse(void) {
err = parse_int(&ini, "osd", param, 0, UCHAR_MAX, &val);
if (err == CONFIG_OK) osds[i].opal = (unsigned char)val;
sprintf(param, "reg%d_posx", i);
err = parse_int(&ini, "osd", param, 0, SHRT_MAX, &val);
err = parse_int(&ini, "osd", param, -1, SHRT_MAX, &val);
if (err == CONFIG_OK) osds[i].posx = (short)val;
sprintf(param, "reg%d_posy", i);
err = parse_int(&ini, "osd", param, 0, SHRT_MAX, &val);
Expand All @@ -406,9 +408,11 @@ enum ConfigError app_config_parse(void) {
parse_int(&ini, "osd", param, 0, USHRT_MAX, &osds[i].color);
sprintf(param, "reg%d_outl", i);
parse_int(&ini, "osd", param, 0, USHRT_MAX, &osds[i].outl);
sprintf(param, "reg%d_bgcolor", i);
parse_int(&ini, "osd", param, 0, USHRT_MAX, &osds[i].bgcolor);
sprintf(param, "reg%d_thick", i);
parse_double(&ini, "osd", param, 0, UCHAR_MAX, &osds[i].thick);
osds[i].updt = 1;
osds[i].updt = !EMPTY(osds[i].text) || !EMPTY(osds[i].img);
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/hal/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ enum ConfigError parse_param_value_n(
}
param_value[res] = 0;

if (res >= 2 && param_value[0] == '"' && param_value[res - 1] == '"') {
if (res >= 2 && (param_value[0] == '"' || param_value[0] == '\'') &&
param_value[res - 1] == param_value[0]) {
memmove(param_value, param_value + 1, res - 2);
param_value[res - 2] = '\0';
res -= 2;
Expand Down
139 changes: 115 additions & 24 deletions src/hal/star/i6_hal.c
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,23 @@ void i6_pipeline_destroy(void)
i6_snr.fnDisable(_i6_snr_index);
}

int i6_region_create(char handle, hal_rect rect, short opacity)
// A text rendered with a proportional font, like a clock, changes its width
// from one second to the next. Recreating the region each time frees and
// reallocates its MMA blocks between the VPE frame buffers, which cycle
// through the heap, until a port buffer no longer fits and frames drop.
// The canvas is rounded up and only grows, the bitmap is padded to it
#define I6_RGN_CANVAS_W 32
#define I6_RGN_CANVAS_H 16

// The padding stays within the room left to the frame's right edge, a
// bitmap that does not fit there on its own keeps its exact width
static unsigned int i6_region_width(unsigned int canvas, short width, short room)
{
if (room < width) room = width;
return MIN(canvas, (unsigned int)room);
}

int i6_region_create(char handle, hal_rect rect, short opacity, short room)
{
int ret;

Expand All @@ -390,18 +406,21 @@ int i6_region_create(char handle, hal_rect rect, short opacity)

region.type = I6_RGN_TYPE_OSD;
region.pixFmt = I6_RGN_PIXFMT_ARGB1555;
region.size.width = rect.width;
region.size.height = rect.height;
region.size.width = i6_region_width(
(rect.width + I6_RGN_CANVAS_W - 1) & ~(I6_RGN_CANVAS_W - 1), rect.width, room);
region.size.height = (rect.height + I6_RGN_CANVAS_H - 1) & ~(I6_RGN_CANVAS_H - 1);

if (i6_rgn.fnGetRegionConfig(handle, &regionCurr)) {
HAL_INFO("i6_rgn", "Creating region %d...\n", handle);
HAL_INFO("i6_rgn", "Creating region %d (%ux%u)...\n", handle,
region.size.width, region.size.height);
if (ret = i6_rgn.fnCreateRegion(handle, &region))
return ret;
} else if (regionCurr.type != region.type ||
regionCurr.size.height != region.size.height ||
regionCurr.size.width != region.size.width) {
regionCurr.size.width < region.size.width ||
regionCurr.size.width > i6_region_width(regionCurr.size.width, rect.width, room)) {
HAL_INFO("i6_rgn", "Parameters are different, recreating "
"region %d...\n", handle);
"region %d (%ux%u)...\n", handle, region.size.width, region.size.height);
for (char i = 0; i < I6_VENC_CHN_NUM; i++) {
if (!i6_state[i].enable) continue;
dest.port = i;
Expand All @@ -412,19 +431,6 @@ int i6_region_create(char handle, hal_rect rect, short opacity)
return ret;
}

if (i6_rgn.fnGetChannelConfig(handle, &dest, &attribCurr))
HAL_INFO("i6_rgn", "Attaching region %d...\n", handle);
else if (attribCurr.point.x != rect.x || attribCurr.point.y != rect.y ||
attribCurr.osd.bgFgAlpha[1] != opacity) {
HAL_INFO("i6_rgn", "Parameters are different, reattaching "
"region %d...\n", handle);
for (char i = 0; i < I6_VENC_CHN_NUM; i++) {
if (!i6_state[i].enable) continue;
dest.port = i;
i6_rgn.fnDetachChannel(handle, &dest);
}
}

memset(&attrib, 0, sizeof(attrib));
attrib.show = 1;
attrib.point.x = rect.x;
Expand All @@ -434,18 +440,41 @@ int i6_region_create(char handle, hal_rect rect, short opacity)
attrib.osd.bgFgAlpha[0] = 0;
attrib.osd.bgFgAlpha[1] = opacity;

// Each port is checked on its own, so one enabled after the region was
// attached still gets it
ret = EXIT_SUCCESS;
for (char i = 0; i < I6_VENC_CHN_NUM; i++) {
if (!i6_state[i].enable) continue;
// On SSC32x the snapshot port is YUYV, where a region comes out
// stretched with every glyph doubled
if (series == 0xEF && i6_state[i].payload == HAL_VIDCODEC_JPG) continue;
dest.port = i;
i6_rgn.fnAttachChannel(handle, &dest, &attrib);
if (!i6_rgn.fnGetChannelConfig(handle, &dest, &attribCurr)) {
if (attribCurr.point.x == rect.x && attribCurr.point.y == rect.y &&
attribCurr.osd.bgFgAlpha[1] == opacity)
continue;
HAL_INFO("i6_rgn", "Parameters are different, reattaching "
"region %d to VPE port %d...\n", handle, i);
i6_rgn.fnDetachChannel(handle, &dest);
} else
HAL_INFO("i6_rgn", "Attaching region %d to VPE port %d...\n", handle, i);
int err = i6_rgn.fnAttachChannel(handle, &dest, &attrib);
if (err) {
HAL_DANGER("i6_rgn", "Attaching region %d to VPE port %d failed "
"with %#x!\n", handle, i, err);
ret = err;
}
}

return EXIT_SUCCESS;
return ret;
}

static char _i6_rgn_ready = 0;

void i6_region_deinit(void)
{
i6_rgn.fnDeinit();
_i6_rgn_ready = 0;
}

void i6_region_destroy(char handle)
Expand All @@ -463,16 +492,78 @@ void i6_region_destroy(char handle)

void i6_region_init(void)
{
if (_i6_rgn_ready) return;

i6_rgn_pal palette = {{{0, 0, 0, 0}}};
i6_rgn.fnInit(&palette);
_i6_rgn_ready = 1;
}

int i6_region_prepare(char handle, short width, short height, short room)
{
i6_rgn_cnf region;

i6_region_init();

memset(&region, 0, sizeof(region));
region.type = I6_RGN_TYPE_OSD;
region.pixFmt = I6_RGN_PIXFMT_ARGB1555;
// A quarter more than the text needs, so a clock or a counter can grow
region.size.width = i6_region_width(
(width + width / 4 + I6_RGN_CANVAS_W - 1) & ~(I6_RGN_CANVAS_W - 1), width, room);
region.size.height = (height + I6_RGN_CANVAS_H - 1) & ~(I6_RGN_CANVAS_H - 1);

HAL_INFO("i6_rgn", "Reserving region %d (%ux%u)...\n", handle,
region.size.width, region.size.height);
return i6_rgn.fnCreateRegion(handle, &region);
}

int i6_region_setbitmap(int handle, hal_bitmap *bitmap)
{
i6_rgn_bmp nativeBmp = { .data = bitmap->data, .pixFmt = I6_RGN_PIXFMT_ARGB1555,
.size.height = bitmap->dim.height, .size.width = bitmap->dim.width };
int ret;
i6_rgn_cnf region;

if (ret = i6_rgn.fnGetRegionConfig(handle, &region)) {
HAL_DANGER("i6_rgn", "Reading the size of region %d failed "
"with %#x!\n", handle, ret);
return ret;
}
if (bitmap->dim.width > region.size.width ||
bitmap->dim.height > region.size.height) {
HAL_DANGER("i6_rgn", "Bitmap %ux%u exceeds the %ux%u canvas "
"of region %d!\n", bitmap->dim.width, bitmap->dim.height,
region.size.width, region.size.height, handle);
return EXIT_FAILURE;
}

// ARGB1555 zero is a transparent pixel, the margin stays invisible
unsigned short *canvas = bitmap->data;
if (bitmap->dim.width != region.size.width ||
bitmap->dim.height != region.size.height) {
canvas = calloc(region.size.width * region.size.height, sizeof(*canvas));
if (!canvas) {
HAL_DANGER("i6_rgn", "Allocating the canvas of region %d failed!\n", handle);
return EXIT_FAILURE;
}
for (unsigned int y = 0; y < bitmap->dim.height; y++)
memcpy(canvas + y * region.size.width,
(unsigned short*)bitmap->data + y * bitmap->dim.width,
bitmap->dim.width * sizeof(*canvas));
}

return i6_rgn.fnSetBitmap(handle, &nativeBmp);
i6_rgn_bmp nativeBmp = { .data = canvas, .pixFmt = I6_RGN_PIXFMT_ARGB1555,
.size.height = region.size.height, .size.width = region.size.width };

ret = i6_rgn.fnSetBitmap(handle, &nativeBmp);
if (ret)
HAL_DANGER("i6_rgn", "Setting the bitmap of region %d (%ux%u) "
"failed with %#x!\n", handle, nativeBmp.size.width,
nativeBmp.size.height, ret);

if (canvas != bitmap->data)
free(canvas);

return ret;
}

int i6_sensor_exposure(unsigned int micros)
Expand Down
3 changes: 2 additions & 1 deletion src/hal/star/i6_hal.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ int i6_config_load(char *path);
int i6_pipeline_create(char index, short width, short height, char mirror, char flip, char framerate);
void i6_pipeline_destroy(void);

int i6_region_create(char handle, hal_rect rect, short opacity);
int i6_region_create(char handle, hal_rect rect, short opacity, short room);
void i6_region_deinit(void);
void i6_region_destroy(char handle);
void i6_region_init(void);
int i6_region_prepare(char handle, short width, short height, short room);
int i6_region_setbitmap(int handle, hal_bitmap *bitmap);

int i6_sensor_exposure(unsigned int micros);
Expand Down
2 changes: 1 addition & 1 deletion src/hal/tools.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ int color_parse(const char *str) {
int b = hex_to_int(str[3]);

if (r >= 0 && g >= 0 && b >= 0)
return (1 << 16) | (r << 11) | (g << 6) | (b << 1);
return (1 << 15) | (r << 11) | (g << 6) | (b << 1);
}

// 8-bit hex format "#RRGGBB"
Expand Down
3 changes: 3 additions & 0 deletions src/media.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "media.h"
#include "region.h"

char audioOn = 0, udpOn = 0;
pthread_mutex_t aencMtx, chnMtx, mp4Mtx;
Expand Down Expand Up @@ -874,6 +875,8 @@ int sdk_start(void) {
HAL_ERROR("media", "System initialization failed with %#x!\n%s\n",
ret, errstr(ret));

region_prepare();

if (app_config.audio_enable) {
ret = media_audio_enable();
if (ret)
Expand Down
Loading