V4L2 camera support, raw pixel video support, added MistProcAV, improved MistProcFFMPEG
Co-authored-by: Thulinma <jaron@vietors.com> Co-authored-by: Balder <balder.vietor@ddvtech.com>
This commit is contained in:
		
							parent
							
								
									c990f49b0e
								
							
						
					
					
						commit
						f009856b64
					
				
					 35 changed files with 3934 additions and 633 deletions
				
			
		| 
						 | 
				
			
			@ -391,6 +391,16 @@ namespace Mist{
 | 
			
		|||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config->hasOption("enumerate") && config->getString("enumerate").size()){
 | 
			
		||||
      std::cout << enumerateSources(config->getString("enumerate")).toString() << std::endl;
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config->hasOption("getcapa") && config->getString("getcapa").size()){
 | 
			
		||||
      std::cout << getSourceCapa(config->getString("getcapa")).toString() << std::endl;
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    INFO_MSG("Input booting");
 | 
			
		||||
 | 
			
		||||
    //Check if the input uses the name-based-override, and strip it
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,6 +98,8 @@ namespace Mist{
 | 
			
		|||
    virtual void userLeadOut();
 | 
			
		||||
    virtual void connStats(Comms::Connections & statComm);
 | 
			
		||||
    virtual void parseHeader();
 | 
			
		||||
    virtual JSON::Value enumerateSources(const std::string &){ return JSON::Value(); };
 | 
			
		||||
    virtual JSON::Value getSourceCapa(const std::string &){ return JSON::Value(); };
 | 
			
		||||
    bool bufferFrame(size_t track, uint32_t keyNum);
 | 
			
		||||
    void doInputAbortTrigger(pid_t pid, char *mRExitReason, char *exitReason);
 | 
			
		||||
    bool exitAndLogReason();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -143,7 +143,11 @@ namespace Mist{
 | 
			
		|||
        meta.setType(idx, "audio");
 | 
			
		||||
        meta.setRate(idx, strm->codecpar->sample_rate);
 | 
			
		||||
        meta.setSize(idx, strm->codecpar->frame_size);
 | 
			
		||||
#if (LIBAVUTIL_VERSION_MAJOR < 57 || (LIBAVUTIL_VERSION_MAJOR == 57 && LIBAVUTIL_VERSION_MINOR < 24))
 | 
			
		||||
        meta.setChannels(idx, strm->codecpar->channels);
 | 
			
		||||
#else
 | 
			
		||||
        meta.setChannels(idx, strm->codecpar->ch_layout.nb_channels);
 | 
			
		||||
#endif
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -209,8 +209,12 @@ namespace Mist{
 | 
			
		|||
      }else{
 | 
			
		||||
        if (initData.count(i)){meta.setInit(i, initData[i]);}
 | 
			
		||||
      }
 | 
			
		||||
      DTSC::Fragments fragments(M.fragments(i));
 | 
			
		||||
      if (fragments.getEndValid() < fragCount){fragCount = fragments.getEndValid();}
 | 
			
		||||
      if (M.hasEmbeddedFrames(i)){
 | 
			
		||||
        fragCount = FRAG_BOOT;
 | 
			
		||||
      }else{
 | 
			
		||||
        DTSC::Fragments fragments(M.fragments(i));
 | 
			
		||||
        if (fragments.getEndValid() < fragCount){fragCount = fragments.getEndValid();}
 | 
			
		||||
      }
 | 
			
		||||
      if (M.getFirstms(i) < firstms){firstms = M.getFirstms(i);}
 | 
			
		||||
      if (M.getLastms(i) > lastms){lastms = M.getLastms(i);}
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -404,6 +408,7 @@ namespace Mist{
 | 
			
		|||
    }
 | 
			
		||||
    for (std::set<size_t>::iterator idx = tracks.begin(); idx != tracks.end(); idx++){
 | 
			
		||||
      size_t i = *idx;
 | 
			
		||||
      if (M.hasEmbeddedFrames(i)){continue;}
 | 
			
		||||
      std::string type = M.getType(i);
 | 
			
		||||
      DTSC::Keys keys(M.keys(i));
 | 
			
		||||
      // non-video tracks need to have a second keyframe that is <= firstVideo
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,6 +46,7 @@ namespace Mist{
 | 
			
		|||
    capa["codecs"]["video"].append("AV1");
 | 
			
		||||
    capa["codecs"]["video"].append("theora");
 | 
			
		||||
    capa["codecs"]["video"].append("MPEG2");
 | 
			
		||||
    capa["codecs"]["video"].append("JPEG");
 | 
			
		||||
    capa["codecs"]["audio"].append("opus");
 | 
			
		||||
    capa["codecs"]["audio"].append("vorbis");
 | 
			
		||||
    capa["codecs"]["audio"].append("AAC");
 | 
			
		||||
| 
						 | 
				
			
			@ -376,6 +377,10 @@ namespace Mist{
 | 
			
		|||
          trueCodec = "MPEG2";
 | 
			
		||||
          trueType = "video";
 | 
			
		||||
        }
 | 
			
		||||
        if (codec == "V_MJPEG"){
 | 
			
		||||
          trueCodec = "JPEG";
 | 
			
		||||
          trueType = "video";
 | 
			
		||||
        }
 | 
			
		||||
        if (codec == "A_PCM/FLOAT/IEEE"){
 | 
			
		||||
          trueCodec = "FLOAT";
 | 
			
		||||
          trueType = "audio";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										531
									
								
								src/input/input_v4l2.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										531
									
								
								src/input/input_v4l2.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,531 @@
 | 
			
		|||
#include <mist/defines.h>
 | 
			
		||||
#include <mist/stream.h>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <dirent.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <sys/ioctl.h>
 | 
			
		||||
#include <sys/mman.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include "input_v4l2.h"
 | 
			
		||||
 | 
			
		||||
namespace Mist{
 | 
			
		||||
  inputVideo4Linux::inputVideo4Linux(Util::Config *cfg) : Input(cfg){
 | 
			
		||||
    capa["name"] = "V4L2";
 | 
			
		||||
    capa["desc"] = "";
 | 
			
		||||
    capa["source_match"] = "v4l2:*";
 | 
			
		||||
    capa["always_match"] = capa["source_match"];
 | 
			
		||||
    capa["priority"] = 10;
 | 
			
		||||
    width = 0;
 | 
			
		||||
    height = 0;
 | 
			
		||||
    fpsDenominator = 0;
 | 
			
		||||
    fpsNumerator = 0;
 | 
			
		||||
    pixelFmt = 0;
 | 
			
		||||
 | 
			
		||||
    JSON::Value option;
 | 
			
		||||
    option["arg"] = "string";
 | 
			
		||||
    option["long"] = "format";
 | 
			
		||||
    option["short"] = "F";
 | 
			
		||||
    option["help"] = "Requested resolution, framerate and pixel format, like 'MJPG-1920x1080@90.00'. FPS is optional. Defaults to using the highest surface area and FPS if not given";
 | 
			
		||||
    option["value"].append("");
 | 
			
		||||
    config->addOption("format", option);
 | 
			
		||||
 | 
			
		||||
    capa["optional"]["format"]["name"] = "Device resolution, framerate and pixel format";
 | 
			
		||||
    capa["optional"]["format"]["help"] = "Requested format, like 'MJPG-1920x1080@90.00'. FPS is optional. Defaults to using the highest surface area and FPS if not given";
 | 
			
		||||
    capa["optional"]["format"]["option"] = "--format";
 | 
			
		||||
    capa["optional"]["format"]["short"] = "F";
 | 
			
		||||
    capa["optional"]["format"]["default"] = "";
 | 
			
		||||
    capa["optional"]["format"]["type"] = "string";
 | 
			
		||||
 | 
			
		||||
    capa["enum_static_prefix"] = "v4l2:";
 | 
			
		||||
    option.null();
 | 
			
		||||
    option["long"] = "enumerate";
 | 
			
		||||
    option["short"] = "e";
 | 
			
		||||
    option["help"] = "Output MistIn supported devices in JSON format, then exit";
 | 
			
		||||
    option["value"].append("");
 | 
			
		||||
    config->addOption("enumerate", option);
 | 
			
		||||
 | 
			
		||||
    capa["dynamic_capa"] = true;
 | 
			
		||||
    option.null();
 | 
			
		||||
    option["long"] = "getcapa";
 | 
			
		||||
    option["arg"] = "string";
 | 
			
		||||
    option["short"] = "q";
 | 
			
		||||
    option["help"] = "(string) Output device capabilities for given device in JSON format, then exit";
 | 
			
		||||
    option["value"].append("");
 | 
			
		||||
    config->addOption("getcapa", option);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// @brief Writes a JSON list of connected video inputs to stdout
 | 
			
		||||
  JSON::Value inputVideo4Linux::enumerateSources(const std::string & device){
 | 
			
		||||
    JSON::Value output;
 | 
			
		||||
    DIR *d = opendir("/sys/class/video4linux");
 | 
			
		||||
    if (!d){
 | 
			
		||||
      FAIL_MSG("Unable to enumerate video devices. Is v4l2 available on the system?");
 | 
			
		||||
      return output;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Cycle through all devices
 | 
			
		||||
    struct dirent *dp;
 | 
			
		||||
    do{
 | 
			
		||||
      errno = 0;
 | 
			
		||||
      if ((dp = readdir(d))){
 | 
			
		||||
        // Only consider devices starting with video
 | 
			
		||||
        if (dp->d_type != DT_LNK || strncmp(dp->d_name, "video", 5) != 0){continue;}
 | 
			
		||||
 | 
			
		||||
        // Open FD to the corresponding /dev/videoN device
 | 
			
		||||
        std::string path = "/dev/" + std::string(dp->d_name);
 | 
			
		||||
        fd = open(path.c_str() ,O_RDWR);
 | 
			
		||||
        if(fd < 0){
 | 
			
		||||
          FAIL_MSG("Failed to check device %s, continuing", dp->d_name);
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Query the device for any video input capabilities
 | 
			
		||||
        struct v4l2_fmtdesc fmt;
 | 
			
		||||
        fmt.index = 0;
 | 
			
		||||
        fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 | 
			
		||||
        if (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) >= 0) {
 | 
			
		||||
          output.append("v4l2:"+path);
 | 
			
		||||
        }
 | 
			
		||||
        close(fd);
 | 
			
		||||
      }
 | 
			
		||||
    }while (dp != NULL);
 | 
			
		||||
 | 
			
		||||
    closedir(d);
 | 
			
		||||
    return output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// @brief Writes a JSON list compatible pixel formats, resolution and FPS for a video input to stdout
 | 
			
		||||
  /// \param device: path to the device to query
 | 
			
		||||
  JSON::Value inputVideo4Linux::getSourceCapa(const std::string & device){
 | 
			
		||||
    JSON::Value output = capa;
 | 
			
		||||
    std::string input = getInput(device);
 | 
			
		||||
 | 
			
		||||
    // Open FD to the corresponding device
 | 
			
		||||
    fd = open(input.c_str(), O_RDWR);
 | 
			
		||||
    if(fd < 0){
 | 
			
		||||
      FAIL_MSG("Failed to open device, aborting");
 | 
			
		||||
      return output;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    output["optional"]["format"]["short"] = "F";
 | 
			
		||||
    output["optional"]["format"]["type"] = "string";
 | 
			
		||||
    JSON::Value & opts = output["optional"]["format"]["datalist"];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Query the device for pixel formats
 | 
			
		||||
    struct v4l2_fmtdesc fmt;
 | 
			
		||||
    fmt.index = 0;
 | 
			
		||||
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 | 
			
		||||
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) >= 0) {
 | 
			
		||||
      // For each pixel format, query supported resolutions
 | 
			
		||||
      struct v4l2_frmsizeenum frmSizes;
 | 
			
		||||
      frmSizes.pixel_format = fmt.pixelformat;
 | 
			
		||||
      frmSizes.index = 0;
 | 
			
		||||
      while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmSizes) >= 0) {
 | 
			
		||||
        // Only support discrete frame size types for now
 | 
			
		||||
        if (frmSizes.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
 | 
			
		||||
          // For each frame size, query supported FPS values
 | 
			
		||||
          struct v4l2_frmivalenum frmIntervals;
 | 
			
		||||
          memset(&frmIntervals, 0, sizeof(frmIntervals));
 | 
			
		||||
          frmIntervals.pixel_format = fmt.pixelformat;
 | 
			
		||||
          frmIntervals.width = frmSizes.discrete.width;
 | 
			
		||||
          frmIntervals.height = frmSizes.discrete.height;
 | 
			
		||||
          bool setHighestFPS = false;
 | 
			
		||||
          if (frmSizes.discrete.width * frmSizes.discrete.height > width * height){
 | 
			
		||||
            width = frmSizes.discrete.width;
 | 
			
		||||
            height = frmSizes.discrete.height;
 | 
			
		||||
            setHighestFPS = true;
 | 
			
		||||
          }
 | 
			
		||||
          ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmIntervals);
 | 
			
		||||
          double maxFPS = 0;
 | 
			
		||||
          while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmIntervals) != -1) {
 | 
			
		||||
            if (frmIntervals.type == V4L2_FRMIVAL_TYPE_DISCRETE){
 | 
			
		||||
              double fps = (double)frmIntervals.discrete.denominator / (double)frmIntervals.discrete.numerator;
 | 
			
		||||
              std::stringstream ss;
 | 
			
		||||
              ss << intToString(fmt.pixelformat) << "-" << frmSizes.discrete.width << "x" << frmSizes.discrete.height << "@";
 | 
			
		||||
              ss.setf(std::ios::fixed);
 | 
			
		||||
              ss.precision(2);
 | 
			
		||||
              // Use a human readable format for FPS
 | 
			
		||||
              ss << fps;
 | 
			
		||||
              opts.append(ss.str());
 | 
			
		||||
              if (setHighestFPS && fps >= maxFPS){
 | 
			
		||||
                maxFPS = fps;
 | 
			
		||||
                output["optional"]["format"]["default"] = ss.str();
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            frmIntervals.index += 1;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        frmSizes.index++;
 | 
			
		||||
      }
 | 
			
		||||
      fmt.index++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    close(fd);
 | 
			
		||||
    return output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// \brief Checks whether the device supports the given config and sets defaults for any missing properties
 | 
			
		||||
  bool inputVideo4Linux::checkArguments(){
 | 
			
		||||
    std::string input = getInput(config->getString("input"));
 | 
			
		||||
 | 
			
		||||
    // Open file descriptor to the requested device
 | 
			
		||||
    INFO_MSG("Opening video device %s", input.c_str());
 | 
			
		||||
    fd = open(input.c_str() ,O_RDWR);
 | 
			
		||||
    if(fd < 0){
 | 
			
		||||
      FAIL_MSG("Failed to open device %s, aborting", config->getString("input").c_str());
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Init params to requested format if it was given
 | 
			
		||||
    // If not set, we will default to the highest surface area and pick
 | 
			
		||||
    //  the highest FPS the camera supports for that resolution
 | 
			
		||||
    std::string format = "";
 | 
			
		||||
    if (config->hasOption("format") && config->getString("format").size()){
 | 
			
		||||
      format = config->getString("format");
 | 
			
		||||
 | 
			
		||||
      // Anything before a - is the requested pixel format
 | 
			
		||||
      size_t fmtDelPos = format.find('-');
 | 
			
		||||
      if (fmtDelPos != std::string::npos){
 | 
			
		||||
        pixelFmt = strToInt(format.substr(0, fmtDelPos));
 | 
			
		||||
        format = format.substr(fmtDelPos + 1);
 | 
			
		||||
      }else{
 | 
			
		||||
        FAIL_MSG("Unable to find pixel format in requested format %s", config->getString("format").c_str());
 | 
			
		||||
        close(fd);
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Anything before the @ sign is the resolution
 | 
			
		||||
      size_t resolutionDelPos = format.find('@');
 | 
			
		||||
      size_t widthDelPos = format.find('x');
 | 
			
		||||
      if (resolutionDelPos != std::string::npos && widthDelPos != std::string::npos){
 | 
			
		||||
        width = atoi(format.substr(0, widthDelPos).c_str());
 | 
			
		||||
        format = format.substr(widthDelPos + 1);
 | 
			
		||||
        height = atoi(format.substr(0, resolutionDelPos - widthDelPos - 1).c_str());
 | 
			
		||||
        format = format.substr(resolutionDelPos - widthDelPos);
 | 
			
		||||
      }else{
 | 
			
		||||
        FAIL_MSG("Unable to find resolution in requested format %s", config->getString("format").c_str());
 | 
			
		||||
        close(fd);
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      // Remaining string is the target FPS, which we will match to a fraction in the following loop
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set defaults for unset parameters, set FPS and sanity checks
 | 
			
		||||
    struct v4l2_fmtdesc fmt;
 | 
			
		||||
    fmt.index = 0;
 | 
			
		||||
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 | 
			
		||||
    bool hasFPS = format.size(); //< Automatically adjust FPS is none was set
 | 
			
		||||
    bool hasResolution = width && height; //< Automatically adjust resolution is none was set
 | 
			
		||||
    bool hasPixFmt = pixelFmt; //< Automatically adjust pixel format is none was set
 | 
			
		||||
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) >= 0) {
 | 
			
		||||
      // If we have a requested pixelFmt, skip any non-matching formats
 | 
			
		||||
      if (hasPixFmt && fmt.pixelformat != pixelFmt){
 | 
			
		||||
        fmt.index++;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Else go through supported resolution and FPS combos
 | 
			
		||||
      struct v4l2_frmsizeenum frmSizes;
 | 
			
		||||
      frmSizes.pixel_format = fmt.pixelformat;
 | 
			
		||||
      frmSizes.index = 0;
 | 
			
		||||
      while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmSizes) >= 0) {
 | 
			
		||||
        if (frmSizes.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
 | 
			
		||||
          if (!hasResolution){
 | 
			
		||||
            // If we have no resolution set, select the largest supported surface area
 | 
			
		||||
            if (frmSizes.discrete.width * frmSizes.discrete.height > width * height){
 | 
			
		||||
              width = frmSizes.discrete.width;
 | 
			
		||||
              height = frmSizes.discrete.height;
 | 
			
		||||
              pixelFmt = fmt.pixelformat;
 | 
			
		||||
            }else{
 | 
			
		||||
              // Current surface area is lower, so skip it
 | 
			
		||||
              frmSizes.index++;
 | 
			
		||||
              continue;
 | 
			
		||||
            }
 | 
			
		||||
          }else if (frmSizes.discrete.width != width || frmSizes.discrete.height != height){
 | 
			
		||||
            // Current resolution does not match requested resolution, so skip it
 | 
			
		||||
            frmSizes.index++;
 | 
			
		||||
            continue;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // At this point we found the requested resolution or adjusted it upwards, so check supported FPS values
 | 
			
		||||
          struct v4l2_frmivalenum frmIntervals;
 | 
			
		||||
          memset(&frmIntervals, 0, sizeof(frmIntervals));
 | 
			
		||||
          frmIntervals.pixel_format = pixelFmt;
 | 
			
		||||
          frmIntervals.width = width;
 | 
			
		||||
          frmIntervals.height = height;
 | 
			
		||||
          ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmIntervals);
 | 
			
		||||
          while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmIntervals) != -1) {
 | 
			
		||||
            if (frmIntervals.type == V4L2_FRMIVAL_TYPE_DISCRETE){
 | 
			
		||||
              if (!hasFPS){
 | 
			
		||||
                // If we have no FPS set, select the largest FPS we can get for the current resolution
 | 
			
		||||
                if (fpsNumerator && (float)frmIntervals.discrete.denominator / (float)frmIntervals.discrete.numerator
 | 
			
		||||
                  <= (float)fpsDenominator / (float)fpsNumerator){
 | 
			
		||||
                  // Current FPS is lower, so skip it
 | 
			
		||||
                  frmIntervals.index++;
 | 
			
		||||
                  continue;
 | 
			
		||||
                }
 | 
			
		||||
              }else if (int(frmIntervals.discrete.denominator / frmIntervals.discrete.numerator) != atoi(format.c_str())){
 | 
			
		||||
                // Current FPS does not match requested FPS, so skip it
 | 
			
		||||
                frmIntervals.index++;
 | 
			
		||||
                continue;
 | 
			
		||||
              }
 | 
			
		||||
              // Store the denominator and numerator for the requested FPS
 | 
			
		||||
              fpsDenominator = frmIntervals.discrete.denominator;
 | 
			
		||||
              fpsNumerator = frmIntervals.discrete.numerator;
 | 
			
		||||
            }
 | 
			
		||||
            frmIntervals.index++;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        frmSizes.index++;
 | 
			
		||||
      }
 | 
			
		||||
      fmt.index++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Abort if this input does not support the requested pixel format
 | 
			
		||||
    std::string pixFmtStr = intToString(pixelFmt);
 | 
			
		||||
    if (pixFmtStr != "MJPG" && pixFmtStr != "YUYV" && pixFmtStr != "UYVY") {
 | 
			
		||||
      FAIL_MSG("Unsupported pixel format %s, aborting", pixFmtStr.c_str());
 | 
			
		||||
      close(fd);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Abort if we have no resolution
 | 
			
		||||
    if (!width || !height) {
 | 
			
		||||
      FAIL_MSG("Unable to determine resolution, aborting");
 | 
			
		||||
      close(fd);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Abort if we have no FPS
 | 
			
		||||
    if (!fpsDenominator || !fpsNumerator) {
 | 
			
		||||
      FAIL_MSG("Unable to determine FPS, aborting");
 | 
			
		||||
      close(fd);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// \brief Applies config to the video device and maps its buffer to a local pointer
 | 
			
		||||
  bool inputVideo4Linux::openStreamSource(){
 | 
			
		||||
    if(fd < 0){
 | 
			
		||||
      FAIL_MSG("Lost connection to the device, aborting");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    std::string pixFmtStr = intToString(pixelFmt);
 | 
			
		||||
    INFO_MSG("Opening video device with pixel format %s, resolution %lux%lu @ %.1f fps", pixFmtStr.c_str(), width, height, (float)fpsDenominator / (float)fpsNumerator);
 | 
			
		||||
 | 
			
		||||
    // Set requested pixel format and resolution
 | 
			
		||||
    struct v4l2_format imageFormat;
 | 
			
		||||
    imageFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 | 
			
		||||
    imageFormat.fmt.pix.width = width;
 | 
			
		||||
    imageFormat.fmt.pix.height = height;
 | 
			
		||||
    imageFormat.fmt.pix.pixelformat = pixelFmt;
 | 
			
		||||
    imageFormat.fmt.pix.field = V4L2_FIELD_NONE;
 | 
			
		||||
    if(ioctl(fd, VIDIOC_S_FMT, &imageFormat) < 0){
 | 
			
		||||
      FAIL_MSG("Could not apply image parameters, aborting");
 | 
			
		||||
      close(fd);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set requested framerate
 | 
			
		||||
    struct v4l2_streamparm streamParam;
 | 
			
		||||
    streamParam.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 | 
			
		||||
    if (ioctl(fd, VIDIOC_G_PARM, &streamParam) != 0){
 | 
			
		||||
      FAIL_MSG("Could not apply stream parameters, aborting");
 | 
			
		||||
      close(fd);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    streamParam.parm.capture.capturemode |= V4L2_CAP_TIMEPERFRAME;
 | 
			
		||||
    streamParam.parm.capture.timeperframe.denominator = fpsDenominator;
 | 
			
		||||
    streamParam.parm.capture.timeperframe.numerator = fpsNumerator;
 | 
			
		||||
    if(ioctl(fd, VIDIOC_S_PARM, &streamParam) != 0){
 | 
			
		||||
      FAIL_MSG("Could not apply stream parameters, aborting");
 | 
			
		||||
      close(fd);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initiate memory mapping
 | 
			
		||||
    v4l2_requestbuffers requestBuffer = {0};
 | 
			
		||||
    requestBuffer.count = 1;
 | 
			
		||||
    requestBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 | 
			
		||||
    requestBuffer.memory = V4L2_MEMORY_MMAP;
 | 
			
		||||
    if(ioctl(fd, VIDIOC_REQBUFS, &requestBuffer) < 0){
 | 
			
		||||
      FAIL_MSG("Could not initiate memory mapping, aborting");
 | 
			
		||||
      close(fd);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Query location of the buffers in device memory
 | 
			
		||||
    v4l2_buffer queryBuffer = {0};
 | 
			
		||||
    queryBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 | 
			
		||||
    queryBuffer.memory = V4L2_MEMORY_MMAP;
 | 
			
		||||
    queryBuffer.index = 0;
 | 
			
		||||
    if(ioctl(fd, VIDIOC_QUERYBUF, &queryBuffer) < 0){
 | 
			
		||||
      FAIL_MSG("Unable to query buffer information, aborting");
 | 
			
		||||
      close(fd);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Map buffer to local address space
 | 
			
		||||
    buffer = (char*)mmap(NULL, queryBuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, queryBuffer.m.offset);
 | 
			
		||||
    memset(buffer, 0, queryBuffer.length);
 | 
			
		||||
 | 
			
		||||
    // Init buffer info struct, which is going to contain pointers to buffers and meta information
 | 
			
		||||
    memset(&bufferinfo, 0, sizeof(bufferinfo));
 | 
			
		||||
    bufferinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 | 
			
		||||
    bufferinfo.memory = V4L2_MEMORY_MMAP;
 | 
			
		||||
    bufferinfo.index = 0;
 | 
			
		||||
 | 
			
		||||
    // Activate streaming I/O
 | 
			
		||||
    int type = bufferinfo.type;
 | 
			
		||||
    if(ioctl(fd, VIDIOC_STREAMON, &type) < 0){
 | 
			
		||||
      FAIL_MSG("Unable to start streaming I/O, aborting");
 | 
			
		||||
      close(fd);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create video track
 | 
			
		||||
    // Note: pixFmtStr is not always identical to "codec", but it is for all currently-supported raw formats at least
 | 
			
		||||
    size_t staticSize = Util::pixfmtToSize(pixFmtStr, imageFormat.fmt.pix.width, imageFormat.fmt.pix.height);
 | 
			
		||||
    if (staticSize){
 | 
			
		||||
      //Known static frame sizes: raw track mode
 | 
			
		||||
      tNumber = meta.addTrack(0, 0, 0, 0, true, staticSize);
 | 
			
		||||
    }else{
 | 
			
		||||
      // Other cases: standard track mode
 | 
			
		||||
      tNumber = meta.addTrack();
 | 
			
		||||
    }
 | 
			
		||||
    meta.setLive(true);
 | 
			
		||||
    meta.setVod(false);
 | 
			
		||||
    meta.setID(tNumber, tNumber);
 | 
			
		||||
    meta.setType(tNumber, "video");
 | 
			
		||||
    meta.setWidth(tNumber, imageFormat.fmt.pix.width);
 | 
			
		||||
    meta.setHeight(tNumber, imageFormat.fmt.pix.height);
 | 
			
		||||
    meta.setFpks(tNumber, 1000 * fpsDenominator / fpsNumerator);
 | 
			
		||||
    if (pixFmtStr == "MJPG"){
 | 
			
		||||
      meta.setCodec(tNumber, "JPEG");
 | 
			
		||||
    }else if (pixFmtStr == "YUYV"){
 | 
			
		||||
      meta.setCodec(tNumber, "YUYV");
 | 
			
		||||
    }else if (pixFmtStr == "UYVY"){
 | 
			
		||||
      meta.setCodec(tNumber, "UYVY");
 | 
			
		||||
    }else{
 | 
			
		||||
      FAIL_MSG("Unsupported pixel format %s, aborting", pixFmtStr.c_str());
 | 
			
		||||
      closeStreamSource();
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void inputVideo4Linux::streamMainLoop(){
 | 
			
		||||
    uint64_t statTimer = 0;
 | 
			
		||||
    uint64_t startTime = Util::bootSecs();
 | 
			
		||||
    uint64_t timeOffset = 0;
 | 
			
		||||
    if (tNumber){
 | 
			
		||||
      timeOffset = meta.getBootMsOffset();
 | 
			
		||||
    }else{
 | 
			
		||||
      timeOffset = Util::bootMS();
 | 
			
		||||
      meta.setBootMsOffset(timeOffset);
 | 
			
		||||
    }
 | 
			
		||||
    Comms::Connections statComm;
 | 
			
		||||
    thisIdx = tNumber;
 | 
			
		||||
    if (!userSelect.count(thisIdx)){
 | 
			
		||||
      userSelect[thisIdx].reload(streamName, thisIdx, COMM_STATUS_ACTIVE | COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
 | 
			
		||||
    }
 | 
			
		||||
    while (config->is_active && userSelect[thisIdx]){
 | 
			
		||||
      if (userSelect[thisIdx].getStatus() & COMM_STATUS_REQDISCONNECT){
 | 
			
		||||
        Util::logExitReason(ER_CLEAN_LIVE_BUFFER_REQ, "buffer requested shutdown");
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Enqueue an empty buffer to the driver's incoming queue
 | 
			
		||||
      if(ioctl(fd, VIDIOC_QBUF, &bufferinfo) < 0){
 | 
			
		||||
        ERROR_MSG("Could not enqueue buffer, aborting");
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      // Dequeue the filled buffer from the drivers outgoing queue
 | 
			
		||||
      if(ioctl(fd, VIDIOC_DQBUF, &bufferinfo) < 0){
 | 
			
		||||
        ERROR_MSG("Could not dequeue the buffer, aborting");
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (!bufferinfo.bytesused){
 | 
			
		||||
        Util::logExitReason(ER_CLEAN_EOF, "no more data");
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      INSANE_MSG("Buffer has %f KBytes of data", (double)bufferinfo.bytesused / 1024);
 | 
			
		||||
      thisIdx = tNumber;
 | 
			
		||||
      thisTime = Util::bootMS() - timeOffset;
 | 
			
		||||
      bufferLivePacket(thisTime, 0, tNumber, buffer, bufferinfo.bytesused, 0, true);
 | 
			
		||||
 | 
			
		||||
      if (!userSelect.count(thisIdx)){
 | 
			
		||||
        userSelect[thisIdx].reload(streamName, thisIdx, COMM_STATUS_ACTIVE | COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
 | 
			
		||||
      }
 | 
			
		||||
      if (Util::bootSecs() - statTimer > 1){
 | 
			
		||||
        // Connect to stats for INPUT detection
 | 
			
		||||
        if (!statComm){statComm.reload(streamName, getConnectedBinHost(), JSON::Value(getpid()).asString(), "INPUT:" + capa["name"].asStringRef(), "");}
 | 
			
		||||
        if (statComm){
 | 
			
		||||
          if (!statComm){
 | 
			
		||||
            config->is_active = false;
 | 
			
		||||
            Util::logExitReason(ER_CLEAN_CONTROLLER_REQ, "received shutdown request from controller");
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          uint64_t now = Util::bootSecs();
 | 
			
		||||
          statComm.setNow(now);
 | 
			
		||||
          statComm.setStream(streamName);
 | 
			
		||||
          statComm.setTime(now - startTime);
 | 
			
		||||
          statComm.setLastSecond(0);
 | 
			
		||||
          connStats(statComm);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        statTimer = Util::bootSecs();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void inputVideo4Linux::closeStreamSource(){
 | 
			
		||||
    if (fd){
 | 
			
		||||
      int type = bufferinfo.type;
 | 
			
		||||
      if(ioctl(fd, VIDIOC_STREAMOFF, &type) < 0){
 | 
			
		||||
        ERROR_MSG("Could not stop camera streaming I/O");
 | 
			
		||||
      }
 | 
			
		||||
      close(fd);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// \brief converts an int representing an encoded string back to it's original form
 | 
			
		||||
  std::string inputVideo4Linux::intToString(int n){
 | 
			
		||||
    std::string output;
 | 
			
		||||
    while(n){
 | 
			
		||||
      output += (char)n & 0xFF;
 | 
			
		||||
      n >>= 8;
 | 
			
		||||
    }
 | 
			
		||||
    return output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// \brief Converts a string to a hex encoded int
 | 
			
		||||
  int inputVideo4Linux::strToInt(std::string str){
 | 
			
		||||
    int output = 0;
 | 
			
		||||
    for (int i = str.size() - 1; i >= 0; i--){
 | 
			
		||||
      output <<= 8;
 | 
			
		||||
      output += (char)str[i];
 | 
			
		||||
    }
 | 
			
		||||
    return output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// \brief Translates an input string to it's matching device path
 | 
			
		||||
  std::string inputVideo4Linux::getInput(std::string input){
 | 
			
		||||
    // Remove 'v4l2://' prefix to get the requested video device
 | 
			
		||||
    if (input.substr(0, 5) == "v4l2:"){
 | 
			
		||||
      input = input.substr(5);
 | 
			
		||||
    }
 | 
			
		||||
    // If /dev/ is not prepended to the input, add it
 | 
			
		||||
    if (input.substr(0, 5) != "/dev/"){
 | 
			
		||||
      input = "/dev/" + input;
 | 
			
		||||
    }
 | 
			
		||||
    return input;
 | 
			
		||||
  }
 | 
			
		||||
}// namespace Mist
 | 
			
		||||
							
								
								
									
										41
									
								
								src/input/input_v4l2.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/input/input_v4l2.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
#include "input.h"
 | 
			
		||||
#include <mist/dtsc.h>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <linux/videodev2.h>
 | 
			
		||||
 | 
			
		||||
namespace Mist{
 | 
			
		||||
  class inputVideo4Linux : public Input{
 | 
			
		||||
  public:
 | 
			
		||||
    inputVideo4Linux(Util::Config *cfg);
 | 
			
		||||
 | 
			
		||||
  protected:
 | 
			
		||||
    bool checkArguments();
 | 
			
		||||
    virtual bool needHeader(){return false;}
 | 
			
		||||
    virtual bool isSingular(){return true;}
 | 
			
		||||
    bool needsLock(){return false;}
 | 
			
		||||
    JSON::Value enumerateSources(const std::string & device);
 | 
			
		||||
    JSON::Value getSourceCapa(const std::string & device);
 | 
			
		||||
    
 | 
			
		||||
    void parseStreamHeader(){};
 | 
			
		||||
    bool openStreamSource();
 | 
			
		||||
    void closeStreamSource();
 | 
			
		||||
    void streamMainLoop();
 | 
			
		||||
 | 
			
		||||
    std::string intToString(int n);
 | 
			
		||||
    int strToInt(std::string str);
 | 
			
		||||
    std::string getInput(std::string input);
 | 
			
		||||
 | 
			
		||||
    uint64_t width;
 | 
			
		||||
    uint64_t height;
 | 
			
		||||
    uint64_t fpsDenominator;
 | 
			
		||||
    uint64_t fpsNumerator;
 | 
			
		||||
    uint pixelFmt;
 | 
			
		||||
    uint64_t startTime;
 | 
			
		||||
    size_t tNumber;
 | 
			
		||||
    int fd;
 | 
			
		||||
    v4l2_buffer bufferinfo;
 | 
			
		||||
    char* buffer;
 | 
			
		||||
  };
 | 
			
		||||
}// namespace Mist
 | 
			
		||||
 | 
			
		||||
typedef Mist::inputVideo4Linux mistIn;
 | 
			
		||||
| 
						 | 
				
			
			@ -32,15 +32,12 @@ if have_srt
 | 
			
		|||
  inputs += {'name' : 'TSSRT', 'format' : 'tssrt', 'extra' : 'with_srt'}
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
av_libs = []
 | 
			
		||||
 | 
			
		||||
if get_option('WITH_AV')
 | 
			
		||||
  inputs += {'name' : 'AV', 'format' : 'av'}
 | 
			
		||||
  av_libs = [
 | 
			
		||||
    dependency('libavformat'),
 | 
			
		||||
    dependency('libavcodec'),
 | 
			
		||||
    dependency('libavutil'),
 | 
			
		||||
  ]
 | 
			
		||||
  inputs += {'name' : 'AV', 'format' : 'av', 'extra': 'with_av'}
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if ccpp.has_header('linux/videodev2.h')
 | 
			
		||||
  inputs += {'name' : 'V4L2', 'format' : 'v4l2'}
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
inputs_tgts = []
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +53,9 @@ foreach input : inputs
 | 
			
		|||
      deps += libmist_srt_dep
 | 
			
		||||
      deps += libsrt
 | 
			
		||||
    endif
 | 
			
		||||
    if input.get('extra').contains('with_av')
 | 
			
		||||
      deps += av_libs
 | 
			
		||||
    endif
 | 
			
		||||
  endif
 | 
			
		||||
  if input.get('name').contains('AV')
 | 
			
		||||
    deps += av_libs
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue