| /* |
| * Copyright Samsung Electronics Co.,LTD. |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <cstdio> |
| |
| #include <cstring> |
| #include <unistd.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/mman.h> |
| #include <fcntl.h> |
| |
| #include <linux/videodev2.h> |
| |
| #include <exynos-hwjpeg.h> |
| #include <hwjpeglib-exynos.h> |
| |
| #include "hwjpeg-internal.h" |
| |
| #define ALOGERR(fmt, args...) ((void)ALOG(LOG_ERROR, LOG_TAG, fmt " [%s]", ##args, strerror(errno))) |
| |
| #define ROUND_DOWN(val, denom) ((val) & ~((denom) - 1)) |
| #define ROUND_UP(val, denom) ROUND_DOWN((val) + (denom) - 1, denom) |
| #define TO_MASK(val) ((val) - 1) |
| |
| class CJpegStreamParser { |
| private: |
| unsigned char *m_pStreamBase; |
| size_t m_nStreamSize; |
| |
| unsigned char m_nComponents; |
| unsigned short m_nWidth; |
| unsigned short m_nHeight; |
| |
| void Initialize(); |
| size_t GetLength(unsigned char *addr); |
| bool ParseFrame(unsigned char *addr); |
| |
| off_t GetOffset(unsigned char *addr) { |
| unsigned long beg = reinterpret_cast<unsigned long>(m_pStreamBase); |
| unsigned long cur = reinterpret_cast<unsigned long>(addr); |
| return static_cast<off_t>(cur - beg); |
| } |
| |
| public: |
| unsigned char m_iHorizontalFactor; |
| unsigned char m_iVerticalFactor; |
| |
| CJpegStreamParser() : m_pStreamBase(NULL), m_nStreamSize(0) { } |
| ~CJpegStreamParser() { } |
| |
| bool Parse(unsigned char *streambase, size_t length); |
| |
| int GetImageFormat(); |
| unsigned int GetWidth() { return m_nWidth; } |
| unsigned int GetHeight() { return m_nHeight; } |
| unsigned int GetNumComponents() { return m_nComponents; } |
| }; |
| |
| void CJpegStreamParser::Initialize() |
| { |
| m_nComponents = 0; |
| m_nWidth = 0; |
| m_nHeight = 0; |
| m_iHorizontalFactor = 1; |
| m_iVerticalFactor = 1; |
| } |
| |
| size_t CJpegStreamParser::GetLength(unsigned char *addr) |
| { |
| size_t len = static_cast<size_t>(*addr++) * 0x100; |
| return len + *addr; |
| } |
| |
| bool CJpegStreamParser::Parse(unsigned char *streambase, size_t length) |
| { |
| Initialize(); |
| |
| m_pStreamBase = streambase; |
| m_nStreamSize = length; |
| |
| unsigned char *addr = m_pStreamBase; |
| size_t filelen = m_nStreamSize; |
| |
| // Finding SOI (xFFD8) |
| if ((filelen < 2) || (addr[0] != 0xFF) || (addr[1] != 0xD8)) { |
| ALOGE("Not a valid JPEG stream (len %zu, marker %02x%02x", filelen, addr[0], addr[1]); |
| return false; |
| } |
| addr += 2; |
| filelen -= 2; |
| |
| while (true) { // DHT, DQT, SOF, SOS |
| if (filelen < 2) { |
| ALOGE("Incomplete JPEG Stream"); |
| return false; |
| } |
| |
| if (*addr++ != 0xFF) { |
| ALOGE("Corrupted JPEG stream"); |
| return false; |
| } |
| |
| unsigned char marker = *addr++; |
| |
| if ((marker != 0xC4) && ((marker & 0xF0) == 0xC0)) { // SOFn |
| if (marker != 0xC0) { |
| ALOGE("SOF%d is not supported (offset %zu)", marker & 0xF, m_nStreamSize - filelen); |
| return false; |
| } |
| |
| if (filelen < GetLength(addr)) { |
| ALOGE("Too small SOF0 segment"); |
| return false; |
| } |
| |
| if (!ParseFrame(addr)) |
| return false; |
| |
| return true; // this is the successful exit point |
| } else if (marker == 0xD9) { // EOI |
| // This will not meet. |
| ALOGE("Unexpected EOI found at %lu\n", GetOffset(addr - 2)); |
| return false; |
| } else { |
| if ((marker == 0xCC) || (marker == 0xDC)) { // DAC and DNL |
| ALOGE("Unsupported JPEG stream: found marker 0xFF%02X", marker); |
| return false; |
| } |
| |
| if (filelen < GetLength(addr)) { |
| ALOGE("Corrupted JPEG stream"); |
| return false; |
| } |
| } |
| |
| if (GetLength(addr) == 0) { |
| ALOGE("Invalid length 0 is read at offset %lu", GetOffset(addr)); |
| return false; |
| } |
| |
| filelen -= GetLength(addr); |
| addr += GetLength(addr); |
| } |
| |
| // NEVER REACH HERE |
| |
| ALOGE("Unable to find the frame header"); |
| |
| return false; |
| } |
| |
| bool CJpegStreamParser::ParseFrame(unsigned char *addr) |
| { // 2 bytes of length |
| // 1 byte of bits per sample |
| // 2 bytes of height |
| // 2 bytes of width |
| // 1 byte of number of components |
| // n * 3 byte component specifications |
| if (GetLength(addr) < 17) { |
| ALOGE("SOF0 should include all three components"); |
| return false; |
| } |
| addr += 2; // skip length |
| |
| if (*addr != 8) { // bits per sample |
| ALOGE("Bits Per Sample should be 8 but it is %d", *addr); |
| return false; |
| } |
| addr++; |
| |
| m_nHeight = static_cast<unsigned short>(GetLength(addr)); |
| if ((m_nHeight < 8) || (m_nHeight > 16383)) { |
| ALOGE("Height %d is not supported", m_nHeight); |
| return false; |
| } |
| addr += 2; |
| |
| m_nWidth = static_cast<unsigned short>(GetLength(addr)); |
| if ((m_nWidth < 8) || (m_nWidth > 16383)) { |
| ALOGE("Width %d is not supported", m_nWidth); |
| return false; |
| } |
| addr += 2; |
| |
| m_nComponents = *addr; |
| if (m_nComponents != 3) { |
| ALOGE("Number of components should be 3 but it is %d", m_nComponents); |
| return false; |
| } |
| addr++; |
| |
| // Only the first component is needed to find chroma subsampling factor |
| addr++; // skip component identifier |
| if ((*addr != 0x11) && (*addr != 0x21) && (*addr != 0x12) && (*addr != 0x22)) { |
| ALOGE("Invalid Luma sampling factor %#02x", *addr); |
| return false; |
| } |
| m_iHorizontalFactor = *addr >> 4; |
| m_iVerticalFactor = *addr & 0xF; |
| |
| return true; |
| } |
| |
| class CLibhwjpegDecompressor: public hwjpeg_decompressor_struct { |
| enum { |
| HWJPG_FLAG_NEED_MUNMAP = 1, |
| }; |
| |
| unsigned int m_flags; |
| bool m_bPrepared; |
| CHWJpegDecompressor *m_hwjpeg; |
| |
| unsigned char *m_pStreamBuffer; |
| size_t m_nStreamLength; |
| size_t m_nDummyBytes; |
| |
| CJpegStreamParser m_jpegStreamParser; |
| public: |
| CLibhwjpegDecompressor() : m_flags(0) { |
| // members of hwjpeg_decompressor_struct |
| image_width = 0; |
| image_height = 0; |
| num_components = 3; |
| chroma_h_samp_factor = 1; |
| chroma_v_samp_factor = 1; |
| scale_factor = 1; |
| output_width = 0; |
| output_height = 0; |
| m_bPrepared = false; |
| m_pStreamBuffer = NULL; |
| |
| output_format = V4L2_PIX_FMT_RGB32; |
| |
| // members of this |
| m_nStreamLength = 0; |
| m_nDummyBytes = 0; |
| |
| m_hwjpeg = new CHWJpegV4L2Decompressor; |
| if (!m_hwjpeg || !*m_hwjpeg) { |
| ALOGE("Failed to create HWJPEG decompressor"); |
| delete m_hwjpeg; |
| } |
| } |
| |
| ~CLibhwjpegDecompressor() { |
| delete m_hwjpeg; |
| |
| if (!!(m_flags & HWJPG_FLAG_NEED_MUNMAP)) |
| munmap(m_pStreamBuffer, m_nStreamLength + m_nDummyBytes); |
| } |
| |
| bool SetStreamPath(const char *path) { |
| if ((m_pStreamBuffer != NULL) && !!(m_flags & HWJPG_FLAG_NEED_MUNMAP)) { |
| munmap(m_pStreamBuffer, m_nStreamLength + m_nDummyBytes); |
| m_flags &= ~HWJPG_FLAG_NEED_MUNMAP; |
| m_pStreamBuffer = NULL; |
| m_nStreamLength = 0; |
| } |
| |
| int fd = open(path, O_RDONLY); |
| if (fd < 0) { |
| ALOGERR("Failed to open '%s' for decompression", path); |
| return false; |
| } |
| |
| struct stat st; |
| if (fstat(fd, &st) < 0) { |
| ALOGERR("Failed to read size of '%s'", path); |
| close(fd); |
| return false; |
| } |
| |
| m_nStreamLength = st.st_size; |
| m_nDummyBytes = 0; |
| |
| m_pStreamBuffer = reinterpret_cast<unsigned char *>( |
| mmap(NULL, m_nStreamLength, |
| PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)); |
| if (m_pStreamBuffer == MAP_FAILED) { |
| m_pStreamBuffer = NULL; |
| close(fd); |
| ALOGERR("Failed to mmap %zu bytes of '%s'", m_nStreamLength, path); |
| return false; |
| } |
| |
| m_bPrepared = false; |
| |
| m_flags |= HWJPG_FLAG_NEED_MUNMAP; |
| |
| close(fd); |
| return true; |
| } |
| |
| bool SetStreamBuffer(unsigned char *buffer, size_t len, size_t dummybytes) { |
| if ((m_pStreamBuffer != NULL) && !!(m_flags & HWJPG_FLAG_NEED_MUNMAP)) { |
| munmap(m_pStreamBuffer, m_nStreamLength + m_nDummyBytes); |
| m_flags &= ~HWJPG_FLAG_NEED_MUNMAP; |
| } |
| |
| m_pStreamBuffer = buffer; |
| m_nStreamLength = len; |
| m_nDummyBytes = dummybytes; |
| |
| m_bPrepared = false; |
| |
| return true; |
| } |
| |
| bool SetStreamBuffer(int buffer, size_t len, size_t dummybytes) { |
| if ((m_pStreamBuffer != NULL) && !!(m_flags & HWJPG_FLAG_NEED_MUNMAP)) { |
| munmap(m_pStreamBuffer, m_nStreamLength + m_nDummyBytes); |
| m_flags &= ~HWJPG_FLAG_NEED_MUNMAP; |
| } |
| |
| m_nStreamLength = len; |
| m_nDummyBytes = dummybytes; |
| |
| m_pStreamBuffer = reinterpret_cast<unsigned char *>( |
| mmap(NULL, m_nStreamLength + m_nDummyBytes, |
| PROT_READ | PROT_WRITE, MAP_SHARED, buffer, 0)); |
| if (m_pStreamBuffer == MAP_FAILED) { |
| m_pStreamBuffer = NULL; |
| ALOGERR("Failed to mmap %zu bytes of dmabuf fd %d", m_nStreamLength, buffer); |
| return false; |
| } |
| |
| m_flags |= HWJPG_FLAG_NEED_MUNMAP; |
| |
| m_bPrepared = false; |
| |
| return true; |
| } |
| |
| bool SetImageBuffer(unsigned char *buffer[3], size_t len[3], unsigned int num_bufs) { |
| if (num_bufs != 1) { |
| ALOGE("multi-planar image is not supported(%u planes)", num_bufs); |
| return false; |
| } |
| |
| return m_hwjpeg->SetImageBuffer(reinterpret_cast<char *>(buffer[0]), len[0]); |
| } |
| |
| bool SetImageBuffer(int buffer[3], size_t len[3], unsigned int num_bufs) { |
| if (num_bufs != 1) { |
| ALOGE("multi-planar image is not supported(%u planes)", num_bufs); |
| return false; |
| } |
| |
| return m_hwjpeg->SetImageBuffer(buffer[0], len[0]); |
| } |
| |
| void SetDownscaleFactor(unsigned int factor) { scale_factor = factor; } |
| |
| bool PrepareDecompression(); |
| bool Decompress(); |
| |
| bool IsEnoughStreamBuffer() { return true; } |
| }; |
| |
| bool CLibhwjpegDecompressor::PrepareDecompression() |
| { |
| if (!m_hwjpeg) { |
| ALOGE("device node is not opened!"); |
| return false; |
| } |
| |
| if ((scale_factor != 1) && (scale_factor != 2) && |
| (scale_factor != 4) && (scale_factor != 8)) { |
| ALOGE("Invalid downscaling factor %d", scale_factor); |
| return false; |
| } |
| |
| if (m_pStreamBuffer == NULL) { |
| ALOGE("No stream buffer is configured"); |
| return false; |
| } |
| |
| if (!m_jpegStreamParser.Parse(m_pStreamBuffer, m_nStreamLength)) |
| return false; |
| |
| image_width = m_jpegStreamParser.GetWidth(); |
| image_height = m_jpegStreamParser.GetHeight(); |
| num_components = m_jpegStreamParser.GetNumComponents(); |
| chroma_h_samp_factor = m_jpegStreamParser.m_iHorizontalFactor; |
| chroma_v_samp_factor = m_jpegStreamParser.m_iVerticalFactor; |
| |
| if (((image_width % (chroma_h_samp_factor * scale_factor)) != 0) || |
| ((image_height % (chroma_v_samp_factor * scale_factor)) != 0)) { |
| ALOGE("Downscaling by factor %d of compressed image size %dx%d(chroma %d:%d) is not supported", |
| scale_factor, image_width, image_height, chroma_h_samp_factor, chroma_v_samp_factor); |
| return false; |
| } |
| |
| output_width = image_width / scale_factor; |
| output_height = image_height / scale_factor; |
| |
| if (!m_hwjpeg->SetStreamPixelSize(image_width, image_height)) { |
| ALOGE("Failed to configure stream pixel size (%ux%u)", image_width, image_height); |
| return false; |
| } |
| |
| if (!m_hwjpeg->SetImageFormat(output_format, output_width, output_height)) { |
| ALOGE("Failed to configure image format (%ux%u/%08X)", output_width, output_height, output_format); |
| return false; |
| } |
| |
| m_bPrepared = true; |
| |
| return true; |
| } |
| |
| bool CLibhwjpegDecompressor::Decompress() |
| { |
| if (!m_bPrepared) { |
| ALOGE("JPEG header is not parsed"); |
| return false; |
| } |
| |
| if (!IsEnoughStreamBuffer()) { |
| ALOGE("Not enough buffer length for HWJPEG"); |
| return false; |
| } |
| |
| m_bPrepared = false; |
| |
| if (!m_hwjpeg->Decompress(reinterpret_cast<char *>(m_pStreamBuffer), m_nStreamLength)) { |
| ALOGE("Failed to decompress"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| hwjpeg_decompress_ptr hwjpeg_create_decompress() |
| { |
| hwjpeg_decompress_ptr p = new CLibhwjpegDecompressor(); |
| if (!p) |
| ALOGE("Failed to create decompress struct"); |
| return p; |
| } |
| |
| bool hwjpeg_file_src(hwjpeg_decompress_ptr cinfo, const char *path) |
| { |
| CLibhwjpegDecompressor *decomp = reinterpret_cast<CLibhwjpegDecompressor *>(cinfo); |
| return decomp->SetStreamPath(path); |
| } |
| |
| void hwjpeg_config_image_format(hwjpeg_decompress_ptr cinfo, __u32 v4l2_pix_fmt) |
| { |
| cinfo->output_format = v4l2_pix_fmt; |
| } |
| |
| bool hwjpeg_dmabuf_src(hwjpeg_decompress_ptr cinfo, int infd, size_t insize, size_t dummybytes) |
| { |
| CLibhwjpegDecompressor *decomp = reinterpret_cast<CLibhwjpegDecompressor *>(cinfo); |
| return decomp->SetStreamBuffer(infd, insize, dummybytes); |
| } |
| |
| bool hwjpeg_mem_src(hwjpeg_decompress_ptr cinfo, |
| unsigned char *inbuffer, size_t insize, size_t dummybytes) |
| { |
| CLibhwjpegDecompressor *decomp = reinterpret_cast<CLibhwjpegDecompressor *>(cinfo); |
| return decomp->SetStreamBuffer(inbuffer, insize, dummybytes); |
| } |
| |
| bool hwjpeg_mem_dst(hwjpeg_decompress_ptr cinfo, |
| unsigned char *outbuffer[], size_t outsize[], unsigned int num_buffers) |
| { |
| CLibhwjpegDecompressor *decomp = reinterpret_cast<CLibhwjpegDecompressor *>(cinfo); |
| return decomp->SetImageBuffer(outbuffer, outsize, num_buffers); |
| } |
| |
| bool hwjpeg_dmabuf_dst(hwjpeg_decompress_ptr cinfo, |
| int outfd[], size_t outsize[], unsigned int num_buffers) |
| { |
| CLibhwjpegDecompressor *decomp = reinterpret_cast<CLibhwjpegDecompressor *>(cinfo); |
| return decomp->SetImageBuffer(outfd, outsize, num_buffers); |
| } |
| |
| void hwjpeg_set_downscale_factor(hwjpeg_decompress_ptr cinfo, unsigned int factor) |
| { |
| cinfo->scale_factor = factor; |
| } |
| |
| bool hwjpeg_read_header(hwjpeg_decompress_ptr cinfo) |
| { |
| CLibhwjpegDecompressor *decomp = reinterpret_cast<CLibhwjpegDecompressor *>(cinfo); |
| return decomp->PrepareDecompression(); |
| } |
| |
| bool hwjpeg_start_decompress(hwjpeg_decompress_ptr cinfo) |
| { |
| CLibhwjpegDecompressor *decomp = reinterpret_cast<CLibhwjpegDecompressor *>(cinfo); |
| return decomp->Decompress(); |
| } |
| |
| void hwjpeg_destroy_decompress(hwjpeg_decompress_ptr cinfo) |
| { |
| CLibhwjpegDecompressor *decomp = reinterpret_cast<CLibhwjpegDecompressor *>(cinfo); |
| delete decomp; |
| } |
| |
| bool hwjpeg_has_enough_stream_buffer(hwjpeg_decompress_ptr cinfo) |
| { |
| CLibhwjpegDecompressor *decomp = reinterpret_cast<CLibhwjpegDecompressor *>(cinfo); |
| return decomp->IsEnoughStreamBuffer(); |
| } |