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-gnuall: server client
server: server.c server.h
$(CC) $(CFLAGS) $(LDFLAGS) -o server server.c -lpngclient: 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:client.c抓取帧并保存为PNG #include <stdio.h> // 标准输入输出库 #include <stdlib.h> // 标准库,包含内存分配等函数 #include <string.h> // 字符串操作库 #include <linux/videodev2.h> // V4L2 视频设备…...

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

计算机网络:TCP/IP协议的五大重要特性介绍
目录 一、逻辑编址 二、路由选择 三、名称解析 四、错误控制和流量控制 五、多应用支持 今天给大家聊聊TCP/IP协议中五大重要特性相关的知识,希望对大家深入了解该协议提供一些帮助! 一、逻辑编址 首先要了解什么是物理地址、逻辑地址。 ●...
Java与AWS S3的文件操作
从零开始:Java与AWS S3的文件操作 一、什么是 AWS S3?AWS S3 的特点AWS S3 的应用场景 二、Java整合S3方法使用 MinIO 客户端操作 S3使用 AWS SDK 操作 S3 (推荐使用) 三、总结 一、什么是 AWS S3? Amazon Simple Sto…...
详解 YOLOv5 模型运行参数含义以及设置及在 PyCharm 中的配置方法
详解 YOLOv5 模型运行参数含义以及设置及在 PyCharm 中的配置方法 这段代码中使用的命令行参数允许用户在运行 YOLOv5 模型时自定义多种行为和设置。以下是各个参数的详细说明和使用示例,以及如何在 PyCharm 中设置这些参数以确保正确运行带有参数的脚本。 命令行…...
Vue根据Div内容的高度给其Div设置style height
在 Vue.js 中,你可以使用 JavaScript 来动态地根据 div 的内容高度来设置其 style 的 height 属性。这通常是在组件挂载或更新时完成的,因为这时你已经有了实际的 DOM 元素可以操作。 以下是一个简单的例子,展示了如何实现这一点:…...

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

OpenSSL 自建CA 以及颁发证书(网站部署https双向认证)
前言 1、前面写过一篇 阿里云免费ssl证书申请与部署,大家可以去看下 一、openssl 安装说明 1、这部分就不再说了,我使用centos7.9,是自带 openssl的,window的话,要去下载安装 二、CA机构 CA机构,全称为…...

吾杯网络安全技能大赛WP(部分)
吾杯网络安全技能大赛WP(部分) MISC Sign 直接16进制解码即可 原神启动 将图片用StegSolve打开 找到了压缩包密码 将解出docx文件改为zip 找到了一张图片和zip 再把图片放到stegSlove里找到了img压缩包的密码 然后在document.xml里找到了text.zip压缩包密码 然后就出来fl…...
按vue组件实例类型实现非侵入式国际化多语言翻译
#vue3##国际化##本地化##international# web界面国际化,I18N(Internationalization,国际化),I11L(International,英特纳雄耐尔),L10N(Localization,本地化)&…...
Java入门:22.集合的特点,List,Set和Map集合的使用
1 什么是集合 本质就是容器的封装,可以存储多个元素 数组一旦创建,长度就不能再改变了。 数组一旦创建,存储内容的类型不能改变。 数组可以存储基本类型,也可以存储引用类型。 数组可以通过length获得容量的大小,但…...

重生之我在异世界学编程之C语言:深入指针篇(下)
大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 目录 题集(1)指针笔试题1&a…...

理解Parquet文件和Arrow格式:从Hugging Face数据集的角度出发
parquet发音:美 [pɑrˈkeɪ] 镶木地板;拼花木地板 理解Parquet文件和Arrow格式:从Hugging Face数据集的角度出发 引言 在机器学习和大数据处理中,数据的存储和传输格式对于性能至关重要。两种广泛使用的格式是 Parquet 和 Arr…...
下载 M3U8 格式的视频
要下载 M3U8 格式的视频(通常是 HLS 视频流),可以尝试以下几种方法: 方法 1:使用下载工具(推荐) 1. IDM(Internet Download Manager): 安装 IDM 并启用浏…...

Tomcat使用教程
下载地址: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的氢气纯化控制系统满足氢气纯化过程中对精确控制的需求,具备参数设置、过程监控、数据记录和报警功能,体现了LabVIEW在复杂工业控制系统中的应用效能。 项目背景 在众多行业中,尤其是石油化工和航天航空领域,氢气作为…...

现在的电商风口已经很明显了
随着电商行业的不断发展,直播带货的热潮似乎正逐渐降温,而货架电商正成为新的焦点。抖音等平台越来越重视货架电商,强调搜索功能的重要性,预示着未来的电商中心将转向货架和搜索。 在这一转型期,AI技术与电商的结合为…...
Uniapp触底刷新
在你的代码中,使用了 scroll-view 来实现一个可滚动的评论区域,并且通过监听 scrolltolower 事件来触发 handleScrollToLower 函数,以实现“触底更新”或加载更多评论的功能。 关键部分分析: scroll-view 组件: scroll-view 是一…...

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

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

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...