当前位置: 首页 > news >正文

Linux-Ubuntu16.04摄像头 客户端抓取帧并保存为PNG

 1.0:client.c抓取帧并保存为PNG

#include <stdio.h>      // 标准输入输出库
#include <stdlib.h>     // 标准库,包含内存分配等函数
#include <string.h>     // 字符串操作库
#include <linux/videodev2.h> // V4L2 视频设备接口库
#include <sys/ioctl.h>  // 输入输出控制库
#include <fcntl.h>      // 文件控制定义
#include <unistd.h>     // UNIX 标准定义
#include <sys/mman.h>   // 内存映射库
#include <png.h>        // PNG 图像处理库// 定义摄像头设备文件和图像分辨率
#define CAM_DEV "/dev/video0" // 摄像头设备文件路径
#define WIDTH 640             // 图像宽度
#define HEIGHT 480            // 图像高度
#define NB_BUFFER 4           // 缓冲区数量// 定义用于存储图像数据的结构体
struct pic_data {unsigned char *tmpbuffer[NB_BUFFER];  // 存储每个缓冲区的指针unsigned int tmpbytesused[NB_BUFFER]; // 存储每个缓冲区的实际字节数
} pic;// 定义摄像头文件描述符
int cam_fd;// 声明函数
int v4l2_init(void);         // 初始化摄像头
int v4l2Grab(void);          // 抓取图像
int v4l2_close(void);        // 关闭摄像头
int CLAMP(int value, int min, int max); // 辅助函数,用于限制数值范围
int yuv422_rgb24(unsigned char *yuv_buf, unsigned char *rgb_buf, unsigned int width, unsigned int height); // YUV422 转 RGB24
void write_data_to_png(const char *png_name, png_uint_32 width, png_uint_32 height, png_bytepp data, int color_type); // 将 RGB 数据写入 PNG 文件// 定义用于存储 RGB 数据的结构体
typedef struct pixel_RGB {png_byte red, green, blue; // 每个像素的红、绿、蓝分量
} pixel_RGB;int main(int argc, char* argv[]) {// 初始化摄像头if (v4l2_init() == -1) {fprintf(stderr, "Failed to initialize camera.\n");return -1;}// 从摄像头抓取图像if (v4l2Grab() == -1) {fprintf(stderr, "Failed to grab image from camera.\n");v4l2_close();return -1;}// 分配 RGB 缓冲区unsigned char *rgb_buf = (unsigned char *)malloc(WIDTH * HEIGHT * 3);if (!rgb_buf) {fprintf(stderr, "Failed to allocate RGB buffer.\n");v4l2_close();return -1;}// 将 YUV 数据转换为 RGB 数据yuv422_rgb24(pic.tmpbuffer[0], rgb_buf, WIDTH, HEIGHT);// 分配 PNG 图像数据pixel_RGB *image_data_rgb = calloc(WIDTH * HEIGHT, sizeof(pixel_RGB));if (!image_data_rgb) {fprintf(stderr, "Failed to allocate image data for PNG.\n");free(rgb_buf);v4l2_close();return -1;}// 将 RGB 数据复制到 PNG 图像数据结构体中for (unsigned int i = 0; i < WIDTH * HEIGHT; i++) {image_data_rgb[i].red = rgb_buf[i * 3 + 2];image_data_rgb[i].green = rgb_buf[i * 3 + 1];image_data_rgb[i].blue = rgb_buf[i * 3 + 0];}// 保存为 PNG 图像const char *png_name = "camera_image.png";write_data_to_png(png_name, WIDTH, HEIGHT, (png_bytepp)image_data_rgb, PNG_COLOR_TYPE_RGB);// 释放资源free(rgb_buf);free(image_data_rgb);v4l2_close();return 0;
}// 初始化摄像头
int v4l2_init(void) {int i;int ret = 0;// 打开摄像头设备if ((cam_fd = open(CAM_DEV, O_RDWR)) == -1) {perror("ERROR opening V4L interface.");return -1;}// 判断设备是否为摄像头struct v4l2_capability cam_cap;if (ioctl(cam_fd, VIDIOC_QUERYCAP, &cam_cap) == -1) {perror("Error opening device %s: unable to query device.");return -1;}if ((cam_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {perror("ERROR video capture not supported.");return -1;}// 设置输出参数struct v4l2_format v4l2_fmt;v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4l2_fmt.fmt.pix.width = WIDTH;v4l2_fmt.fmt.pix.height = HEIGHT;v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;if (ioctl(cam_fd, VIDIOC_S_FMT, &v4l2_fmt) == -1) {perror("ERROR camera VIDIOC_S_FMT Failed.");return -1;}// 检查参数是否设置成功if (ioctl(cam_fd, VIDIOC_G_FMT, &v4l2_fmt) == -1) {perror("ERROR camera VIDIOC_G_FMT Failed.");return -1;}if (v4l2_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {printf("Set VIDIOC_S_FMT successful\n");}// 请求缓冲区存储图像数据struct v4l2_requestbuffers v4l2_req;v4l2_req.count = NB_BUFFER;v4l2_req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4l2_req.memory = V4L2_MEMORY_MMAP;if (ioctl(cam_fd, VIDIOC_REQBUFS, &v4l2_req) == -1) {perror("ERROR camera VIDIOC_REQBUFS Failed.");return -1;}// 开始内存映射struct v4l2_buffer v4l2_buf;v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4l2_buf.memory = V4L2_MEMORY_MMAP;for (i = 0; i < NB_BUFFER; i++) {v4l2_buf.index = i;if (ioctl(cam_fd, VIDIOC_QUERYBUF, &v4l2_buf) < 0) {perror("Unable to query buffer.");return -1;}pic.tmpbuffer[i] = mmap(NULL, v4l2_buf.length, PROT_READ, MAP_SHARED, cam_fd, v4l2_buf.m.offset);if (pic.tmpbuffer[i] == MAP_FAILED) {perror("Unable to map buffer.");return -1;}if (ioctl(cam_fd, VIDIOC_QBUF, &v4l2_buf) < 0) {perror("Unable to queue buffer.");return -1;}}// 开启流输入int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(cam_fd, VIDIOC_STREAMON, &type) < 0) {perror("Unable to start capture.");return -1;}return 0;
}//抓取图像
int v4l2Grab(void) {// 获取图像struct v4l2_buffer buff;buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buff.memory = V4L2_MEMORY_MMAP;if (ioctl(cam_fd, VIDIOC_DQBUF, &buff) < 0) {printf("camera VIDIOC_DBUF Failed.\n");return -1;}pic.tmpbytesused[buff.index] = buff.bytesused;printf("size : %d\n", pic.tmpbytesused[buff.index]);// 保存图像int jpg_fd = open("v4l2.yuyv", O_RDWR | O_CREAT, 00700);if (jpg_fd == -1) {printf("open ipg Failed!\n");return -1;}int writesize = write(jpg_fd, pic.tmpbuffer[buff.index], pic.tmpbytesused[buff.index]);printf("Write successfully size : %d\n", writesize);close(jpg_fd);// 将缓冲区重新入队列if (ioctl(cam_fd, VIDIOC_QBUF, &buff) < 0) {printf("camera VIDIOC_QBUF Failed.");return -1;}return 0;
}//关闭摄像头
int v4l2_close(void) {// 解除内存映射struct v4l2_buffer v4l2_buf;v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4l2_buf.memory = V4L2_MEMORY_MMAP;for (int i = 0; i < NB_BUFFER; i++) {v4l2_buf.index = i;if (ioctl(cam_fd, VIDIOC_QUERYBUF, &v4l2_buf) == 0) {munmap(pic.tmpbuffer[i], v4l2_buf.length);}}close(cam_fd);return 0;
}//YUV422 转 RGB24
int yuv422_rgb24(unsigned char *yuv_buf, unsigned char *rgb_buf, unsigned int width, unsigned int height) {unsigned int i, j;unsigned char y0, y1, u, v;int r, g, b; // 使用 int 类型进行中间计算,以处理溢出问题for (i = 0; i < height; i++) {for (j = 0; j < width; j += 2) {// 提取 YUV 组件y0 = yuv_buf[i * width * 2 + j * 2];   // 第一个像素的 Y 分量u  = yuv_buf[i * width * 2 + j * 2 + 1]; // U 是两个像素共享的y1 = yuv_buf[i * width * 2 + j * 2 + 2]; // 第二个像素的 Y 分量v  = yuv_buf[i * width * 2 + j * 2 + 3]; // V 是两个像素共享的// 转换第一个像素r = (int)(y0 + 1.402 * (v - 128));g = (int)(y0 - 0.344 * (u - 128) - 0.714 * (v - 128));b = (int)(y0 + 1.772 * (u - 128));// 限制并赋值到 RGB 缓冲区rgb_buf[(i * width + j) * 3 + 0] = (unsigned char)CLAMP(b, 0, 255);rgb_buf[(i * width + j) * 3 + 1] = (unsigned char)CLAMP(g, 0, 255);rgb_buf[(i * width + j) * 3 + 2] = (unsigned char)CLAMP(r, 0, 255);// 转换第二个像素,使用相同的 UV 值r = (int)(y1 + 1.402 * (v - 128));g = (int)(y1 - 0.344 * (u - 128) - 0.714 * (v - 128));b = (int)(y1 + 1.772 * (u - 128));// 限制并赋值到 RGB 缓冲区rgb_buf[(i * width + j + 1) * 3 + 0] = (unsigned char)CLAMP(b, 0, 255);rgb_buf[(i * width + j + 1) * 3 + 1] = (unsigned char)CLAMP(g, 0, 255);rgb_buf[(i * width + j + 1) * 3 + 2] = (unsigned char)CLAMP(r, 0, 255);}}return 0;
}// 辅助函数:限制数值范围
int CLAMP(int value, int min, int max) {if (value < min) return min;if (value > max) return max;return value;
}//将 RGB 数据写入 PNG 文件
void write_data_to_png(const char *png_name, png_uint_32 width, png_uint_32 height, png_bytepp data, int color_type) {FILE *fp = fopen(png_name, "wb");if (!fp) {perror("Error opening PNG file for writing");return;}png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);if (!png) {perror("Error creating PNG write struct");fclose(fp);return;}png_infop info = png_create_info_struct(png);if (!info) {perror("Error creating PNG info struct");png_destroy_write_struct(&png, NULL);fclose(fp);return;}png_init_io(png, fp);png_set_IHDR(png, info, width, height, 8, color_type, PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);png_write_info(png, info);// 分配行指针png_bytep *row_pointers = malloc(height * sizeof(png_bytep));if (!row_pointers) {perror("Error allocating row pointers");png_destroy_write_struct(&png, &info);fclose(fp);return;}// 分配每行的内存for (unsigned int i = 0; i < height; ++i) {row_pointers[i] = (png_bytep)&(((pixel_RGB *)data)[i * width]);}png_write_image(png, row_pointers);png_write_end(png, NULL);png_destroy_write_struct(&png, &info);fclose(fp);free(row_pointers);
}

更:客户端传获取到的图像给服务器

客户端:

client.c
#include <png.h>
#include "client.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <linux/videodev2.h>// 摄像头文件描述符
int cam_fd;// 用于存储图像数据的结构体
/*
struct pic_data {unsigned char *tmpbuffer[NB_BUFFER];unsigned int tmpbytesused[NB_BUFFER];
} pic;
*/// 初始化摄像头的函数
int v4l2_init(void) {int i;// 打开摄像头设备if ((cam_fd = open(CAM_DEV, O_RDWR)) == -1) {perror("打开V4L接口出错");return -1;}// 检查设备是否为摄像头struct v4l2_capability cam_cap;if (ioctl(cam_fd, VIDIOC_QUERYCAP, &cam_cap) == -1) {perror("打开设备时查询设备能力出错");return -1;}if ((cam_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {perror("不支持视频捕获");return -1;}// 设置输出参数struct v4l2_format v4l2_fmt;v4l2_fmt.type = V4L2_CAP_VIDEO_CAPTURE;v4l2_fmt.fmt.pix.width = WIDTH;v4l2_fmt.fmt.pix.height = HEIGHT;v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;if (ioctl(cam_fd, VIDIOC_S_FMT, &v4l2_fmt) == -1) {perror("设置摄像头格式失败");return -1;}// 检查参数是否设置成功if (ioctl(cam_fd, VIDIOC_G_FMT, &v4l2_fmt) == -1) {perror("获取摄像头格式失败");return -1;}if (v4l2_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {printf("设置VIDIOC_S_FMT成功\n");}// 请求缓冲区以存储图像数据struct v4l2_requestbuffers v4l2_req;v4l2_req.count = NB_BUFFER;v4l2_req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4l2_req.memory = V4L2_MEMORY_MMAP;if (ioctl(cam_fd, VIDIOC_REQBUFS, &v4l2_req) == -1) {perror("请求摄像头缓冲区失败");return -1;}// 映射内存struct v4l2_buffer v4l2_buf;v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4l2_buf.memory = V4L2_MEMORY_MMAP;for (i = 0; i < NB_BUFFER; i++) {v4l2_buf.index = i;if (ioctl(cam_fd, VIDIOC_QUERYBUF, &v4l2_buf) < 0) {perror("查询缓冲区失败");return -1;}pic.tmpbuffer[i] = mmap(NULL, v4l2_buf.length, PROT_READ, MAP_SHARED, cam_fd, v4l2_buf.m.offset);if (pic.tmpbuffer[i] == MAP_FAILED) {perror("映射缓冲区失败");return -1;}if (ioctl(cam_fd, VIDIOC_QBUF, &v4l2_buf) < 0) {perror("将缓冲区入队列失败");return -1;}}// 开启视频流int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(cam_fd, VIDIOC_STREAMON, &type) < 0) {perror("启动捕获失败");return -1;}return 0;
}// 从摄像头抓取图像的函数
int v4l2Grab(void) {// 获取图像struct v4l2_buffer buff;buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buff.memory = V4L2_MEMORY_MMAP;if (ioctl(cam_fd, VIDIOC_DQBUF, &buff) < 0) {printf("从摄像头获取缓冲区失败\n");return -1;}pic.tmpbytesused[buff.index] = buff.bytesused;printf("大小 : %d\n", pic.tmpbytesused[buff.index]);// 查看部分YUV数据(调试用)unsigned char *yuv_data = pic.tmpbuffer[buff.index];printf("YUV数据示例: Y=%d, U=%d, V=%d\n", yuv_data[0], yuv_data[1], yuv_data[3]);// 保存图像int jpg_fd = open("v4l2.yuyv", O_RDWR | O_CREAT, 00700);if (jpg_fd == -1) {printf("打开文件失败!\n");return -1;}int writesize = write(jpg_fd, pic.tmpbuffer[buff.index], pic.tmpbytesused[buff.index]);printf("写入成功大小 : %d\n", writesize);close(jpg_fd);// 将缓冲区重新入队列if (ioctl(cam_fd, VIDIOC_QBUF, &buff) < 0) {printf("将缓冲区重新入队列失败");return -1;}return 0;
}// 关闭摄像头的函数
int v4l2_close(void) {// 解除内存映射struct v4l2_buffer v4l2_buf;v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4l2_buf.memory = V4L2_MEMORY_MMAP;for (int i = 0; i < NB_BUFFER; i++) {v4l2_buf.index = i;if (ioctl(cam_fd, VIDIOC_QUERYBUF, &v4l2_buf) == 0) {munmap(pic.tmpbuffer[i], v4l2_buf.length);}}close(cam_fd);return 0;
}// 辅助函数:限制数值范围
int CLAMP(int value, int min, int max) {if (value < min) return min;if (value > max) return max;return value;
}// 将YUV 422格式转换为RGB 24位格式的函数
int yuv422_rgb24(unsigned char *yuv_buf, unsigned char *rgb_buf, unsigned int width, unsigned int height) {unsigned int i, j;unsigned char y0, y1, u, v;unsigned char r, g, b;for (i = 0; i < height; i++) {for (j = 0; j < width; j += 2) {// 提取 YUV 组件y0 = yuv_buf[i * width * 2 + j * 2];   // 第一个像素的 Y 分量u  = yuv_buf[i * width * 2 + j * 2 + 1]; // U 是两个像素共享的y1 = yuv_buf[i * width * 2 + j * 2 + 2]; // 第二个像素的 Y 分量v  = yuv_buf[i * width * 2 + j * 2 + 3]; // V 是两个像素共享的// 转换第一个像素r = (int)(y0 + 1.402 * (v - 128));g = (int)(y0 - 0.344 * (u - 128) - 0.714 * (v - 128));b = (int)(y0 + 1.772 * (u - 128));// 限制并赋值到 RGB 缓冲区rgb_buf[(i * width + j) * 3 + 0] = (unsigned char)CLAMP(b, 0, 255);rgb_buf[(i * width + j) * 3 + 1] = (unsigned char)CLAMP(g, 0, 255);rgb_buf[(i * width + j) * 3 + 2] = (unsigned char)CLAMP(r, 0, 255);// 转换第二个像素,使用相同的 UV 值r = (int)(y1 + 1.402 * (v - 128));g = (int)(y1 - 0.344 * (u - 128) - 0.714 * (v - 128));b = (int)(y1 + 1.772 * (u - 128));// 限制并赋值到 RGB 缓冲区rgb_buf[(i * width + j + 1) * 3 + 0] = (unsigned char)CLAMP(b, 0, 255);rgb_buf[(i * width + j + 1) * 3 + 1] = (unsigned char)CLAMP(g, 0, 255);rgb_buf[(i * width + j + 1) * 3 + 2] = (unsigned char)CLAMP(r, 0, 255);}}return 0;
}// 将RGB数据写入PNG文件的函数
void write_data_to_png(const char *png_name, png_uint_32 width, png_uint_32 height, png_bytepp data, int color_type) {// 查看部分RGB数据(调试用)for (int i = 0; i < 10; i++) {pixel_RGB *pixel = &((pixel_RGB *)data)[i];printf("RGB数据示例: R=%d, G=%d, B=%d\n", pixel->red, pixel->green, pixel->blue);}FILE *fp = fopen(png_name, "wb");if (!fp) {perror("打开PNG文件用于写入出错");return;}png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);if (!png) {perror("创建PNG写入结构体出错");fclose(fp);return;}png_infop info = png_create_info_struct(png);if (!info) {perror("创建PNG信息结构体出错");png_destroy_write_struct(&png, NULL);fclose(fp);return;}png_init_io(png, fp);png_set_IHDR(png, info, width, height, 8, color_type, PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);png_write_info(png, info);// 分配行指针png_bytep *row_pointers = malloc(height * sizeof(png_bytep));if (!row_pointers) {perror("分配行指针出错");png_destroy_write_struct(&png, &info);fclose(fp);return;}// 分配每行的内存for (unsigned int i = 0; i < height; ++i) {row_pointers[i] = (png_bytep)&(((pixel_RGB *)data)[i * width]);}png_write_image(png, row_pointers);png_write_end(png, NULL);png_destroy_write_struct(&png, &info);fclose(fp);free(row_pointers);
}// 设置客户端并发送PNG图像数据的函数
int client_setup_and_send(const char* server_ip) {// 初始化摄像头if (v4l2_init() == -1) {fprintf(stderr, "初始化摄像头失败.\n");return -1;}// 从摄像头抓取图像if (v4l2Grab() == -1) {fprintf(stderr, "从摄像头抓取图像失败.\n");v4l2_close();return -1;}// 分配RGB缓冲区unsigned char *rgb_buf = (unsigned char *)malloc(WIDTH * HEIGHT * 3);if (!rgb_buf) {fprintf(stderr, "分配RGB缓冲区失败.\n");v4l2_close();return -1;}// 将YUV数据转换为RGB数据yuv422_rgb24(pic.tmpbuffer[0], rgb_buf, WIDTH, HEIGHT);// 分配PNG图像数据typedef struct {unsigned char red;unsigned char green;unsigned char blue;} pixel_RGB;pixel_RGB *image_data_rgb = calloc(WIDTH * HEIGHT, sizeof(pixel_RGB));if (!image_data_rgb) {fprintf(stderr, "分配PNG图像数据失败.\n");free(rgb_buf);v4l2_close();return -1;}// 将RGB数据复制到PNG图像数据结构体中for (unsigned int i = 0; i < WIDTH * HEIGHT; i++) {image_data_rgb[i].red = rgb_buf[i * 3 + 2];image_data_rgb[i].green = rgb_buf[i * 3 + 1];image_data_rgb[i].blue = rgb_buf[i * 3 + 0];}// 保存为PNG图像const char *png_name = "camera_image.png";write_data_to_png(png_name, WIDTH, HEIGHT, (png_bytepp)image_data_rgb, PNG_COLOR_TYPE_RGB);// 创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {perror("创建套接字出错");free(rgb_buf);free(image_data_rgb);v4l2_close();return -1;}// 设置服务器地址和端口struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = inet_addr(server_ip);// 连接到服务器if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("连接到服务器出错");close(sockfd);free(rgb_buf);free(image_data_rgb);v4l2_close();return -1;}// 获取PNG图像数据大小FILE *png_file = fopen(png_name, "rb");if (!png_file) {perror("打开PNG文件出错");close(sockfd);free(rgb_buf);free(image_data_rgb);v4l2_close();return -1;}fseek(png_file, 0, SEEK_END);long png_data_size = ftell(png_file);fseek(png_file, 0, SEEK_SET);// 发送图像数据大小信息if (send(sockfd, &png_data_size, sizeof(png_data_size), 0) == -1) {perror("发送图像大小信息出错");fclose(png_file);close(sockfd);free(rgb_buf);free(image_data_rgb);v4l2_close();return -1;}// 发送图像数据char *png_data_buffer = (char *)malloc(png_data_size);if (!png_data_buffer) {perror("为发送PNG数据分配缓冲区出错");fclose(png_file);close(sockfd);free(rgb_buf);free(image_data_rgb);v4l2_close();return -1;}fread(png_data_buffer, 1, png_data_size, png_file);if (send(sockfd, png_data_buffer, png_data_size, 0) == -1) {perror("发送PNG数据出错");free(png_data_buffer);fclose(png_file);close(sockfd);free(rgb_buf);free(image_data_rgb);v4l2_close();return -1;}free(png_data_buffer);fclose(png_file);// 关闭套接字close(sockfd);// 释放资源free(rgb_buf);free(image_data_rgb);v4l2_close();return 0;
}// 客户端主函数
int main(int argc, char *argv[]) {if (argc!= 2) {fprintf(stderr, "用法: %s <服务器IP地址>\n", argv[0]);return -1;}const char* server_ip = argv[1];if (client_setup_and_send(server_ip) == -1) {fprintf(stderr, "客户端发送图像数据失败.\n");return -1;}return 0;
}
client.h
#ifndef CLIENT_H
#define CLIENT_H#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
//#include <time.h>
#include <linux/videodev2.h>
#include <png.h>#define CAM_DEV "/dev/video0"
#define WIDTH 640
#define HEIGHT 480
#define NB_BUFFER 4
#define PORT 8888// 用于存储图像数据的结构体
struct pic_data {unsigned char *tmpbuffer[NB_BUFFER];unsigned int tmpbytesused[NB_BUFFER];
} pic;typedef struct {unsigned char red;unsigned char green;unsigned char blue;
} pixel_RGB;// 函数原型声明
int v4l2_init(void);
int v4l2Grab(void);
int v4l2_close(void);
// 辅助函数:限制数值范围
int CLAMP(int value, int min, int max);
int yuv422_rgb24(unsigned char *yuv_buf, unsigned char *rgb_buf, unsigned int width, unsigned int height);
void write_data_to_png(const char *png_name, png_uint_32 width, png_uint_32 height, png_bytepp data, int color_type);
int client_setup_and_send();#endif

服务器

server.c
#include "server.h"// 处理单个客户端连接的函数
void handle_single_connection(int client_socket) {// 接收PNG图像数据的大小long png_data_size;if (recv(client_socket, &png_data_size, sizeof(png_data_size), 0) == -1) {perror("接收图像大小出错");close(client_socket);return;}// 接收PNG图像数据char *png_data_buffer = (char *)malloc(png_data_size);if (!png_data_buffer) {perror("为PNG数据分配缓冲区出错");close(client_socket);return;}long total_received = 0;while (total_received < png_data_size) {long received = recv(client_socket, png_data_buffer + total_received, png_data_size - total_received, 0);if (received == -1) {perror("接收PNG数据出错");free(png_data_buffer);close(client_socket);return;}total_received += received;}// 将接收到的数据保存为PNG文件const char *png_name = "received_image.png";FILE *fp = fopen(png_name, "wb");if (!fp) {perror("打开PNG文件用于写入出错");free(png_data_buffer);close(client_socket);return;}fwrite(png_data_buffer, 1, png_data_size, fp);fclose(fp);free(png_data_buffer);// 关闭客户端套接字close(client_socket);
}// 启动服务器并监听传入连接的函数
int start_server() {// 创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {perror("创建套接字出错");return -1;}// 绑定套接字到特定地址和端口struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("绑定套接字出错");close(sockfd);return -1;}// 监听传入连接if (listen(sockfd, 5) == -1) {perror("监听连接出错");close(sockfd);return -1;}return sockfd;
}// 服务器主函数,用于持续监听并处理客户端连接
int main() {int server_socket = start_server();if (server_socket == -1) {fprintf(stderr, "服务器启动失败.\n");return -1;}while (1) {// 接受客户端连接struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);if (client_socket == -1) {perror("接受客户端连接出错");continue;}// 处理客户端连接handle_single_connection(client_socket);}// 关闭服务器套接字(实际上这里不会执行到,因为循环一直运行)close(server_socket);return 0;
}
server.h
#ifndef SERVER_H
#define SERVER_H#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <png.h>#define BUFFER_SIZE 1024
#define PORT 8888// 函数原型声明
int start_server();
void handle_connection(int client_socket);#endif

Makefile

CC = gcc
CFLAGS = -Wall --std=gnu99 -I/usr/src/linux-headers-3.2.0-23-generic-pae/include -Wno-cpp
LDFLAGS = -L/usr/lib/i386-linux-gnu

all: server client

server: server.c server.h
    $(CC) $(CFLAGS) $(LDFLAGS) -o server server.c -lpng

client: client.c client.h
    $(CC) $(CFLAGS) $(LDFLAGS) -o client client.c -lpng
clean:
    rm -f server client

这两个要换成自己的路径:

  • CFLAGS = -Wall --std=gnu99 -I/usr/src/linux-headers-3.2.0-23-generic-pae/include -Wno-cpp
  • LDFLAGS = -L/usr/lib/i386-linux-gnu

相关文章:

Linux-Ubuntu16.04摄像头 客户端抓取帧并保存为PNG

1.0&#xff1a;client.c抓取帧并保存为PNG #include <stdio.h> // 标准输入输出库 #include <stdlib.h> // 标准库&#xff0c;包含内存分配等函数 #include <string.h> // 字符串操作库 #include <linux/videodev2.h> // V4L2 视频设备…...

手机ip地址取决于什么?可以随便改吗

手机IP地址是指手机在连接到互联网时所获得的唯一网络地址&#xff0c;这个地址由一串数字组成&#xff0c;用于在网络中标识和定位设备。每个设备在连接到网络时都会被分配一个IP地址&#xff0c;它可以帮助数据包在网络中准确地找到目标设备。那么&#xff0c;手机IP地址究竟…...

计算机网络:TCP/IP协议的五大重要特性介绍

目录 一、逻辑编址 二、路由选择 三、名称解析 四、错误控制和流量控制 五、多应用支持 今天给大家聊聊TCP/IP协议中五大重要特性相关的知识,希望对大家深入了解该协议提供一些帮助! 一、逻辑编址 首先要了解什么是物理地址、逻辑地址。 ●...

Java与AWS S3的文件操作

从零开始&#xff1a;Java与AWS S3的文件操作 一、什么是 AWS S3&#xff1f;AWS S3 的特点AWS S3 的应用场景 二、Java整合S3方法使用 MinIO 客户端操作 S3使用 AWS SDK 操作 S3 &#xff08;推荐使用&#xff09; 三、总结 一、什么是 AWS S3&#xff1f; Amazon Simple Sto…...

详解 YOLOv5 模型运行参数含义以及设置及在 PyCharm 中的配置方法

详解 YOLOv5 模型运行参数含义以及设置及在 PyCharm 中的配置方法 这段代码中使用的命令行参数允许用户在运行 YOLOv5 模型时自定义多种行为和设置。以下是各个参数的详细说明和使用示例&#xff0c;以及如何在 PyCharm 中设置这些参数以确保正确运行带有参数的脚本。 命令行…...

Vue根据Div内容的高度给其Div设置style height

在 Vue.js 中&#xff0c;你可以使用 JavaScript 来动态地根据 div 的内容高度来设置其 style 的 height 属性。这通常是在组件挂载或更新时完成的&#xff0c;因为这时你已经有了实际的 DOM 元素可以操作。 以下是一个简单的例子&#xff0c;展示了如何实现这一点&#xff1a…...

驱动篇的开端

准备 在做之后的动作前&#xff0c;因为win7及其以上的版本默认是不支持DbgPrint&#xff08;大家暂时理解为内核版的printf&#xff09;的打印&#xff0c;所以&#xff0c;为了方便我们的调试&#xff0c;我们先要修改一下注册表 创建一个reg文件然后运行 Windows Registr…...

OpenSSL 自建CA 以及颁发证书(网站部署https双向认证)

前言 1、前面写过一篇 阿里云免费ssl证书申请与部署&#xff0c;大家可以去看下 一、openssl 安装说明 1、这部分就不再说了&#xff0c;我使用centos7.9&#xff0c;是自带 openssl的&#xff0c;window的话&#xff0c;要去下载安装 二、CA机构 CA机构&#xff0c;全称为…...

吾杯网络安全技能大赛WP(部分)

吾杯网络安全技能大赛WP(部分) MISC Sign 直接16进制解码即可 原神启动 将图片用StegSolve打开 找到了压缩包密码 将解出docx文件改为zip 找到了一张图片和zip 再把图片放到stegSlove里找到了img压缩包的密码 然后在document.xml里找到了text.zip压缩包密码 然后就出来fl…...

按vue组件实例类型实现非侵入式国际化多语言翻译

#vue3##国际化##本地化##international# web界面国际化&#xff0c;I18N&#xff08;Internationalization&#xff0c;国际化&#xff09;&#xff0c;I11L(International&#xff0c;英特纳雄耐尔)&#xff0c;L10N&#xff08;Localization&#xff0c;本地化&#xff09;&…...

Java入门:22.集合的特点,List,Set和Map集合的使用

1 什么是集合 本质就是容器的封装&#xff0c;可以存储多个元素 数组一旦创建&#xff0c;长度就不能再改变了。 数组一旦创建&#xff0c;存储内容的类型不能改变。 数组可以存储基本类型&#xff0c;也可以存储引用类型。 数组可以通过length获得容量的大小&#xff0c;但…...

重生之我在异世界学编程之C语言:深入指针篇(下)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 目录 题集&#xff08;1&#xff09;指针笔试题1&a…...

理解Parquet文件和Arrow格式:从Hugging Face数据集的角度出发

parquet发音&#xff1a;美 [pɑrˈkeɪ] 镶木地板&#xff1b;拼花木地板 理解Parquet文件和Arrow格式&#xff1a;从Hugging Face数据集的角度出发 引言 在机器学习和大数据处理中&#xff0c;数据的存储和传输格式对于性能至关重要。两种广泛使用的格式是 Parquet 和 Arr…...

下载 M3U8 格式的视频

要下载 M3U8 格式的视频&#xff08;通常是 HLS 视频流&#xff09;&#xff0c;可以尝试以下几种方法&#xff1a; 方法 1&#xff1a;使用下载工具&#xff08;推荐&#xff09; 1. IDM&#xff08;Internet Download Manager&#xff09;&#xff1a; 安装 IDM 并启用浏…...

Tomcat使用教程

下载地址&#xff1a;https://tomcat.apache.org/ 配置环境变量 变量名: CATALINA_HOME 变量值: D:\tools\apache-tomcat-9.0.97 Path: %CATALINA_HOME%\bin 启动Tomcat(打开命令提示符) startup.bat 解决乱码问题(打开conf\logging.properties) java.util.logging.Conso…...

LabVIEW氢气纯化控制系统

基于LabVIEW的氢气纯化控制系统满足氢气纯化过程中对精确控制的需求&#xff0c;具备参数设置、过程监控、数据记录和报警功能&#xff0c;体现了LabVIEW在复杂工业控制系统中的应用效能。 项目背景 在众多行业中&#xff0c;尤其是石油化工和航天航空领域&#xff0c;氢气作为…...

现在的电商风口已经很明显了

随着电商行业的不断发展&#xff0c;直播带货的热潮似乎正逐渐降温&#xff0c;而货架电商正成为新的焦点。抖音等平台越来越重视货架电商&#xff0c;强调搜索功能的重要性&#xff0c;预示着未来的电商中心将转向货架和搜索。 在这一转型期&#xff0c;AI技术与电商的结合为…...

Uniapp触底刷新

在你的代码中&#xff0c;使用了 scroll-view 来实现一个可滚动的评论区域&#xff0c;并且通过监听 scrolltolower 事件来触发 handleScrollToLower 函数&#xff0c;以实现“触底更新”或加载更多评论的功能。 关键部分分析&#xff1a; scroll-view 组件: scroll-view 是一…...

开源项目 - face parsing 人脸区域分割 人像区域分割 人脸分割 人像区域分割 BiSeNet

开源项目 - face parsing 人脸区域分割 人像区域分割 人脸分割 人像区域分割 BiSeNet 项目地址&#xff1a;GitHub - XIAN-HHappy/face_parsing: face_parsing 脸部分割 示例&#xff1a; ​​​​ 助力快速掌握数据集的信息和使用方式。 数据可以如此美好&#xff01;...

python游戏设计---飞机大战

1.前言 上次做飞机大战游戏有人这么说&#xff1a; 好好好&#xff01;今天必须整一个&#xff0c;今天我们来详细讲解一下&#xff0c;底部找素材文件下载&#xff01;&#xff01;&#xff01; 2.游戏制作 目录如下&#xff1a; 1.导入的包 import pygame import sys imp…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

LLM基础1_语言模型如何处理文本

基于GitHub项目&#xff1a;https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken&#xff1a;OpenAI开发的专业"分词器" torch&#xff1a;Facebook开发的强力计算引擎&#xff0c;相当于超级计算器 理解词嵌入&#xff1a;给词语画"…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关

在水泥厂的生产流程中&#xff0c;工业自动化网关起着至关重要的作用&#xff0c;尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关&#xff0c;为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多&#xff0c;其中不少设备采用Devicenet协议。Devicen…...

GraphQL 实战篇:Apollo Client 配置与缓存

GraphQL 实战篇&#xff1a;Apollo Client 配置与缓存 上一篇&#xff1a;GraphQL 入门篇&#xff1a;基础查询语法 依旧和上一篇的笔记一样&#xff0c;主实操&#xff0c;没啥过多的细节讲解&#xff0c;代码具体在&#xff1a; https://github.com/GoldenaArcher/graphql…...