524 lines
15 KiB
C++
524 lines
15 KiB
C++
/*
|
|
* 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();
|
|
}
|