上一篇说到了上层函数调用硬件驱动,驱动文件libCommon/HardwareDriver.c中,这一片讲一下C++上层控制逻辑,注意大部分数据传输都是调用的的PostMessage~
1、首先进入主函数:
ConfigFile cfg;
SetDefaults(cfg);
auto& FileInfo = cfg.FileInfo;
auto& Settings = cfg.Settings;
auto& StreamFileName = cfg.BitstreamFileName;
auto& RecFileName = cfg.RecFileName;
auto& RunInfo = cfg.RunInfo;
ParseCommandLine(argc, argv, cfg);
DisplayVersionInfo();
设置默认参数,然后我们看一下config配置了啥
/*************************************************************************//*!
\brief Whole configuration file
*****************************************************************************/
AL_INTROSPECT(category = "debug") struct ConfigFile
{
// \brief YUV input file name(s) 输入文件名
std::string YUVFileName;
// \brief Output bitstream file name 输出文件名字
std::string BitstreamFileName;
// \brief Reconstructed YUV output file name
std::string RecFileName;
// \brief Name of the file specifying the frame numbers where scene changes
// happen 命令行配置文件
std::string sCmdFileName;
// \brief Name of the file specifying the region of interest per frame is specified
// happen
std::string sRoiFileName;
// \brief Folder where qp tables files are located, if load qp enabled.
std::string sQPTablesFolder;
#if AL_ENABLE_TWOPASS
// \brief Name of the file that reads/writes video statistics for TwoPassMode
std::string sTwoPassFileName;
#endif
// \brief Information relative to YUV input file (from section INPUT) 文件信息
TYUVFileInfo FileInfo;
// \brief FOURCC Code of the reconstructed picture output file
TFourCC RecFourCC;//fourcc code
// \brief Sections RATE_CONTROL and SETTINGS
AL_TEncSettings Settings;
// \brief Section RUN
TCfgRunInfo RunInfo;
// \brief control the strictness when parsing the configuration file
bool strict_mode;
};
其中runinfo如下:
typedef AL_INTROSPECT (category = "debug") struct tCfgRunInfo
{
bool bUseBoard;
SCHEDULER_TYPE iSchedulerType;
bool bLoop;//循环编码
int iMaxPict;//最大的编码帧数
unsigned int iFirstPict;//第一张帧数
unsigned int iScnChgLookAhead;
std::string sMd5Path;
int eVQDescr;
IpCtrlMode ipCtrlMode;
std::string logsFile = "";
bool trackDma = false;
bool printPictureType = false;
AL_64U uInputSleepInMilliseconds;
}TCfgRunInfo;
全局默认设置信息,配置信息见encode_example.cfg,说的比较清楚,这里把我自己觉得比较重要的放在里面:
void SetDefaults(ConfigFile& cfg)
{
cfg.BitstreamFileName = "Stream.bin";//默认输出stram.bin
cfg.RecFourCC = FOURCC(NULL);
AL_Settings_SetDefaults(&cfg.Settings);
cfg.FileInfo.FourCC = FOURCC(I420);
cfg.FileInfo.FrameRate = 0;//0fps
cfg.FileInfo.PictHeight = 0;//0 pixel height
cfg.FileInfo.PictWidth = 0;//0 width width
cfg.RunInfo.bUseBoard = true;
cfg.RunInfo.iSchedulerType = SCHEDULER_TYPE_MCU;//mcu 控制
# Loop : specifies whether the encoder should loop back to the beginning of the YUV input stream when it reaches the end of the file
cfg.RunInfo.bLoop = false;//关闭回环编码
cfg.RunInfo.iMaxPict = INT_MAX; // ALL
cfg.RunInfo.iFirstPict = 0;//第一张是0frame
cfg.RunInfo.iScnChgLookAhead = 3;
cfg.RunInfo.ipCtrlMode = IPCTRL_MODE_STANDARD;
cfg.RunInfo.uInputSleepInMilliseconds = 0;
cfg.strict_mode = false;
}
然后编码器设置信息
typedef AL_INTROSPECT (category = "debug") struct t_EncSettings
{
// Stream
AL_TEncChanParam tChParam[MAX_NUM_LAYER];
bool bEnableAUD;
bool bEnableFillerData;
uint32_t uEnableSEI;
AL_EAspectRatio eAspectRatio; /*!< specifies the display aspect ratio */
AL_EColourDescription eColourDescription;
AL_EScalingList eScalingList;
bool bDependentSlice;
bool bDisIntra;
bool bForceLoad;
int32_t iPrefetchLevel2;
uint16_t uClipHrzRange;
uint16_t uClipVrtRange;
AL_EQpCtrlMode eQpCtrlMode;
int NumView;
int NumLayer;
uint8_t ScalingList[4][6][64];
uint8_t SclFlag[4][6];
uint8_t DcCoeff[8];
uint8_t DcCoeffFlag[8];
bool bEnableWatchdog;
#if AL_ENABLE_TWOPASS
int LookAhead;
int TwoPass;
#endif
}AL_TEncSettings;
设置默认函数在下面,这里面的东西比较细,然后在一张配置文件里面讲的比较详细,然后直接在里面给注释好了,注意带#只能在脚本中用,这里不一样的
void AL_Settings_SetDefaults(AL_TEncSettings* pSettings)
{
assert(pSettings);
Rtos_Memset(pSettings, 0, sizeof(*pSettings));
# Width, Height: frame width/height in pixels
# width and height shall be multiple of 8 pixels
pSettings->tChParam[0].uWidth = 0;
pSettings->tChParam[0].uHeight = 0;
# Profile : specifies the standard/profile to which the bitstream conforms
# allowed values : AVC_BASELINE, AVC_MAIN, AVC_HIGH, AVC_HIGH10, AVC_HIGH_422,
# HEVC_MAIN, HEVC_MAIN10, HEVC_MAIN_422_10...
pSettings->tChParam[0].eProfile = AL_PROFILE_HEVC_MAIN;
pSettings->tChParam[0].uLevel = 51;//最高53,越高编码性能越好
pSettings->tChParam[0].uTier = 0; // MAIN_TIER
pSettings->tChParam[0].eOptions = AL_OPT_LF | AL_OPT_LF_X_SLICE | AL_OPT_LF_X_TILE;
pSettings->tChParam[0].eOptions |= AL_OPT_RDO_COST_MODE;
# BitDepth : specifies the bit depth of the luma and chroma samples in the encoded stream
# Format : FOURCC format of input file
# typical file formats : I420, I422, I0AL, I2AL...
# hardware supported formats : NV12, NV16, P010, P210... (depends of the hw ip)
pSettings->tChParam[0].ePicFormat = AL_420_8BITS;
pSettings->tChParam[0].uSrcBitDepth = 8;
# GopCtrlMode : specifies the Group Of Pictures configuration
# allowed values : DEFAULT_GOP, LOW_DELAY_P, LOW_DELAY_B, PYRAMIDAL_GOP
# default value : DEFAULT_GOP
pSettings->tChParam[0].tGopParam.eMode = AL_GOP_MODE_DEFAULT;
pSettings->tChParam[0].tGopParam.uFreqIDR = 0x7FFFFFFF;
# Gop.Length : GOP length in frames including the I picture. 0 = Intra only,这里比较重要,我需要说一下,h265视频压缩时可以只用帧内压缩,此时跟图像压缩一样了,这里就是设置编码多少幅图像再次用一张关键帧,设置为0就是关闭帧内压缩,这里最好用25,因为25帧人眼就看不出啥来,压缩图像还是0
#Gop.FreqIDR : minimum number of frames between two IDR pictures (IDR insertion depends on the position of the GOP boundary)
# allowed values : positive value or -1 to disable IDR insertion
pSettings->tChParam[0].tGopParam.uGopLength = 30;
pSettings->tChParam[0].tGopParam.eGdrMode = AL_GDR_OFF;
AL_Settings_SetDefaultRCParam(&pSettings->tChParam[0].tRCParam);
pSettings->tChParam[0].iTcOffset = -1;
pSettings->tChParam[0].iBetaOffset = -1;
pSettings->tChParam[0].eColorSpace = UNKNOWN;
# NumSlices : number of row-based slices used for each frame
pSettings->tChParam[0].uNumCore = NUMCORE_AUTO;
pSettings->tChParam[0].uNumSlices = 1;
pSettings->uEnableSEI = SEI_NONE;
pSettings->bEnableAUD = true;
pSettings->bEnableFillerData = true;
pSettings->eAspectRatio = AL_ASPECT_RATIO_AUTO;
pSettings->eColourDescription = COLOUR_DESC_BT_470_PAL;
# QPCtrlMode : specifies how to generate the QP per coding unit
# allowed values : UNIFORM_QP, AUTO_QP, LOAD_QP, LOAD_QP | RELATIVE_QP
# default value : UNIFORM_QP
pSettings->eQpCtrlMode = UNIFORM_QP;// ADAPTIVE_AUTO_QP;
pSettings->tChParam[0].eLdaCtrlMode = AUTO_LDA;
pSettings->eScalingList = AL_SCL_DEFAULT;
pSettings->bForceLoad = true;
pSettings->tChParam[0].pMeRange[SLICE_P][0] = -1; // Horz
pSettings->tChParam[0].pMeRange[SLICE_P][1] = -1; // Vert
pSettings->tChParam[0].pMeRange[SLICE_B][0] = -1; // Horz
pSettings->tChParam[0].pMeRange[SLICE_B][1] = -1; // Vert
pSettings->tChParam[0].uMaxCuSize = 5; // 32x32
pSettings->tChParam[0].uMinCuSize = 3; // 8x8
pSettings->tChParam[0].uMaxTuSize = 5; // 32x32
pSettings->tChParam[0].uMinTuSize = 2; // 4x4
pSettings->tChParam[0].uMaxTransfoDepthIntra = 1;
pSettings->tChParam[0].uMaxTransfoDepthInter = 1;
pSettings->NumLayer = 1;
pSettings->NumView = 1;
pSettings->tChParam[0].eEntropyMode = AL_MODE_CABAC;
pSettings->tChParam[0].eWPMode = AL_WP_DEFAULT;
pSettings->tChParam[0].eSrcMode = AL_SRC_NVX;
#if AL_ENABLE_TWOPASS
pSettings->LookAhead = 0;
pSettings->TwoPass = 0;
#endif
pSettings->tChParam[0].eVideoMode = AL_VM_PROGRESSIVE;
}
插曲:
我们会发现会有大量篇幅在整这个FOURCC,这到底是个啥?
FourCC全称Four-Character Codes,代表四字符代码 (four character code), 它是一个32位的标示符,其实就是typedef unsigned int FOURCC;是一种独立标示视频数据流格式的四字符代码。
ok看一下定义
typedef uint32_t TFourCC;
#define FOURCC(A) ((TFourCC)(((uint32_t)((# A)[0])) \
| ((uint32_t)((# A)[1]) << 8) \
| ((uint32_t)((# A)[2]) << 16) \
| ((uint32_t)((# A)[3]) << 24)))
实际就是把字符串转化为32位~
继续
然后把几个重要的信息给拿出来了,引用一下,然后后面修改
auto& FileInfo = cfg.FileInfo;//编码的文件信息,主要时宽、高、然后大小,我呢见编码yuv是nv12还是16等等
auto& Settings = cfg.Settings;//编码配置信息,这个解析命令行的cfg文件,不然用默认的配置
auto& StreamFileName = cfg.BitstreamFileName;//输出文件名
auto& RecFileName = cfg.RecFileName;//记录文件名
auto& RunInfo = cfg.RunInfo;//运行信息
设置完默认信息之后开始解析命令行以及配置文件,解析cfg文件最主要在下面
if(g_Verbosity)
cerr << warning.str();
if(cfg.FileInfo.PictWidth > UINT16_MAX)
throw runtime_error("Unsupported picture width value");
if(cfg.FileInfo.PictHeight > UINT16_MAX)
throw runtime_error("Unsupported picture height value");
//设置编码图像宽高
AL_SetSrcWidth(&cfg.Settings.tChParam[0], cfg.FileInfo.PictWidth);
AL_SetSrcHeight(&cfg.Settings.tChParam[0], cfg.FileInfo.PictHeight);
//设置编码的图像位宽、格式等
if(ipbitdepth != -1)
{
AL_SET_BITDEPTH(cfg.Settings.tChParam[0].ePicFormat, ipbitdepth);
}
cfg.Settings.tChParam[0].uSrcBitDepth = AL_GET_BITDEPTH(cfg.Settings.tChParam[0].ePicFormat);
if(AL_IS_STILL_PROFILE(cfg.Settings.tChParam[0].eProfile))
cfg.RunInfo.iMaxPict = 1;
然后就是打印版本信息
然后设置默认setting参数,然后还是整cfg文件哪些东西
ok到此文件信息配置完毕,接下来用编码器编码了
首先获取编码器的控制信息
function<AL_TIpCtrl* (AL_TIpCtrl*)> wrapIpCtrl = GetIpCtrlWrapper(RunInfo);
auto pIpDevice = CreateIpDevice(!RunInfo.bUseBoard, RunInfo.iSchedulerType, Settings, wrapIpCtrl, RunInfo.trackDma, RunInfo.eVQDescr);
if(!pIpDevice)
throw runtime_error("Can't create IpDevice");
首先看一下function是个啥,这里给一个例子:
#include
#include
int f(int a, int b)
{
return a+b;
}
int main()
{
std::function<int(int, int)>func = f;
cout<<func(1, 2)<<endl; // 3
system("pause");
return 0;
}
ok,function是一个通用的多态函数包装器。 std :: function的实例可以存储,复制和调用任何可调用的目标 :包括函数,lambda表达式,绑定表达式或其他函数对象,以及指向成员函数和指向数据成员的指针。
也就是说上面的wrapIpCtrl是一个函数,可以直接用,然后其参数是一个指针,指向一个指针,AL_TIpCtrl* (AL_TIpCtrl*),打开后面的获取函数来看一下:
function<AL_TIpCtrl* (AL_TIpCtrl*)> GetIpCtrlWrapper(TCfgRunInfo& RunInfo)
{
function<AL_TIpCtrl* (AL_TIpCtrl*)> wrapIpCtrl;
switch(RunInfo.ipCtrlMode)
{
default:
//这里是一个lambda表达式
wrapIpCtrl = [](AL_TIpCtrl* ipCtrl) -> AL_TIpCtrl*
{
return ipCtrl;
};
break;
}
return wrapIpCtrl;
}
上面其实一个lamba表达式,看一下c++lambda表达式定义形式:
*[函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体}*
也就是说返回类型是AL_TIpCtrl*,函数参数是AL_TIpCtrl* ipCtrl,那其实就是这样的:
warpIpCtrl(param a){
return a;
}
真绕啊~
然后下面就是创建ip实例了:
shared_ptr<CIpDevice> CreateIpDevice(bool bUseRefSoftware, int iSchedulerType, AL_TEncSettings& Settings, function<AL_TIpCtrl* (AL_TIpCtrl*)> wrapIpCtrl, bool trackDma, int eVqDescr)
{
(void)bUseRefSoftware, (void)Settings, (void)wrapIpCtrl, (void)eVqDescr, (void)trackDma;
//wc你就这一种不直接默认得了???
if(iSchedulerType == SCHEDULER_TYPE_MCU)
return createMcuIpDevice();
throw runtime_error("No support for this scheduling type");
}
费劲。。。再接着走:
static unique_ptr<CIpDevice> createMcuIpDevice()
{
auto device = make_unique<CIpDevice>();
//设备创建dma区域,这里是一个智能指针,然后在这里reset并初始化,源码不解释了
device->m_pAllocator.reset(createDmaAllocator("/dev/allegroIP"), &AL_Allocator_Destroy);
if(!device->m_pAllocator)
throw runtime_error("Can't open DMA allocator");
//这里创建mcu调度器,这里讲一下
device->m_pScheduler = AL_SchedulerMcu_Create(AL_GetHardwareDriver(), device->m_pAllocator.get());
if(!device->m_pScheduler)
throw std::runtime_error("Failed to create MCU scheduler");
return device;
}
展开创建mcu调度器函数
static const TSchedulerVtable McuSchedulerVtable =
{
&destroy,
&createChannel,
&destroyChannel,
&encodeOneFrame,
&putStreamBuffer,
&getRecPicture,
&releaseRecPicture,
};
TScheduler* AL_SchedulerMcu_Create(AL_TDriver* driver, AL_TAllocator* pDmaAllocator)
{
AL_TSchedulerMcu* scheduler = Rtos_Malloc(sizeof(*scheduler));
if(!scheduler)
return NULL;
scheduler->vtable = &McuSchedulerVtable;
scheduler->driver = driver;
scheduler->allocator = pDmaAllocator;
return (TScheduler*)scheduler;
}
这里的rtos_malloc其实还是封装了标准库:
void* Rtos_Malloc(size_t zSize)
{
return malloc(zSize);
}
然后这里的driver就是输入我们打开的ip,就是我们底层的ip控制层了,dma暂时先不研究~
static AL_DriverVtable hardwareDriverVtable =
{
&Open,
&Close,
&PostMessage,
};
static AL_TDriver hardwareDriver =
{
&hardwareDriverVtable
};
AL_TDriver* AL_GetHardwareDriver()
{
return &hardwareDriver;
}
所以这里需要画个图~
其中vtable其实就是调用driver发送消息给mcu进行编解码,然后这里看一个createChannel先
static AL_ERR createChannel(AL_HANDLE* hChannel, TScheduler* pScheduler, AL_TEncChanParam* pChParam, TMemDesc* pEP1, AL_TISchedulerCallBacks* pCBs)
{
AL_ERR errorCode = AL_ERROR;
AL_TSchedulerMcu* schedulerMcu = (AL_TSchedulerMcu*)pScheduler;
Channel* chan = Rtos_Malloc(sizeof(*chan));
if(!chan)
{
errorCode = AL_ERR_NO_MEMORY;
goto channel_creation_fail;
}
Rtos_Memset(chan, 0, sizeof(*chan));
chan->driver = schedulerMcu->driver;
chan->fd = AL_Driver_Open(chan->driver, deviceFile);
if(chan->fd < 0)
{
perror("Can't open driver");
goto driver_open_fail;
}
struct al5_channel_config msg = { 0 };
setChannelParam(&msg.param, pChParam, pEP1);
chan->outputRec = pChParam->eOptions & AL_OPT_FORCE_REC;
AL_EDriverError errdrv = AL_Driver_PostMessage(chan->driver, chan->fd, AL_MCU_CONFIG_CHANNEL, &msg);
if(errdrv != DRIVER_SUCCESS)
{
if(errdrv == DRIVER_ERROR_NO_MEMORY)
errorCode = AL_ERR_NO_MEMORY;
/* the ioctl might not have been called at all,
* so the error_code might no be set. leave it to AL_ERROR in this case */
if((errdrv == DRIVER_ERROR_CHANNEL) && (msg.status.error_code != 0))
errorCode = msg.status.error_code;
goto fail;
}
assert(msg.status.error_code == 0);
setChannelFeedback(pChParam, &msg.status);
setCallbacks(chan, pCBs);
chan->shouldContinue = 1;
chan->thread = Rtos_CreateThread(&WaitForStatus, chan);
if(!chan->thread)
goto fail;
SetChannelInfo(&chan->info, pChParam);
*hChannel = (AL_HANDLE)chan;
return AL_SUCCESS;
fail:
AL_Driver_Close(schedulerMcu->driver, chan->fd);
driver_open_fail:
Rtos_Free(chan);
channel_creation_fail:
*hChannel = AL_INVALID_CHANNEL;
return errorCode;
}
其中的AL_TEncChanParam是分配的物理地址以及转化的虚拟地址
注意重点来了
设置通道信息,这里是将信息传入到msg结构体中
void setChannelParam(struct al5_params* msg, AL_TEncChanParam* pChParam, TMemDesc* pEP1)
{
static_assert(sizeof(*pChParam) <= sizeof(msg->opaque_params), "Driver channel_param struct is too small");
msg->size = 0;//这里size=0说明是msg消息刚开始
write(msg, pChParam, sizeof(*pChParam));//写入msg中,
uint32_t uEp1VirtAddr = 0;//虚拟地址
if(pEP1)
uEp1VirtAddr = pEP1->uPhysicalAddr + DCACHE_OFFSET;//虚拟地址
write(msg, &uEp1VirtAddr, sizeof(uEp1VirtAddr));
}
把消息传入,msg结构体如下:
typedef AL_INTROSPECT (category = "debug") struct __AL_ALIGNED__ (4) AL_t_EncChanParam
{
int iLayerID;
/* Encoding resolution */
uint16_t uWidth;
uint16_t uHeight;
AL_EVideoMode eVideoMode;
/* Encoding picture format */
AL_EPicFormat ePicFormat;
AL_EColorSpace eColorSpace;
AL_ESrcMode eSrcMode;
/* Input picture bitdepth */
uint8_t uSrcBitDepth;
/* encoding profile/level */
AL_EProfile eProfile;
uint8_t uLevel;
uint8_t uTier;
uint32_t uSpsParam;
uint32_t uPpsParam;
/* Encoding tools parameters */
AL_EChEncOption eOptions;
int8_t iBetaOffset;
int8_t iTcOffset;
int8_t iCbSliceQpOffset;
int8_t iCrSliceQpOffset;
int8_t iCbPicQpOffset;
int8_t iCrPicQpOffset;
uint8_t uCuQPDeltaDepth;
uint8_t uCabacInitIdc;
uint8_t uNumCore;
uint16_t uSliceSize;
uint16_t uNumSlices;
/* L2 prefetch parameters */
uint32_t uL2PrefetchMemOffset;
uint32_t uL2PrefetchMemSize;
uint16_t uClipHrzRange;
uint16_t uClipVrtRange;
/* MV range */
int16_t pMeRange[2][2]; /*!< Allowed range for motion estimation */
/* encoding block size */
uint8_t uMaxCuSize;
uint8_t uMinCuSize;
uint8_t uMaxTuSize;
uint8_t uMinTuSize;
uint8_t uMaxTransfoDepthIntra;
uint8_t uMaxTransfoDepthInter;
// For AVC
AL_EEntropyMode eEntropyMode;
AL_EWPMode eWPMode;
/* Gop & Rate control parameters */
AL_TRCParam tRCParam;
AL_TGopParam tGopParam;
bool bSubframeLatency;
AL_ELdaCtrlMode eLdaCtrlMode;
} AL_TEncChanParam;
也就是说我们配置的时候也要把这个结构体写进入,然后写入虚拟地址
继续
创建互斥锁以及条件变量
AL_EVENT Rtos_CreateEvent(bool bInitialState)
{
evt_t* pEvt = (evt_t*)Rtos_Malloc(sizeof(evt_t));
if(pEvt)
{
pthread_mutex_init(&pEvt->Mutex, 0);
pthread_cond_init(&pEvt->Cond, 0);
pEvt->bSignaled = bInitialState;
}
return (AL_EVENT)pEvt;
}
emm比较简单,自己看吧
然后创建销毁~,这里不太一样,有用到了lambda表达式
auto scopeMutex = scopeExit([&]() {
Rtos_DeleteEvent(hFinished);
});
输入是一个全局变量的引用,也就是hFinished,然后把lambda表达式传输scopeExit中,看一下这个函数
template<typename Lambda>
class ScopeExitClass
{
public:
ScopeExitClass(Lambda fn) : m_fn(fn)
{
}
~ScopeExitClass()
{
m_fn();
}
private:
Lambda m_fn;
};
创建一个类,并把传入的函数设置为私有函数,也就时说scopeMutex是个类,然后他的rtos_DetleEvent(hFinshed)是自己的私有函数,ok浸提你先到这里
字数太多了,转下一篇