Hi,
after some more research into the entire V4L topic and after looking
into the 5dpo package which contains an almost complete V4L2
implementation (but unfortunately no code for V4l1 and I need both
versions) I have made a little unit that solves my problem in a very
simple way:
* It is using V4l1 API only
* It is using the libv4l1.so wrapper library for V4L2 compatibility
* libv4l1 will also transparently decode all known image formats and
encodings of the V2 cameras (V2 does not allow decoding inside the
driver anymore) into uncompressed BGR24 or YUV420p on the fly.
I have made a little camera application with it (similar to xawtv) and
it was the first day in my life where I could see how all my cameras
(V1 and V2) could work with the *same* application (some worked only
with xawtv, one worked only with mplayer, one did never work before at
all).
This unit will be part of a small application that I will soon release
under GPL. The Unit is licensed under modified LGPL.
Attached is the v4l1 unit. Enjoy!
Bernd
{ Implements the bare minimum of v4l (and v4l2 indirectly though
libv4l1.so), just enough to open the webcam, set the picture
settings and grab images. It does not contain anything regarding
tuners, audio, etc.
v4l2 support is implemented through libv4l1 which will transparently
and automatically emulate the old v4l1 API when the cam has a
v4l2 driver. This will also add support for all known image formats
and encodings which will be converted on the fly into uncompressed
24 bit BGR (VIDEO_PALETTE_RGB24).
Programming against the V4l1 API and then running it though a compatibility
layer to support V4l2 might not be the most recommended way for many
situations but I still need V4l1 support for some old exotic cameras and
this seems to be the most pragmatic way of doing it.
The API definitions from videodev.h were manually translated from FreeBSD's
linux-kmod-compat re-implementation of the original Linux header file.
***
Copyright (c) 2011 Bernd Kreuss <[email protected]>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version with the following modification:
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,and
to copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the terms
and conditions of the license of that module. An independent module is a
module which is not derived from or based on this library. If you modify
this library, you may extend this exception to your version of the library,
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
for more details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
unit v4l1;
{$mode objfpc}{$H+}
interface
const
// these are used in the TVideo_Capability record
VID_TYPE_CAPTURE = 1 ; { Can capture }
VID_TYPE_TUNER = 2 ; { Can tune }
VID_TYPE_TELETEXT = 4 ; { Does teletext }
VID_TYPE_OVERLAY = 8 ; { Overlay onto frame buffer }
VID_TYPE_CHROMAKEY = 16 ; { Overlay by chromakey }
VID_TYPE_CLIPPING = 32 ; { Can clip }
VID_TYPE_FRAMERAM = 64 ; { Uses the frame buffer memory }
VID_TYPE_SCALES = 128 ; { Scalable }
VID_TYPE_MONOCHROME = 256 ; { Monochrome only }
VID_TYPE_SUBCAPTURE = 512 ; { Can capture subareas of the image }
VID_TYPE_MPEG_DECODER = 1024 ; { Can decode MPEG streams }
VID_TYPE_MPEG_ENCODER = 2048 ; { Can encode MPEG streams }
VID_TYPE_MJPEG_DECODER = 4096 ; { Can decode MJPEG streams }
VID_TYPE_MJPEG_ENCODER = 8192 ; { Can encode MJPEG streams }
// these are used in the TVideo_Picture record
VIDEO_PALETTE_GREY = 1; { Linear greyscale }
VIDEO_PALETTE_HI240 = 2; { High 240 cube (BT848) }
VIDEO_PALETTE_RGB565 = 3; { 565 16 bit RGB }
VIDEO_PALETTE_RGB24 = 4; { 24bit RGB }
VIDEO_PALETTE_RGB32 = 5; { 32bit RGB }
VIDEO_PALETTE_RGB555 = 6; { 555 15bit RGB }
VIDEO_PALETTE_YUV422 = 7; { YUV422 capture }
VIDEO_PALETTE_YUYV = 8;
VIDEO_PALETTE_UYVY = 9; { The great thing about standards is ... }
VIDEO_PALETTE_YUV420 = 10;
VIDEO_PALETTE_YUV411 = 11; { YUV411 capture }
VIDEO_PALETTE_RAW = 12; { RAW capture (BT848) }
VIDEO_PALETTE_YUV422P = 13; { YUV 4:2:2 Planar }
VIDEO_PALETTE_YUV411P = 14; { YUV 4:1:1 Planar }
VIDEO_PALETTE_YUV420P = 15; { YUV 4:2:0 Planar }
VIDEO_PALETTE_YUV410P = 16; { YUV 4:1:0 Planar }
VIDEO_PALETTE_PLANAR = 13; { start of planar entries }
VIDEO_PALETTE_COMPONENT = 7; { start of component entries }
// used in TVideo_Mbuf
VIDEO_MAX_FRAME = 32;
type
{ will be filled by issuing a VIDIOCGCAP to get some
capabilities of the device. This is the first thing
one should do after opening the device}
TVideo_Capability = record
name: array[0..31] of char;
typ: Integer;
channels: Integer;
audios: Integer;
maxwidth: Integer;
maxheight: Integer;
minwidth: Integer;
minheight: Integer;
end;
{ This is used to get or set the picture settings with
VIDIOCGPICT and VIDIOCSPICT. This is the next thing
to do after querying the capabilities.}
Tvideo_Picture = record
brightess: Word;
hue: Word;
colour: Word;
contrast: Word;
whiteness: Word;
depth: Word;
palette: Word;
end;
{ clipping regions, referenced in TVideo_Window. This is not
something one needs for everyday use. Its only defined here
for the sake of completeness because TVideo_Window mentions them.}
Pclips = ^TClips;
TClips = record
x,y: Integer;
width, height: Integer;
next: Pclips;
end;
{ This must be sent with a VIDIOCSWIN or queried with a VIDIOCGWIN
to set (or get) the video size. before starting capturing. Usually
you set it to the maximum width and height that is found in
TVideo_Capabilities}
TVideo_Window = record
x,y : DWord;
width, height: DWord;
chromakey: DWord;
flags: DWord;
clips: Pclips;
clipcount: Integer;
end;
{ this is used to ask the driver for the size of the memory
that should be mapped. Use it with VIDIOCGMBUF and then
use the returned size value to mmmap() the device and
after capturing you can use the offsets array to calculate
the pointers to individual frames in the mapped memory }
TVideo_Mbuf = record
size: Integer;
frames: Integer;
offsets: array[0..VIDEO_MAX_FRAME-1] of Integer;
end;
{ use this to request the capturing of exactloy one frame with
VIDIOCMCAPTURE. It will immedately return. You must use
VIDIOCSYNC to wait for this frame. VIDIOCMCAPTURE must be
called for each frame. Usually you do it like the following,
alternating between two frame numbers 0 and 1.
VIDIOCMCAPTURE(0)
while ... do begin
VIDIOCMCAPTURE(1) // start 1
VIDIOCSYNC(0) // wait for 0
... process the frame ...
VIDIOCMCAPTURE(0) // start 0
VIDIOCSYNC(1) // wait for 1
... process the frame ...
end;
}
TVideo_Mmap = record
frame: DWord; // Frame (0..n-1)
height: Integer;
width: Integer;
format: DWord; // should be VIDEO_PALETTE_*
end;
// the following were actually constants in the
// original headers but their values are computed
// by the C preprocessor at compile time with the
// help of a lot of nested macros, its actually
// easier to make them functions.
function VIDIOCGCAP: Cardinal;
function VIDIOCGPICT: Cardinal;
function VIDIOCSPICT: Cardinal;
function VIDIOCGWIN: Cardinal;
function VIDIOCSWIN: Cardinal;
function VIDIOCGMBUF: Cardinal;
function VIDIOCMCAPTURE: Cardinal;
function VIDIOCSYNC: Cardinal;
///////////////////////
// End of V4l Header //
///////////////////////
type
{ TSimpleV4l1Device implments something that will actually make use of
all the above. It represents a webcam and will set the Device to the
largest supported resolution and start capturing images. Its only
a quick and dirty hack that works for my QuickCam Express but it
illustrates the principle.
Usage:
Cam := TSimpleV4l1Device.Create('/dev/video0', VIDEO_PALETTE_RGB24);
Cam.Open;
while (WhatEver) do begin
Data := Cam.GetNextFrame: // waits for the next frame
// now use that pointer to copy
// the frame and process it
end;
the methods can throw exceptions when the camera is not supported
so you should enclose it all in try/except and don't forget to
use Cam.Close afterwards; }
TSimpleV4l1Device = class(TObject)
FDevice: String;
FHandle: Integer;
FPalette: Word;
FVideo_Capability: TVideo_Capability;
FVideo_Picture: Tvideo_Picture;
FVideo_Window: TVideo_Window;
FVideo_Mbuf : TVideo_Mbuf;
FVideo_Mmap : TVideo_Mmap;
FBuf: PByte;
FFrameNum: Integer;
constructor Create(ADevice: String; APalette: Word);
{Open the device and set all parameters so that capturing
can begin. This will also trigger the very first Capture call
with frame 0. the next Capture/Sync will then be 1/0, then
0/1, etc.}
procedure Open;
{free the buffers and close the device}
procedure Close;
{ tell the driver to start capturing a frame. This will
also toggle the frame number to the other of the two
frames. A subsequant Sync will then wait for the frame
that was captured before.}
procedure Capture;
{ wait for the next frame.}
procedure Sync;
{ get the pointer to the last sync'd frame }
function GetLastFrame: PByte;
{ Capture, Sync and then return the Pointer to the new
frame. This function will block until a new frame
is available. Simply calling this in a loop is all
you need to do to get streaming video from the cam. }
function GetNextFrame: PByte;
{ get the picture settings, store them in FVideo_Picture }
procedure GetPictureSettigs;
{ set the picture settings, that are in FVideo_Picture }
procedure SetPictureSettings;
{ Set the color strength}
procedure SetPictureColor(Color: Word);
{ Set the color hue}
procedure SetPictureHue(Hue: Word);
{ Set the brightness (on some cameras this is the gain)}
procedure SetPictureBrightness(Brightness: Word);
{ Set the contrast (on some cameras this is the exposure)}
procedure SetPictureContrast(Contrast: Word);
end;
{ The v4l_open/close/ioctl/etc... functions from the libv4l1.so are used
instead of their original native couterparts since they will be able to
fully emulate the old v4l1 behavior on v4l2 devices with v4l2 drivers.
This automatically adds support for all new v2 devices. Also all the
format conversion from all known v2 formats will be automatically done
transparently on the fly into either RGB24 (BGR) or YUV240p. Every
V4l2 cam will appear as a V4l1 cam capable of 24 bit uncompressed BGR}
function v4l1_open (filname: PChar; oflag: Integer): Integer;
cdecl;external 'libv4l1.so';
function v4l1_close(fd: Integer): Integer;
cdecl; external 'libv4l1.so';
function v4l1_ioctl(fd: Integer; request: Cardinal; data: Pointer): Integer;
cdecl; external 'libv4l1.so';
function v4l1_read(fd: Integer; buffer: Pointer; n: Integer): Integer;
cdecl; external 'libv4l1.so';
function v4l1_mmap(start: Pointer; length: Integer; prot: Integer;
flags: Integer; fd: Integer; offset: Int64): Pointer;
cdecl; external 'libv4l1.so';
function v4l1_munmap(start: Pointer; length: Integer): Integer;
cdecl; external 'libv4l1.so';
// the functions below are only for debugging purposes,
// to print the contents of some records to the console.
procedure DebugPrintCapabilities(cap: TVideo_Capability);
procedure DebugPrintPicture(pict: Tvideo_Picture);
procedure DebugPrintWindow(win: TVideo_Window);
implementation
uses
Classes, SysUtils, BaseUnix, kernelioctl;
{ the ioctl "constants" implemented as functions }
function VIDIOCGCAP: Cardinal;
begin
Result := _IOR(ord('v'), 1, SizeOf(TVideo_Capability));
end;
function VIDIOCGPICT: Cardinal;
begin
Result := _IOR(ord('v'), 6, SizeOf(Tvideo_Picture));
end;
function VIDIOCSPICT: Cardinal;
begin
Result := _IOW(ord('v'), 7, SizeOf(Tvideo_Picture));
end;
function VIDIOCGWIN: Cardinal;
begin
Result := _IOR(ord('v'), 9, SizeOf(TVideo_Window));
end;
function VIDIOCSWIN: Cardinal;
begin
Result := _IOW(ord('v'),10, SizeOf(TVideo_Window));
end;
function VIDIOCGMBUF: Cardinal;
begin
Result := _IOR(ord('v'), 20, SizeOf(TVideo_Mbuf));
end;
function VIDIOCMCAPTURE: Cardinal;
begin
Result := _IOW(ord('v'), 19, SizeOf(TVideo_Mmap));
end;
function VIDIOCSYNC: Cardinal;
begin
Result := _IOW(ord('v'), 18, SizeOf(Integer));
end;
{ TSimpleV4l1Device }
constructor TSimpleV4l1Device.Create(ADevice: String; APalette: Word);
begin
FDevice := ADevice;
FPalette := APalette;
end;
procedure TSimpleV4l1Device.Open;
begin
// open the device
FHandle := v4l1_open(pchar(FDevice), O_RDWR);
if FHandle = -1 then
raise Exception.Create('could not open video device ' + FDevice);
// get cpability
if v4l1_ioctl(FHandle, VIDIOCGCAP, @FVideo_Capability) < 0 then
raise Exception.Create('could not query capabilities');
DebugPrintCapabilities(FVideo_Capability);
// get picture settings (and set palette)
GetPictureSettigs;
DebugPrintPicture(FVideo_Picture);
if FVideo_Picture.palette <> FPalette then begin
writeln('trying to set desired palette');
FVideo_Picture.palette := FPalette;
try
SetPictureSettings; // will throw exception if palette is not supported.
except
writeln('could not set palette');
raise Exception.Create('could not set palette');
end;
DebugPrintPicture(FVideo_Picture);
end;
// set video window
with FVideo_Window do begin
x := 0;
y := 0;
width := FVideo_Capability.maxwidth;
height := FVideo_Capability.maxheight;
chromakey := 0;
flags := 0;
clips := Nil;
clipcount := 0;
end;
if v4l1_ioctl(FHandle, VIDIOCSWIN, @FVideo_Window) < 0 then
raise Exception.Create('could not set video window');
// get video window
if v4l1_ioctl(FHandle, VIDIOCGWIN, @FVideo_Window) < 0 then
raise Exception.Create('could not query video window');
DebugPrintWindow(FVideo_Window);
// ask the driver how much memory to mmap and do it
if v4l1_ioctl(FHandle, VIDIOCGMBUF, @FVideo_Mbuf) < 0 then
raise Exception.Create('could not query VIDIOCGMBUF');
FBuf := v4l1_mmap(nil, FVideo_Mbuf.size, PROT_READ, MAP_SHARED, FHandle, 0);
FFrameNum := 0;
Capture; // start capturing the first frame already
end;
procedure TSimpleV4l1Device.Close;
begin
v4l1_munmap(FBuf, FVideo_Mbuf.size);
v4l1_close(FHandle);
end;
procedure TSimpleV4l1Device.Capture;
begin
FVideo_Mmap.format := FVideo_Picture.palette;
FVideo_Mmap.height := FVideo_Window.height;
FVideo_Mmap.width := FVideo_Window.width;
FVideo_Mmap.frame := FFrameNum;
if v4l1_ioctl(FHandle, VIDIOCMCAPTURE, @FVideo_Mmap) < 0 then
raise Exception.Create('could not send VIDIOCMCAPTURE');
// now we switch to the other of the two frame numbers. The
// application will now call Sync on the other frame which
// has already been capturing for a bit longer.
FFrameNum := 1 - FFrameNum;
end;
procedure TSimpleV4l1Device.Sync;
begin
if v4l1_ioctl(FHandle, VIDIOCSYNC, @FFrameNum) < 0 then
raise Exception.Create('could not do VIDIOCSYNC');
end;
function TSimpleV4l1Device.GetLastFrame: PByte;
begin
Result := FBuf + FVideo_Mbuf.offsets[FFrameNum];
end;
function TSimpleV4l1Device.GetNextFrame: PByte;
begin
Capture;
Sync;
Result := GetLastFrame;
end;
procedure TSimpleV4l1Device.GetPictureSettigs;
begin
if v4l1_ioctl(FHandle, VIDIOCGPICT, @FVideo_Picture) < 0 then
raise Exception.Create('could not query picture settings');
end;
procedure TSimpleV4l1Device.SetPictureSettings;
begin
if v4l1_ioctl(FHandle, VIDIOCSPICT, @FVideo_Picture) < 0 then
raise Exception.Create('could not set picture settings');
end;
procedure TSimpleV4l1Device.SetPictureColor(Color: Word);
begin
GetPictureSettigs;
FVideo_Picture.colour := Color;
SetPictureSettings;
end;
procedure TSimpleV4l1Device.SetPictureHue(Hue: Word);
begin
GetPictureSettigs;
FVideo_Picture.hue := Hue;
SetPictureSettings;
end;
procedure TSimpleV4l1Device.SetPictureBrightness(Brightness: Word);
begin
GetPictureSettigs;
FVideo_Picture.brightess := Brightness;
SetPictureSettings;
end;
procedure TSimpleV4l1Device.SetPictureContrast(Contrast: Word);
begin
GetPictureSettigs;
FVideo_Picture.contrast := Contrast;
SetPictureSettings;
end;
procedure DebugPrintCapabilities(cap: TVideo_Capability);
var
captyp : String = '';
begin
if (cap.typ and VID_TYPE_CAPTURE) <> 0 then captyp += 'VID_TYPE_CAPTURE ';
if (cap.typ and VID_TYPE_TUNER) <> 0 then captyp += 'VID_TYPE_TUNER ';
if (cap.typ and VID_TYPE_TELETEXT) <> 0 then captyp += 'VID_TYPE_TELETEXT ';
if (cap.typ and VID_TYPE_OVERLAY) <> 0 then captyp += 'VID_TYPE_OVERLAY ';
if (cap.typ and VID_TYPE_CHROMAKEY) <> 0 then captyp += 'VID_TYPE_CHROMAKEY ';
if (cap.typ and VID_TYPE_CLIPPING) <> 0 then captyp += 'VID_TYPE_CLIPPING ';
if (cap.typ and VID_TYPE_FRAMERAM) <> 0 then captyp += 'VID_TYPE_FRAMERAM ';
if (cap.typ and VID_TYPE_SCALES) <> 0 then captyp += 'VID_TYPE_SCALES ';
if (cap.typ and VID_TYPE_MONOCHROME) <> 0 then captyp += 'VID_TYPE_MONOCHROME ';
if (cap.typ and VID_TYPE_SUBCAPTURE) <> 0 then captyp += 'VID_TYPE_SUBCAPTURE ';
if (cap.typ and VID_TYPE_MPEG_DECODER) <> 0 then captyp += 'VID_TYPE_MPEG_DECODER ';
if (cap.typ and VID_TYPE_MPEG_ENCODER) <> 0 then captyp += 'VID_TYPE_MPEG_ENCODER ';
if (cap.typ and VID_TYPE_MJPEG_DECODER) <> 0 then captyp += 'VID_TYPE_MJPEG_DECODER ';
if (cap.typ and VID_TYPE_MJPEG_ENCODER) <> 0 then captyp += 'VID_TYPE_MJPEG_ENCODER ';
writeln('video_capability');
writeln(' Name: ' + cap.name);
writeln(' typ: ' + captyp);
writeln(' channels: ' + IntToStr(cap.channels));
writeln(' audios: ' + IntToStr(cap.audios));
writeln(' maxwidth: ' + IntToStr(cap.maxwidth));
writeln(' maxheight: ' + IntToStr(cap.maxheight));
writeln(' minwidth: ' + IntToStr(cap.minwidth));
writeln(' minheight: ' + IntToStr(cap.minheight));
writeln;
end;
procedure DebugPrintPicture(pict: Tvideo_Picture);
var
palette: String;
begin
if pict.palette = VIDEO_PALETTE_GREY then palette := 'VIDEO_PALETTE_GREY';
if pict.palette = VIDEO_PALETTE_HI240 then palette := 'VIDEO_PALETTE_HI240';
if pict.palette = VIDEO_PALETTE_RGB565 then palette := 'VIDEO_PALETTE_RGB565';
if pict.palette = VIDEO_PALETTE_RGB24 then palette := 'VIDEO_PALETTE_RGB24';
if pict.palette = VIDEO_PALETTE_RGB32 then palette := 'VIDEO_PALETTE_RGB32';
if pict.palette = VIDEO_PALETTE_RGB555 then palette := 'VIDEO_PALETTE_RGB555';
if pict.palette = VIDEO_PALETTE_YUV422 then palette := 'VIDEO_PALETTE_YUV422';
if pict.palette = VIDEO_PALETTE_YUYV then palette := 'VIDEO_PALETTE_YUYV';
if pict.palette = VIDEO_PALETTE_UYVY then palette := 'VIDEO_PALETTE_UYVY';
if pict.palette = VIDEO_PALETTE_YUV420 then palette := 'VIDEO_PALETTE_YUV420';
if pict.palette = VIDEO_PALETTE_YUV411 then palette := 'VIDEO_PALETTE_YUV411';
if pict.palette = VIDEO_PALETTE_RAW then palette := 'VIDEO_PALETTE_RAW';
if pict.palette = VIDEO_PALETTE_YUV422P then palette := 'VIDEO_PALETTE_YUV422P';
if pict.palette = VIDEO_PALETTE_YUV411P then palette := 'VIDEO_PALETTE_YUV411P';
if pict.palette = VIDEO_PALETTE_YUV420P then palette := 'VIDEO_PALETTE_YUV420P';
if pict.palette = VIDEO_PALETTE_YUV410P then palette := 'VIDEO_PALETTE_YUV410P';
if pict.palette = VIDEO_PALETTE_PLANAR then palette := 'VIDEO_PALETTE_PLANAR';
if pict.palette = VIDEO_PALETTE_COMPONENT then palette := 'VIDEO_PALETTE_COMPONENT';
writeln('video_picture');
writeln(' brightness: ' + IntToStr(pict.brightess));
writeln(' hue: ' + IntToStr(pict.hue));
writeln(' colour: ' + IntToStr(pict.colour));
writeln(' contrast: ' + IntToStr(pict.contrast));
writeln(' whiteness: ' + IntToStr(pict.whiteness));
writeln(' depth: ' + IntToStr(pict.depth));
writeln(' palette: ' + palette);
writeln;
end;
procedure DebugPrintWindow(win: TVideo_Window);
begin
writeln('video_window:');
writeln(' x: ' + IntToStr(win.x));
writeln(' y: ' + IntToStr(win.y));
writeln(' width: ' + IntToStr(win.width));
writeln(' height: ' + IntToStr(win.height));
writeln(' chromakey: ' + IntToStr(win.chromakey));
writeln(' flags: ' + IntToStr(win.flags));
writeln(Format(' clips (pointer): %p', [win.clips]));
writeln(' clipcount: ' + IntToStr(win.clipcount));
writeln;
end;
end.
--
_______________________________________________
Lazarus mailing list
[email protected]
http://lists.lazarus.freepascal.org/mailman/listinfo/lazarus