/* ======================================================================== *\ ! ! * ! * This file is part of MARS, the MAGIC Analysis and Reconstruction ! * Software. It is distributed to you in the hope that it can be a useful ! * and timesaving tool in analysing Data of imaging Cerenkov telescopes. ! * It is distributed WITHOUT ANY WARRANTY. ! * ! * Permission to use, copy, modify and distribute this software and its ! * documentation for any purpose is hereby granted without fee, ! * provided that the above copyright notice appear in all copies and ! * that both that copyright notice and this permission notice appear ! * in supporting documentation. It is provided "as is" without express ! * or implied warranty. ! * ! ! ! Author(s): Thomas Bretz 1/2008 ! ! Copyright: MAGIC Software Development, 2000-2008 ! ! \* ======================================================================== */ ///////////////////////////////////////////////////////////////////////////// // // MVideo // // Interface to Video4Linux at a simple level // ///////////////////////////////////////////////////////////////////////////// #include "MVideo.h" // iostream #include // open #include #include #include #include // usleep #include // errno #include // mmap #include // ioctl #include #include #include "MLog.h" #include "MLogManip.h" #undef DEBUG using namespace std; //ClassImp(MVideo); MVideoCtrl::MVideoCtrl(const v4l2_queryctrl &ctrl) { fId = ctrl.id; fName = (const char*)ctrl.name; fMinimum = ctrl.minimum; fMaximum = ctrl.maximum; fStep = ctrl.step; fDefault = ctrl.default_value; } // ------------------------------------------------------------- // // Constructor. Specify the device (e.g. "/dev/video") to be used // MVideo::MVideo(const char *path) : fPath(path), fFileDesc(-1), fMapBuffer(0) { Reset(); fControls.SetOwner(); } // ------------------------------------------------------------- // // Internal function to reset the descriptors of the device // void MVideo::Reset() { memset(&fCaps, 0, sizeof(fCaps)); memset(&fChannel, 0, sizeof(fChannel)); memset(&fBuffer, 0, sizeof(fBuffer)); memset(&fAbil, 0, sizeof(fAbil)); fFileDesc = -1; fMapBuffer = 0; fChannel.channel = -1; fAbil.tuner = -1; fControls.Delete(); } // ------------------------------------------------------------- // // Mapper around ioctl for easier access to the device // int MVideo::Ioctl(int req, void *opt, bool allowirq, bool force) const { if (fFileDesc<0) { gLog << err << "ERROR - Ioctl: Device " << fPath << " not open." << endl; return -1; } while (1) { // FIXME: This call is a possible source for a hangup const int rc = ioctl(fFileDesc, req, opt); if (rc>=0) return rc; // errno== 4: Interrupted system call (e.g. by alarm()) // errno==16: Device or resource busy if (errno==4 || errno==16) { if (!allowirq && errno==4) return -4; gLog << err << "ERROR - MVideo::Ioctl 0x" << hex << req << ": errno=" << dec << errno << " - "; gLog << strerror(errno) << " (rc=" << rc << ")" << endl; usleep(10); continue; } if (!force) { gLog << err << "ERROR - MVideo::Ioctl 0x" << hex << req << ": errno=" << dec << errno << " - "; gLog << strerror(errno) << " (rc=" << rc << ")" << endl; } return rc; } return -1; } // ------------------------------------------------------------- // // Read the capabilities of the device // Bool_t MVideo::GetCapabilities() { return Ioctl(VIDIOCGCAP, &fCaps)!=-1; } // ------------------------------------------------------------- // // Read the properties of the device // Bool_t MVideo::GetProperties() { return Ioctl(VIDIOCGPICT, &fProp)!=-1; } // ------------------------------------------------------------- // // Read the video standard // Bool_t MVideo::GetVideoStandard() { return Ioctl(VIDIOC_G_STD, &fVideoStandard)==-1; } // ------------------------------------------------------------- // // Read the abilities of the tuner // Bool_t MVideo::GetTunerAbilities() { fAbil.tuner = 0; // FIXME? return Ioctl(VIDIOCGTUNER, &fAbil)!=-1; } // ------------------------------------------------------------- // // Enumerate (get) all controls from the device and store them // as MVideoCtrl in fControls, starting with the id given as // argument. // Bool_t MVideo::EnumerateControls(UInt_t id) { struct v4l2_queryctrl qctrl; memset(&qctrl, 0, sizeof(qctrl)); qctrl.id = id; while (1) { if (Ioctl(VIDIOC_QUERYCTRL, &qctrl, true, true)==-1) break; if (qctrl.maximum<=qctrl.minimum) continue; fControls.Add(new MVideoCtrl(qctrl)); qctrl.id++; } return kTRUE; } // ------------------------------------------------------------- // // Enumerate (get) all basic and private controls from the // device and store them as MVideoCtrl in fControls. // Bool_t MVideo::EnumerateControls() { if (!EnumerateControls(V4L2_CID_BASE)) return kFALSE; if (!EnumerateControls(V4L2_CID_PRIVATE_BASE)) return kFALSE; return kTRUE; } // ------------------------------------------------------------- // // Reset a given control to it's default value as defined // by the device. // Bool_t MVideo::ResetControl(MVideoCtrl &vctrl) const { return WriteControl(vctrl, vctrl.fDefault); } // ------------------------------------------------------------- // // Reset all enumereated device controls to their default. // The default is defined by the device iteself. // Bool_t MVideo::ResetControls() const { Bool_t rc = kTRUE; TIter Next(&fControls); MVideoCtrl *ctrl = 0; while ((ctrl=((MVideoCtrl*)Next()))) if (!ResetControl(*ctrl)) { gLog << err << "ERROR - Could not reset " << ctrl->fName << "." << endl; rc = kFALSE; } return rc; } // ------------------------------------------------------------- // // Read the value of the given control from the device // and store it back into the given MVideoCtrl. // Bool_t MVideo::ReadControl(MVideoCtrl &vctrl) const { struct v4l2_control ctrl = { vctrl.fId, 0 }; if (Ioctl(VIDIOC_G_CTRL, &ctrl)==-1) return kFALSE; vctrl.fValue = ctrl.value; return kTRUE; } // ------------------------------------------------------------- // // Write the given value into the given control of the device. // On success the value is stored in the given MVideoCtrl. // Bool_t MVideo::WriteControl(MVideoCtrl &vctrl, Int_t val) const { if (valvctrl.fMaximum) { gLog << err << "ERROR - Value of " << val << " above maximum of " << vctrl.fMaximum << " for " << vctrl.fName << endl; return kFALSE; } struct v4l2_control ctrl = { vctrl.fId, val }; if (Ioctl(VIDIOC_S_CTRL, &ctrl)==-1) return kFALSE; vctrl.fValue = val; return kTRUE; } // ------------------------------------------------------------- // // Set all controls from a TEnv. Note that all whitespaces // and colons in the control names (as defined by the name of // the MVideoCtrls stored in fControls) are replaced by // underscores. // Bool_t MVideo::SetControls(TEnv &env) const { Bool_t rc = kTRUE; TIter Next(&fControls); TObject *o = 0; while ((o=Next())) { if (!env.Defined(o->GetName())) continue; TString str = env.GetValue(o->GetName(), ""); str = str.Strip(TString::kBoth); str.ReplaceAll(" ", "_"); str.ReplaceAll(":", "_"); if (str.IsNull()) continue; MVideoCtrl &ctrl = *static_cast(o); const Int_t val = str=="default" || str=="def" ? ctrl.fDefault : env.GetValue(o->GetName(), 0); if (!WriteControl(ctrl, val)) rc = kFALSE; } return rc; } // ------------------------------------------------------------- // // Open channel ch of the device // Bool_t MVideo::Open(Int_t ch) { Bool_t rc = Init(ch); if (!rc) Close(); return rc; } // ------------------------------------------------------------- // // Open a channel of the device and retriev all necessary // informations from the driver. Initialize the shared // memory. Other access methods are not supported yet. // Bool_t MVideo::Init(Int_t channel) { if (IsOpen()) { gLog << warn << "WARNING - Device " << fPath << " already open." << endl; return kTRUE; } gLog << all << "Opening " << fPath << "... " << flush; do { fFileDesc = open(fPath, O_RDWR); usleep(1); } while (errno==19 && fFileDesc==-1); if (fFileDesc == -1) { gLog << err << "ERROR: " << strerror(errno) << endl; return kFALSE; } gLog << "done (" << fFileDesc << ")." << endl; // Close device on exit if (fcntl(fFileDesc, F_SETFD, FD_CLOEXEC)<0) { gLog << err << "ERROR - Call to fnctl (F_SETFD, FD_CLOEXEC) failed." << endl; return kFALSE; } gLog << warn << "Setting video standard to PAL-N." << endl; fVideoStandard = V4L2_STD_PAL_N; if (Ioctl(VIDIOC_S_STD, &fVideoStandard)==-1) { gLog << err << "ERROR - Could not set video standard to PAL-N." << endl; return kFALSE; } if (!EnumerateControls()) { gLog << err << "ERROR - Could not enumerate controls." << endl; return kFALSE; } if (!ResetControls()) { gLog << err << "ERROR - Could not reset controls to default." << endl; return kFALSE; } if (!GetCapabilities()) { gLog << err << "ERROR - Getting device capabilities failed." << endl; return kFALSE; } if (!SetChannel(channel)) return kFALSE; if (!GetProperties()) { gLog << err << "ERROR - Couldn't get picture properties." << endl; return kFALSE; } /* if (HasTuner()) { if (!GetTunerAbilities()) { gLog << err << "ERROR - Couldn't get tuner abilities." << endl; return kFALSE; } } */ // get mmap grab buffer info if (Ioctl(VIDIOCGMBUF, &fBuffer)==-1) { gLog << err << "ERROR - Couldn't get info about memory map buffer." << endl; return kFALSE; } // map file (device) into memory fMapBuffer = (unsigned char*)mmap(0, fBuffer.size, PROT_READ|PROT_WRITE, MAP_SHARED, fFileDesc, 0); if (fMapBuffer == (void*)-1) { gLog << err << "ERROR - Couldn't map device buffer into memory." << endl; fMapBuffer = 0; return kFALSE; } Print(); return kTRUE; } // ------------------------------------------------------------- // // Close device. Free the shared memory // Int_t MVideo::Close() { // if (!IsOpen()) // return kTRUE; Bool_t rc = kTRUE; gLog << all << "Closing " << fPath << " (" << fFileDesc << ")... " << flush; if (fFileDesc != -1) { if (close(fFileDesc)<0) { gLog << err << "ERROR!" << endl; rc = kFALSE; } fFileDesc = -1; } gLog << "done." << endl; // unmap device memory if (fMapBuffer) munmap(fMapBuffer, fBuffer.size); Reset(); return rc; } // ------------------------------------------------------------- // // Instruct hardware to start capture into framebuffer frame // Bool_t MVideo::CaptureStart(unsigned int frame) const { frame %= fBuffer.frames; struct video_mmap gb = { frame, // frame fCaps.maxheight, fCaps.maxwidth, // height, width VIDEO_PALETTE_RGB24 // palette }; #ifdef DEBUG gLog << dbg << "CapturStart(" << frame << ")" << endl; #endif // // capture frame // if (Ioctl(VIDIOCMCAPTURE, &gb) != -1) return kTRUE; // if (errno == EAGAIN) gLog << err; gLog << "ERROR - Couldn't start capturing frame " << frame << "." << endl; gLog << " Maybe your card doesn't support VIDEO_PALETTE_RGB24." << endl; return kFALSE; } // ------------------------------------------------------------- // // Wait until hardware has finished capture into framebuffer frame // Int_t MVideo::CaptureWait(unsigned int frame, unsigned char **ptr) const { frame %= fBuffer.frames; if (ptr) *ptr = NULL; const int SYNC_TIMEOUT = 1; #ifdef DEBUG gLog << dbg << "CaptureWait(" << frame << ")" << endl; #endif alarm(SYNC_TIMEOUT); const Int_t rc = Ioctl(VIDIOCSYNC, &frame, false); if (rc==-4) { //cout << "ERROR - Waiting for frame " << frame << " timed out." << endl; return kSKIP; } alarm(0); if (rc==-1) { gLog << err << "ERROR - Waiting for " << frame << " frame failed." << endl; return kFALSE; } if (ptr) *ptr = fMapBuffer+fBuffer.offsets[frame]; return kTRUE; } // ------------------------------------------------------------- // // Change the channel of a priviously opened device // Int_t MVideo::SetChannel(Int_t chan) { if (fChannel.channel==chan) return kSKIP; if (chan<0 || chan>=fCaps.channels) { gLog << err << "ERROR - Set channel " << chan << " out of range." << endl; return kFALSE; } // Switch to channel struct video_channel ch = { chan, "", 0, 0, 0, 0 }; if (Ioctl(VIDIOCSCHAN, &ch)==-1) { gLog << err << "ERROR - Couldn't switch to channel " << chan << "." << endl; gLog << " You might need a bttv version > 0.5.13" << endl; return kFALSE; } // Get information about channel if (Ioctl(VIDIOCGCHAN, &ch)==-1) { gLog << err << "ERROR - Getting information for channel " << chan << " failed." << endl; return kFALSE; } memcpy(&fChannel, &ch, sizeof(fChannel)); gLog << all << "Switched to channel " << chan << endl; return kTRUE; } // ------------------------------------------------------------- // // Has the device capture capabilities? // Bool_t MVideo::CanCapture() const { return fCaps.type&VID_TYPE_CAPTURE; } // ------------------------------------------------------------- // // Has a tuner // Bool_t MVideo::HasTuner() const { return fCaps.type&VID_TYPE_TUNER; } // ------------------------------------------------------------- // // Returns the number of frame buffers which can be used // Int_t MVideo::GetNumBuffers() const { return fBuffer.frames; } // ------------------------------------------------------------- // // Maximum width of the frame which can be captured // Int_t MVideo::GetWidth() const { return fCaps.maxwidth; } // ------------------------------------------------------------- // // Maximum height of the frame which can be captured // Int_t MVideo::GetHeight() const { return fCaps.maxheight; } // ------------------------------------------------------------- // // Return the device type as string // TString MVideo::GetDevType(int type) const { TString rc; if (CanCapture()) rc += " capture"; if (HasTuner()) rc += " tuner"; if (type&VID_TYPE_TELETEXT) rc += " teletext"; if (type&VID_TYPE_OVERLAY) rc += " overlay"; if (type&VID_TYPE_CHROMAKEY) rc += " chromakey"; if (type&VID_TYPE_CLIPPING) rc += " clipping"; if (type&VID_TYPE_FRAMERAM) rc += " frameram"; if (type&VID_TYPE_SCALES) rc += " scales"; if (type&VID_TYPE_MONOCHROME) rc += " monochrom"; if (type&VID_TYPE_SUBCAPTURE) rc += " subcapature"; return rc; } TString MVideo::GetTunerFlags(Int_t flags) const { TString rc; if (flags&VIDEO_TUNER_PAL) rc += " PAL"; if (flags&VIDEO_TUNER_NTSC) rc += " NTSC"; if (flags&VIDEO_TUNER_SECAM) rc += " SECAM"; if (flags&VIDEO_TUNER_LOW) rc += " kHz"; if (flags&VIDEO_TUNER_NORM) rc += " CanSetNorm"; if (flags&VIDEO_TUNER_STEREO_ON) rc += " StereoOn"; return rc; } TString MVideo::GetTunerMode(Int_t mode) const { switch (mode) { case VIDEO_MODE_PAL: return "PAL"; case VIDEO_MODE_NTSC: return "NTSC"; case VIDEO_MODE_SECAM: return "SECAM"; case VIDEO_MODE_AUTO: return "AUTO"; } return "undefined"; } // ------------------------------------------------------------- // // Return the channel flags as string // TString MVideo::GetChannelFlags(Int_t flags) const { TString rc = "video"; if (flags&VIDEO_VC_TUNER) rc += " tuner"; if (flags&VIDEO_VC_AUDIO) rc += " audio"; // if (flags&VIDEO_VC_NORM) // rc += " normsetting"; return rc; } // ------------------------------------------------------------- // // Return the channel type as string // TString MVideo::GetChannelType(Int_t type) const { if (type&VIDEO_TYPE_TV) return "TV"; if (type&VIDEO_TYPE_CAMERA) return "Camera"; return "unknown"; } // ------------------------------------------------------------- // // Return the palette pal as string // TString MVideo::GetPalette(Int_t pal) const { switch (pal) { case VIDEO_PALETTE_GREY: return "VIDEO_PALETTE_GREY: Linear intensity grey scale"; case VIDEO_PALETTE_HI240: return "VIDEO_PALETTE_HI240: BT848 8-bit color cube"; case VIDEO_PALETTE_RGB565: return "VIDEO_PALETTE_RGB565: RGB565 packed into 16-bit words"; case VIDEO_PALETTE_RGB555: return "VIDEO_PALETTE_RGB555: RGB555 packed into 16-bit words, top bit undefined"; case VIDEO_PALETTE_RGB24: return "VIDEO_PALETTE_RGB24: RGB888 packed into 24-bit words"; case VIDEO_PALETTE_RGB32: return "VIDEO_PALETTE_RGB32: RGB888 packed into the low three bytes of 32-bit words. Top bits undefined."; case VIDEO_PALETTE_YUV422: return "VIDEO_PALETTE_YUV422: Video style YUV422 - 8-bit packed, 4-bit Y, 2-bits U, 2-bits V"; case VIDEO_PALETTE_YUYV: return "VIDEO_PALETTE_YUYV: YUYV"; case VIDEO_PALETTE_UYVY: return "VIDEO_PALETTE_UYVY: UYVY"; case VIDEO_PALETTE_YUV420: return "VIDEO_PALETTE_YUV420: YUV420"; case VIDEO_PALETTE_YUV411: return "VIDEO_PALETTE_YUV411: YUV411"; case VIDEO_PALETTE_RAW: return "VIDEO_PALETTE_RAW: Raw capture (Bt848)"; case VIDEO_PALETTE_YUV422P: return "VIDEO_PALETTE_YUV422P: YUV 4:2:2 planar"; case VIDEO_PALETTE_YUV411P: return "VIDEO_PALETTE_YUV411P: YUV 4:1:1 planar"; } return "unknown"; } // ------------------------------------------------------------- // // Print informations about the device, the capabilities, the // channel and all available information // void MVideo::Print() const { gLog << all << dec; gLog << "Device " << fPath << " " << (IsOpen()?"open":"closed") << "." << endl; if (!IsOpen()) return; gLog << " - Name: " << fCaps.name << endl; gLog << " - DevType: " << GetDevType(fCaps.type) << endl; gLog << " - Channels: " << fCaps.channels << endl; gLog << " - Audios: " << fCaps.audios << endl; gLog << " - Size: "; gLog << fCaps.minwidth << "x" << fCaps.minheight << " to "; gLog << fCaps.maxwidth << "x" << fCaps.maxheight << endl; gLog << endl; if (fChannel.channel>=0) { gLog << " - Channel: " << fChannel.channel << " (" << fChannel.name << ")" << endl; gLog << " - IsA: " << GetChannelType(fChannel.type) << " with " << GetChannelFlags(fChannel.flags) << " (" << fChannel.flags << ")" << endl; //if (fChannel.flags&VIDEO_VC_NORM) gLog << " - Norm: " << fChannel.norm << endl; gLog << endl; } if (fAbil.tuner>=0) { gLog << " - Tuner: " << fAbil.tuner << endl; gLog << " - Name: " << fAbil.name << endl; gLog << " - Tuner Range: " << fAbil.rangelow << " - " << fAbil.rangehigh << endl; gLog << " - Tuner flags: " << GetTunerFlags(fAbil.flags) << " (" << fAbil.flags << ")" << endl; gLog << " - Tuner mode: " << GetTunerMode(fAbil.mode) << " (" << fAbil.mode << ")" <