static GstFlowReturn gst_jpeg_dec_decode_direct (GstJpegDec * dec, GstVideoFrame * frame) { guchar **line[3]; /* the jpeg line buffer */ guchar *y[4 * DCTSIZE] = { NULL, }; /* alloc enough for the lines */ guchar *u[4 * DCTSIZE] = { NULL, }; /* r_v will be <4 */ guchar *v[4 * DCTSIZE] = { NULL, }; gint i, j; gint lines, v_samp[3]; guchar *base[3], *last[3]; gint stride[3]; guint height; line[0] = y; line[1] = u; line[2] = v; v_samp[0] = dec->cinfo.comp_info[0].v_samp_factor; v_samp[1] = dec->cinfo.comp_info[1].v_samp_factor; v_samp[2] = dec->cinfo.comp_info[2].v_samp_factor; if (G_UNLIKELY (v_samp[0] > 2 || v_samp[1] > 2 || v_samp[2] > 2)) goto format_not_supported; height = GST_VIDEO_FRAME_HEIGHT (frame); for (i = 0; i < 3; i++) { base[i] = GST_VIDEO_FRAME_COMP_DATA (frame, i); stride[i] = GST_VIDEO_FRAME_COMP_STRIDE (frame, i); /* make sure we don't make jpeglib write beyond our buffer, * which might happen if (height % (r_v*DCTSIZE)) != 0 */ last[i] = base[i] + (GST_VIDEO_FRAME_COMP_STRIDE (frame, i) * (GST_VIDEO_FRAME_COMP_HEIGHT (frame, i) - 1)); } /* let jpeglib decode directly into our final buffer */ GST_DEBUG_OBJECT (dec, "decoding directly into output buffer"); for (i = 0; i < height; i += v_samp[0] * DCTSIZE) { for (j = 0; j < (v_samp[0] * DCTSIZE); ++j) { /* Y */ line[0][j] = base[0] + (i + j) * stride[0]; if (G_UNLIKELY (line[0][j] > last[0])) line[0][j] = last[0]; /* U */ if (v_samp[1] == v_samp[0]) { line[1][j] = base[1] + ((i + j) / 2) * stride[1]; } else if (j < (v_samp[1] * DCTSIZE)) { line[1][j] = base[1] + ((i / 2) + j) * stride[1]; } if (G_UNLIKELY (line[1][j] > last[1])) line[1][j] = last[1]; /* V */ if (v_samp[2] == v_samp[0]) { line[2][j] = base[2] + ((i + j) / 2) * stride[2]; } else if (j < (v_samp[2] * DCTSIZE)) { line[2][j] = base[2] + ((i / 2) + j) * stride[2]; } if (G_UNLIKELY (line[2][j] > last[2])) line[2][j] = last[2]; } lines = jpeg_read_raw_data (&dec->cinfo, line, v_samp[0] * DCTSIZE); if (G_UNLIKELY (!lines)) { GST_INFO_OBJECT (dec, "jpeg_read_raw_data() returned 0"); } } return GST_FLOW_OK; format_not_supported: { gboolean ret = GST_FLOW_OK; GST_VIDEO_DECODER_ERROR (dec, 1, STREAM, DECODE, (_("Failed to decode JPEG image")), ("Unsupported subsampling schema: v_samp factors: %u %u %u", v_samp[0], v_samp[1], v_samp[2]), ret); return ret; } }
static void gst_jpeg_dec_decode_indirect (GstJpegDec * dec, GstVideoFrame * frame, gint r_v, gint r_h, gint comp) { guchar *y_rows[16], *u_rows[16], *v_rows[16]; guchar **scanarray[3] = { y_rows, u_rows, v_rows }; gint i, j, k; gint lines; guchar *base[3], *last[3]; gint stride[3]; gint width, height; GST_DEBUG_OBJECT (dec, "unadvantageous width or r_h, taking slow route involving memcpy"); width = GST_VIDEO_FRAME_WIDTH (frame); height = GST_VIDEO_FRAME_HEIGHT (frame); if (G_UNLIKELY (!gst_jpeg_dec_ensure_buffers (dec, GST_ROUND_UP_32 (width)))) return; for (i = 0; i < 3; i++) { base[i] = GST_VIDEO_FRAME_COMP_DATA (frame, i); stride[i] = GST_VIDEO_FRAME_COMP_STRIDE (frame, i); /* make sure we don't make jpeglib write beyond our buffer, * which might happen if (height % (r_v*DCTSIZE)) != 0 */ last[i] = base[i] + (GST_VIDEO_FRAME_COMP_STRIDE (frame, i) * (GST_VIDEO_FRAME_COMP_HEIGHT (frame, i) - 1)); } memcpy (y_rows, dec->idr_y, 16 * sizeof (gpointer)); memcpy (u_rows, dec->idr_u, 16 * sizeof (gpointer)); memcpy (v_rows, dec->idr_v, 16 * sizeof (gpointer)); /* fill chroma components for grayscale */ if (comp == 1) { GST_DEBUG_OBJECT (dec, "grayscale, filling chroma"); for (i = 0; i < 16; i++) { memset (u_rows[i], GST_ROUND_UP_32 (width), 0x80); memset (v_rows[i], GST_ROUND_UP_32 (width), 0x80); } } for (i = 0; i < height; i += r_v * DCTSIZE) { lines = jpeg_read_raw_data (&dec->cinfo, scanarray, r_v * DCTSIZE); if (G_LIKELY (lines > 0)) { for (j = 0, k = 0; j < (r_v * DCTSIZE); j += r_v, k++) { if (G_LIKELY (base[0] <= last[0])) { memcpy (base[0], y_rows[j], stride[0]); base[0] += stride[0]; } if (r_v == 2) { if (G_LIKELY (base[0] <= last[0])) { memcpy (base[0], y_rows[j + 1], stride[0]); base[0] += stride[0]; } } if (G_LIKELY (base[1] <= last[1] && base[2] <= last[2])) { if (r_h == 2) { memcpy (base[1], u_rows[k], stride[1]); memcpy (base[2], v_rows[k], stride[2]); } else if (r_h == 1) { hresamplecpy1 (base[1], u_rows[k], stride[1]); hresamplecpy1 (base[2], v_rows[k], stride[2]); } else { /* FIXME: implement (at least we avoid crashing by doing nothing) */ } } if (r_v == 2 || (k & 1) != 0) { base[1] += stride[1]; base[2] += stride[2]; } } } else { GST_INFO_OBJECT (dec, "jpeg_read_raw_data() returned 0"); } } }
int main(int argc, char *argv[]) { const char *jpg_path; const char *yuv_path; struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; FILE *jpg_fd; int luma_width; int luma_height; int chroma_width; int chroma_height; int frame_width; int yuv_size; JSAMPLE *jpg_buffer; JSAMPROW yrow_pointer[16]; JSAMPROW cbrow_pointer[8]; JSAMPROW crrow_pointer[8]; JSAMPROW *plane_pointer[3]; unsigned char *yuv_buffer; int x; int y; FILE *yuv_fd; if (argc != 3) { fprintf(stderr, "Required arguments:\n"); fprintf(stderr, "1. Path to JPG input file\n"); fprintf(stderr, "2. Path to YUV output file\n"); return 1; } /* Will check these for validity when opening via 'fopen'. */ jpg_path = argv[1]; yuv_path = argv[2]; cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); jpg_fd = fopen(jpg_path, "rb"); if (!jpg_fd) { fprintf(stderr, "Invalid path to JPEG file!\n"); return 1; } jpeg_stdio_src(&cinfo, jpg_fd); jpeg_read_header(&cinfo, TRUE); cinfo.raw_data_out = TRUE; cinfo.do_fancy_upsampling = FALSE; jpeg_start_decompress(&cinfo); luma_width = cinfo.output_width; luma_height = cinfo.output_height; chroma_width = (luma_width + 1) >> 1; chroma_height = (luma_height + 1) >> 1; yuv_size = luma_width*luma_height + 2*chroma_width*chroma_height; yuv_buffer = malloc(yuv_size); if (!yuv_buffer) { fclose(jpg_fd); fprintf(stderr, "Memory allocation failure!\n"); return 1; } frame_width = (cinfo.output_width + (16 - 1)) & ~(16 - 1); jpg_buffer = malloc(frame_width*16 + 2*(frame_width/2)*8); if (!jpg_buffer) { fclose(jpg_fd); free(yuv_buffer); fprintf(stderr, "Memory allocation failure!\n"); return 1; } plane_pointer[0] = yrow_pointer; plane_pointer[1] = cbrow_pointer; plane_pointer[2] = crrow_pointer; for (y = 0; y < 16; y++) { yrow_pointer[y] = &jpg_buffer[frame_width*y]; } for (y = 0; y < 8; y++) { cbrow_pointer[y] = &jpg_buffer[frame_width*16 + (frame_width/2)*y]; crrow_pointer[y] = &jpg_buffer[frame_width*16 + (frame_width/2)*(8 + y)]; } while (cinfo.output_scanline < cinfo.output_height) { int luma_scanline; int chroma_scanline; luma_scanline = cinfo.output_scanline; chroma_scanline = (luma_scanline + 1) >> 1; jpeg_read_raw_data(&cinfo, plane_pointer, 16); for (y = 0; y < 16 && luma_scanline + y < luma_height; y++) { for (x = 0; x < luma_width; x++) { yuv_buffer[luma_width*(luma_scanline + y) + x] = yrow_pointer[y][x]; } } for (y = 0; y < 8 && chroma_scanline + y < chroma_height; y++) { for (x = 0; x < chroma_width; x++) { yuv_buffer[luma_width*luma_height + chroma_width*(chroma_scanline + y) + x] = cbrow_pointer[y][x]; yuv_buffer[luma_width*luma_height + chroma_width*chroma_height + chroma_width*(chroma_scanline + y) + x] = crrow_pointer[y][x]; } } } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); fclose(jpg_fd); free(jpg_buffer); yuv_fd = fopen(yuv_path, "wb"); if (!yuv_fd) { fprintf(stderr, "Invalid path to YUV file!"); free(yuv_buffer); return 1; } if (fwrite(yuv_buffer, yuv_size, 1, yuv_fd) != 1) { fprintf(stderr, "Error writing yuv file\n"); } fclose(yuv_fd); free(yuv_buffer); return 0; }
DLLEXPORT int DLLCALL tjDecompress(tjhandle h, unsigned char *srcbuf, unsigned long size, unsigned char *dstbuf, int width, int pitch, int height, int ps, int flags) { int i, row, retval=0; JSAMPROW *row_pointer=NULL, *outbuf[MAX_COMPONENTS]; int cw[MAX_COMPONENTS], ch[MAX_COMPONENTS], iw[MAX_COMPONENTS], tmpbufsize=0, usetmpbuf=0, th[MAX_COMPONENTS]; JSAMPLE *_tmpbuf=NULL; JSAMPROW *tmpbuf[MAX_COMPONENTS]; checkhandle(h); for(i=0; i<MAX_COMPONENTS; i++) { tmpbuf[i]=NULL; outbuf[i]=NULL; } if(srcbuf==NULL || size<=0 || dstbuf==NULL || width<=0 || pitch<0 || height<=0) _throw("Invalid argument in tjDecompress()"); if(ps!=3 && ps!=4 && ps!=1) _throw("This decompressor can only handle 24-bit and 32-bit RGB or 8-bit grayscale output"); if(!j->initd) _throw("Instance has not been initialized for decompression"); if(pitch==0) pitch=width*ps; if(flags&TJ_FORCEMMX) putenv("JSIMD_FORCEMMX=1"); else if(flags&TJ_FORCESSE) putenv("JSIMD_FORCESSE=1"); else if(flags&TJ_FORCESSE2) putenv("JSIMD_FORCESSE2=1"); if(setjmp(j->jerr.jb)) { // this will execute if LIBJPEG has an error retval=-1; goto bailout; } j->jsms.bytes_in_buffer = size; j->jsms.next_input_byte = srcbuf; jpeg_read_header(&j->dinfo, TRUE); if(flags&TJ_YUV) { j_decompress_ptr dinfo=&j->dinfo; JSAMPLE *ptr=dstbuf; for(i=0; i<dinfo->num_components; i++) { jpeg_component_info *compptr=&dinfo->comp_info[i]; int ih; iw[i]=compptr->width_in_blocks*DCTSIZE; ih=compptr->height_in_blocks*DCTSIZE; cw[i]=PAD(dinfo->image_width, dinfo->max_h_samp_factor) *compptr->h_samp_factor/dinfo->max_h_samp_factor; ch[i]=PAD(dinfo->image_height, dinfo->max_v_samp_factor) *compptr->v_samp_factor/dinfo->max_v_samp_factor; if(iw[i]!=cw[i] || ih!=ch[i]) usetmpbuf=1; th[i]=compptr->v_samp_factor*DCTSIZE; tmpbufsize+=iw[i]*th[i]; if((outbuf[i]=(JSAMPROW *)malloc(sizeof(JSAMPROW)*ch[i]))==NULL) _throw("Memory allocation failed in tjDecompress()"); for(row=0; row<ch[i]; row++) { outbuf[i][row]=ptr; ptr+=PAD(cw[i], 4); } } if(usetmpbuf) { if((_tmpbuf=(JSAMPLE *)malloc(sizeof(JSAMPLE)*tmpbufsize))==NULL) _throw("Memory allocation failed in tjDecompress()"); ptr=_tmpbuf; for(i=0; i<dinfo->num_components; i++) { if((tmpbuf[i]=(JSAMPROW *)malloc(sizeof(JSAMPROW)*th[i]))==NULL) _throw("Memory allocation failed in tjDecompress()"); for(row=0; row<th[i]; row++) { tmpbuf[i][row]=ptr; ptr+=iw[i]; } } } } else { if((row_pointer=(JSAMPROW *)malloc(sizeof(JSAMPROW)*height))==NULL) _throw("Memory allocation failed in tjDecompress()"); for(i=0; i<height; i++) { if(flags&TJ_BOTTOMUP) row_pointer[i]= &dstbuf[(height-i-1)*pitch]; else row_pointer[i]= &dstbuf[i*pitch]; } } if(ps==1) j->dinfo.out_color_space = JCS_GRAYSCALE; #if JCS_EXTENSIONS==1 else j->dinfo.out_color_space = JCS_EXT_RGB; if(ps==3 && (flags&TJ_BGR)) j->dinfo.out_color_space = JCS_EXT_BGR; else if(ps==4 && !(flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) j->dinfo.out_color_space = JCS_EXT_RGBX; else if(ps==4 && (flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) j->dinfo.out_color_space = JCS_EXT_BGRX; else if(ps==4 && (flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) j->dinfo.out_color_space = JCS_EXT_XBGR; else if(ps==4 && !(flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) j->dinfo.out_color_space = JCS_EXT_XRGB; #else #error "TurboJPEG requires JPEG colorspace extensions" #endif if(flags&TJ_FASTUPSAMPLE) j->dinfo.do_fancy_upsampling=FALSE; if(flags&TJ_YUV) j->dinfo.raw_data_out=TRUE; jpeg_start_decompress(&j->dinfo); if(flags&TJ_YUV) { j_decompress_ptr dinfo=&j->dinfo; for(row=0; row<dinfo->output_height; row+=dinfo->max_v_samp_factor*DCTSIZE) { JSAMPARRAY yuvptr[MAX_COMPONENTS]; int crow[MAX_COMPONENTS]; for(i=0; i<dinfo->num_components; i++) { jpeg_component_info *compptr=&dinfo->comp_info[i]; crow[i]=row*compptr->v_samp_factor/dinfo->max_v_samp_factor; if(usetmpbuf) yuvptr[i]=tmpbuf[i]; else yuvptr[i]=&outbuf[i][crow[i]]; } jpeg_read_raw_data(dinfo, yuvptr, dinfo->max_v_samp_factor*DCTSIZE); if(usetmpbuf) { int j; for(i=0; i<dinfo->num_components; i++) { for(j=0; j<min(th[i], ch[i]-crow[i]); j++) { memcpy(outbuf[i][crow[i]+j], tmpbuf[i][j], cw[i]); } } } } } else { while(j->dinfo.output_scanline<j->dinfo.output_height) { jpeg_read_scanlines(&j->dinfo, &row_pointer[j->dinfo.output_scanline], j->dinfo.output_height-j->dinfo.output_scanline); } } jpeg_finish_decompress(&j->dinfo); bailout: if(j->dinfo.global_state>DSTATE_START) jpeg_abort_decompress(&j->dinfo); for(i=0; i<MAX_COMPONENTS; i++) { if(tmpbuf[i]) free(tmpbuf[i]); if(outbuf[i]) free(outbuf[i]); } if(_tmpbuf) free(_tmpbuf); if(row_pointer) free(row_pointer); return retval; }
SkCodec::Result SkJpegCodec::onGetYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]) { SkYUVSizeInfo defaultInfo; // This will check is_yuv_supported(), so we don't need to here. bool supportsYUV = this->onQueryYUV8(&defaultInfo, nullptr); if (!supportsYUV || sizeInfo.fSizes[SkYUVSizeInfo::kY] != defaultInfo.fSizes[SkYUVSizeInfo::kY] || sizeInfo.fSizes[SkYUVSizeInfo::kU] != defaultInfo.fSizes[SkYUVSizeInfo::kU] || sizeInfo.fSizes[SkYUVSizeInfo::kV] != defaultInfo.fSizes[SkYUVSizeInfo::kV] || sizeInfo.fWidthBytes[SkYUVSizeInfo::kY] < defaultInfo.fWidthBytes[SkYUVSizeInfo::kY] || sizeInfo.fWidthBytes[SkYUVSizeInfo::kU] < defaultInfo.fWidthBytes[SkYUVSizeInfo::kU] || sizeInfo.fWidthBytes[SkYUVSizeInfo::kV] < defaultInfo.fWidthBytes[SkYUVSizeInfo::kV]) { return fDecoderMgr->returnFailure("onGetYUV8Planes", kInvalidInput); } // Set the jump location for libjpeg errors if (setjmp(fDecoderMgr->getJmpBuf())) { return fDecoderMgr->returnFailure("setjmp", kInvalidInput); } // Get a pointer to the decompress info since we will use it quite frequently jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo(); dinfo->raw_data_out = TRUE; if (!jpeg_start_decompress(dinfo)) { return fDecoderMgr->returnFailure("startDecompress", kInvalidInput); } // A previous implementation claims that the return value of is_yuv_supported() // may change after calling jpeg_start_decompress(). It looks to me like this // was caused by a bug in the old code, but we'll be safe and check here. SkASSERT(is_yuv_supported(dinfo)); // Currently, we require that the Y plane dimensions match the image dimensions // and that the U and V planes are the same dimensions. SkASSERT(sizeInfo.fSizes[SkYUVSizeInfo::kU] == sizeInfo.fSizes[SkYUVSizeInfo::kV]); SkASSERT((uint32_t) sizeInfo.fSizes[SkYUVSizeInfo::kY].width() == dinfo->output_width && (uint32_t) sizeInfo.fSizes[SkYUVSizeInfo::kY].height() == dinfo->output_height); // Build a JSAMPIMAGE to handle output from libjpeg-turbo. A JSAMPIMAGE has // a 2-D array of pixels for each of the components (Y, U, V) in the image. // Cheat Sheet: // JSAMPIMAGE == JSAMPLEARRAY* == JSAMPROW** == JSAMPLE*** JSAMPARRAY yuv[3]; // Set aside enough space for pointers to rows of Y, U, and V. JSAMPROW rowptrs[2 * DCTSIZE + DCTSIZE + DCTSIZE]; yuv[0] = &rowptrs[0]; // Y rows (DCTSIZE or 2 * DCTSIZE) yuv[1] = &rowptrs[2 * DCTSIZE]; // U rows (DCTSIZE) yuv[2] = &rowptrs[3 * DCTSIZE]; // V rows (DCTSIZE) // Initialize rowptrs. int numYRowsPerBlock = DCTSIZE * dinfo->comp_info[0].v_samp_factor; for (int i = 0; i < numYRowsPerBlock; i++) { rowptrs[i] = SkTAddOffset<JSAMPLE>(planes[SkYUVSizeInfo::kY], i * sizeInfo.fWidthBytes[SkYUVSizeInfo::kY]); } for (int i = 0; i < DCTSIZE; i++) { rowptrs[i + 2 * DCTSIZE] = SkTAddOffset<JSAMPLE>(planes[SkYUVSizeInfo::kU], i * sizeInfo.fWidthBytes[SkYUVSizeInfo::kU]); rowptrs[i + 3 * DCTSIZE] = SkTAddOffset<JSAMPLE>(planes[SkYUVSizeInfo::kV], i * sizeInfo.fWidthBytes[SkYUVSizeInfo::kV]); } // After each loop iteration, we will increment pointers to Y, U, and V. size_t blockIncrementY = numYRowsPerBlock * sizeInfo.fWidthBytes[SkYUVSizeInfo::kY]; size_t blockIncrementU = DCTSIZE * sizeInfo.fWidthBytes[SkYUVSizeInfo::kU]; size_t blockIncrementV = DCTSIZE * sizeInfo.fWidthBytes[SkYUVSizeInfo::kV]; uint32_t numRowsPerBlock = numYRowsPerBlock; // We intentionally round down here, as this first loop will only handle // full block rows. As a special case at the end, we will handle any // remaining rows that do not make up a full block. const int numIters = dinfo->output_height / numRowsPerBlock; for (int i = 0; i < numIters; i++) { JDIMENSION linesRead = jpeg_read_raw_data(dinfo, yuv, numRowsPerBlock); if (linesRead < numRowsPerBlock) { // FIXME: Handle incomplete YUV decodes without signalling an error. return kInvalidInput; } // Update rowptrs. for (int i = 0; i < numYRowsPerBlock; i++) { rowptrs[i] += blockIncrementY; } for (int i = 0; i < DCTSIZE; i++) { rowptrs[i + 2 * DCTSIZE] += blockIncrementU; rowptrs[i + 3 * DCTSIZE] += blockIncrementV; } } uint32_t remainingRows = dinfo->output_height - dinfo->output_scanline; SkASSERT(remainingRows == dinfo->output_height % numRowsPerBlock); SkASSERT(dinfo->output_scanline == numIters * numRowsPerBlock); if (remainingRows > 0) { // libjpeg-turbo needs memory to be padded by the block sizes. We will fulfill // this requirement using a dummy row buffer. // FIXME: Should SkCodec have an extra memory buffer that can be shared among // all of the implementations that use temporary/garbage memory? SkAutoTMalloc<JSAMPLE> dummyRow(sizeInfo.fWidthBytes[SkYUVSizeInfo::kY]); for (int i = remainingRows; i < numYRowsPerBlock; i++) { rowptrs[i] = dummyRow.get(); } int remainingUVRows = dinfo->comp_info[1].downsampled_height - DCTSIZE * numIters; for (int i = remainingUVRows; i < DCTSIZE; i++) { rowptrs[i + 2 * DCTSIZE] = dummyRow.get(); rowptrs[i + 3 * DCTSIZE] = dummyRow.get(); } JDIMENSION linesRead = jpeg_read_raw_data(dinfo, yuv, numRowsPerBlock); if (linesRead < remainingRows) { // FIXME: Handle incomplete YUV decodes without signalling an error. return kInvalidInput; } } return kSuccess; }