1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310
  311
  312
  313
  314
  315
  316
  317
  318
  319
  320
  321
  322
  323
  324
  325
  326
  327

media / gpu / test / video_test_helpers.h [blame]

// Copyright 2018 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_TEST_VIDEO_TEST_HELPERS_H_
#define MEDIA_GPU_TEST_VIDEO_TEST_HELPERS_H_

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/base/decoder_buffer.h"
#include "media/base/video_codecs.h"
#include "media/base/video_frame.h"
#include "media/base/video_frame_layout.h"
#include "media/base/video_types.h"
#include "media/gpu/test/raw_video.h"
#include "media/media_buildflags.h"
#include "media/parsers/ivf_parser.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"

#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
#include "media/parsers/h265_parser.h"
#endif

namespace media {
namespace test {

// Helper class allowing one thread to wait on a notification from another.
// If notifications come in faster than they are Wait()'d for, they are
// accumulated (so exactly as many Wait() calls will unblock as Notify() calls
// were made, regardless of order).
template <typename StateEnum>
class ClientStateNotification {
 public:
  ClientStateNotification();
  ~ClientStateNotification();

  // Used to notify a single waiter of a ClientState.
  void Notify(StateEnum state);
  // Used by waiters to wait for the next ClientState Notification.
  StateEnum Wait();

 private:
  base::Lock lock_;
  base::ConditionVariable cv_;
  base::queue<StateEnum> pending_states_for_notification_;
};

template <typename StateEnum>
ClientStateNotification<StateEnum>::ClientStateNotification() : cv_(&lock_) {}

template <typename StateEnum>
ClientStateNotification<StateEnum>::~ClientStateNotification() {}

template <typename StateEnum>
void ClientStateNotification<StateEnum>::Notify(StateEnum state) {
  base::AutoLock auto_lock(lock_);
  pending_states_for_notification_.push(state);
  cv_.Signal();
}

template <typename StateEnum>
StateEnum ClientStateNotification<StateEnum>::Wait() {
  base::AutoLock auto_lock(lock_);
  while (pending_states_for_notification_.empty())
    cv_.Wait();
  StateEnum ret = pending_states_for_notification_.front();
  pending_states_for_notification_.pop();
  return ret;
}

struct IvfFrame {
  IvfFrameHeader header;
  raw_ptr<uint8_t> data = nullptr;
};

// Read functions to fill IVF file header and IVF frame header from |data|.
// |data| must have sufficient length.
IvfFileHeader GetIvfFileHeader(base::span<const uint8_t> data);
IvfFrameHeader GetIvfFrameHeader(base::span<const uint8_t> data);

// The helper class to save data as ivf format.
class IvfWriter {
 public:
  IvfWriter(base::FilePath output_filepath);
  bool WriteFileHeader(VideoCodec codec,
                       const gfx::Size& resolution,
                       uint32_t frame_rate,
                       uint32_t num_frames);
  bool WriteFrame(uint32_t data_size, uint64_t timestamp, const uint8_t* data);

 private:
  base::File output_file_;
};

// Helper to extract (full) frames from a video |stream|.
class EncodedDataHelper {
 public:
  static std::unique_ptr<EncodedDataHelper> Create(
      base::span<const uint8_t> stream,
      VideoCodec codec);

  static bool HasConfigInfo(const uint8_t* data, size_t size, VideoCodec codec);

  virtual ~EncodedDataHelper();
  virtual scoped_refptr<DecoderBuffer> GetNextBuffer() = 0;

  virtual void Rewind();
  virtual bool ReachEndOfStream() const;

 protected:
  EncodedDataHelper(base::span<const uint8_t> stream, VideoCodec codec);

  std::string data_;
  const VideoCodec codec_;
  size_t next_pos_to_parse_ = 0;
};

// This class returns one by one the NALUs in |stream| via GetNextBuffer().
// |stream| must be in H.264 Annex B or H.265 Annex B formats.
class EncodedDataHelperH26x : public EncodedDataHelper {
 public:
  EncodedDataHelperH26x(base::span<const uint8_t> stream, VideoCodec codec);
  ~EncodedDataHelperH26x() override = default;

  static bool HasConfigInfo(const uint8_t* data, size_t size, VideoCodec codec);

  scoped_refptr<DecoderBuffer> GetNextBuffer() override;

 private:
  size_t GetBytesForNextNALU(size_t pos);
  bool IsNALHeader(const std::string& data, size_t pos);
  bool LookForSPS();
};

#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
// This class returns one by one the full frames (which can be composed of one
// or multiple NALUs) in |stream| via GetNextBuffer(). |stream| must be in H.265
// Annex B format (see www.itu.int/rec/T-REC-H.265).
// Note that this is an issue specific to testing (which this class serves),
// since in production there's always a container to give information about
// frame boundaries, hence the logic here.
class EncodedDataHelperH265 : public EncodedDataHelper {
 public:
  EncodedDataHelperH265(base::span<const uint8_t> stream, VideoCodec codec);
  ~EncodedDataHelperH265() override;

  scoped_refptr<DecoderBuffer> GetNextBuffer() override;
  bool ReachEndOfStream() const override;
  void Rewind() override;

 private:
  // This struct is needed because:
  // a) We need to keep both a pointer and an index to where a NALU starts (the
  //    pointer is for |data_| arithmetic, the index is for base::span ops.
  // b) H265NALUs don't provide NALU header size (it can be 3 or 4 bytes long),
  //    so a few pointer ops are needed to calculate the |size_with_header|.
  struct NALUMetadata {
    const uint8_t* start_pointer;
    size_t start_index;
    size_t header_size;
    size_t size_with_header;

    friend std::ostream& operator<<(std::ostream& os, const NALUMetadata& m) {
      return os << "start_index=" << m.start_index
                << ", header_size=" << m.header_size
                << ", size_with_header=" << m.size_with_header;
    }
  };

  scoped_refptr<DecoderBuffer> ReassembleNALUs(
      const std::vector<struct NALUMetadata>& nalus);

  std::unique_ptr<H265Parser> h265_parser_;
  std::vector<struct NALUMetadata> previous_nalus_;
  std::unique_ptr<H265SliceHeader> previous_slice_header_;
};
#endif  // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)

// This class returns one by one the IVF frames in |stream| via GetNextBuffer().
class EncodedDataHelperIVF : public EncodedDataHelper {
 public:
  EncodedDataHelperIVF(base::span<const uint8_t> stream, VideoCodec codec);
  ~EncodedDataHelperIVF() override = default;

  scoped_refptr<DecoderBuffer> GetNextBuffer() override;

 private:
  std::optional<IvfFrameHeader> GetNextIvfFrameHeader() const;
  std::optional<IvfFrame> ReadNextIvfFrame();
};

#if defined(ARCH_CPU_ARM_FAMILY)
// ARM performs CPU cache management with CPU cache line granularity. We thus
// need to ensure our buffers are CPU cache line-aligned (64 byte-aligned).
// Otherwise newer kernels will refuse to accept them, and on older kernels
// we'll be treating ourselves to random corruption.
// Moreover, some hardware codecs require 128-byte alignment for physical
// buffers.
constexpr size_t kPlatformBufferAlignment = 128;
#else
constexpr size_t kPlatformBufferAlignment = 8;
#endif

// Helper to align data and extract frames from raw video streams.
// GetNextFrame() returns VideoFrames with a specified |storage_type|. The
// VideoFrames are aligned by the specified |alignment| in the case of
// MojoSharedBuffer VideoFrame. On the other hand, GpuMemoryBuffer based
// VideoFrame is determined by the GpuMemoryBuffer allocation backend.
// GetNextFrame() returns valid frame if AtEndOfStream() returns false, i.e.,
// until GetNextFrame() is called |num_read_frames| times.
// |num_frames| is the number of frames contained in |stream|. |num_read_frames|
// can be larger than |num_frames|.
// If |reverse| is true , GetNextFrame() for a frame returns frames in a
// round-trip playback fashion (0, 1,.., |num_frames| - 2, |num_frames| - 1,
// |num_frames| - 1, |num_frames_| - 2,.., 1, 0, 0, 1,..) so that the content of
// returned frames is consecutive.
// If |reverse| is false, GetNextFrame() just loops the stream (0, 1,..,
// |num_frames| - 2, |num_frames| - 1, 0, 1,..), so the content of returned
// frames is not consecutive.
class AlignedDataHelper {
 public:
  AlignedDataHelper(const RawVideo* video,
                    uint32_t num_read_frames,
                    bool reverse,
                    const gfx::Size& aligned_coded_size,
                    const gfx::Size& natural_size,
                    uint32_t frame_rate,
                    VideoFrame::StorageType storage_type);
  ~AlignedDataHelper();

  // Compute and return the next frame to be sent to the encoder.
  scoped_refptr<VideoFrame> GetNextFrame();

  // Rewind to the position of the video stream.
  void Rewind();
  // Check whether we are at the start of the video stream.
  bool AtHeadOfStream() const;
  // Check whether we are at the end of the video stream.
  bool AtEndOfStream() const;
  // Change the timing between frames.
  void UpdateFrameRate(uint32_t frame_rate);

 private:
  struct VideoFrameData;
  enum class CreateFrameMode {
    kAllAtOnce,
    kOnDemand,
  };
  scoped_refptr<VideoFrame> CreateVideoFrameFromVideoFrameData(
      const VideoFrameData& video_frame_data,
      base::TimeDelta frame_timestamp) const;

  static VideoFrameData CreateVideoFrameData(
      VideoFrame::StorageType storage_type,
      const RawVideo::FrameData& src_frame,
      const VideoFrameLayout& src_layout,
      const VideoFrameLayout& dst_layout);

  const raw_ptr<const RawVideo> video_;
  // The number of frames in the given |stream|.
  const uint32_t num_frames_;
  // The number of frames to be read. It may be more than |num_frames_|.
  const uint32_t num_read_frames_;

  const bool reverse_;

  const CreateFrameMode create_frame_mode_;

  // The index of VideoFrame to be read next.
  uint32_t frame_index_ = 0;

  const VideoFrame::StorageType storage_type_;

  // The layout of VideoFrames returned by GetNextFrame().
  std::optional<VideoFrameLayout> layout_;
  const gfx::Rect visible_rect_;
  const gfx::Size natural_size_;

  base::TimeDelta time_stamp_interval_;
  base::TimeDelta elapsed_frame_time_;

  // The frame data returned by GetNextFrame().
  std::vector<VideoFrameData> video_frame_data_;
};

// Small helper class to extract video frames from raw data streams.
// However, the data wrapped by VideoFrame is not guaranteed to be aligned.
// This class doesn't change |video|, but cannot be mark it as constant because
// GetFrame() returns non const |data_| wrapped by the returned VideoFrame.
// If |reverse| is true , GetNextFrame() for a frame returns frames in a
// round-trip playback fashion (0, 1,.., |num_frames| - 2, |num_frames| - 1,
// |num_frames| - 1, |num_frames_| - 2,.., 1, 0, 0, 1,..).
// If |reverse| is false, GetNextFrame() just loops the stream (0, 1,..,
// |num_frames| - 2, |num_frames| - 1, 0, 1,..).
class RawDataHelper {
 public:
  RawDataHelper(const RawVideo* video, bool reverse);
  ~RawDataHelper();

  // Returns i-th VideoFrame in |video|. The returned frame doesn't own the
  // underlying video data.
  scoped_refptr<const VideoFrame> GetFrame(size_t index) const;

 private:
  // |video| and its associated data must outlive this class and VideoFrames
  // returned by GetFrame().
  const raw_ptr<const RawVideo> video_;

  const bool reverse_;
};
}  // namespace test
}  // namespace media

#endif  // MEDIA_GPU_TEST_VIDEO_TEST_HELPERS_H_