1 /*
2  * Copyright (C) 2024 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 "v4l2_helpers.h"
18 
19 #include <fcntl.h>
20 #include <linux/videodev2.h>
21 #include <log/log.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/ioctl.h>
26 #include <unistd.h>
27 
28 namespace cuttlefish {
29 
V4l2GetBpp(int format)30 Result<size_t> V4l2GetBpp(int format) {
31   CF_EXPECT(format == V4L2_PIX_FMT_BGRX32,
32             "Error: v4l2_get_bpp; only V4L2_PIX_FMT_BGRX32 supported");
33   return 4;
34 }
35 
V4l2GetFrameSize(int format,int width,int height)36 Result<size_t> V4l2GetFrameSize(int format, int width, int height) {
37   size_t bytes_per_pixel =
38       CF_EXPECT(V4l2GetBpp(format), "Error: invalid bpp format");
39 
40   return width * height * bytes_per_pixel;
41 }
42 
V4l2GetLineWidth(int format,int width)43 Result<size_t> V4l2GetLineWidth(int format, int width) {
44   size_t bytes_per_pixel =
45       CF_EXPECT(V4l2GetBpp(format), "Error: invalid bpp format");
46 
47   return width * bytes_per_pixel;
48 }
49 
V4l2PrintFormat(struct v4l2_format * vid_format)50 void V4l2PrintFormat(struct v4l2_format* vid_format) {
51   ALOGI("	vid_format->type                =%d", vid_format->type);
52   ALOGI("	vid_format->fmt.pix.width       =%d",
53         vid_format->fmt.pix.width);
54   ALOGI("	vid_format->fmt.pix.height      =%d",
55         vid_format->fmt.pix.height);
56   ALOGI("	vid_format->fmt.pix.pixelformat =%d",
57         vid_format->fmt.pix.pixelformat);
58   ALOGI("	vid_format->fmt.pix.sizeimage   =%d",
59         vid_format->fmt.pix.sizeimage);
60   ALOGI("	vid_format->fmt.pix.field       =%d",
61         vid_format->fmt.pix.field);
62   ALOGI("	vid_format->fmt.pix.bytesperline=%d",
63         vid_format->fmt.pix.bytesperline);
64   ALOGI("	vid_format->fmt.pix.colorspace  =%d",
65         vid_format->fmt.pix.colorspace);
66 }
67 
V4l2ReadRawFile(const std::string & filename)68 Result<std::vector<char>> V4l2ReadRawFile(const std::string& filename) {
69   std::streampos filepos = 0;
70   std::ifstream file(filename, std::ios::binary);
71 
72   filepos = file.tellg();
73   file.seekg(0, std::ios::end);
74   long buffersize = file.tellg() - filepos;
75   file.seekg(0, std::ios::beg);
76 
77   std::vector<char> buffer;
78   buffer.resize(buffersize);
79 
80   file.read(buffer.data(), buffersize);
81 
82   CF_EXPECT_NE(file.fail(), 0,
83                "Error reading Raw file buffer: " << strerror(errno));
84 
85   ALOGI("Allocated and read %ld bytes", buffersize);
86 
87   return buffer;
88 }
89 
V4l2InitDevice(const std::string & device_path,int format,int width,int height)90 Result<SharedFD> V4l2InitDevice(const std::string& device_path, int format,
91                                 int width, int height) {
92   int framesize = CF_EXPECT(V4l2GetFrameSize(format, width, height),
93                             "Error calculating frame size");
94   int linewidth =
95       CF_EXPECT(V4l2GetLineWidth(format, width), "Error calculating linewidth");
96 
97   SharedFD fdwr = SharedFD::Open(device_path, O_RDWR);
98 
99   CF_EXPECT(fdwr->IsOpen(), "Error: Could not open v4l2 device for O_RDWR: "
100                                 << fdwr->StrError());
101 
102   struct v4l2_capability vid_caps;
103   int ret_code = fdwr->Ioctl(VIDIOC_QUERYCAP, &vid_caps);
104 
105   CF_EXPECT_NE(ret_code, -1,
106                "Error: VIDIOC_QUERYCAP failed: " << fdwr->StrError());
107 
108   struct v4l2_format vid_format = v4l2_format{};
109 
110   V4l2PrintFormat(&vid_format);
111 
112   vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
113   vid_format.fmt.pix.width = width;
114   vid_format.fmt.pix.height = height;
115   vid_format.fmt.pix.pixelformat = format;
116   vid_format.fmt.pix.sizeimage = framesize;
117   vid_format.fmt.pix.field = V4L2_FIELD_NONE;
118   vid_format.fmt.pix.bytesperline = linewidth;
119   vid_format.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
120 
121   V4l2PrintFormat(&vid_format);
122 
123   ret_code = fdwr->Ioctl(VIDIOC_S_FMT, &vid_format);
124 
125   CF_EXPECT_NE(ret_code, -1,
126                "Error: VIDIOC_S_FMT failed: " << fdwr->StrError());
127 
128   ALOGI("frame: format=%d\tsize=%d", format, framesize);
129   V4l2PrintFormat(&vid_format);
130 
131   return fdwr;
132 }
133 
134 // This is a testing / debugging method. Only used optionally for
135 // troubleshooting a v4l2 by dumping raw movie frames direct to the device. It
136 // avoids using the network for simplifying the debug process.   It also shows
137 // how to use the API methods provided in this file.
V4l2StreamFile(const std::string & device_path,const std::string & raw_movie_file)138 Result<void> V4l2StreamFile(const std::string& device_path,
139                             const std::string& raw_movie_file) {
140   int width = 640;
141   int height = 480;
142   int format = V4L2_PIX_FMT_BGRX32;
143   int framesize = CF_EXPECT(V4l2GetFrameSize(format, width, height),
144                             "Error getting frame size");
145 
146   ALOGI("Starting.... using framesize(%d)", framesize);
147 
148   std::vector<char> buffer =
149       CF_EXPECT(V4l2ReadRawFile(raw_movie_file), "Error reading buffer");
150 
151   ALOGI("Beginning frame push with buffersize(%ld)", buffer.size());
152 
153   SharedFD fdwr = CF_EXPECT(V4l2InitDevice(device_path, format, width, height),
154                             "Error initializing device");
155 
156   CF_EXPECT(fdwr->IsOpen(), "Error: initdevice == 0");
157 
158   ALOGI("Device initialized(%s)", device_path.c_str());
159 
160   ALOGI("Beginning stream:");
161 
162   CF_EXPECT(buffer.size() > framesize, "Error: invalid buffer size");
163 
164   for (long i = 0; i < buffer.size() - framesize; i += framesize) {
165     ALOGI("Beginning frame:");
166     if (fdwr->Write(((char*)buffer.data()) + i, framesize) == -1) {
167       ALOGE("Error writing buffer data: %s", fdwr->StrError().c_str());
168     }
169     sleep(1);
170     if (i % 20 == 0) {
171       ALOGI("Wrote %ld frames", ((i + framesize) / framesize));
172     }
173   }
174 
175   ALOGI("ended stream:");
176 
177   fdwr->Close();
178 
179   ALOGI("Streaming complete.");
180 
181   return {};
182 }
183 
184 }  // End namespace cuttlefish