aboutsummaryrefslogtreecommitdiff
path: root/accel/v4l2_video_decode_accelerator.h
diff options
context:
space:
mode:
Diffstat (limited to 'accel/v4l2_video_decode_accelerator.h')
-rw-r--r--accel/v4l2_video_decode_accelerator.h497
1 files changed, 497 insertions, 0 deletions
diff --git a/accel/v4l2_video_decode_accelerator.h b/accel/v4l2_video_decode_accelerator.h
new file mode 100644
index 0000000..99076ed
--- /dev/null
+++ b/accel/v4l2_video_decode_accelerator.h
@@ -0,0 +1,497 @@
+// 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.
+//
+// This file contains an implementation of VideoDecodeAccelerator
+// that utilizes hardware video decoders, which expose Video4Linux 2 API
+// (http://linuxtv.org/downloads/v4l-dvb-apis/).
+// Note: ported from Chromium commit head: 85fdf90
+// Note: image processor is not ported.
+
+#ifndef MEDIA_GPU_V4L2_VIDEO_DECODE_ACCELERATOR_H_
+#define MEDIA_GPU_V4L2_VIDEO_DECODE_ACCELERATOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <list>
+#include <memory>
+#include <queue>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "picture.h"
+#include "size.h"
+#include "v4l2_device.h"
+#include "video_decode_accelerator.h"
+
+namespace media {
+
+// This class handles video accelerators directly through a V4L2 device exported
+// by the hardware blocks.
+//
+// The threading model of this class is driven by the fact that it needs to
+// interface two fundamentally different event queues -- the one Chromium
+// provides through MessageLoop, and the one driven by the V4L2 devices which
+// is waited on with epoll(). There are three threads involved in this class:
+//
+// * The child thread, which is the main GPU process thread which calls the
+// VideoDecodeAccelerator entry points. Calls from this thread
+// generally do not block (with the exception of Initialize() and Destroy()).
+// They post tasks to the decoder_thread_, which actually services the task
+// and calls back when complete through the
+// VideoDecodeAccelerator::Client interface.
+// * The decoder_thread_, owned by this class. It services API tasks, through
+// the *Task() routines, as well as V4L2 device events, through
+// ServiceDeviceTask(). Almost all state modification is done on this thread
+// (this doesn't include buffer (re)allocation sequence, see below).
+// * The device_poll_thread_, owned by this class. All it does is epoll() on
+// the V4L2 in DevicePollTask() and schedule a ServiceDeviceTask() on the
+// decoder_thread_ when something interesting happens.
+// TODO(sheu): replace this thread with an TYPE_IO decoder_thread_.
+//
+// Note that this class has (almost) no locks, apart from the pictures_assigned_
+// WaitableEvent. Everything (apart from buffer (re)allocation) is serviced on
+// the decoder_thread_, so there are no synchronization issues.
+// ... well, there are, but it's a matter of getting messages posted in the
+// right order, not fiddling with locks.
+// Buffer creation is a two-step process that is serviced partially on the
+// Child thread, because we need to wait for the client to provide textures
+// for the buffers we allocate. We cannot keep the decoder thread running while
+// the client allocates Pictures for us, because we need to REQBUFS first to get
+// the required number of output buffers from the device and that cannot be done
+// unless we free the previous set of buffers, leaving the decoding in a
+// inoperable state for the duration of the wait for Pictures. So to prevent
+// subtle races (esp. if we get Reset() in the meantime), we block the decoder
+// thread while we wait for AssignPictureBuffers from the client.
+//
+// V4L2VideoDecodeAccelerator may use image processor to convert the output.
+// There are three cases:
+// Flush: V4L2VDA should wait until image processor returns all processed
+// frames.
+// Reset: V4L2VDA doesn't need to wait for image processor. When image processor
+// returns an old frame, drop it.
+// Resolution change: V4L2VDA destroy image processor when destroying output
+// buffrers. We cannot drop any frame during resolution change. So V4L2VDA
+// should destroy output buffers after image processor returns all the frames.
+class V4L2VideoDecodeAccelerator
+ : public VideoDecodeAccelerator {
+ public:
+ V4L2VideoDecodeAccelerator(
+ const scoped_refptr<V4L2Device>& device);
+ ~V4L2VideoDecodeAccelerator() override;
+
+ // VideoDecodeAccelerator implementation.
+ // Note: Initialize() and Destroy() are synchronous.
+ bool Initialize(const Config& config, Client* client) override;
+ void Decode(const BitstreamBuffer& bitstream_buffer) override;
+ void AssignPictureBuffers(const std::vector<PictureBuffer>& buffers) override;
+ void ImportBufferForPicture(
+ int32_t picture_buffer_id,
+ VideoPixelFormat pixel_format,
+ const NativePixmapHandle& native_pixmap_handle) override;
+ void ReusePictureBuffer(int32_t picture_buffer_id) override;
+ void Flush() override;
+ void Reset() override;
+ void Destroy() override;
+ bool TryToSetupDecodeOnSeparateThread(
+ const base::WeakPtr<Client>& decode_client,
+ const scoped_refptr<base::SingleThreadTaskRunner>& decode_task_runner)
+ override;
+
+ static VideoDecodeAccelerator::SupportedProfiles GetSupportedProfiles();
+
+ private:
+ // These are rather subjectively tuned.
+ enum {
+ kInputBufferCount = 8,
+ // TODO(posciak): determine input buffer size based on level limits.
+ // See http://crbug.com/255116.
+ // Input bitstream buffer size for up to 1080p streams.
+ kInputBufferMaxSizeFor1080p = 1024 * 1024,
+ // Input bitstream buffer size for up to 4k streams.
+ kInputBufferMaxSizeFor4k = 4 * kInputBufferMaxSizeFor1080p,
+ // This is originally from media/base/limits.h in Chromium.
+ kMaxVideoFrames = 4,
+ // Number of output buffers to use for each VDA stage above what's required
+ // by the decoder (e.g. DPB size, in H264). We need
+ // limits::kMaxVideoFrames to fill up the GpuVideoDecode pipeline,
+ // and +1 for a frame in transit.
+ kDpbOutputBufferExtraCount = kMaxVideoFrames + 1,
+ // Number of extra output buffers if image processor is used.
+ kDpbOutputBufferExtraCountForImageProcessor = 1,
+ };
+
+ // Internal state of the decoder.
+ enum State {
+ kUninitialized, // Initialize() not yet called.
+ kInitialized, // Initialize() returned true; ready to start decoding.
+ kDecoding, // DecodeBufferInitial() successful; decoding frames.
+ kResetting, // Presently resetting.
+ // Performing resolution change and waiting for image processor to return
+ // all frames.
+ kChangingResolution,
+ // Requested new PictureBuffers via ProvidePictureBuffers(), awaiting
+ // AssignPictureBuffers().
+ kAwaitingPictureBuffers,
+ kError, // Error in kDecoding state.
+ };
+
+ enum OutputRecordState {
+ kFree, // Ready to be queued to the device.
+ kAtDevice, // Held by device.
+ kAtProcessor, // Held by image processor.
+ kAtClient, // Held by client of V4L2VideoDecodeAccelerator.
+ };
+
+ enum BufferId {
+ kFlushBufferId = -2 // Buffer id for flush buffer, queued by FlushTask().
+ };
+
+ // Auto-destruction reference for BitstreamBuffer, for message-passing from
+ // Decode() to DecodeTask().
+ struct BitstreamBufferRef;
+
+ // Record for decoded pictures that can be sent to PictureReady.
+ struct PictureRecord {
+ PictureRecord(bool cleared, const Picture& picture);
+ ~PictureRecord();
+ bool cleared; // Whether the texture is cleared and safe to render from.
+ Picture picture; // The decoded picture.
+ };
+
+ // Record for input buffers.
+ struct InputRecord {
+ bool at_device = false; // held by device.
+ std::unique_ptr<BitstreamBufferRef> bitstream_buffer;
+ };
+
+ // Record for output buffers.
+ struct OutputRecord {
+ OutputRecord();
+ OutputRecord(OutputRecord&&) = default;
+ ~OutputRecord();
+ OutputRecordState state;
+ int32_t picture_id; // picture buffer id as returned to PictureReady().
+ bool cleared; // Whether the texture is cleared and safe to render
+ // from. See TextureManager for details.
+ // Output fds of the decoded frame.
+ std::vector<base::ScopedFD> output_fds;
+ // offsets of each decoded frame from each fd in |output_fds|.
+ std::vector<size_t> offsets;
+ };
+
+ //
+ // Decoding tasks, to be run on decode_thread_.
+ //
+
+ // Task to finish initialization on decoder_thread_.
+ void InitializeTask();
+
+ // Enqueue a BitstreamBuffer to decode. This will enqueue a buffer to the
+ // decoder_input_queue_, then queue a DecodeBufferTask() to actually decode
+ // the buffer.
+ void DecodeTask(const BitstreamBuffer& bitstream_buffer);
+
+ // Decode from the buffers queued in decoder_input_queue_. Calls
+ // DecodeBufferInitial() or DecodeBufferContinue() as appropriate.
+ void DecodeBufferTask();
+ // Schedule another DecodeBufferTask() if we're behind.
+ void ScheduleDecodeBufferTaskIfNeeded();
+
+ // Return true if we should continue to schedule DecodeBufferTask()s after
+ // completion.
+ bool DecodeBufferInitial();
+ bool DecodeBufferContinue();
+
+ // Flush data for one decoded frame.
+ bool TrySubmitInputFrame();
+
+ // Allocate V4L2 buffers and assign them to |buffers| provided by the client
+ // via AssignPictureBuffers() on decoder thread.
+ void AssignPictureBuffersTask(const std::vector<PictureBuffer>& buffers);
+
+ // Use buffer backed by dmabuf file descriptors in |dmabuf_fds| for the
+ // OutputRecord associated with |picture_buffer_id|, taking ownership of the
+ // file descriptors.
+ void ImportBufferForPictureTask(int32_t picture_buffer_id,
+ std::vector<size_t> offsets,
+ std::vector<base::ScopedFD> dmabuf_fds);
+
+ // Service I/O on the V4L2 devices. This task should only be scheduled from
+ // DevicePollTask(). If |event_pending| is true, one or more events
+ // on file descriptor are pending.
+ void ServiceDeviceTask(bool event_pending);
+ // Handle the various device queues.
+ void Enqueue();
+ void Dequeue();
+ // Dequeue one input buffer. Return true if success.
+ bool DequeueInputBuffer();
+ // Dequeue one output buffer. Return true if success.
+ bool DequeueOutputBuffer();
+
+ // Return true if there is a resolution change event pending.
+ bool DequeueResolutionChangeEvent();
+
+ // Enqueue a buffer on the corresponding queue.
+ bool EnqueueInputRecord();
+ bool EnqueueOutputRecord();
+
+ // Process a ReusePictureBuffer() API call. The API call create an EGLSync
+ // object on the main (GPU process) thread; we will record this object so we
+ // can wait on it before reusing the buffer.
+ void ReusePictureBufferTask(int32_t picture_buffer_id);
+
+ // Flush() task. Child thread should not submit any more buffers until it
+ // receives the NotifyFlushDone callback. This task will schedule an empty
+ // BitstreamBufferRef (with input_id == kFlushBufferId) to perform the flush.
+ void FlushTask();
+ // Notify the client of a flush completion, if required. This should be
+ // called any time a relevant queue could potentially be emptied: see
+ // function definition.
+ void NotifyFlushDoneIfNeeded();
+ // Returns true if VIDIOC_DECODER_CMD is supported.
+ bool IsDecoderCmdSupported();
+ // Send V4L2_DEC_CMD_START to the driver. Return true if success.
+ bool SendDecoderCmdStop();
+
+ // Reset() task. Drop all input buffers. If V4L2VDA is not doing resolution
+ // change or waiting picture buffers, call FinishReset.
+ void ResetTask();
+ // This will schedule a ResetDoneTask() that will send the NotifyResetDone
+ // callback, then set the decoder state to kResetting so that all intervening
+ // tasks will drain.
+ void FinishReset();
+ void ResetDoneTask();
+
+ // Device destruction task.
+ void DestroyTask();
+
+ // Start |device_poll_thread_|.
+ bool StartDevicePoll();
+
+ // Stop |device_poll_thread_|.
+ bool StopDevicePoll();
+
+ bool StopInputStream();
+ bool StopOutputStream();
+
+ void StartResolutionChange();
+ void FinishResolutionChange();
+
+ // Try to get output format and visible size, detected after parsing the
+ // beginning of the stream. Sets |again| to true if more parsing is needed.
+ // |visible_size| could be nullptr and ignored.
+ bool GetFormatInfo(struct v4l2_format* format,
+ Size* visible_size,
+ bool* again);
+ // Create output buffers for the given |format| and |visible_size|.
+ bool CreateBuffersForFormat(const struct v4l2_format& format,
+ const Size& visible_size);
+
+ // Try to get |visible_size|. Return visible size, or, if querying it is not
+ // supported or produces invalid size, return |coded_size| instead.
+ Size GetVisibleSize(const Size& coded_size);
+
+ //
+ // Device tasks, to be run on device_poll_thread_.
+ //
+
+ // The device task.
+ void DevicePollTask(bool poll_device);
+
+ //
+ // Safe from any thread.
+ //
+
+ // Error notification (using PostTask() to child thread, if necessary).
+ void NotifyError(Error error);
+
+ // Set the decoder_state_ to kError and notify the client (if necessary).
+ void SetErrorState(Error error);
+
+ //
+ // Other utility functions. Called on decoder_thread_, unless
+ // decoder_thread_ is not yet started, in which case the child thread can call
+ // these (e.g. in Initialize() or Destroy()).
+ //
+
+ // Create the buffers we need.
+ bool CreateInputBuffers();
+ bool CreateOutputBuffers();
+
+ // Destroy buffers.
+ void DestroyInputBuffers();
+ // In contrast to DestroyInputBuffers, which is called only on destruction,
+ // we call DestroyOutputBuffers also during playback, on resolution change.
+ // Even if anything fails along the way, we still want to go on and clean
+ // up as much as possible, so return false if this happens, so that the
+ // caller can error out on resolution change.
+ bool DestroyOutputBuffers();
+
+ // Set input and output formats before starting decode.
+ bool SetupFormats();
+
+ //
+ // Methods run on child thread.
+ //
+
+ // Send decoded pictures to PictureReady.
+ void SendPictureReady();
+
+ // Callback that indicates a picture has been cleared.
+ void PictureCleared();
+
+ // Our original calling task runner for the child thread.
+ scoped_refptr<base::SingleThreadTaskRunner> child_task_runner_;
+
+ // Task runner Decode() and PictureReady() run on.
+ scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner_;
+
+ // WeakPtr<> pointing to |this| for use in posting tasks from the decoder or
+ // 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 decoder or device thread should use base::Unretained(this),
+ // and tasks posted the other way should use |weak_this_|.
+ base::WeakPtr<V4L2VideoDecodeAccelerator> weak_this_;
+
+ // To expose client callbacks from VideoDecodeAccelerator.
+ // NOTE: all calls to these objects *MUST* be executed on
+ // child_task_runner_.
+ std::unique_ptr<base::WeakPtrFactory<Client>> client_ptr_factory_;
+ base::WeakPtr<Client> client_;
+ // Callbacks to |decode_client_| must be executed on |decode_task_runner_|.
+ base::WeakPtr<Client> decode_client_;
+
+ //
+ // Decoder state, owned and operated by decoder_thread_.
+ // Before decoder_thread_ has started, the decoder state is managed by
+ // the child (main) thread. After decoder_thread_ has started, the decoder
+ // thread should be the only one managing these.
+ //
+
+ // This thread services tasks posted from the VDA API entry points by the
+ // child thread and device service callbacks posted from the device thread.
+ base::Thread decoder_thread_;
+ // Decoder state machine state.
+ State decoder_state_;
+
+ Config::OutputMode output_mode_;
+
+ // BitstreamBuffer we're presently reading.
+ std::unique_ptr<BitstreamBufferRef> decoder_current_bitstream_buffer_;
+ // The V4L2Device this class is operating upon.
+ scoped_refptr<V4L2Device> device_;
+ // FlushTask() and ResetTask() should not affect buffers that have been
+ // queued afterwards. For flushing or resetting the pipeline then, we will
+ // delay these buffers until after the flush or reset completes.
+ int decoder_delay_bitstream_buffer_id_;
+ // We track the number of buffer decode tasks we have scheduled, since each
+ // task execution should complete one buffer. If we fall behind (due to
+ // resource backpressure, etc.), we'll have to schedule more to catch up.
+ int decoder_decode_buffer_tasks_scheduled_;
+ // Picture buffers held by the client.
+ int decoder_frames_at_client_;
+
+ // Are we flushing?
+ bool decoder_flushing_;
+ // True if VIDIOC_DECODER_CMD is supported.
+ bool decoder_cmd_supported_;
+ // True if flushing is waiting for last output buffer. After
+ // VIDIOC_DECODER_CMD is sent to the driver, this flag will be set to true to
+ // wait for the last output buffer. When this flag is true, flush done will
+ // not be sent. After an output buffer that has the flag V4L2_BUF_FLAG_LAST is
+ // received, this is set to false.
+ bool flush_awaiting_last_output_buffer_;
+
+ // Got a reset request while we were performing resolution change or waiting
+ // picture buffers.
+ bool reset_pending_;
+ // Input queue for decoder_thread_: BitstreamBuffers in.
+ std::queue<std::unique_ptr<BitstreamBufferRef>> decoder_input_queue_;
+
+ //
+ // Hardware state and associated queues. Since decoder_thread_ services
+ // the hardware, decoder_thread_ owns these too.
+ // output_buffer_map_, free_output_buffers_ and output_planes_count_ are an
+ // exception during the buffer (re)allocation sequence, when the
+ // decoder_thread_ is blocked briefly while the Child thread manipulates
+ // them.
+ //
+
+ // Completed decode buffers.
+ std::queue<int> input_ready_queue_;
+
+ // Input buffer state.
+ bool input_streamon_;
+ // Input buffers enqueued to device.
+ int input_buffer_queued_count_;
+ // Input buffers ready to use, as a LIFO since we don't care about ordering.
+ std::vector<int> free_input_buffers_;
+ // Mapping of int index to input buffer record.
+ std::vector<InputRecord> input_buffer_map_;
+ // The size of input buffer that bitstream buffer can be copied.
+ size_t input_buffer_size_;
+
+ // Output buffer state.
+ bool output_streamon_;
+ // Output buffers enqueued to device.
+ int output_buffer_queued_count_;
+ // Output buffers ready to use, as a FIFO since we want oldest-first to hide
+ // synchronization latency with GL.
+ std::list<int> free_output_buffers_;
+ // Mapping of int index to output buffer record.
+ std::vector<OutputRecord> output_buffer_map_;
+ // Required size of DPB for decoding.
+ int output_dpb_size_;
+
+ // Number of planes (i.e. separate memory buffers) for output.
+ size_t output_planes_count_;
+
+ // Pictures that are ready but not sent to PictureReady yet.
+ std::queue<PictureRecord> pending_picture_ready_;
+
+ // The number of pictures that are sent to PictureReady and will be cleared.
+ int picture_clearing_count_;
+
+ // Output picture coded size.
+ Size coded_size_;
+
+ // Output picture visible size.
+ Size visible_size_;
+
+ //
+ // The device polling thread handles notifications of V4L2 device changes.
+ //
+
+ // The thread.
+ base::Thread device_poll_thread_;
+
+ //
+ // Other state, held by the child (main) thread.
+ //
+
+ // The codec we'll be decoding for.
+ VideoCodecProfile video_profile_;
+ // Chosen input format for video_profile_.
+ uint32_t input_format_fourcc_;
+ // Chosen output format.
+ uint32_t output_format_fourcc_;
+
+ // Input format V4L2 fourccs this class supports.
+ static const uint32_t supported_input_fourccs_[];
+
+ // The WeakPtrFactory for |weak_this_|.
+ base::WeakPtrFactory<V4L2VideoDecodeAccelerator> weak_this_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(V4L2VideoDecodeAccelerator);
+};
+
+} // namespace media
+
+#endif // MEDIA_GPU_V4L2_VIDEO_DECODE_ACCELERATOR_H_