LCOV - code coverage report
Current view: top level - filters - enc_jpg.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 150 183 82.0 %
Date: 2021-04-29 23:48:07 Functions: 10 10 100.0 %

          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             : }

Generated by: LCOV version 1.13