1 DMAI修改
DMAI(DaVinciMultimedia Application Interface)是一个搭建于操作系统(Linux或者DSP/BIOS)和Codec Engine之上的功能层,用来帮助用户快速地编写达芬奇平台上的可移植程序。在下图中可以看到,DMAI没有覆盖操作系统和Codec Engine,应用程序可以选择使用DMAI,也可以直接使用操作系统或Codec Engine。DMAI包含许多模块,例如framecopy,resize,ccv,buffergfx等。DMAI将实际的操作抽象了,用户只需调用功能模块而不必知道底层的实现过程。
由于OV5642输出视频格式比较特别,VGA分辨率(640x480),8帧每秒,颜 {MOD}空间为YUYUV422,数据位宽10bit,在DMAI中对这种输入不支持,需要手动修改代码。
对于DMAI的修改不是很复杂,主要包括以下几处修改。
该文件中定义了所有支持的输入格式,S-Video、复合视频和分量视频,需要添加对摄像头的定义。
typedef enum {
/** @brief S-Video video input */
Capture_Input_SVIDEO = 0,
/** @brief Composite video input */
Capture_Input_COMPOSITE,
/** @brief Component video input */
Capture_Input_COMPONENT,
/** @brief Component video input */
Capture_Input_CAMERA,
Capture_Input_COUNT
} Capture_Input;
该文件中定义了DMAI支持的像素空间格式,例如RGB、UYVY、YUV444等等,需要添加对摄像头输入像素格式的支持。
typedef enum {
ColorSpace_NOTSET = -1,
ColorSpace_YUV420PSEMI = 0,
ColorSpace_YUV422PSEMI,
ColorSpace_UYVY,
ColorSpace_RGB888,
ColorSpace_RGB565,
ColorSpace_2BIT,
ColorSpace_YUV420P,
ColorSpace_YUV422P,
ColorSpace_YUV444P,
ColorSpace_GRAY,
/* 10-bit camera YUYV 4:2:2 input, corresponding to V4L2_PIX_FMT_YUYV in v4l2 */
ColorSpace_YUV422_10bit,
ColorSpace_COUNT
} ColorSpace_Type;
该文件中有用于计算某种像素空间格式下每个像素点占用多少字节的函数,也需要添加对OV5642输入像素格式的支持。
Int ColorSpace_getBpp(ColorSpace_Type colorSpace)
{
Int bpp;
switch (colorSpace) {
case ColorSpace_RGB888:
case ColorSpace_YUV422_10bit:
bpp = 32;
break;
case ColorSpace_YUV444P:
bpp = 24;
break;
case ColorSpace_RGB565:
case ColorSpace_UYVY:
bpp = 16;
break;
case ColorSpace_GRAY:
case ColorSpace_YUV422P:
case ColorSpace_YUV420P:
case ColorSpace_YUV420PSEMI:
case ColorSpace_YUV422PSEMI:
bpp = 8;
break;
case ColorSpace_2BIT:
bpp = 2;
break;
default:
return Dmai_EINVAL;
}
return bpp;
}
该文件中定义了DMAI支持的视频格式,例如PAL、NTSC和720P等,需要添加对OV5642输入VGA视频格式的支持。
typedef enum {
VideoStd_AUTO = 0, /**< Automatically select standard (if supported) */
VideoStd_CIF, /**< CIF @ 30 frames per second */
VideoStd_SIF_NTSC, /**< SIF @ 30 frames per second */
VideoStd_SIF_PAL, /**< SIF @ 25 frames per second */
VideoStd_VGA, /**< VGA (640x480) @ 60 frames per second */
VideoStd_D1_NTSC, /**< D1 NTSC @ 30 frames per second */
VideoStd_D1_PAL, /**< D1 PAL @ 25 frames per second */
VideoStd_480P, /**< D1 Progressive NTSC @ 60 frames per second */
VideoStd_576P, /**< D1 Progressive PAL @ 50 frames per second */
VideoStd_720P_60, /**< 720P @ 60 frames per second */
VideoStd_720P_50, /**< 720P @ 50 frames per second */
VideoStd_720P_30, /**< 720P @ 30 frames per second */
VideoStd_1080I_30, /**< 1080I @ 30 frames per second */
VideoStd_1080I_25, /**< 1080I @ 25 frames per second */
VideoStd_1080P_30, /**< 1080P @ 30 frames per second */
VideoStd_1080P_25, /**< 1080P @ 25 frames per second */
VideoStd_1080P_24, /**< 1080P @ 24 frames per second */
VideoStd_1080P_60, /**< 1080P @ 60 frames per second */
VideoStd_1080P_50, /**< 1080P @ 50 frames per second */
VideoStd_VGA_15, /**< 480P @ 15 frame per second */
VideoStd_COUNT
} VideoStd_Type;
该文件中有获取某种视频格式下视频图像分辨率的函数,需要添加对OV5642输入VGA分辨率视频格式的支持。
Int VideoStd_getResolution(VideoStd_Type videoStd, Int32 *widthPtr, Int32 *heightPtr)
{
switch (videoStd) {
case VideoStd_CIF:
*widthPtr = VideoStd_CIF_WIDTH;
*heightPtr = VideoStd_CIF_HEIGHT;
break;
case VideoStd_SIF_NTSC:
*widthPtr = VideoStd_SIF_WIDTH;
*heightPtr = VideoStd_SIF_NTSC_HEIGHT;
break;
case VideoStd_SIF_PAL:
*widthPtr = VideoStd_SIF_WIDTH;
*heightPtr = VideoStd_SIF_PAL_HEIGHT;
break;
case VideoStd_VGA:
*widthPtr = VideoStd_VGA_WIDTH;
*heightPtr = VideoStd_VGA_HEIGHT;
break;
case VideoStd_D1_NTSC:
case VideoStd_480P:
*widthPtr = VideoStd_D1_WIDTH;
*heightPtr = VideoStd_D1_NTSC_HEIGHT;
break;
case VideoStd_D1_PAL:
case VideoStd_576P:
*widthPtr = VideoStd_D1_WIDTH;
*heightPtr = VideoStd_D1_PAL_HEIGHT;
break;
case VideoStd_720P_50:
case VideoStd_720P_60:
*widthPtr = VideoStd_720P_WIDTH;
*heightPtr = VideoStd_720P_HEIGHT;
break;
case VideoStd_1080I_25:
case VideoStd_1080I_30:
case VideoStd_1080P_24:
case VideoStd_1080P_25:
case VideoStd_1080P_30:
case VideoStd_1080P_60:
case VideoStd_1080P_50:
*widthPtr = VideoStd_1080I_WIDTH;
*heightPtr = VideoStd_1080I_HEIGHT;
break;
case VideoStd_VGA_15:
*widthPtr = VideoStd_VGA_WIDTH;
*heightPtr = VideoStd_VGA_HEIGHT;
break;
}
return Dmai_EOK;
}
该文件中有一个_Dmai_blackFill函数,用于对存储视频的缓冲区数据进行黑 {MOD}填充,这里也需要添加对OV5642输入颜 {MOD}空间的支持。
Void _Dmai_blackFill(Buffer_Handle hBuf)
{
switch (BufferGfx_getColorSpace(hBuf)) {
case ColorSpace_YUV422PSEMI:
{
Int8 *yPtr = Buffer_getUserPtr(hBuf);
Int32 ySize = Buffer_getSize(hBuf) / 2;
Int8 *cbcrPtr = yPtr + ySize;
Int bpp = ColorSpace_getBpp(ColorSpace_YUV422PSEMI);
Int y;
BufferGfx_Dimensions dim;
UInt32 offset;
BufferGfx_getDimensions(hBuf, &dim);
offset = dim.y * dim.lineLength + dim.x * bpp / 8;
for (y = 0; y < dim.height; y++) {
memset(yPtr + offset, 0x0, dim.width * bpp / 8);
yPtr += dim.lineLength;
}
for (y = 0; y < dim.height; y++) {
memset(cbcrPtr + offset, 0x80, dim.width * bpp / 8);
cbcrPtr += dim.lineLength;
}
break;
}
case ColorSpace_YUV420PSEMI:
{
Int8 *yPtr = Buffer_getUserPtr(hBuf);
Int32 ySize = Buffer_getSize(hBuf) * 2 / 3;
Int8 *cbcrPtr = yPtr + ySize;
Int bpp = ColorSpace_getBpp(ColorSpace_YUV420PSEMI);
Int y;
BufferGfx_Dimensions dim;
BufferGfx_getDimensions(hBuf, &dim);
yPtr += dim.y * dim.lineLength + dim.x * bpp / 8;
for (y = 0; y < dim.height; y++) {
memset(yPtr, 0x0, dim.width * bpp / 8);
yPtr += dim.lineLength;
}
cbcrPtr += dim.y * dim.lineLength / 2 + dim.x * bpp / 8;
for (y = 0; y < dim.height / 2; y++) {
memset(cbcrPtr, 0x80, dim.width * bpp / 8);
cbcrPtr += dim.lineLength;
}
break;
}
case ColorSpace_RGB565:
case ColorSpace_YUV422_10bit:
{
memset(Buffer_getUserPtr(hBuf), 0, Buffer_getSize(hBuf));
break;
}
}
}
这是DMAI中最核心的一个文件,用于创建Capture模块,创建过程中有许多对底层驱动的调用,这里要修改的比较多。
首先,添加对不同输入模式的支持。
static Char *captureInputString[Capture_Input_COUNT] = {
"S-Video",
"Composite",
"Component",
"camera"
};
static v4l2_std_id stds[VideoStd_COUNT] = { 0, 0, 0, 0, 0, V4L2_STD_NTSC,
V4L2_STD_PAL, V4L2_STD_525P_60, V4L2_STD_625P_50, V4L2_STD_720P_60,
V4L2_STD_720P_50, 0, V4L2_STD_1080I_60, V4L2_STD_1080I_50,
0, 0, 0, V4L2_STD_1080P_60, V4L2_STD_1080P_50,
V4L2_STD_CAMERA_VGA };
然后,设置sensor输入时传递给capture_creat函数的默认参数。
const Capture_Attrs Capture_Attrs_DM6467_CAMERA = {
3,
Capture_Input_CAMERA,
-1,
-1,
-1,
-1,
"/dev/video0",
FALSE,
VideoStd_VGA_15,
1,
ColorSpace_YUV422_10bit,,
NULL
};
其次,修改视频格式参数设置函数。
if (std == V4L2_STD_CAMERA_VGA) {
*videoStdPtr = VideoStd_VGA_15;
}
else if (std & V4L2_STD_NTSC) {
*videoStdPtr = VideoStd_D1_NTSC;
}
else if (std & V4L2_STD_PAL) {
*videoStdPtr = VideoStd_D1_PAL;
}
else if (std & V4L2_STD_525P_60) {
*videoStdPtr = VideoStd_480P;
}
else if (std & V4L2_STD_625P_50) {
*videoStdPtr = VideoStd_576P;
}
else if (std == V4L2_STD_720P_60) {
*videoStdPtr = VideoStd_720P_60;
}
else {
Dmai_err1("Unknown video standard on capture device %s
",
attrs->captureDevice);
return Dmai_EFAIL;
}
attrs->videoStd = *videoStdPtr;
Dmai_dbg2("Capture input set to %s:%d
", captureInputString[attrs->videoInput], *videoStdPtr);
修改完成后,在终端中切换到DVSDK目录,运行makedmai命令,如果没有error提示,那么编译通过,接下来可以开始进行encode demo视频编码程序修改。
要使用OV5642进行视频采集并对其编码,我们可以在DVSDK中提供的encode demo的基础上进行一定加工修改。对于encode demo的修改主要集中在Capture.c文件,修改的目的是提供标准的YUV420PSEMI格式视频图像给H264编码器进行编码。修改过程包括以下部分。
在该文件中,定义了在创建编码器时所需要的传递给编码器的参数,需要进行一定修改。
static IH264VENC_DynamicParams extDynParams = {
{
sizeof(IVIDENC1_DynamicParams), /* size */
480, /* inputHeight */
640, /* inputWidth */
30000, /* refFrameRate */
30000, /* targetFrameRate */
2000000, /* targetBitRate (override in app) */
30, /* intraFrameInterval */
XDM_DECODE_AU, /* generateHeader */
0, /* captureWidth */
IVIDEO_NA_FRAME, /* forceFrame */
1, /* interFrameInterval */
0 /* mbDataFlag */
},
18, /* QPISlice */
20, /* QPSlice */
51, /* RateCtrlQpMax */
0, /* RateCtrlQpMin */
0, /* NumRowsInSlice */
0, /* LfDisableIdc */
0, /* LFAlphaC0Offset */
0, /* LFBetaOffset */
0, /* ChromaQPOffset */
0, /* SecChromaQPOffset */
};
main.c中是整个程序的主函数,用于初始化各个线程。在main.c文件中需要修改程序运行时通过命令行传递给main函数的默认参数。
#define DEFAULT_ARGS
{ Sound_Input_MIC, NULL, NULL, NULL, NULL, NULL, NULL,
VideoStd_VGA_WIDTH, VideoStd_VGA_HEIGHT, -1, 64000, 44100,
FALSE, FALSE, 600/*FOREVER*/, FALSE, FALSE }
Video.c中主要的函数是videoThrFxn(),即视频编码线程。它负责对编码器进行初始化,然后进入主循环,从capture线程获取视频帧并编码,然后传递给writer线程进行视频存储或者网络传输。在video.c文件中,需要修改一些编码器初始化参数。
capture.c文件用于初始化capture线程,完成之后就循环从视频设备获取视频帧,经过颜 {MOD}空间转换之后传递给video线程进行编码。capture.c文件中修改比较多。
首先,修改创建capture模块时的默认参数。
Capture_Attrs cAttrs =
Capture_Attrs_DM6467_CAMERA;
然后,修改创建buffer时的一些参数设置,主要是设置buffer的大小。
/* Calculate the dimensions of a video standard given a color space */
if (BufferGfx_calcDimensions(
VideoStd_VGA_15, cAttrs.colorSpace, &gfxAttrs.dim) < 0) {
ERR("Failed to calculate Buffer dimensions
");
cleanup(THREAD_FAILURE);
}
/* Calculate buffer size needed of a video standard given a color space */
bufSize = BufferGfx_calcSize(
VideoStd_VGA_15, cAttrs.colorSpace); // 10-bit
/* Create a table of buffers to use with the device drivers */
gfxAttrs.colorSpace =
ColorSpace_YUV422_10bit;
hBufTab = BufTab_create(NUM_DRIVER_BUFS, bufSize,BufferGfx_getBufferAttrs(&gfxAttrs));
其次,需要添加将YUYV422转换为YUV420PSEMI颜 {MOD}空间格式的函数。
Void YUYV10_to_YUV420PSEMI8(Buffer_Handle hSrcBuf, Buffer_Handle hDstBuf)
{
BufferGfx_Dimensions srcDim;
BufferGfx_Dimensions dstDim;
UInt32 srcOffset, dstOffset;
Int8 *src, *dst_Y, *dst_UV;
Int i, j;
BufferGfx_getDimensions(hSrcBuf, &srcDim);
BufferGfx_getDimensions(hDstBuf, &dstDim);
assert((srcDim.x & 0x1) == 0);
assert((dstDim.x & 0x1) == 0);
srcOffset = srcDim.y * srcDim.lineLength + srcDim.x;
dstOffset = dstDim.y * dstDim.lineLength + dstDim.x;
src = Buffer_getUserPtr(hSrcBuf) + srcOffset;
dst_Y = Buffer_getUserPtr(hDstBuf) + dstOffset;
dst_UV = Buffer_getUserPtr(hDstBuf) + dstOffset + srcDim.width * srcDim.height;
if (dst_Y != src) {
for (i = 0; i < srcDim.height; i++) {
if (i % 2 == 1) {
for (j = 0; j < srcDim.width * 4; j += 8) {
*dst_Y = ((*((Int16 *)(src + 0))) / 4) & 0xff;
dst_Y ++;
*dst_Y = ((*((Int16 *)(src + 4))) / 4) & 0xff;
dst_Y ++;
*dst_UV = ((*((Int16 *)(src + 2))) / 4) & 0xff;
dst_UV ++;
*dst_UV = ((*((Int16 *)(src + 6))) / 4) & 0xff;
dst_UV ++;
src += 8;
}
}
else {
for (j = 0; j < srcDim.width * 4; j += 8) {
*dst_Y = ((*((Int16 *)(src + 0))) / 4) & 0xff;
dst_Y ++;
*dst_Y = ((*((Int16 *)(src + 4))) / 4) & 0xff;
dst_Y ++;
src += 8;
}
}
}
}
Buffer_setNumBytesUsed(hDstBuf, srcDim.width * srcDim.height * 3 / 2);
}
最后,屏蔽encode demo默认使用的ccv,使用自己编写的颜 {MOD}空间转换函数。
#if 0
/* Color convert the captured buffer from 422Psemi to 420Psemi */
if (Ccv_execute(hCcv, hCapBuf, hDstBuf) < 0) {
ERR("Failed to execute color conversion job
");
cleanup(THREAD_FAILURE);
}
#endif
/* convert the captured buffer from 10-bit to 8-bit */
YUYV10_to_YUV420PSEMI8(hCapBuf, hDstBuf);
修改完成之后,在终端中切换路径到DVSDK中,运行makedemos命令,如果没有error,表示编译通过。需要注意的是由于encode demo在编译时会调用DMAI中的API,所以一定要先编译DMAI再编译demos,否则修改的DMAI就没有起到左右。
将编译好的encode程序copy到文件系统中的/opt/dvsdk/dm6467目录下,系统上电,运行程序,查看效果。