blob: 05d17cbb5be5da306f857f97e1badd9ab3b40551 [file] [log] [blame]
/*
* Copyright (C) 2017-2018 Intel Corporation
*
* 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.
*/
#define LOG_TAG "GraphConfig"
#include "GraphConfig.h"
#include "GraphConfigManager.h"
#include "LogHelper.h"
#include "FormatUtils.h"
#include "NodeTypes.h"
#include "Camera3GFXFormat.h"
#include "PlatformData.h"
#include <GCSSParser.h>
#include <cros-camera/v4l2_device.h>
#include <linux/v4l2-subdev.h>
#include <linux/intel-ipu3.h>
#include <algorithm>
#include "MediaEntity.h"
using GCSS::GraphConfigNode;
using std::string;
using std::vector;
using std::map;
using std::set;
namespace cros {
namespace intel {
extern int32_t gDumpType;
namespace gcu = graphconfig::utils;
// TODO: Change the format attribute natively as integer attribute
#ifndef VIDEO_RECORDING_FORMAT
#define VIDEO_RECORDING_FORMAT TILE
#endif
#define MEDIACTL_PAD_OUTPUT_NUM 2
#define MEDIACTL_PAD_VF_NUM 3
#define SCALING_FACTOR 1
const string csi2WithoutPort = "ipu3-csi2 ";
const string imguWithoutPort = "ipu3-imgu ";
const string MEDIACTL_INPUTNAME = " input";
const string MEDIACTL_PARAMETERNAME = " parameters";
const string MEDIACTL_MAIN_NAME = " output";
const string MEDIACTL_VF_NAME = " viewfinder";
const string MEDIACTL_STATNAME = " 3a stat";
GraphConfig::GraphConfig() :
mSettings(nullptr),
mReqId(0),
mPipeType(PIPE_VIDEO),
mSourceType(SRC_NONE)
{
mSinkPeerPort.clear();
mStreamToSinkIdMap.clear();
mStream2TuningMap.clear();
mCSIBE = CSI_BE + "0";
}
GraphConfig::~GraphConfig()
{
fullReset();
}
/*
* Full reset
* This is called whenever we want to reset the whole object. Currently that
* is only, when GraphConfig object is destroyed.
*/
void GraphConfig::fullReset()
{
mSinkPeerPort.clear();
mStreamToSinkIdMap.clear();
mReqId = 0;
mStream2TuningMap.clear();
}
/*
* Reset
* This is called per frame
*/
void GraphConfig::reset(GraphConfig *me)
{
if (CC_LIKELY(me != nullptr)) {
me->mReqId = 0;
} else {
LOGE("Trying to reset a null GraphConfig - BUG!");
}
}
const GCSS::IGraphConfig* GraphConfig::getInterface(Node *node) const
{
if (!node)
return nullptr;
return node;
}
const GCSS::IGraphConfig* GraphConfig::getInterface() const
{
return mSettings;
}
/**
* Per frame initialization of graph config.
* Updates request id
* \param[in] reqId
*/
void GraphConfig::init(int32_t reqId)
{
mReqId = reqId;
}
/**
* Prepare graph config once per stream config.
* \param[in] manager
* \param[in] settings
* \param[in] streamToSinkIdMap
* \param[in] active
*/
status_t GraphConfig::prepare(Node *settings,
StreamToSinkMap &streamToSinkIdMap)
{
if (CC_UNLIKELY(settings == nullptr)) {
LOGW("Settings is nullptr!! - BUG?");
return UNKNOWN_ERROR;
}
mSettings = settings;
status_t ret = OK;
analyzeSourceType();
ret = getActiveOutputPorts(streamToSinkIdMap);
if (ret != OK) {
LOGE("Failed to get output ports");
return ret;
}
calculateSinkDependencies();
storeTuningModes();
return ret;
}
/**
* Store the tuning modes for each stream id into a map that can be used on a
* per frame basis.
* This method is executed once per stream configuration.
* The tuning mode is used by AIC to find the correct tuning tables in CPF.
*
*/
void GraphConfig::storeTuningModes()
{
GraphConfigNode::const_iterator it = mSettings->begin();
css_err_t ret = css_err_none;
GraphConfigNode *result = nullptr;
int32_t tuningMode = 0;
int32_t streamId = 0;
mStream2TuningMap.clear();
while (it != mSettings->end()) {
ret = mSettings->getDescendant(GCSS_KEY_TYPE, "program_group", it, &result);
if (ret == css_err_none) {
ret = result->getValue(GCSS_KEY_STREAM_ID, streamId);
if (ret != css_err_none) {
string pgName;
// This should not fail
ret = result->getValue(GCSS_KEY_NAME, pgName);
LOGW("Failed to find stream id for PG %s", pgName.c_str());
continue;
}
tuningMode = 0; // default value in case it is not found
ret = result->getValue(GCSS_KEY_TUNING_MODE, tuningMode);
if (ret != css_err_none) {
string pgName;
// This should not fail
ret = result->getValue(GCSS_KEY_NAME, pgName);
LOGW("Failed t find tuning mode for PG %s, defaulting to %d",
pgName.c_str(), tuningMode);
}
mStream2TuningMap[streamId] = tuningMode;
}
}
}
/*
* According to the node, analyze the source type:
* TPG or sensor
*/
status_t GraphConfig::analyzeSourceType()
{
css_err_t ret = css_err_none;
Node *inputDevNode = nullptr;
ret = mSettings->getDescendant(GCSS_KEY_SENSOR, &inputDevNode);
if (ret == css_err_none) {
mSourceType = SRC_SENSOR;
} else {
LOG1("No sensor node from the graph");
}
return OK;
}
/**
* Finds the sink nodes and the output port peer. Use streamToSinkIdMap
* since we are interested only in sinks that serve a stream. Takes an
* internal copy of streamToSinkIdMap to be used later.
*
* \param[in] streamToSinkIdMap to get the virtual sink id from a client stream pointer
* \return OK in case of success.
* \return UNKNOWN_ERROR or BAD_VALUE in case of fail.
*/
status_t GraphConfig::getActiveOutputPorts(const StreamToSinkMap &streamToSinkIdMap)
{
status_t status = OK;
css_err_t ret = css_err_none;
NodesPtrVector sinks;
mStreamToSinkIdMap.clear();
mStreamToSinkIdMap = streamToSinkIdMap;
mSinkPeerPort.clear();
StreamToSinkMap::const_iterator it;
it = streamToSinkIdMap.begin();
for (; it != streamToSinkIdMap.end(); ++it) {
sinks.clear();
status = graphGetSinksByName(GCSS::ItemUID::key2str(it->second), sinks);
if (CC_UNLIKELY(status != OK) || sinks.empty()) {
string sinkName = GCSS::ItemUID::key2str(it->second);
LOGE("Found %zu sinks, expecting 1 for sink %s", sinks.size(),
sinkName.c_str());
return BAD_VALUE;
}
Node *sink = sinks[0];
Node *outputPort = nullptr;
// Get the sinkname for getting the output port
string sinkName;
ret = sink->getValue(GCSS_KEY_NAME, sinkName);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get sink name");
return BAD_VALUE;
}
LOG2("sink name %s", sinkName.c_str());
int32_t streamId = -1;
ret = sink->getValue(GCSS_KEY_STREAM_ID, streamId);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get stream id");
return BAD_VALUE;
}
LOG2("stream id %d", streamId);
outputPort = getOutputPortForSink(sinkName);
if (outputPort == nullptr) {
LOGE("No output port found for sink");
return UNKNOWN_ERROR;
}
LOG2("output port name %s", NODE_NAME(outputPort));
mSinkPeerPort[sink] = outputPort;
}
return OK;
}
string GraphConfig::getNodeName(Node * node)
{
string nodeName("");
if (node == nullptr) {
LOGE("Node is nullptr");
return nodeName;
}
node->getValue(GCSS_KEY_NAME, nodeName);
return nodeName;
}
/**
* Finds the output port which is the peer to the sink node.
*
* Gets root node, and finds the sink with the given name. Use portGetPeer()
* to find the output port.
* \return GraphConfigNode in case of success.
* \return nullptr in case of fail.
*/
GraphConfig::Node *GraphConfig::getOutputPortForSink(const string &sinkName)
{
css_err_t ret = css_err_none;
status_t retErr = OK;
Node *rootNode = nullptr;
Node *portNode = nullptr;
Node *peerNode = nullptr;
rootNode = mSettings->getRootNode();
if (rootNode == nullptr) {
LOGE("Couldn't get root node, BUG!");
return nullptr;
}
ret = rootNode->getDescendantByString(sinkName, &portNode);
if (ret != css_err_none) {
LOGE("Error getting sink");
return nullptr;
}
retErr = portGetPeer(portNode, &peerNode);
if (retErr != OK) {
LOGE("Error getting peer");
return nullptr;
}
return portNode;
}
/**
* Retrieve all the sinks in the current graph configuration that match the
* input parameter string in their name attribute.
*
* If the name to match is empty it returns all the nodes of type sink
*
* \param[in] name String containing the name to match.
* \param[out] sink List of sinks that match that name
* \return OK in case of success
* \return UNKNOWN_ERROR if no sinks were found in the graph config.
*
*/
status_t GraphConfig::graphGetSinksByName(const std::string &name,
NodesPtrVector &sinks)
{
css_err_t ret = css_err_none;
GraphConfigNode *result;
NodesPtrVector allSinks;
string foundName;
GraphConfigNode::const_iterator it = mSettings->begin();
while (it != mSettings->end()) {
ret = mSettings->getDescendant(GCSS_KEY_TYPE, "sink", it, &result);
if (ret == css_err_none) {
allSinks.push_back(result);
}
}
if (allSinks.empty()) {
LOGE("Failed to find any sinks -check graph config file");
return UNKNOWN_ERROR;
}
/*
* if the name is empty it means client wants all sinks.
*/
if (name.empty()) {
sinks = allSinks;
return OK;
}
for (size_t i = 0; i < allSinks.size(); i++) {
allSinks[i]->getValue(GCSS_KEY_NAME, foundName);
if (foundName.find(name) != string::npos) {
sinks.push_back(allSinks[i]);
}
}
return OK;
}
/*
* Imgu helper functions
*/
status_t GraphConfig::graphGetDimensionsByName(const std::string &name,
int &widht, int &height)
{
widht = 0;
height = 0;
Node *csiBEOutput = nullptr;
// Get csi_be node. If not found, try csi_be_soc. If not found return error.
int ret = mSettings->getDescendantByString(name, &csiBEOutput);
if (ret != css_err_none) {
LOGE("Error: Couldn't find node: %s", name.c_str());
return UNKNOWN_ERROR;
}
ret = getDimensions(csiBEOutput, widht, height);
if (ret != OK) {
LOGE("Error: Couldn't find dimensions from <%s>", name.c_str());
return UNKNOWN_ERROR;
}
return OK;
}
/*
* Imgu helper functions
*/
status_t GraphConfig::graphGetDimensionsByName(const std::string &name,
unsigned short &widht, unsigned short &height)
{
int w, h;
int ret = graphGetDimensionsByName(name, w, h);
widht = w;
height = h;
return ret;
}
/**
* This method creates SinkDependency structure for every active sink found in
* the graph. These structs allow quick access to information that is required
* by other methods.
* Active sinks are the ones that have a connection to an active port.
* This list of active sinks(mSinkPeerPort) has to be filled before this method
* is executed.
* For every virtual sink we store the name (as a key) and the terminal id of
* the input port of the stream associated with that stream. This input port
* will be the destination of the buffers from the capture unit.
*
* This method is used during init()
* If we would have different settings per frame then this would be enough
* to detect the active ISA nodes, but we are not there yet. we are still using
* the base graph settings every frame.
*/
void GraphConfig::calculateSinkDependencies()
{
status_t status = OK;
Node *streamInputPort = nullptr;
NodesPtrVector sinks;
std::string sinkName;
SinkDependency aSinkDependency;
uint32_t stageId; //not needed
map<Node*, Node*>::iterator sinkIter = mSinkPeerPort.begin();
for (; sinkIter != mSinkPeerPort.end(); sinkIter++) {
sinkIter->first->getValue(GCSS_KEY_NAME, sinkName);
aSinkDependency.sinkGCKey = GCSS::ItemUID::str2key(sinkName);
aSinkDependency.streamId = sinkGetStreamId(sinkIter->first);
status = streamGetInputPort(aSinkDependency.streamId,
&streamInputPort);
if (CC_UNLIKELY(status != OK)) {
LOGE("Failed to get input port for stream %d associated to sink %s",
aSinkDependency.streamId, sinkName.c_str());
continue;
}
status = portGetFourCCInfo(*streamInputPort,
stageId,
aSinkDependency.streamInputPortId);
if (CC_UNLIKELY(status != OK)) {
LOGE("Failed to get stream %d input port 4CC code",
aSinkDependency.streamId);
continue;
}
LOG2("Adding dependency %s stream id %d", sinkName.c_str(), aSinkDependency.streamId);
// get the output port of capture unit
Node* isaOutPutPort = nullptr;
status = portGetPeer(streamInputPort, &isaOutPutPort);
if (CC_UNLIKELY(status != OK)) {
LOGE("Fail to get isa output port for sink %s", sinkName.c_str());
continue;
}
std::string fullName;
status = portGetFullName(isaOutPutPort, fullName);
if (CC_UNLIKELY(status != OK)) {
LOGE("Fail to get isa output port name");
continue;
}
}
}
/**
* Find distinct stream ids from the graph and return them in a vector.
* \param streamIds Vector to be populated with stream ids.
*/
status_t GraphConfig::graphGetStreamIds(StreamsVector &streamIds)
{
GraphConfigNode *result;
int32_t streamId = -1;
css_err_t ret;
GraphConfigNode::const_iterator it = mSettings->begin();
while (it != mSettings->end()) {
bool found = false;
// Find all program groups
ret = mSettings->getDescendant(GCSS_KEY_TYPE, "hw",
it, &result);
if (ret != css_err_none)
continue;
ret = result->getValue(GCSS_KEY_STREAM_ID, streamId);
if (ret != css_err_none)
continue;
// If stream id is not yet in vector, add it
StreamsVector::iterator ite = streamIds.begin();
for(; ite !=streamIds.end(); ++ite) {
if (streamId == *ite) {
found = true;
break;
}
}
if (found)
continue;
streamIds.push_back(streamId);
}
if (streamIds.empty()) {
LOGE("Failed to find any streamIds %d)", streamId);
return UNKNOWN_ERROR;
}
return OK;
}
/**
* Retrieve the stream id associated with a given sink.
* The stream id represents the branch of the PSYS processing nodes that
* precedes this sink.
* This id is used for IQ tunning purposes.
*
* \param[in] sink sink node to query
* \return stream id or -1 in case of error
*/
int32_t GraphConfig::sinkGetStreamId(Node *sink)
{
css_err_t ret = css_err_none;
int32_t streamId = -1;
string type;
if (CC_UNLIKELY(sink == nullptr)) {
LOGE("Invalid Node, cannot get the sink stream id");
return -1;
}
ret = sink->getValue(GCSS_KEY_TYPE, type);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get Node Type");
return -1;
}
if (CC_UNLIKELY(type != "sink")) {
LOGE("Node is not a sink");
return -1;
}
ret = sink->getValue(GCSS_KEY_STREAM_ID, streamId);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get stream ID");
return -1;
}
return streamId;
}
status_t GraphConfig::streamGetInputPort(int32_t streamId, Node **port)
{
css_err_t ret = css_err_none;
Node *result = nullptr;
Node *pgNode = nullptr;
int32_t streamIdFound = -1;
*port = nullptr;
Node::const_iterator it = mSettings->begin();
while (it != mSettings->end()) {
ret = mSettings->getDescendant(GCSS_KEY_TYPE, "hw",
it, &pgNode);
if (ret != css_err_none)
continue;
ret = pgNode->getValue(GCSS_KEY_STREAM_ID, streamIdFound);
if ((ret == css_err_none) && (streamIdFound == streamId)) {
Node::const_iterator it = pgNode->begin();
while (it != pgNode->end()) {
ret = pgNode->getDescendant(GCSS_KEY_TYPE, "port",
it, &result);
if (ret != css_err_none)
continue;
int32_t direction = portGetDirection(result);
if (direction == PORT_DIRECTION_INPUT) {
/*
* TODO: add check that the port is at the edge of the stream
* this could be done checking that the peer's port is in a PG
* that belongs to a different stream id or to a virtual sink
*
*/
*port = result;
return OK;
}
}
}
}
return (*port == nullptr) ? BAD_VALUE : OK;
}
/**
* Retrieve the graph config node of the port that is connected to a given port.
*
* \param[in] port Node with the info of the port that we want to find its peer.
* \param[out] peer Pointer to a node where the peer node reference will be
* stored
* \return OK
* \return INVALID_OPERATION if the port is disabled.
* \return BAD_VALUE if any of the graph settings is incorrect.
*/
status_t GraphConfig::portGetPeer(Node *port, Node **peer)
{
css_err_t ret = css_err_none;
int32_t enabled = 1;
string peerName;
if (CC_UNLIKELY(port == nullptr || peer == nullptr)) {
LOGE("Invalid Node, cannot get the peer port");
return BAD_VALUE;
}
ret = port->getValue(GCSS_KEY_ENABLED, enabled);
if (ret == css_err_none && !enabled) {
LOG1("This port is disabled, keep on getting the connection");
return INVALID_OPERATION;
}
ret = port->getValue(GCSS_KEY_PEER, peerName);
if (ret != css_err_none) {
LOGE("Error getting peer attribute");
return BAD_VALUE;
}
ret = mSettings->getDescendantByString(peerName, peer);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to find peer by name %s", peerName.c_str());
return BAD_VALUE;
}
return OK;
}
/**
* Return the port direction
*
* \param[in] port Reference to port Graph node
* \return 0 if it is an input port
* \return 1 if it is an output port
*/
int32_t GraphConfig::portGetDirection(Node *port)
{
int32_t direction = 0;
css_err_t ret = css_err_none;
ret = port->getValue(GCSS_KEY_DIRECTION, direction);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to retrieve port direction, default to input");
}
return direction;
}
/**
* Return the port full name
* The port full name is made out from:
* - the name program group it belongs to
* - the name of the port
* separated by ":"
*
* \param[in] port Reference to port Graph node
* \param[out] fullName reference to a string to store the full name
*
* \return OK if everything went fine.
* \return BAD_VALUE if any of the graph queries failed.
*/
status_t GraphConfig::portGetFullName(Node *port, string &fullName)
{
string portName, ancestorName;
Node *ancestor;
css_err_t ret = css_err_none;
if (CC_UNLIKELY(port == nullptr)) {
LOGE("Invalid parameter, could not get port full name");
return BAD_VALUE;
}
ret = port->getAncestor(&ancestor);
ret = ancestor->getValue(GCSS_KEY_NAME, ancestorName);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get ancestor name for port");
port->dumpNodeTree(port, 1);
return BAD_VALUE;
}
ret = port->getValue(GCSS_KEY_NAME, portName);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to retrieve port name");
return BAD_VALUE;
}
fullName = ancestorName + ":" + portName;
return OK;
}
/**
* Return true if the port is a virtual port, this is the end point
* of the graph.
* Virtual ports are the nodes of type sink.
*
* \param[in] port Reference to port Graph node
* \return true if it is a virtual port
* \return false if it is not a virtual port
*/
bool GraphConfig::portIsVirtual(Node *port)
{
string type;
css_err_t ret = css_err_none;
ret = port->getValue(GCSS_KEY_TYPE, type);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to retrieve port type, default to input");
}
return (type == string("sink"));
}
/**
* For a given port node it constructs the fourCC code used in the connection
* object. This is constructed from the program group id.
*
* \param[in] portNode
* \param[out] stageId Fourcc code that describes the PG where this node is
* contained
* \param[out] terminalID Fourcc code that describes the port, in FW jargon,
* this is a PG terminal.
* \return OK in case of no error
* \return BAD_VALUE in case some error is found
*/
status_t GraphConfig::portGetFourCCInfo(Node &portNode,
uint32_t &stageId, uint32_t &terminalId)
{
Node *pgNode; // The Program group node
css_err_t ret = css_err_none;
int32_t portId;
string type, subsystem;
ret = portNode.getValue(GCSS_KEY_ID, portId);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get port's id");
portNode.dumpNodeTree(&portNode, 1);
return BAD_VALUE;
}
ret = portNode.getAncestor(&pgNode);
if (CC_UNLIKELY(ret != css_err_none || pgNode == nullptr)) {
LOGE("Failed to get port ancestor");
return BAD_VALUE;
}
ret = pgNode->getValue(GCSS_KEY_TYPE, type);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get port's ancestor type ");
pgNode->dumpNodeTree(pgNode, 1);
return BAD_VALUE;
}
ret = pgNode->getValue(GCSS_KEY_SUBSYSTEM, subsystem);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get port's ancestor subsystem ");
pgNode->dumpNodeTree(pgNode, 1);
return BAD_VALUE;
}
if (type == string("hw")) {
stageId = 0;
terminalId = portId;
}
return OK;
}
/**
* Return the the terminal id of the peer port.
*
* Given a name of a port in canonical format (i.e. isa:non_scaled_ouptut)
* this method returns the terminal uid (the fourcc code) associated with its
* peer port.
*
* \param[in] name Port name in canonical format
* \param[out] terminalId Terminal id of the peer port
*
* \return BAD_VALUE in case name is empty
* \return INVALID_OPERATION if the port or peer was not found in the graph
* \return OK
*/
status_t GraphConfig::portGetPeerIdByName(string name,
uid_t &terminalId)
{
uid_t stageId; // not used
css_err_t ret;
status_t retErr;
Node *portNode = nullptr;
Node *peerNode = nullptr;
if (name.empty())
return BAD_VALUE;
ret = mSettings->getDescendantByString(name, &portNode);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to find port %s.", name.c_str());
return INVALID_OPERATION;
}
retErr = portGetPeer(portNode, &peerNode);
if (ret != OK || peerNode == nullptr) {
LOGE("Failed to find peer for port %s.", name.c_str());
return INVALID_OPERATION;
}
portGetFourCCInfo(*peerNode, stageId, terminalId);
return OK;
}
/**
* Parse the information of the sensor node in the graph and store it in the
* provided SourceNodeInfo struct.
*
* \param[in] sensorNode Pointer to the graph config node that represents
* the sensor.
* \param[out] info Data structure where the information is stored.
*/
status_t GraphConfig::parseSensorNodeInfo(Node* sensorNode,
SourceNodeInfo &info)
{
status_t retErr = OK;
css_err_t ret = css_err_none;
string metadata;
string tmp;
ret = sensorNode->getValue(GCSS_KEY_CSI_PORT, info.csiPort);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Error: Couldn't get csi port from the graph");
return UNKNOWN_ERROR;
}
ret = sensorNode->getValue(GCSS_KEY_SENSOR_NAME, info.name);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Error: Couldn't get sensor name from sensor");
return UNKNOWN_ERROR;
}
ret = sensorNode->getValue(GCSS_KEY_LINK_FREQ, info.link_freq);
if (CC_UNLIKELY(ret != css_err_none)) {
info.link_freq = "0"; // default to zero
}
// Find i2c address for the sensor from sensor info
const CameraHWInfo *camHWInfo = PlatformData::getCameraHWInfo();
for (size_t i = 0; i < camHWInfo->mSensorInfo.size(); i++) {
if (camHWInfo->mSensorInfo[i].mSensorName == info.name)
info.i2cAddress = camHWInfo->mSensorInfo[i].mI2CAddress;
}
if (info.i2cAddress == "") {
LOGE("Couldn't get i2c address from Platformdata");
return UNKNOWN_ERROR;
}
ret = sensorNode->getValue(GCSS_KEY_METADATA, metadata);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Error: Couldn't get metadata enabled from sensor");
return UNKNOWN_ERROR;
}
info.metadataEnabled = atoi(metadata.c_str());
ret = sensorNode->getValue(GCSS_KEY_MODE_ID, info.modeId);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Error: Couldn't get sensor mode id from sensor");
return UNKNOWN_ERROR;
}
ret = sensorNode->getValue(GCSS_KEY_BAYER_ORDER, info.nativeBayer);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Error: Couldn't get native bayer order from sensor");
return UNKNOWN_ERROR;
}
retErr = getDimensions(sensorNode, info.output.w, info.output.h);
if (retErr != OK) {
LOGE("Error: Couldn't get values from sensor");
return UNKNOWN_ERROR;
}
// v-flip is not mandatory. Some sensors may not have this control
ret = sensorNode->getValue(GCSS_KEY_VFLIP, info.verticalFlip);
// h-flip is not mandatory. Some sensors may not have this control
ret = sensorNode->getValue(GCSS_KEY_HFLIP, info.horizontalFlip);
Node *port0Node = nullptr;
ret = sensorNode->getDescendant(GCSS_KEY_PORT_0, &port0Node);
if (ret != css_err_none) {
LOGE("Error: Couldn't get port_0");
return UNKNOWN_ERROR;
}
ret = port0Node->getValue(GCSS_KEY_FORMAT, tmp);
if (ret != css_err_none) {
LOGE("Error: Couldn't get format from the graph");
return UNKNOWN_ERROR;
}
/* find mbus format from common format in settings */
info.output.mbusFormat = gcu::getMBusFormat(get_fourcc(tmp[0], tmp[1],
tmp[2], tmp[3]));
// Imgu format. The tool and getMBusFormat is not in sync.
if (tmp == "RA10" || tmp == "RG10") {
info.output.mbusFormat = MEDIA_BUS_FMT_SRGGB10_1X10;
} else if (tmp == "BA10" || tmp == "BG10") {
info.output.mbusFormat = MEDIA_BUS_FMT_SGRBG10_1X10;
} else {
LOGE("Error: No valid format set in the settings.");
}
/*
* Get size and cropping from pixel array to use in format and selection
*/
Node *pixelArrayOutput = nullptr;
ret = sensorNode->getDescendantByString("pixel_array:output",
&pixelArrayOutput);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Error: Couldn't get pixel array node from the graph");
return UNKNOWN_ERROR;
}
retErr = getDimensions(pixelArrayOutput, info.pa.out.w,
info.pa.out.h,
info.pa.out.l,
info.pa.out.t);
if (retErr != OK) {
LOGE("Error: Couldn't get values from pixel array output");
return UNKNOWN_ERROR;
}
info.pa.name = info.name+ " " + info.i2cAddress;
/* Populate the formats for each subdevice
* The format for the Pixel Array is determined by the native bayer order
* and the bpp selected by the settings.
* We extract the bpp from the format in the sensor port.
*
* The format in the sensor output port may be different that the
* pixel array format because the sensor may be changing the effective
* bayer order by flipping or internal cropping
* */
int32_t bpp = gcu::getBpp(info.output.mbusFormat);
info.pa.out.mbusFormat = gcu::getMBusFormat(info.nativeBayer, bpp);
return OK;
}
/*
* Create mediaCtlConfig structure to configure sensor mode. All settings are
* retrieved from graph config settings and applied one by one.
*
* TODO a lot of string values because of android gcss keys, which does not
* have support for ints. Some could be moved to gcss_keys
*
* \param[in/out] cio2Format the format to use
* \param[out] mediaCtlConfig the struct to populate
* \return OK at success
* \return UNKNOWN ERROR at failure
*/
status_t GraphConfig::getCio2MediaCtlData(int *cio2Format, MediaCtlConfig *mediaCtlConfig)
{
CheckError((!mediaCtlConfig), BAD_VALUE, "@%s null ptr\n", __FUNCTION__);
ConfigProperties cameraProps; // camera properties
css_err_t ret = css_err_none;
status_t retErr = OK;
string formatStr;
SourceNodeInfo sourceInfo;
Node *sourceNode = nullptr;
string csi2;
if (mSourceType == SRC_SENSOR) {
ret = mSettings->getDescendant(GCSS_KEY_SENSOR, &sourceNode);
if (ret != css_err_none) {
LOGE("Error: Couldn't get sensor node from the graph");
return UNKNOWN_ERROR;
}
retErr = parseSensorNodeInfo(sourceNode, sourceInfo);
if (retErr != OK) {
LOGE("Error: Couldn't get sensor node info");
return UNKNOWN_ERROR;
}
*cio2Format = sourceInfo.pa.out.mbusFormat;
// get the next entity port of the "ov5670 binner 11-0010" which is dynamical.
// it could "ipu3-csi2 0" or "ipu3-csi2 1", then it will get 0 or 1.
int port = 0;
std::shared_ptr<MediaEntity> entity = nullptr;
string entityName = sourceInfo.name + " " + sourceInfo.i2cAddress;
LOG1("entityName:%s\n", entityName.c_str());
status_t ret = mMediaCtl->getMediaEntity(entity, entityName.c_str());
if (ret != NO_ERROR) {
LOGE("@%s, fail to call getMediaEntity, ret:%d\n", __FUNCTION__, ret);
return UNKNOWN_ERROR;
}
std::vector<media_link_desc> links;
entity->getLinkDesc(links);
LOG1("@%s, links number:%zu\n", __FUNCTION__, links.size());
if (links.size()) {
struct media_pad_desc* pad = &links[0].sink;
LOG1("@%s, sink entity:%d, flags:%d, index:%d\n",
__FUNCTION__, pad->entity, pad->flags, pad->index);
struct media_entity_desc entityDesc;
mMediaCtl->findMediaEntityById(pad->entity, entityDesc);
LOG1("@%s, name:%s\n", __FUNCTION__, entityDesc.name);
string name = entityDesc.name;
std::size_t p = name.find(" ");
if (p != std::string::npos) {
string s;
s.append(name, p + 1, 1);
port = std::stoi(s);
LOG1("@%s, port:%d\n", __FUNCTION__, port);
}
}
// get csi2 and cio2 names
csi2 = csi2WithoutPort + std::to_string(port);
mCSIBE = CSI_BE + std::to_string(port);
LOG1(" csi2 is:%s, cio2 is:%s\n", csi2.c_str(), mCSIBE.c_str());
} else {
LOGE("Error: No source");
return UNKNOWN_ERROR;
}
// Add control params
retErr = addControls(sourceNode, sourceInfo, mediaCtlConfig);
if (retErr != OK) {
return UNKNOWN_ERROR;
}
/*
* Add Camera properties to mediaCtlConfig
*/
int id = 0;
ret = sourceNode->getValue(GCSS_KEY_ID, id);
if (ret != css_err_none) {
LOGE("Error: Couldn't get sensor id from sensor");
return UNKNOWN_ERROR;
}
std::string cameraName = sourceInfo.name + " " + sourceInfo.modeId;
cameraProps.outputWidth = sourceInfo.output.w;
cameraProps.outputHeight = sourceInfo.output.h;
cameraProps.id = id;
cameraProps.name = cameraName.c_str();
mediaCtlConfig->mCameraProps = cameraProps;
mediaCtlConfig->mFTCSize.Width = sourceInfo.output.w;
mediaCtlConfig->mFTCSize.Height = sourceInfo.output.h;
// Get csi_be node. If not found, try csi_be_soc. If not found return error.
int csiBEOutW = 0, csiBEOutH = 0;
Node *csiBEOutput = nullptr;
ret = mSettings->getDescendantByString("csi_be:output", &csiBEOutput);
CheckError(ret != css_err_none, UNKNOWN_ERROR, "Failed to get csi_be nodes from the graph");
ret = csiBEOutput->getValue(GCSS_KEY_FORMAT, formatStr);
CheckError(ret != css_err_none, UNKNOWN_ERROR, "Failed to get format from the graph");
retErr = getDimensions(csiBEOutput, csiBEOutW, csiBEOutH);
CheckError(retErr != OK, UNKNOWN_ERROR, "Failed to get values from csi be output");
/* consistency check, we have at least one CSI-BE */
CheckError(csiBEOutput == nullptr, UNKNOWN_ERROR, "CSI BE Output nullptr");
/* Set sensor pixel array parameter to the attributes in 'sensor_mode' node,
* ignore the attributes in pixel_array and binner node due to upstream driver
* removed binner and scaler subdev.
*/
addFormatParams(sourceInfo.pa.name, sourceInfo.output.w, sourceInfo.output.h, 0, sourceInfo.output.mbusFormat, 0, mediaCtlConfig);
// Add cio2 format into mediaCtlConfig
addFormatParams(csi2, csiBEOutW, csiBEOutH, 0, sourceInfo.output.mbusFormat, 0, mediaCtlConfig);
addFormatParams(csi2, csiBEOutW, csiBEOutH, 1, sourceInfo.output.mbusFormat, 0, mediaCtlConfig);
addFormatParams(mCSIBE, csiBEOutW, csiBEOutH, 0, sourceInfo.pa.out.mbusFormat, 0, mediaCtlConfig);
addFormatParams(mCSIBE, csiBEOutW, csiBEOutH, 1, sourceInfo.pa.out.mbusFormat, 0, mediaCtlConfig);
// Add video nodes into mediaCtlConfig
addVideoNodes(mediaCtlConfig);
/* Start populating selections into mediaCtlConfig
* entity name, width, height, left crop, top crop, target, pad, config */
addSelectionParams(sourceInfo.pa.name,
sourceInfo.pa.out.w,
sourceInfo.pa.out.h,
sourceInfo.pa.out.l,
sourceInfo.pa.out.t,
V4L2_SEL_TGT_CROP, 0 /* sink pad */, mediaCtlConfig);
LOG1("Adding Isys link %s:[0] -> %s:[0]", sourceInfo.pa.name.c_str(), csi2.c_str());
addLinkParams(sourceInfo.pa.name, 0, csi2, 0, 1, MEDIA_LNK_FL_ENABLED, mediaCtlConfig);
if (gDumpType & CAMERA_DUMP_MEDIA_CTL)
dumpMediaCtlConfig(*mediaCtlConfig);
return OK;
}
status_t GraphConfig::getNodeInfo(const ia_uid uid, const Node &parent, int* width, int* height)
{
status_t status = OK;
Node *node = nullptr;
status = parent.getDescendant(uid, &node);
if (status != css_err_none) {
LOGE("pipe log <%s> node is not present in graph (descriptor or settings) - continuing.",GCSS::ItemUID::key2str(uid));
return UNKNOWN_ERROR;
}
status = node->getValue(GCSS_KEY_WIDTH, *width);
if (status != css_err_none) {
LOGE("pipe log Could not get width for <%s>", NODE_NAME(node));
return UNKNOWN_ERROR;
}
if (width == 0) {
LOGE("pipe log Could not get width for <%s>", NODE_NAME(node));
return UNKNOWN_ERROR;
}
status = node->getValue(GCSS_KEY_HEIGHT, *height);
if (status != css_err_none) {
LOGE("pipe log Could not get height for <%s>", NODE_NAME(node));
return UNKNOWN_ERROR;
}
if (height == 0) {
LOGE("pipe log Could not get height for <%s>", NODE_NAME(node));
return UNKNOWN_ERROR;
}
return status;
}
/*
* Imgu specific function
*/
status_t GraphConfig::getImguMediaCtlData(int32_t cameraId,
int cio2Format,
int32_t testPatternMode,
bool enableStill,
MediaCtlConfig *mediaCtlConfig)
{
CheckError(!mediaCtlConfig, BAD_VALUE, "@%s null ptr\n", __func__);
int ret;
Node *imgu = nullptr;
int width = 0, height = 0, format = 0;
int enabled = 1;
string imguIndex = enableStill ? std::to_string(STILL_PIPE_INDEX) : std::to_string(VIDEO_PIPE_INDEX);
string kImguName = imguWithoutPort + imguIndex;
string inputName = imguWithoutPort + imguIndex + MEDIACTL_INPUTNAME;
string statName = imguWithoutPort + imguIndex + MEDIACTL_STATNAME;
string parameterName = imguWithoutPort + imguIndex + MEDIACTL_PARAMETERNAME;
string outputName = imguWithoutPort + imguIndex + MEDIACTL_MAIN_NAME;
ret = mSettings->getDescendant(GCSS_KEY_IMGU, &imgu);
if (ret != css_err_none) {
LOGE("Error: Couldn't get imgu node");
return UNKNOWN_ERROR;
}
for (size_t i = 0; i < mLut.size(); i++) {
Node *pipe = nullptr;
struct v4l2_subdev_selection cropSelect, composeSelect;
CLEAR(cropSelect);
CLEAR(composeSelect);
ret = imgu->getDescendant(mLut[i].uid, &pipe);
if (ret != css_err_none) {
LOGD("<%u> node is not present in graph (descriptor or settings) - continuing.", mLut[i].uid);
continue;
}
// Assume enabled="1" by default. Explicitly set to 0 in settings, if necessary.
enabled = 1;
ret = pipe->getValue(GCSS_KEY_ENABLED, enabled);
if (ret != css_err_none) {
LOG1("Attribute 'enabled' not present in <%s>. Assuming enabled=\"1\"", NODE_NAME(pipe));
}
if (!enabled) {
LOG1("Node <%s> not enabled - continuing", NODE_NAME(pipe));
continue;
}
ret = pipe->getValue(GCSS_KEY_WIDTH, width);
if (ret != css_err_none) {
LOGE("Could not get width for <%s>", NODE_NAME(pipe));
return UNKNOWN_ERROR;
}
if (width == 0)
continue;
ret = pipe->getValue(GCSS_KEY_HEIGHT, height);
if (ret != css_err_none) {
LOGE("Could not get height for <%s>", NODE_NAME(pipe));
return UNKNOWN_ERROR;
}
string fourccFormat = "";
ret = pipe->getValue(GCSS_KEY_FORMAT, fourccFormat);
if (ret != css_err_none) {
LOGE("Could not get format for <%s>", NODE_NAME(pipe));
return UNKNOWN_ERROR;
}
if (fourccFormat == "NV12")
format = V4L2_PIX_FMT_NV12;
else if (fourccFormat == "CIO2")
format = cio2Format;
else if (fourccFormat == "YUYV")
format = V4L2_PIX_FMT_YUYV;
else
return UNKNOWN_ERROR;
if (mLut[i].uidStr == GC_MAIN ||
mLut[i].uidStr == GC_VF) {
int nodeWidth = 0, nodeHeight = 0;
// Get GDC info
ret = getNodeInfo(GCSS_KEY_IMGU_GDC, *imgu, &nodeWidth, &nodeHeight);
if (ret != OK) {
LOGE("pipe log name: %s can't get info!", mLut[i].nodeName.c_str());
return UNKNOWN_ERROR;
}
LOG2("pipe log name: %s gdc size %dx%d", mLut[i].nodeName.c_str(), nodeWidth, nodeHeight);
addFormatParams(kImguName, nodeWidth, nodeHeight, 0,
V4L2_MBUS_FMT_UYVY8_2X8, 0, mediaCtlConfig);
// Get IF info
ret = getNodeInfo(GCSS_KEY_IMGU_IF, *imgu, &nodeWidth, &nodeHeight);
if (ret != OK) {
LOGE("pipe log name: %s can't get info!", mLut[i].nodeName.c_str());
return UNKNOWN_ERROR;
}
cropSelect.which = V4L2_SUBDEV_FORMAT_ACTIVE;
cropSelect.target = V4L2_SEL_TGT_CROP;
cropSelect.pad = 0;
cropSelect.flags = 0;
cropSelect.r.left = 0;
cropSelect.r.top = 0;
cropSelect.r.width = nodeWidth;
cropSelect.r.height = nodeHeight;
addSelectionVideoParams(inputName, cropSelect, mediaCtlConfig);
LOG2("pipe log name: %s if size %dx%d", mLut[i].nodeName.c_str(), nodeWidth, nodeHeight);
// Get BDS info
ret = getNodeInfo(GCSS_KEY_IMGU_BDS, *imgu, &nodeWidth, &nodeHeight);
if (ret != OK) {
LOGE("pipe log name: %s can't get info!", mLut[i].nodeName.c_str());
return UNKNOWN_ERROR;
}
composeSelect.which = V4L2_SUBDEV_FORMAT_ACTIVE;
composeSelect.target = V4L2_SEL_TGT_COMPOSE;
composeSelect.pad = 0;
composeSelect.flags = 0;
composeSelect.r.left = 0;
composeSelect.r.top = 0;
composeSelect.r.width = nodeWidth;
composeSelect.r.height = nodeHeight;
addSelectionVideoParams(inputName, composeSelect, mediaCtlConfig);
LOG2("pipe log name: %s bds size %dx%d", mLut[i].nodeName.c_str(), nodeWidth, nodeHeight);
}
/* if node active add it to mediactl config */
if (width != 0) {
LOG2("Adding video node: %d %s %s", mLut[i].ipuNodeName, NODE_NAME(pipe), mLut[i].nodeName.c_str());
addImguVideoNode(mLut[i].ipuNodeName, mLut[i].nodeName, mediaCtlConfig);
}
/* Use BGGR as bayer format when specific sensor receives test pattern request */
if (testPatternMode != ANDROID_SENSOR_TEST_PATTERN_MODE_OFF) {
const IPU3CameraCapInfo* capInfo = getIPU3CameraCapInfo(cameraId);
CheckError(capInfo == nullptr, UNKNOWN_ERROR, "@%s: failed to get cameraCapInfo", __FUNCTION__);
if (mLut[i].nodeName.compare(inputName) == 0 &&
!capInfo->getTestPatternBayerFormat().empty()) {
format = gcu::getV4L2Format(capInfo->getTestPatternBayerFormat());
}
}
addFormatParams(mLut[i].nodeName, width, height, 1, format, 0, mediaCtlConfig);
if (mLut[i].uidStr != GC_INPUT) {
addLinkParams(kImguName, mLut[i].pad, mLut[i].nodeName, 0, 1, MEDIA_LNK_FL_ENABLED, mediaCtlConfig);
/*
* Set main node of imgu to be same with vf node if the
* selected graph setting just has only one output
*/
int32_t outputNum = 0;
ret = mSettings->getValue(GCSS_KEY_ACTIVE_OUTPUTS, outputNum);
CheckError(ret != css_err_none, UNKNOWN_ERROR, "Couldn't get active output from graph config");
if (outputNum < MAX_OUTPUT_NUM_IN_PIPE) {
LOG2("Adding video node: %d %s %s", IMGU_NODE_MAIN, "video", outputName.c_str());
addSelectionVideoParams(inputName, cropSelect, mediaCtlConfig);
addSelectionVideoParams(inputName, composeSelect, mediaCtlConfig);
addImguVideoNode(IMGU_NODE_MAIN, outputName, mediaCtlConfig);
addFormatParams(outputName, width, height, 1, format, 0, mediaCtlConfig);
addLinkParams(kImguName, MEDIACTL_PAD_OUTPUT_NUM, outputName,
0, 1, MEDIA_LNK_FL_ENABLED, mediaCtlConfig);
}
} else {
addLinkParams(mLut[i].nodeName, 0, kImguName, mLut[i].pad, 1, MEDIA_LNK_FL_ENABLED, mediaCtlConfig);
}
}
LOG2("Adding stat node");
addImguVideoNode(IMGU_NODE_STAT, statName, mediaCtlConfig);
addLinkParams(kImguName, 4, statName, 0, 1, MEDIA_LNK_FL_ENABLED, mediaCtlConfig);
LOG2("Adding parameter node");
addImguVideoNode(IMGU_NODE_PARAM, parameterName, mediaCtlConfig);
addLinkParams(parameterName, 0, kImguName, 1, 1, MEDIA_LNK_FL_ENABLED, mediaCtlConfig);
std::string runningMode = std::to_string(enableStill ? STILL_PIPE_INDEX : VIDEO_PIPE_INDEX);
addCtlParams(kImguName, 0, V4L2_CID_INTEL_IPU3_MODE, runningMode, mediaCtlConfig);
return ret;
}
void GraphConfig::setMediaCtlConfig(std::shared_ptr<MediaController> mediaCtl,
bool enableStill)
{
mMediaCtl = mediaCtl;
// "ipu3-imgu 0" for video pipe, "ipu3-imgu 1" for still pipe
string imguIndex = enableStill ? std::to_string(STILL_PIPE_INDEX) : std::to_string(VIDEO_PIPE_INDEX);
const string mainName = imguWithoutPort + imguIndex + MEDIACTL_MAIN_NAME;
const string vfName = imguWithoutPort + imguIndex + MEDIACTL_VF_NAME;
const string inputName = imguWithoutPort + imguIndex + MEDIACTL_INPUTNAME;
int32_t outputNum = 0;
mLut.clear();
MediaCtlLut lut;
lut.ipuNodeName = IMGU_NODE_NULL;
int ret = mSettings->getValue(GCSS_KEY_ACTIVE_OUTPUTS, outputNum);
CheckError(ret != css_err_none, VOID_VALUE, "Couldn't not get the active output from graph config");
lut.uid = GCSS_KEY_IMGU_VF;
lut.uidStr = GCSS::ItemUID::key2str(lut.uid);
lut.pad = MEDIACTL_PAD_VF_NUM;
lut.nodeName = vfName;
LOG2("save graph setting nodes: %s, pad: %d, nodename: %s", lut.uidStr.c_str(), lut.pad, lut.nodeName.c_str());
mLut.push_back(lut);
if (outputNum >= MAX_OUTPUT_NUM_IN_PIPE) {
lut.uid = GCSS_KEY_IMGU_MAIN;
lut.uidStr = GCSS::ItemUID::key2str(lut.uid);
lut.pad = MEDIACTL_PAD_OUTPUT_NUM;
lut.nodeName = mainName;
LOG2("save graph setting nodes: %s, pad: %d, nodename: %s", lut.uidStr.c_str(), lut.pad, lut.nodeName.c_str());
mLut.push_back(lut);
}
lut.uid = GCSS_KEY_INPUT;
lut.uidStr = GCSS::ItemUID::key2str(lut.uid);
lut.pad = 0;
lut.nodeName = inputName;
LOG2("save graph setting nodes: %s, pad: %d, nodename: %s", lut.uidStr.c_str(), lut.pad, lut.nodeName.c_str());
mLut.push_back(lut);
for (auto & it : mLut) {
it.ipuNodeName = (it.uid == GCSS_KEY_IMGU_MAIN) ? IMGU_NODE_MAIN
: (it.uid == GCSS_KEY_IMGU_VF) ? IMGU_NODE_VF
: (it.uid == GCSS_KEY_INPUT) ? IMGU_NODE_INPUT
: IMGU_NODE_NULL;
}
}
/*
* Imgu helper function
*/
bool GraphConfig::doesNodeExist(string nodeName)
{
int ret;
Node *node = nullptr;
ret = mSettings->getDescendantByString(nodeName, &node);
if (ret != css_err_none) {
LOGD("Node <%s> was not found.", nodeName.c_str());
return false;
}
/* There is no good way to search if node exists or not.
* Because mSettings has both descriptor and settings combined we
* need to ask for specific value see if node really exists in the
* settings side. */
int width;
ret = node->getValue(GCSS_KEY_WIDTH, width);
if (ret != css_err_none) {
LOGD("Node <%s> was not found.", nodeName.c_str());
return false;
}
return true;
}
/*
* Get values for MediaCtlConfig control params.
* \Node sensorNode pointer to sensor node
*/
status_t GraphConfig::addControls(const Node *sensorNode,
const SourceNodeInfo &sourceInfo,
MediaCtlConfig* config)
{
css_err_t ret = css_err_none;
std::string value;
std::string entityName;
if (!sourceInfo.pa.name.empty()) {
entityName = sourceInfo.pa.name;
} else if (!sourceInfo.tpg.name.empty()) {
entityName = sourceInfo.tpg.name;
} else {
LOGE("Empty entity name");
return UNKNOWN_ERROR;
}
ret = sensorNode->getValue(GCSS_KEY_EXPOSURE, value);
if (ret == css_err_none) {
addCtlParams(entityName, GCSS_KEY_EXPOSURE, V4L2_CID_EXPOSURE,
value, config);
}
ret = sensorNode->getValue(GCSS_KEY_GAIN, value);
if (ret == css_err_none) {
addCtlParams(entityName, GCSS_KEY_GAIN, V4L2_CID_ANALOGUE_GAIN,
value, config);
}
return OK;
}
/**
* Add video nodes into mediaCtlConfig
*
* \param mediaCtlConfig[out] populate this struct with given values
*/
void GraphConfig::addVideoNodes(MediaCtlConfig *config)
{
CheckError((!config), VOID_VALUE, "@%s null ptr\n", __FUNCTION__);
MediaCtlElement mediaCtlElement;
// Imgu support
mediaCtlElement.isysNodeName = ISYS_NODE_RAW;
mediaCtlElement.name = mCSIBE;
config->mVideoNodes.push_back(mediaCtlElement);
}
void GraphConfig::addImguVideoNode(int ipuNodeName, const string& nodeName, MediaCtlConfig *config)
{
CheckError((!config), VOID_VALUE, "@%s null ptr\n", __FUNCTION__);
CheckError((ipuNodeName == IMGU_NODE_NULL), VOID_VALUE, "@%s null ipu node name\n", __FUNCTION__);
MediaCtlElement mediaCtlElement;
mediaCtlElement.name = nodeName;
mediaCtlElement.isysNodeName = ipuNodeName;
config->mVideoNodes.push_back(mediaCtlElement);
}
/*
* Imgu helper function
*/
status_t GraphConfig::getValue(string &nodeName, uint32_t id, int &value)
{
Node *node;
int ret = mSettings->getDescendantByString(nodeName, &node);
if (ret != css_err_none) {
LOGE("Error: Couldn't get %s node", nodeName.c_str());
return UNKNOWN_ERROR;
}
GCSS::GraphConfigAttribute *attr;
ret = node->getAttribute(id, &attr);
if (ret != css_err_none) {
LOGE("Error: Couldn't get attribute '%s' for node: %s", GCSS::ItemUID::key2str(id), NODE_NAME(node));
return UNKNOWN_ERROR;
}
string valueString = "-2";
ret = attr->getValue(valueString);
if (ret != css_err_none) {
LOGE("Error: Couldn't get value of '%s' for node: %s", GCSS::ItemUID::key2str(id), NODE_NAME(node));
return UNKNOWN_ERROR;
}
value = atoi(valueString.c_str());
return OK;
}
/**
* Dump contents of mediaCtlConfig struct
*/
void GraphConfig::dumpMediaCtlConfig(const MediaCtlConfig &config) const {
size_t i = 0;
LOGE("MediaCtl config w=%d ,height=%d"
,config.mCameraProps.outputWidth,
config.mCameraProps.outputHeight);
for (i = 0; i < config.mLinkParams.size() ; i++) {
LOGE("Link Params srcName=%s srcPad=%d ,sinkName=%s, sinkPad=%d enable=%d"
,config.mLinkParams[i].srcName.c_str(),
config.mLinkParams[i].srcPad,
config.mLinkParams[i].sinkName.c_str(),
config.mLinkParams[i].sinkPad,
config.mLinkParams[i].enable);
}
for (i = 0; i < config.mFormatParams.size() ; i++) {
LOGE("Format Params entityName=%s pad=%d ,width=%d, height=%d formatCode=%x"
,config.mFormatParams[i].entityName.c_str(),
config.mFormatParams[i].pad,
config.mFormatParams[i].width,
config.mFormatParams[i].height,
config.mFormatParams[i].formatCode);
}
for (i = 0; i < config.mSelectionParams.size() ; i++) {
LOGE("Selection Params entityName=%s pad=%d ,target=%d, top=%d left=%d width=%d, height=%d"
,config.mSelectionParams[i].entityName.c_str(),
config.mSelectionParams[i].pad,
config.mSelectionParams[i].target,
config.mSelectionParams[i].top,
config.mSelectionParams[i].left,
config.mSelectionParams[i].width,
config.mSelectionParams[i].height);
}
for (i = 0; i < config.mControlParams.size() ; i++) {
LOGE("Control Params entityName=%s controlId=%x ,value=%d, controlName=%s"
,config.mControlParams[i].entityName.c_str(),
config.mControlParams[i].controlId,
config.mControlParams[i].value,
config.mControlParams[i].controlName.c_str());
}
}
/**
* Get binning factor values from the given node
* \param[in] node the node to read the values from
* \param[out] hBin horizontal binning factor
* \param[out] vBin vertical binning factor
*/
status_t GraphConfig::getBinningFactor(const Node *node, int &hBin, int &vBin) const
{
css_err_t ret = css_err_none;
string value;
ret = node->getValue(GCSS_KEY_BINNING_H_FACTOR, hBin);
if (ret != css_err_none) {
LOGE("Error: Couldn't get horizontal binning factor");
return UNKNOWN_ERROR;
}
ret = node->getValue(GCSS_KEY_BINNING_V_FACTOR, vBin);
if (ret != css_err_none) {
LOGE("Error: Couldn't get vertical binning factor");
return UNKNOWN_ERROR;
}
return OK;
}
/**
* Get scaling factor values from the given node
* \param[in] node the node to read the values from
* \param[out] scalingNum scaling ratio
* \param[out] scalingDenom scaling ratio
*/
status_t GraphConfig::getScalingFactor(const Node *node,
int32_t &scalingNum,
int32_t &scalingDenom) const
{
css_err_t ret = css_err_none;
string value;
ret = node->getValue(GCSS_KEY_SCALING_FACTOR_NUM, scalingNum);
if (ret != css_err_none) {
LOGE("Error: Couldn't get width scaling num ratio");
return UNKNOWN_ERROR;
}
ret = node->getValue(GCSS_KEY_SCALING_FACTOR_DENOM, scalingDenom);
if (ret != css_err_none) {
LOGE("Error: Couldn't get width scaling num ratio");
return UNKNOWN_ERROR;
}
return OK;
}
/**
* Get width and height values from the given node
* \param[in] node the node to read the values from
* \param[out] w width
* \param[out] h height
*/
status_t GraphConfig::getDimensions(const Node *node, int &w, int &h) const
{
css_err_t ret = css_err_none;
ret = node->getValue(GCSS_KEY_WIDTH, w);
if (ret != css_err_none) {
LOGE("Error: Couldn't get width");
return UNKNOWN_ERROR;
}
ret = node->getValue(GCSS_KEY_HEIGHT, h);
if (ret != css_err_none) {
LOGE("Error: Couldn't get height");
return UNKNOWN_ERROR;
}
return OK;
}
/**
* Get width, height and cropping values from the given node
* \param[in] node the node to read the values from
* \param[out] w width
* \param[out] h height
* \param[out] l left crop
* \param[out] t top crop
*/
status_t GraphConfig::getDimensions(const Node *node, int32_t &w, int32_t &h,
int32_t &l, int32_t &t) const
{
status_t retErr = getDimensions(node, w, h);
if (retErr != OK)
return UNKNOWN_ERROR;
css_err_t ret = node->getValue(GCSS_KEY_LEFT, l);
if (ret != css_err_none) {
LOGE("Error: Couldn't get left crop");
return UNKNOWN_ERROR;
}
ret = node->getValue(GCSS_KEY_TOP, t);
if (ret != css_err_none) {
LOGE("Error: Couldn't get top crop");
return UNKNOWN_ERROR;
}
return OK;
}
/**
* Add format params to config
*
* \param[in] entityName
* \param[in] width
* \param[in] height
* \param[in] pad
* \param[in] format
* \param[out] config populate this struct with given values
*/
void GraphConfig::addFormatParams(const string &entityName,
int width,
int height,
int pad,
int format,
int field,
MediaCtlConfig *config)
{
if (!entityName.empty() && config) {
MediaCtlFormatParams mediaCtlFormatParams;
mediaCtlFormatParams.entityName = entityName;
mediaCtlFormatParams.width = width;
mediaCtlFormatParams.height = height;
mediaCtlFormatParams.pad = pad;
mediaCtlFormatParams.formatCode = format;
mediaCtlFormatParams.stride = 0;
mediaCtlFormatParams.field = field;
config->mFormatParams.push_back(mediaCtlFormatParams);
LOG2("@%s, entityName:%s, width:%d, height:%d, pad:%d, format:%d, format:%s, field:%d",
__FUNCTION__, entityName.c_str(), width, height, pad, format, v4l2Fmt2Str(format), field);
}
}
/**
* Add control params into config
* \param[in] entityName
* \param[in] controlName
* \param[in] controlId
* \param[in] strValue
* \param[out] config populate this struct with given values
*/
void GraphConfig::addCtlParams(const string &entityName,
uint32_t controlName,
int controlId,
const string &strValue,
MediaCtlConfig *config)
{
if (!entityName.empty() && config) {
int value = atoi(strValue.c_str());
string controlNameStr("");
if (controlName)
controlNameStr = GCSS::ItemUID::key2str(controlName);
MediaCtlControlParams mediaCtlControlParams;
mediaCtlControlParams.entityName = entityName;
mediaCtlControlParams.controlName = controlNameStr;
mediaCtlControlParams.controlId = controlId;
mediaCtlControlParams.value = value;
config->mControlParams.push_back(mediaCtlControlParams);
LOG2("@%s, entityName:%s, controlNameStr:%s, controlId:%d, value:%d",
__FUNCTION__, entityName.c_str(), controlNameStr.c_str(), controlId, value);
}
}
/**
* Add selection params into config
*
* \param[in] entityName
* \param[in] width
* \param[in] height
* \param[in] left
* \param[in] top
* \param[in] target
* \param[in] pad
* \param[out] config populate this struct with given values
*/
void GraphConfig::addSelectionParams(const string &entityName,
int width,
int height,
int left,
int top,
int target,
int pad,
MediaCtlConfig *config)
{
if (!entityName.empty() && config) {
MediaCtlSelectionParams mediaCtlSelectionParams;
mediaCtlSelectionParams.width = width;
mediaCtlSelectionParams.height = height;
mediaCtlSelectionParams.left = left;
mediaCtlSelectionParams.top = top;
mediaCtlSelectionParams.target = target;
mediaCtlSelectionParams.pad = pad;
mediaCtlSelectionParams.entityName = entityName;
config->mSelectionParams.push_back(mediaCtlSelectionParams);
LOG2("@%s, width:%d, height:%d, left:%d, top:%d, target:%d, pad:%d, entityName:%s",
__FUNCTION__, width, height, left, top, target, pad, entityName.c_str());
}
}
void GraphConfig::addSelectionVideoParams(const string &entityName,
const struct v4l2_subdev_selection &select,
MediaCtlConfig* config)
{
if (entityName.empty() || !config) {
LOGE("The config or entity <%s> is empty!", entityName.c_str());
return;
}
MediaCtlSelectionVideoParams mediaCtlSelectionVideoParams;
mediaCtlSelectionVideoParams.entityName = entityName;
mediaCtlSelectionVideoParams.select = select;
config->mSelectionVideoParams.push_back(mediaCtlSelectionVideoParams);
LOG2("@%s, width:%d, height:%d, left:%d, top:%d, target:%d, which:%d, flags:%d entityName:%s",
__FUNCTION__, select.r.width, select.r.height, select.r.left, select.r.top,
select.target, select.which, select.flags, entityName.c_str());
}
/**
* Add link params into config
*
* \param[in] srcName
* \param[in] srcPad
* \param[in] sinkName
* \param[in] sinkPad
* \param[in] enable
* \param[in] flags
* \param[out] config populate this struct with given values
*/
void GraphConfig::addLinkParams(const string &srcName,
int srcPad,
const string &sinkName,
int sinkPad,
int enable,
int flags,
MediaCtlConfig *config)
{
if (!srcName.empty() && !sinkName.empty() && config) {
MediaCtlLinkParams mediaCtlLinkParams;
mediaCtlLinkParams.srcName = srcName;
mediaCtlLinkParams.srcPad = srcPad;
mediaCtlLinkParams.sinkName = sinkName;
mediaCtlLinkParams.sinkPad = sinkPad;
mediaCtlLinkParams.enable = enable;
mediaCtlLinkParams.flags = flags;
config->mLinkParams.push_back(mediaCtlLinkParams);
LOG2("@%s, srcName:%s, srcPad:%d, sinkName:%s, sinkPad:%d, enable:%d, flags:%d",
__FUNCTION__, srcName.c_str(), srcPad, sinkName.c_str(), sinkPad, enable, flags);
}
}
/**
* Retrieve the resolution of the sensor mode in use.
* sensor frame params is used to inform 3A what is the size of the
* image that arrives to the ISP, in this case the ISA PG.
* We pick it up from the sensor node of the graph.
* In the settings we have had only width and height. We do not have
* attributes for the cropping or scaling factor.
* For that reason the dimensions set is the settings of the node should
* be the final size produced by the sensor, not the one of the pixel array.
*/
status_t
GraphConfig::getSensorFrameParams(ia_aiq_frame_params &sensorFrameParams)
{
Node *sensorNode = nullptr;
Node *pixelArrayNode = nullptr;
Node *binnerNode = nullptr;
Node *scalerNode = nullptr;
css_err_t ret = css_err_none;
int32_t w,h;
int32_t wPixArray,hPixArray;
int32_t lPixArray,tPixArray;
int32_t lFinalCrop = 0;
int32_t tFinalCrop = 0;
if (mSourceType != SRC_SENSOR) {
LOGE("wrong source type");
return UNKNOWN_ERROR;
}
// calculate the frame params when source is sensor
ret = mSettings->getDescendant(GCSS_KEY_SENSOR, &sensorNode);
if (ret != css_err_none) {
LOGE("Error: Couldn't get sensor mode node from the graph");
return UNKNOWN_ERROR;
}
ret = getDimensions(sensorNode, w, h);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get dimension for sensor Node");
return UNKNOWN_ERROR;
}
ret = sensorNode->getDescendantByString("pixel_array:output", &pixelArrayNode);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get pixel_array:output");
return UNKNOWN_ERROR;
}
ret = getDimensions(pixelArrayNode, wPixArray, hPixArray, lPixArray, tPixArray);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get pixel array output dimension and crop");
return UNKNOWN_ERROR;
}
// start to accumulate cropping.
lFinalCrop = lPixArray;
tFinalCrop = tPixArray;
LOG1("%s: PixelArray output: w: %d, h: %d, crop l: %d, crop t: %d",
__FUNCTION__, wPixArray, hPixArray, lPixArray, tPixArray);
int32_t hBinning = 1;
int32_t vBinning = 1;
ret = sensorNode->getDescendant(GCSS_KEY_BINNER, &binnerNode);
if (ret != css_err_none) {
LOGW("Warning, no binner found, make sure sensor has no binner");
} else {
int32_t lBinner, tBinner; // binner left and top crop
int32_t wBinner, hBinner; // width and height of binner output
ret = getBinningFactor(binnerNode, hBinning, vBinning);
if (ret != css_err_none) {
LOGE("Error: Couldn't get binning factor");
return UNKNOWN_ERROR;
}
ret = sensorNode->getDescendantByString("binner:output", &binnerNode);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get binner:output");
return UNKNOWN_ERROR;
}
ret = getDimensions(binnerNode, wBinner, hBinner, lBinner, tBinner);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get binner output dimensions and crop");
return UNKNOWN_ERROR;
}
LOG1("%s: binner output w: %d, %d,"
" binning: w: %d, h: %d, crop w: %d, crop h: %d", __FUNCTION__, wBinner, hBinner,
hBinning, vBinning,
lBinner, tBinner);
// accumulate binner cropping.
lFinalCrop += (lBinner * hBinning);
tFinalCrop += (tBinner * vBinning);
}
int32_t scalingNum = 1; // avoid possible division by 0
int32_t scalingDenom = 1; // avoid possible division by 0
int32_t lScaler = 0; // left scaler crop
int32_t tScaler = 0; // top scaler crop
hBinning = (hBinning <= 0) ? 1 : hBinning;
vBinning = (vBinning <= 0) ? 1 : vBinning;
int32_t wScaler = wPixArray / hBinning;
int32_t hScaler = hPixArray / vBinning;
ret = sensorNode->getDescendant(GCSS_KEY_SCALER, &scalerNode);
if (ret == css_err_none) {
ret = getScalingFactor(scalerNode, scalingNum, scalingDenom);
if (ret != css_err_none) {
LOGE("Error: Couldn't get scaling factor");
return UNKNOWN_ERROR;
}
if (scalingDenom == 0) {
LOGE("Scaling Denominator is 0! Wrong setting! Set to 16");
scalingNum = 16;
}
if (scalingNum == 0) {
LOGE("Scaling Numerator is 0! Wrong setting! Set to 16");
scalingDenom = 16;
}
ret = sensorNode->getDescendantByString("scaler:output", &scalerNode);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get scaler:output");
return UNKNOWN_ERROR;
}
ret = getDimensions(scalerNode, wScaler, hScaler, lScaler, tScaler);
if (CC_UNLIKELY(ret != css_err_none)) {
LOGE("Failed to get scaler output dimensions and crop");
return UNKNOWN_ERROR;
}
LOG1("%s: scaler output w: %d, h: %d, crop w: %d, crop h: %d",
__FUNCTION__,
wScaler, hScaler,
lScaler, tScaler);
}
int32_t wPixFormatIn = wScaler;
int32_t hPixFormatIn = hScaler;
int32_t wPixFormatOut = wPixFormatIn;
int32_t hPixFormatOut = hPixFormatIn;
int32_t lPixFormat = 0;
int32_t tPixFormat = 0;
int32_t lLastStep; // all horizontal croppings after last scaling
int32_t tLastStep; // all vertical croppings after last scaling
/*pixel formatter crop and scaler crop are be handled at the same time
* since they appear after the scaling
*/
lLastStep = lPixFormat + lScaler;
tLastStep = tPixFormat + tScaler;
lFinalCrop += ((lLastStep * scalingDenom) / scalingNum) * hBinning;
tFinalCrop += ((tLastStep * scalingDenom) / scalingNum) * vBinning;
int32_t wCroppedImage = ((wPixFormatOut * scalingDenom) / scalingNum * hBinning);
int32_t hCroppedImage = ((hPixFormatOut * scalingDenom) / scalingNum * vBinning);
LOG1("------------------- sensorFrameParams ---------------------------");
LOG1("%s: Final cropped Image w = %d, Final cropped Image h = %d",
__FUNCTION__, wCroppedImage, hCroppedImage);
LOG1("%s: Horizontal_crop_offset = %d, Vertical_crop_offset = %d",
__FUNCTION__, lFinalCrop, tFinalCrop);
LOG1("-----------------------------------------------------------------");
sensorFrameParams.cropped_image_width = wCroppedImage;
sensorFrameParams.cropped_image_height = hCroppedImage;
sensorFrameParams.horizontal_crop_offset = lFinalCrop;
sensorFrameParams.vertical_crop_offset = tFinalCrop;
sensorFrameParams.horizontal_scaling_numerator = SCALING_FACTOR;
sensorFrameParams.horizontal_scaling_denominator = SCALING_FACTOR;
sensorFrameParams.vertical_scaling_numerator = SCALING_FACTOR;
sensorFrameParams.vertical_scaling_denominator = SCALING_FACTOR;
return OK;
}
GraphConfig::Rectangle::Rectangle(): w(0),h(0),t(0),l(0) {}
GraphConfig::SubdevPad::SubdevPad(): Rectangle(), mbusFormat(0){}
GraphConfig::SourceNodeInfo::SourceNodeInfo() : metadataEnabled(false),
interlaced(0) {}
} // namespace intel
} // namespace cros