Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2018-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / libjpeg encoder filter
9 : *
10 : * GPAC is free software; you can redistribute it and/or modify
11 : * it under the terms of the GNU Lesser General Public License as published by
12 : * the Free Software Foundation; either version 2, or (at your option)
13 : * any later version.
14 : *
15 : * GPAC is distributed in the hope that it will be useful,
16 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Lesser General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Lesser General Public
21 : * License along with this library; see the file COPYING. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 : #include <gpac/filters.h>
27 : #include <gpac/constants.h>
28 :
29 : #ifdef GPAC_HAS_JPEG
30 :
31 : #ifdef WIN32
32 : #define HAVE_UNSIGNED_CHAR
33 : #endif
34 :
35 : #include <jpeglib.h>
36 : #include <setjmp.h>
37 :
38 : typedef struct
39 : {
40 : //opts
41 : u32 dctmode;
42 : u32 quality;
43 :
44 : GF_FilterPid *ipid, *opid;
45 : u32 width, height, pixel_format, stride, stride_uv, nb_planes, uv_height;
46 :
47 : GF_FilterPacket *dst_pck;
48 : u8 *output;
49 :
50 : /*io manager*/
51 : struct jpeg_destination_mgr dst;
52 : u32 dst_pck_size, max_size;
53 :
54 : struct jpeg_error_mgr pub;
55 : jmp_buf jmpbuf;
56 :
57 : Bool in_fmt_negotiate;
58 : } GF_JPGEncCtx;
59 :
60 23 : static GF_Err jpgenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
61 : {
62 : char n[100];
63 : const GF_PropertyValue *prop;
64 23 : GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) gf_filter_get_udta(filter);
65 :
66 : //disconnect of src pid (not yet supported)
67 23 : if (is_remove) {
68 0 : if (ctx->opid) {
69 0 : gf_filter_pid_remove(ctx->opid);
70 0 : ctx->opid = NULL;
71 : }
72 0 : ctx->ipid = NULL;
73 0 : return GF_OK;
74 : }
75 23 : if (! gf_filter_pid_check_caps(pid))
76 : return GF_NOT_SUPPORTED;
77 :
78 :
79 23 : ctx->ipid = pid;
80 :
81 23 : if (!ctx->opid) {
82 7 : ctx->opid = gf_filter_pid_new(filter);
83 : }
84 : //copy properties at init or reconfig
85 23 : gf_filter_pid_copy_properties(ctx->opid, ctx->ipid);
86 23 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_JPEG ));
87 23 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, NULL);
88 23 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, NULL);
89 23 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL);
90 23 : gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL);
91 :
92 : #ifdef JPEG_LIB_VERSION_MAJOR
93 : sprintf(n, "encjpg:%d.%d", JPEG_LIB_VERSION_MAJOR, JPEG_LIB_VERSION_MINOR);
94 : #else
95 : sprintf(n, "encjpg:%d", JPEG_LIB_VERSION);
96 : #endif
97 :
98 23 : gf_filter_set_name(filter, n);
99 :
100 : //some props may not be set yet
101 23 : prop = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
102 23 : if (!prop) return GF_OK;
103 23 : ctx->width = prop->value.uint;
104 :
105 23 : prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
106 23 : if (!prop) return GF_OK;
107 23 : ctx->height = prop->value.uint;
108 :
109 23 : prop = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT);
110 23 : if (!prop) return GF_OK;
111 21 : ctx->pixel_format = prop->value.uint;
112 :
113 21 : prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE);
114 21 : if (prop) ctx->stride = prop->value.uint;
115 :
116 21 : prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV);
117 21 : if (prop) ctx->stride_uv = prop->value.uint;
118 :
119 21 : gf_pixel_get_size_info(ctx->pixel_format, ctx->width, ctx->height, NULL, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height);
120 :
121 :
122 : //TODO: for now we only allow YUV420p input, we should refine this to allow any YUV
123 21 : if (ctx->pixel_format != GF_PIXEL_YUV) {
124 9 : gf_filter_pid_negociate_property(pid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_YUV));
125 9 : ctx->in_fmt_negotiate = GF_TRUE;
126 9 : return GF_OK;
127 : }
128 12 : ctx->in_fmt_negotiate = GF_FALSE;
129 12 : return GF_OK;
130 : }
131 :
132 7 : static void jpgenc_output_message (j_common_ptr cinfo)
133 : {
134 7 : if (cinfo) {
135 : char buffer[JMSG_LENGTH_MAX];
136 : /* Create the message */
137 0 : (*cinfo->err->format_message) (cinfo, buffer);
138 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc]: %s\n", buffer));
139 : } else {
140 7 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[JPGEnc] coverage test\n"));
141 : }
142 7 : }
143 7 : static void jpgenc_nonfatal_error2(j_common_ptr cinfo, int lev)
144 : {
145 7 : if (cinfo) {
146 : char buffer[JMSG_LENGTH_MAX];
147 : /* Create the message */
148 0 : (*cinfo->err->format_message) (cinfo, buffer);
149 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc]: %s\n", buffer));
150 : } else {
151 7 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[JPGEnc] coverage test\n"));
152 : }
153 7 : }
154 :
155 7 : static void jpgenc_fatal_error(j_common_ptr cinfo)
156 : {
157 7 : if (!cinfo) {
158 7 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[JPGEnc] coverage test\n"));
159 : } else {
160 0 : GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) cinfo->client_data;
161 0 : jpgenc_output_message(cinfo);
162 0 : longjmp(ctx->jmpbuf, 1);
163 : }
164 7 : }
165 :
166 : //start with 4k when we have no clue about the size
167 : #define ALLOC_STEP_SIZE 4096
168 7 : static void jpgenc_init_dest(j_compress_ptr cinfo)
169 : {
170 7 : GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) cinfo->client_data;
171 7 : if (ctx->dst_pck)
172 : return;
173 :
174 7 : ctx->dst_pck = gf_filter_pck_new_alloc(ctx->opid, ALLOC_STEP_SIZE, &ctx->output);
175 7 : if (!ctx->dst_pck) return;
176 :
177 7 : cinfo->dest->next_output_byte = ctx->output;
178 7 : cinfo->dest->free_in_buffer = ALLOC_STEP_SIZE;
179 7 : ctx->dst_pck_size += ALLOC_STEP_SIZE;
180 : }
181 :
182 97 : static boolean jpgenc_empty_output(j_compress_ptr cinfo)
183 : {
184 : u8 *data;
185 : u32 new_size;
186 97 : GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) cinfo->client_data;
187 :
188 97 : if (!ctx->dst_pck)
189 : return FALSE;
190 :
191 97 : if (gf_filter_pck_expand(ctx->dst_pck, ALLOC_STEP_SIZE, &ctx->output, &data, &new_size) != GF_OK) {
192 : return FALSE;
193 : }
194 97 : cinfo->dest->next_output_byte = data;
195 97 : cinfo->dest->free_in_buffer = ALLOC_STEP_SIZE;
196 97 : ctx->dst_pck_size += ALLOC_STEP_SIZE;
197 97 : return TRUE;
198 : }
199 :
200 7 : static void jpgenc_term_dest(j_compress_ptr cinfo)
201 : {
202 7 : GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) cinfo->client_data;
203 :
204 7 : ctx->dst_pck_size -= (u32) cinfo->dest->free_in_buffer;
205 7 : gf_filter_pck_truncate(ctx->dst_pck, ctx->dst_pck_size);
206 7 : }
207 :
208 22 : static GF_Err jpgenc_process(GF_Filter *filter)
209 : {
210 22 : GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) gf_filter_get_udta(filter);
211 22 : GF_Err e = GF_OK;
212 : struct jpeg_compress_struct cinfo;
213 22 : GF_FilterPacket *pck = NULL;
214 : char *in_data;
215 22 : GF_FilterFrameInterface *frame_ifce = NULL;
216 : u32 size, stride, stride_uv;
217 : u32 i, j;
218 : u8 *pY, *pU, *pV;
219 : JSAMPROW y[16],cb[16],cr[16];
220 : JSAMPARRAY block[3];
221 :
222 22 : if (ctx->ipid)
223 22 : pck = gf_filter_pid_get_packet(ctx->ipid);
224 22 : if (!ctx->ipid)
225 : return GF_EOS;
226 :
227 22 : if (!pck) {
228 15 : if (gf_filter_pid_is_eos(ctx->ipid)) {
229 7 : gf_filter_pid_set_eos(ctx->opid);
230 : return GF_EOS;
231 : }
232 : return GF_OK;
233 : }
234 7 : if (ctx->in_fmt_negotiate) return GF_OK;
235 :
236 7 : in_data = (char *) gf_filter_pck_get_data(pck, &size);
237 7 : if (!in_data) {
238 0 : frame_ifce = gf_filter_pck_get_frame_interface(pck);
239 0 : if (!frame_ifce || !frame_ifce->get_plane) {
240 0 : gf_filter_pid_drop_packet(ctx->ipid);
241 : return GF_NOT_SUPPORTED;
242 : }
243 : }
244 :
245 7 : block[0] = y;
246 7 : block[1] = cb;
247 7 : block[2] = cr;
248 :
249 7 : cinfo.err = jpeg_std_error(&(ctx->pub));
250 7 : cinfo.client_data = ctx;
251 7 : ctx->pub.error_exit = jpgenc_fatal_error;
252 7 : ctx->pub.output_message = jpgenc_output_message;
253 7 : ctx->pub.emit_message = jpgenc_nonfatal_error2;
254 7 : if (setjmp(ctx->jmpbuf)) {
255 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc] : Failed to encode\n"));
256 : e = GF_NON_COMPLIANT_BITSTREAM;
257 : goto exit;
258 : }
259 :
260 7 : ctx->dst.init_destination = jpgenc_init_dest;
261 7 : ctx->dst.empty_output_buffer = jpgenc_empty_output;
262 7 : ctx->dst.term_destination = jpgenc_term_dest;
263 :
264 7 : if (ctx->max_size) {
265 0 : ctx->dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->max_size, &ctx->output);
266 0 : if (!ctx->dst_pck) {
267 : e = GF_OUT_OF_MEM;
268 : goto exit;
269 : }
270 0 : ctx->dst.next_output_byte = ctx->output;
271 0 : ctx->dst.free_in_buffer = ctx->max_size;
272 0 : ctx->dst_pck_size = ctx->max_size;
273 : }
274 :
275 7 : jpeg_create_compress(&cinfo);
276 7 : cinfo.image_width = ctx->width;
277 7 : cinfo.image_height = ctx->height;
278 7 : cinfo.input_components = 3;
279 7 : cinfo.in_color_space = JCS_YCbCr;
280 7 : if (ctx->dctmode==0) cinfo.dct_method = JDCT_ISLOW;
281 7 : else if (ctx->dctmode==2) cinfo.dct_method = JDCT_FLOAT;
282 7 : else cinfo.dct_method = JDCT_IFAST;
283 7 : cinfo.optimize_coding = TRUE;
284 7 : jpeg_set_defaults (&cinfo);
285 :
286 7 : cinfo.raw_data_in = TRUE;
287 7 : cinfo.comp_info[0].h_samp_factor = 2;
288 7 : cinfo.comp_info[0].v_samp_factor = 2;
289 7 : cinfo.comp_info[1].h_samp_factor = 1;
290 7 : cinfo.comp_info[1].v_samp_factor = 1;
291 7 : cinfo.comp_info[2].h_samp_factor = 1;
292 7 : cinfo.comp_info[2].v_samp_factor = 1;
293 : #ifdef JPEG_LIB_VERSION_MAJOR
294 : cinfo.do_fancy_downsampling = FALSE;
295 : #endif
296 7 : jpeg_set_colorspace(&cinfo, JCS_YCbCr);
297 7 : jpeg_set_quality(&cinfo, MIN(100, ctx->quality), TRUE);
298 :
299 7 : cinfo.dest = &ctx->dst;
300 :
301 7 : jpeg_start_compress (&cinfo, TRUE);
302 :
303 7 : stride = ctx->stride;
304 7 : stride_uv = ctx->stride_uv;
305 7 : pY = pU = pV = 0;
306 7 : if (in_data) {
307 7 : pY = in_data;
308 7 : pU = pY + ctx->stride * ctx->height;
309 7 : pV = pU + ctx->stride_uv * ctx->height/2;
310 : } else {
311 0 : e = frame_ifce->get_plane(frame_ifce, 0, (const u8 **)&pY, &stride);
312 0 : if (e) {
313 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc] Failed to fetch first plane in hardware frame\n"));
314 : goto exit;
315 : }
316 0 : if (ctx->nb_planes>1) {
317 0 : e = frame_ifce->get_plane(frame_ifce, 1, (const u8 **)&pU, &stride_uv);
318 0 : if (e) {
319 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc] Failed to fetch first plane in hardware frame\n"));
320 : goto exit;
321 : }
322 0 : if (ctx->nb_planes>2) {
323 0 : e = frame_ifce->get_plane(frame_ifce, 2, (const u8 **)&pV, &stride_uv);
324 0 : if (e) {
325 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc] Failed to fetch first plane in hardware frame\n"));
326 : goto exit;
327 : }
328 : }
329 : }
330 : }
331 :
332 183 : for (j=0; j<ctx->height; j+=16) {
333 2816 : for (i=0;i<16;i++) {
334 2816 : y[i] = pY + stride*(i+j);
335 2816 : if (i%2 == 0) {
336 1408 : cb[i/2] = pU + stride_uv*((i+j)/2);
337 1408 : cr[i/2] = pV + stride_uv*((i+j)/2);
338 : }
339 : }
340 176 : jpeg_write_raw_data (&cinfo, block, 16);
341 : }
342 7 : jpeg_finish_compress(&cinfo);
343 :
344 7 : exit:
345 :
346 7 : jpeg_destroy_compress(&cinfo);
347 7 : if (ctx->dst_pck) {
348 7 : if (!e) {
349 7 : gf_filter_pck_merge_properties(pck, ctx->dst_pck);
350 7 : gf_filter_pck_send(ctx->dst_pck);
351 : } else {
352 0 : gf_filter_pck_discard(ctx->dst_pck);
353 : }
354 : }
355 7 : if (ctx->max_size<ctx->dst_pck_size)
356 7 : ctx->max_size = ctx->dst_pck_size;
357 :
358 7 : ctx->dst_pck = NULL;
359 7 : ctx->output = NULL;
360 7 : ctx->dst_pck_size = 0;
361 7 : gf_filter_pid_drop_packet(ctx->ipid);
362 : return GF_OK;
363 : }
364 :
365 7 : static GF_Err jpgenc_initialize(GF_Filter *filter)
366 : {
367 : #ifdef GPAC_ENABLE_COVERAGE
368 7 : if (gf_sys_is_cov_mode()) {
369 7 : jpgenc_output_message(NULL);
370 7 : jpgenc_nonfatal_error2(NULL, 0);
371 7 : jpgenc_fatal_error(NULL);
372 : }
373 : #endif
374 7 : return GF_OK;
375 : }
376 :
377 : static const GF_FilterCapability JPGEncCaps[] =
378 : {
379 : CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
380 : CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW),
381 : CAP_UINT(GF_CAPS_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_JPEG)
382 : };
383 :
384 : #define OFFS(_n) #_n, offsetof(GF_JPGEncCtx, _n)
385 : static GF_FilterArgs JPGEncArgs[] =
386 : {
387 : { OFFS(dctmode), "type of DCT used\n"
388 : "- slow: precise but slow integer DCT\n"
389 : "- fast: less precise but faster integer DCT\n"
390 : "- float: float DCT"
391 : "", GF_PROP_UINT, "fast", "slow|fast|float", GF_FS_ARG_HINT_ADVANCED},
392 : { OFFS(quality), "compression quality", GF_PROP_UINT, "100", "0-100", GF_FS_ARG_UPDATE},
393 : {0}
394 : };
395 :
396 : GF_FilterRegister JPGEncRegister = {
397 : .name = "jpgenc",
398 : GF_FS_SET_DESCRIPTION("JPG encoder")
399 : GF_FS_SET_HELP("This filter encodes a single uncompressed video PID to JPEG using libjpeg.")
400 : .private_size = sizeof(GF_JPGEncCtx),
401 : .args = JPGEncArgs,
402 : SETCAPS(JPGEncCaps),
403 : .initialize = jpgenc_initialize,
404 : .configure_pid = jpgenc_configure_pid,
405 : .process = jpgenc_process,
406 : };
407 :
408 : #endif
409 :
410 2877 : const GF_FilterRegister *jpgenc_register(GF_FilterSession *session)
411 : {
412 : #ifdef GPAC_HAS_JPEG
413 2877 : return &JPGEncRegister;
414 : #else
415 : return NULL;
416 : #endif
417 : }
|