libwebsockets/minimal-examples-lowlevel/api-tests/api-test-ssjpeg/main.c
Andy Green 1d3ec6a3a1 lws-jpeg
Introduce a rewritten picojpeg that is able to operate statefully and
rasterize into an internal line ringbuffer, emitting a line of pixels
at a time to the caller.  This is the JPEG equivalent of the lws
PNG decoder.

JPEG is based around 8- or 16- line height MCU blocks, depending on
the chroma coding, mandating a corresponding internal line buffer
requirement.

Example total heap requirement for various kinds of 600px width jpeg
decoding:

  Grayscale:    6.5KB
  RGB 4:4:4:   16.4KB
  RGB 4:2:2v:  16.4KB
  RGB 4:4:2h:  31KB
  RGB 4:4:0:   31KB

No other allocations occur during decode.

Stateful stream parsing means decode can be paused for lack of input
at any time and resumed seamlessly when more input becomes available.
2022-03-25 08:13:48 +00:00

252 lines
5.5 KiB
C

/*
* lws-api-test-ssjpeg
*
* Written in 2010-2022 by Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*/
#include <libwebsockets.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
/*
* The dlo and the flow are inside the context of the SS
*/
LWS_SS_USER_TYPEDEF
lws_flow_t flow;
lws_jpeg_t *j;
} myss_t;
static lws_dlo_rasterize_t rast;
struct lws_context *cx;
static int fdout = 1, result = 1;
/* sul to produce some lines of output bitmap */
static void
rasterize(lws_sorted_usec_list_t *sul)
{
lws_dlo_rasterize_t *rast = lws_container_of(sul, lws_dlo_rasterize_t, sul);
lws_flow_t *flow = lws_container_of(rast->owner.head, lws_flow_t, list);
myss_t *m = lws_container_of(flow, myss_t, flow);
const uint8_t *pix = NULL;
lws_stateful_ret_t r;
ssize_t os;
do {
if (!flow->len) {
if (flow->blseglen)
lws_buflist_use_segment(&flow->bl, flow->blseglen);
flow->len = lws_buflist_next_segment_len(
&flow->bl, (uint8_t **)&flow->data);
flow->blseglen = (uint32_t)flow->len;
if (!flow->len)
return;
}
r = lws_jpeg_emit_next_line(m->j, &pix,
(const uint8_t **)&flow->data, &flow->len, 0);
if (!flow->len && flow->blseglen) {
lws_buflist_use_segment(&flow->bl, flow->blseglen);
flow->blseglen = 0;
}
if (r == LWS_SRET_WANT_INPUT) {
if (lws_buflist_next_segment_len(&flow->bl, NULL))
continue;
if (r == LWS_SRET_WANT_INPUT && flow->h) {
int32_t est = lws_ss_get_est_peer_tx_credit(flow->h) +
(int)lws_buflist_total_len(&flow->bl) +
(int)flow->len;
lwsl_debug("%s: est %d\n", __func__, est);
if (est < flow->window)
lws_ss_add_peer_tx_credit(flow->h, flow->window);
}
return;
}
if (r >= LWS_SRET_FATAL) {
lwsl_notice("%s: emit returned FATAL\n", __func__);
flow->state = LWSDLOFLOW_STATE_READ_FAILED;
lws_default_loop_exit(cx);
return;
}
if (!pix)
return;
os = (ssize_t)(lws_jpeg_get_width(m->j) *
(lws_jpeg_get_pixelsize(m->j) / 8));
if (write(fdout, pix,
#if defined(WIN32)
(unsigned int)
#endif
(size_t)os) < os) {
lwsl_err("%s: write %d failed %d\n", __func__,
(int)os, errno);
goto bail1;
}
lwsl_debug("%s: wrote %d: r %u (left %u)\n", __func__,
(int)os, r, (unsigned int)flow->len);
if (r == LWS_SRET_OK) {
lwsl_notice("%s: feels complete\n", __func__);
flow->state = LWSDLOFLOW_STATE_READ_COMPLETED;
result = 0;
lws_default_loop_exit(cx);
return;
}
} while (1);
return;
bail1:
return;
}
/* secure streams payload interface */
static lws_ss_state_return_t
myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
myss_t *m = (myss_t *)userobj;
lws_dlo_rasterize_t *rast1 = lws_container_of(m->flow.list.owner,
lws_dlo_rasterize_t, owner);
if (len && lws_buflist_append_segment(&m->flow.bl, buf, len) < 0)
return LWSSSSRET_DISCONNECT_ME;
if (flags & LWSSS_FLAG_EOM) {
m->flow.state = LWSDLOFLOW_STATE_READ_COMPLETED;
return LWSSSSRET_DISCONNECT_ME;
}
lws_sul_schedule(lws_ss_get_context(m->ss), 0, &rast1->sul, rasterize, 1);
return LWSSSSRET_OK;
}
static lws_ss_state_return_t
myss_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
myss_t *m = (myss_t *)userobj;
const char *url = (const char*)m->opaque_data;
lws_ss_state_return_t r;
switch (state) {
case LWSSSCS_CREATING:
m->flow.h = m->ss;
m->flow.window = 4096;
m->j = lws_jpeg_new();
if (!m->j) {
lwsl_err("%s: failed to allocate\n", __func__);
return LWSSSSRET_DESTROY_ME;
}
if (lws_ss_set_metadata(m->ss, "endpoint", url, strlen(url))) {
lwsl_err("%s: failed to use metadata %s\n", __func__,
url);
return LWSSSSRET_DESTROY_ME;
}
r = lws_ss_client_connect(m->ss);
if (r)
return r;
lws_dll2_add_tail(&m->flow.list, &rast.owner);
break;
case LWSSSCS_DESTROYING:
m->flow.h = NULL;
lws_buflist_destroy_all_segments(&m->flow.bl);
lws_jpeg_free(&m->j);
lws_dll2_remove(&m->flow.list);
break;
default:
break;
}
return LWSSSSRET_OK;
}
static LWS_SS_INFO("default", myss_t)
.rx = myss_rx,
.state = myss_state,
.manual_initial_tx_credit = 1400
};
static void
sigint_handler(int sig)
{
lws_default_loop_exit(cx);
}
int
main(int argc, const char **argv)
{
struct lws_context_creation_info info;
const char *p;
size_t l = 0;
lwsl_user("LWS SS JPEG test client <https://server/my.jpg>\n");
signal(SIGINT, sigint_handler);
memset(&info, 0, sizeof info);
lws_cmdline_option_handle_builtin(argc, argv, &info);
if ((p = lws_cmdline_option(argc, argv, "--stdout"))) {
fdout = open(p, LWS_O_WRONLY | LWS_O_CREAT | LWS_O_TRUNC, 0600);
if (fdout < 0) {
lwsl_err("%s: unable to open stdout file\n", __func__);
goto bail;
}
}
info.fd_limit_per_thread = 1 + 6 + 1;
info.port = CONTEXT_PORT_NO_LISTEN;
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW;
/* create the cx */
cx = lws_create_context(&info);
if (!cx) {
lwsl_err("lws init failed\n");
return 1;
}
/* create the SS to the jpg using the URL on argv[1] */
if (lws_ss_create(cx, 0, &ssi_myss_t, (void *)argv[1], NULL, NULL, NULL)) {
lws_context_destroy(cx);
goto bail2;
}
lws_context_default_loop_run_destroy(cx);
bail2:
if (fdout != 1)
close(fdout);
bail:
lwsl_user("Completed: %s (read %u)\n", result ? "FAIL" : "PASS",
(unsigned int)l);
return result;
}