将相机预览的视频帧转发给AI模块识别并添加tag后生成的新图像预览在屏幕上的功能实现。
在android框架中,移植的ai处理框架一般opencv框架做图像处理。opencv吐出的图像数据接入android app可以通过jni给到java,但是处理起来很麻烦。之前做其他平台的时候,见到过一个直接通过c代码实现的相机预览功能。记录下实现的方法,做个笔记下次使用方便查阅。
main.cpp
关于格式转换,根据相机输出的视频格式做转换就行,我的相机输出的是yuyv。
cpp
#define LOG_TAG "V4L2CameraPreview"
#include <utils/Log.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/Surface.h>
#include <system/window.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
using namespace android;
void yuyv_to_rgba(const uint8_t* yuyv, uint32_t* rgba, int width, int height, int out_stride) {
if (!yuyv || !rgba || width <= 0 || height <= 0) return;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; j += 2) {
int src_idx = (i * width + j) * 2; // 每像素 2 字节,每 2 像素 4 字节
if (src_idx + 3 >= width * height * 2) break; // 防越界
uint8_t y0 = yuyv[src_idx + 0];
uint8_t u = yuyv[src_idx + 1];
uint8_t y1 = yuyv[src_idx + 2];
uint8_t v = yuyv[src_idx + 3];
// 转换 Y0
int c0 = y0 - 16;
int d = u - 128;
int e = v - 128;
int r0 = (298 * c0 + 409 * e + 128) >> 8;
int g0 = (298 * c0 - 100 * d - 208 * e + 128) >> 8;
int b0 = (298 * c0 + 516 * d + 128) >> 8;
r0 = r0 < 0 ? 0 : (r0 > 255 ? 255 : r0);
g0 = g0 < 0 ? 0 : (g0 > 255 ? 255 : g0);
b0 = b0 < 0 ? 0 : (b0 > 255 ? 255 : b0);
// 转换 Y1
int c1 = y1 - 16;
int r1 = (298 * c1 + 409 * e + 128) >> 8;
int g1 = (298 * c1 - 100 * d - 208 * e + 128) >> 8;
int b1 = (298 * c1 + 516 * d + 128) >> 8;
r1 = r1 < 0 ? 0 : (r1 > 255 ? 255 : r1);
g1 = g1 < 0 ? 0 : (g1 > 255 ? 255 : g1);
b1 = b1 < 0 ? 0 : (b1 > 255 ? 255 : b1);
// 写入两个像素(注意 stride)
int idx0 = i * out_stride + j;
int idx1 = i * out_stride + j + 1;
rgba[idx0] = 0xFF000000 | (r0 << 16) | (g0 << 8) | b0;
if (j + 1 < width) {
rgba[idx1] = 0xFF000000 | (r1 << 16) | (g1 << 8) | b1;
}
}
}
}
struct Buffer {
void* start;
size_t length;
};
int main() {
const char* device = "/dev/video69";
int width = 1024, height = 600;
int fd = open(device, O_RDWR | O_NONBLOCK);
if (fd < 0) {
ALOGE("Failed to open %s", device);
return -1;
}
// 设置格式
struct v4l2_format fmt = {};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1024; // 选一个支持的尺寸,比如 640x480
fmt.fmt.pix.height = 600;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // ← 改为 YUYV
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
ALOGE("VIDIOC_S_FMT failed");
close(fd);
return -1;
}
ioctl(fd, VIDIOC_G_FMT, &fmt); // 重新读取
ALOGI("Actual format: %c%c%c%c, size=%dx%d",
fmt.fmt.pix.pixelformat & 0xFF,
(fmt.fmt.pix.pixelformat >> 8) & 0xFF,
(fmt.fmt.pix.pixelformat >> 16) & 0xFF,
(fmt.fmt.pix.pixelformat >> 24) & 0xFF,
fmt.fmt.pix.width, fmt.fmt.pix.height);
int v4l_wid = fmt.fmt.pix.width;
int v4l_height = fmt.fmt.pix.height;
// 请求缓冲区
struct v4l2_requestbuffers req = {};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
ALOGE("VIDIOC_REQBUFS failed");
close(fd);
return -1;
}
Buffer buffers[4];
for (uint32_t i = 0; i < req.count; ++i) {
struct v4l2_buffer buf = {};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
ALOGE("VIDIOC_QUERYBUF failed");
close(fd);
return -1;
}
buffers[i].length = buf.length;
buffers[i].start = mmap(nullptr, buf.length,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
if (buffers[i].start == MAP_FAILED) {
ALOGE("mmap failed");
close(fd);
return -1;
}
ioctl(fd, VIDIOC_QBUF, &buf); // 入队
}
// 启动流
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
ALOGE("VIDIOC_STREAMON failed");
close(fd);
return -1;
}
// 创建 Surface
sp<SurfaceComposerClient> client = SurfaceComposerClient::getDefault();
sp<SurfaceControl> surfaceCtrl = client->createSurface(
String8("V4L2Preview"), width, height,
PIXEL_FORMAT_RGBA_8888,
0
);
sp<Surface> surface = surfaceCtrl->getSurface();
// === 新增:计算缩放并设置 Matrix ===
float src_w = v4l_wid; // 实际摄像头宽度(如 640)
float src_h = v4l_height; // 实际摄像头高度(如 480)
float dst_w = width; // Surface 宽度(1024)
float dst_h = height; // Surface 高度(600)
// 计算等比缩放因子(保证覆盖全屏)
float scale = fmaxf(dst_w / src_w, dst_h / src_h); // 1024/640=1.6, 600/480=1.25 → scale=1.6
// 缩放后图像尺寸
float scaled_w = src_w * scale; // 1024
float scaled_h = src_h * scale; // 768
// 计算需要裁剪的偏移(居中裁剪)
float crop_x = (scaled_w - dst_w) / 2.0f; // 0
float crop_y = (scaled_h - dst_h) / 2.0f; // (768-600)/2 = 84
// 构建变换:先缩放,再平移(负值表示向上/向左裁剪)
SurfaceComposerClient::Transaction t2;
t2.setMatrix(surfaceCtrl, scale, 0, 0, scale)
.setPosition(surfaceCtrl, -crop_x, -crop_y)
.setLayer(surfaceCtrl, 0x7FFFFFFF)
.show(surfaceCtrl)
.apply();
// === 新增结束 ===
ANativeWindow* window = surface.get();
//SurfaceComposerClient::Transaction t;
//t.setLayer(surfaceCtrl, 0x7FFFFFFF).show(surfaceCtrl).apply();
ALOGI("Streaming from V4L2...");
//for (int frame = 0; frame < 300; ++frame) {
while(1) {
struct v4l2_buffer buf = {};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
// 出队一帧
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) continue;
// 转换并渲染
ANativeWindow_Buffer anb;
if (ANativeWindow_lock(window, &anb, nullptr) == 0) {
ALOGI("Streaming from V4L2...wid:%d,hei:%d", anb.width, anb.height);
yuyv_to_rgba(
(const uint8_t*)buffers[buf.index].start,
(uint32_t*)anb.bits,
v4l_wid, // 从 VIDIOC_G_FMT 获取的实际宽
v4l_height, // 实际高
anb.stride // Surface 的 stride
);
ANativeWindow_unlockAndPost(window);
}
// 重新入队
ioctl(fd, VIDIOC_QBUF, &buf);
usleep(33000); // ~30 FPS
}
// 停止流
ioctl(fd, VIDIOC_STREAMOFF, &type);
for (int i = 0; i < 4; ++i) {
munmap(buffers[i].start, buffers[i].length);
}
close(fd);
ALOGI("Done.");
return 0;
}
Android.bp
bash
cc_binary {
name: "camera_preview_hal_bp",
// 源文件
srcs: ["main.cpp"],
// 编译选项
cflags: [
"-Wall",
"-Werror",
],
cppflags: [
"-std=c++17",
"-fexceptions",
"-frtti",
],
// STL:使用 AOSP 默认的 libc++
stl: "libc++",
// 链接系统库
shared_libs: [
"libgui", // SurfaceComposerClient, SurfaceControl
"libui", // GraphicBuffer, PixelFormat
"libbinder", // IPC
"libutils", // RefBase, String8, SortedVector (内部)
"libcutils", // compatibility
"libnativewindow", // ANativeWindow_lock/unlockAndPost
"liblog", // ALOGI/ALOGE
],
// 可选:仅在 eng/userdebug 版本编译
owner: "your_team", // 可删除
//vendor_available: true, // 如果要放 vendor 分区
//vendor_specific: true, // ← Android 13+ 新属性
//vendor: true,
//proprietary: true, // 绕过 private: true 限制
}