1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //#define LOG_NDEBUG 0
6 #define LOG_TAG "VideoFramePool"
7
8 #include <v4l2_codec2/components/VideoFramePool.h>
9
10 #include <stdint.h>
11 #include <memory>
12
13 #include <C2BlockInternal.h>
14 #include <bufferpool/BufferPoolTypes.h>
15
16 #include <android/hardware/graphics/common/1.0/types.h>
17 #include <base/bind.h>
18 #include <base/memory/ptr_util.h>
19 #include <base/time/time.h>
20 #include <log/log.h>
21
22 #include <v4l2_codec2/common/VideoTypes.h>
23 #include <v4l2_codec2/plugin_store/DmabufHelpers.h>
24 #include <v4l2_codec2/plugin_store/V4L2AllocatorId.h>
25
26 using android::hardware::graphics::common::V1_0::BufferUsage;
27 using android::hardware::media::bufferpool::BufferPoolData;
28
29 namespace android {
30
31 // static
getBufferIdFromGraphicBlock(C2BlockPool & blockPool,const C2Block2D & block)32 std::optional<uint32_t> VideoFramePool::getBufferIdFromGraphicBlock(C2BlockPool& blockPool,
33 const C2Block2D& block) {
34 ALOGV("%s() blockPool.getAllocatorId() = %u", __func__, blockPool.getAllocatorId());
35
36 switch (blockPool.getAllocatorId()) {
37 case V4L2AllocatorId::SECURE_GRAPHIC:
38 FALLTHROUGH;
39 case C2PlatformAllocatorStore::BUFFERQUEUE: {
40 auto dmabufId = android::getDmabufId(block.handle()->data[0]);
41 if (!dmabufId) {
42 return std::nullopt;
43 }
44 return dmabufId.value();
45 }
46 case C2PlatformAllocatorStore::GRALLOC:
47 FALLTHROUGH;
48 case V4L2AllocatorId::SECURE_LINEAR: {
49 std::shared_ptr<_C2BlockPoolData> blockPoolData =
50 _C2BlockFactory::GetGraphicBlockPoolData(block);
51 if (blockPoolData->getType() != _C2BlockPoolData::TYPE_BUFFERPOOL) {
52 ALOGE("Obtained C2GraphicBlock is not bufferpool-backed.");
53 return std::nullopt;
54 }
55 std::shared_ptr<BufferPoolData> bpData;
56 if (!_C2BlockFactory::GetBufferPoolData(blockPoolData, &bpData) || !bpData) {
57 ALOGE("BufferPoolData unavailable in block.");
58 return std::nullopt;
59 }
60 return bpData->mId;
61 }
62 }
63
64 ALOGE("%s(): unknown allocator ID: %u", __func__, blockPool.getAllocatorId());
65 return std::nullopt;
66 }
67
68 // static
Create(std::shared_ptr<C2BlockPool> blockPool,const size_t numBuffers,const ui::Size & size,HalPixelFormat pixelFormat,bool isSecure,scoped_refptr<::base::SequencedTaskRunner> taskRunner)69 std::unique_ptr<VideoFramePool> VideoFramePool::Create(
70 std::shared_ptr<C2BlockPool> blockPool, const size_t numBuffers, const ui::Size& size,
71 HalPixelFormat pixelFormat, bool isSecure,
72 scoped_refptr<::base::SequencedTaskRunner> taskRunner) {
73 ALOG_ASSERT(blockPool != nullptr);
74
75 uint64_t usage = static_cast<uint64_t>(BufferUsage::VIDEO_DECODER);
76 if (isSecure) {
77 usage |= C2MemoryUsage::READ_PROTECTED;
78 } else if (blockPool->getAllocatorId() == C2PlatformAllocatorStore::GRALLOC) {
79 // CPU access to buffers is only required in byte buffer mode.
80 usage |= C2MemoryUsage::CPU_READ;
81 }
82 const C2MemoryUsage memoryUsage(usage);
83
84 std::unique_ptr<VideoFramePool> pool =
85 ::base::WrapUnique(new VideoFramePool(std::move(blockPool), numBuffers, size,
86 pixelFormat, memoryUsage, std::move(taskRunner)));
87 if (!pool->initialize()) return nullptr;
88 return pool;
89 }
90
VideoFramePool(std::shared_ptr<C2BlockPool> blockPool,const size_t maxBufferCount,const ui::Size & size,HalPixelFormat pixelFormat,C2MemoryUsage memoryUsage,scoped_refptr<::base::SequencedTaskRunner> taskRunner)91 VideoFramePool::VideoFramePool(std::shared_ptr<C2BlockPool> blockPool, const size_t maxBufferCount,
92 const ui::Size& size, HalPixelFormat pixelFormat,
93 C2MemoryUsage memoryUsage,
94 scoped_refptr<::base::SequencedTaskRunner> taskRunner)
95 : mBlockPool(std::move(blockPool)),
96 mMaxBufferCount(maxBufferCount),
97 mSize(size),
98 mPixelFormat(pixelFormat),
99 mMemoryUsage(memoryUsage),
100 mClientTaskRunner(std::move(taskRunner)) {
101 ALOGV("%s(size=%dx%d)", __func__, size.width, size.height);
102 ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
103 DCHECK(mBlockPool);
104 DCHECK(mClientTaskRunner);
105 }
106
initialize()107 bool VideoFramePool::initialize() {
108 if (!mFetchThread.Start()) {
109 ALOGE("Fetch thread failed to start.");
110 return false;
111 }
112 mFetchTaskRunner = mFetchThread.task_runner();
113
114 mClientWeakThis = mClientWeakThisFactory.GetWeakPtr();
115 mFetchWeakThis = mFetchWeakThisFactory.GetWeakPtr();
116
117 return true;
118 }
119
~VideoFramePool()120 VideoFramePool::~VideoFramePool() {
121 ALOGV("%s()", __func__);
122 ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
123
124 mClientWeakThisFactory.InvalidateWeakPtrs();
125
126 if (mFetchThread.IsRunning()) {
127 mFetchTaskRunner->PostTask(FROM_HERE,
128 ::base::BindOnce(&VideoFramePool::destroyTask, mFetchWeakThis));
129 mFetchThread.Stop();
130 }
131 }
132
destroyTask()133 void VideoFramePool::destroyTask() {
134 ALOGV("%s()", __func__);
135 ALOG_ASSERT(mFetchTaskRunner->RunsTasksInCurrentSequence());
136
137 mFetchWeakThisFactory.InvalidateWeakPtrs();
138 }
139
shouldDropBuffer(uint32_t bufferId)140 bool VideoFramePool::shouldDropBuffer(uint32_t bufferId) {
141 if (mBuffers.size() < mMaxBufferCount) {
142 return false;
143 }
144
145 if (mBuffers.find(bufferId) != mBuffers.end()) {
146 return false;
147 }
148
149 return true;
150 }
151
getVideoFrame(GetVideoFrameCB cb)152 bool VideoFramePool::getVideoFrame(GetVideoFrameCB cb) {
153 ALOGV("%s()", __func__);
154 ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
155
156 if (mOutputCb) {
157 return false;
158 }
159
160 mOutputCb = std::move(cb);
161 mFetchTaskRunner->PostTask(
162 FROM_HERE, ::base::BindOnce(&VideoFramePool::getVideoFrameTask, mFetchWeakThis));
163 return true;
164 }
165
166 // static
getVideoFrameTaskThunk(scoped_refptr<::base::SequencedTaskRunner> taskRunner,std::optional<::base::WeakPtr<VideoFramePool>> weakPool)167 void VideoFramePool::getVideoFrameTaskThunk(
168 scoped_refptr<::base::SequencedTaskRunner> taskRunner,
169 std::optional<::base::WeakPtr<VideoFramePool>> weakPool) {
170 ALOGV("%s()", __func__);
171 ALOG_ASSERT(weakPool);
172
173 taskRunner->PostTask(FROM_HERE,
174 ::base::BindOnce(&VideoFramePool::getVideoFrameTask, *weakPool));
175 }
176
getVideoFrameTask()177 void VideoFramePool::getVideoFrameTask() {
178 ALOGV("%s()", __func__);
179 ALOG_ASSERT(mFetchTaskRunner->RunsTasksInCurrentSequence());
180
181 // Variables used to exponential backoff retry when buffer fetching times out.
182 constexpr size_t kFetchRetryDelayInit = 256; // Initial delay: 256us
183 constexpr size_t kFetchRetryDelayMax = 16384; // Max delay: 16ms (1 frame at 60fps)
184 constexpr size_t kFenceWaitTimeoutNs = 16000000; // 16ms (1 frame at 60fps)
185 static size_t sNumRetries = 0;
186 static size_t sDelay = kFetchRetryDelayInit;
187
188 C2Fence fence;
189 std::shared_ptr<C2GraphicBlock> block;
190 c2_status_t err = mBlockPool->fetchGraphicBlock(mSize.width, mSize.height,
191 static_cast<uint32_t>(mPixelFormat),
192 mMemoryUsage, &block, &fence);
193 // C2_BLOCKING can be returned either based on the state of the block pool itself
194 // or the state of the underlying buffer queue. If the cause is the underlying
195 // buffer queue, then the block pool returns a null fence. Since a null fence is
196 // immediately ready, we need to delay instead of trying to wait on the fence, to
197 // avoid spinning.
198 //
199 // Unfortunately, a null fence is considered a valid fence, so the best we can do
200 // to detect a null fence is to assume that any fence that is immediately ready
201 // is the null fence. A false positive by racing with a real fence can result in
202 // an unnecessary delay, but the only alternative is to ignore fences altogether
203 // and always delay.
204 if (err == C2_BLOCKING && !fence.ready()) {
205 err = fence.wait(kFenceWaitTimeoutNs);
206 if (err == C2_OK) {
207 ALOGV("%s(): fence wait succeded, retrying now", __func__);
208 mFetchTaskRunner->PostTask(
209 FROM_HERE,
210 ::base::BindOnce(&VideoFramePool::getVideoFrameTask, mFetchWeakThis));
211 return;
212 }
213 ALOGV("%s(): fence wait unsucessful err=%d", __func__, err);
214 } else if (err == C2_OMITTED) {
215 // Fenced version is not supported, try legacy version.
216 err = mBlockPool->fetchGraphicBlock(mSize.width, mSize.height,
217 static_cast<uint32_t>(mPixelFormat), mMemoryUsage,
218 &block);
219 }
220
221 std::optional<uint32_t> bufferId;
222 if (err == C2_OK) {
223 bufferId = getBufferIdFromGraphicBlock(*mBlockPool, *block);
224
225 if (bufferId) {
226 ALOGV("%s(): Got buffer with id = %u", __func__, *bufferId);
227
228 if (shouldDropBuffer(*bufferId)) {
229 // We drop buffer, since we got more then needed.
230 ALOGV("%s(): Dropping allocated buffer with id = %u", __func__, *bufferId);
231 bufferId = std::nullopt;
232 block.reset();
233 err = C2_TIMED_OUT;
234 }
235 }
236 }
237
238 if (err == C2_TIMED_OUT || err == C2_BLOCKING) {
239 ALOGV("%s(): fetchGraphicBlock() timeout, waiting %zuus (%zu retry)", __func__, sDelay,
240 sNumRetries + 1);
241 mFetchTaskRunner->PostDelayedTask(
242 FROM_HERE, ::base::BindOnce(&VideoFramePool::getVideoFrameTask, mFetchWeakThis),
243 ::base::TimeDelta::FromMicroseconds(sDelay));
244
245 sDelay = std::min(sDelay * 4, kFetchRetryDelayMax); // Exponential backoff
246 sNumRetries++;
247 return;
248 }
249
250 // Reset to the default value.
251 sNumRetries = 0;
252 sDelay = kFetchRetryDelayInit;
253
254 if (err != C2_OK) {
255 ALOGE("%s(): Failed to fetch block, err=%d", __func__, err);
256 return;
257 }
258
259 ALOG_ASSERT(block != nullptr);
260 std::unique_ptr<VideoFrame> frame = VideoFrame::Create(std::move(block));
261 std::optional<FrameWithBlockId> frameWithBlockId;
262
263 if (bufferId && frame) {
264 // Only pass the frame + id pair if both have successfully been obtained.
265 // Otherwise exit the loop so a nullopt is passed to the client.
266 frameWithBlockId = std::make_pair(std::move(frame), *bufferId);
267 mBuffers.insert(*bufferId);
268 } else {
269 ALOGE("%s(): Failed to generate VideoFrame or get the buffer id.", __func__);
270 }
271
272 mClientTaskRunner->PostTask(
273 FROM_HERE, ::base::BindOnce(&VideoFramePool::onVideoFrameReady, mClientWeakThis,
274 std::move(frameWithBlockId)));
275 }
276
onVideoFrameReady(std::optional<FrameWithBlockId> frameWithBlockId)277 void VideoFramePool::onVideoFrameReady(std::optional<FrameWithBlockId> frameWithBlockId) {
278 ALOGV("%s()", __func__);
279 ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
280
281 if (!frameWithBlockId) {
282 ALOGE("Failed to get GraphicBlock, abandoning all pending requests.");
283 mClientWeakThisFactory.InvalidateWeakPtrs();
284 mClientWeakThis = mClientWeakThisFactory.GetWeakPtr();
285 }
286
287 ALOG_ASSERT(mOutputCb);
288 std::move(mOutputCb).Run(std::move(frameWithBlockId));
289 }
290
291 } // namespace android
292