blob: 485ee9c5a773f27c84b27b7e9e0173352329a09e [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
* Copyright@ Samsung Electronics Co. LTD
*
* 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.
*/
/*!
* \file libscaler-m2m1shot.cpp
* \brief source file for Scaler HAL
* \author Cho KyongHo <pullip.cho@samsung.com>
* \date 2014/05/08
*
* <b>Revision History: </b>
* - 2014.05.08 : Cho KyongHo (pullip.cho@samsung.com) \n
* Create
*/
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <exynos_scaler.h>
#include "libscaler-common.h"
#include "libscaler-m2m1shot.h"
#include "libscaler-swscaler.h"
using namespace std;
const char *dev_base_name[] = {
"/dev/m2m1shot_scaler0",
"/dev/m2m1shot_scaler1",
"/dev/m2m1shot_scaler2",
"/dev/m2m1shot_scaler3",
};
struct PixFormat {
unsigned int pixfmt;
char planes;
char bit_pp[3];
};
const static PixFormat g_pixfmt_table[] = {
{V4L2_PIX_FMT_RGB32, 1, {32, 0, 0}, },
{V4L2_PIX_FMT_BGR32, 1, {32, 0, 0}, },
{V4L2_PIX_FMT_RGB565, 1, {16, 0, 0}, },
{V4L2_PIX_FMT_RGB555X, 1, {16, 0, 0}, },
{V4L2_PIX_FMT_RGB444, 1, {16, 0, 0}, },
{V4L2_PIX_FMT_YUYV, 1, {16, 0, 0}, },
{V4L2_PIX_FMT_YVYU, 1, {16, 0, 0}, },
{V4L2_PIX_FMT_UYVY, 1, {16, 0, 0}, },
{V4L2_PIX_FMT_NV16, 1, {16, 0, 0}, },
{V4L2_PIX_FMT_NV61, 1, {16, 0, 0}, },
{V4L2_PIX_FMT_YUV420, 1, {12, 0, 0}, },
{V4L2_PIX_FMT_YVU420, 1, {12, 0, 0}, },
{V4L2_PIX_FMT_NV12M, 2, {8, 4, 0}, },
{V4L2_PIX_FMT_NV21M, 2, {8, 4, 0}, },
{v4l2_fourcc('V', 'M', '1', '2'), 2, {8, 4, 0}, },
{V4L2_PIX_FMT_NV12, 1, {12, 0, 0}, },
{V4L2_PIX_FMT_NV21, 1, {12, 0, 0}, },
{v4l2_fourcc('N', 'M', '2', '1'), 2, {8, 4, 0}, },
{V4L2_PIX_FMT_YUV420M, 3, {8, 2, 2}, },
{V4L2_PIX_FMT_YVU420M, 3, {8, 2, 2}, },
{V4L2_PIX_FMT_NV12M_P010, 2, {16, 8, 0}, },
{V4L2_PIX_FMT_NV24, 1, {24, 0, 0}, },
{V4L2_PIX_FMT_NV42, 1, {24, 0, 0}, },
};
CScalerM2M1SHOT::CScalerM2M1SHOT(int devid, int __UNUSED__ drm) : m_iFD(-1)
{
memset(&m_task, 0, sizeof(m_task));
if ((devid < 0) || (devid > 3)) { // instance number must be between 0 ~ 3
SC_LOGE("Invalid device instance ID %d", devid);
return;
}
m_iFD = open(dev_base_name[devid], O_RDWR);
if (m_iFD < 0) {
SC_LOGERR("Failed to open '%s'", dev_base_name[devid]);
} else {
// default 3 planes not to miss any buffer address
m_task.buf_out.num_planes = 3;
m_task.buf_cap.num_planes = 3;
}
}
CScalerM2M1SHOT::~CScalerM2M1SHOT()
{
if (m_iFD >= 0)
close(m_iFD);
}
bool CScalerM2M1SHOT::Run()
{
int ret;
if (LibScaler::UnderOne16thScaling(
m_task.fmt_out.crop.width, m_task.fmt_out.crop.height,
m_task.fmt_cap.crop.width, m_task.fmt_cap.crop.height,
m_task.op.rotate))
return RunSWScaling();
ret = ioctl(m_iFD, M2M1SHOT_IOC_PROCESS, &m_task);
if (ret < 0) {
SC_LOGERR("Failed to process the given M2M1SHOT task");
return false;
}
return true;
}
#define SCALER_EXT_SIZE 512
bool CScalerM2M1SHOT::SetFormat(m2m1shot_pix_format &fmt, m2m1shot_buffer &buf,
unsigned int width, unsigned int height, unsigned int v4l2_fmt) {
const PixFormat *pixfmt = NULL;
fmt.width = width;
fmt.height = height;
fmt.fmt = v4l2_fmt;
for (size_t i = 0; i < ARRSIZE(g_pixfmt_table); i++) {
if (g_pixfmt_table[i].pixfmt == v4l2_fmt) {
pixfmt = &g_pixfmt_table[i];
break;
}
}
if (!pixfmt) {
SC_LOGE("Format %#x is not supported", v4l2_fmt);
return false;
}
for (int i = 0; i < pixfmt->planes; i++) {
if (((pixfmt->bit_pp[i] * width) % 8) != 0) {
SC_LOGE("Plane %d of format %#x must have even width", i, v4l2_fmt);
return false;
}
buf.plane[i].len = (pixfmt->bit_pp[i] * width * height) / 8;
}
if (pixfmt->pixfmt == V4L2_PIX_FMT_YVU420) {
unsigned int y_size = width * height;
unsigned int c_span = ALIGN(width / 2, 16);
buf.plane[0].len = y_size + (c_span * height / 2) * 2;
}
#ifdef SCALER_ALIGN_RESTRICTION
for (int i = 0; i < pixfmt->planes; i++)
buf.plane[i].len += (i == 0) ? SCALER_EXT_SIZE : SCALER_EXT_SIZE / 2;
#endif
buf.num_planes = pixfmt->planes;
return true;
}
bool CScalerM2M1SHOT::SetCrop(m2m1shot_pix_format &fmt,
unsigned int l, unsigned int t, unsigned int w, unsigned int h) {
if (fmt.width <= l) {
SC_LOGE("crop left %d is larger than image width %d", l, fmt.width);
return false;
}
if (fmt.height <= t) {
SC_LOGE("crop top %d is larger than image height %d", t, fmt.height);
return false;
}
if (fmt.width < (l + w)) {
SC_LOGE("crop width %d@%d exceeds image width %d", w, l, fmt.width);
return false;
}
if (fmt.height < (t + h)) {
SC_LOGE("crop height %d@%d exceeds image height %d", h, t, fmt.height);
return false;
}
fmt.crop.left = l;
fmt.crop.top = t;
fmt.crop.width = w;
fmt.crop.height = h;
return true;
}
bool CScalerM2M1SHOT::SetAddr(
m2m1shot_buffer &buf, void *addr[SC_NUM_OF_PLANES], int mem_type) {
if (mem_type == V4L2_MEMORY_DMABUF) {
buf.type = M2M1SHOT_BUFFER_DMABUF;
for (int i = 0; i < buf.num_planes; i++)
buf.plane[i].fd = static_cast<__s32>(reinterpret_cast<long>(addr[i]));
} else if (mem_type == V4L2_MEMORY_USERPTR) {
buf.type = M2M1SHOT_BUFFER_USERPTR;
for (int i = 0; i < buf.num_planes; i++)
buf.plane[i].userptr = reinterpret_cast<unsigned long>(addr[i]);
} else {
SC_LOGE("Unknown buffer type %d", mem_type);
return false;
}
return true;
}
bool CScalerM2M1SHOT::SetRotate(int rot, int hflip, int vflip) {
if ((rot % 90) != 0) {
SC_LOGE("Rotation degree %d must be multiple of 90", rot);
return false;
}
rot = rot % 360;
if (rot < 0)
rot = 360 + rot;
m_task.op.rotate = rot;
m_task.op.op &= ~(M2M1SHOT_OP_FLIP_HORI | M2M1SHOT_OP_FLIP_VIRT);
if (hflip)
m_task.op.op |= M2M1SHOT_OP_FLIP_HORI;
if (vflip)
m_task.op.op |= M2M1SHOT_OP_FLIP_VIRT;
return true;
}
static bool GetBuffer(m2m1shot_buffer &buf, char *addr[])
{
for (int i = 0; i < buf.num_planes; i++) {
if (buf.type == M2M1SHOT_BUFFER_DMABUF) {
addr[i] = reinterpret_cast<char *>(mmap(NULL, buf.plane[i].len,
PROT_READ | PROT_WRITE, MAP_SHARED,
buf.plane[i].fd, 0));
if (addr[i] == MAP_FAILED) {
SC_LOGE("Failed to map FD %d", buf.plane[i].fd);
while (i-- > 0)
munmap(addr[i], buf.plane[i].len);
return false;
}
} else {
addr[i] = reinterpret_cast<char *>(buf.plane[i].userptr);
}
}
return true;
}
static void PutBuffer(m2m1shot_buffer &buf, char *addr[])
{
for (int i = 0; i < buf.num_planes; i++) {
if (buf.type == M2M1SHOT_BUFFER_DMABUF)
munmap(addr[i], buf.plane[i].len);
}
}
bool CScalerM2M1SHOT::RunSWScaling()
{
if (m_task.fmt_cap.fmt != m_task.fmt_out.fmt) {
SC_LOGE("Source and target image format must be the same");
return false;
}
if (m_task.op.rotate != 0) {
SC_LOGE("Rotation is not allowed for S/W Scaling");
return false;
}
SC_LOGI("Running S/W Scaler: %dx%d -> %dx%d",
m_task.fmt_out.crop.width, m_task.fmt_out.crop.height,
m_task.fmt_cap.crop.width, m_task.fmt_cap.crop.height);
CScalerSW *swsc;
char *src[3], *dst[3];
switch (m_task.fmt_cap.fmt) {
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YVYU:
if (!GetBuffer(m_task.buf_out, src))
return false;
if (!GetBuffer(m_task.buf_cap, dst)) {
PutBuffer(m_task.buf_out, src);
return false;
}
swsc = new CScalerSW_YUYV(src[0], dst[0]);
break;
case V4L2_PIX_FMT_NV12M:
case V4L2_PIX_FMT_NV21M:
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_NV21:
if (!GetBuffer(m_task.buf_out, src))
return false;
if (!GetBuffer(m_task.buf_cap, dst)) {
PutBuffer(m_task.buf_out, src);
return false;
}
if (m_task.buf_out.num_planes == 1)
src[1] = src[0] + m_task.fmt_out.width * m_task.fmt_out.height;
if (m_task.buf_cap.num_planes == 1)
dst[1] = dst[0] + m_task.fmt_cap.width * m_task.fmt_cap.height;
swsc = new CScalerSW_NV12(src[0], src[1], dst[0], dst[1]);
break;
case V4L2_PIX_FMT_UYVY: // TODO: UYVY is not implemented yet.
default:
SC_LOGE("Format %x is not supported", m_task.fmt_out.fmt);
return false;
}
if (swsc == NULL) {
SC_LOGE("Failed to allocate SW Scaler");
PutBuffer(m_task.buf_out, src);
PutBuffer(m_task.buf_cap, dst);
return false;
}
swsc->SetSrcRect(m_task.fmt_out.crop.left, m_task.fmt_out.crop.top,
m_task.fmt_out.crop.width, m_task.fmt_out.crop.height,
m_task.fmt_out.width);
swsc->SetDstRect(m_task.fmt_cap.crop.left, m_task.fmt_cap.crop.top,
m_task.fmt_cap.crop.width, m_task.fmt_cap.crop.height,
m_task.fmt_cap.width);
bool ret = swsc->Scale();
delete swsc;
PutBuffer(m_task.buf_out, src);
PutBuffer(m_task.buf_cap, dst);
return ret;
}