为了账号安全,请及时绑定邮箱和手机立即绑定

作业社区

探索学习新天地,共享知识资源!

0 提交作业
0 布置作业
0 满分作业
得分 100
学习任务

枝wenz_fpJNR0 的学生作业:

#include #include #include #include #include #include #include #include #include #define FIFO_PATH "./my_fifo" #define BUFFER_SIZE 256 int main(int argc, char const *argv[]) { int maxfd;//最大文件描述符 int fd;//有名管道文件描述符 int ret_read;// read函数返回值 int ret_select;// select函数返回值 int ret_access;// 有名管道函数返回值 char buf[BUFFER_SIZE]={0};// 读取缓冲区 struct timeval tv = {10,0};// 超时时间10秒 fd_set readfds,tmpfds; // 读fds集合和临时fds集合 // 检查有名管道FIFO是否存在 ret_access = access(FIFO_PATH, F_OK); if(ret_access == -1){// 如果FIFO不存在 mkfifo(FIFO_PATH, 0666);// 创建FIFO } printf("以只读模式打开FIFO有名管道\n"); fd = open(FIFO_PATH, O_RDONLY);// 打开FIFO为读模式 if(fd == -1){// 如果打开失败 perror("[ERROR] open() : "); exit(EXIT_FAILURE); } FD_ZERO(&readfds);// 初始化读fds集合为空 FD_SET(fd, &readfds);// 将有名管道文件描述符fd添加到读fds集合中 FD_SET(STDIN_FILENO, &readfds);// 将标准输入文件描述符添加到读fds集合中 //用select函数循环监听 while(1){ tmpfds = readfds;// 复制读fds集合到临时fds集合(select函数会修改读fds集合,所以每次需要重置临时fds集合) maxfd = fd > STDIN_FILENO ? fd : STDIN_FILENO;// 找到最大文件描述符 ret_select = select(maxfd+1, &tmpfds, NULL, NULL, &tv);// 监听读fds集合中的文件描述符 if(ret_select == -1){// 如果select函数返回-1 perror("[ERROR] select() : "); exit(EXIT_FAILURE); } if(ret_select == 0){// 如果select函数返回0 printf("select函数超时\n"); continue; } if(FD_ISSET(fd, &tmpfds)){// 如果有名管道文件描述符fd在读fds集合中 memset(buf, 0, BUFFER_SIZE);// 清空读取缓冲区 ret_read = read(fd, buf, BUFFER_SIZE-1);// 读有名管道文件描述符fd if(ret_read == -1){// 如果读取失败 perror("[ERROR] read() : "); exit(EXIT_FAILURE); } if(ret_read == 0){// 如果读取到0字节 printf("有名管道文件描述符fd关闭\n"); break; } printf("有名管道文件描述符fd读取到:%s", buf); // 如果数据不以换行结尾,添加换行 if (buf[strlen(buf) - 1] != '\n') { printf("\n"); } } tv.tv_sec = 10;// 重置超时时间为10秒 } close(fd);// 关闭有名管道文件描述符fd unlink(FIFO_PATH);// 删除FIFO有名管道 return 0; } #include #include #include #include #include #include #include #include #define FIFO_NAME "./my_fifo" //有名管道文件名 #define BUFFER_SIZE 256 int main(int argc, char const *argv[]) { int fd;// 有名管道文件描述符 int ret_access;// 有名管道函数返回值 int ret_write;// write函数返回值 int maxfd;// 最大文件描述符 char buf[BUFFER_SIZE]={0};// 读取缓冲区 struct timeval tv = {3,0};// 超时时间3秒 ret_access = access(FIFO_NAME, F_OK);// 检查FIFO是否存在 if(ret_access == -1){// 如果FIFO不存在 mkfifo(FIFO_NAME, 0666);// 创建FIFO } printf("正在以只写模式打开FIFO有名管道\n"); fd = open(FIFO_NAME, O_WRONLY);// 打开FIFO为写模式 if(fd == -1){// 如果打开失败 perror("[ERROR] open() :");// 打印错误信息 exit(EXIT_FAILURE);// 退出程序 } printf("FIFO有名管道打开成功\n"); fd_set readfds,tmpfds; // 读fds集合和临时fds集合 FD_ZERO(&readfds);// 初始化读fds集合为空 FD_SET(fd, &readfds);// 将fd添加到读fds集合中 //循环向有名管道写入数据 while(1){ printf("> "); fflush(stdout);// 刷新标准缓冲区 memset(buf, 0, BUFFER_SIZE);// 清空读取缓冲区 fgets(buf, BUFFER_SIZE, stdin);// 从标准输入读取数据 if(strcmp(buf, "exit\n") == 0){// strcmp比较buf是否等于exit,如果等于则退出 break; } //去除buf末尾的换行符 buf[strcspn(buf, "\n")] = 0;//strcspn() 返回字符串中第一次出现 "\n" 的位置索引,如果找到换行符,将其替换为 \0,如果没找到换行符,返回字符串长度(指向 \0),赋值给自身(无影响) ret_write = write(fd, buf, strlen(buf));// 向有名IFO写入数据 if(ret_write == -1){// 如果写入失败 perror("[ERROR] write() :");// 打印错误信息 exit(EXIT_FAILURE);// 退出程序 } printf("向有名管道写入数据成功!\n"); } close(fd);// 关闭文件描述符 return 0; }

得分 100
学习任务

学无止境呀呀呀 的学生作业:

camera.c #include "camera.h" #include #include #include #include #include #include #include #include #include #include #include /* * camera.c 封装了本示例的完整采集链路: * 1. 打开 /dev/videoX 并确认它支持 V4L2 视频采集 * 2. 把采集格式固定为 640x480 的 YUYV * 3. 申请内核采集缓冲区,并通过 mmap 映射到用户空间 * 4. 启动 streaming,让驱动持续向缓冲区写入图像 * 5. 取出一帧,分别保存成 picture.yuv、picture.rgb,以及一组不同质量的 JPEG * 6. 停止采集并释放所有资源 * * 这个示例选择 mmap + streaming 模式,是因为它最能体现 V4L2 * 采集的标准工作流:应用程序不自己 malloc 大块帧缓冲,而是让驱动 * 分配 buffer,再映射到用户空间直接访问。 */ /* 保存驱动分配并映射到用户空间的所有缓冲区信息。 */ static struct camera_t *camera_data; /* 记录驱动实际分配到的缓冲区数量。 */ static unsigned int n_buffer; /* 标记是否已经对设备执行过 STREAMON。 */ static int camera_streaming; /* * libjpeg 默认的错误处理方式比较“激烈”: * 一旦内部发现严重错误,可能直接结束当前流程。 * * 为了让在出错时仍然能回到自己的清理代码里释放资源, * 这里额外包了一层错误上下文: * 1. pub 是 libjpeg 规定的标准错误管理结构 * 2. setjmp_buffer 用来保存“返回点” * 3. message 用来保存可读的错误文本 */ struct jpeg_error_context { struct jpeg_error_mgr pub; jmp_buf setjmp_buffer; char message[JMSG_LENGTH_MAX]; }; /* * 当 libjpeg 内部出错时,会回调到这里。 * 这里不直接退出程序,而是: * 1. 先把错误信息格式化成字符串 * 2. 再通过 longjmp 跳回 yuyv_to_jpeg() 里的 setjmp 检查点 * * 这样调用者就还能执行 fclose/free/jpeg_destroy_compress 等清理动作。 */ static void jpeg_error_exit(j_common_ptr cinfo) { struct jpeg_error_context *err = (struct jpeg_error_context *)cinfo->err; (*cinfo->err->format_message)(cinfo, err->message); longjmp(err->setjmp_buffer, 1); } /* * 对 ioctl 做一层简单封装: * 如果系统调用被信号中断(EINTR),就自动重试一次, * 避免上层到处重复写相同的错误处理。 */ static int xioctl(int fd, unsigned long request, void *arg) { int ret; do { ret = ioctl(fd, request, arg); } while (ret < 0 && errno == EINTR); return ret; } /* * RGB 每个颜色分量都只占 1 个字节,因此合法范围只能是 0~255。 * YUV -> RGB 的公式是浮点运算,算出来的结果可能会小于 0 或大于 255, * 所以最后必须做一次“截断”,避免写出非法颜色值。 */ static unsigned char clip_color(int value) { if (value < 0) { return 0; } if (value > 255) { return 255; } return (unsigned char)value; } /* * 把一行 YUYV 数据转换成一行 RGB888 数据。 * * 先把几个概念说清楚: * 1. Y 表示亮度,可以简单理解为“明暗” * 2. U/V 表示色度,可以简单理解为“颜色往哪边偏” * 3. RGB 则是更熟悉的红、绿、蓝三种颜色强度 * * 当前代码处理的是 YUYV 格式。 * 会把它和 “YUV4”“YVU4” 之类的名字混着叫, * 但这里真正的字节排列是: * Y0 U Y1 V * * 这 4 个字节描述的是两个相邻像素,而不是一个像素: * 1. 第 1 个像素使用自己的亮度 Y0,共享色度 U/V * 2. 第 2 个像素使用自己的亮度 Y1,也共享同一组 U/V * * 为什么两个像素共用 U/V? * 因为人眼对亮度变化更敏感,对颜色细节没那么敏感。 * 所以这类格式会让两个相邻像素共用一组颜色信息,从而节省带宽和存储空间。 * * 输入一行占 width * 2 字节: * 每 2 个像素只需要 4 个字节,所以平均每个像素占 2 字节。 * * 输出一行占 width * 3 字节: * RGB888 中每个像素都要单独保存 R/G/B 三个分量,因此每个像素占 3 字节。 */ static void convert_yuyv_row_to_rgb(const unsigned char *src, unsigned char *dst, int width) { for (int col = 0; col < width; col += 2) { /* * 每次循环处理 2 个像素,所以 col 一次加 2。 * 这 2 个像素刚好对应 src 当前指向的 4 个字节: * src[0] -> 第 1 个像素的亮度 Y0 * src[1] -> 两个像素共享的色度 U * src[2] -> 第 2 个像素的亮度 Y1 * src[3] -> 两个像素共享的色度 V */ int y0 = src[0]; int u = src[1]; int y1 = src[2]; int v = src[3]; /* * 下面三行是在计算“第 1 个像素”的 R/G/B。 * * 为什么公式里有 (u - 128)、(v - 128)? * 因为 U/V 在字节里通常是以 128 为中心存储的: * 1. 128 左右可理解为“颜色偏移不大” * 2. 小于 128 表示往一个方向偏 * 3. 大于 128 表示往另一个方向偏 * * 所以在代入公式前,先减 128,才能把它还原成“以 0 为中心”的偏移量。 * * 公式本身可以先不用死记,只要知道: * 1. R 主要受 Y 和 V 影响 * 2. G 同时受 Y、U、V 影响 * 3. B 主要受 Y 和 U 影响 */ dst[0] = clip_color((int)(y0 + 1.402 * (v - 128))); dst[1] = clip_color((int)(y0 - 0.344 * (u - 128) - 0.714 * (v - 128))); dst[2] = clip_color((int)(y0 + 1.772 * (u - 128))); /* * 下面三行计算“第 2 个像素”的 R/G/B。 * 注意它和第 1 个像素唯一的区别,是亮度换成了 y1; * U/V 仍然是同一组共享色度。 */ dst[3] = clip_color((int)(y1 + 1.402 * (v - 128))); dst[4] = clip_color((int)(y1 - 0.344 * (u - 128) - 0.714 * (v - 128))); dst[5] = clip_color((int)(y1 + 1.772 * (u - 128))); /* * 当前这一轮已经把 4 字节 YUYV 变成了 6 字节 RGB: * 1. src 前进 4,去读下一组 YUYV * 2. dst 前进 6,去写下一组 RGB */ src += 4; dst += 6; } } /* * 按二进制方式把一段“裸数据”原样写入文件。 * 这里不附加任何文件头,因此输出文件只是纯字节流: * picture.yuv 是原始 YUYV 数据,picture.rgb 是原始 RGB888 数据。 */ static int write_binary_file(const char *file_name, const void *data, size_t length) { FILE *fp = fopen(file_name, "wb"); if (fp == NULL) { perror(file_name); return -1; } if (fwrite(data, 1, length, fp) != length) { perror(file_name); fclose(fp); return -1; } if (fclose(fp) != 0) { perror(file_name); return -1; } return 0; } /* * 打印一张 JPEG 文件的体积信息,便于观察质量和文件大小的权衡。 */ static int print_jpeg_size_summary(const char *file_name, int quality) { struct stat st = {0}; if (stat(file_name, &st) < 0) { perror(file_name); return -1; } printf("%-8d %-18s %-12lld %.2f\n", quality, file_name, (long long)st.st_size, (double)st.st_size / 1024.0); return 0; } /* * 用同一帧 YUYV 数据导出一组不同压缩质量的 JPEG, * 并打印每张图对应的文件大小。 */ static int export_jpeg_quality_series(const unsigned char *yuv, int width, int height) { static const int jpeg_qualities[] = {10, 20, 30, 40, 50, 60, 70, 80, 90}; char file_name[32]; if (yuv == NULL || width > 8) & 0xFF, (fmt.pixelformat >> 16) & 0xFF, (fmt.pixelformat >> 24) & 0xFF, fmt.description); ++fmt.index; } /* * 向驱动申请示例想使用的采集格式。 * 这里固定请求 YUYV,是因为后续 read_camera() 会把拿到的数据 * 直接按 YUYV 的内存布局解释并转换为 RGB。 */ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format.fmt.pix.width = IMG_WIDTH; format.fmt.pix.height = IMG_HEIGHT; format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; format.fmt.pix.field = V4L2_FIELD_INTERLACED; /* 如果驱动不接受这个格式,后面按固定长度解释图像数据就会出错。 */ if (xioctl(fd, VIDIOC_S_FMT, &format) < 0) { perror("VIDIOC_S_FMT failure"); close(fd); return -1; } /* * S_FMT 并不保证驱动一定完全按请求值接受。 * 某些设备会悄悄改成别的分辨率或像素格式,所以这里必须二次核对。 * 一旦驱动实际给出的不是 640x480 YUYV,后面按固定长度写文件、 * 按 YUYV 公式转 RGB 就都会出错。 */ if (format.fmt.pix.width != IMG_WIDTH || format.fmt.pix.height != IMG_HEIGHT || format.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) { fprintf(stderr, "camera did not accept %dx%d YUYV format\n", IMG_WIDTH, IMG_HEIGHT); close(fd); return -1; } return fd; } /* * 申请一组 V4L2 采集缓冲区,并映射到用户空间。 * * 这一阶段对应 streaming 模式中最关键的一段准备工作: * REQBUFS -> QUERYBUF -> mmap -> QBUF * * 完成后,camera_data[] 中保存了每块缓冲区的地址和长度, * 而且这些空 buffer 都已经回送给驱动,驱动随时可以往里写入图像。 * * 返回值: * 成功返回 0;失败返回 -1。 * 失败时若前面已经申请了部分资源,调用者仍应执行 cleanup_camera()。 */ int init_mmap(int fd) { /* 用于向驱动申请一组采集缓冲区。 */ struct v4l2_requestbuffers reqbuf = {0}; /* * 一次申请 4 块 buffer。 * 对这个只抓一帧的示例来说,数量并不需要很大,但多块缓冲区是 * streaming 模式的常见做法,可以让驱动和应用程序之间更顺畅地轮转。 */ reqbuf.count = 4; reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = V4L2_MEMORY_MMAP; /* 让驱动在内核空间分配缓冲区。 */ if (xioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) { perror("VIDIOC_REQBUFS failure"); return -1; } /* 驱动可能返回比请求值更少的 buffer,0 表示申请失败。 */ if (reqbuf.count == 0) { fprintf(stderr, "driver did not allocate capture buffers\n"); return -1; } /* * 在用户空间准备一个数组,记录每块 mmap buffer 的元信息。 * 注意:真正的大块图像内存仍然由驱动分配,这里只保存映射地址和长度。 */ camera_data = calloc(reqbuf.count, sizeof(*camera_data)); if (camera_data == NULL) { perror("calloc failure"); return -1; } n_buffer = reqbuf.count; /* * QUERYBUF 用来询问“第 i 块 buffer 在驱动里长什么样”: * 它会告诉这块缓冲区的长度,以及它在设备内存中的偏移位置。 * 有了这些信息,用户进程才能用 mmap 把它映射进自己的地址空间。 */ for (unsigned int i = 0; i < n_buffer; ++i) { struct v4l2_buffer buf = {0}; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (xioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) { perror("VIDIOC_QUERYBUF failure"); return -1; } camera_data[i].length = buf.length; /* * 把第 i 个内核 buffer 映射到当前进程的地址空间。 * 映射成功后,camera_data[i].start 就是这块缓冲区在用户态看到的起始地址, * 之后驱动把图像写进 buffer,应用程序就能直接从这个地址读取像素数据。 */ camera_data[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (camera_data[i].start == MAP_FAILED) { camera_data[i].start = NULL; perror("mmap failure"); return -1; } } /* * 映射完成后,还要做一次“预入队”: * 把每块空 buffer 放回驱动输入队列。 * 只有已经 QBUF 的 buffer,驱动才会把采集到的图像填进去。 * 如果忘了这一步,即使后面 STREAMON 成功,驱动也没有可写入的目标 buffer。 */ for (unsigned int i = 0; i < n_buffer; ++i) { struct v4l2_buffer buf = {0}; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (xioctl(fd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF failure"); return -1; } } return 0; } /* * 通知驱动正式开始采集视频流。 * * 调用前提: * init_mmap() 已成功,且至少有一块 buffer 已经 QBUF 入队。 * * 返回值: * 成功返回 0;失败返回 -1。 */ int start_camera(int fd) { /* STREAMON 之后,驱动才真正开始往已入队的 buffer 写数据。 */ enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (xioctl(fd, VIDIOC_STREAMON, &type) < 0) { perror("VIDIOC_STREAMON failure"); return -1; } camera_streaming = 1; return 0; } /* * 读取一帧已经采集完成的图像,并写出一组文件: * 1. picture.yuv: 驱动给出的原始 YUYV 帧数据 * 2. picture.rgb: 把同一帧转换成 RGB888 后的裸数据 * 3. picture_q10.jpg ~ picture_q90.jpg: 把同一帧压缩成不同质量的 JPEG 图片 * * 这个函数体现了 streaming 模式下 buffer 的典型生命周期: * 1. DQBUF: 从驱动取出一块“已经装满数据”的 buffer * 2. 处理这块 buffer 里的内容 * 3. QBUF: 处理完成后把 buffer 再次放回驱动队列 * * 注意: * 一旦 DQBUF 成功,哪怕后续写文件或转换失败,也应该尽量把 buffer 回队, * 否则驱动手里的可用 buffer 会越来越少。 * * 返回值: * 成功返回 0;失败返回 -1。 */ int read_camera(int fd) { /* 用于从输出队列中取出一块已经装满图像数据的 buffer。 */ struct v4l2_buffer buf = {0}; const unsigned char *frame_ptr; size_t frame_len; /* 640x480 的 YUYV 一帧占用 width * height * 2 字节。 */ const size_t expected_frame_len = (size_t)IMG_WIDTH * (size_t)IMG_HEIGHT * 2U; int status = -1; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; /* * DQBUF 表示“出队”: * 从驱动的完成队列里取出一块已经写好图像的 buffer。 * 调用成功后,buf.index 会指出是哪一块 buffer 已经准备完毕。 */ if (xioctl(fd, VIDIOC_DQBUF, &buf) < 0) { perror("VIDIOC_DQBUF failure"); return -1; } /* 防御检查:驱动返回的索引必须能在本地数组中找到。 */ if (buf.index >= n_buffer || camera_data[buf.index].start == NULL) { fprintf(stderr, "invalid capture buffer index %u\n", buf.index); goto requeue_buffer; } frame_ptr = (const unsigned char *)camera_data[buf.index].start; /* * bytesused 是“这一帧真正有效的数据长度”。 * 它和 buffer 总长度不是一个概念: * 1. buf.length / camera_data[i].length 是整块缓冲区容量 * 2. bytesused 是这次采集实际写进来的字节数 * * 写 picture.yuv 时应该优先使用 bytesused,避免把缓冲区里未使用的尾部空间 * 也一起写到文件里。 */ frame_len = buf.bytesused; if (frame_len == 0) { fprintf(stderr, "captured frame is empty\n"); goto requeue_buffer; } if (frame_len > camera_data[buf.index].length) { frame_len = camera_data[buf.index].length; } /* 先保存驱动给出的原始 YUYV 数据,方便后续分析。 */ if (write_binary_file("picture.yuv", frame_ptr, frame_len) < 0) { goto requeue_buffer; } /* * 640x480 YUYV 的理论帧长 = width * height * 2。 * YUYV 每个像素平均占 2 字节,因此长度不足时不能继续按整帧去做 * YUV->RGB 转换,否则会越界读取或得到残缺图像。 */ if (frame_len < expected_frame_len) { fprintf(stderr, "captured frame is too short for %dx%d YUYV data\n", IMG_WIDTH, IMG_HEIGHT); goto requeue_buffer; } /* 再把这帧原始数据转换成 RGB 字节流。 */ if (yuyv_to_rgb(frame_ptr, "picture.rgb", IMG_WIDTH, IMG_HEIGHT) < 0) { goto requeue_buffer; } /* * 用同一帧图像导出多张不同质量的 JPEG, * 便于直接观察 10~90 质量档位在体积和视觉效果上的差别。 */ if (export_jpeg_quality_series(frame_ptr, IMG_WIDTH, IMG_HEIGHT) < 0) { goto requeue_buffer; } status = 0; requeue_buffer: /* * 不管处理成功还是失败,都要把这块 buffer 再次 QBUF 回输入队列。 * 这是 streaming 模式的核心规则之一: * 应用程序并不“拥有”这些 buffer,只是在 DQBUF 和下一次 QBUF 之间 * 暂时借用它们处理数据。 */ if (xioctl(fd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF failure"); return -1; } return status; } /* * 统一释放采集过程中可能持有的所有资源。 * * 设计目标是“可以安全地处理部分初始化成功的情况”: * 即使某一步中途失败,调用者也可以直接跳到这里统一收尾。 */ void cleanup_camera(int fd) { /* * 清理顺序之所以这样安排,是为了符合资源依赖关系: * 1. 先 STREAMOFF,停止驱动继续使用这些 buffer * 2. 再 munmap,解除用户态到内核 buffer 的映射 * 3. 释放保存映射信息的用户态数组 * 4. 最后 close 设备 fd */ if (fd >= 0 && camera_streaming) { enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (xioctl(fd, VIDIOC_STREAMOFF, &type) < 0) { perror("VIDIOC_STREAMOFF failure"); } camera_streaming = 0; } if (camera_data != NULL) { for (unsigned int i = 0; i < n_buffer; ++i) { if (camera_data[i].start != NULL) { munmap(camera_data[i].start, camera_data[i].length); } } free(camera_data); camera_data = NULL; } n_buffer = 0; if (fd >= 0) { close(fd); } } /* * 把一帧 YUYV 数据转换成 RGB888 原始字节流并写入文件。 * * 参数约束: * 1. yuv 必须指向完整的一帧 YUYV 数据 * 2. width 必须为偶数,因为 YUYV 每 4 字节描述 2 个像素 * 3. 输出文件是裸 RGB 数据,不带 BMP/PNG 等文件头 * * 返回值: * 成功返回 0;失败返回 -1。 */ int yuyv_to_rgb(const unsigned char *yuv, const char *fileName, int width, int height) { FILE *fp = NULL; /* * line_buf 用来保存“当前一整行”的 RGB 数据。 * 这里不一次性申请整帧 RGB 图像,而是每次只处理一行: * 1. 内存更省 * 2. 处理流程更直观 * 3. 后面写 JPEG 时也能复用这种“逐行处理”的思路 */ unsigned char *line_buf = NULL; /* * src 始终指向“还没处理到的 YUYV 数据起点”。 * 每完成一行转换,就往后移动一整行的 YUYV 字节数。 */ const unsigned char *src = yuv; size_t line_len; int status = -1; if (yuv == NULL || fileName == NULL) { return -1; } if (width QBUF 的完整流程。 */ if (read_camera(cam_fd) < 0) { goto cleanup; } status = EXIT_SUCCESS; cleanup: /* * 第六步:统一收尾。 * 不论前面是初始化失败、等待超时还是成功拿到一帧,最后都走这里。 * cleanup_camera() 会按“streamoff -> munmap -> free -> close”的顺序释放资源, * 因此主流程可以保持直线结构,不需要在每个失败分支重复写清理代码。 */ cleanup_camera(cam_fd); return status; } 输出 quality file bytes KB 10 picture_q10.jpg 6333 6.18 20 picture_q20.jpg 7304 7.13 30 picture_q30.jpg 8239 8.05 40 picture_q40.jpg 9305 9.09 50 picture_q50.jpg 10669 10.42 60 picture_q60.jpg 12473 12.18 70 picture_q70.jpg 15653 15.29 80 picture_q80.jpg 21587 21.08 90 picture_q90.jpg 36616 35.76

首页上一页1234567下一页尾页
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号