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…...

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

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: 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 请求,并检查收到的响应。它以以下模式之一…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...

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

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