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…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...

实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...

【多线程初阶】单例模式 指令重排序问题
文章目录 1.单例模式1)饿汉模式2)懒汉模式①.单线程版本②.多线程版本 2.分析单例模式里的线程安全问题1)饿汉模式2)懒汉模式懒汉模式是如何出现线程安全问题的 3.解决问题进一步优化加锁导致的执行效率优化预防内存可见性问题 4.解决指令重排序问题 1.单例模式 单例模式确保某…...
MySQL基本操作(续)
第3章:MySQL基本操作(续) 3.3 表操作 表是关系型数据库中存储数据的基本结构,由行和列组成。在MySQL中,表操作包括创建表、查看表结构、修改表和删除表等。本节将详细介绍这些操作。 3.3.1 创建表 在MySQL中&#…...

解决MybatisPlus使用Druid1.2.11连接池查询PG数据库报Merge sql error的一种办法
目录 前言 一、问题重现 1、环境说明 2、重现步骤 3、错误信息 二、关于LATERAL 1、Lateral作用场景 2、在四至场景中使用 三、问题解决之道 1、源码追踪 2、关闭sql合并 3、改写处理SQL 四、总结 前言 在博客:【写在创作纪念日】基于SpringBoot和PostG…...