输入初始化input_init
init_videoIn
input_init->init_videoIn
这段代码是init_videoIn函数的实现。该函数用于初始化视频输入设备。
函数接收多个参数,包括指向vdIn结构体的指针vd、设备名称device、宽度width、高度height、帧率fps、格式format、抓取方法grabmethod、全局上下文指针pglobal和IDid。
函数的主要步骤如下:
检查vd和device是否为NULL,如果是则返回-1。
检查width和height是否为0,如果是则返回-1。
检查grabmethod的值是否在0和1之间,如果不是,则将其设置为1(默认值)。
分配内存并初始化vd结构体中的指针成员。
使用calloc函数分配内存并将设备名称拷贝到vd->videodevice中。
设置vd结构体的其他成员变量,如toggleAvi、getPict、signalquit、width、height、fps、formatIn和grabmethod。
调用init_v4l2函数初始化V4L2设备。
枚举支持的格式并存储到全局上下文的in_formats数组中。
如果当前格式与指定的format匹配,则将其索引存储到currentFormat中。
枚举当前格式下支持的分辨率,并存储到相应的数据结构中。
分配临时缓冲区和帧缓冲区,根据指定的格式和分辨率确定缓冲区的大小。
返回0表示初始化成功。
如果在初始化过程中发生错误,将执行错误处理步骤,释放已分配的内存并关闭视频设备,然后返回-1表示初始化失败。
int init_videoIn(struct vdIn *vd, char *device, int width, int height, int fps, int format, int grabmethod, globals *pglobal, int id) { if(vd == NULL || device == NULL) // 如果vd或device为空,则返回-1 return -1; if(width == 0 || height == 0) // 如果width或height为0,则返回-1 return -1; if(grabmethod < 0 || grabmethod > 1) // 如果grabmethod小于0或大于1,则将其设置为1 grabmethod = 1; //默认使用mmap; vd->videodevice = NULL; // 初始化vd的videodevice、status、pictName为NULL vd->status = NULL; vd->pictName = NULL; vd->videodevice = (char *) calloc(1, 16 * sizeof(char)); // 为vd的videodevice、status、pictName分配内存 vd->status = (char *) calloc(1, 100 * sizeof(char)); vd->pictName = (char *) calloc(1, 80 * sizeof(char)); snprintf(vd->videodevice, 12, "%s", device); // 将device的值复制到vd的videodevice中 vd->toggleAvi = 0; // 初始化vd的toggleAvi、getPict、signalquit为0、0、1 vd->getPict = 0; vd->signalquit = 1; vd->width = width; // 初始化vd的width、height、fps、formatIn、grabmethod为传入的参数 vd->height = height; vd->fps = fps; vd->formatIn = format; vd->grabmethod = grabmethod; if(init_v4l2(vd) < 0) { // 如果init_v4l2返回小于0的值,则输出错误信息并跳转到error标签 fprintf(stderr, " Init v4L2 failed !! exit fatal \n"); goto error;; } // 枚举格式 int currentWidth, currentHeight = 0; struct v4l2_format currentFormat; currentFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(xioctl(vd->fd, VIDIOC_G_FMT, ¤tFormat) == 0) { currentWidth = currentFormat.fmt.pix.width; currentHeight = currentFormat.fmt.pix.height; DBG("Current size: %dx%d\n", currentWidth, currentHeight); } pglobal->in[id].in_formats = NULL; for(pglobal->in[id].formatCount = 0; 1; pglobal->in[id].formatCount++) { struct v4l2_fmtdesc fmtdesc; fmtdesc.index = pglobal->in[id].formatCount; fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(xioctl(vd->fd, VIDIOC_ENUM_FMT, &fmtdesc) < 0) { break; } if (pglobal->in[id].in_formats == NULL) { pglobal->in[id].in_formats = (input_format*)calloc(1, sizeof(input_format)); } else { pglobal->in[id].in_formats = (input_format*)realloc(pglobal->in[id].in_formats, (pglobal->in[id].formatCount + 1) * sizeof(input_format)); } if (pglobal->in[id].in_formats == NULL) { DBG("Calloc/realloc failed: %s\n", strerror(errno)); return -1; } // 将fmtdesc复制到pglobal->in[id].in_formats[pglobal->in[id].formatCount]中 memcpy(&pglobal->in[id].in_formats[pglobal->in[id].formatCount], &fmtdesc, sizeof(input_format)); // 如果fmtdesc.pixelformat等于format,则将pglobal->in[id].currentFormat设置为pglobal->in[id].formatCount if(fmtdesc.pixelformat == format) pglobal->in[id].currentFormat = pglobal->in[id].formatCount; DBG("Supported format: %s\n", fmtdesc.description); // 枚举分辨率 struct v4l2_frmsizeenum fsenum; fsenum.index = pglobal->in[id].formatCount; fsenum.pixel_format = fmtdesc.pixelformat; int j = 0; pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions = NULL; pglobal->in[id].in_formats[pglobal->in[id].formatCount].resolutionCount = 0; pglobal->in[id].in_formats[pglobal->in[id].formatCount].currentResolution = -1; while(1) { fsenum.index = j; j++; if(xioctl(vd->fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0) { pglobal->in[id].in_formats[pglobal->in[id].formatCount].resolutionCount++; // 为pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions分配内存 if (pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions == NULL) { pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions = (input_resolution*)calloc(1, sizeof(input_resolution)); } else { pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions = (input_resolution*)realloc(pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions, j * sizeof(input_resolution)); } // 如果分配内存失败,则输出错误信息并返回-1 if (pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions == NULL) { DBG("Calloc/realloc failed\n"); return -1; } // 将分辨率信息添加到pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions中 pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions[j-1].width = fsenum.discrete.width; pglobal->in[id].in_formats[pglobal->in[id].formatCount].supportedResolutions[j-1].height = fsenum.discrete.height; // 如果format等于fmtdesc.pixelformat,则将pglobal->in[id].in_formats[pglobal->in[id].formatCount].currentResolution设置为(j - 1) if(format == fmtdesc.pixelformat) { pglobal->in[id].in_formats[pglobal->in[id].formatCount].currentResolution = (j - 1); DBG("\tSupported size with the current format: %dx%d\n", fsenum.discrete.width, fsenum.discrete.height); } else { DBG("\tSupported size: %dx%d\n", fsenum.discrete.width, fsenum.discrete.height); } } else { break; } } } /* 为重构图像分配临时缓冲区 */ vd->framesizeIn = (vd->width * vd->height << 1); switch(vd->formatIn) { case V4L2_PIX_FMT_MJPEG: vd->tmpbuffer = (unsigned char *) calloc(1, (size_t) vd->framesizeIn); if(!vd->tmpbuffer) goto error; vd->framebuffer = (unsigned char *) calloc(1, (size_t) vd->width * (vd->height + 8) * 2); break; case V4L2_PIX_FMT_YUYV: vd->framebuffer = (unsigned char *) calloc(1, (size_t) vd->framesizeIn); break; default: fprintf(stderr, " should never arrive exit fatal !!\n"); goto error; break; } if(!vd->framebuffer) goto error; return 0; error: free(pglobal->in[id].in_parameters); free(vd->videodevice); free(vd->status); free(vd->pictName); CLOSE_VIDEO(vd->fd); return -1; }
init_v4l2
input_init->init_videoIn->init_v4l2
代码与初始化V4L2(Video4Linux2)应用程序中的视频输入有关。以下是代码的功能解析:
init_v4l2函数用于初始化视频捕获的V4L2接口。
使用带有O_RDWR标志的OPEN_VIDEO宏打开指定的视频设备(由vd->videodevice指定)。
使用VIDIOC_QUERYCAP ioctl查询视频设备的功能。如果查询失败,将打印错误消息,并跳转到fatal标签,表示发生致命错误。
使用设备功能的capabilities字段中的V4L2_CAP_VIDEO_CAPTURE标志检查是否支持视频捕获。如果不支持,则打印错误消息,并跳转到fatal标签。
根据vd->grabmethod指定的捕获方法,检查设备是否支持流式I/O(V4L2_CAP_STREAMING)或读取I/O(V4L2_CAP_READWRITE)。如果不支持,将打印错误消息,并跳转到fatal标签。
使用VIDIOC_S_FMT ioctl设置视频捕获的所需格式。格式参数(如宽度、高度、像素格式和字段)在vd->fmt结构中设置。如果ioctl调用失败,将打印错误消息,并跳转到fatal标签。
如果请求的格式不可用,则根据设备报告的支持格式调整宽度、高度和像素格式。如果调整后的格式不受支持,或者请求的格式为MJPEG并且设备不支持MJPEG模式,或者请求的格式为YUV并且设备不支持YUV模式,则打印错误消息,并跳转到fatal标签。
使用VIDIOC_S_PARM ioctl设置所需的帧率。帧率在struct v4l2_streamparm结构的timeperframe字段中设置。如果ioctl调用失败,则忽略错误,函数继续执行。
使用VIDIOC_REQBUFS ioctl请求视频缓冲区。请求的缓冲区数量和内存类型(V4L2_MEMORY_MMAP)在vd->rb结构中设置。如果ioctl调用失败,将打印错误消息,并跳转到fatal标签。
使用mmap函数将视频缓冲区映射到应用程序的内存中。每个缓冲区使用从VIDIOC_QUERYBUF ioctl获得的缓冲区长度和偏移量进行映射。如果映射失败,将打印错误消息,并跳转到fatal标签。
使用VIDIOC_QBUF ioctl将映射的缓冲区排队进行视频捕获。索引、类型和内存类型在vd->buf结构中设置。如果排队失败,将打印错误消息,并跳转到fatal标签。
// 初始化视频设备 static int init_v4l2(struct vdIn *vd) { int i; int ret = 0; // 打开视频设备 if((vd->fd = OPEN_VIDEO(vd->videodevice, O_RDWR)) == -1) { perror("ERROR opening V4L interface"); DBG("errno: %d", errno); return -1; } // 查询设备信息 memset(&vd->cap, 0, sizeof(struct v4l2_capability)); ret = xioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap); if(ret < 0) { fprintf(stderr, "Error opening device %s: unable to query device.\n", vd->videodevice); goto fatal; } // 判断设备是否支持视频捕获 if((vd->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) { fprintf(stderr, "Error opening device %s: video capture not supported.\n", vd->videodevice); goto fatal;; } // 判断设备是否支持流式I/O或读写I/O if(vd->grabmethod) { if(!(vd->cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "%s does not support streaming i/o\n", vd->videodevice); goto fatal; } } else { if(!(vd->cap.capabilities & V4L2_CAP_READWRITE)) { fprintf(stderr, "%s does not support read i/o\n", vd->videodevice); goto fatal; } } /* * 设置视频格式 */ memset(&vd->fmt, 0, sizeof(struct v4l2_format)); vd->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置视频流类型 vd->fmt.fmt.pix.width = vd->width; // 设置视频宽度 vd->fmt.fmt.pix.height = vd->height; // 设置视频高度 vd->fmt.fmt.pix.pixelformat = vd->formatIn; // 设置像素格式 vd->fmt.fmt.pix.field = V4L2_FIELD_ANY; // 设置视频场 ret = xioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt); // 设置视频格式 if(ret < 0) { fprintf(stderr, "Unable to set format: %d res: %dx%d\n", vd->formatIn, vd->width, vd->height); goto fatal; } if((vd->fmt.fmt.pix.width != vd->width) || (vd->fmt.fmt.pix.height != vd->height)) { fprintf(stderr, "i: The format asked unavailable, so the width %d height %d \n", vd->fmt.fmt.pix.width, vd->fmt.fmt.pix.height); vd->width = vd->fmt.fmt.pix.width; vd->height = vd->fmt.fmt.pix.height; /* * 检查所需格式是否可用 */ if(vd->formatIn != vd->fmt.fmt.pix.pixelformat) { if(vd->formatIn == V4L2_PIX_FMT_MJPEG) { fprintf(stderr, "The inpout device does not supports MJPEG mode\nYou may also try the YUV mode (-yuv option), but it requires a much more CPU power\n"); goto fatal; } else if(vd->formatIn == V4L2_PIX_FMT_YUYV) { fprintf(stderr, "The input device does not supports YUV mode\n"); goto fatal; } } else { vd->formatIn = vd->fmt.fmt.pix.pixelformat; } } /* * 设置帧率 */ struct v4l2_streamparm *setfps; setfps = (struct v4l2_streamparm *) calloc(1, sizeof(struct v4l2_streamparm)); memset(setfps, 0, sizeof(struct v4l2_streamparm)); setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置视频流类型 setfps->parm.capture.timeperframe.numerator = 1; // 设置帧率分子 setfps->parm.capture.timeperframe.denominator = vd->fps; // 设置帧率分母 ret = xioctl(vd->fd, VIDIOC_S_PARM, setfps); // 设置帧率 free(setfps); /* * 请求缓冲区 */ memset(&vd->rb, 0, sizeof(struct v4l2_requestbuffers)); vd->rb.count = NB_BUFFER; // 设置缓冲区数量 vd->rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置视频流类型 vd->rb.memory = V4L2_MEMORY_MMAP; // 设置内存映射方式 ret = xioctl(vd->fd, VIDIOC_REQBUFS, &vd->rb); // 请求缓冲区 if(ret < 0) { perror("Unable to allocate buffers"); goto fatal; } /* * map the buffers */ for(i = 0; i < NB_BUFFER; i++) { // 循环映射缓冲区 memset(&vd->buf, 0, sizeof(struct v4l2_buffer)); // 清空缓冲区 vd->buf.index = i; // 设置缓冲区索引 vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置视频流类型 vd->buf.memory = V4L2_MEMORY_MMAP; // 设置内存映射方式 ret = xioctl(vd->fd, VIDIOC_QUERYBUF, &vd->buf); // 查询缓冲区 if(ret < 0) { // 查询失败 perror("Unable to query buffer"); // 输出错误信息 goto fatal; // 跳转到错误处理 } if(debug) // 如果是调试模式 fprintf(stderr, "length: %u offset: %u\n", vd->buf.length, vd->buf.m.offset); // 输出缓冲区长度和偏移量 vd->mem[i] = mmap(0 /* start anywhere */ , // 映射缓冲区 vd->buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, vd->fd, vd->buf.m.offset); if(vd->mem[i] == MAP_FAILED) { // 映射失败 perror("Unable to map buffer"); // 输出错误信息 goto fatal; // 跳转到错误处理 } if(debug) // 如果是调试模式 fprintf(stderr, "Buffer mapped at address %p.\n", vd->mem[i]); // 输出缓冲区映射地址 } /* * Queue the buffers. */ for(i = 0; i < NB_BUFFER; ++i) { // 循环将缓冲区加入队列 memset(&vd->buf, 0, sizeof(struct v4l2_buffer)); // 清空缓冲区 vd->buf.index = i; // 设置缓冲区索引 vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置视频流类型 vd->buf.memory = V4L2_MEMORY_MMAP; // 设置内存映射方式 ret = xioctl(vd->fd, VIDIOC_QBUF, &vd->buf); // 将缓冲区加入队列 if(ret < 0) { // 加入队列失败 perror("Unable to queue buffer"); // 输出错误信息 goto fatal;; // 跳转到错误处理 } } return 0; fatal: return -1; }
启动摄像头输入线程
cam_thread
这个函数是一个摄像头线程函数,主要功能是从摄像头抓取视频帧并进行处理。下面是对函数的概括总结:
函数接受一个参数 arg,该参数是一个结构体指针 context。
函数中使用了一个全局变量 pglobal,该变量指向 pcontext 结构体中的全局变量。
函数使用 pthread_cleanup_push 设置了清理处理程序 cam_cleanup,以便在函数结束时清理分配的资源。
函数通过一个循环来不断抓取视频帧,直到全局变量 pglobal 的 stop 标志被设置为真。
在抓取视频帧之前,检查视频流的状态是否为暂停状态,如果是暂停状态,则使用 usleep 函数进行延迟等待。
使用 uvcGrab 函数抓取一帧视频。
对于捕获的视频帧,进行一些检查和处理:
如果视频帧的大小小于 minimum_size,则认为是损坏的帧,跳过处理。
如果视频帧的输入格式为 V4L2_PIX_FMT_YUYV,则进行 YUV 到 JPEG 的压缩处理;否则,直接将视频帧复制到全局缓冲区。
复制处理后的帧到全局缓冲区之前,获取全局缓冲区的互斥锁,确保线程安全。
将处理后的帧的大小和时间戳复制到全局缓冲区,并通过信号 db_update 通知其他线程。
释放全局缓冲区的互斥锁。
根据帧率决定是否使用 usleep 进行等待,如果帧率低于 5,则等待一定时间,否则直接等待下一帧。
当全局变量 pglobal 的 stop 标志被设置为真时,退出循环。
在函数结束之前,使用 pthread_cleanup_pop 调用清理处理程序。
函数返回 NULL。
总体而言,该函数负责从摄像头抓取视频帧,并将处理后的帧复制到全局缓冲区,同时控制帧率和处理错误帧。
// 摄像头线程函数 void *cam_thread(void *arg) { context *pcontext = arg; pglobal = pcontext->pglobal; /* 设置清理处理程序以清理分配的资源 */ pthread_cleanup_push(cam_cleanup, pcontext); while(!pglobal->stop) { while(pcontext->videoIn->streamingState == STREAMING_PAUSED) { usleep(1); // 可能不是最好的方法,所以FIXME } /* 抓取一帧 */ if(uvcGrab(pcontext->videoIn) < 0) { IPRINT("Error grabbing frames\n"); exit(EXIT_FAILURE); } DBG("received frame of size: %d from plugin: %d\n", pcontext->videoIn->buf.bytesused, pcontext->id); /* * Workaround for broken, corrupted frames: * Under low light conditions corrupted frames may get captured. * The good thing is such frames are quite small compared to the regular pictures. * For example a VGA (640x480) webcam picture is normally >= 8kByte large, * corrupted frames are smaller. */ if(pcontext->videoIn->buf.bytesused < minimum_size) { DBG("dropping too small frame, assuming it as broken\n"); continue; } /* 将 JPG 图片复制到全局缓冲区 */ pthread_mutex_lock(&pglobal->in[pcontext->id].db); /* * 如果以 YUV 模式捕获,则现在将其转换为 JPEG。 * 此压缩需要许多 CPU 周期,因此尽量避免使用 YUV 格式。 * 直接从网络摄像头获取 JPEG 是 Linux-UVC 兼容设备的主要优点之一。 */ if(pcontext->videoIn->formatIn == V4L2_PIX_FMT_YUYV) { DBG("compressing frame from input: %d\n", (int)pcontext->id); pglobal->in[pcontext->id].size = compress_yuyv_to_jpeg(pcontext->videoIn, pglobal->in[pcontext->id].buf, pcontext->videoIn->framesizeIn, gquality); } else { DBG("compressing frame from input: %d\n", (int)pcontext->id); pglobal->in[pcontext->id].size = memcpy_picture(pglobal->in[pcontext->id].buf, pcontext->videoIn->tmpbuffer, pcontext->videoIn->buf.bytesused); } #if 0 /* 运动检测可以通过比较图片大小来完成,但不是非常准确!! */ if((prev_size - global->size)*(prev_size - global->size) > 4 * 1024 * 1024) { DBG("检测到运动(差值:%d kB)\n", (prev_size - global->size) / 1024); } prev_size = global->size; #endif /* 将此帧的时间戳复制到用户空间 */ pglobal->in[pcontext->id].timestamp = pcontext->videoIn->buf.timestamp; /* 信号 fresh_frame */ pthread_cond_broadcast(&pglobal->in[pcontext->id].db_update); pthread_mutex_unlock(&pglobal->in[pcontext->id].db); /* 只有在 fps 低于 5 时才使用 usleep,否则开销太大 */ if(pcontext->videoIn->fps < 5) { DBG("waiting for next frame for %d us\n", 1000 * 1000 / pcontext->videoIn->fps); usleep(1000 * 1000 / pcontext->videoIn->fps); } else { DBG("waiting for next frame\n"); } } DBG("leaving input thread, calling cleanup function now\n"); pthread_cleanup_pop(1); return NULL; }
uvcGrab
这段代码是用于从视频设备中抓取一帧视频的函数 uvcGrab。以下是对函数的概括总结:
函数接受一个参数 vd,该参数是一个指向 struct vdIn 结构体的指针。
首先检查视频流的状态,如果当前状态为关闭状态,则通过调用 video_enable 函数启动视频流。
将缓冲区 vd->buf 清零,并设置缓冲区的类型为视频捕获 (V4L2_BUF_TYPE_VIDEO_CAPTURE),内存类型为内存映射 (V4L2_MEMORY_MMAP)。
使用 xioctl 函数从队列中取出一个缓冲区,存储在 vd->buf 中。
根据输入的格式进行处理:
如果输入格式为 V4L2_PIX_FMT_MJPEG,检查当前缓冲区的大小是否小于等于 HEADERFRAME1(宏定义的值),如果是,则输出警告信息并返回。
将缓冲区的内容复制到临时缓冲区 vd->tmpbuffer。
如果输入格式为 V4L2_PIX_FMT_YUYV,将缓冲区的内容复制到帧缓冲区 vd->framebuffer,根据实际大小进行复制。
对于其他输入格式,跳转到错误处理。
使用 xioctl 函数将缓冲区重新放入队列。
如果发生错误,设置退出信号为 0,并返回 -1。
函数正常执行完成后,返回 0。
该函数的主要目的是从视频设备中获取一帧视频数据,并根据输入格式进行相应处理和复制。
int uvcGrab(struct vdIn *vd) { #define HEADERFRAME1 0xaf int ret; if(vd->streamingState == STREAMING_OFF) { // 如果当前状态为关闭状态 if(video_enable(vd)) // 启动视频流 goto err; } memset(&vd->buf, 0, sizeof(struct v4l2_buffer)); // 将缓冲区清零 vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置缓冲区类型为视频捕获 vd->buf.memory = V4L2_MEMORY_MMAP; // 设置缓冲区内存类型为内存映射 ret = xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf); // 从队列中取出缓冲区 if(ret < 0) { // 取出失败 perror("Unable to dequeue buffer"); // 输出错误信息 goto err; // 跳转到错误处理 } switch(vd->formatIn) { // 根据输入格式进行处理 case V4L2_PIX_FMT_MJPEG: // 如果是MJPEG格式 if(vd->buf.bytesused <= HEADERFRAME1) { // 如果当前缓冲区大小小于等于0xaf /* Prevent crash * on empty image */ fprintf(stderr, "Ignoring empty buffer ...\n"); // 输出警告信息 return 0; // 返回0 } /* memcpy(vd->tmpbuffer, vd->mem[vd->buf.index], vd->buf.bytesused); memcpy (vd->tmpbuffer, vd->mem[vd->buf.index], HEADERFRAME1); memcpy (vd->tmpbuffer + HEADERFRAME1, dht_data, sizeof(dht_data)); memcpy (vd->tmpbuffer + HEADERFRAME1 + sizeof(dht_data), vd->mem[vd->buf.index] + HEADERFRAME1, (vd->buf.bytesused - HEADERFRAME1)); */ memcpy(vd->tmpbuffer, vd->mem[vd->buf.index], vd->buf.bytesused); // 将缓冲区内容复制到临时缓冲区 if(debug) fprintf(stderr, "bytes in used %d \n", vd->buf.bytesused); // 输出调试信息 break; case V4L2_PIX_FMT_YUYV: // 如果是YUYV格式 if(vd->buf.bytesused > vd->framesizeIn) memcpy(vd->framebuffer, vd->mem[vd->buf.index], (size_t) vd->framesizeIn); // 将缓冲区内容复制到帧缓冲区 else memcpy(vd->framebuffer, vd->mem[vd->buf.index], (size_t) vd->buf.bytesused); // 将缓冲区内容复制到帧缓冲区 break; default: goto err; // 跳转到错误处理 break; } ret = xioctl(vd->fd, VIDIOC_QBUF, &vd->buf); // 将缓冲区加入队列 if(ret < 0) { // 加入队列失败 perror("Unable to requeue buffer"); // 输出错误信息 goto err; // 跳转到错误处理 } return 0; // 返回0 err: vd->signalquit = 0; // 设置退出信号为0 return -1; // 返回-1 } // 启用视频流 static int video_enable(struct vdIn *vd) { int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置视频流类型 int ret; ret = xioctl(vd->fd, VIDIOC_STREAMON, &type); // 启用视频流 if(ret < 0) { // 启用失败 perror("Unable to start capture"); // 输出错误信息 return ret; // 返回错误码 } vd->streamingState = STREAMING_ON; // 设置视频流状态为开启 return 0; // 返回成功 }
compress_yuyv_to_jpeg
这段代码是用于将 YUYV 格式的帧数据压缩为 JPEG 格式的函数 compress_yuyv_to_jpeg。以下是对函数的概括总结:
函数接受四个参数:指向 struct vdIn 结构体的指针 vd,存储压缩后数据的缓冲区 buffer,缓冲区大小 size,以及压缩的质量 quality。
首先分配一行像素数据的缓冲区 line_buffer,并获取 YUYV 格式的帧数据存储在 yuyv 中。
初始化 JPEG 压缩结构体 cinfo 和 JPEG 错误管理器 jerr。
将压缩后的数据存储到内存中的 buffer 中,通过调用 dest_buffer 函数来实现,同时记录已经写入的字节数。
设置图像的宽度、高度、颜色空间等信息。
设置 JPEG 压缩参数,包括压缩质量。
开始压缩过程,调用 jpeg_start_compress 函数。
遍历每一行像素数据:
遍历每个像素点,根据 YUV 值计算对应的 RGB 值。
将 RGB 值存储到 line_buffer 中。
更新 YUV 值。
存储一行像素数据,通过调用 jpeg_write_scanlines 函数实现。
压缩结束后,调用 jpeg_finish_compress 完成压缩过程。
销毁 JPEG 压缩结构体,通过调用 jpeg_destroy_compress 函数。
释放缓冲区 line_buffer。
返回已经写入的字节数。
该函数使用 libjpeg 库将 YUYV 格式的帧数据压缩为 JPEG 格式,并将压缩后的数据存储在指定的缓冲区中,并返回已经写入的字节数。
int compress_yuyv_to_jpeg(struct vdIn *vd, unsigned char *buffer, int size, int quality) { // 初始化jpeg压缩结构体 struct jpeg_compress_struct cinfo; // 初始化jpeg错误管理器 struct jpeg_error_mgr jerr; // 存储一行像素数据 JSAMPROW row_pointer[1]; // 存储一行像素数据的缓冲区 unsigned char *line_buffer, *yuyv; // 计数器 int z; // 已经写入的字节数 static int written; // 分配一行像素数据的缓冲区 line_buffer = calloc(vd->width * 3, 1); // 获取YUYV格式的帧缓冲区 yuyv = vd->framebuffer; // 初始化jpeg错误管理器 cinfo.err = jpeg_std_error(&jerr); // 创建jpeg压缩结构体 jpeg_create_compress(&cinfo); // 将压缩后的数据存储到内存中 dest_buffer(&cinfo, buffer, size, &written); // 设置图像的宽、高、颜色空间等信息 cinfo.image_width = vd->width; cinfo.image_height = vd->height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; // 设置jpeg压缩参数 jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); // 开始压缩 jpeg_start_compress(&cinfo, TRUE); // 遍历每一行像素数据 z = 0; while(cinfo.next_scanline < vd->height) { int x; unsigned char *ptr = line_buffer; // 遍历每一个像素点 for(x = 0; x < vd->width; x++) { int r, g, b; int y, u, v; // 获取YUV值 if(!z) y = yuyv[0] << 8; else y = yuyv[2] << 8; u = yuyv[1] - 128; v = yuyv[3] - 128; // 转换为RGB值 r = (y + (359 * v)) >> 8; g = (y - (88 * u) - (183 * v)) >> 8; b = (y + (454 * u)) >> 8; // 存储RGB值 *(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r); *(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g); *(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b); // 更新YUV值 if(z++) { z = 0; yuyv += 4; } } // 存储一行像素数据 row_pointer[0] = line_buffer; jpeg_write_scanlines(&cinfo, row_pointer, 1); } // 压缩结束 jpeg_finish_compress(&cinfo); // 销毁jpeg压缩结构体 jpeg_destroy_compress(&cinfo); // 释放缓冲区 free(line_buffer); // 返回已经写入的字节数 return (written); }
memcpy_picture
这段代码是用于复制图像数据的函数 memcpy_picture。以下是对函数的概括总结:
函数接受三个参数:目标缓冲区 out,源数据缓冲区 buf,以及源数据大小 size。
首先检查源数据是否使用哈夫曼编码,通过调用 is_huffman 函数来判断。
如果源数据不是哈夫曼编码:
初始化指针 ptdeb、ptlimit 和 ptcur,分别指向源数据的起始位置、结束位置和当前位置。
在源数据中查找标识为 0xffc0 的位置,表示图像数据的起始。
如果没有找到标识,说明源数据不完整,函数返回当前位置 pos。
计算需要复制的前半部分的大小 sizein。
将前半部分复制到目标缓冲区 out 中,并更新 pos。
将 DHT 数据复制到目标缓冲区 out 中,并更新 pos。
将后半部分复制到目标缓冲区 out 中,并更新 pos。
如果源数据使用哈夫曼编码:
将源数据直接复制到目标缓冲区 out 中,并更新 pos。
返回当前位置 pos。
该函数用于将图像数据复制到目标缓冲区,并根据是否使用哈夫曼编码进行相应的处理。如果源数据不完整或者不是哈夫曼编码,函数将返回当前复制的位置,否则返回复制完成后的位置。
// 复制图片 int memcpy_picture(unsigned char *out, unsigned char *buf, int size) { unsigned char *ptdeb, *ptlimit, *ptcur = buf; int sizein, pos = 0; if(!is_huffman(buf)) { // 如果不是哈夫曼编码 ptdeb = ptcur = buf; // 设置起始位置 ptlimit = buf + size; // 设置结束位置 while((((ptcur[0] << 8) | ptcur[1]) != 0xffc0) && (ptcur < ptlimit)) // 查找0xffc0 ptcur++; // 移动指针 if(ptcur >= ptlimit) // 如果指针超出范围 return pos; // 返回当前位置 sizein = ptcur - ptdeb; // 计算需要复制的大小 memcpy(out + pos, buf, sizein); pos += sizein; // 复制前半部分 memcpy(out + pos, dht_data, sizeof(dht_data)); pos += sizeof(dht_data); // 复制DHT数据 memcpy(out + pos, ptcur, size - sizein); pos += size - sizein; // 复制后半部分 } else { // 如果是哈夫曼编码 memcpy(out + pos, ptcur, size); pos += size; // 直接复制 } return pos; // 返回当前位置
}
输出初始化output_init
该函数接受两个参数:一个名为 param 的指向 output_parameter 结构体的指针和一个整数 id。
以下是代码的执行过程:
初始化变量:
port 被设为 htons(8080) 的结果,将端口号转换为网络字节顺序。
credentials 和 www_folder 被设为 NULL。
nocommands 被设为 0。
使用 DBG 宏打印调试信息,指示输出编号。
将 param->argv(一个字符串数组)的第一个元素设为 OUTPUT_PLUGIN_NAME。
使用 getopt_long_only 循环遍历命令行选项:
如果遇到无法识别的选项,调用 help 函数并返回 1。
否则,根据 option_index 的值进行切换:
Case 0 和 1:使用 help 函数打印帮助信息并返回 1。
Case 2 和 3:解析端口选项(-p 或 --port),将提供的值转换为网络字节顺序后设置给 port。
Case 4 和 5:解析凭证选项(-c 或 --credentials),为 credentials 分配内存并复制提供的值。
Case 6 和 7:解析 WWW 选项(-w 或 --www),为 www_folder 分配内存并复制提供的值。如果值不以斜杠结尾,则添加斜杠。
Case 8 和 9:将 nocommands 设为 1,表示禁用命令。
根据解析的选项设置服务器的配置值:
将 servers[param->id].id 设为 param->id。
将 servers[param->id].pglobal 设为 param->global。
将 servers[param->id].conf.port 设为 port。
将 servers[param->id].conf.credentials 设为 credentials。
将 servers[param->id].conf.www_folder 设为 www_folder。
将 servers[param->id].conf.nocommands 设为 nocommands。
使用 OPRINT 宏打印配置值。
返回 0 表示初始化成功。
int output_init(output_parameter *param, int id) { int i; int port; char *credentials, *www_folder; char nocommands; DBG("output #%02d\n", param->id); port = htons(8080); credentials = NULL; www_folder = NULL; nocommands = 0; param->argv[0] = OUTPUT_PLUGIN_NAME; /* show all parameters for DBG purposes */ for(i = 0; i < param->argc; i++) { DBG("argv[%d]=%s\n", i, param->argv[i]); } reset_getopt(); while(1) { int option_index = 0, c = 0; static struct option long_options[] = { {"h", no_argument, 0, 0 }, {"help", no_argument, 0, 0}, {"p", required_argument, 0, 0}, {"port", required_argument, 0, 0}, {"c", required_argument, 0, 0}, {"credentials", required_argument, 0, 0}, {"w", required_argument, 0, 0}, {"www", required_argument, 0, 0}, {"n", no_argument, 0, 0}, {"nocommands", no_argument, 0, 0}, {0, 0, 0, 0} }; c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index); /* no more options to parse */ if(c == -1) break; /* unrecognized option */ if(c == '?') { help(); return 1; } switch(option_index) { /* h, help */ case 0: case 1: DBG("case 0,1\n"); help(); return 1; break; /* p, port */ case 2: case 3: DBG("case 2,3\n"); port = htons(atoi(optarg)); break; /* c, credentials */ case 4: case 5: DBG("case 4,5\n"); credentials = strdup(optarg); break; /* w, www */ case 6: case 7: DBG("case 6,7\n"); www_folder = malloc(strlen(optarg) + 2); strcpy(www_folder, optarg); if(optarg[strlen(optarg)-1] != '/') strcat(www_folder, "/"); break; /* n, nocommands */ case 8: case 9: DBG("case 8,9\n"); nocommands = 1; break; } } servers[param->id].id = param->id; servers[param->id].pglobal = param->global; servers[param->id].conf.port = port; servers[param->id].conf.credentials = credentials; servers[param->id].conf.www_folder = www_folder; servers[param->id].conf.nocommands = nocommands; OPRINT("www-folder-path...: %s\n", (www_folder == NULL) ? "disabled" : www_folder); OPRINT("HTTP TCP port.....: %d\n", ntohs(port)); OPRINT("username:password.: %s\n", (credentials == NULL) ? "disabled" : credentials); OPRINT("commands..........: %s\n", (nocommands) ? "disabled" : "enabled"); return 0; }