// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MEDIA_GPU_V4L2_IMAGE_PROCESSOR_H_
#define MEDIA_GPU_V4L2_IMAGE_PROCESSOR_H_

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <queue>
#include <vector>

#include "base/macros.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread.h"
#include "media/base/video_frame.h"
#include "media/gpu/media_gpu_export.h"
#include "media/gpu/v4l2_device.h"

namespace media {

// Handles image processing accelerators that expose a V4L2 memory-to-memory
// interface. The threading model of this class is the same as for other V4L2
// hardware accelerators (see V4L2VideoDecodeAccelerator) for more details.
class MEDIA_GPU_EXPORT V4L2ImageProcessor {
 public:
  explicit V4L2ImageProcessor(const scoped_refptr<V4L2Device>& device);
  virtual ~V4L2ImageProcessor();

  // Initializes the processor to convert from |input_format| to |output_format|
  // and/or scale from |input_visible_size| to |output_visible_size|.
  // Request the input buffers to be of at least |input_allocated_size| and the
  // output buffers to be of at least |output_allocated_size|. The number of
  // input buffers and output buffers will be |num_buffers|. Provided |error_cb|
  // will be called if an error occurs. Return true if the requested
  // configuration is supported.
  bool Initialize(VideoPixelFormat input_format,
                  VideoPixelFormat output_format,
                  v4l2_memory input_memory_type,
                  gfx::Size input_visible_size,
                  gfx::Size input_allocated_size,
                  gfx::Size output_visible_size,
                  gfx::Size output_allocated_size,
                  int num_buffers,
                  const base::Closure& error_cb);

  // Returns a vector of dmabuf file descriptors, exported for V4L2 output
  // buffer with |index|. The size of vector will be the number of planes of the
  // buffer. Return an empty vector on failure.
  std::vector<base::ScopedFD> GetDmabufsForOutputBuffer(
      int output_buffer_index);

  // Returns a vector of supported input formats in fourcc. This can be called
  // before Initialize.
  std::vector<uint32_t> GetSupportedInputFormats();

  // Returns a vector of supported output formats in fourcc. This can be called
  // before Initialize.
  std::vector<uint32_t> GetSupportedOutputFormats();

  // Gets the output allocated size and number of planes given |pixelformat|
  // fourcc and visible size |size|. Return true if success. The adjusted coded
  // size will be stored in |size| and the number of planes will be stored in
  // |num_planes|. This can be called before Initialize.
  bool TryOutputFormat(uint32_t pixelformat,
                       gfx::Size* size,
                       size_t* num_planes);

  // Returns input allocated size required by the processor to be fed with.
  gfx::Size input_allocated_size() const { return input_allocated_size_; }

  // Returns output allocated size required by the processor.
  gfx::Size output_allocated_size() const { return output_allocated_size_; }

  // Callback to be used to return the index of a processed image to the
  // client. After the client is done with the frame, call Process with the
  // index to return the output buffer to the image processor.
  typedef base::Callback<void(int output_buffer_index)> FrameReadyCB;

  // Called by client to process |frame|. The resulting processed frame will be
  // stored in |output_buffer_index| output buffer and notified via |cb|. The
  // processor will drop all its references to |frame| after it finishes
  // accessing it.
  void Process(const scoped_refptr<VideoFrame>& frame,
               int output_buffer_index,
               const FrameReadyCB& cb);

  // Stop all processing and clean up. After this method returns no more
  // callbacks will be invoked.  Deletes |this| unconditionally, so make sure
  // to drop all pointers to it!
  void Destroy();

 private:
  // Record for input buffers.
  struct InputRecord {
    InputRecord();
    ~InputRecord();
    scoped_refptr<VideoFrame> frame;
    bool at_device;
  };

  // Record for output buffers.
  struct OutputRecord {
    OutputRecord();
    ~OutputRecord();
    bool at_device;
  };

  // Job record. Jobs are processed in a FIFO order. This is separate from
  // InputRecord, because an InputRecord may be returned before we dequeue
  // the corresponding output buffer. The processed frame will be stored in
  // |output_buffer_index| output buffer.
  struct JobRecord {
    JobRecord();
    ~JobRecord();
    scoped_refptr<VideoFrame> frame;
    int output_buffer_index;
    FrameReadyCB ready_cb;
  };

  void EnqueueInput();
  void EnqueueOutput(int index);
  void Dequeue();
  bool EnqueueInputRecord();
  bool EnqueueOutputRecord(int index);
  bool CreateInputBuffers();
  bool CreateOutputBuffers();
  void DestroyInputBuffers();
  void DestroyOutputBuffers();

  void NotifyError();
  void NotifyErrorOnChildThread(const base::Closure& error_cb);
  void DestroyTask();

  void ProcessTask(std::unique_ptr<JobRecord> job_record);
  void ServiceDeviceTask();

  // Attempt to start/stop device_poll_thread_.
  bool StartDevicePoll();
  bool StopDevicePoll();

  // Ran on device_poll_thread_ to wait for device events.
  void DevicePollTask(bool poll_device);

  // A processed frame is ready.
  void FrameReady(const FrameReadyCB& cb, int output_buffer_index);

  // Size and format-related members remain constant after initialization.
  // The visible/allocated sizes of the input frame.
  gfx::Size input_visible_size_;
  gfx::Size input_allocated_size_;

  // The visible/allocated sizes of the destination frame.
  gfx::Size output_visible_size_;
  gfx::Size output_allocated_size_;

  VideoPixelFormat input_format_;
  VideoPixelFormat output_format_;
  v4l2_memory input_memory_type_;
  uint32_t input_format_fourcc_;
  uint32_t output_format_fourcc_;

  size_t input_planes_count_;
  size_t output_planes_count_;

  // Our original calling task runner for the child thread.
  const scoped_refptr<base::SingleThreadTaskRunner> child_task_runner_;

  // V4L2 device in use.
  scoped_refptr<V4L2Device> device_;

  // Thread to communicate with the device on.
  base::Thread device_thread_;
  // Thread used to poll the V4L2 for events only.
  base::Thread device_poll_thread_;

  // All the below members are to be accessed from device_thread_ only
  // (if it's running).
  std::queue<linked_ptr<JobRecord>> input_queue_;
  std::queue<linked_ptr<JobRecord>> running_jobs_;

  // Input queue state.
  bool input_streamon_;
  // Number of input buffers enqueued to the device.
  int input_buffer_queued_count_;
  // Input buffers ready to use; LIFO since we don't care about ordering.
  std::vector<int> free_input_buffers_;
  // Mapping of int index to an input buffer record.
  std::vector<InputRecord> input_buffer_map_;

  // Output queue state.
  bool output_streamon_;
  // Number of output buffers enqueued to the device.
  int output_buffer_queued_count_;
  // Mapping of int index to an output buffer record.
  std::vector<OutputRecord> output_buffer_map_;
  // The number of input or output buffers.
  int num_buffers_;

  // Error callback to the client.
  base::Closure error_cb_;

  // WeakPtr<> pointing to |this| for use in posting tasks from the device
  // worker threads back to the child thread.  Because the worker threads
  // are members of this class, any task running on those threads is guaranteed
  // that this object is still alive.  As a result, tasks posted from the child
  // thread to the device thread should use base::Unretained(this),
  // and tasks posted the other way should use |weak_this_|.
  base::WeakPtr<V4L2ImageProcessor> weak_this_;

  // Weak factory for producing weak pointers on the child thread.
  base::WeakPtrFactory<V4L2ImageProcessor> weak_this_factory_;

  DISALLOW_COPY_AND_ASSIGN(V4L2ImageProcessor);
};

}  // namespace media

#endif  // MEDIA_GPU_V4L2_IMAGE_PROCESSOR_H_
