LINUX.ORG.RU

V4L2: ошибка удаления буфера из очереди: недопустимый аргумент

 ,


0

1

Я написал сервер, который постоянно захватывает видеокадры, и накапливает их в буфере (видео идет 5 секунд, оно постоянно обновляется), как только клиент подключается к серверу, это видео передается через сокет.

У меня проблема. Ошибка «Ошибка удаления буфера из очереди: недопустимый аргумент» в строке «if (ioctl (fd, VIDIOC_DQBUF, &buf) == -1)» Что я делаю неправильно?

    #include <unistd.h>
    #include <thread>
    #include <mutex>
    #include <deque>
    #include <stdio.h>
    #include <stdlib.h>
    #include <cstdlib>
    #include <string.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <sys/mman.h>
    #include <linux/videodev2.h>
    #include <math.h>
    #include <typeinfo>
    #include <iostream>
    #include <pthread.h>
    #include <linux/i2c.h>
    #include <sys/ioctl.h>
    
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <cstring> // memset()
    
    #include <cstring>
    #include <vector>
    
    #include <opencv2/opencv.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/core.hpp>
    #include "opencv2/imgproc.hpp"
    #include <opencv2/imgcodecs.hpp>
    #include <opencv2/core/mat.hpp>
    #include <opencv2/videoio.hpp>
    
    
    
    
    extern "C" {
        #include <linux/i2c-dev.h>
        #include "i2c/smbus.h"
    }
    
    #define CLEAR(x) memset(&(x), 0, sizeof(x))//очистка
    #define   WIDTH   1920                       // The width of the picture
    #define   HEIGHT  1080                       // The height of the picture
    #define   FMT     V4L2_PIX_FMT_BGR24 // формат поддерживаемый матрицей
    #define mydelay usleep (1000)// минимальная возможная задержка шага 100 мкс
    #define pwmmydelay usleep (1000) // задержка между шагами
    #define I2C4_PATH "/dev/i2c-3"
    #define I2C2_PATH "/dev/i2c-1"
    const unsigned default_port = 3425; // порт, по которому будет стучаться клиент
    const int number_of_seconds = 5;   // кол-во секунд, что сервер будет хранить в памяти
    #define INPUT_ADDR 0x22
    #define INPUT_REG_P0 0x04
    #define dev_name "/dev/video0"
    const double default_fps = 25;      // должно совпадать с таким же значением из client.cpp
    cv::VideoWriter writer;
    using namespace cv;
    using namespace std;
    int grows = 1920; // высота
    int gcols = 1080; // и ширина текущего видео захвата
    deque<Mat> q;  // очередь с кадрами
    mutex qmutex;  // мьютекс для охраны очереди
    int working = 0;
    //структура для получения кадра
           struct buffer {
                   void   *start;
                   int64_t length;
    
           };
    
    /*char*                  	BUFIMAGEA = new char [WIDTH*HEIGHT];
           char*                    	BUFIMAGER = new char [WIDTH*HEIGHT];
           char*                    	BUFIMAGEG = new char [WIDTH*HEIGHT];
           char*                    	BUFIMAGEB = new char [WIDTH*HEIGHT];
           char*                    	BUFIMAGEY = new char [WIDTH*HEIGHT];*/
    
    
           v4l2_format			fmt; // формат видео
           v4l2_buffer              	buf; //
           v4l2_requestbuffers      	req; //
           v4l2_capability          	device_params; //параметры видеомодуля
           v4l2_buf_type            	type;
           fd_set                   	fds;
           timeval                  	tv;
    
           int				r, fd = -1;
    
           unsigned int			i, n_buffers;
    
           buffer*			buffers;
    
           char*				val = (char*)malloc(sizeof(char)*1*WIDTH*HEIGHT);
    
    
    
           using namespace cv;
           using namespace std;
    
    
           static void xioctl(int fh, int request, void *arg)//настройка параметров камеры
           {
                   int r;
    
                   do {
                           r = ioctl(fh, request, arg);
                   } while (r == -1 && ((errno == EINTR) || (errno == EAGAIN)));
    
                   if (r == -1) {
                           fprintf(stderr, "error %d, %s\\n", errno, strerror(errno), request);
                           exit(EXIT_FAILURE);
                   }
           }
    
    
           // структура для получения значения из потока с плавающей запятой
           typedef struct someArgs_tag {
               int id;
               const char *msg;
               double out[2];
           } someArgs_t;
    
    
    
    
    
    
    
    
    
           // тред, в котором идет накопление захваченных кадров в памяти
           void video_grabber_thread() {
               cout << "[VIDEO_GRABBER] : start" << endl;
    
               // очищаем очередь перед началом работы
               {
                   lock_guard<mutex> lk(qmutex);
                   q.clear();
               }
    
               printf("BEGIN\n");
    
                       fd = open(dev_name, O_RDWR | O_NONBLOCK, 0);//открытие камеры
    
                       if (fd < 0) {
                               perror("Cannot open device");
                               exit(EXIT_FAILURE);
                       }
    
                       CLEAR(device_params);
    
                       if (ioctl(fd, VIDIOC_QUERYCAP, &device_params) == -1)// получение параметров видеомодуля из драйвера
                       {
                         printf ("\"VIDIOC_QUERYCAP\" error %d, %s\n", errno, strerror(errno));
                         exit(EXIT_FAILURE);
                       }
    
                       CLEAR(fmt);
    
               //настройка формата
                       fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                       fmt.fmt.pix.width       = WIDTH;
                       fmt.fmt.pix.height      = HEIGHT;
                       fmt.fmt.pix.pixelformat = FMT;
                       fmt.fmt.pix.field       = V4L2_FIELD_NONE;
                       xioctl(fd, VIDIOC_S_FMT, &fmt);
                       if (fmt.fmt.pix.pixelformat != FMT) {
                               printf("Libv4l didn't accept ЭТОТ format. Can't proceed.\\n");
                               exit(EXIT_FAILURE);
                       }
    
                       CLEAR(req);//очистка структуры запросов
    
                       req.count = 4;//количество буферов
                       req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                       req.memory = V4L2_MEMORY_MMAP;
                       xioctl(fd, VIDIOC_REQBUFS, &req);
    
                       buffers =(buffer*)calloc(req.count, sizeof(*buffers));//выделение памяти под буферы
                       printf("buffers\n");
                       for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {//подготовка буферов к захвату
                               CLEAR(buf);
    
                               buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                               buf.memory      = V4L2_MEMORY_MMAP;
                               buf.index       = n_buffers;
    
                               xioctl(fd, VIDIOC_QUERYBUF, &buf);
    
                               buffers[n_buffers].length = buf.length;
                               buffers[n_buffers].start = mmap(NULL, buf.length,
                                             PROT_READ | PROT_WRITE, MAP_SHARED,
                                             fd, buf.m.offset);
                               if (MAP_FAILED == buffers[n_buffers].start) {
                                       printf("MAP fail. Can't proceed.\\n");perror("mmap");
                                       printf("MAP fail. Can't proceed.\\n");
                                       exit(EXIT_FAILURE);
                               }
    
                       }
    
               	printf("cycle\n");
                       for (i = 0; i < n_buffers ; ++i) {// поставка буферов в очередь на захват кадров
                               CLEAR(buf);
               		printf("qbuf\n");
                               buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                               buf.memory = V4L2_MEMORY_MMAP;
                               buf.index = i;
                               xioctl(fd, VIDIOC_QBUF, &buf);
                       }
    
               	printf("on\n");
                       type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                       printf("V4L2_BUF_TYPE_VIDEO_CAPTURE\n");
                       ioctl(fd, VIDIOC_STREAMON, &type);
    cout<<"streamon on "<<endl;
    
                       printf("endon\n");
    
               i=0;
               	Mat frame(HEIGHT, WIDTH, CV_8UC1);
    
               	VideoWriter writer("outpu517.avi", VideoWriter::fourcc('H','2','6','4'), 25, Size(WIDTH, HEIGHT), true);//открытие файла для записи
               	if (!writer.isOpened()) {
               	         std::cerr << "Failed to open output file" << std::endl;
               	         return;
               	            }
    
    cout << "124"<<endl;
               		while (working)  { //цикл записи видео в файл
    
                               do {
                                       FD_ZERO(&fds);
                                       FD_SET(fd, &fds);
    
                                       // задаем время ожидания доступности буфера
                                       tv.tv_sec = 2;
                                       tv.tv_usec = 0;//100
    
                                       // проверяем доступность буфера
                                       r = select(fd + 1, &fds, NULL, NULL, &tv);//проверка доступности буфера
                               } while ((r == -1 && (errno = EINTR)));
                               if (r == -1) {
                                       perror("select");
                                       return;
                               }
    
                               // захватываем мьютекс, охраняющий очередь с кадрами
                                              if (!qmutex.try_lock()) {
                                                  this_thread::sleep_for(50ms);
                                              }
    
                                              // удаляем "старые" кадры
                                              while (q.size() >= number_of_seconds * default_fps) {
                                                  q.pop_back();
                                              }
    
               i++;
               cout<<"KADR: "<< i << endl;
                               CLEAR(buf);//макрос для очистки структуры buf от мусора и установки всех ее полей в 0.
                               printf("dqbuf\n");
                               buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                               buf.memory = V4L2_MEMORY_MMAP;//установка способа доступа к буферу
    
                               if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
    
                                   if (errno == EAGAIN || errno == EIO) {
                                       // Возможно, буфер еще не готов или возникла ошибка ввода-вывода.
                                       // В этом случае пропустим итерацию и продолжим цикл.
                                       continue;
                                   } else {
                                       perror("Error dequeuing buffer");
                                       return;
                                   }
                               }
                                  printf("memcpy\n");
                                   memcpy(val, buffers[buf.index].start, sizeof(char)*WIDTH*HEIGHT);//копирование содержимого буфера, на который указывает buf.index, в память по указателю val
                                   printf("memcpy proshel \n");
    
                       printf("zapis poshla\n");
    
                       cv::Mat frame(HEIGHT, WIDTH, CV_8UC1, val);
                       cv::Mat framer;
                       cv::cvtColor(frame, framer, cv::COLOR_BayerRG2BGR);
                       writer.write(framer);
    
                       // выводим значения пикселей кадра
                                                             for (int i = 0; i < frame.rows; i++) {
                                                                 for (int j = 0; j < frame.cols; j++) {
                                                                     Vec3b pixel = frame.at<Vec3b>(i, j);
                                                                     cout << "Pixel (" << i << ", " << j << "): ";
                                                                     cout << "B = " << static_cast<int>(pixel[0]) << ", ";
                                                                     cout << "G = " << static_cast<int>(pixel[1]) << ", ";
                                                                     cout << "R = " << static_cast<int>(pixel[2]) << endl;
                                                                 }
                                                             }
    
    
                       if (framer.empty()) {//проверка на пустоту файла
                                   cerr << "ERROR! blank frame grabbed\n";
                                   break;
                               }
    
                       if (ioctl(fd, VIDIOC_QBUF, &buf)==-1){// Помещаем буфер обратно в очередь захвата
                                   	   perror("Error dequeuing buffer");
                                   	                 return;
                                      }
                       // добавляем его в очередь
                                      q.emplace_front(move(framer));
                                      // разблокируем очередь
                                      qmutex.unlock();
                       printf("Kadr zapisan\n\n\n\n\n\n");
    
    
               	}
    
    
               	CLEAR(buf);
               		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
               		buf.memory = V4L2_MEMORY_MMAP;
    
               	        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
               	        xioctl(fd, VIDIOC_STREAMOFF, &type);//отключение потока видеозахвата
               	        printf("xioctl\n");
    
               	        for (i = 0; i < n_buffers; ++i){
               	            munmap(buffers[i].start, buffers[i].length);//Эта строка используется для освобождения памяти, которая была выделена для буферов.
               	            //Она вызывает функцию munmap для каждого буфера, которая освобождает ранее выделенную память.
    
               	    }
    
    
               	 printf("video saved\n");
    
                               close(fd);
                               delete[] buffers;
                               printf("close fd\n");
    
               cout << "[VIDEO_GRABBER] : stop" << endl;
           }
    
    
    
    void send_queue(int sock) {
        lock_guard<mutex> lk(qmutex);
        uint32_t t = q.size();
    cout<<"start queue"<<endl;
        // пишем кол-во кадров
        send(sock, (char*) &t, sizeof(t), 0);
        // высоту
        t = grows;
        send(sock, (char*) &t, sizeof(t), 0);
        // и ширину кадра
        t = gcols;
        send(sock, (char*) &t, sizeof(t), 0);
    
        cv::VideoWriter videoWriterig;
            videoWriterig.open("out23.avi", cv::VideoWriter::fourcc('M','J','P','G'), default_fps, cv::Size(gcols, grows));
            cout<<"1"<<endl;
        int rc = 0;
        while (!q.empty()) {
        	cout<<"cikl"<<endl;
            // берем самый старый кадр
            Mat & ref_mat = q.back();
            cout<<"ref_mat"<<endl;
            // сохраняем кадр в файл
            videoWriterig.write(ref_mat);
                    cout<<"writer"<<endl;
            // вычисляем кол-во байт в нем
            uint32_t total = (ref_mat.dataend - ref_mat.data);
            // шлем это значение, чтобы клиент знал сколько
            // ему нужно вычитать байт этого кадра
            rc = send(sock, (char*) &total, sizeof(total), 0);
            int sent = 0;
            while (total > 0) {
                // шлем непосредственно байты кадра
                rc = send(sock, ref_mat.data + sent, total, 0);
                if (rc == -1) break;
                sent  += rc;
                total -= rc;
            }
            // удаляем обработанный кадр из очереди
            q.pop_back();
        }
        // освобождаем ресурсы видеозаписи
        videoWriterig.release();
    }
    
    
    
    
    int main() {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0) {
            perror("socket");
            exit(1);
        }
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(default_port);
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
        if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
            perror("ERR: binding failed!");
            exit(2);
        }
    
        listen(sock, 1);
    
        for (;;) {
            // запускаем граббинг кадров в очередь
            // параллельно нашему треду
            working = 1;
            std::thread t(&video_grabber_thread);
    
            // а сами в этом треде ждем подключение клиента
            struct sockaddr_in client;
            socklen_t len = sizeof(struct sockaddr_in);
            int new_socket = accept(sock, (struct sockaddr *)&client, &len);
    
            // к нам подключились, тогда останавливаем граббинг видео
            working = 0;
            t.join();
    
            if (new_socket == -1) { // если подключения клиента нет, то...
                cerr << "ERR: new_socket = " << new_socket << endl;
                //break;
            } else { //если подключился клиент, то ..
                send_queue(new_socket);
            }
        }
    
        close(sock); // закрытие сокета
        return 0;
    }


Последнее исправление: konstitycii (всего исправлений: 2)

Идея - перекомпилить ядро с максимумом отладки, или найти тот модуль в который входит программа при вызове проблемного ioctl. Шутки шутками, а я так не раз что то отладил. Просто лезешь в драйвер и смотришь, а по какой ветке кода оно пошло, что ему не понравилось

Это не должно пугать. Можно для начала просто https://elixir.bootlin.com/linux/latest/source

https://elixir.bootlin.com/linux/latest/A/ident/VIDIOC_DQBUF

I-Love-Microsoft ★★★★★
()

Простая гуглежка показывает, что у людей были аналогичные проблемы. Кто-то перезагружал модуль uvc с параметрами, кто-то доустанавливал пакет.

И еще. Почему не проверяются возвращаемые xioctl значения?

seiken ★★★★★
()

@konstitycii, погляди: https://www.kernel.org/doc/html/v6.3/userspace-api/media/v4l/vidioc-qbuf.html

EINVAL :: The buffer type is not supported, or the index is out of bounds, or no buffers have been allocated yet, or the userptr or length are invalid, or the V4L2_BUF_FLAG_REQUEST_FD flag was set but the given request_fd was invalid, or m.fd was an invalid DMABUF file descriptor.

Посмотри внимательно на все эти условия. Можешь залогировать.

hatred ★★★
()
Последнее исправление: hatred (всего исправлений: 1)
4 июля 2023 г.

в общем думаю что ты сделал зануление всех полей в buf перед отдачей его в ioctl — а последней нужны поля, которые ты повторно не заполняешь... А вообще занулять так вот бинарно думаю плохая идея...
Выше думаю правильно указывали на то, как должно происходить в коде и мануалы по использованию.

safocl ★★
()
Последнее исправление: safocl (всего исправлений: 1)