mjpg-streamer实现细节分析(上)

简介: mjpg-streamer实现细节分析

输入初始化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, &currentFormat) == 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;
}

目录
相关文章
|
3月前
|
传感器 C++ 计算机视觉
【opencv3】详述PnP测距完整流程(附C++代码)
【opencv3】详述PnP测距完整流程(附C++代码)
|
9月前
|
存储 JSON 网络协议
mjpg-streamer实现细节分析(下)
mjpg-streamer实现细节分析(下)
134 0
mjpg-streamer实现细节分析(下)
|
8月前
|
缓存 iOS开发 C++
iOS 逆向编程(二十三)dsc_extractor 动态库提取器
iOS 逆向编程(二十三)dsc_extractor 动态库提取器
122 1
|
9月前
|
存储 网络协议 数据安全/隐私保护
mjpg-streamer框架分析
mjpg-streamer框架分析
46 0
|
9月前
|
编解码 C++
UVC调用过程部分细节分析
UVC调用过程部分细节分析
359 0
|
10月前
|
JSON Go API
如何基于 zap 封装一个更好用的日志库
如何基于 zap 封装一个更好用的日志库
342 0
|
11月前
|
传感器
【Magisk模块】QSC定量停充
【Magisk模块】QSC定量停充
992 0
|
人工智能 算法 人机交互
基于内容的图像检索系统 课设总结分析 01 Image retrieval Pipeline
基于内容的图像检索系统 课设总结分析 01 Image retrieval Pipeline
111 0
基于内容的图像检索系统 课设总结分析 01 Image retrieval Pipeline
|
存储 机器学习/深度学习 算法
Python 实现LSB算法进行信息隐藏 包含空域与变换域 JPEG信息隐藏算法 对PDF文件进行信息隐藏 基于卷积神经网络的隐写分析 Matlab SRM、SCA隐写分析
Python 实现LSB算法进行信息隐藏 包含空域与变换域 JPEG信息隐藏算法 对PDF文件进行信息隐藏 基于卷积神经网络的隐写分析 Matlab SRM、SCA隐写分析
396 0
Python 实现LSB算法进行信息隐藏 包含空域与变换域 JPEG信息隐藏算法 对PDF文件进行信息隐藏 基于卷积神经网络的隐写分析 Matlab SRM、SCA隐写分析
|
Linux API Python
wrf模式学习记录--使用ERA5数据驱动WRF模式三层嵌套:数据下载以及模式处理
wrf模式学习记录--使用ERA5数据驱动WRF模式三层嵌套:数据下载以及模式处理
wrf模式学习记录--使用ERA5数据驱动WRF模式三层嵌套:数据下载以及模式处理