概述
(1)Sensor Drv做Flip + Mirror效果
这里列举几个示例(会影响所有输出流)
path: /kernel-4.19/drivers/misc/mediatek/imgsensor/src/mtxxxx/
//(1)gc8034mipi_Sensor.h和gc8034mipi_Sensor.c
//用于在gc8034mipi_Sensor.c来修改寄存器的值
#define GC8034_MIRROR_NORMAL
#undef GC8034_MIRROR_H
#undef GC8034_MIRROR_V
#undef GC8034_MIRROR_HV
#if defined(GC8034_MIRROR_NORMAL)
#define GC8034_MIRROR 0xc0
#define GC8034_BinStartY 0x04
#define GC8034_BinStartX 0x05
#define GC8034_FullStartY 0x08
#define GC8034_FullStartX 0x09
#elif defined(GC8034_MIRROR_H)
#define GC8034_MIRROR 0xc1
#define GC8034_BinStartY 0x04
#define GC8034_BinStartX 0x05
#define GC8034_FullStartY 0x08
#define GC8034_FullStartX 0x0b
#elif defined(GC8034_MIRROR_V)
#define GC8034_MIRROR 0xc2
#define GC8034_BinStartY 0x04
#define GC8034_BinStartX 0x05
#define GC8034_FullStartY 0x08
#define GC8034_FullStartX 0x09
#elif defined(GC8034_MIRROR_HV)
#define GC8034_MIRROR 0xc3
#define GC8034_BinStartY 0x04
#define GC8034_BinStartX 0x05
#define GC8034_FullStartY 0x08
#define GC8034_FullStartX 0x0b
#else
#define GC8034_MIRROR 0xc0
#define GC8034_BinStartY 0x04
#define GC8034_BinStartX 0x05
#define GC8034_FullStartY 0x08
#define GC8034_FullStartX 0x09
#endif
//(2)sc500csmipi_Sensor.h和sc500csmipi_Sensor.c
/* SENSOR MIRROR FLIP INFO */
#define SC500CS_MIRROR_FLIP_ENABLE 0
#if SC500CS_MIRROR_FLIP_ENABLE
#define SC500CS_MIRROR 0x66 //mirror&flip
#else
#define SC500CS_MIRROR 0x00 //normal
#endif
//(3)gc08a3mipi_Sensor.h和gc08a3mipi_Sensor.c
/* SENSOR MIRROR FLIP INFO */
#define GC08A3_MIRROR_NORMAL 0
#define GC08A3_MIRROR_H 0
#define GC08A3_MIRROR_V 0
#define GC08A3_MIRROR_HV 1
#if GC08A3_MIRROR_NORMAL
#define GC08A3_MIRROR 0x00
#elif GC08A3_MIRROR_H
#define GC08A3_MIRROR 0x01
#elif GC08A3_MIRROR_V
#define GC08A3_MIRROR 0x02
#elif GC08A3_MIRROR_HV
#define GC08A3_MIRROR 0x03
#else
#define GC08A3_MIRROR 0x00
#endif
(2)API1在Framework做Flip+Mirror
//frameworks/av/services/camera/libcameraservice/api1/client2/Parameters.cpp
previewTransform = degToTransform(0, cameraFacing == CAMERA_FACING_FRONT);
int Parameters::degToTransform(int degrees, bool mirror) {
if (!mirror) {
if (degrees == 0) return 0;
else if (degrees == 90) return HAL_TRANSFORM_ROT_90;
else if (degrees == 180) return HAL_TRANSFORM_ROT_180;
else if (degrees == 270) return HAL_TRANSFORM_ROT_270;
} else { // Do mirror (horizontal flip)
if (degrees == 0) { // FLIP_H and ROT_0
return HAL_TRANSFORM_FLIP_H;
} else if (degrees == 90) { // FLIP_H and ROT_90
return HAL_TRANSFORM_FLIP_H | HAL_TRANSFORM_ROT_90;
} else if (degrees == 180) { // FLIP_H and ROT_180
return HAL_TRANSFORM_FLIP_V;
} else if (degrees == 270) { // FLIP_H and ROT_270
return HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_ROT_90;
}
}
ALOGE("%s: Bad input: %d", __FUNCTION__, degrees);
return -1;
}
(3)API2在Framework做Flip+ Mirror
//frameworks/av/camera/CameraUtils.cpp
bool mirror = (entryFacing.data.u8[0] == ANDROID_LENS_FACING_FRONT);
int orientation = entry.data.i32[0];
ALOGE("%s: dxf_getRotationTransform : mirror = %d , orientation = %d", __FUNCTION__ , mirror , orientation);
if (!mirror) {
switch (orientation) {
case 0:
flags = 0;
break;
case 90:
flags = NATIVE_WINDOW_TRANSFORM_ROT_90;
break;
case 180:
flags = NATIVE_WINDOW_TRANSFORM_ROT_180;
break;
case 270:
flags = NATIVE_WINDOW_TRANSFORM_ROT_270;
break;
default:
ALOGE("%s: Invalid HAL android.sensor.orientation value: %d",
__FUNCTION__, orientation);
return INVALID_OPERATION;
}
} else {
// Front camera needs to be horizontally flipped for mirror-like behavior.
// Note: Flips are applied before rotates; using XOR here as some of these flags are
// composed in terms of other flip/rotation flags, and are not bitwise-ORable.
switch (orientation) {
case 0:
flags = NATIVE_WINDOW_TRANSFORM_FLIP_H;
break;
case 90:
flags = NATIVE_WINDOW_TRANSFORM_FLIP_H ^
NATIVE_WINDOW_TRANSFORM_ROT_270;
break;
case 180:
flags = NATIVE_WINDOW_TRANSFORM_FLIP_H ^
NATIVE_WINDOW_TRANSFORM_ROT_180;
break;
case 270:
flags = NATIVE_WINDOW_TRANSFORM_FLIP_H ^
NATIVE_WINDOW_TRANSFORM_ROT_90;
break;
default:
ALOGE("%s: Invalid HAL android.sensor.orientation value: %d",
__FUNCTION__, orientation);
return INVALID_OPERATION;
}
}
(4)JpegNode对大图做旋转和Flip操作
这里旋转有如下几种方式,可进行相互组合。
//vendor/mediatek/proprietary/hardware/mtkcam/include/mtkcam/def/ImageFormat.h
enum
{
/* do not rotate or flip image */
eTransform_None = 0x00,
/* flip source image horizontally (around the vertical axis) */ 水平翻转
eTransform_FLIP_H = 0x01,
/* flip source image vertically (around the horizontal axis)*/ 垂直翻转
eTransform_FLIP_V = 0x02,
/* rotate source image 90 degrees clockwise */ 顺时针90
eTransform_ROT_90 = 0x04,
/* rotate source image 180 degrees */ 倒置180
eTransform_ROT_180 = 0x03,
/* rotate source image 270 degrees clockwise */ 顺时针270
eTransform_ROT_270 = 0x07,
};
enum EImageFormat
{
eImgFmt_IMPLEMENTATION_DEFINED = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
eImgFmt_RAW16 = HAL_PIXEL_FORMAT_RAW16, /*!< raw 16-bit 1 plane */
eImgFmt_RAW_OPAQUE = HAL_PIXEL_FORMAT_RAW_OPAQUE, /*!< raw 1 plane */
/*
* This format is used to carry task-specific data which does not have a
* standard image structure. The details of the format are left to the two
* endpoints.
*
* Buffers of this format must have a height of 1, and width equal to their
* size in bytes.
*/
eImgFmt_BLOB = HAL_PIXEL_FORMAT_BLOB,
eImgFmt_RGBA8888 = HAL_PIXEL_FORMAT_RGBA_8888, /*!< RGBA (32-bit; LSB:R, MSB:A), 1 plane */
eImgFmt_RGBX8888 = HAL_PIXEL_FORMAT_RGBX_8888, /*!< RGBX (32-bit; LSB:R, MSB:X), 1 plane */
eImgFmt_RGB888 = HAL_PIXEL_FORMAT_RGB_888, /*!< RGB 888 (24-bit), 1 plane (RGB) */
eImgFmt_RGB565 = HAL_PIXEL_FORMAT_RGB_565, /*!< RGB 565 (16-bit), 1 plane */
eImgFmt_BGRA8888 = HAL_PIXEL_FORMAT_BGRA_8888, /*!< BGRA (32-bit; LSB:B, MSB:A), 1 plane */
eImgFmt_YUY2 = HAL_PIXEL_FORMAT_YCbCr_422_I, /*!< 422 format, 1 plane (YUYV) */
eImgFmt_NV16 = HAL_PIXEL_FORMAT_YCbCr_422_SP, /*!< 422 format, 2 plane (Y),(UV) */
eImgFmt_NV21 = HAL_PIXEL_FORMAT_YCrCb_420_SP, /*!< 420 format, 2 plane (Y),(VU) */
eImgFmt_NV12 = HAL_PIXEL_FORMAT_NV12, /*!< 420 format, 2 plane (Y),(UV) */
eImgFmt_YV12 = HAL_PIXEL_FORMAT_YV12, /*!< 420 format, 3 plane (Y),(V),(U) */
eImgFmt_Y8 = HAL_PIXEL_FORMAT_Y8, /*!< 8-bit Y plane */
eImgFmt_Y800 = eImgFmt_Y8, /*!< deprecated; Replace it with eImgFmt_Y8 */
eImgFmt_Y16 = HAL_PIXEL_FORMAT_Y16, /*!< 16-bit Y plane */
eImgFmt_CAMERA_OPAQUE = HAL_PIXEL_FORMAT_CAMERA_OPAQUE, /*!< Opaque format, RAW10 + Metadata */
eImgFmt_YUV_P010 = HAL_PIXEL_FORMAT_YCBCR_P010, /*!< 420 format, 16bit, 2 plane (Y),(UV) = P010 */
//...
}
(A)对Jpeg大图做旋转
//vendor/mediatek/proprietary/hardware/mtkcam3/pipeline/hwnode/JpegNode/v2.0/JpegNode.cpp
JpegNodeImp::
encodeMainJpeg(
sp<encode_frame>& pEncodeFrame)
{
MINT8 iEncTypeCheck = isHwEncodeSupported(pEncodeFrame->mpYUV_Main->getImgFormat()) ?
NSSImager::JPEGENC_HW_ONLY : NSSImager::JPEGENC_SW;
my_encode_params params;
params.pSrc = pEncodeFrame->mpYUV_Main.get();
params.pDst = pEncodeFrame->mpJpeg_Main.get();
params.transform = 0; //transform——用来处理翻转问题
params.crop = MRect(MPoint(0,0), pEncodeFrame->mpYUV_Main->getImgSize()); //crop
params.isSOI = (!pEncodeFrame->mbSkipParseMeta) ? 0 : 1;
params.quality = pEncodeFrame->mParams.quality;
params.bsOffset = pEncodeFrame->exif.getHeaderSize();
if(pEncodeFrame->miJpegEncType != -1){
iEncTypeCheck = pEncodeFrame->miJpegEncType;
}
if (pEncodeFrame->muJpegSwMode > 0)
{
iEncTypeCheck = NSSImager::JPEGENC_SW;
}
params.codecType = iEncTypeCheck;
//
MERROR const err = hardwareOps_encode(params);
if( err != OK ) {
pEncodeFrame->mbSuccess = MFALSE;
}
pEncodeFrame->mpOutImgBufferHeap->setBitstreamSize(
params.pDst->getBitstreamSize());
}
params.transform = 0 //不做任何处理
params.transform = eTransform_FLIP_H //水平翻转,相当于镜像
params.transform = eTransform_FLIP_V //垂直翻转,相当于上下翻转
params.transform = eTransform_ROT_90 //顺时针旋转90
params.transform = eTransform_ROT_180 //顺时针旋转180
params.transform = eTransform_FLIP_H | eTransform_FLIP_V //水平+垂直翻转 == eTransform_ROT_180
(B)对Jpeg大图做Flip
//vendor/mediatek/proprietary/hardware/mtkcam3/pipeline/hwnode/JpegNode/v2.0/JpegNode.cpp
MVOID
Request::
getJpegParams(
IMetadata* pMetadata_request,
jpeg_params& rParams
) const
{
if( NULL == pMetadata_request)
{
MY_LOGE("pMetadata_request=NULL");
return;
}
rParams.gpsCoordinates =
pMetadata_request->entryFor(MTK_JPEG_GPS_COORDINATES);
rParams.gpsProcessingMethod =
pMetadata_request->entryFor(MTK_JPEG_GPS_PROCESSING_METHOD);
rParams.gpsTimestamp =
pMetadata_request->entryFor(MTK_JPEG_GPS_TIMESTAMP);
#define getParam(meta, tag, type, param, needWarn)
do {
if( !tryGetMetadata<type>(meta, tag, param) ) {
if (needWarn) MY_LOGW("no tag: %s", #tag);
else MY_LOGW_IF( mInfo.mLogLevel,"no tag: %s", #tag);
}
} while(0)
#define getAppParam(tag, type, param ,needWarn) getParam(pMetadata_request, tag, type, param, needWarn)
// request from app
getAppParam(MTK_JPEG_ORIENTATION , MINT32, rParams.orientation, MTRUE);
MY_LOGD("orientation:%d", rParams.orientation);
getAppParam(MTK_JPEG_QUALITY , MUINT8, rParams.quality, MTRUE);
getAppParam(MTK_JPEG_THUMBNAIL_QUALITY, MUINT8, rParams.quality_thumbnail, MTRUE);
getAppParam(MTK_JPEG_THUMBNAIL_SIZE , MSize , rParams.size_thumbnail, MTRUE);
getAppParam(MTK_SCALER_CROP_REGION , MRect , rParams.cropRegion, MTRUE);
//Flip动作
getAppParam(MTK_CONTROL_CAPTURE_JPEG_FLIP_MODE , MINT32, rParams.flipMode, MFALSE);
getAppParam(MTK_VSDOF_FEATURE_REFOCUS_CAPTURE_FLOW , MINT32, rParams.pakType, MTRUE);
#undef getAppParam
#undef getParam
}
(5)JpegNode对Thumbnail做旋转和Flip操作
//vendor/mediatek/proprietary/hardware/mtkcam3/pipeline/hwnode/JpegNode/v2.0/JpegNode.cpp
MVOID
JpegNodeImp::
encodeThumbnailJpeg(
sp<encode_frame>& pEncodeFrame)
{
{
// set thread naming
pthread_setname_np(pthread_self(), THUMBTHREAD_NAME);
}
MSize thumbsize = pEncodeFrame->mParams.size_thumbnail;
MUINT32 transform = (pEncodeFrame->mpYUV_MainStreamInfo.get()) ? pEncodeFrame->mpYUV_MainStreamInfo->getTransform() : 0;
// do encode
{
my_encode_params params;
params.frameNo = pEncodeFrame->mpFrame->getFrameNo();
params.requestNo = pEncodeFrame->mpFrame->getRequestNo();
params.pSrc = pEncodeFrame->mpYUV_Thumbnail.get();
params.pDst = pEncodeFrame->mpJpeg_Thumbnail.get();
if (pEncodeFrame->mParams.flipMode || info.mFlip) {
if( pEncodeFrame->mParams.orientation == 90 && transform & eTransform_ROT_90)
{
params.transform = eTransform_ROT_90 | eTransform_FLIP_V;
std::swap(thumbsize.w, thumbsize.h);
} else if (pEncodeFrame->mParams.orientation == 180 && transform & eTransform_ROT_180)
{
params.transform = eTransform_FLIP_V;
} else if (pEncodeFrame->mParams.orientation == 270 && transform & eTransform_ROT_270)
{
params.transform = eTransform_ROT_90 | eTransform_FLIP_H;
std::swap(thumbsize.w, thumbsize.h);
} else
{
params.transform = eTransform_FLIP_H;
}
#if MTKCAM_EARLY_FLIP_SUPPORT == 1
// has flipped in P2A, need to un-flip
auto doUnflip= [&](MUINT32& trans, MINT32 dvOri) -> MVOID {
if (dvOri == 270) {
MY_LOGD("dvOri %d, Capture vertical", dvOri);
trans |= eTransform_FLIP_V;
} else if (dvOri == 90) {
MY_LOGD("dvOri %d, Capture vertical", dvOri);
trans &= ~eTransform_FLIP_V;
} else if (dvOri == 180) {
MY_LOGD("dvOri %d, Capture horizontal",dvOri);
trans |= eTransform_FLIP_H;
} else if (dvOri == 0) {
MY_LOGD("dvOri %d, Capture horizontal",dvOri);
trans &= ~eTransform_FLIP_H;
}
};
if (pEncodeFrame->mParams.isFlipped) {
MY_LOGD("before trans 0x%" PRIx64, params.transform);
doUnflip(params.transform, pEncodeFrame->mParams.orientation);
MY_LOGD("after trans 0x%" PRIx64, params.transform);
}
#endif
} else {
if( pEncodeFrame->mParams.orientation == 90 && transform & eTransform_ROT_90)
{
params.transform = eTransform_ROT_90;
std::swap(thumbsize.w, thumbsize.h);
} else if (pEncodeFrame->mParams.orientation == 180 && transform & eTransform_ROT_180)
{
params.transform = eTransform_ROT_180;
} else if (pEncodeFrame->mParams.orientation == 270 && transform & eTransform_ROT_270)
{
params.transform = eTransform_ROT_270;
std::swap(thumbsize.w, thumbsize.h);
} else
{
params.transform = 0;
}
}
//...
}
(5)APP下Vendor Tag来实现拍照的Mirror(或setprop)
//vendor/mediatek/proprietary/hardware/mtkcam3/pipeline/policy/request/CaptureStreamUpdaterPolicy.cpp
static auto createRotationStreamInfoLocked_Main_YUV(
RequestOutputParams& out __unused,
RequestInputParams const& in __unused
) -> int
{
auto const pMetadata = in.pRequest_AppControl;
auto const pCfgMainYUV = *(in.pConfiguration_HalImage_Jpeg_YUV);
if ( CC_UNLIKELY( ! pMetadata || ! pCfgMainYUV.get() ) ) {
MY_LOGE("invlaid input params");
return NO_INIT;
}
IMetadata::IEntry const& entryJpegOrientation = pMetadata->entryFor(MTK_JPEG_ORIENTATION);
if ( entryJpegOrientation.isEmpty() ) {
MY_LOGW("No tag: MTK_JPEG_ORIENTATION");
return -EINVAL;
}
int32_t jpegFlip = 0;
//(1)通过Vendor Tag来设置Flip
IMetadata::IEntry const& entryJpegFlip = pMetadata->entryFor(MTK_CONTROL_CAPTURE_JPEG_FLIP_MODE);
if (entryJpegFlip.isEmpty()) {
MY_LOGD("No tag: MTK_CONTROL_CAPTURE_JPEG_FLIP_MODE");
} else {
jpegFlip = entryJpegFlip.itemAt(0, Type2Type<MINT32>());
}
//(2)也可下系统属性来实现Flip
int32_t jpegFlipProp = ::property_get_int32("vendor.debug.camera.Jpeg.flip", 0);
int32_t const jpegOrientation = (in.isSupportJpegPack) ? 0 :
entryJpegOrientation.itemAt(0, Type2Type<MINT32>());
uint32_t reqTransform = 0;
if (jpegFlip || jpegFlipProp) {
if ( 0==jpegOrientation )
reqTransform = eTransform_FLIP_H;
else if ( 90==jpegOrientation )
reqTransform = eTransform_ROT_90 | eTransform_FLIP_V;
else if ( 180==jpegOrientation )
reqTransform = eTransform_FLIP_V;
else if ( 270==jpegOrientation )
reqTransform = eTransform_ROT_90 | eTransform_FLIP_H;
else
MY_LOGW("Invalid Jpeg Orientation value: %d", jpegOrientation);
} else {
if ( 0==jpegOrientation )
reqTransform = 0;
else if ( 90==jpegOrientation )
reqTransform = eTransform_ROT_90;
else if ( 180==jpegOrientation )
reqTransform = eTransform_ROT_180;
else if ( 270==jpegOrientation )
reqTransform = eTransform_ROT_270;
else
MY_LOGW("Invalid Jpeg Orientation value: %d", jpegOrientation);
}
uint32_t const cfgTransform = pCfgMainYUV->getTransform();
MY_LOGD_IF( 1, "Jpeg orientation metadata: %d degrees; transform request(%d) & config(%d) & flip(%d) pack(%d)",
jpegOrientation, reqTransform, cfgTransform, jpegFlip, in.isSupportJpegPack);
//...
}
可在createRotationStreamInfoLocked_Main_YUV 函数中的if (jpegFlip || jpegFlipProp) 用于判断是否flip。
- 可用Meta data MTK_CONTROL_CAPTURE_JPEG_FLIP_MODE来控制Jpeg的镜像效果;
- 也可用adb设置属性vendor.debug.camera.Jpeg.flip 1 来debug拍照的Jpeg是否有镜像效果;
以上是拍照会走的Flow,当App设置如下Mirror的Vendor Tag后,拍照的Jpeg图片将有Flip效果。
App通过request来设置Flip
private static final String FLIP_KEY_MODE_REQUEST =
"com.mediatek.control.capture.flipmode"
if (value != null && captureBuilder != null) {
int[] mode = new int[1];
mode[0] = Integer.parseInt(value);
captureBuilder.set(mKeyMirrorRequestValue, mode);
}
(6)setprop来对录像视频和Vss照片进行Mirror(或App下Vendor Tag)
可参考如下这篇FAQ进行调试:
https://online.mediatek.com/QuickStart/QS00137#QSS01454
//vendor/mediatek/proprietary/hardware/mtkcam3/pipeline/model/session/PipelineModelSessionBase.cpp
auto
PipelineModelSessionBase::
submitRequest(
std::vector<std::shared_ptr<UserRequestParams>>const& requests,
uint32_t& numRequestProcessed
) -> int
{
//...
//Submit ParsedAppRequest one by one
for (size_t i = 0; i < reqs.size(); i++, numRequestProcessed++) {
//add mirror for video start
int SensorID = mStaticInfo.pPipelineStaticInfo->sensorId[i];
if(SensorID == 1){
auto const& pAppControl = reqs[i]->pAppMetaControlStreamBuffer;
IMetadata* pMetadata = pAppControl->tryReadLock(LOG_TAG);
if ( CC_UNLIKELY( ! pMetadata ) ) {
MY_LOGE("bad metadata(%p) SBuffer(%p)", pMetadata, pAppControl.get() );
pAppControl->unlock(LOG_TAG, pMetadata);
return -EINVAL;
}
MINT32 videoFlip = 0;
int32_t videoOrientation = 90;
//(1)通过Vendor Tag / setprop
//IMetadata::getEntry(pMetadata, MTK_CONTROL_CAPTURE_JPEG_FLIP_MODE, videoFlip);
int32_t videoFlip = ::property_get_int32("vendor.debug.camera.videocontrol.flip", 0);
int32_t videoOrientation = ::property_get_int32("vendor.debug.camera.videocontrol.orientation", 90);
pAppControl->unlock(LOG_TAG, pMetadata);
MY_LOGD("%s:videoFlip = %d videoOrientation = %dn",__FUNCTION__,videoFlip,videoOrientation);
auto OpRequest_AppImageStreamInfo = reqs[i]->pParsedAppImageStreamInfo.get();
for (auto & it : OpRequest_AppImageStreamInfo->vAppImage_Output_Proc) {
//(2)录像条件
if (it.second->getUsageForConsumer() & GRALLOC_USAGE_HW_VIDEO_ENCODER ){
uint32_t reqTransform = 0;
if (videoFlip) {
if ( 0 == videoOrientation ) {
reqTransform = eTransform_FLIP_H;
}else if ( 90 == videoOrientation ) {
reqTransform = eTransform_FLIP_V;
}else if ( 180 == videoOrientation ) {
reqTransform = eTransform_FLIP_H;
}else if ( 270 == videoOrientation ) {
reqTransform = eTransform_FLIP_V;}
}else {
MY_LOGW("Invalid videoOrientation value: %d", videoOrientation);
}
MY_LOGD("set video flip reqTransform: %d", reqTransform);
it.second->setTransform(reqTransform);
}
}
}
//add mirror for video end
RETURN_ERROR_IF_NOT_OK( submitOneRequest(reqs[i]),
"submitOneRequest fail on requestNo:%u - %u/%zu requests submitted sucessfully",
reqs[i]->requestNo, numRequestProcessed, reqs.size() );
}
return OK;
}
以上可对录像的视频和Vss的照片进行Flip动作。
最后
以上就是热心星月为你收集整理的Mtk Camera 对预览和拍照做Flip + Mirror的全部内容,希望文章能够帮你解决Mtk Camera 对预览和拍照做Flip + Mirror所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复