blob: 1bfa55a77945088e3d25aa764a8cadf2ad41cd27 [file] [log] [blame]
/*
* 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();
}