三、V4L2的demo

capture.c是官方示例程序。

capture.c 程序中的 process_image 函数:

capture.c 程序主要是用来演示怎样使用 v4l2 接口,并没有对采集到的视频帧数据做任何实际的处理,仅仅用 process_image 函数表示了处理图像的代码位置。

process_image 函数只有一个参数,就是存储视频帧的内存的地址指针,但是在真正的应用中,通常还需要知道该指针指向的数据的大小。

因此可以修改函数,改成 void process_image ( const void * p, int len ) ,但是每次调用 process_image 的时候,第 2 个参数该传递什么值?

考虑到程序中对 buffer 的定义

struct buffer {

void * start;

size_t length};

如果将 buffer.length 作为第 2 个参数传递到修改后的 process_image 函数中,这样做是不正确的。 process_image 需要的第二个参数应该是每帧图像的大小,仔细阅读代码后会发现, buffer.length 并不一定就等于图像帧的大小。 (buffer 的大小,还需要考虑其他的一些因素,比如内存对齐等 )。

capture.c只是一个示例程序,仅仅是演示怎样使用v4l2中最基本的接口。尤其是在main函数中的那几个函数调用,表明了在使用v4l2时的最基本的一个流程,包括 open_device,init_device,start_capturing,mainloop,stop_capturing,uninit_device,close_device。在写程序的时候,可以充分的利用这几个基本模块,把他们分散在不同的代码位置上,灵活的调用,有兴趣的可以看一下gstreamer中v4l2src的源代码或者其他的大型程序的相关部分。

总之一句话,capture.c仅仅是一个演示程序,不要局限于它的代码结构,要灵活的使用。

下面是capture.c的源代码:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <assert.h>

#include <getopt.h>             /* getopt_long() */

#include <fcntl.h>              /* low-level i/o */

#include <unistd.h>

#include <errno.h>

#include <malloc.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <sys/time.h>

#include <sys/mman.h>

#include <sys/ioctl.h>

#include <asm/types.h>          /* for videodev2.h */

#include <linux/videodev2.h>

#define CLEAR(x) memset (&(x), 0, sizeof (x))

typedef enum {

IO_METHOD_READ, IO_METHOD_MMAP, IO_METHOD_USERPTR,

} io_method;

struct buffer {

void * start;

size_t length;//buffer's length is different from cap_image_size

};

static char * dev_name = NULL;

static io_method io = IO_METHOD_MMAP;//IO_METHOD_READ;//IO_METHOD_MMAP;

static int fd = -1;

struct buffer * buffers = NULL;

static unsigned int n_buffers = 0;

static FILE * outf = 0;

static unsigned int cap_image_size = 0;//to keep the real image size!!

//

static void errno_exit(const char * s) {

fprintf(stderr, "%s error %d, %s/n", s, errno, strerror(errno));

exit(EXIT_FAILURE);

}

static int xioctl(int fd, int request, void * arg) {

int r;

do

r = ioctl(fd, request, arg);

while (-1 == r && EINTR == errno);

return r;

}

static void process_image(const void * p, int len) {

//  static char[115200] Outbuff ;

fputc('.', stdout);

if (len > 0) {

fputc('.', stdout);

fwrite(p, 1, len, outf);

}

fflush(stdout);

}

static int read_frame(void) {

struct v4l2_buffer buf;

unsigned int i;

switch (io) {

case IO_METHOD_READ:

if (-1 == read(fd, buffers[0].start, buffers[0].length)) {

switch (errno) {

case EAGAIN:

return 0;

case EIO:

/* Could ignore EIO, see spec. */

/* fall through */

default:

errno_exit("read");

}

}

//      printf("length = %d/r", buffers[0].length);

//      process_image(buffers[0].start, buffers[0].length);

printf("image_size = %d,/t IO_METHOD_READ buffer.length=%d/r",

cap_image_size, buffers[0].length);

process_image(buffers[0].start, cap_image_size);

break;

case IO_METHOD_MMAP:

CLEAR (buf);

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory = V4L2_MEMORY_MMAP;

if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {

switch (errno) {

case EAGAIN:

return 0;

case EIO:

/* Could ignore EIO, see spec. */

/* fall through */

default:

errno_exit("VIDIOC_DQBUF");

}

}

assert(buf.index < n_buffers);

//      printf("length = %d/r", buffers[buf.index].length);

//      process_image(buffers[buf.index].start, buffers[buf.index].length);

printf("image_size = %d,/t IO_METHOD_MMAP buffer.length=%d/r",

cap_image_size, buffers[0].length);

process_image(buffers[0].start, cap_image_size);

if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))

errno_exit("VIDIOC_QBUF");

break;

case IO_METHOD_USERPTR:

CLEAR (buf);

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory = V4L2_MEMORY_USERPTR;

if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {

switch (errno) {

case EAGAIN:

return 0;

case EIO:

/* Could ignore EIO, see spec. */

/* fall through */

default:

errno_exit("VIDIOC_DQBUF");

}

}

for (i = 0; i < n_buffers; ++i)

if (buf.m.userptr == (unsigned long) buffers[i].start && buf.length

== buffers[i].length)

break;

assert(i < n_buffers);

//      printf("length = %d/r", buffers[i].length);

//      process_image((void *) buf.m.userptr, buffers[i].length);

printf("image_size = %d,/t IO_METHOD_USERPTR buffer.length=%d/r",

cap_image_size, buffers[0].length);

process_image(buffers[0].start, cap_image_size);

if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))

errno_exit("VIDIOC_QBUF");

break;

}

return 1;

}

static void mainloop(void) {

unsigned int count;

count = 100;

while (count-- > 0) {

for (;;) {

fd_set fds;

struct timeval tv;

int r;

FD_ZERO(&fds);

FD_SET(fd, &fds);

/* Timeout. */

tv.tv_sec = 2;

tv.tv_usec = 0;

r = select(fd + 1, &fds, NULL, NULL, &tv);

if (-1 == r) {

if (EINTR == errno)

continue;

errno_exit("select");

}

if (0 == r) {

fprintf(stderr, "select timeout/n");

exit(EXIT_FAILURE);

}

if (read_frame())

break;

/* EAGAIN - continue select loop. */

}

}

}

static void stop_capturing(void) {

enum v4l2_buf_type type;

switch (io) {

case IO_METHOD_READ:

/* Nothing to do. */

break;

case IO_METHOD_MMAP:

case IO_METHOD_USERPTR:

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))

errno_exit("VIDIOC_STREAMOFF");

break;

}

}

static void start_capturing(void) {

unsigned int i;

enum v4l2_buf_type type;

switch (io) {

case IO_METHOD_READ:

/* Nothing to do. */

break;

case IO_METHOD_MMAP:

for (i = 0; i < n_buffers; ++i) {

struct v4l2_buffer buf;

CLEAR (buf);

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory = V4L2_MEMORY_MMAP;

buf.index = i;

if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))

errno_exit("VIDIOC_QBUF");

}

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))

errno_exit("VIDIOC_STREAMON");

break;

case IO_METHOD_USERPTR:

for (i = 0; i < n_buffers; ++i) {

struct v4l2_buffer buf;

CLEAR (buf);

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory = V4L2_MEMORY_USERPTR;

buf.index = i;

buf.m.userptr = (unsigned long) buffers[i].start;

buf.length = buffers[i].length;

if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))

errno_exit("VIDIOC_QBUF");

}

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))

errno_exit("VIDIOC_STREAMON");

break;

}

}

static void uninit_device(void) {

unsigned int i;

switch (io) {

case IO_METHOD_READ:

free(buffers[0].start);

break;

case IO_METHOD_MMAP:

for (i = 0; i < n_buffers; ++i)

if (-1 == munmap(buffers[i].start, buffers[i].length))

errno_exit("munmap");

break;

case IO_METHOD_USERPTR:

for (i = 0; i < n_buffers; ++i)

free(buffers[i].start);

break;

}

free(buffers);

}

static void init_read(unsigned int buffer_size) {

buffers = calloc(1, sizeof(*buffers));

if (!buffers) {

fprintf(stderr, "Out of memory/n");

exit(EXIT_FAILURE);

}

buffers[0].length = buffer_size;

buffers[0].start = malloc(buffer_size);

if (!buffers[0].start) {

fprintf(stderr, "Out of memory/n");

exit(EXIT_FAILURE);

}

}

static void init_mmap(void) {

struct v4l2_requestbuffers req;

CLEAR (req);

req.count = 4;

req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

req.memory = V4L2_MEMORY_MMAP;

if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {

if (EINVAL == errno) {

fprintf(stderr, "%s does not support "

"memory mapping/n", dev_name);

exit(EXIT_FAILURE);

} else {

errno_exit("VIDIOC_REQBUFS");

}

}

if (req.count < 2) {

fprintf(stderr, "Insufficient buffer memory on %s/n", dev_name);

exit(EXIT_FAILURE);

}

buffers = calloc(req.count, sizeof(*buffers));

if (!buffers) {

fprintf(stderr, "Out of memory/n");

exit(EXIT_FAILURE);

}

for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {

struct v4l2_buffer buf;

CLEAR (buf);

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory = V4L2_MEMORY_MMAP;

buf.index = n_buffers;

if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))

errno_exit("VIDIOC_QUERYBUF");

buffers[n_buffers].length = buf.length;

buffers[n_buffers].start = mmap(NULL /* start anywhere */, buf.length,

PROT_READ | PROT_WRITE /* required */,

MAP_SHARED /* recommended */, fd, buf.m.offset);

if (MAP_FAILED == buffers[n_buffers].start)

errno_exit("mmap");

}

}

static void init_userp(unsigned int buffer_size) {

struct v4l2_requestbuffers req;

unsigned int page_size;

page_size = getpagesize();

buffer_size = (buffer_size + page_size - 1) & ~(page_size - 1);

CLEAR (req);

req.count = 4;

req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

req.memory = V4L2_MEMORY_USERPTR;

if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {

if (EINVAL == errno) {

fprintf(stderr, "%s does not support "

"user pointer i/o/n", dev_name);

exit(EXIT_FAILURE);

} else {

errno_exit("VIDIOC_REQBUFS");

}

}

buffers = calloc(4, sizeof(*buffers));

if (!buffers) {

fprintf(stderr, "Out of memory/n");

exit(EXIT_FAILURE);

}

for (n_buffers = 0; n_buffers < 4; ++n_buffers) {

buffers[n_buffers].length = buffer_size;

buffers[n_buffers].start = memalign(/* boundary */page_size,

buffer_size);

if (!buffers[n_buffers].start) {

fprintf(stderr, "Out of memory/n");

exit(EXIT_FAILURE);

}

}

}

static void init_device(void) {

struct v4l2_capability cap;

struct v4l2_cropcap cropcap;

struct v4l2_crop crop;

struct v4l2_format fmt;

unsigned int min;

if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) {

if (EINVAL == errno) {

fprintf(stderr, "%s is no V4L2 device/n", dev_name);

exit(EXIT_FAILURE);

} else {

errno_exit("VIDIOC_QUERYCAP");

}

}

if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {

fprintf(stderr, "%s is no video capture device/n", dev_name);

exit(EXIT_FAILURE);

}

switch (io) {

case IO_METHOD_READ:

if (!(cap.capabilities & V4L2_CAP_READWRITE)) {

fprintf(stderr, "%s does not support read i/o/n", dev_name);

exit(EXIT_FAILURE);

}

break;

case IO_METHOD_MMAP:

case IO_METHOD_USERPTR:

if (!(cap.capabilities & V4L2_CAP_STREAMING)) {

fprintf(stderr, "%s does not support streaming i/o/n", dev_name);

exit(EXIT_FAILURE);

}

break;

}

//not all capture support crop!!!!!!!

/* Select video input, video standard and tune here. */

printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");

CLEAR (cropcap);

cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) {

crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

#ifndef CROP_BY_JACK

crop.c = cropcap.defrect; /* reset to default */

#else

crop.c.left = cropcap.defrect.left;

crop.c.top = cropcap.defrect.top;

crop.c.width = 352;

crop.c.height = 288;

#endif

printf("----->has ability to crop!!/n");

printf("cropcap.defrect = (%d, %d, %d, %d)/n", cropcap.defrect.left,

cropcap.defrect.top, cropcap.defrect.width,

cropcap.defrect.height);

if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) {

switch (errno) {

case EINVAL:

/* Cropping not supported. */

break;

default:

/* Errors ignored. */

break;

}

printf("-----!!but crop to (%d, %d, %d, %d) Failed!!/n",

crop.c.left, crop.c.top, crop.c.width, crop.c.height);

} else {

printf("----->sussess crop to (%d, %d, %d, %d)/n", crop.c.left,

crop.c.top, crop.c.width, crop.c.height);

}

} else {

/* Errors ignored. */

printf("!! has no ability to crop!!/n");

}

printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");

printf("/n");

crop finished!

//set the format

CLEAR (fmt);

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

fmt.fmt.pix.width = 640;

fmt.fmt.pix.height = 480;

//V4L2_PIX_FMT_YVU420, V4L2_PIX_FMT_YUV420 — Planar formats with 1/2 horizontal and vertical chroma resolution, also known as YUV 4:2:0

//V4L2_PIX_FMT_YUYV — Packed format with 1/2 horizontal chroma resolution, also known as YUV 4:2:2

fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YUV420;//V4L2_PIX_FMT_YUYV;

fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

{

printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");

printf("=====will set fmt to (%d, %d)--", fmt.fmt.pix.width,

fmt.fmt.pix.height);

if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {

printf("V4L2_PIX_FMT_YUYV/n");

} else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) {

printf("V4L2_PIX_FMT_YUV420/n");

} else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) {

printf("V4L2_PIX_FMT_NV12/n");

}

}

if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))

errno_exit("VIDIOC_S_FMT");

{

printf("=====after set fmt/n");

printf("    fmt.fmt.pix.width = %d/n", fmt.fmt.pix.width);

printf("    fmt.fmt.pix.height = %d/n", fmt.fmt.pix.height);

printf("    fmt.fmt.pix.sizeimage = %d/n", fmt.fmt.pix.sizeimage);

cap_image_size = fmt.fmt.pix.sizeimage;

printf("    fmt.fmt.pix.bytesperline = %d/n", fmt.fmt.pix.bytesperline);

printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");

printf("/n");

}

cap_image_size = fmt.fmt.pix.sizeimage;

/* Note VIDIOC_S_FMT may change width and height. */

printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");

/* Buggy driver paranoia. */

min = fmt.fmt.pix.width * 2;

if (fmt.fmt.pix.bytesperline < min)

fmt.fmt.pix.bytesperline = min;

min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;

if (fmt.fmt.pix.sizeimage < min)

fmt.fmt.pix.sizeimage = min;

printf("After Buggy driver paranoia/n");

printf("    >>fmt.fmt.pix.sizeimage = %d/n", fmt.fmt.pix.sizeimage);

printf("    >>fmt.fmt.pix.bytesperline = %d/n", fmt.fmt.pix.bytesperline);

printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");

printf("/n");

switch (io) {

case IO_METHOD_READ:

init_read(fmt.fmt.pix.sizeimage);

break;

case IO_METHOD_MMAP:

init_mmap();

break;

case IO_METHOD_USERPTR:

init_userp(fmt.fmt.pix.sizeimage);

break;

}

}

static void close_device(void) {

if (-1 == close(fd))

errno_exit("close");

fd = -1;

}

static void open_device(void) {

struct stat st;

if (-1 == stat(dev_name, &st)) {

fprintf(stderr, "Cannot identify '%s': %d, %s/n", dev_name, errno,

strerror(errno));

exit(EXIT_FAILURE);

}

if (!S_ISCHR(st.st_mode)) {

fprintf(stderr, "%s is no device/n", dev_name);

exit(EXIT_FAILURE);

}

fd = open(dev_name, O_RDWR /* required */| O_NONBLOCK, 0);

if (-1 == fd) {

fprintf(stderr, "Cannot open '%s': %d, %s/n", dev_name, errno,

strerror(errno));

exit(EXIT_FAILURE);

}

}

static void usage(FILE * fp, int argc, char ** argv) {

fprintf(fp, "Usage: %s [options]/n/n"

"Options:/n"

"-d | --device name   Video device name [/dev/video0]/n"

"-h | --help          Print this message/n"

"-m | --mmap          Use memory mapped buffers/n"

"-r | --read          Use read() calls/n"

"-u | --userp         Use application allocated buffers/n"

"", argv[0]);

}

static const char short_options[] = "d:hmru";

static const struct option long_options[] = { { "device", required_argument,

NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "mmap", no_argument,

NULL, 'm' }, { "read", no_argument, NULL, 'r' }, { "userp",

no_argument, NULL, 'u' }, { 0, 0, 0, 0 } };

int main(int argc, char ** argv) {

dev_name = "/dev/video0";

outf = fopen("out.yuv", "wb");

for (;;) {

int index;

int c;

c = getopt_long(argc, argv, short_options, long_options, &index);

if (-1 == c)

break;

switch (c) {

case 0: /* getopt_long() flag */

break;

case 'd':

dev_name = optarg;

break;

case 'h':

usage(stdout, argc, argv);

exit(EXIT_SUCCESS);

case 'm':

io = IO_METHOD_MMAP;

break;

case 'r':

io = IO_METHOD_READ;

break;

case 'u':

io = IO_METHOD_USERPTR;

break;

default:

usage(stderr, argc, argv);

exit(EXIT_FAILURE);

}

}

open_device();

init_device();

start_capturing();

mainloop();

printf("/n");

stop_capturing();

fclose(outf);

uninit_device();

close_device();

exit(EXIT_SUCCESS);

return 0;

}

(完)

V4L2驱动的移植与应用(三)相关推荐

  1. V4L2驱动详解 API翻译

    博主按:介绍V4L2基础的东西,不知道是哪位同志翻译的,莫名的感动啊.这个必须转! 另,对未翻译的部分博主加以补充.文中以蓝色字体表示,如果有错误请高手指正.还有些图片好像不能显示,我从原文复制过来了 ...

  2. v4l2驱动编写篇【转】

    转自:http://blog.csdn.net/michaelcao1980/article/details/53008418 大部分所需的信息都在这里.作为一个驱动作者,当挖掘头文件的时候,你可能也 ...

  3. V4L2驱动编写(转载)

    转自:http://blog.chinaunix.net/uid-11765716-id-196071.html 在转载过程中,图片 显示出错.请到原始连接进行查看原图. 正文: 博主按:介绍V4L2 ...

  4. Android wifi驱动的移植 realtek 8188

    Android wifi驱动的移植 一般我们拿到的android源代码中wifi应用层部分是好的, 主要是wifi芯片的驱动要移植并添加进去. wifi驱动的移植, 以realtek的8188etv为 ...

  5. 7.camera驱动06-自己实现v4l2驱动-虚拟摄像头

    1. 框架分层 实际上的v4l2框架: v4l2本质是还是一个字符设备驱动,有自己的fops. 每注册一个video_device都会以次设备号为下标放到v4l2层的一个数组里. 应用调用open函数 ...

  6. 深入学习Linux摄像头(二)v4l2驱动框架

    深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...

  7. 以太网卡驱动程序移植linux,基于S3C2440的DM9000网卡驱动的移植

    摘  要: 主要研究了基于Linux内核的网卡驱动的移植.Linux网络设备驱动程序的体系结构可以分为4层,首先分析了各层的具体功能实现,并在此基础上充分利用S3C2440开发板完成DM9000网卡驱 ...

  8. RK3568 Sensor驱动开发移植(3)

    RK3568 Camera 使用 RK3568 Sensor驱动开发移植(1) RK3568 Sensor驱动开发移植(2) RK3568 Sensor驱动开发移植(3) 实现标准 I2C 子设备驱动 ...

  9. RK3568 Sensor驱动开发移植(2)

    RK3568 Camera 使用 RK3568 Sensor驱动开发移植(1) RK3568 Sensor驱动开发移植(2) RK3568 Sensor驱动开发移植(3) v4l2_subdev_op ...

  10. V4l2框架-平台V4L2驱动共性

    文章目录 一.前言 二.主要结构体 2.1 v4l2_device 三.应用程序调用系统函数到驱动流程 3.1 使用open函数打开文件结点 3.2 read函数 3.3 ioctl 本文为学习v4l ...

最新文章

  1. 关于计算机网络的好坏处的英语作文,网购的好处和坏处英语作文带翻译
  2. a*算法的优缺点_五种聚类算法一览与python实现
  3. android 始终显示overflow菜单
  4. Swing俄罗斯游戏编写详解(附源码)
  5. 最短路径——迪杰斯特拉算法——图的数据结构
  6. SpringSecurity-1-AuthenticationFailureHandler接口(登录失败之后的处理逻辑)
  7. _GNUC__宏函数
  8. 【Kettle】date类型不能被excel输出
  9. vector中针对自定义类型的排序
  10. 基于jsp、ssm企业工资管理系统
  11. 小米手机电池恢复代码_小米手机隐藏技巧,你真的会用吗?别再浪费如此强大的功能了...
  12. Android MTK 预制应用遇到的问题
  13. 母亲节为什么要定在5月的第二个星期日? [节假日]
  14. Win10系统更新显卡驱动无限蓝屏重启-驱动人生解决方案
  15. laravel框架中Cache缓存类中的原子锁
  16. android 怎么改变字体颜色,安卓系统字体颜色修改教程
  17. Python爬虫—手机销量
  18. BigWorld Server - Architecture
  19. mysql高级教程(一)
  20. Python 实现简单的客户端认证

热门文章

  1. Android通知渠道
  2. Java提取成对括号内容 支持扩展多种括号
  3. 李维看 .net 和 DELPHI 6 (含李维照片) (转)
  4. 打开word出现自动化automation错误、Microsoft visual basic 运行时错误
  5. Float类型出现舍入误差的原因
  6. 抖音 触摸精灵_触控精灵app下载-触控精灵手机版 v1.3.2 - 安下载
  7. 《秘密》· 东野圭吾
  8. vs2012运行c语言出现:无法查找或打开 PDB 文件
  9. Objectdock stacks docklet 无法启动程序快捷方式
  10. PDF在线转换成word免费版