Catturare un'immagine con una webcam mediante le funzioni esterne del API di libv4l2

Da Gambas-it.org - Wikipedia.

La risorsa libv4l è una raccolta di librerie per la gestione dei dispositivi video4linux2 evitando che si debba scrivere del codice separato, nella stessa classe, per i diversi dispositivi. libv4l è composta da tre librerie differenti: libv4lconvert, libv4l1 e libv4l2.

La libreria libv4l2, che qui ci interessa, mette a disposizione l'API v4l2 per i dispositivi v4l2.
Per poter fruire delle risorse fornite dalla libreria v4l2 è necessario richiamare nell'applicazione Gambas la libreria condivisa: "libv4l2.so.0.0.0 ".

Mostriamo di seguito un possibile codice per la cattura di un'immagine attraverso una webcam.

L'immagine sarà salvata in un file formato ppm:

Public Struct Buffer
  start As Pointer
  length As Integer
End Struct


Library "libc:6"

Public Struct timeval
  tv_sec As Long
  tv_usec As Long
End Struct

Public Struct v4l2_requestbuffers
  count As Integer
  type As Integer
  memory_ As Integer
  capabilities As Integer
  flags As Byte
  reserved[3] As Byte
End Struct

Public Struct v4l2_timecode
  type As Integer
  flags As Integer
  frames As Byte
  seconds As Byte
  minutes As Byte
  hours As Byte
  userbits[4] As Byte
End Struct

Public Struct v4l2_buffer
  index As Integer
  type As Integer
  bytesused As Integer
  flags As Integer
  field As Integer
  timestamp As Struct Timeval
  timecode As Struct V4l2_timecode
  sequence As Integer
  memory_ As Integer
  m As Long
  length As Integer
  reserved2 As Integer
  request_fd As Integer
End Struct

Private Enum O_RDONLY = 0, O_WRONLY, O_RDWR, O_NONBLOCK = &2000
Private Enum PROT_NONE = 0, PROT_READ, PROT_WRITE
Private Enum V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, V4L2_BUF_TYPE_VIDEO_OUTPUT, V4L2_BUF_TYPE_VIDEO_OVERLAY,
             V4L2_BUF_TYPE_VBI_CAPTURE, V4L2_BUF_TYPE_VBI_OUTPUT, V4L2_BUF_TYPE_SLICED_VBI_CAPTURE,
             V4L2_BUF_TYPE_SLICED_VBI_OUTPUT, V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
             V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, V4L2_BUF_TYPE_SDR_CAPTURE, V4L2_BUF_TYPE_SDR_OUTPUT,
             V4L2_BUF_TYPE_META_CAPTURE, V4L2_BUF_TYPE_META_OUTPUT
Private Enum V4L2_FIELD_ANY, V4L2_FIELD_NONE, V4L2_FIELD_TOP, V4L2_FIELD_BOTTOM, V4L2_FIELD_INTERLACED,
             V4L2_FIELD_SEQ_TB, V4L2_FIELD_SEQ_BT, V4L2_FIELD_ALTERNATE, V4L2_FIELD_INTERLACED_TB,
             V4L2_FIELD_INTERLACED_BT
Private Enum V4L2_MEMORY_MMAP = 1, V4L2_MEMORY_USERPTR, V4L2_MEMORY_OVERLAY, V4L2_MEMORY_DMABUF
Private Const V4L2_PIX_FMT_RGB24 As Integer = 859981650
Private Const VIDIOC_S_FMT As Long = 3234878981
Private Const VIDIOC_REQBUFS As Long = 3222558216
Private Const VIDIOC_QUERYBUF As Long = 3227014665
Private Const VIDIOC_QBUF As Long = 3227014671
Private Const VIDIOC_STREAMON As Long = 1074026002
Private Const VIDIOC_STREAMOFF As Long = 1074026003
Private Const VIDIOC_DQBUF As Long = 3227014673
Private Const MAP_SHARED As Integer = 1
 
' int select (int __nfds, fd_set *__restrict __readfds, fd_set *__restrict __writefds, fd_set *__restrict __exceptfds, struct timeval *__restrict __timeout)
' Blocks the calling process until there is activity on any of the specified sets of file descriptors, or until the timeout period has expired.
Private Extern select_C(nfds As Integer, readfds As Pointer, writefds As Pointer, exceptfds As Pointer, timeout As Timeval) As Integer Exec "select"

 
Library "libv4l2:0.0.0"

' int v4l2_open(const char *file, int oflag, ...)
' Open a V4L2 device.
Private Extern v4l2_open(fl As String, oflag As Integer, alterum As Integer) As Integer

' int v4l2_ioctl(int fd, unsigned long int request, ...)
' Program a V4L2 device.
Private Extern v4l2_ioctl_pointer(fd As Integer, request As Long, arg As Pointer) As Integer Exec "v4l2_ioctl"

' int v4l2_ioctl(int fd, unsigned long int request, ...)
' Program a V4L2 device.
Private Extern v4l2_ioctl_buffer(fd As Integer, request As Long, arg As V4l2_buffer) As Integer Exec "v4l2_ioctl"

' int v4l2_ioctl(int fd, unsigned long int request, ...)
' Program a V4L2 device.
Private Extern v4l2_ioctl_request(fd As Integer, request As Long, arg As V4l2_requestbuffers) As Integer Exec "v4l2_ioctl"

' void *v4l2_mmap(void *start, size_t length, int prot, int flags, int fd, int64_t offset)
' Map device memory into application address space.
Private Extern v4l2_mmap(start As Pointer, lengthI As Integer, prot As Integer, flags As Integer, fd As Integer, offset As Long) As Pointer

' int v4l2_munmap(void *start, size_t length)
' Unmap device memory.
Private Extern v4l2_munmap(startP As Pointer, lengthI As Integer) As Integer

' int v4l2_close(int fd)
' Close a V4L2 device.
Private Extern v4l2_close(fd As Integer)


Public Sub Main()

  Dim buf As New V4l2_buffer
  Dim req As New V4l2_requestbuffers
  Dim buffers As Buffer[]
  Dim tv As New Timeval
  Dim fout As File
  Dim fmt, p As Pointer
  Dim st As Stream
  Dim fd, r, width, height, pix, type As Integer
  Dim ppm As String

  Write "\e[31m\e[5mAcquisizione immagini...\e[0m"
  Flush

  fd = v4l2_open("/dev/video0", O_RDWR Or O_NONBLOCK, 0)

  fmt = Alloc(SizeOf(gb.Byte), 208)
  st = Memory fmt For Read Write
  Write #st, V4L2_BUF_TYPE_VIDEO_CAPTURE As Integer
  Seek #st, 8
  Write #st, 640 As Integer
  Write #st, 480 As Integer
  Write #st, V4L2_PIX_FMT_RGB24 As Integer
  Write #st, V4L2_FIELD_INTERLACED As Integer

  Repeat
    r = v4l2_ioctl_pointer(fd, VIDIOC_S_FMT, fmt)
    If r = -1 Then Error.Raise("Errore !")
  Until r > -1
  Seek #st, 8
  Read #st, width
  Read #st, height
  Read #st, pix
  If (width <> 640) Or (height <> 480) Then Print "Attenzione: il dispositivo imposterà l'immagine alle dimensioni";; width; "x"; height
  If pix <> V4L2_PIX_FMT_RGB24 Then Error.Raise("Libv4l non accetta il formato RGB24.\nImpossibile procedere !")
  st.Close
  Free(fmt)

  With req
    .count = 1
    .type = V4L2_BUF_TYPE_VIDEO_CAPTURE
    .memory_ = V4L2_MEMORY_MMAP
  End With 
  Repeat
    r = v4l2_ioctl_request(fd, VIDIOC_REQBUFS, req)
    If r = -1 Then Error.Raise("Errore !")
  Until r > -1

  buffers = New Buffer[req.count]

  For n_buffers As Integer = 0 To req.count - 1
    With buf
      .index = n_buffers
      .type = V4L2_BUF_TYPE_VIDEO_CAPTURE
      .memory_ = V4L2_MEMORY_MMAP
    End With 
    Repeat
      r = v4l2_ioctl_buffer(fd, VIDIOC_QUERYBUF, buf)
      If r = -1 Then Error.Raise("Errore !")
    Until r > -1
    With buffers[n_buffers] = New Buffer
      .length = buf.length
      .start = v4l2_mmap(Null, buf.length, PROT_READ Or PROT_WRITE, MAP_SHARED, fd, buf.m)
    End With
  Next

  For i As Integer = 0 To n_buffers - 1
    With buf
      .index = i
      .type = V4L2_BUF_TYPE_VIDEO_CAPTURE
      .memory_ = V4L2_MEMORY_MMAP
    End With 
    Repeat
      r = v4l2_ioctl_buffer(fd, VIDIOC_QBUF, buf)
      If r = -1 Then Error.Raise("Errore !")
    Until r > -1
  Next
  type = V4L2_BUF_TYPE_VIDEO_CAPTURE
  Repeat
    r = v4l2_ioctl_pointer(fd, VIDIOC_STREAMON, VarPtr(type))
    If r = -1 Then Error.Raise("Errore !")
  Until r > -1
  Repeat
    tv.tv_sec = 1
    r = select_C(fd + 1, Null, Null, Null, tv)
    If r = -1 Then Error.Raise("Errore !")
  Until r > -1
  buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
  buf.memory_ = V4L2_MEMORY_MMAP
  Repeat
    r = v4l2_ioctl_buffer(fd, VIDIOC_DQBUF, buf)
    If r = -1 Then Error.Raise("Errore !")
  Until r > -1

  ppm = "/tmp/V4L" &/ Format(Now, "dd-mm-yyyy_hh:nn:ss") & ".ppm"
' Scrive i dati d'intestazione del file immagine PPM:
  File.Save(ppm, "P6\x0A" & CStr(width) & Chr(32) & CStr(height) & " 255\x0A")

  fout = Open ppm For Write Append 

  p = buffers[buf.index].start
  If p == 0 Then Error.Raise("Errore !")

' Aggiunge i dati immagine al file PPM:
  Write #fout, p, buf.bytesused    ' [nota 1]

  fout.Close


' Va in chiusura:
  i = V4L2_BUF_TYPE_VIDEO_CAPTURE
  Repeat
    r = v4l2_ioctl_pointer(fd, VIDIOC_STREAMOFF, VarPtr(i))
    If r = -1 Then Error.Raise("Errore !")
  Until r > -1

  For i = 0 To n_buffers - 1
    v4l2_munmap(buffers[i].start, buffers[i].length)
  Next

  v4l2_close(fd)

  Write "\rAcquisite n. " & t & " immagini !"

End


Note

[1] Per questo uso del Puntatore con WRITE vedere: https://www.gambas-it.org/wiki/index.php/Write#Scrivere_in_un_file_i_dati_contenuti_in_un.27area_di_memoria_puntata_da_un_Puntatore


Riferimenti