1 /*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "emul/VideoCapture.h"
18
19 #include <android-base/logging.h>
20 #include <processgroup/sched_policy.h>
21
22 #include <assert.h>
23 #include <errno.h>
24 #include <error.h>
25 #include <fcntl.h>
26 #include <memory.h>
27 #include <png.h>
28 #include <pthread.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <sys/ioctl.h>
32 #include <sys/mman.h>
33 #include <unistd.h>
34
35 #include <fstream>
36 #include <iomanip>
37
38 namespace {
39
40 const char* kPngFileExtension = ".png";
41 const char* kDumpFileExtension = ".bin";
42
validatePng(std::ifstream & source)43 bool validatePng(std::ifstream& source) {
44 const int kSigSize = 8;
45 png_byte header[kSigSize] = {0};
46 source.read((char*)header, kSigSize);
47
48 return source.good() && (png_sig_cmp(header, 0, kSigSize) == 0);
49 }
50
readPngDataFromStream(png_structp pngPtr,png_bytep data,png_size_t length)51 void readPngDataFromStream(png_structp pngPtr, png_bytep data, png_size_t length) {
52 png_voidp p = png_get_io_ptr(pngPtr);
53 ((std::ifstream*)p)->read((char*)data, length);
54 }
55
fillBufferFromPng(const std::string & filename,imageMetadata & info)56 char* fillBufferFromPng(const std::string& filename, imageMetadata& info) {
57 // Open a PNG file
58 std::ifstream source(filename, std::ios::in | std::ios::binary);
59 if (!source.is_open()) {
60 LOG(ERROR) << "Failed to open " << filename;
61 return nullptr;
62 }
63
64 // Validate an input PNG file
65 if (!validatePng(source)) {
66 LOG(ERROR) << filename << " is not a valid PNG file";
67 source.close();
68 return nullptr;
69 }
70
71 // Prepare a control structure
72 png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
73 if (!pngPtr) {
74 LOG(ERROR) << "Failed to create a control structure";
75 source.close();
76 return nullptr;
77 }
78
79 // Set up an image info
80 png_infop infoPtr = png_create_info_struct(pngPtr);
81 if (!infoPtr) {
82 LOG(ERROR) << " Failed to initialize a png_info";
83 png_destroy_read_struct(&pngPtr, nullptr, nullptr);
84 source.close();
85 return nullptr;
86 }
87
88 // Set up an error handler
89 if (setjmp(png_jmpbuf(pngPtr))) {
90 png_destroy_read_struct(&pngPtr, &infoPtr, nullptr);
91 source.close();
92 return nullptr;
93 }
94
95 // Set up PNG reader and fetch the remaining header bytes
96 png_set_read_fn(pngPtr, (png_voidp)&source, readPngDataFromStream);
97 const int kSigSize = 8;
98 png_set_sig_bytes(pngPtr, kSigSize);
99 png_read_info(pngPtr, infoPtr);
100
101 // Get basic image information
102 png_uint_32 width = png_get_image_width(pngPtr, infoPtr);
103 png_uint_32 height = png_get_image_height(pngPtr, infoPtr);
104 png_uint_32 bitdepth = png_get_bit_depth(pngPtr, infoPtr);
105 png_uint_32 channels = png_get_channels(pngPtr, infoPtr);
106 png_uint_32 colorType = png_get_color_type(pngPtr, infoPtr);
107
108 // Record video device info
109 info.width = width;
110 info.height = height;
111 switch (colorType) {
112 case PNG_COLOR_TYPE_GRAY:
113 png_set_expand_gray_1_2_4_to_8(pngPtr);
114 bitdepth = 8;
115 info.format = V4L2_PIX_FMT_GREY;
116 break;
117
118 case PNG_COLOR_TYPE_RGB:
119 info.format = V4L2_PIX_FMT_XBGR32;
120 break;
121
122 case PNG_COLOR_TYPE_RGB_ALPHA:
123 info.format = V4L2_PIX_FMT_ABGR32;
124 break;
125
126 default:
127 LOG(INFO) << "Unsupported PNG color type: " << colorType;
128 return nullptr;
129 }
130
131 // If the image has a transparency set, convert it to a full Alpha channel
132 if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) {
133 png_set_tRNS_to_alpha(pngPtr);
134 channels += 1;
135 info.format = V4L2_PIX_FMT_ABGR32;
136 }
137
138 // Refresh PNG info
139 png_read_update_info(pngPtr, infoPtr);
140
141 // Allocate a buffer to contain pixel data. This buffer will be managed
142 // by the caller.
143 const int stride = png_get_rowbytes(pngPtr, infoPtr);
144 info.stride = stride;
145 LOG(DEBUG) << "width = " << width << ", height = " << height << ", bitdepth = " << bitdepth
146 << ", channels = " << channels << ", colorType = " << colorType
147 << ", stride = " << stride;
148
149 char* buffer = new char[info.stride * height];
150 png_bytep* rowPtrs = new png_bytep[height];
151 for (int r = 0; r < height; ++r) {
152 rowPtrs[r] = reinterpret_cast<unsigned char*>(buffer) + r * stride;
153 }
154
155 // Read the image
156 png_read_image(pngPtr, rowPtrs);
157 png_read_end(pngPtr, nullptr);
158
159 // Clean up
160 png_destroy_read_struct(&pngPtr, &infoPtr, nullptr);
161 delete[] rowPtrs;
162 source.close();
163
164 return buffer;
165 }
166 } // namespace
167
168 namespace android {
169 namespace automotive {
170 namespace evs {
171 namespace V1_1 {
172 namespace implementation {
173
~VideoCapture()174 VideoCapture::~VideoCapture() {
175 // Stop active stream
176 stopStream();
177
178 // Close the device
179 close();
180 }
181
open(const std::string & path,const std::chrono::nanoseconds interval)182 bool VideoCapture::open(const std::string& path, const std::chrono::nanoseconds interval) {
183 // Report device properties
184 LOG(INFO) << "Open a virtual video stream with data from " << path;
185
186 // Store the source location
187 if (!std::filesystem::exists(path) || !std::filesystem::is_directory(path)) {
188 LOG(INFO) << path << " does not exist or is not a directory.";
189 return false;
190 }
191
192 // Sets a directory iterator
193 LOG(INFO) << "directory_iterator is set to " << path;
194 mSrcIter = std::filesystem::directory_iterator(path);
195 mSourceDir = path;
196
197 // Set a frame rate
198 mDesiredFrameInterval = interval;
199
200 // Make sure we're initialized to the STOPPED state
201 mRunMode = STOPPED;
202 mFrameReady = false;
203 mVideoReady = true;
204
205 // Ready to go!
206 return true;
207 }
208
close()209 void VideoCapture::close() {
210 LOG(DEBUG) << __FUNCTION__;
211
212 // Stream must be stopped first!
213 assert(mRunMode == STOPPED);
214
215 // Tell this is now closed
216 mVideoReady = false;
217
218 // Free allocated resources
219 delete[] mPixelBuffer;
220 }
221
startStream(std::function<void (VideoCapture *,imageBufferDesc *,void *)> callback)222 bool VideoCapture::startStream(
223 std::function<void(VideoCapture*, imageBufferDesc*, void*)> callback) {
224 // Set the state of our background thread
225 int prevRunMode = mRunMode.fetch_or(RUN);
226 if (prevRunMode & RUN) {
227 // The background thread is already running, so we can't start a new stream
228 LOG(ERROR) << "Already in RUN state, so we can't start a new streaming thread";
229 return false;
230 }
231
232 // Remembers who to tell about new frames as they arrive
233 mCallback = callback;
234
235 // Fires up a thread to generate and dispatch the video frames
236 mCaptureThread = std::thread([&]() {
237 if (mCurrentStreamEvent != StreamEvent::INIT) {
238 LOG(ERROR) << "Not in the right state to start a video stream. Current state is "
239 << mCurrentStreamEvent;
240 return;
241 }
242
243 // We'll periodically send a new frame
244 mCurrentStreamEvent = StreamEvent::PERIODIC;
245
246 // Sets a background priority
247 if (set_sched_policy(0, SP_BACKGROUND) != 0) {
248 PLOG(WARNING) << "Failed to set background scheduling priority";
249 }
250
251 // Sets a looper for the communication
252 if (android::Looper::getForThread() != nullptr) {
253 LOG(DEBUG) << "Use existing looper thread";
254 }
255
256 mLooper = android::Looper::prepare(/*opts=*/0);
257 if (mLooper == nullptr) {
258 LOG(ERROR) << "Failed to initialize the looper. Exiting the thread.";
259 return;
260 }
261
262 // Requests to start generating frames periodically
263 mLooper->sendMessage(this, StreamEvent::PERIODIC);
264
265 // Polling the messages until the stream stops
266 while (mRunMode == RUN) {
267 mLooper->pollAll(/*timeoutMillis=*/-1);
268 }
269
270 LOG(INFO) << "Capture thread is exiting!!!";
271 });
272
273 LOG(DEBUG) << "Stream started.";
274 return true;
275 }
276
stopStream()277 void VideoCapture::stopStream() {
278 // Tell the background thread to stop
279 int prevRunMode = mRunMode.fetch_or(STOPPING);
280 if (prevRunMode == STOPPED) {
281 // The background thread wasn't running, so set the flag back to STOPPED
282 mRunMode = STOPPED;
283 } else if (prevRunMode & STOPPING) {
284 LOG(ERROR) << "stopStream called while stream is already stopping. "
285 << "Reentrancy is not supported!";
286 return;
287 } else {
288 // Block until the background thread is stopped
289 if (mCaptureThread.joinable()) {
290 // Removes all pending messages and awake the looper
291 mLooper->removeMessages(this, StreamEvent::PERIODIC);
292 mLooper->wake();
293 mCaptureThread.join();
294 } else {
295 LOG(ERROR) << "Capture thread is not joinable";
296 }
297
298 mRunMode = STOPPED;
299 LOG(DEBUG) << "Capture thread stopped.";
300 }
301
302 // Drop our reference to the frame delivery callback interface
303 mCallback = nullptr;
304 }
305
markFrameReady()306 void VideoCapture::markFrameReady() {
307 mFrameReady = true;
308 }
309
returnFrame()310 bool VideoCapture::returnFrame() {
311 // We're using a single buffer synchronousely so just need to set
312 // mFrameReady as false.
313 mFrameReady = false;
314
315 return true;
316 }
317
318 // This runs on a background thread to receive and dispatch video frames
collectFrames()319 void VideoCapture::collectFrames() {
320 const std::filesystem::directory_iterator end_iter;
321 imageMetadata header = {};
322 static uint64_t sequence = 0; // counting frames
323
324 while (mPixelBuffer == nullptr && mSrcIter != end_iter) {
325 LOG(INFO) << "Synthesizing a frame from " << mSrcIter->path();
326 auto ext = mSrcIter->path().extension();
327 if (ext == kPngFileExtension) {
328 // Read PNG image; a buffer will be allocated inside
329 mPixelBuffer = fillBufferFromPng(mSrcIter->path(), header);
330
331 // Update frame info
332 mPixelBufferSize = header.stride * header.height;
333 } else if (ext == kDumpFileExtension) {
334 // Read files dumped by the reference EVS HAL implementation
335 std::ifstream fin(mSrcIter->path(), std::ios::in | std::ios::binary);
336 if (fin.is_open()) {
337 // Read a header
338 fin.read((char*)&header, sizeof(header));
339 const size_t length = header.stride * header.height;
340
341 // Allocate memory for pixel data
342 mPixelBuffer = new char[length];
343 mPixelBufferSize = length;
344
345 // Read pixels
346 fin.read(mPixelBuffer, length);
347 if (fin.gcount() != length) {
348 LOG(WARNING) << mSrcIter->path() << " contains less than expected.";
349 }
350 fin.close();
351 } else {
352 PLOG(ERROR) << "Failed to open " << mSrcIter->path();
353 }
354 } else {
355 LOG(DEBUG) << "Unsupported file extension. Ignores " << mSrcIter->path().filename();
356 }
357
358 // Moves to next file
359 ++mSrcIter;
360 }
361
362 // Fill the buffer metadata
363 mBufferInfo.info = header;
364 mBufferInfo.sequence = sequence++;
365
366 int64_t now = nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC));
367 mBufferInfo.timestamp.tv_sec = (time_t)(now / 1000LL);
368 mBufferInfo.timestamp.tv_usec = (suseconds_t)((now % 1000LL) * 1000LL);
369
370 if (mCallback != nullptr) {
371 mCallback(this, &mBufferInfo, mPixelBuffer);
372 }
373
374 // Delete a consumed pixel buffer
375 delete[] mPixelBuffer;
376 mPixelBuffer = nullptr;
377 mPixelBufferSize = 0;
378
379 // If the last file is processed, reset the iterator to the first file.
380 if (mSrcIter == end_iter) {
381 LOG(DEBUG) << "Rewinds the iterator to the beginning.";
382 mSrcIter = std::filesystem::directory_iterator(mSourceDir);
383 }
384 }
385
setParameter(v4l2_control &)386 int VideoCapture::setParameter(v4l2_control& /*control*/) {
387 // Not implemented yet.
388 return -ENOSYS;
389 }
390
getParameter(v4l2_control &)391 int VideoCapture::getParameter(v4l2_control& /*control*/) {
392 // Not implemented yet.
393 return -ENOSYS;
394 }
395
handleMessage(const android::Message & message)396 void VideoCapture::handleMessage(const android::Message& message) {
397 const auto received = static_cast<StreamEvent>(message.what);
398 switch (received) {
399 case StreamEvent::PERIODIC: {
400 // Generates a new frame and send
401 collectFrames();
402
403 // Updates a timestamp and arms a message for next frame
404 mLastTimeFrameSent = systemTime(SYSTEM_TIME_MONOTONIC);
405 const auto next = mLastTimeFrameSent + mDesiredFrameInterval.count();
406 mLooper->sendMessageAtTime(next, this, received);
407 break;
408 }
409
410 case StreamEvent::STOP: {
411 // Stopping a frame generation
412 LOG(INFO) << "Stop generating frames";
413 break;
414 }
415
416 default:
417 LOG(WARNING) << "Unknown event is received: " << received;
418 break;
419 }
420 }
421
422 } // namespace implementation
423 } // namespace V1_1
424 } // namespace evs
425 } // namespace automotive
426 } // namespace android
427