From 38a190156a11be5515ca82fd06abc0ca204a13d0 Mon Sep 17 00:00:00 2001
From: Peter Bee <bijunda1@xiaomi.com>
Date: Tue, 15 Nov 2022 18:25:31 +0800
Subject: [PATCH] apps/system: add nxcamera app

Add NxCamera app similar to NxLooper, which can be used to control video
capture device. The basic capture n' display function is complete, while
file output and camera control features are left TODO.

Signed-off-by: Peter Bee <bijunda1@xiaomi.com>
---
 include/system/nxcamera.h       | 254 ++++++++++
 system/nxcamera/Kconfig         |  41 ++
 system/nxcamera/Make.defs       |  23 +
 system/nxcamera/Makefile        |  36 ++
 system/nxcamera/nxcamera.c      | 849 ++++++++++++++++++++++++++++++++
 system/nxcamera/nxcamera_main.c | 496 +++++++++++++++++++
 6 files changed, 1699 insertions(+)
 create mode 100644 include/system/nxcamera.h
 create mode 100644 system/nxcamera/Kconfig
 create mode 100644 system/nxcamera/Make.defs
 create mode 100644 system/nxcamera/Makefile
 create mode 100644 system/nxcamera/nxcamera.c
 create mode 100644 system/nxcamera/nxcamera_main.c

diff --git a/include/system/nxcamera.h b/include/system/nxcamera.h
new file mode 100644
index 000000000..21da9a53c
--- /dev/null
+++ b/include/system/nxcamera.h
@@ -0,0 +1,254 @@
+/****************************************************************************
+ * apps/include/system/nxcamera.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __APPS_INCLUDE_SYSTEM_NXCAMERA_H
+#define __APPS_INCLUDE_SYSTEM_NXCAMERA_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <nuttx/video/video.h>
+#include <nuttx/video/fb.h>
+#include <mqueue.h>
+#include <pthread.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Standard Video Message Queue message IDs */
+
+#define VIDEO_MSG_NONE              0
+#define VIDEO_MSG_STOP              1
+
+/****************************************************************************
+ * Public Type Declarations
+ ****************************************************************************/
+
+/* This structure describes the internal state of the nxcamera */
+
+struct nxcamera_s
+{
+  int                   loopstate;                   /* Current looper test state */
+  int                   capture_fd;                  /* File descriptor of active
+                                                      * capture device */
+  char                  capturedev[CONFIG_NAME_MAX]; /* Preferred capture device */
+  int                   display_fd;                  /* File descriptor of active
+                                                      * display device */
+  char                  displaydev[CONFIG_NAME_MAX]; /* Display framebuffer device */
+  struct fb_planeinfo_s display_pinfo;               /* Display plane info */
+  char                  videopath[CONFIG_NAME_MAX];  /* Output video file path */
+  char                  imagepath[CONFIG_NAME_MAX];  /* Output image file path */
+  int                   crefs;                       /* Number of references */
+  pthread_mutex_t       mutex;                       /* Thread sync mutex */
+  char                  mqname[14];                  /* Name of display message queue */
+  mqd_t                 mq;                          /* Message queue for the
+                                                      * loopthread */
+  pthread_t             loop_id;                     /* Thread ID of the loopthread */
+  v4l2_format_t         fmt;                         /* Buffer format */
+  size_t                nbuffers;                    /* Number of buffers */
+  FAR size_t            *buf_sizes;                  /* Buffer lengths */
+  FAR uint8_t           **bufs;                      /* Buffer pointers */
+};
+
+struct video_msg_s
+{
+  uint16_t      msg_id;       /* Message ID */
+  union
+  {
+    FAR void    *ptr;         /* Buffer being dequeued */
+    uint32_t    data;         /* Message data */
+  } u;
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nxcamera_create
+ *
+ *   Allocates and Initializes a nxcamera context that is passed to all
+ *   nxcamera routines.  The looper MUST be destroyed using the
+ *   nxcamera_destroy() routine since the context is reference counted.
+ *   The context can be used in a mode where the caller creates the
+ *   context, starts a looping, and then forgets about the context
+ *   and it will self free.  This is because the nxcamera_stream
+ *   will also create a reference to the context, so the client calling
+ *   nxcamera_destroy() won't actually de-allocate anything. The freeing
+ *   will occur after the loopthread has completed.
+ *
+ *   Alternately, the caller can create the object and hold on to it, then
+ *   the context will persist until the original creator destroys it.
+ *
+ * Input Parameters:    None
+ *
+ * Returned Value:
+ *   Pointer to created nxcamera context or NULL if error.
+ ****************************************************************************/
+
+FAR struct nxcamera_s *nxcamera_create(void);
+
+/****************************************************************************
+ * Name: nxcamera_release
+ *
+ *   Reduces the reference count to the looper and if it reaches zero,
+ *   frees all memory used by the context.
+ *
+ * Input Parameters:
+ *   pcam    Pointer to the nxcamera context
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+void nxcamera_release(FAR struct nxcamera_s *pcam);
+
+/****************************************************************************
+ * Name: nxcamera_reference
+ *
+ *   Increments the reference count to the looper.
+ *
+ * Input Parameters:
+ *   pcam    Pointer to the nxcamera context
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+void nxcamera_reference(FAR struct nxcamera_s *pcam);
+
+/****************************************************************************
+ * 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 streamed
+ *   -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);
+
+/****************************************************************************
+ * Name: nxcamera_stop
+ *
+ *   Stops current loopback.
+ *
+ * Input Parameters:
+ *   pcam   - Pointer to the context to initialize
+ *
+ * Returned Value:
+ *   OK if file found, device found, and loopback started.
+ *
+ ****************************************************************************/
+
+int nxcamera_stop(FAR struct nxcamera_s *pcam);
+
+/****************************************************************************
+ * Name: nxcamera_setdevice
+ *
+ *   Sets the preferred video device to use with the instance of the
+ *   nxcamera.
+ *
+ * Input Parameters:
+ *   pcam   - Pointer to the context to initialize
+ *   device - Pointer to pathname of the preferred device
+ *
+ * Returned Value:
+ *   OK if context initialized successfully, error code otherwise.
+ *
+ ****************************************************************************/
+
+int nxcamera_setdevice(FAR struct nxcamera_s *pcam,
+                       FAR const char *device);
+
+/****************************************************************************
+ * Name: nxcamera_setfb
+ *
+ *   Sets the output framebuffer device to use with the
+ *   provided nxcamera context.
+ *
+ * Input Parameters:
+ *   pcam   - Pointer to the context to initialize
+ *   device - Pointer to pathname of the preferred framebuffer device
+ *
+ * Returned Value:
+ *   OK if context initialized successfully, error code otherwise.
+ *
+ ****************************************************************************/
+
+int nxcamera_setfb(FAR struct nxcamera_s *pcam, FAR const char *device);
+
+/****************************************************************************
+ * Name: nxcamera_setfile
+ *
+ *   Sets the output file path to use with the provided nxcamera context.
+ *
+ * Input Parameters:
+ *   pcam      - Pointer to the context to initialize
+ *   file      - Pointer to pathname of the preferred output file
+ *   isimage   - The path is for image file or not
+ *
+ * Returned Value:
+ *   OK if file found, device found, and loopback started.
+ *
+ ****************************************************************************/
+
+int nxcamera_setfile(FAR struct nxcamera_s *pcam, FAR const char *pfile,
+                     bool isimage);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __APPS_INCLUDE_SYSTEM_NXCAMERA_H */
diff --git a/system/nxcamera/Kconfig b/system/nxcamera/Kconfig
new file mode 100644
index 000000000..7e42e6b2c
--- /dev/null
+++ b/system/nxcamera/Kconfig
@@ -0,0 +1,41 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+config SYSTEM_NXCAMERA
+	bool "NxCamera video test application"
+	default n
+	depends on VIDEO
+	---help---
+		Enable support for the NxCam media tester library and optional
+		command line interface.
+
+if SYSTEM_NXCAMERA
+
+config NXCAMERA_MAINTHREAD_STACKSIZE
+	int "NxCamera main thread stack size"
+	default DEFAULT_TASK_STACKSIZE
+	---help---
+		Stack size to use with the NxCamera main thread.
+
+config NXCAMERA_LOOPTHREAD_STACKSIZE
+	int "NxCamera thread stack size"
+	default PTHREAD_STACK_DEFAULT
+	---help---
+		Stack size to use with the NxCamera play thread.
+
+config NXCAMERA_MSG_PRIO
+	int "NxCamera priority of message queen"
+	default 1
+	---help---
+		Priority of stop message to notice NxCamera thread.
+
+config NXCAMERA_INCLUDE_HELP
+	bool "Include HELP command and text"
+	default y
+	---help---
+		Compiles in the NxCamera help text to provide online help
+		for available commands with syntax.
+
+endif
diff --git a/system/nxcamera/Make.defs b/system/nxcamera/Make.defs
new file mode 100644
index 000000000..bb3931b44
--- /dev/null
+++ b/system/nxcamera/Make.defs
@@ -0,0 +1,23 @@
+############################################################################
+# apps/system/nxcamera/Make.defs
+#
+# 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.
+#
+############################################################################
+
+ifneq ($(CONFIG_SYSTEM_NXCAMERA),)
+CONFIGURED_APPS += $(APPDIR)/system/nxcamera
+endif
diff --git a/system/nxcamera/Makefile b/system/nxcamera/Makefile
new file mode 100644
index 000000000..88cbffe73
--- /dev/null
+++ b/system/nxcamera/Makefile
@@ -0,0 +1,36 @@
+############################################################################
+# apps/system/nxcamera/Makefile
+#
+# 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.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs
+
+# NxCamera Library
+
+CSRCS     = nxcamera.c
+
+ifneq ($(CONFIG_SYSTEM_NXCAMERA),)
+PROGNAME  = nxcamera
+PRIORITY  = SCHED_PRIORITY_DEFAULT
+STACKSIZE = $(CONFIG_NXCAMERA_MAINTHREAD_STACKSIZE)
+MODULE    = $(CONFIG_SYSTEM_NXCAMERA)
+
+MAINSRC   = nxcamera_main.c
+endif
+
+include $(APPDIR)/Application.mk
diff --git a/system/nxcamera/nxcamera.c b/system/nxcamera/nxcamera.c
new file mode 100644
index 000000000..c1d90ac48
--- /dev/null
+++ b/system/nxcamera/nxcamera.c
@@ -0,0 +1,849 @@
+/****************************************************************************
+ * 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 <nuttx/queue.h>
+#include <nuttx/video/video.h>
+#include <nuttx/video/fb.h>
+#include <sys/ioctl.h>
+#include <sys/mman.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
+
+#ifndef MIN
+#  define MIN(a, b)              (((a) < (b)) ? (a) : (b))
+#endif
+
+#define convert_frame            ConvertToARGB
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static int show_image(FAR struct nxcamera_s *pcam, FAR v4l2_buffer_t *buf)
+{
+#ifdef CONFIG_LIBYUV
+  return convert_frame(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
+  uint32_t *pbuf = 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: 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;
+        }
+
+      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;
+  struct fb_videoinfo_s vinfo;
+
+  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)&vinfo) != OK)
+    {
+      /* Not an Video device! */
+
+      close(temp_fd);
+      return -errno;
+    }
+
+  /* Close the file */
+
+  close(temp_fd);
+
+  if (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;
+  FAR void           *value;
+
+  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. */
+
+  pthread_join(pcam->loop_id, &value);
+  pcam->loop_id = 0;
+
+  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;
+  FAR void                   *value;
+  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.
+   */
+
+  if (pcam->loop_id != 0)
+    {
+      pthread_join(pcam->loop_id, &value);
+    }
+
+  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)
+{
+  FAR void *value;
+  int      refcount;
+
+  /* Lock the mutex */
+
+  pthread_mutex_lock(&pcam->mutex);
+
+  /* Check if there was a previous thread and join it if there was */
+
+  if (pcam->loop_id != 0)
+    {
+      pthread_mutex_unlock(&pcam->mutex);
+      pthread_join(pcam->loop_id, &value);
+      pcam->loop_id = 0;
+
+      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);
+}
diff --git a/system/nxcamera/nxcamera_main.c b/system/nxcamera/nxcamera_main.c
new file mode 100644
index 000000000..5e3d43708
--- /dev/null
+++ b/system/nxcamera/nxcamera_main.c
@@ -0,0 +1,496 @@
+/****************************************************************************
+ * apps/system/nxcamera/nxcamera_main.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 <nuttx/video/video.h>
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <system/readline.h>
+#include <system/nxcamera.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define NXCAMERA_VER    "1.00"
+
+#ifdef CONFIG_NXCAMERA_INCLUDE_HELP
+#  define NXCAMERA_HELP_TEXT(x)  x
+#else
+#  define NXCAMERA_HELP_TEXT(x)
+#endif
+
+/****************************************************************************
+ * Private Type Declarations
+ ****************************************************************************/
+
+typedef CODE int (*nxcamera_func)(FAR struct nxcamera_s *cam, FAR char *arg);
+
+struct nxcamera_cmd_s
+{
+  FAR const char *cmd;       /* The command text */
+  FAR const char *arghelp;   /* Text describing the args */
+  nxcamera_func  pfunc;      /* Pointer to command handler */
+  FAR const char *help;      /* The help text */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int nxcamera_cmd_quit(FAR struct nxcamera_s *pcam, FAR char *parg);
+static int nxcamera_cmd_stream(FAR struct nxcamera_s *pcam, FAR char *parg);
+static int nxcamera_cmd_input(FAR struct nxcamera_s *pcam, FAR char *parg);
+static int nxcamera_cmd_output(FAR struct nxcamera_s *pcam, FAR char *parg);
+static int nxcamera_cmd_stop(FAR struct nxcamera_s *pcam, FAR char *parg);
+#ifdef CONFIG_NXCAMERA_INCLUDE_HELP
+static int nxcamera_cmd_help(FAR struct nxcamera_s *pcam, FAR char *parg);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct nxcamera_cmd_s g_nxcamera_cmds[] =
+{
+  {
+    "input",
+    "videodev",
+    nxcamera_cmd_input,
+    NXCAMERA_HELP_TEXT("Specify a preferred capture device")
+  },
+  {
+    "output",
+    "fbdev|filename",
+    nxcamera_cmd_output,
+    NXCAMERA_HELP_TEXT("Specify a display device or filename")
+  },
+#ifdef CONFIG_NXCAMERA_INCLUDE_HELP
+  {
+    "h",
+    "",
+    nxcamera_cmd_help,
+    NXCAMERA_HELP_TEXT("Display help for commands")
+  },
+  {
+    "help",
+    "",
+    nxcamera_cmd_help,
+    NXCAMERA_HELP_TEXT("Display help for commands")
+  },
+#endif
+  {
+    "stream",
+    "width height framerate format",
+    nxcamera_cmd_stream,
+    NXCAMERA_HELP_TEXT("Video stream test (format is fourcc)")
+  },
+  {
+    "stop",
+    "",
+    nxcamera_cmd_stop,
+    NXCAMERA_HELP_TEXT("Stop stream")
+  },
+  {
+    "q",
+    "",
+    nxcamera_cmd_quit,
+    NXCAMERA_HELP_TEXT("Exit NxCamera")
+  },
+  {
+    "quit",
+    "",
+    nxcamera_cmd_quit,
+    NXCAMERA_HELP_TEXT("Exit NxCamera")
+  }
+};
+
+static const int g_nxcamera_cmd_count = sizeof(g_nxcamera_cmds) /
+                                        sizeof(struct nxcamera_cmd_s);
+
+/****************************************************************************
+ * Name: nxcamera_cmd_stream
+ *
+ *   nxcamera_cmd_loop() play and record the raw data file using the nxcamera
+ *   context.
+ *
+ ****************************************************************************/
+
+static int nxcamera_cmd_stream(FAR struct nxcamera_s *pcam, FAR char *parg)
+{
+  uint16_t width = 0;
+  uint16_t height = 0;
+  uint32_t framerate = 0;
+  uint32_t format = 0;
+  int      ret;
+  char     cc[4] =
+    {
+      0
+    };
+
+  sscanf(parg, "%hd %hd %d %s", &width, &height, &framerate, cc);
+  format = v4l2_fourcc(cc[0], cc[1], cc[2], cc[3]);
+
+  /* Try to stream raw data with settings specified */
+
+  ret = nxcamera_stream(pcam, width, height, framerate, format);
+
+  /* nxcamera_stream returned values:
+   *
+   *   OK         Stream is being run
+   *   -EBUSY     The media device is busy
+   *   -ENOSYS    The video format is not unsupported
+   *   -ENODEV    No video device suitable to capture media
+   */
+
+  switch (ret)
+    {
+      case OK:
+        break;
+
+      case -ENODEV:
+        printf("No suitable Video Device found\n");
+        break;
+
+      case -EBUSY:
+        printf("Video device busy\n");
+        break;
+
+      case -ENOSYS:
+        printf("Unknown video format\n");
+        break;
+
+      default:
+        printf("Error stream test: %d\n", ret);
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: nxcamera_cmd_stop
+ *
+ *   nxcamera_cmd_stop() stops stream context.
+ *
+ ****************************************************************************/
+
+static int nxcamera_cmd_stop(FAR struct nxcamera_s *pcam, FAR char *parg)
+{
+  /* Stop the stream */
+
+  return nxcamera_stop(pcam);
+}
+
+/****************************************************************************
+ * Name: nxcamera_cmd_input
+ *
+ *   nxcamera_cmd_input() sets the preferred capture device for stream.
+ *
+ ****************************************************************************/
+
+static int nxcamera_cmd_input(FAR struct nxcamera_s *pcam, FAR char *parg)
+{
+  int  ret;
+  char path[PATH_MAX];
+
+  /* First try to open the file directly */
+
+  ret = nxcamera_setdevice(pcam, parg);
+  if (ret < 0)
+    {
+      /* Append the /dev path and try again */
+
+      snprintf(path, sizeof(path), "/dev/%s", parg);
+      ret = nxcamera_setdevice(pcam, path);
+    }
+
+  /* Test if the device file exists */
+
+  if (ret == -ENOENT)
+    {
+      /* Device doesn't exit.  Report error */
+
+      printf("Device %s not found\n", parg);
+    }
+  else if (ret == -ENODEV)
+    {
+      /* Test if is is an video device */
+
+      printf("Device %s is not an video device\n", parg);
+    }
+
+  /* Return error value */
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: nxcamera_cmd_output
+ *
+ *   nxcamera_cmd_device() sets the output device/file for display/save.
+ *
+ ****************************************************************************/
+
+static int nxcamera_cmd_output(FAR struct nxcamera_s *pcam, FAR char *parg)
+{
+  int      ret;
+  char     path[PATH_MAX];
+  FAR char *ext;
+  bool     isimage;
+
+  /* First try to open the device directly */
+
+  ret = nxcamera_setfb(pcam, parg);
+  if (ret < 0)
+    {
+      /* Append the /dev path and try again */
+
+      snprintf(path, sizeof(path), "/dev/%s", parg);
+      ret = nxcamera_setfb(pcam, path);
+    }
+
+  /* Device doesn't exist or is not a video device. Treat as file */
+
+  if (ret < 0)
+    {
+      if (ret == -ENODEV)
+        {
+          printf("Device %s is not an video device\n", parg);
+          return ret;
+        }
+
+      ext = strrchr(parg, '.');
+      if (ext && (ext != parg))
+        {
+          ext++;
+          isimage = strncmp(ext, "jpg", sizeof("jpg")) == 0 ||
+                    strncmp(ext, "jpeg", sizeof("jpeg")) == 0;
+        }
+
+      ret = nxcamera_setfile(pcam, parg, isimage);
+    }
+
+  if (ret < 0)
+    {
+      /* Create file error. Report error */
+
+      printf("Error outputting to %s\n", parg);
+    }
+
+  /* Output device or file set successfully */
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: nxcamera_cmd_quit
+ *
+ *   nxcamera_cmd_quit() terminates the application.
+ *
+ ****************************************************************************/
+
+static int nxcamera_cmd_quit(FAR struct nxcamera_s *pcam, FAR char *parg)
+{
+  /* Stop the stream if any */
+
+  return nxcamera_stop(pcam);
+}
+
+/****************************************************************************
+ * Name: nxcamera_cmd_help
+ *
+ *   nxcamera_cmd_help() displays the application's help information on
+ *   supported commands and command syntax.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NXCAMERA_INCLUDE_HELP
+static int nxcamera_cmd_help(FAR struct nxcamera_s *pcam, FAR char *parg)
+{
+  int len;
+  int maxlen = 0;
+  int x;
+  int c;
+
+  /* Calculate length of longest cmd + arghelp */
+
+  for (x = 0; x < g_nxcamera_cmd_count; x++)
+    {
+      len = strlen(g_nxcamera_cmds[x].cmd) +
+            strlen(g_nxcamera_cmds[x].arghelp);
+      if (len > maxlen)
+        {
+          maxlen = len;
+        }
+    }
+
+  printf("NxCamera commands\n================\n");
+  for (x = 0; x < g_nxcamera_cmd_count; x++)
+    {
+      /* Print the command and it's arguments */
+
+      printf("  %s %s", g_nxcamera_cmds[x].cmd, g_nxcamera_cmds[x].arghelp);
+
+      /* Calculate number of spaces to print before the help text */
+
+      len = maxlen - (strlen(g_nxcamera_cmds[x].cmd) +
+                      strlen(g_nxcamera_cmds[x].arghelp));
+      for (c = 0; c < len; c++)
+        {
+          printf(" ");
+        }
+
+      printf("  : %s\n", g_nxcamera_cmds[x].help);
+    }
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nxcamera
+ *
+ *   nxcamera reads in commands from the console using the readline
+ *   system add-in and impalements a command-line based media loop
+ *   tester that uses the NuttX video system to capture and then display
+ *   or save video from the lower video driver. Commands are provided for
+ *   setting width, height, framerate, pixel format and other video controls,
+ *   as well as for stopping the test.
+ *
+ ****************************************************************************/
+
+int main(int argc, FAR char *argv[])
+{
+  char                  buffer[CONFIG_NSH_LINELEN];
+  int                   len;
+  int                   x;
+  bool                  running = true;
+  FAR char              *cmd;
+  FAR char              *arg;
+  FAR struct nxcamera_s *pcam;
+
+  printf("NxCamera version " NXCAMERA_VER "\n");
+  printf("h for commands, q to exit\n");
+#ifndef CONFIG_LIBYUV
+  printf("Libyuv is not enabled, won't output to RGB framebuffer\n");
+#endif
+  printf("\n");
+
+  /* Initialize our NxCamera context */
+
+  pcam = nxcamera_create();
+  if (pcam == NULL)
+    {
+      printf("Error: Out of RAM\n");
+      return -ENOMEM;
+    }
+
+  /* Loop until the user exits */
+
+  while (running)
+    {
+      /* Print a prompt */
+
+      printf("nxcamera> ");
+      fflush(stdout);
+
+      /* Read a line from the terminal */
+
+      len = readline(buffer, sizeof(buffer), stdin, stdout);
+      if (len > 0)
+        {
+          buffer[len] = '\0';
+          if (strncmp(buffer, "!", 1) != 0)
+            {
+              /* nxcamera command */
+
+              if (buffer[len - 1] == '\n')
+                {
+                  buffer[len - 1] = '\0';
+                }
+
+              /* Parse the command from the argument */
+
+              cmd = strtok_r(buffer, " \n", &arg);
+              if (cmd == NULL)
+                {
+                  continue;
+                }
+
+              /* Find the command in our cmd array */
+
+              for (x = 0; x < g_nxcamera_cmd_count; x++)
+                {
+                  if (strcmp(cmd, g_nxcamera_cmds[x].cmd) == 0)
+                    {
+                      /* Command found.  Call it's handler if not NULL */
+
+                      if (g_nxcamera_cmds[x].pfunc != NULL)
+                        {
+                          g_nxcamera_cmds[x].pfunc(pcam, arg);
+                        }
+
+                      /* Test if it is a quit command */
+
+                      if (g_nxcamera_cmds[x].pfunc == nxcamera_cmd_quit)
+                        {
+                          running = false;
+                        }
+
+                      break;
+                    }
+                }
+            }
+          else
+            {
+#ifdef CONFIG_SYSTEM_SYSTEM
+              /* Transfer nuttx shell */
+
+              system(buffer + 1);
+#else
+              printf("%s: unknown nxcamera command\n", buffer);
+#endif
+            }
+        }
+    }
+
+  /* Release the NxCamera context */
+
+  nxcamera_release(pcam);
+
+  return OK;
+}