/****************************************************************************
 * apps/system/nxcamera/nxcamera.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <assert.h>
#include <debug.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>

#include <nuttx/queue.h>
#include <nuttx/video/video.h>
#include <nuttx/video/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/types.h>

#include <system/nxcamera.h>

#ifdef CONFIG_LIBYUV
#  include <libyuv.h>
#endif

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define NXCAMERA_STATE_IDLE      0
#define NXCAMERA_STATE_STREAMING 1
#define NXCAMERA_STATE_LOOPING   2
#define NXCAMERA_STATE_PAUSED    3

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * pan_display
 ****************************************************************************/

static void pan_display(int fb_device, FAR struct fb_planeinfo_s *plane_info)
{
  struct pollfd pfd;
  int ret;
  pfd.fd = fb_device;
  pfd.events = POLLOUT;

  ret = poll(&pfd, 1, 0);

  if (ret > 0)
    {
      ioctl(fb_device, FBIOPAN_DISPLAY, plane_info);
    }
}

static int show_image(FAR struct nxcamera_s *pcam, FAR v4l2_buffer_t *buf)
{
#ifdef CONFIG_LIBYUV
  if (pcam->display_vinfo.fmt == FB_FMT_RGB32)
    {
      return ConvertToARGB(pcam->bufs[buf->index],
                           pcam->buf_sizes[buf->index],
                           pcam->display_pinfo.fbmem,
                           pcam->display_pinfo.stride,
                           0,
                           0,
                           pcam->fmt.fmt.pix.width,
                           pcam->fmt.fmt.pix.height,
                           pcam->fmt.fmt.pix.width,
                           pcam->fmt.fmt.pix.height,
                           0,
                           pcam->fmt.fmt.pix.pixelformat);
    }
  else if (pcam->display_vinfo.fmt == FB_FMT_RGB16_565)
    {
      if (pcam->fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420)
        {
          return ConvertFromI420(pcam->bufs[buf->index],
                                 pcam->fmt.fmt.pix.width,
                                 &pcam->bufs[buf->index][
                                            pcam->fmt.fmt.pix.width *
                                            pcam->fmt.fmt.pix.height],
                                 pcam->fmt.fmt.pix.width / 2,
                                 &pcam->bufs[buf->index][
                                      pcam->fmt.fmt.pix.width *
                                      pcam->fmt.fmt.pix.height * 5 / 4],
                                 pcam->fmt.fmt.pix.width / 2,
                                 pcam->display_pinfo.fbmem,
                                 pcam->display_pinfo.stride,
                                 pcam->fmt.fmt.pix.width,
                                 pcam->fmt.fmt.pix.height,
                                 V4L2_PIX_FMT_RGB565);
        }
      else
        {
          int ret;
          FAR uint8_t *dst = malloc(pcam->fmt.fmt.pix.width *
                                    pcam->fmt.fmt.pix.height * 3 / 2);
          if (!dst)
            {
              return -ENOMEM;
            }

          ret = ConvertToI420(pcam->bufs[buf->index],
                              pcam->buf_sizes[buf->index],
                              dst,
                              pcam->fmt.fmt.pix.width,
                              &dst[pcam->fmt.fmt.pix.width *
                                        pcam->fmt.fmt.pix.height],
                              pcam->fmt.fmt.pix.width / 2,
                              &dst[pcam->fmt.fmt.pix.width *
                                        pcam->fmt.fmt.pix.height * 5 / 4],
                              pcam->fmt.fmt.pix.width / 2,
                              0,
                              0,
                              pcam->fmt.fmt.pix.width,
                              pcam->fmt.fmt.pix.height,
                              pcam->fmt.fmt.pix.width,
                              pcam->fmt.fmt.pix.height,
                              0,
                              pcam->fmt.fmt.pix.pixelformat);
          if (ret < 0)
            {
              goto err;
            }

          ret = ConvertFromI420(dst,
                                pcam->fmt.fmt.pix.width,
                                &dst[pcam->fmt.fmt.pix.width *
                                            pcam->fmt.fmt.pix.height],
                                pcam->fmt.fmt.pix.width / 2,
                                &dst[pcam->fmt.fmt.pix.width *
                                     pcam->fmt.fmt.pix.height * 5 / 4],
                                pcam->fmt.fmt.pix.width / 2,
                                pcam->display_pinfo.fbmem,
                                pcam->display_pinfo.stride,
                                pcam->fmt.fmt.pix.width,
                                pcam->fmt.fmt.pix.height,
                                V4L2_PIX_FMT_RGB565);
          if (ret < 0)
            {
              goto err;
            }

err:
          free(dst);
          return ret;
        }
    }

  return 0;
#else
  FAR uint32_t *pbuf = (FAR uint32_t *)pcam->bufs[buf->index];
  vinfo("show image from %p: %" PRIx32 " %" PRIx32, pbuf, pbuf[0], pbuf[1]);
  return 0;
#endif
}

/****************************************************************************
 * Name: nxcamera_opendevice
 *
 *   nxcamera_opendevice() tries to open the preferred devices as specified.
 *
 * Return:
 *    OK        if compatible device opened (searched or preferred)
 *    -ENODEV   if no compatible device opened.
 *    -ENOENT   if preferred device couldn't be opened.
 *
 ****************************************************************************/

static int nxcamera_opendevice(FAR struct nxcamera_s *pcam)
{
  int errcode;

  if (pcam->capturedev[0] != '\0')
    {
      pcam->capture_fd = open(pcam->capturedev, O_RDWR);
      if (pcam->capture_fd == -1)
        {
          errcode = errno;
          DEBUGASSERT(errcode > 0);

          verr("ERROR: Failed to open pcam->capturedev %d\n", -errcode);
          return -errcode;
        }

      if (pcam->displaydev[0] != '\0')
        {
          pcam->display_fd = open(pcam->displaydev, O_RDWR);
          if (pcam->display_fd == -1)
            {
              errcode = errno;
              DEBUGASSERT(errcode > 0);

              close(pcam->capture_fd);
              pcam->capture_fd = -1;
              verr("ERROR: Failed to open pcam->displaydev %d\n", -errcode);
              return -errcode;
            }

          errcode = ioctl(pcam->display_fd, FBIOGET_PLANEINFO,
                          ((uintptr_t)&pcam->display_pinfo));

          if (errcode == OK)
            {
              pcam->display_pinfo.fbmem = mmap(NULL,
                                               pcam->display_pinfo.fblen,
                                               PROT_READ | PROT_WRITE,
                                               MAP_SHARED | MAP_FILE,
                                               pcam->display_fd,
                                               0);
            }

          if (errcode < 0 || pcam->display_pinfo.fbmem == MAP_FAILED)
            {
              errcode = errno;
              close(pcam->capture_fd);
              close(pcam->display_fd);
              verr("ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d\n", -errcode);
              return -errcode;
            }

          return OK;
        }
      else
        {
          /* TODO: Add file output */

          return -ENOTSUP;
        }
    }

  return -ENODEV;
}

/****************************************************************************
 * Name: nxcameraer_jointhread
 ****************************************************************************/

static void nxcamera_jointhread(FAR struct nxcamera_s *pcam)
{
  FAR void *value;
  int id = 0;

  if (gettid() == pcam->loop_id)
    {
      return;
    }

  pthread_mutex_lock(&pcam->mutex);

  if (pcam->loop_id > 0)
    {
      id = pcam->loop_id;
      pcam->loop_id = 0;
    }

  pthread_mutex_unlock(&pcam->mutex);

  if (id > 0)
    {
      pthread_join(id, &value);
    }
}

/****************************************************************************
 * Name: nxcamera_loopthread
 *
 *  This is the thread that streams the video and handles video controls.
 *
 ****************************************************************************/

static void *nxcamera_loopthread(pthread_addr_t pvarg)
{
  FAR struct nxcamera_s   *pcam = (FAR struct nxcamera_s *)pvarg;
  unsigned int            prio;
  ssize_t                 size;
  struct video_msg_s      msg;
  bool                    streaming = true;
  int                     i;
  int                     ret;
  struct v4l2_buffer      buf;
  uint32_t                type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  vinfo("Entry\n");
  memset(&buf, 0, sizeof(buf));
  for (i = 0; i < pcam->nbuffers; i++)
    {
      buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
      buf.memory = V4L2_MEMORY_MMAP;
      buf.index = i;
      ret = ioctl(pcam->capture_fd, VIDIOC_QBUF, (uintptr_t)&buf);
      if (ret < 0)
        {
          verr("VIDIOC_QBUF failed: %d\n", ret);
          goto err_out;
        }
    }

  /* VIDIOC_STREAMON start stream */

  ret = ioctl(pcam->capture_fd, VIDIOC_STREAMON, (uintptr_t)&type);
  if (ret < 0)
    {
      verr("VIDIOC_STREAMON failed: %d\n", ret);
      goto err_out;
    }
  else
    {
      pcam->loopstate = NXCAMERA_STATE_STREAMING;
    }

  /* Loop until we specifically break. streaming == true means that we are
   * still looping waiting for the stream to complete.  All of the data
   * may have been sent, but the stream is not complete until we get
   * VIDEO_MSG_STOP message
   *
   * The normal protocol for looping errors detected by the video driver
   * is as follows:
   *
   * (1) The video driver will indicated the error by returning a negated
   *     error value when the next buffer is enqueued.  The upper level
   *     then knows that this buffer was not queued.
   * (2) The video driver must return all queued buffers using the
   *     VIDEO_MSG_DEQUEUE message.
   */

  while (streaming)
    {
      size = mq_receive(pcam->mq, (FAR char *)&msg, sizeof(msg), &prio);

      /* Validate a message was received */

      if (size == sizeof(msg))
        {
          /* Perform operation based on message id */

          vinfo("message received size %zd id%d\n", size, msg.msg_id);
          switch (msg.msg_id)
            {
              /* Someone wants to stop the stream. */

              case VIDEO_MSG_STOP:

                /* Send a stop message to the device */

                vinfo("Stopping looping\n");
                ioctl(pcam->capture_fd, VIDIOC_STREAMOFF, (uintptr_t)&type);
                streaming = false;
                goto err_out;

              /* Unknown / unsupported message ID */

              default:
                break;
            }
        }

      ret = ioctl(pcam->capture_fd, VIDIOC_DQBUF, (uintptr_t)&buf);
      if (ret < 0)
        {
          verr("Fail DQBUF %d\n", errno);
          goto err_out;
        }

      ret = show_image(pcam, &buf);
      if (ret < 0)
        {
          verr("Fail to show image %d\n", -ret);
          goto err_out;
        }

      if (pcam->display_pinfo.yres_virtual > pcam->display_vinfo.yres)
        {
          pan_display(pcam->display_fd, &pcam->display_pinfo);
        }

      ret = ioctl(pcam->capture_fd, VIDIOC_QBUF, (uintptr_t)&buf);
      if (ret < 0)
        {
          verr("Fail QBUF %d\n", errno);
          goto err_out;
        }
    }

  /* Release our video buffers and unregister / release the device */

err_out:
  vinfo("Clean-up and exit\n");

  /* Cleanup */

  pthread_mutex_lock(&pcam->mutex);  /* Lock the mutex */

  close(pcam->display_fd);           /* Close the display device */
  close(pcam->capture_fd);           /* Close the capture device */
  pcam->display_fd = -1;             /* Mark display device as closed */
  pcam->capture_fd = -1;             /* Mark capture device as closed */
  mq_close(pcam->mq);                /* Close the message queue */
  mq_unlink(pcam->mqname);           /* Unlink the message queue */
  pcam->loopstate = NXCAMERA_STATE_IDLE;
  for (i = 0; i < pcam->nbuffers; i++)
    {
      munmap(pcam->bufs[i], pcam->buf_sizes[i]);
    }

  free(pcam->bufs);
  free(pcam->buf_sizes);
  pthread_mutex_unlock(&pcam->mutex);     /* Unlock the mutex */

  vinfo("Exit\n");

  return NULL;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: nxcamera_setdevice
 *
 *   nxcamera_setdevice() sets the preferred video device to use with the
 *   provided nxcamera context.
 *
 ****************************************************************************/

int nxcamera_setdevice(FAR struct nxcamera_s *pcam,
                       FAR const char *device)
{
  int                    temp_fd;
  struct v4l2_capability caps;

  DEBUGASSERT(pcam != NULL);
  DEBUGASSERT(device != NULL);

  /* Try to open the device */

  temp_fd = open(device, O_RDWR);
  if (temp_fd == -1)
    {
      /* Error opening the device */

      return -errno;
    }

  /* Validate it's a video device by issuing an VIDIOC_QUERYCAP ioctl */

  if (ioctl(temp_fd, VIDIOC_QUERYCAP, (uintptr_t)&caps) != OK)
    {
      /* Not a video device! */

      close(temp_fd);
      return -errno;
    }

  /* Close the file */

  close(temp_fd);

  /* Save the path of the preferred device */

  if ((caps.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0)
    {
      return -ENODEV;
    }

  strlcpy(pcam->capturedev, device, sizeof(pcam->capturedev));
  return OK;
}

/****************************************************************************
 * Name: nxcamera_setfb
 *
 *   nxcamera_setfb() sets the output framebuffer device to use with the
 *   provided nxcamera context.
 *
 ****************************************************************************/

int nxcamera_setfb(FAR struct nxcamera_s *pcam, FAR const char *device)
{
  int temp_fd;

  DEBUGASSERT(pcam != NULL);
  DEBUGASSERT(device != NULL);

  /* Try to open the device */

  temp_fd = open(device, O_RDWR);
  if (temp_fd == -1)
    {
      /* Error opening the device */

      return -errno;
    }

  /* Validate it's a fb device by issuing an FBIOGET_VIDEOINFO ioctl */

  if (ioctl(temp_fd, FBIOGET_VIDEOINFO,
            (uintptr_t)&pcam->display_vinfo) != OK)
    {
      /* Not an Video device! */

      close(temp_fd);
      return -errno;
    }

  /* Close the file */

  close(temp_fd);

  if (pcam->display_vinfo.nplanes == 0)
    {
      return -ENODEV;
    }

  /* Save the path of the framebuffer device */

  strlcpy(pcam->displaydev, device, sizeof(pcam->displaydev));
  return OK;
}

/****************************************************************************
 * Name: nxcamera_setfile
 *
 *   nxcamera_setfile() sets the output file path to use with the provided
 *   nxcamera context.
 *
 ****************************************************************************/

int nxcamera_setfile(FAR struct nxcamera_s *pcam, FAR const char *pfile,
                     bool isimage)
{
  int temp_fd;

  DEBUGASSERT(pcam != NULL);
  DEBUGASSERT(pfile != NULL);

  /* Try to open the file */

  temp_fd = open(pfile, O_CREAT | O_RDWR);
  if (temp_fd == -1)
    {
      /* Error opening the file */

      return -errno;
    }

  /* Close the file */

  close(temp_fd);

  /* Save the path of the output file */

  if (isimage)
    {
      strlcpy(pcam->imagepath, pfile, sizeof(pcam->imagepath));
    }
  else
    {
      strlcpy(pcam->videopath, pfile, sizeof(pcam->videopath));
    }

  return OK;
}

/****************************************************************************
 * Name: nxcamera_stop
 *
 *   nxcamera_stop() stops the current playback to loop and closes the
 *   file and the associated device.
 *
 * Input:
 *   pcam    Pointer to the initialized looper context
 *
 ****************************************************************************/

int nxcamera_stop(FAR struct nxcamera_s *pcam)
{
  struct video_msg_s term_msg;

  DEBUGASSERT(pcam != NULL);

  /* Validate we are not in IDLE state */

  pthread_mutex_lock(&pcam->mutex);          /* Lock the mutex */
  if (pcam->loopstate == NXCAMERA_STATE_IDLE)
    {
      pthread_mutex_unlock(&pcam->mutex);    /* Unlock the mutex */
      return OK;
    }

  /* Notify the stream thread that it needs to cancel the stream */

  term_msg.msg_id = VIDEO_MSG_STOP;
  term_msg.u.data = 0;
  mq_send(pcam->mq, (FAR const char *)&term_msg, sizeof(term_msg),
          CONFIG_NXCAMERA_MSG_PRIO);

  pthread_mutex_unlock(&pcam->mutex);

  /* Join the thread.  The thread will do all the cleanup. */

  nxcamera_jointhread(pcam);

  return OK;
}

/****************************************************************************
 * Name: nxcamera_stream
 *
 *   nxcamera_stream() tries to capture and then display the raw data using
 *   the Video system. If a capture device is specified, it will try to use
 *   that device.
 *
 * Input:
 *   pcam    Pointer to the initialized Looper context
 *   width      Capture frame width
 *   height     Capture frame height
 *   framerate  Capture frame rate
 *   format     Capture frame pixel format
 *
 * Returns:
 *   OK         Video is being looped
 *   -EBUSY     Capture device is busy
 *   -ENOSYS    No supported video format found
 *   -ENODEV    No video capture or framebuffer device suitable
 *
 ****************************************************************************/

int nxcamera_stream(FAR struct nxcamera_s *pcam,
                    uint16_t width, uint16_t height,
                    uint32_t framerate, uint32_t format)
{
  struct mq_attr             attr;
  struct sched_param         sparam;
  pthread_attr_t             tattr;
  int                        ret;
  int                        i;
  struct v4l2_buffer         buf;
  struct v4l2_requestbuffers req;
  struct v4l2_streamparm     parm;

  DEBUGASSERT(pcam != NULL);

  pthread_mutex_lock(&pcam->mutex);          /* Lock the mutex */
  if (pcam->loopstate != NXCAMERA_STATE_IDLE)
    {
      pthread_mutex_unlock(&pcam->mutex);    /* Unlock the mutex */
      return -EBUSY;
    }

  pthread_mutex_unlock(&pcam->mutex);    /* Unlock the mutex */

  vinfo("==============================\n");
  vinfo("streaming video\n");
  vinfo("==============================\n");

  /* Try to open the device */

  ret = nxcamera_opendevice(pcam);
  if (ret < 0)
    {
      /* Error opening the device */

      verr("ERROR: nxcamera_opendevice failed: %d\n", ret);
      return ret;
    }

  /* VIDIOC_S_FMT set format */

  pcam->fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  pcam->fmt.fmt.pix.width       = width;
  pcam->fmt.fmt.pix.height      = height;
  pcam->fmt.fmt.pix.field       = V4L2_FIELD_ANY;
  pcam->fmt.fmt.pix.pixelformat = format;

  ret = ioctl(pcam->capture_fd, VIDIOC_S_FMT, (uintptr_t)&pcam->fmt);
  if (ret < 0)
    {
      ret = -errno;
      verr("VIDIOC_S_FMT failed: %d\n", ret);
      return ret;
    }

  memset(&parm, 0, sizeof(parm));
  parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  parm.parm.capture.timeperframe.denominator = framerate;
  parm.parm.capture.timeperframe.numerator = 1;
  ret = ioctl(pcam->capture_fd, VIDIOC_S_PARM, (uintptr_t)&parm);
  if (ret < 0)
    {
      ret = -errno;
      verr("VIDIOC_S_PARM failed: %d\n", ret);
      return ret;
    }

  /* VIDIOC_REQBUFS initiate user pointer I/O */

  memset(&req, 0, sizeof(req));
  req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  req.memory = V4L2_MEMORY_MMAP;
  req.count  = CONFIG_VIDEO_REQBUFS_COUNT_MAX;

  ret = ioctl(pcam->capture_fd, VIDIOC_REQBUFS, (uintptr_t)&req);
  if (ret < 0)
    {
      ret = -errno;
      verr("VIDIOC_REQBUFS failed: %d\n", ret);
      return ret;
    }

  if (req.count < 2)
    {
      verr("VIDIOC_REQBUFS failed: not enough buffers\n");
      return -ENOMEM;
    }

  pcam->nbuffers  = req.count;
  pcam->bufs      = calloc(req.count, sizeof(*pcam->bufs));
  pcam->buf_sizes = calloc(req.count, sizeof(*pcam->buf_sizes));
  if (pcam->bufs == NULL || pcam->buf_sizes == NULL)
    {
      verr("Cannot allocate buffer pointers\n");
      ret = -ENOMEM;
      goto err_out;
    }

  /* VIDIOC_QBUF enqueue buffer */

  for (i = 0; i < req.count; i++)
    {
      memset(&buf, 0, sizeof(buf));
      buf.type   = req.type;
      buf.memory = V4L2_MEMORY_MMAP;
      buf.index  = i;
      ret = ioctl(pcam->capture_fd, VIDIOC_QUERYBUF, (uintptr_t)&buf);
      if (ret < 0)
        {
          ret = -errno;
          verr("VIDIOC_QUERYBUF failed: %d\n", ret);
          goto err_out;
        }

      pcam->bufs[i] = mmap(NULL, buf.length,
                           PROT_READ | PROT_WRITE, MAP_SHARED,
                           pcam->capture_fd, buf.m.offset);
      if (pcam->bufs[i] == MAP_FAILED)
        {
          ret = -errno;
          verr("MMAP failed\n");
          goto err_out;
        }

      pcam->buf_sizes[i] = buf.length;
    }

  /* Create a message queue for the loopthread */

  memset(&attr, 0, sizeof(attr));
  attr.mq_maxmsg  = 8;
  attr.mq_msgsize = sizeof(struct video_msg_s);

  snprintf(pcam->mqname, sizeof(pcam->mqname), "/tmp/%lx",
           (unsigned long)((uintptr_t)pcam) & 0xffffffff);

  pcam->mq = mq_open(pcam->mqname, O_RDWR | O_CREAT | O_NONBLOCK, 0644,
                     &attr);
  if (pcam->mq == (mqd_t)-1)
    {
      /* Unable to open message queue! */

      ret = -errno;
      verr("ERROR: mq_open failed: %d\n", ret);
      goto err_out;
    }

  /* Check if there was a previous thread and join it if there was
   * to perform clean-up.
   */

  nxcamera_jointhread(pcam);

  pthread_attr_init(&tattr);
  sparam.sched_priority = sched_get_priority_max(SCHED_FIFO) - 9;
  pthread_attr_setschedparam(&tattr, &sparam);
  pthread_attr_setstacksize(&tattr, CONFIG_NXCAMERA_LOOPTHREAD_STACKSIZE);

  /* Add a reference count to the looper for the thread and start the
   * thread.  We increment for the thread to avoid thread start-up
   * race conditions.
   */

  nxcamera_reference(pcam);
  ret = pthread_create(&pcam->loop_id, &tattr, nxcamera_loopthread,
                       (pthread_addr_t)pcam);
  pthread_attr_destroy(&tattr);
  if (ret != OK)
    {
      ret = -ret;
      verr("ERROR: Failed to create loopthread: %d\n", ret);
      goto err_out;
    }

  /* Name the thread */

  pthread_setname_np(pcam->loop_id, "nxcameraloop");
  return OK;

err_out:
  if (pcam->bufs)
    {
      for (i = 0; i < pcam->nbuffers; i++)
        {
          if (pcam->bufs[i] != NULL && pcam->bufs[i] != MAP_FAILED)
            {
              munmap(pcam->bufs[i], pcam->buf_sizes[i]);
            }
        }

      free(pcam->bufs);
    }

  if (pcam->buf_sizes)
    {
      free(pcam->buf_sizes);
    }

  return ret;
}

/****************************************************************************
 * Name: nxcamera_create
 *
 *   nxcamera_create() allocates and initializes a nxcamera context for
 *   use by further nxcamera operations.  This routine must be called before
 *   to perform the create for proper reference counting.
 *
 * Input Parameters:  None
 *
 * Returned values:
 *   Pointer to the created context or NULL if there was an error.
 *
 ****************************************************************************/

FAR struct nxcamera_s *nxcamera_create(void)
{
  FAR struct nxcamera_s *pcam;
  int err;

  /* Allocate the memory */

  pcam = (FAR struct nxcamera_s *)calloc(1, sizeof(struct nxcamera_s));
  if (pcam == NULL)
    {
      return NULL;
    }

  /* Initialize the context data */

  pcam->loopstate = NXCAMERA_STATE_IDLE;
  pcam->display_fd = -1;
  pcam->capture_fd = -1;
  err = pthread_mutex_init(&pcam->mutex, NULL);
  if (err)
    {
      verr("ERROR: pthread_mutex_init failed: %d\n", err);
      free(pcam);
      pcam = NULL;
    }

  return pcam;
}

/****************************************************************************
 * Name: nxcamera_release
 *
 *   nxcamera_release() reduces the reference count by one and if it
 *   reaches zero, frees the context.
 *
 * Input Parameters:
 *   pcam    Pointer to the NxCamera context
 *
 * Returned values:    None
 *
 ****************************************************************************/

void nxcamera_release(FAR struct nxcamera_s *pcam)
{
  int      refcount;

  /* Check if there was a previous thread and join it if there was */

  nxcamera_jointhread(pcam);

  /* Lock the mutex */

  pthread_mutex_lock(&pcam->mutex);

  /* Reduce the reference count */

  refcount = pcam->crefs--;
  pthread_mutex_unlock(&pcam->mutex);

  /* If the ref count *was* one, then free the context */

  if (refcount == 1)
    {
      pthread_mutex_destroy(&pcam->mutex);
      free(pcam);
    }
}

/****************************************************************************
 * Name: nxcamera_reference
 *
 *   nxcamera_reference() increments the reference count by one.
 *
 * Input Parameters:
 *   pcam    Pointer to the NxCamera context
 *
 * Returned values:    None
 *
 ****************************************************************************/

void nxcamera_reference(FAR struct nxcamera_s *pcam)
{
  /* Lock the mutex */

  pthread_mutex_lock(&pcam->mutex);

  /* Increment the reference count */

  pcam->crefs++;
  pthread_mutex_unlock(&pcam->mutex);
}
