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