/* * 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-v4l2.cpp * \brief source file for Scaler HAL * \author Cho KyongHo * \date 2014/05/12 * * Revision History: * - 2014.05.12 : Cho KyongHo (pullip.cho@samsung.com) \n * Create */ #include #include #include #include #include #include #include "libscaler-v4l2.h" #include "libscaler-swscaler.h" #define V4L2_CID_EXYNOS_BASE (V4L2_CTRL_CLASS_USER | 0x2000) #define V4L2_CID_CSC_EQ_MODE (V4L2_CID_EXYNOS_BASE + 100) #define V4L2_CID_CSC_EQ (V4L2_CID_EXYNOS_BASE + 101) #define V4L2_CID_CSC_RANGE (V4L2_CID_EXYNOS_BASE + 102) #define V4L2_CID_CONTENT_PROTECTION (V4L2_CID_EXYNOS_BASE + 201) void CScalerV4L2::Initialize(int instance) { snprintf(m_cszNode, SC_MAX_NODENAME, SC_DEV_NODE "%d", SC_NODE(instance)); m_fdScaler = open(m_cszNode, O_RDWR); if (m_fdScaler < 0) { SC_LOGERR("Failed to open '%s'", m_cszNode); return; } m_fdValidate = -m_fdScaler; } CScalerV4L2::CScalerV4L2(int instance, int allow_drm) { m_fdScaler = -1; m_iInstance = instance; m_nRotDegree = 0; m_fStatus = 0; m_filter = 0; memset(&m_frmSrc, 0, sizeof(m_frmSrc)); memset(&m_frmDst, 0, sizeof(m_frmDst)); m_frmSrc.fdAcquireFence = -1; m_frmDst.fdAcquireFence = -1; m_frmSrc.name = "output"; m_frmDst.name = "capture"; m_frmSrc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; m_frmDst.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; m_frameRate = 0; Initialize(instance); if(Valid()) { if (allow_drm) SetFlag(m_fStatus, SCF_ALLOW_DRM); SC_LOGD("Successfully opened '%s'; returned fd %d; drmmode %s", m_cszNode, m_fdScaler, allow_drm ? "enabled" : "disabled"); } } CScalerV4L2::~CScalerV4L2() { if (m_fdScaler >= 0) close(m_fdScaler); m_fdScaler = -1; } bool CScalerV4L2::Stop() { if (!ResetDevice(m_frmSrc)) { SC_LOGE("Failed to stop Scaler for the output frame"); return false; } if (!ResetDevice(m_frmDst)) { SC_LOGE("Failed to stop Scaler for the cature frame"); return false; } return true; } bool CScalerV4L2::Run() { if (LibScaler::UnderOne16thScaling( m_frmSrc.crop.width, m_frmSrc.crop.height, m_frmDst.crop.width, m_frmDst.crop.height, m_nRotDegree)) return RunSWScaling(); if (!DevSetCtrl()) return false; if (!DevSetFormat()) return false; if (!ReqBufs()) return false; if (!StreamOn()) return false; if (!QBuf()) { Stop(); return false; } return DQBuf(); } bool CScalerV4L2::SetCtrl() { struct v4l2_control ctrl; if (TestFlag(m_fStatus, SCF_DRM_FRESH)) { if (!Stop()) return false; ctrl.id = V4L2_CID_CONTENT_PROTECTION; ctrl.value = TestFlag(m_fStatus, SCF_DRM); if (ioctl(m_fdScaler, VIDIOC_S_CTRL, &ctrl) < 0) { SC_LOGERR("Failed configure V4L2_CID_CONTENT_PROTECTION to %d", TestFlag(m_fStatus, SCF_DRM)); return false; } ClearFlag(m_fStatus, SCF_DRM_FRESH); } else { SC_LOGD("Skipping DRM configuration"); } if (TestFlag(m_fStatus, SCF_ROTATION_FRESH)) { if (!Stop()) return false; ctrl.id = V4L2_CID_ROTATE; ctrl.value = m_nRotDegree; if (ioctl(m_fdScaler, VIDIOC_S_CTRL, &ctrl) < 0) { SC_LOGERR("Failed V4L2_CID_ROTATE with degree %d", m_nRotDegree); return false; } ctrl.id = V4L2_CID_VFLIP; ctrl.value = TestFlag(m_fStatus, SCF_HFLIP); if (ioctl(m_fdScaler, VIDIOC_S_CTRL, &ctrl) < 0) { SC_LOGERR("Failed V4L2_CID_VFLIP - %d", TestFlag(m_fStatus, SCF_VFLIP)); return false; } ctrl.id = V4L2_CID_HFLIP; ctrl.value = TestFlag(m_fStatus, SCF_VFLIP); if (ioctl(m_fdScaler, VIDIOC_S_CTRL, &ctrl) < 0) { SC_LOGERR("Failed V4L2_CID_HFLIP - %d", TestFlag(m_fStatus, SCF_HFLIP)); return false; } SC_LOGD("Successfully set CID_ROTATE(%d), CID_VFLIP(%d) and CID_HFLIP(%d)", m_nRotDegree, TestFlag(m_fStatus, SCF_VFLIP), TestFlag(m_fStatus, SCF_HFLIP)); ClearFlag(m_fStatus, SCF_ROTATION_FRESH); } else { SC_LOGD("Skipping rotation and flip setting due to no change"); } if (m_filter > 0) { if (!Stop()) return false; ctrl.id = LIBSC_V4L2_CID_DNOISE_FT; ctrl.value = m_filter; if (ioctl(m_fdScaler, VIDIOC_S_CTRL, &ctrl) < 0) { SC_LOGERR("Failed LIBSC_V4L2_CID_DNOISE_FT to %d", m_filter); return false; } } if (TestFlag(m_fStatus, SCF_CSC_FRESH)) { if (!Stop()) return false; ctrl.id = V4L2_CID_CSC_RANGE; ctrl.value = TestFlag(m_fStatus, SCF_CSC_WIDE) ? 1 : 0; if (ioctl(m_fdScaler, VIDIOC_S_CTRL, &ctrl) < 0) { SC_LOGERR("Failed V4L2_CID_CSC_RANGE to %d", TestFlag(m_fStatus, SCF_CSC_WIDE)); return false; } ctrl.id = V4L2_CID_CSC_EQ; ctrl.value = m_colorspace; if (ioctl(m_fdScaler, VIDIOC_S_CTRL, &ctrl) < 0) { SC_LOGERR("Failed V4L2_CID_CSC_EQ to %d", m_colorspace); } ClearFlag(m_fStatus, SCF_CSC_FRESH); } /* This is optional, so we don't return failure. */ if (TestFlag(m_fStatus, SCF_FRAMERATE)) { if (!Stop()) return false; ctrl.id = SC_CID_FRAMERATE; ctrl.value = m_frameRate; if (ioctl(m_fdScaler, VIDIOC_S_CTRL, &ctrl) < 0) { SC_LOGD("Failed SC_CID_FRAMERATE to %d", m_frameRate); } ClearFlag(m_fStatus, SCF_FRAMERATE); } return true; } bool CScalerV4L2::DevSetCtrl() { return SetCtrl(); } bool CScalerV4L2::ResetDevice(FrameInfo &frm) { DQBuf(frm); if (TestFlag(frm.flags, SCFF_STREAMING)) { if (ioctl(m_fdScaler, VIDIOC_STREAMOFF, &frm.type) < 0) { SC_LOGERR("Failed STREAMOFF for the %s", frm.name); } ClearFlag(frm.flags, SCFF_STREAMING); } SC_LOGD("VIDIC_STREAMOFF is successful for the %s", frm.name); if (TestFlag(frm.flags, SCFF_REQBUFS)) { v4l2_requestbuffers reqbufs; memset(&reqbufs, 0, sizeof(reqbufs)); reqbufs.type = frm.type; reqbufs.memory = frm.memory; if (ioctl(m_fdScaler, VIDIOC_REQBUFS, &reqbufs) < 0 ) { SC_LOGERR("Failed to REQBUFS(0) for the %s", frm.name); } ClearFlag(frm.flags, SCFF_REQBUFS); } SC_LOGD("VIDIC_REQBUFS(0) is successful for the %s", frm.name); return true; } bool CScalerV4L2::DevSetFormat(FrameInfo &frm) { if (!TestFlag(frm.flags, SCFF_BUF_FRESH)) { SC_LOGD("Skipping S_FMT for the %s since it is already done", frm.name); return true; } if (!ResetDevice(frm)) { SC_LOGE("Failed to VIDIOC_S_FMT for the %s", frm.name); return false; } v4l2_format fmt; memset(&fmt, 0, sizeof(fmt)); fmt.type = frm.type; fmt.fmt.pix_mp.pixelformat = frm.color_format; fmt.fmt.pix_mp.width = frm.width; fmt.fmt.pix_mp.height = frm.height; if (TestFlag(frm.flags, SCFF_PREMULTIPLIED)) { #ifdef SCALER_USE_PREMUL_FMT fmt.fmt.pix_mp.flags = V4L2_PIX_FMT_FLAG_PREMUL_ALPHA; #else fmt.fmt.pix_mp.reserved[1] = SC_V4L2_FMT_PREMULTI_FLAG; #endif } if (ioctl(m_fdScaler, VIDIOC_S_FMT, &fmt) < 0) { SC_LOGERR("Failed S_FMT(fmt: %d, w:%d, h:%d) for the %s", fmt.fmt.pix_mp.pixelformat, fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height, frm.name); return false; } // returned fmt.fmt.pix_mp.num_planes and fmt.fmt.pix_mp.plane_fmt[i].sizeimage frm.out_num_planes = fmt.fmt.pix_mp.num_planes; for (int i = 0; i < frm.out_num_planes; i++) frm.out_plane_size[i] = fmt.fmt.pix_mp.plane_fmt[i].sizeimage; v4l2_crop crop; crop.type = frm.type; crop.c = frm.crop; if (ioctl(m_fdScaler, VIDIOC_S_CROP, &crop) < 0) { SC_LOGERR("Failed S_CROP(fmt: %d, l:%d, t:%d, w:%d, h:%d) for the %s", crop.type, crop.c.left, crop.c.top, crop.c.width, crop.c.height, frm.name); return false; } if (frm.out_num_planes > SC_MAX_PLANES) { SC_LOGE("Number of planes exceeds %d of %s", frm.out_num_planes, frm.name); return false; } ClearFlag(frm.flags, SCFF_BUF_FRESH); SC_LOGD("Successfully S_FMT and S_CROP for the %s", frm.name); return true; } bool CScalerV4L2::DevSetFormat() { if (!DevSetFormat(m_frmSrc)) return false; return DevSetFormat(m_frmDst); } bool CScalerV4L2::QBuf(FrameInfo &frm, int *pfdReleaseFence) { v4l2_buffer buffer; v4l2_plane planes[SC_MAX_PLANES]; if (!TestFlag(frm.flags, SCFF_REQBUFS)) { SC_LOGE("Trying to QBUF without REQBUFS for %s is not allowed", frm.name); return false; } if (!DQBuf(frm)) return false; memset(&buffer, 0, sizeof(buffer)); memset(&planes, 0, sizeof(planes)); buffer.type = frm.type; buffer.memory = frm.memory; buffer.index = 0; buffer.length = frm.out_num_planes; if (pfdReleaseFence) { buffer.flags = V4L2_BUF_FLAG_USE_SYNC; buffer.reserved = frm.fdAcquireFence; } buffer.m.planes = planes; for (unsigned long i = 0; i < buffer.length; i++) { planes[i].length = frm.out_plane_size[i]; if (V4L2_TYPE_IS_OUTPUT(buffer.type)) planes[i].bytesused = planes[i].length; if (buffer.memory == V4L2_MEMORY_DMABUF) planes[i].m.fd = static_cast<__s32>(reinterpret_cast(frm.addr[i])); else planes[i].m.userptr = reinterpret_cast(frm.addr[i]); } if (ioctl(m_fdScaler, VIDIOC_QBUF, &buffer) < 0) { SC_LOGERR("Failed to QBUF for the %s", frm.name); return false; } SetFlag(frm.flags, SCFF_QBUF); if (pfdReleaseFence) { if (frm.fdAcquireFence >= 0) close(frm.fdAcquireFence); frm.fdAcquireFence = -1; *pfdReleaseFence = static_cast(buffer.reserved); } SC_LOGD("Successfully QBUF for the %s", frm.name); return true; } bool CScalerV4L2::ReqBufs(FrameInfo &frm) { v4l2_requestbuffers reqbufs; if (TestFlag(frm.flags, SCFF_REQBUFS)) { SC_LOGD("Skipping REQBUFS for the %s since it is already done", frm.name); return true; } memset(&reqbufs, 0, sizeof(reqbufs)); reqbufs.type = frm.type; reqbufs.memory = frm.memory; reqbufs.count = 1; if (ioctl(m_fdScaler, VIDIOC_REQBUFS, &reqbufs) < 0) { SC_LOGERR("Failed to REQBUFS for the %s", frm.name); return false; } SetFlag(frm.flags, SCFF_REQBUFS); SC_LOGD("Successfully REQBUFS for the %s", frm.name); return true; } bool CScalerV4L2::SetRotate(int rot, int flip_h, int flip_v) { if ((rot % 90) != 0) { SC_LOGE("Rotation of %d degree is not supported", rot); return false; } SetRotDegree(rot); if (flip_h) SetFlag(m_fStatus, SCF_VFLIP); else ClearFlag(m_fStatus, SCF_VFLIP); if (flip_v) SetFlag(m_fStatus, SCF_HFLIP); else ClearFlag(m_fStatus, SCF_HFLIP); SetFlag(m_fStatus, SCF_ROTATION_FRESH); return true; } bool CScalerV4L2::StreamOn(FrameInfo &frm) { if (!TestFlag(frm.flags, SCFF_REQBUFS)) { SC_LOGE("Trying to STREAMON without REQBUFS for %s is not allowed", frm.name); return false; } if (!TestFlag(frm.flags, SCFF_STREAMING)) { if (ioctl(m_fdScaler, VIDIOC_STREAMON, &frm.type) < 0 ) { SC_LOGERR("Failed StreamOn for the %s", frm.name); return false; } SetFlag(frm.flags, SCFF_STREAMING); SC_LOGD("Successfully VIDIOC_STREAMON for the %s", frm.name); } return true; } bool CScalerV4L2::DQBuf(FrameInfo &frm) { if (!TestFlag(frm.flags, SCFF_QBUF)) return true; v4l2_buffer buffer; v4l2_plane plane[SC_NUM_OF_PLANES]; memset(&buffer, 0, sizeof(buffer)); buffer.type = frm.type; buffer.memory = frm.memory; if (V4L2_TYPE_IS_MULTIPLANAR(buffer.type)) { memset(plane, 0, sizeof(plane)); buffer.length = frm.out_num_planes; buffer.m.planes = plane; } ClearFlag(frm.flags, SCFF_QBUF); if (ioctl(m_fdScaler, VIDIOC_DQBUF, &buffer) < 0 ) { SC_LOGERR("Failed to DQBuf the %s", frm.name); return false; } if (buffer.flags & V4L2_BUF_FLAG_ERROR) { SC_LOGE("Error occurred while processing streaming data"); return false; } SC_LOGD("Successfully VIDIOC_DQBUF for the %s", frm.name); return true; } static bool GetBuffer(CScalerV4L2::FrameInfo &frm, char *addr[]) { for (int i = 0; i < frm.out_num_planes; i++) { if (frm.memory == V4L2_MEMORY_DMABUF) { addr[i] = reinterpret_cast(mmap(NULL, frm.out_plane_size[i], PROT_READ | PROT_WRITE, MAP_SHARED, static_cast(reinterpret_cast(frm.addr[i])), 0)); if (addr[i] == MAP_FAILED) { SC_LOGE("Failed to map FD %ld", reinterpret_cast(frm.addr[i])); while (i-- > 0) munmap(addr[i], frm.out_plane_size[i]); return false; } } else { addr[i] = reinterpret_cast(frm.addr[i]); } } return true; } static void PutBuffer(CScalerV4L2::FrameInfo &frm, char *addr[]) { for (int i = 0; i < frm.out_num_planes; i++) { if (frm.memory == V4L2_MEMORY_DMABUF) { munmap(addr[i], frm.out_plane_size[i]); } } } bool CScalerV4L2::RunSWScaling() { if (m_frmSrc.color_format != m_frmDst.color_format) { SC_LOGE("Source and target image format must be the same"); return false; } if (m_nRotDegree != 0) { SC_LOGE("Rotation is not allowed for S/W Scaling"); return false; } SC_LOGI("Running S/W Scaler: %dx%d -> %dx%d", m_frmSrc.crop.width, m_frmSrc.crop.height, m_frmDst.crop.width, m_frmDst.crop.height); CScalerSW *swsc; char *src[3], *dst[3]; switch (m_frmSrc.color_format) { case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YVYU: m_frmSrc.out_num_planes = 1; m_frmSrc.out_plane_size[0] = m_frmSrc.width * m_frmSrc.height * 2; m_frmDst.out_num_planes = 1; m_frmDst.out_plane_size[0] = m_frmDst.width * m_frmDst.height * 2; if (!GetBuffer(m_frmSrc, src)) return false; if (!GetBuffer(m_frmDst, dst)) { PutBuffer(m_frmSrc, src); return false; } swsc = new CScalerSW_YUYV(src[0], dst[0]); break; case V4L2_PIX_FMT_NV12M: case V4L2_PIX_FMT_NV21M: m_frmSrc.out_num_planes = 2; m_frmDst.out_num_planes = 2; m_frmSrc.out_plane_size[0] = m_frmSrc.width * m_frmSrc.height; m_frmDst.out_plane_size[0] = m_frmDst.width * m_frmDst.height; m_frmSrc.out_plane_size[1] = m_frmSrc.out_plane_size[0] / 2; m_frmDst.out_plane_size[1] = m_frmDst.out_plane_size[0] / 2; if (!GetBuffer(m_frmSrc, src)) return false; if (!GetBuffer(m_frmDst, dst)) { PutBuffer(m_frmSrc, src); return false; } swsc = new CScalerSW_NV12(src[0], src[1], dst[0], dst[1]); break; case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21: m_frmSrc.out_num_planes = 1; m_frmDst.out_num_planes = 1; m_frmSrc.out_plane_size[0] = m_frmSrc.width * m_frmSrc.height; m_frmDst.out_plane_size[0] = m_frmDst.width * m_frmDst.height; m_frmSrc.out_plane_size[0] += m_frmSrc.out_plane_size[0] / 2; m_frmDst.out_plane_size[0] += m_frmDst.out_plane_size[0] / 2; if (!GetBuffer(m_frmSrc, src)) return false; if (!GetBuffer(m_frmDst, dst)) { PutBuffer(m_frmSrc, src); return false; } src[1] = src[0] + m_frmSrc.width * m_frmSrc.height; dst[1] = dst[0] + m_frmDst.width * m_frmDst.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_frmSrc.color_format); return false; } if (swsc == NULL) { SC_LOGE("Failed to allocate SW Scaler"); PutBuffer(m_frmSrc, src); PutBuffer(m_frmDst, dst); return false; } swsc->SetSrcRect(m_frmSrc.crop.left, m_frmSrc.crop.top, m_frmSrc.crop.width, m_frmSrc.crop.height, m_frmSrc.width); swsc->SetDstRect(m_frmDst.crop.left, m_frmDst.crop.top, m_frmDst.crop.width, m_frmDst.crop.height, m_frmDst.width); bool ret = swsc->Scale(); delete swsc; PutBuffer(m_frmSrc, src); PutBuffer(m_frmDst, dst); return ret; }