mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-12-25 23:00:12 +00:00
1d3ec6a3a1
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.
252 lines
5.5 KiB
C
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;
|
|
}
|