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_