Вопрос или проблема
Я пытаюсь записать видео с 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, ¤t_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. Во-вторых, видео воспроизводится не гладко, с заметными пропусками кадров и несоответствиями.
Причины и возможные решения
Давайте подробно рассмотрим возможные причины каждой из упомянутых проблем и предложим решения.
-
Сжатие и управление временными метками
- Проблема: Ваша кодировка и передача кадров могут не совпадать с ожидаемыми временными метками. Это может произойти, если вы неправильно обрабатываете PTS (Presentation Time Stamp).
- Решение: Убедитесь, что при установке
pts
значение передается корректно. Необходимо использовать методav_rescale_q
для корректного преобразования временных меток соответствия вашемуtime_base
.
-
Контроль частоты кадров
- Проблема: Если ваша USB-камера не поддерживает заданное число кадров в секунду (FPS), это может привести к плохому качеству записи и неправильной длительности. Если ваша камера не поддерживает 30 FPS, вы можете наблюдать такие проблемы.
- Решение: Проверьте, поддерживает ли камера выбранные вами разрешения и частоты кадров. Можете использовать команду
v4l2-ctl --list-formats-ext
для отображения всех поддерживаемых форматов и частот кадров вашей камеры.
-
Параметры настройки кодека
- Проблема: Ваша конфигурация кодека может быть недостаточно оптимизированной, что приведет к проблемам с качеством видео.
- Решение: Настройка параметров кодека, таких как
gop_size
иmax_b_frames
, может значительно повлиять на размер и качество выходного файла. Рассмотрите возможность уменьшения размера GOP, чтобы улучшить управление буферизацией и более плавное видео.
-
Проблемы с буферизацией
- Проблема: Если видео буферизуется или не обрабатывается должным образом, это может привести к пропуску кадров.
- Решение: Убедитесь, что вы правильно обрабатываете очередь буферов. Вы должны повторно ставить буферы в очередь после завершения их обработки, чтобы избежать их потери.
-
Настройки
SWSContext
- Проблема: Неправильные настройки контекста преобразования цветового пространства могут стать причиной потери качества и плавности видео.
- Решение: Перепроверьте параметры настройки
sws_getContext
. ИспользованиеSWS_BICUBIC
как метода преобразования обычно подходит, но может быть имеет смысл протестировать и другие методы (например,SWS_LANCZOS
).
-
Тестирование с различными разрешениями и настройками
- Рекомендуется протестировать вашу программу с различными разрешениями и частотой кадров, чтобы понять, какое из них дает наилучший результат. Если проблема сохраняется только на более высоких разрешениях, возможно, ваша камера или USB-интерфейс не справляются с высоким потоком данных.
Заключение
Приведенные выше рекомендации должны помочь вам в диагностике и разрешении проблем с продолжительностью записанного видео и его гладким воспроизведением. Обратите внимание на правильность управления временными метками, разрешениями и настройками кодека, чтобы достичь желаемого результата. Если новые изменения не решают проблему, стоит рассмотреть возможность использования других форматов или библиотек, для более глубоких анализов аудио- и видеоданных.