如何在安卓系统里面用C++写一个获取摄像头原始数据并保存成.yuv文件

发布于:2024-12-18 ⋅ 阅读:(86) ⋅ 点赞:(0)

在 Android 系统中使用 C++ 编写一个获取摄像头原始数据并保存为 .yuv 文件的程序,并且通过 Android.bp 编译,你需要结合 V4L2 和 Android NDK 的特性来实现。以下是详细的步骤和代码示例。

步骤 1: 设置权限

确保你的应用程序有访问摄像头的权限。在 AndroidManifest.xml 中添加以下权限声明:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

对于 Android 6.0 及以上版本,还需要在运行时请求这些权限。

步骤 2: 创建 C++ 源文件

创建一个新的 C++ 文件,例如 camera_capture.cpp,并在其中编写代码以打开摄像头设备、配置参数、读取帧并保存为 YUV 文件。

示例代码 (camera_capture.cpp)
#include <fcntl.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstring>
#include <cstdio>
#include <vector>
#include <iostream>

#define WIDTH 640
#define HEIGHT 480
#define FRAME_COUNT 4

struct Buffer {
    void *start;
    size_t length;
};

std::vector<Buffer> buffers;

void xioctl(int fh, int request, void *arg) {
    int r;
    do {
        r = ioctl(fh, request, arg);
    } while (r == -1 && ((errno == EINTR) || (errno == EAGAIN)));
    if (r == -1) {
        std::cerr << "ioctl failed" << std::endl;
        exit(EXIT_FAILURE);
    }
}

void save_frame(const void *data, size_t size) {
    static int frame_count = 0;
    char filename[256];
    snprintf(filename, sizeof(filename), "/sdcard/frame_%03d.yuv", frame_count++);
    FILE *fp = fopen(filename, "wb");
    if (!fp) {
        perror("fopen failed");
        return;
    }
    fwrite(data, 1, size, fp);
    fclose(fp);
}

int main() {
    const char *dev_name = "/dev/video0";
    int fd = open(dev_name, O_RDWR);
    if (fd == -1) {
        perror("Cannot open device");
        return EXIT_FAILURE;
    }

    // 获取设备信息
    struct v4l2_capability cap;
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
        perror("Querying capabilities failed");
        close(fd);
        return EXIT_FAILURE;
    }

    // 配置格式
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = WIDTH;
    fmt.fmt.pix.height = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field = V4L2_FIELD_NONE;
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
        perror("Setting format failed");
        close(fd);
        return EXIT_FAILURE;
    }

    // 请求缓冲区
    struct v4l2_requestbuffers req;
    memset(&req, 0, sizeof(req));
    req.count = FRAME_COUNT;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
        perror("Requesting buffers failed");
        close(fd);
        return EXIT_FAILURE;
    }

    // 映射缓冲区
    for (unsigned int i = 0; i < FRAME_COUNT; ++i) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
            perror("Querying buffer failed");
            close(fd);
            return EXIT_FAILURE;
        }

        Buffer buffer;
        buffer.length = buf.length;
        buffer.start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffer.start == MAP_FAILED) {
            perror("mmap failed");
            close(fd);
            return EXIT_FAILURE;
        }
        buffers.push_back(buffer);
    }

    // 将所有缓冲区放入队列
    for (size_t i = 0; i < buffers.size(); ++i) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
            perror("Queueing buffer failed");
            close(fd);
            return EXIT_FAILURE;
        }
    }

    // 开始捕获
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
        perror("Starting stream failed");
        close(fd);
        return EXIT_FAILURE;
    }

    // 捕获循环
    for (int i = 0; i < 10; ++i) {  // Capture 10 frames as an example
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        xioctl(fd, VIDIOC_DQBUF, &buf);

        // Save the frame to a .yuv file
        save_frame(buffers[buf.index].start, WIDTH * HEIGHT * 2);  // YUV420p has 1.5 bytes per pixel

        // Requeue the buffer
        xioctl(fd, VIDIOC_QBUF, &buf);
    }

    // 停止捕获
    if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
        perror("Stopping stream failed");
        close(fd);
        return EXIT_FAILURE;
    }

    // 清理
    for (auto &buffer : buffers) {
        munmap(buffer.start, buffer.length);
    }
    close(fd);
    return 0;
}

步骤 3: 创建 Android.bp 文件

在同一目录下创建 Android.bp 文件来定义构建规则。这个文件告诉 Soong 构建系统如何编译你的 C++ 文件。

示例 (Android.bp)
cc_binary {
    name: "camera_capture",
    srcs: ["camera_capture.cpp"],
    cflags: [
        "-Wall",
        "-Werror",
        "-pthread",
    ],
    shared_libs: [
        "libc++",
        "liblog",
    ],
    stl: "c++_shared",
    target: {
        android: {
            enabled: true,
        },
    },
}

步骤 4: 修改产品定义

为了让构建系统知道要编译这个可执行文件,你需要将其添加到适当的产品定义文件中。例如,在 device/sample/device/BoardConfig.mk 或相应的 Soong 配置文件中添加一行:

PRODUCT_PACKAGES += camera_capture

步骤 5: 编译源代码

进入 Android 源码根目录,然后运行以下命令来编译整个项目,包括你的新可执行文件:

source build/envsetup.sh
lunch <target>  # 例如:aosp_arm-eng
m

步骤 6: 将可执行文件推送到设备

编译完成后,找到生成的可执行文件路径(通常位于 out/target/product/<device>/system/bin/),然后通过 ADB 推送到设备上的适当位置(如 /data/local/tmp/):

adb push out/target/product/<device>/system/bin/camera_capture /data/local/tmp/

步骤 7: 设置权限并运行

为可执行文件设置正确的权限,并通过 ADB shell 运行它:

adb shell chmod 755 /data/local/tmp/camera_capture
adb shell /data/local/tmp/camera_capture

注意事项

  • 权限控制:确保你的应用程序具有适当的权限来访问摄像头和外部存储。
  • 线程安全:处理摄像头数据时要注意线程安全问题,尤其是在多线程环境中。
  • 资源管理:正确管理摄像头和文件资源的生命周期,避免资源泄漏。
  • 设备兼容性:不同的 Android 设备可能有不同的摄像头设备节点名称(如 /dev/video0)。你可以通过枚举 /dev 目录下的设备节点来找到正确的摄像头设备。

通过以上步骤,你应该能够在 Android 系统中成功创建、编译并运行一个用 C++ 编写的获取摄像头原始数据并保存为 .yuv 文件的程序。


网站公告

今日签到

点亮在社区的每一天
去签到