chromium/media/gpu/android/ndk_media_codec_wrapper.h

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

#ifndef MEDIA_GPU_ANDROID_NDK_MEDIA_CODEC_WRAPPER_H_
#define MEDIA_GPU_ANDROID_NDK_MEDIA_CODEC_WRAPPER_H_

#include <media/NdkMediaCodec.h>

#include <memory>
#include <string_view>

#include "base/android/requires_api.h"
#include "base/containers/circular_deque.h"
#include "base/task/sequenced_task_runner.h"
#include "base/thread_annotations.h"
#include "media/gpu/media_gpu_export.h"

// For use with REQUIRES_ANDROID_API() and __builtin_available().
// We need at least Android P for AMediaCodec_getInputFormat(), but in
// Android P we have issues with CFI and dynamic linker on arm64. However
// GetSupportedProfiles() needs Q+, so just limit to Q.
#define NDK_MEDIA_CODEC_MIN_API 29

namespace media {

struct AMediaCodecDeleter {
  inline void operator()(AMediaCodec* ptr) const {
    if (ptr) {
      AMediaCodec_delete(ptr);
    }
  }
};

// A wrapper class which manages async callbacks from an AMediaCodec, as
// well as queues of available input/output buffers.
class REQUIRES_ANDROID_API(NDK_MEDIA_CODEC_MIN_API)
    MEDIA_GPU_EXPORT NdkMediaCodecWrapper {
 public:
  class Client {
   public:
    // Called when a new input buffer is made available.
    // Retrieve the buffer using Inputs_TakeFront().
    virtual void OnInputAvailable() = 0;

    // Called when a new output buffer is made available.
    // Retrieve the buffer using Outputs_TakeFront() or Outputs_PeekFront().
    virtual void OnOutputAvailable() = 0;

    // Called when there was an asynchronous encoding error.
    virtual void OnError(media_status_t error) = 0;
  };

  using BufferIndex = int32_t;

  // Info about output buffers currently pending in media codec.
  struct OutputInfo {
    BufferIndex buffer_index;
    AMediaCodecBufferInfo info;
  };

  // Creates a MediaCodecWrapper, returning nullptr on failure.
  // `client` must outlive the returned MediaCodecWrapper.
  // The `runner` sequence is used to invoke all of `client`'s async methods.
  static std::unique_ptr<NdkMediaCodecWrapper> CreateByCodecName(
      std::string_view codec_name,
      Client* client,
      scoped_refptr<base::SequencedTaskRunner> runner);
  static std::unique_ptr<NdkMediaCodecWrapper> CreateByMimeType(
      std::string_view mime_type,
      Client* client,
      scoped_refptr<base::SequencedTaskRunner> runner);

  NdkMediaCodecWrapper(const NdkMediaCodecWrapper&) = delete;
  NdkMediaCodecWrapper& operator=(const NdkMediaCodecWrapper&) = delete;

  ~NdkMediaCodecWrapper();

  // Starts or stops the underlying `media_codec_` and its async callbacks.
  // The async callbacks will post to `client_` on `task_runner_`.
  // Note: Before calling Start(), `media_codec_` should have already been
  //      configured using AMediaCodec_configure() and the codec() accessor.
  // Note: Stop() must be called before calling Start() again.
  media_status_t Start();
  void Stop();

  // Returns whether there are any available input/output buffers.
  bool HasInput();
  bool HasOutput();

  // Returns the first available input/output buffer and removes it from the
  // queue of available buffers.
  [[nodiscard]] BufferIndex TakeInput();
  [[nodiscard]] OutputInfo TakeOutput();

  // Returns the first available output buffer without removing it form the
  // available buffer queue.
  OutputInfo PeekOutput();

  // Returns the underlying codec directly for use with AMediaCodec_* functions.
  // Note: Do not call AMediaCodec_{start|stop}() directly. Instead, use the
  //      provided Start()/Stop() methods, which also handle setting up and
  //      tearing down the underlying async callbacks.
  AMediaCodec* codec() { return media_codec_.get(); }

 private:
  friend class NdkMediaCodecWrapperTest;

  using MediaCodecPtr = std::unique_ptr<AMediaCodec, AMediaCodecDeleter>;

  NdkMediaCodecWrapper(MediaCodecPtr codec,
                       Client* client,
                       scoped_refptr<base::SequencedTaskRunner> runner);

  // Called by MediaCodec when an input buffer becomes available.
  static void OnAsyncInputAvailable(AMediaCodec* codec,
                                    void* userdata,
                                    int32_t index);
  void OnInputAvailable(int32_t index);

  // Called by MediaCodec when an output buffer becomes available.
  static void OnAsyncOutputAvailable(AMediaCodec* codec,
                                     void* userdata,
                                     int32_t index,
                                     AMediaCodecBufferInfo* bufferInfo);
  void OnOutputAvailable(int32_t index, AMediaCodecBufferInfo bufferInfo);

  // Called by MediaCodec when the output format has changed.
  static void OnAsyncFormatChanged(AMediaCodec* codec,
                                   void* userdata,
                                   AMediaFormat* format) {}

  // Called when the MediaCodec encountered an error.
  static void OnAsyncError(AMediaCodec* codec,
                           void* userdata,
                           media_status_t error,
                           int32_t actionCode,
                           const char* detail);
  void OnError(media_status_t error);

  SEQUENCE_CHECKER(sequence_checker_);

  // Only used for error checking the start/stop state.
  bool started_ = false;

  // A runner all for callbacks and externals calls to public methods.
  scoped_refptr<base::SequencedTaskRunner> task_runner_;

  MediaCodecPtr media_codec_;

  // Outlives `this`, as it owns `this`.
  const raw_ptr<Client> client_;

  // Indices of input buffers currently pending in media codec.
  base::circular_deque<BufferIndex> input_buffers_
      GUARDED_BY_CONTEXT(sequence_checker_);

  base::circular_deque<OutputInfo> output_buffers_
      GUARDED_BY_CONTEXT(sequence_checker_);

  // Declared last to ensure that all weak pointers are invalidated before
  // other destructors run.
  base::WeakPtr<NdkMediaCodecWrapper> weak_this_;
  base::WeakPtrFactory<NdkMediaCodecWrapper> weak_factory_{this};
};

}  // namespace media

#endif  // MEDIA_GPU_ANDROID_NDK_MEDIA_CODEC_WRAPPER_H_