LINUX.ORG.RU

SDL3 - Камера - Заметка

 , , , , sdl3


2

1

Короче захотел сделать фотку через SDL3 столкнулся с парой проблем.

Пишу просто по причине того что я долго ломал голову что не так, откуда ошибка, пока не втыкнул паузу, почему вместо снимка чернота, пока не пропустил несколько первых кадров. Для меня это было нифига не очевидно, может кому сэкономит время.

  1. Появляется ошибка Parameter 'surface' is invalid если не сделать паузу после открытия камеры, решается в установлении паузы в 0,5 секунды после SDL_OpenCamera

  2. Первые кадры с камеры либо просто чёрные, либо очень тёмные. Решается пропуском (в моём случае 7) нескольких первых кадров.

Я забросил в SDL3 уведомительное issue, чтобы было

Продублирую тут тестовый пример и результаты

Информация о системе

Web-camera встроена в ноут, понятия не имею что за камера
description: Notebook
    product: K53U
    vendor: ASUSTeK Computer Inc.
    version: 1.0
Distributor ID:	Debian
Description:	Debian GNU/Linux 12 (bookworm)
Release:	12
Codename:	bookworm
fedor@nixfed:~/drawshoot$ cat SDL3/VERSION.txt 
3.1.3
fedor@nixfed:~/drawshoot$

Тестовый код

Makefile

SDL_DIR=SDL3
SDL_LIB_DIR=$(SDL_DIR)/build
SDL_INC_DIR=$(SDL_DIR)/include

test-1:
	$(CC) main.c -I$(SDL_INC_DIR) -L$(SDL_LIB_DIR) -lSDL3 -o app

test-2:
	$(CC) main.c -I$(SDL_INC_DIR) -L$(SDL_LIB_DIR) -lSDL3 -o app -DENABLE_DELAY

test-3:
	$(CC) main.c -I$(SDL_INC_DIR) -L$(SDL_LIB_DIR) -lSDL3 -o app -DENABLE_DELAY -DENABLE_FRAMEDROP


sdl:
	cd $(SDL_DIR) && mkdir build
	cd  $(SDL_DIR)/build && cmake ..
	cd  $(SDL_DIR)/build && $(MAKE) -j2

clean:
	-$(MAKE) -C $(SDL_DIR) clean
	-rm app

run:
	LD_LIBRARY_PATH=$(SDL_DIR)/build ./app

main.c

#include "SDL3/SDL.h"
#include "SDL3/SDL_camera.h"
#include <stdio.h>

/*just for debug*/
const char * pixel_format_name(SDL_PixelFormat format)
{
    switch(format){
    case SDL_PIXELFORMAT_UNKNOWN:    return      "UNKNOWN";
    case SDL_PIXELFORMAT_INDEX1LSB:  return      "INDEX1LSB";
    case SDL_PIXELFORMAT_INDEX1MSB:  return      "INDEX1MSB";
    case SDL_PIXELFORMAT_INDEX2LSB:  return      "INDEX2LSB";
    case SDL_PIXELFORMAT_INDEX2MSB:  return      "INDEX2MSB";
    case SDL_PIXELFORMAT_INDEX4LSB:  return      "INDEX4LSB";
    case SDL_PIXELFORMAT_INDEX4MSB:  return      "INDEX4MSB";
    case SDL_PIXELFORMAT_INDEX8:     return      "INDEX8";
    case SDL_PIXELFORMAT_RGB332:     return      "RGB332";
    case SDL_PIXELFORMAT_XRGB4444:   return      "XRGB4444";
    case SDL_PIXELFORMAT_XBGR4444:   return      "XBGR4444";
    case SDL_PIXELFORMAT_XRGB1555:   return      "XXRGB1555";
    case SDL_PIXELFORMAT_XBGR1555:   return      "XXBGR1555";
    case SDL_PIXELFORMAT_ARGB4444:   return      "XARGB4444";
    case SDL_PIXELFORMAT_RGBA4444:   return      "XRGBA4444";
    case SDL_PIXELFORMAT_ABGR4444:   return      "XABGR4444";
    case SDL_PIXELFORMAT_BGRA4444:   return      "XBGRA4444";
    case SDL_PIXELFORMAT_ARGB1555:   return      "XARGB1555";
    case SDL_PIXELFORMAT_RGBA5551:   return      "XRGBA5551";
    case SDL_PIXELFORMAT_ABGR1555:   return      "XABGR1555";
    case SDL_PIXELFORMAT_BGRA5551:   return      "XBGRA5551";
    case SDL_PIXELFORMAT_RGB565:     return      "XRGB565";
    case SDL_PIXELFORMAT_BGR565:     return      "XBGR565";
    case SDL_PIXELFORMAT_RGB24:      return      "XRGB24";
    case SDL_PIXELFORMAT_BGR24:      return      "XBGR24";
    case SDL_PIXELFORMAT_XRGB8888:   return      "XXRGB8888";
    case SDL_PIXELFORMAT_RGBX8888:   return      "XRGBX8888";
    case SDL_PIXELFORMAT_XBGR8888:   return      "XXBGR8888";
    case SDL_PIXELFORMAT_BGRX8888:   return      "XBGRX8888";
    case SDL_PIXELFORMAT_ARGB8888:   return      "XARGB8888";
    case SDL_PIXELFORMAT_RGBA8888:   return      "XRGBA8888";
    case SDL_PIXELFORMAT_ABGR8888:   return      "XABGR8888";
    case SDL_PIXELFORMAT_BGRA8888:   return      "XBGRA8888";
    case SDL_PIXELFORMAT_XRGB2101010:   return   "XXRGB2101010";
    case SDL_PIXELFORMAT_XBGR2101010:   return   "XXBGR2101010";
    case SDL_PIXELFORMAT_ARGB2101010:   return   "XARGB2101010";
    case SDL_PIXELFORMAT_ABGR2101010:   return   "XABGR2101010";
    case SDL_PIXELFORMAT_RGB48:         return   "XRGB48";
    case SDL_PIXELFORMAT_BGR48:         return   "XBGR48";
    case SDL_PIXELFORMAT_RGBA64:        return   "XRGBA64";
    case SDL_PIXELFORMAT_ARGB64:        return   "XARGB64";
    case SDL_PIXELFORMAT_BGRA64:        return   "XBGRA64";
    case SDL_PIXELFORMAT_ABGR64:        return   "XABGR64";
    case SDL_PIXELFORMAT_RGB48_FLOAT:   return   "XRGB48_FLOAT";
    case SDL_PIXELFORMAT_BGR48_FLOAT:   return   "XBGR48_FLOAT";
    case SDL_PIXELFORMAT_RGBA64_FLOAT:  return   "XRGBA64_FLOAT";
    case SDL_PIXELFORMAT_ARGB64_FLOAT:  return   "XARGB64_FLOAT";
    case SDL_PIXELFORMAT_BGRA64_FLOAT:  return   "XBGRA64_FLOAT";
    case SDL_PIXELFORMAT_ABGR64_FLOAT:  return   "XABGR64_FLOAT";
    case SDL_PIXELFORMAT_RGB96_FLOAT:   return   "XRGB96_FLOAT";
    case SDL_PIXELFORMAT_BGR96_FLOAT:   return   "XBGR96_FLOAT";
    case SDL_PIXELFORMAT_RGBA128_FLOAT: return   "XRGBA128_FLOAT";
    case SDL_PIXELFORMAT_ARGB128_FLOAT: return   "XARGB128_FLOAT";
    case SDL_PIXELFORMAT_BGRA128_FLOAT: return   "XBGRA128_FLOAT";
    case SDL_PIXELFORMAT_ABGR128_FLOAT: return   "XABGR128_FLOAT";
    case SDL_PIXELFORMAT_YV12:          return   "XYV12";
    case SDL_PIXELFORMAT_IYUV:          return   "XIYUV";
    case SDL_PIXELFORMAT_YUY2:          return   "XYUY2";
    case SDL_PIXELFORMAT_UYVY:          return   "XUYVY";
    case SDL_PIXELFORMAT_YVYU:          return   "XYVYU";
    case SDL_PIXELFORMAT_NV12:          return   "XNV12";
    case SDL_PIXELFORMAT_NV21:          return   "XNV21";
    case SDL_PIXELFORMAT_P010:          return   "XP010";
    case SDL_PIXELFORMAT_EXTERNAL_OES:  return   "XEXTERNAL_OES";
    }
    return "UNKNOWN";
}

int main(int argc, char *argv[])
{
    SDL_InitSubSystem(SDL_INIT_CAMERA);
    int camera_count = 0;
    SDL_CameraID * camera_id = SDL_GetCameras(&camera_count);
    printf("count=%d id=%u %s\n",camera_count,camera_id,SDL_GetError());
    SDL_CameraSpec ** camera_spec;
    int camera_specs = 0;
    camera_spec = SDL_GetCameraSupportedFormats(*camera_id,&camera_specs);
    /*show supported formats and resolutions*/
    for(int i=0; i != camera_specs;i++)
    {
        printf("camera=%-2i "
               "w=%-4d h=%-4d "
               "format=%-14s "
               "frame_n=%-3d "
               "frame_d=%d\n",
               i,
               camera_spec[i]->width,
               camera_spec[i]->height,
               pixel_format_name(camera_spec[i]->format),
               camera_spec[i]->framerate_numerator,
               camera_spec[i]->framerate_denominator);
    }
    /*init camera*/
    SDL_Camera * cam =  SDL_OpenCamera(*camera_id, camera_spec[0]);

    #ifdef ENABLE_DELAY
        SDL_Delay(5000);
    #endif

    printf("[1] %s\n",SDL_GetError());
    SDL_Surface * frame;
    Uint64 t=0;
    SDL_Surface * s;
    SDL_Event event;
    while ( SDL_WaitEvent(&event) >= 0 )
    {
       if(event.type == SDL_EVENT_CAMERA_DEVICE_APPROVED)
       {
            printf("DEVICE_APPROVED!\n");
            break;
       };
       if(event.type == SDL_EVENT_CAMERA_DEVICE_DENIED)
       {
            printf("DEVICE_DENIED!\n");
            return 0;
       };
    }

    #ifdef ENABLE_FRAMEDROP
    for(int i=0;i<7;i++)
    {
        frame = SDL_AcquireCameraFrame(cam, &t);
        SDL_ReleaseCameraFrame(cam, frame);
    }
    #endif

    /*take photo*/
    frame = SDL_AcquireCameraFrame(cam, &t);
    printf("[2] %lu %s\n",t,SDL_GetError());
    /*convert photo*/
    s = SDL_ConvertSurface(frame, SDL_PIXELFORMAT_RGB24);
    /*save result*/
    SDL_SaveBMP(s,"out.bmp");
    printf("[4] %lu %s\n",t,SDL_GetError());
    /*release memory*/
    SDL_ReleaseCameraFrame(cam, frame);
    printf("[4] %lu %s\n",t,SDL_GetError());
    SDL_Quit();
    return 0;
}

Результаты

  • Ошибка Invalid surface
fedor@nixfed:~/drawshoot$ make test-1 run
cc main.c -ISDL3/include -LSDL3/build -lSDL3 -o app
LD_LIBRARY_PATH=SDL3/build ./app
count=1 id=845253408 
camera=0  w=640  h=480  format=XYUY2          frame_n=30  frame_d=1
camera=1  w=640  h=480  format=XYUY2          frame_n=25  frame_d=1
camera=2  w=640  h=480  format=XYUY2          frame_n=20  frame_d=1
camera=3  w=352  h=288  format=XYUY2          frame_n=30  frame_d=1
camera=4  w=352  h=288  format=XYUY2          frame_n=25  frame_d=1
camera=5  w=352  h=288  format=XYUY2          frame_n=20  frame_d=1
camera=6  w=320  h=240  format=XYUY2          frame_n=30  frame_d=1
camera=7  w=320  h=240  format=XYUY2          frame_n=25  frame_d=1
camera=8  w=320  h=240  format=XYUY2          frame_n=20  frame_d=1
camera=9  w=176  h=144  format=XYUY2          frame_n=30  frame_d=1
camera=10 w=176  h=144  format=XYUY2          frame_n=25  frame_d=1
camera=11 w=176  h=144  format=XYUY2          frame_n=20  frame_d=1
camera=12 w=160  h=120  format=XYUY2          frame_n=30  frame_d=1
camera=13 w=160  h=120  format=XYUY2          frame_n=25  frame_d=1
camera=14 w=160  h=120  format=XYUY2          frame_n=20  frame_d=1
[1] 
DEVICE_APPROVED!
[2] 0 
[4] 0 Parameter 'surface' is invalid
[4] 0 Parameter 'surface' is invalid
fedor@nixfed:~/drawshoot$ 

  • Чёрный снимок вместо нормального
fedor@nixfed:~/drawshoot$ make test-2 run
cc main.c -ISDL3/include -LSDL3/build -lSDL3 -o app -DENABLE_DELAY
LD_LIBRARY_PATH=SDL3/build ./app
count=1 id=3957504800 
camera=0  w=640  h=480  format=XYUY2          frame_n=30  frame_d=1
camera=1  w=640  h=480  format=XYUY2          frame_n=25  frame_d=1
camera=2  w=640  h=480  format=XYUY2          frame_n=20  frame_d=1
camera=3  w=352  h=288  format=XYUY2          frame_n=30  frame_d=1
camera=4  w=352  h=288  format=XYUY2          frame_n=25  frame_d=1
camera=5  w=352  h=288  format=XYUY2          frame_n=20  frame_d=1
camera=6  w=320  h=240  format=XYUY2          frame_n=30  frame_d=1
camera=7  w=320  h=240  format=XYUY2          frame_n=25  frame_d=1
camera=8  w=320  h=240  format=XYUY2          frame_n=20  frame_d=1
camera=9  w=176  h=144  format=XYUY2          frame_n=30  frame_d=1
camera=10 w=176  h=144  format=XYUY2          frame_n=25  frame_d=1
camera=11 w=176  h=144  format=XYUY2          frame_n=20  frame_d=1
camera=12 w=160  h=120  format=XYUY2          frame_n=30  frame_d=1
camera=13 w=160  h=120  format=XYUY2          frame_n=25  frame_d=1
camera=14 w=160  h=120  format=XYUY2          frame_n=20  frame_d=1
[1] 
DEVICE_APPROVED!
[2] 706952397 
[4] 706952397 
[4] 706952397 
fedor@nixfed:~/drawshoot$ 
  • Всё хорошо, ошибок нет, фотография с камеры нормальная
fedor@nixfed:~/drawshoot$ make test-3 run
cc main.c -ISDL3/include -LSDL3/build -lSDL3 -o app -DENABLE_DELAY -DENABLE_FRAMEDROP
LD_LIBRARY_PATH=SDL3/build ./app
count=1 id=3940756256 
camera=0  w=640  h=480  format=XYUY2          frame_n=30  frame_d=1
camera=1  w=640  h=480  format=XYUY2          frame_n=25  frame_d=1
camera=2  w=640  h=480  format=XYUY2          frame_n=20  frame_d=1
camera=3  w=352  h=288  format=XYUY2          frame_n=30  frame_d=1
camera=4  w=352  h=288  format=XYUY2          frame_n=25  frame_d=1
camera=5  w=352  h=288  format=XYUY2          frame_n=20  frame_d=1
camera=6  w=320  h=240  format=XYUY2          frame_n=30  frame_d=1
camera=7  w=320  h=240  format=XYUY2          frame_n=25  frame_d=1
camera=8  w=320  h=240  format=XYUY2          frame_n=20  frame_d=1
camera=9  w=176  h=144  format=XYUY2          frame_n=30  frame_d=1
camera=10 w=176  h=144  format=XYUY2          frame_n=25  frame_d=1
camera=11 w=176  h=144  format=XYUY2          frame_n=20  frame_d=1
camera=12 w=160  h=120  format=XYUY2          frame_n=30  frame_d=1
camera=13 w=160  h=120  format=XYUY2          frame_n=25  frame_d=1
camera=14 w=160  h=120  format=XYUY2          frame_n=20  frame_d=1
[1] 
DEVICE_APPROVED!
[2] 1329622684 
[4] 1329622684 
[4] 1329622684 
fedor@nixfed:~/drawshoot$ 

Может быть кому пригодится, досвиданья ::)

★★★★★

Последнее исправление: LINUX-ORG-RU (всего исправлений: 3)

Ответ на: комментарий от anonymous2

В смысле? Ты получаешь фреймы/снимки, а дальше делай что угодно, склеивай из них видео и кодируй, просто отправляй сразу на отрисовку, или сохраняй покадрово. Видеокамера это просто быстрый фотоаппарат. Ну или я не понял тебя :)

LINUX-ORG-RU ★★★★★
() автор топика
Последнее исправление: LINUX-ORG-RU (всего исправлений: 2)
Ответ на: комментарий от peregrine

SDL2 не умеет, SDL3 умеет. Основная суть также что и была, просто в трёшке ещё мультимедиа слоёв напихали, там и звук сильно переработан, сенсоры, вибрация, но я толком не тыкал. Игры могут быть и для 3D шлемов, через камеру можно положение в пространстве вычислять, дополненная реальность ещё. Всё это мулитимедиа. Но так-то, часто от SDL получают лишь окошко, ввод и графический контекст, а дальше всё сами.

LINUX-ORG-RU ★★★★★
() автор топика
Ответ на: комментарий от firkax

Не лучше, цели другие. На вкус, цвет, условия и потребности. В SDL работа с камерой это просто тонкая прослойка к системе и всё. Дабы упростить себе жизнь, взял собрал под линукс, взял собрал под андроид, взял собрал под игровую приставку, взял под BSD и не паришься в каком огороде очередной огородник какой заборы понаставил. А так нет не для GUI софта, а просто для доступа к базовым мультимедийным возможностям системы, ввод, вывод, мышка, клава, окна, геймпад, вибрация, сенсор, звук, и так далее. А уж что из этого будешь использовать и с чем собирать дело хозяйское.

Более того, если под некую ОС или особенную сборку уже существующей нет реализации доступа к камере в SDL, но есть уже пропатченный ffmpeg, можно использовать ffmpeg под капотом SDL для доступа к камере, ffmpeg станет при этом бэком. Ну как пример. К слову можно и наоборот, через SDL работать с камерой в ffmpeg. Так что тут понятие лучше/хуже, работает только в рамках конкретного случая, для чего-то лучше, для чего-то хуже, а порой без разницы.

LINUX-ORG-RU ★★★★★
() автор топика
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)

Ошибка Invalid surface

Из описания к SDL_AcquireCameraFrame:

This is a non blocking API. If there is a frame available, a non-NULL surface is returned, and timestampNS will be filled with a non-zero value.

Note that an error case can also return NULL, but a NULL by itself is normal and just signifies that a new frame is not yet available. Note that even if a camera device fails outright (a USB camera is unplugged while in use, etc), SDL will send an event separately to notify the app, but continue to provide blank frames at ongoing intervals until SDL_CloseCamera() is called, so real failure here is almost always an out of memory condition.

Тебе надо ждать пока SDL_AcquireCameraFrame вернет не ноль, так как это неблокирующий вызов и готового кадра ещё может не быть. Поэтому и delay помог.

Чёрный снимок вместо нормального

Это мне кажется вполне нормально получить тёмное изображение. Ты открываешь девайс камеры, а камера в этот момент только включилась. Так и здесь, твои магические 7 кадров это 230 мс при частоте кадров 30. Если ты откроешь видоискатель на телефоне, он точно так же сначала будет постепенно поднимать яркость.

a1ba ★★
()
Ответ на: комментарий от a1ba

Да, про таймстап я в глаза продолбился, тут мой косяк, спасибо. Но вот уже с тёмным кадром, ну для меня было впервой, навреняка для кого-то тоже так что пусть будет, а то я наивно получал просто 1 кадр, думая что там будет фотка, а не чернота.

LINUX-ORG-RU ★★★★★
() автор топика
Ответ на: комментарий от LINUX-ORG-RU

Видеокамера это просто быстрый фотоаппарат. Ну или я не понял тебя :)

я бы уточнил наоборот - видеокамера это медленный фотоаппарат.
ты же в курсе что такое баланс белого, освещение и все такое? у фотоаппарата для этого есть доп датчики.
для видео этих пара десятков кадров калибровки ты не заметишь, а для фото ты с таймаутами можешь просто не попасть.

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