Проблемы с продолжительностью видеозаписи и плавностью воспроизведения при использовании v4l2 для MP4 (FFmpeg)

Вопрос или проблема

Я пытаюсь записать видео с USB-устройства с использованием фреймворка v4l2 и сохранить его в формате MP4, используя FFmpeg. Мой образец кода успешно захватывает и сохраняет видео, но я сталкиваюсь с некоторыми проблемами:

Продолжительность записанного видео короче, чем ожидалось. Например:

При записи 1-минутного видео в 1280×720 выходной файл имеет только 58 или 59 секунд.
Для 1920×1080 продолжительность еще более непредсказуема — всего около 28 или 30 секунд вместо ожидаемой 1 минуты.
Кроме того, видео идет не плавно. Замечены резкие падения кадров и несоответствия в воспроизведении.

Моя настройка:

Используя USB-устройство с фреймворком v4l2
Сохранение видео в формате MP4
Проверено с различными разрешениями (1280×720, 1920×1080)
Я прикрепил мой образец кода ниже. Кто-нибудь может помочь мне разобраться, почему я сталкиваюсь с этими проблемами с длительностью видео и плавностью воспроизведения?

Любые советы, исправления или предложения будут очень appreciated!

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>

#define WIDTH 1280
#define HEIGHT 720
#define FPS 30
#define DURATION 10 // Продолжительность записи в секундах
#define BUFFER_COUNT 4 // Количество буферов

struct buffer {
    void *start;
    size_t length;
};

struct buffer *buffers;

void open_device(int *fd, const char *device) {
    *fd = open(device, O_RDWR | O_NONBLOCK);
    if (*fd < 0) {
        perror("Не удалось открыть видео устройство");
        exit(1);
    }
}

void init_mmap(int fd) {
    struct v4l2_requestbuffers req;
    memset(&req, 0, sizeof(req));
    req.count = BUFFER_COUNT;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
        perror("Запрос буфера");
        exit(1);
    }

    buffers = calloc(req.count, sizeof(*buffers));
    for (size_t i = 0; i < req.count; ++i) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
            perror("Запрос буфера");
            exit(1);
        }

        buffers[i].length = buf.length;
        buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);

        if (MAP_FAILED == buffers[i].start) {
            perror("mmap");
            exit(1);
        }
    }
}

void start_capturing(int fd) {
    for (size_t i = 0; i < BUFFER_COUNT; ++i) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            perror("Очередь буфера");
            exit(1);
        }
    }

    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
        perror("Начать захват");
        exit(1);
    }
}

void stop_capturing(int fd) {
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
        perror("Остановить захват");
        exit(1);
    }

    printf("Захват видео остановлен.\n");
}

void unmap_buffers() {
    for (size_t i = 0; i < BUFFER_COUNT; ++i) {
        if (munmap(buffers[i].start, buffers[i].length) < 0) {
            perror("munmap");
            exit(1);
        }
    }

    free(buffers);
}

void initialize_ffmpeg(AVFormatContext **fmt_ctx, AVCodecContext **codec_ctx, AVStream **video_stream, const char *filename) {
    av_register_all();

    AVOutputFormat *fmt = av_guess_format(NULL, filename, NULL);
    if (!fmt) {
        fprintf(stderr, "Не удалось определить выходной формат\n");
        exit(1);
    }

    if (avformat_alloc_output_context2(fmt_ctx, fmt, NULL, filename) < 0) {
        fprintf(stderr, "Не удалось выделить контекст формата\n");
        exit(1);
    }

    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        fprintf(stderr, "Кодек не найден\n");
        exit(1);
    }

    *video_stream = avformat_new_stream(*fmt_ctx, NULL);
    if (!*video_stream) {
        fprintf(stderr, "Не удалось создать поток\n");
        exit(1);
    }

    *codec_ctx = avcodec_alloc_context3(codec);
    if (!*codec_ctx) {
        fprintf(stderr, "Не удалось выделить контекст кодека\n");
        exit(1);
    }

    (*codec_ctx)->codec_type = AVMEDIA_TYPE_VIDEO;
    (*codec_ctx)->width = WIDTH;
    (*codec_ctx)->height = HEIGHT;
    (*codec_ctx)->time_base = (AVRational){1, FPS};
    (*codec_ctx)->framerate = (AVRational){FPS, 1};
    (*codec_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;
    (*codec_ctx)->gop_size = 10;
    (*codec_ctx)->max_b_frames = 1;

    av_opt_set(*codec_ctx, "preset", "fast", 0);
    av_opt_set_int(*codec_ctx, "crf", 23, 0);

    (*video_stream)->time_base = (*codec_ctx)->time_base;
    (*video_stream)->codecpar->codec_id = fmt->video_codec;
    (*video_stream)->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    (*video_stream)->codecpar->width = (*codec_ctx)->width;
    (*video_stream)->codecpar->height = (*codec_ctx)->height;
    (*video_stream)->codecpar->format = (*codec_ctx)->pix_fmt;

    if ((*fmt_ctx)->oformat->flags & AVFMT_GLOBALHEADER) {
        (*codec_ctx)->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    if (avcodec_open2(*codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "Не удалось открыть кодек\n");
        exit(1);
    }

    if (avcodec_parameters_from_context((*video_stream)->codecpar, *codec_ctx) < 0) {
        fprintf(stderr, "Не удалось скопировать параметры кодека\n");
        exit(1);
    }

    if (!(fmt->flags & AVFMT_NOFILE)) {
        if (avio_open(&(*fmt_ctx)->pb, filename, AVIO_FLAG_WRITE) < 0) {
            fprintf(stderr, "Не удалось открыть выходной файл\n");
            exit(1);
        }
    }

    if (avformat_write_header(*fmt_ctx, NULL) < 0) {
        fprintf(stderr, "Не удалось записать заголовок\n");
        exit(1);
    }
}

void capture_and_encode(int fd, AVFormatContext *fmt_ctx, AVCodecContext *codec_ctx, struct SwsContext *sws_ctx, AVStream *video_stream, int duration) {
    struct v4l2_buffer buffer;
    AVFrame *frame = av_frame_alloc();
    AVPacket packet;
    av_init_packet(&packet);

    frame->format = codec_ctx->pix_fmt;
    frame->width = codec_ctx->width;
    frame->height = codec_ctx->height;
    av_image_alloc(frame->data, frame->linesize, codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, 32);

    struct timespec start_time;
    clock_gettime(CLOCK_MONOTONIC, &start_time);
    double elapsed_time = 0;
    int64_t pts_counter = 0;
    int frame_count = 0;

    while (elapsed_time < duration) {
        fd_set fds;
        struct timeval tv;
        int r;

        FD_ZERO(&fds);
        FD_SET(fd, &fds);

        tv.tv_sec = 2;
        tv.tv_usec = 0;

        r = select(fd + 1, &fds, NULL, NULL, &tv);
        if (r == -1) {
            perror("select");
            exit(1);
        }

        if (r == 0) {
            fprintf(stderr, "Тайм-аут select\n");
            exit(1);
        }

        memset(&buffer, 0, sizeof(buffer));
        buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buffer.memory = V4L2_MEMORY_MMAP;

        if (ioctl(fd, VIDIOC_DQBUF, &buffer) < 0) {
            if (errno == EAGAIN) continue;
            perror("Не удалось извлечь буфер");
            exit(1);
        }

        uint8_t *src_slices[1] = {buffers[buffer.index].start};
        int src_stride[1] = {WIDTH * 2}; // UYVY занимает 2 байта на пиксель

        sws_scale(sws_ctx, src_slices, src_stride, 0, HEIGHT, frame->data, frame->linesize);

        frame->pts = pts_counter;
        pts_counter += av_rescale_q(1, (AVRational){1, FPS}, codec_ctx->time_base);

        if (avcodec_send_frame(codec_ctx, frame) < 0) {
            fprintf(stderr, "Ошибка при отправке кадра\n");
            exit(1);
        }

        while (avcodec_receive_packet(codec_ctx, &packet) == 0) {
            av_packet_rescale_ts(&packet, codec_ctx->time_base, video_stream->time_base);
            packet.stream_index = video_stream->index;

            if (av_interleaved_write_frame(fmt_ctx, &packet) < 0) {
                fprintf(stderr, "Ошибка записи кадра\n");
                exit(1);
            }

            av_packet_unref(&packet);
        }
        printf("Обработан кадр %d\n", frame_count);

        if (ioctl(fd, VIDIOC_QBUF, &buffer) < 0) {
            perror("Не удалось повторно расставить буфер");
            exit(1);
        }
        frame_count++;
        struct timespec current_time;
        clock_gettime(CLOCK_MONOTONIC, &current_time);
        elapsed_time = (current_time.tv_sec - start_time.tv_sec) + (current_time.tv_nsec - start_time.tv_nsec) / 1e9;
        printf("Прошло времени: %f секунд\n", elapsed_time);
    }

    av_freep(&frame->data[0]);
    av_frame_free(&frame);
    printf("Всего обработано кадров: %d\n", frame_count);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Использование: %s <выходной файл>\n", argv[0]);
        exit(1);
    }

    const char *output_file = argv[1];
    int fd;
    open_device(&fd, "/dev/video2");

    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = WIDTH;
    fmt.fmt.pix.height = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
    fmt.fmt.pix.field = V4L2_FIELD_NONE;

    if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("Установка формата пикселей");
        exit(1);
    }

    if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY) {
        fprintf(stderr, "Устройство не поддерживает формат UYVY\n");
        exit(1);
    }

    init_mmap(fd);
    start_capturing(fd);

    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *codec_ctx = NULL;
    AVStream *video_stream = NULL;

    initialize_ffmpeg(&fmt_ctx, &codec_ctx, &video_stream, output_file);

    struct SwsContext *sws_ctx = sws_getContext(WIDTH, HEIGHT, AV_PIX_FMT_UYVY422,
                                                WIDTH, HEIGHT, AV_PIX_FMT_YUV420P,
                                                SWS_BICUBIC, NULL, NULL, NULL);

    if (!sws_ctx) {
        fprintf(stderr, "Не удалось инициализировать SwsContext\n");
        exit(1);
    }

    capture_and_encode(fd, fmt_ctx, codec_ctx, sws_ctx, video_stream, DURATION);

    sws_freeContext(sws_ctx);
    av_write_trailer(fmt_ctx);
    avcodec_free_context(&codec_ctx);
    avformat_free_context(fmt_ctx);
    stop_capturing(fd);
    unmap_buffers();
    close(fd);

    return 0;
}

Спасибо!

Ответ или решение

Проблемы с продолжительностью видеозаписи и плавностью воспроизведения при использовании v4l2 для MP4 (FFmpeg)

Описание проблемы

Вы столкнулись с двумя основными проблемами при записи видео с USB-устройства с использованием v4l2 и сохранением результата в формате MP4 с помощью FFmpeg. Во-первых, продолжительность записанного видео оказывается короче ожидаемой: вместо одной минуты видео длиной 58–59 секунд при разрешении 1280×720 и 28–30 секунд при 1920×1080. Во-вторых, видео воспроизводится не гладко, с заметными пропусками кадров и несоответствиями.

Причины и возможные решения

Давайте подробно рассмотрим возможные причины каждой из упомянутых проблем и предложим решения.

  1. Сжатие и управление временными метками

    • Проблема: Ваша кодировка и передача кадров могут не совпадать с ожидаемыми временными метками. Это может произойти, если вы неправильно обрабатываете PTS (Presentation Time Stamp).
    • Решение: Убедитесь, что при установке pts значение передается корректно. Необходимо использовать метод av_rescale_q для корректного преобразования временных меток соответствия вашему time_base.
  2. Контроль частоты кадров

    • Проблема: Если ваша USB-камера не поддерживает заданное число кадров в секунду (FPS), это может привести к плохому качеству записи и неправильной длительности. Если ваша камера не поддерживает 30 FPS, вы можете наблюдать такие проблемы.
    • Решение: Проверьте, поддерживает ли камера выбранные вами разрешения и частоты кадров. Можете использовать команду v4l2-ctl --list-formats-ext для отображения всех поддерживаемых форматов и частот кадров вашей камеры.
  3. Параметры настройки кодека

    • Проблема: Ваша конфигурация кодека может быть недостаточно оптимизированной, что приведет к проблемам с качеством видео.
    • Решение: Настройка параметров кодека, таких как gop_size и max_b_frames, может значительно повлиять на размер и качество выходного файла. Рассмотрите возможность уменьшения размера GOP, чтобы улучшить управление буферизацией и более плавное видео.
  4. Проблемы с буферизацией

    • Проблема: Если видео буферизуется или не обрабатывается должным образом, это может привести к пропуску кадров.
    • Решение: Убедитесь, что вы правильно обрабатываете очередь буферов. Вы должны повторно ставить буферы в очередь после завершения их обработки, чтобы избежать их потери.
  5. Настройки SWSContext

    • Проблема: Неправильные настройки контекста преобразования цветового пространства могут стать причиной потери качества и плавности видео.
    • Решение: Перепроверьте параметры настройки sws_getContext. Использование SWS_BICUBIC как метода преобразования обычно подходит, но может быть имеет смысл протестировать и другие методы (например, SWS_LANCZOS).
  6. Тестирование с различными разрешениями и настройками

    • Рекомендуется протестировать вашу программу с различными разрешениями и частотой кадров, чтобы понять, какое из них дает наилучший результат. Если проблема сохраняется только на более высоких разрешениях, возможно, ваша камера или USB-интерфейс не справляются с высоким потоком данных.

Заключение

Приведенные выше рекомендации должны помочь вам в диагностике и разрешении проблем с продолжительностью записанного видео и его гладким воспроизведением. Обратите внимание на правильность управления временными метками, разрешениями и настройками кодека, чтобы достичь желаемого результата. Если новые изменения не решают проблему, стоит рассмотреть возможность использования других форматов или библиотек, для более глубоких анализов аудио- и видеоданных.

Оцените материал
Добавить комментарий

Капча загружается...