Gstreamer (imx8+MAX9286+MAX96702)学习笔记

1.总体架构

1.1 顶层:应用与工具层(Multimedia Applications & Tools)​

功能定位​​:直接面向用户或开发者的接口,提供快速调用 GStreamer 能力的工具和示例应用。

​GStreamer Tools​:

  • gst-launch:通过命令行快速构建媒体处理流水线(如 gst-launch-1.0 filesrc location=test.mp3 ! decodebin ! audioconvert ! autoaudiosink)。
  • gst-inspect:查看插件和元素的详细信息(如支持的格式、参数)。
  • gst-editor(可能为旧版工具):图形化流水线设计工具。

Multimedia Applications​

  • ​VoIP & 视频会议​​:实时音视频传输(如 WebRTC 集成)。
  • ​流媒体服务器​​:支持 RTSP、HTTP 等协议的视频流分发。
  • ​媒体播放器/编辑器​​:基于 GStreamer 的解码、渲染、滤镜链实现。

1.2 中间层:核心框架(Core Framework)​

  • 功能定位​​:GStreamer 的“大脑”,负责媒体流的调度、同步、协商和扩展管理。
  • ​核心机制​​(图中蓝色部分):
    • ​Pipeline Architecture​​:
      • 数据流以 ​​管道(Pipeline)​​ 形式组织,由多个 ​​元素(Element)​​ 构成(如 source → decoder → converter → sink)。
      • ​Source​​:负责​​获取原始媒体数据​​,可以是本地文件、硬件设备或网络流。
      • ​Decoder​​:将​​压缩编码的数据解码为原始格式​​(如 PCM 音频、YUV/RGB 视频)。
      • ​Converter​​:对原始数据进行​​格式转换、处理或添加效果​​,确保数据兼容下游组件。
      • ​Sink​​:将处理后的数据输出到目标设备或存储​​。
    • ​Media Agnostic​​:
      • 支持任意媒体类型(音频、视频、字幕等),通过 ​​Caps Negotiation​​ 动态协商格式(如 video/x-raw, width=1920, height=1080)。
    • ​Plugin System​​:
      • 所有功能(编解码、协议、滤镜)均以插件形式加载,支持动态扩展。
    • ​Message Bus & Synchronization​​:
      • 通过消息总线传递事件(如 EOS 结束信号)、错误处理,并严格管理音画同步(PTS/DTS)。

消息总线是 ​GStreamer 组件间通信的全局通道​​,允许应用程序监听流水线状态、错误、标签(如媒体元数据)等事件,而无需直接与每个组件交互。

核心机制​
  • ​消息类型​​:

    • GST_MESSAGE_STATE_CHANGED(状态变更,如 PLAYING → PAUSED)
    • GST_MESSAGE_ERROR(错误通知,如解码失败)
    • GST_MESSAGE_EOS(流结束信号)
    • GST_MESSAGE_TAG(媒体元数据,如音频比特率)
    • GST_MESSAGE_QOS(服务质量事件,如缓冲不足)
  • ​工作流程​​:

    1. ​组件生成消息​​(如 decoder 解码失败时发送 ERROR 消息)。
    2. ​消息被发布到总线​​(通过 gst_element_post_message())。
    3. ​应用程序监听总线​​(通过 gst_bus_add_watch() 或轮询)。
    4. ​处理消息​​(如显示错误日志或更新 UI)。

1.3 底层:插件与数据源/接收层(Plugins & Protocols/Sources/Sinks)​

最下层为各种插件,实现具体的数据处理及音视频输出,应用不需要关注插件的细节,会由Core Framework层负责插件的加载及管理。主要分类为:

Protocols:负责各种协议的处理,file,http,rtsp等。
Sources:负责数据源的处理,alsa,v4l2,tcp/udp等。
Formats:负责媒体容器的处理,avi,mp4,ogg等。
Codecs:负责媒体的编解码,mp3,vorbis等。
Filters:负责媒体流的处理,converters,mixers,effects等。
Sinks:负责媒体流输出到指定设备或目的地,alsa,xvideo,tcp/udp等。

1.4 GStreamer组件

 1. Element

基本概念

GStreamer 中的 Element(元素) 是最基本的构建块,用于构建媒体处理的 pipeline。每一个 element 负责特定的功能,例如读取数据、解码、编码、转换格式、显示视频等。下面将从架构、类型、生命周期、Pad 连接、事件与消息处理等多个维度详细分析 GStreamer 的 Element。

在 GStreamer 中,Element 是处理媒体数据的最小单位。每个 Element 都是一个 GObject,具备标准的对象继承机制。

  • 一个 Element 可能拥有多个 Pad(输入/输出接口)。

  • 元素可以通过 Pad 链接起来形成 Pipeline

  • GStreamer 提供了丰富的内置 Element,也支持自定义 Element 开发。

Element 类型分类

GStreamer 按功能将 Element 分为以下几类:

类型功能描述
Source数据源,如 filesrc, v4l2src, udpsrc
Sink数据输出,如 autovideosink, filesink, udpsink
Filter中间处理,如 decodebin, audioconvert, videoconvert
Demuxer解复用器,如 qtdemux, tsdemux
Decoder解码器,如 avdec_h264
Encoder编码器,如 x264enc
Muxer复用器,如 mpegtsmux, mp4mux
Parser分析器,如 h264parse

Pad 结构与链接机制

 Pad 类型
  • src pad:输出数据

  • sink pad:接收数据

Pad 分类
  • Static Pad:在创建元素时就固定存在的 pad。

  • Request Pad:需要通过 API 显式请求的 pad(如 tee)。

  • Sometimes Pad:只有在运行时(如探测到流)才动态创建。

Pad 链接
  • 使用 gst_element_link()gst_element_link_many() 自动链接。

  • 使用 gst_pad_link() 可手动精细控制。

gst_element_link(source, filter);

2. Bin

Bin是一个容器,用于管理多个element,改变bin的状态时,bin会自动去修改所包含的element的状态,也会转发所收到的消息。如果没有bin,我们需要依次操作我们所使用的element。通过bin降低了应用的复杂度。
Pipeline继承自bin,为程序提供一个bus用于传输消息,并且对所有子element进行同步。当将pipeline的状态设置为PLAYING时,pipeline会在一个/多个新的线程中通过element处理数据。

3.Pipline

Pipeline(管道)是 GStreamer 中顶层的容器,负责管理整个多媒体处理流程的元素集合。

换句话说,Pipeline 是一种特殊的 Bin,但是它有更重要的任务:
除了像普通 Bin 一样管理子元素以外,它还控制整个数据流的生命周期(比如启动、暂停、停止)。

[ Pipeline (也是Bin) ]
        |
       [MyBin]
        |
    [Element1]--[Element2]

可以看到这个pipeline由8个element构成,每个element都实现各自的功能:
filesrc读取文件,oggdemux解析文件,分别提取audio,video数据,queue缓存数据,vorbisdec解码audio,autoaudiosink自动选择音频设备并输出,theoradec解码video,videoconvert转换video数据格式,autovideosink自动选择显示设备并输出。

不同的element拥有不同数量及类型的pad,只有src pad的element被称为source element,只有sink pad的被称为sink element。

element可以同时拥有多个相同的pad,例如oggdemux在解析文件后,会将audio,video通过不同的pad输出。

4. 示例

gst-launch-1.0 videotestsrc ! "video/x-raw,width=1280,height=720" ! autovideosink

用 GStreamer 启动一个流水线:

  • 生成测试视频(videotestsrc),

  • 强制设置分辨率为 1280×720,

  • 然后把画面显示到屏幕上(autovideosink)。

分模块详细拆解

1. gst-launch-1.0

  • 这是 GStreamer 的命令行工具。

  • 用来直接创建和运行一个 Pipeline。

  • -1.0 表示用 GStreamer 1.x 版本(通常是 1.14、1.18、1.20等)。

2. videotestsrc

  • Element(元素)

  • 产生一段模拟的视频信号(比如彩条、灰阶、运动图形)。

  • 通常用于测试视频链路是否正常。

  • 可以调参数,比如 pattern(不同花样:彩条、棋盘格等)。

👉 默认输出是原始的 video/x-raw 数据格式。


3. !

  • 连接符号。

  • 把上一个元素的输出连接到下一个元素的输入。

  • 在 GStreamer 中,元素通过 Pad 连接起来! 就是链接的意思。


4. "video/x-raw,width=1280,height=720"

  • 这是一个CapsFilter(能力过滤器,Capabilities Filter)。

  • 用来约束数据格式,确保数据流是指定的格式。

  • "video/x-raw" 表示原始未压缩视频帧。

  • width=1280,height=720 指定输出分辨率是 1280×720(HD720p)。

👉 作用就是告诉 GStreamer:

“我要 1280x720 的原始视频流,中间不能自动帮我乱转。”

如果 videotestsrc 输出不是这个尺寸,GStreamer 会自动插入一个 videoconvertvideoscale 等内部元素来匹配。


5. autovideosink

  • Element(元素)

  • 自动选择一个合适的视频输出模块(sink)。

  • 具体使用什么后端,跟你的平台/系统环境有关,比如:

    • Linux:可能是 xvimagesinkximagesinkglimagesink

    • Windows:可能是 d3dvideosinkdirectdrawsink

    • macOS:可能是 osxvideosink

1.5 Gstreamer 消息通信

         在pipeline运行的过程中,各个element以及应用之间不可避免的需要进行数据消息的传输,gstreamer提供了bus系统以及多种数据类型(Buffers、Events、Messages,Queries)来达到此目的:

Bus

       Bus是gstreamer内部用于将消息从内部不同的streaming线程,传递到bus线程,再由bus所在线程将消息发送到应用程序。应用程序只需要向bus注册消息处理函数,即可接收到pipline中各element所发出的消息,使用bus后,应用程序就不用关心消息是从哪一个线程发出的,避免了处理多个线程同时发出消息的复杂性。

Buffers

用于从sources到sinks的媒体数据传输。

Events

用于element之间或者应用到element之间的信息传递,比如播放时的seek操作是通过event实现的。

Messages

是由element发出的消息,通过bus,以异步的方式被应用程序处理。通常用于传递errors, tags, state changes, buffering state, redirects等消息。消息处理是线程安全的。由于大部分消息是通过异步方式处理,所以会在应用程序里存在一点延迟,如果要及时的相应消息,需要在streaming线程捕获处理。

Queries

用于应用程序向gstreamer查询总时间,当前时间,文件大小等信息。

2. 代码是实例

1.demo1

#include <gst/gst.h>  // 引入 GStreamer 主头文件

#ifdef __APPLE__
#include <TargetConditionals.h>  // 针对 Apple 平台进行条件编译
#endif

// 程序主体逻辑
int
tutorial_main (int argc, char *argv[])
{
  GstElement *pipeline; // 用于保存管道元素
  GstBus *bus;           // 用于接收消息的总线
  GstMessage *msg;       // 接收到的消息对象

  /* 初始化 GStreamer 库 */
  gst_init (&argc, &argv);

  /* 构建播放管道,使用 playbin 元素播放在线视频 */
  pipeline =
      gst_parse_launch
      ("playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm",
      NULL);

  /* 将管道设置为播放状态,开始播放 */
  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  /* 获取管道的消息总线,用于监听错误或播放结束(EOS)事件 */
  bus = gst_element_get_bus (pipeline);

  /* 阻塞等待,直到接收到错误(ERROR)或播放结束(EOS)消息 */
  msg =
      gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
      GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

  /* 简单处理错误情况(详细错误处理可见后续教程) */
  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
    g_printerr ("An error occurred! Re-run with the GST_DEBUG=*:WARN "
        "environment variable set for more details.\n");
  }

  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
    g_printerr ("------------Over-----------\n");
  }


  /* 释放消息对象,防止内存泄漏 */
  gst_message_unref (msg);

  /* 释放总线对象 */
  gst_object_unref (bus);

  /* 将管道状态设置为 NULL,完全停止并释放资源 */
  gst_element_set_state (pipeline, GST_STATE_NULL);

  /* 释放管道对象 */
  gst_object_unref (pipeline);

  return 0; // 程序正常退出
}

/* 标准 C 程序入口 */
int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
  /* 在 macOS 平台,使用 GStreamer 提供的特殊主循环接口 */
  return gst_macos_main ((GstMainFunc) tutorial_main, argc, argv, NULL);
#else
  /* 其他平台直接运行主体逻辑 */
  return tutorial_main (argc, argv);
#endif
}

2.通过实例化每个元素来手动构建管道 并将它们全部链接在一起

#include <gst/gst.h>

#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

int
tutorial_main (int argc, char *argv[])
{
  GstElement *pipeline, *source, *sink;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;

  /* Initialize GStreamer */
  gst_init (&argc, &argv);

  /* Create the elements */
  source = gst_element_factory_make ("videotestsrc", "source");
  sink = gst_element_factory_make ("autovideosink", "sink");

  /* Create the empty pipeline */
  pipeline = gst_pipeline_new ("test-pipeline");

  if (!pipeline || !source || !sink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Build the pipeline */
  gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
  if (gst_element_link (source, sink) != TRUE) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (pipeline);
    return -1;
  }

  /* Modify the source's properties */
  g_object_set (source, "pattern", 0, NULL);

  /* Start playing */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (pipeline);
    return -1;
  }

  /* Wait until error or EOS */
  bus = gst_element_get_bus (pipeline);
  msg =
      gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
      GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

  /* Parse message */
  if (msg != NULL) {
    GError *err;
    gchar *debug_info;

    switch (GST_MESSAGE_TYPE (msg)) {
      case GST_MESSAGE_ERROR:
        gst_message_parse_error (msg, &err, &debug_info);
        g_printerr ("Error received from element %s: %s\n",
            GST_OBJECT_NAME (msg->src), err->message);
        g_printerr ("Debugging information: %s\n",
            debug_info ? debug_info : "none");
        g_clear_error (&err);
        g_free (debug_info);
        break;
      case GST_MESSAGE_EOS:
        g_print ("End-Of-Stream reached.\n");
        break;
      default:
        /* We should not reach here because we only asked for ERRORs and EOS */
        g_printerr ("Unexpected message received.\n");
        break;
    }
    gst_message_unref (msg);
  }

  /* Free resources */
  gst_object_unref (bus);
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);
  return 0;
}

int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
  return gst_macos_main ((GstMainFunc) tutorial_main, argc, argv, NULL);
#else
  return tutorial_main (argc, argv);
#endif
}

3.动态管道

在 GStreamer 中,大多数元素的 pad 是静态的,你在构建管道时就可以直接链接它们。

但有一些元素(比如 uridecodebindecodebin)的输出 pad 是运行时才创建的,这是因为它们在播放前并不知道流中会有什么类型(音频?视频?格式?)——它们会在检测到实际媒体类型之后,动态添加 pad。


🌱 动态 Pad 触发流程

  1. 你将一个如 uridecodebin 的元素添加到 pipeline 中。

  2. 设置 URI 或输入文件后,它开始解复用并检测内容。

  3. 识别出例如视频流(video/x-raw)或音频流(audio/x-raw)。

  4. GStreamer 会为该流类型创建一个新的 pad(如 src_0)。

  5. 会触发一个 pad-added 信号。

  6. 程序员负责响应这个信号,把新 pad 和其它元素链接起来。

[ uridecodebin ]
        |
   +----+----+
   |         |
[src pad 0] [src pad 1] ...  ← 动态创建
   |         |
[audio path] [video path]

3.硬件加速视频编解码(以imx8qm 为例)

(1)linux 系统中ISP

Camera系统之ISP综述(一)_camera sensor里面有isp为啥还需要平台有isp-CSDN博客

图像信号处理(ISP)核心是一个完整的视频与静态图像输入处理模块,集成了图像处理及色彩空间转换功能(如RAW Bayer格式到YUV格式的转换)。该图像处理单元支持以下两类传感器:

  1. ​简易CMOS传感器​​:输出未经处理的RGB Bayer模式数据。
  2. ​集成YCbCr处理的传感器​​:直接输出经内部处理的YCbCr格式数据。

IMX8Q ISP支持的输入输出有:

输入:

格式描述典型应用场景
​YCbCr420​YUV 4:2:0 色度抽样,亮度(Y)全分辨率,色度(CbCr)水平垂直半分辨率。常见于H.264/MPEG视频流
​YCbCr422​YUV 4:2:2 色度抽样,亮度全分辨率,色度水平半分辨率(垂直全分辨率)。专业视频采集、HDMI输入
​RAW8​8位Bayer RAW数据,每个像素仅存储单色信息(R/G/B)。低功耗摄像头,需ISP处理
​RAW10​10位Bayer RAW,比RAW8更高的动态范围。高端手机/工业摄像头
​RAW12​12位Bayer RAW,进一步扩展动态范围。专业摄影、医疗成像
​RAW14​14位Bayer RAW,极高动态范围(HDR)。天文摄影、科学相机

输出:

格式描述与输入格式的关联
​YUYV 4:2:2​打包式YUV 4:2:2,每两个像素共享一组CbCr(如YUYVYUYV...)。通常来自YCbCr422输入
​NV12 4:2:0​平面式YUV 4:2:0,亮度(Y)单独存储,色度(CbCr)交错存储且半分辨率。通常由YCbCr420转换而来
​NV16 4:2:2​平面式YUV 4:2:2,色度水平半分辨率但垂直全分辨率。适用于YCbCr422输入
​RG10​10位Bayer RAW的打包格式(如RGrGrGbB...),保留原始传感器数据。直接来自RAW10/RAW12输入

关键点解析​

  1. ​色度抽样(Chroma Subsampling)​

    • ​4:2:0​​(NV12):色度分辨率减半(节省带宽,适合视频编码)。
    • ​4:2:2​​(YUYV/NV16):色度水平减半,垂直保留(适合实时处理)。
    • ​4:4:4​​:无抽样,用于无损处理(未在列表中,但部分高端设备支持)。
  2. ​RAW数据流​

    • RAW8/10/12/14是传感器原始数据,需ISP进行去马赛克(Demosaic)转换为RGB/YUV。
    • ​RG10​​是RAW数据的打包输出格式,可能用于后期处理或直接存储。
  3. ​转换逻辑​

    • ​YCbCr420 → NV12​​:直接对应,均为4:2:0。
    • ​YCbCr422 → YUYV/NV16​​:YUYV为打包式,NV16为平面式。
    • ​RAW → RG10​​:RAW数据经ISP处理后仍保留Bayer模式(未去马赛克)。

(2)IMX8系统中ISP 软件框架

 ISI(Independent Sensor Interface)​

定位与功能​

  • ​核心目标​​:提供统一的传感器控制接口,屏蔽不同传感器的硬件差异,使ISP软件无需针对每个传感器修改代码。
  • ​关键特性​​:
    • ​标准化API​​:定义传感器初始化、寄存器读写、曝光/增益控制等通用操作(如IsiSetGainIssIsiGetIntegrationTimeIss)。
    • ​多模式支持​​:适配线性/HDR模式(如双曝光、三曝光)、Bayer格式(RGGB/GRBG等)。
    • ​跨平台兼容​​:基于C语言实现,与BSP(如Linux LF5.15.32)解耦。

​实现机制​

  • ​结构体与函数指针​
    ISI通过结构体(如IsiSensor_t)封装传感器操作,以函数指针形式暴露接口。例如:

typedef struct {
    const char *pszName;                      // 传感器名称
    RESULT (*pIsiRegisterWriteIss)(...);     // 写寄存器函数指针
    RESULT (*pIsiSetGainIss)(...);           // 设置增益函数指针
    // ...其他操作函数
} IsiSensor_t;
  • ​驱动开发​​:传感器厂商需实现这些函数(如OV2775_IsiSetGainIss),并注册到ISI框架。
  • 动态加载​
    通过IsiCamDrvConfig_t结构动态绑定传感器驱动:
IsiCamDrvConfig_t config = {
    .CameraDriverID = 0x2770,                // 传感器ID(如OV2775)
    .pfIsiGetSensorIss = OV2775_GetSensorIss, // 驱动入口函数
};

VVCAM(Vivante Camera Integration Layer)​

定位与功能​

  • ​核心目标​​:在Linux内核中实现传感器、MIPI-CSI、ISP硬件的驱动集成,提供V4L2兼容接口。
  • ​关键特性​​:
    • ​V4L2子设备抽象​​:将传感器、ISP模块注册为/dev/v4l-subdevX,支持标准IOCTL。
    • ​双工作模式​​:
      • ​Native模式​​:直接通过IOCTL(如VVSENSORIOC_S_EXP)控制传感器。
      • ​V4L2模式​​:兼容Linux V4L2框架(如VIDIOC_S_FMT设置分辨率)。
    • ​硬件加速​​:集成Vivante ISP的专用硬件模块(如DPF去噪、WDR处理)。

实现机制​

  • ​内核驱动架构

User Space
├── /dev/videoX (V4L2设备节点)
└── /dev/v4l-subdevX (传感器节点)
Kernel Space
├── VVCAM核心驱动
│   ├── MIPI-CSI控制器驱动
│   ├── ISP硬件寄存器操作
│   └── 缓冲区管理(DMA/MMAP)
└── 传感器驱动(如OV2775_mipi_v3.c)

ISI与VVCAM的协作关系​

初始化阶段​

  • VVCAM加载传感器驱动(如OV2775),调用IsiCreateSensorIss创建ISI实例。
  • ISI将传感器操作函数注册到VVCAM的V4L2子设备。

​运行时控制​

  • 用户通过V4L2命令(如VIDIOC_S_EXPOSURE)触发VVCAM内核驱动。
  • VVCAM通过ISI接口(如IsiSetIntegrationTimeIss)最终控制传感器寄存器。

​数据处理​

  • 传感器数据通过MIPI-CSI传输到ISP,VVCAM管理DMA缓冲区,ISI提供元数据(如曝光时间)。

(3)V4L2 模式下的摄像头传感器驱动程序:移植指南

要移植相机传感器,必须按照以下部分所述执行以下步骤:

在 CamDevice 中定义传感器属性并创建传感器实例。

ISS 驱动程序和 ISP 媒体服务器。

传感器校准文件。

VVCAM 驱动程序创建。

设备树修改。

(3-1)在 CamDevice 中定义传感器属性并创建传感器实例

(4)IMX8QM+MAX9286+MAX96705

max9286+max96705摄像头调试--基于imx8qm_max96755-CSDN博客

MAX9286芯片

MAX9286是一款四通道1.5Gbps GMSL解串器,支持同轴电缆或屏蔽双绞线(STP)输入,并输出CSI-2信号。

串行输入接口(GMSL 解串输入)

参数​​规格​
​接口类型​4 通道差分输入(IN0± ~ IN3±)
​支持电缆​50Ω 同轴电缆 或 100Ω 屏蔽双绞线(STP)
​数据速率​每通道 500Mbps ~ 1.5Gbps(总带宽 6Gbps)
​输入阻抗​50Ω(同轴) / 100Ω(STP)
​可编程均衡器​11 级可调(2.1dB ~ 13dB)
​极性交换​支持(通过 SWITCHINx 寄存器配置)
​ESD 保护​±8kV(接触放电,ISO 10605 / IEC 61000-4-2)
​典型应用​连接 MAX9271/MAX9273 等 GMSL 串行器

 CSI-2 输出接口(MIPI 输出)​

​参数​​规格​
​接口类型​4 对差分数据通道(DOUT0± ~ DOUT3±) + 1 对差分时钟(CLK±)
​数据速率​每通道 80Mbps ~ 1.2Gbps(支持 D-PHY 1.2Gbps)
​输出模式​可配置 1/2/4 通道输出
​极性交换​支持(通过 SWITCHDOx 寄存器配置)
​输出阻抗​50Ω(单端)
​数据格式​RAW8/10/12/14/16、YUV422、RGB565/666/888 等(通过 DATATYPE 配置)
​典型应用​连接 SoC(如 NVIDIA Jetson、TI Jacinto、Renesas R-Car)

 控制通道接口(I2C/UART)

​参数​​规格​
​接口模式​I2C 或 UART(由 I2CSEL 引脚选择)
​I2C 模式​- 标准 I2C(SCL/TX、SDA/RX)
- 支持 9.6kbps ~ 1Mbps(可编程)
- 支持时钟拉伸
​UART 模式​- 半双工(TX/RX)
- 9.6kbps ~ 1Mbps(自动波特率检测)
- 支持 UART-to-I2C 转换
​设备地址​可编程(默认由 ADD0/ADD1 引脚设置)
​典型应用​连接 MCU(如 STM32、NXP i.MX)或 SoC

 GPIO 与状态监控接口​

​参数​​规格​
​GPIO0/GPIO1​开漏输出(带内部 60kΩ 上拉),可配置为输入/输出
​FRSYNC/GPI​帧同步输入/输出(可配置为 GPIO)
​LOCK​开漏输出,指示 PLL 锁定状态(高=锁定)
​ERR​开漏输出,指示数据错误(低=错误)
​LFLT​开漏输出,指示线路故障(低=故障)
​LMN0~LMN3​线路故障检测输入(支持短路、开路、电池短路检测)

 典型应用配置

​功能​​推荐配置​
​4x 摄像头系统​- 4x GMSL 输入(IN0~IN3)
- CSI-2 4 通道输出
- I2C 控制所有摄像头
​2x 摄像头+GPIO​- 2x GMSL 输入(IN0/IN1)
- CSI-2 2 通道输出
- GPIO 控制外部设备
​低功耗模式​PWDN=0 或 SLEEP=1(关闭串行链路,仅保留控制通道)

IMX8QM 结构图

 MIPI接口介绍

MIPI是什么意思? MIPI是什么接口? MIPI接口多少引脚?全面解读MIPI测试测量技术 - 知乎

设备树配置

i2c@58226000 {
	compatible = "fsl,imx8qm-lpi2c"; // 匹配NXP LPI2C驱动
	reg = <0x0 0x58226000 0x0 0x1000>; // 寄存器基地址 + 大小(64-bit地址格式)
	interrupts = <0x8 0x4>; // 中断号,使用中断控制器 phandle 为 0xc4
	interrupt-parent = <0xc4>;

	clocks = <0x3 0x112 0x3 0x23b>; // 外设时钟和IPG总线时钟
	clock-names = "per", "ipg";

	assigned-clocks = <0x3 0x112>; // 分配并设置外设时钟
	assigned-clock-rates = <0x16e3600>; // 设置为25MHz(0x16e3600)

	power-domains = <0xee>; // 电源域信息,控制I2C模块供电
	status = "okay";

	#address-cells = <0x1>;
	#size-cells = <0x0>;
	clock-frequency = <0x186a0>; // 设置I2C总线默认频率为100kHz(0x186a0)

		max9286_mipi@6A {
			compatible = "maxim,max9286_mipi";
			reg = <0x6a>;// I2C地址为0x6A
			pinctrl-names = "default";
			pinctrl-0 = <0xef>;// 使用设备树中编号为 0xef 的 pinctrl 配置
			clocks = <0x3 0x0>;// 时钟来源(dummy 或外部 MCLK)
			clock-names = "capture_mclk";
			mclk = <0x19bfcc0>;// 频率为27MHz(0x19bfcc0 = 27000000)
			mclk_source = <0x0>;// MCLK来源选择(驱动定义)
			pwn-gpios = <0xf0 0x1b 0x0>;// Power Down 引脚:GPIO控制摄像头模块上电,GPIO控制器phandle=0xf0,IO编号0x1b(27),低电平有效
			virtual-channel;// 启用虚拟通道支持(可管理多摄像头流)
           //MIPI CSI 接口绑定:port / endpoint
			port {

				endpoint {
					remote-endpoint = <0xf1>;// 对应接收端设备的endpoint(例如CSI0接口)
					data-lanes = <0x1 0x2 0x3 0x4>;// 使用 MIPI 数据通道 1~4
					linux,phandle = <0xc5>;
					phandle = <0xc5>;
				};
			};
		};
	};
  • 由设备树可知MAX9286在linux 中被视做一个i2C设备

相机I2C地址

root@imx8qmmek:~# i2cdetect -r -y 9
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- 36 37 38 39 3a -- -- -- -- --
40: -- 41 42 43 44 45 -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- UU -- -- -- -- --

为什么i2c地址是以上的呢

观察max9286.c驱动如下:

// 定义支持的最大摄像头数(即最大 MAX96705 数量)
#define MAX96705_MAX_SENSOR_NUM  4

// 定义解串器 MAX9286 的 I2C 地址
#define ADDR_MAX9286             0x6A

// 定义串行器 MAX96705 的起始地址
#define ADDR_MAX96705           0x40

// 定义广播地址(用于统一对所有 MAX96705 写指令)
#define ADDR_MAX96705_ALL       (ADDR_MAX96705 + 5)  // 0x45

// 定义摄像头类型对应的出厂 I2C 地址
#define ADDR_OV_SENSOR          0x30  // OmniVision 摄像头默认地址
#define ADDR_AP_SENSOR          0x36  // Aptina 或其他品牌摄像头默认地址

// 开始循环初始化每个 MAX96705 + 摄像头组合
for (i = 1; i <= MAX96705_MAX_SENSOR_NUM; i++) {

    // 如果该路(i)没有连接摄像头(根据传感器存在的位图判断),则跳过
    if (((0x1 << (i - 1)) & max9286_data->sensor_is_there) == 0)
        continue;

    // 开启 MAX9286 的控制链路和视频链路,第 i 位设置为 1(0x11 表示启用 control 和 video channel)
    reg |= (0x11 << (i - 1));
    max9286_write_reg(max9286_data, 0x0A, reg);  // STEP 15,寄存器 0x0A 是控制链路寄存器

    // 给第 i 路的 MAX96705 设置新的 I2C 地址(通过广播通道或默认地址设置)
    max96705_write_reg(max9286_data, 0, 0x00, (ADDR_MAX96705 + i) << 1);  // 写入寄存器 0x00 设置新地址
    msleep(2);  // 等待配置生效

    // 配置串行器的解串器目标地址(告诉 MAX96705 数据应该发送到哪个 MAX9286)
    max96705_write_reg(max9286_data, i, 0x01, ADDR_MAX9286 << 1);

    // 设置摄像头 alias 地址(新的地址),主控后续通过 alias 地址访问 sensor(用于转发)
    max96705_write_reg(max9286_data, i, 0x09, (sensor_addr + i) << 1);  // 0x09 是 alias 寄存器

    // 设置摄像头的默认地址(设备实际出厂 I2C 地址)
    max96705_write_reg(max9286_data, i, 0x0A, sensor_addr << 1);  // 通常为 0x30 或 0x36

    // 设置所有串行器的广播地址,允许主控对所有串行器发广播命令(非必要,但通常保留)
    max96705_write_reg(max9286_data, i, 0x0B, ADDR_MAX96705_ALL << 1);

    // 设置该串行器的新地址,后续通信将使用此地址代替默认值或广播地址
    max96705_write_reg(max9286_data, i, 0x0C, (ADDR_MAX96705 + i) << 1);  // 设置为 0x41、0x42 等

    msleep(1);  // 等待设置稳定

    // 打印初始化完成的摄像头索引(调试信息)
    pr_info("max9286_mipi: initialized sensor  = 0x%02x.\n", i);

    // 读取串行器寄存器,用于调试确认配置正确
    max96705_dump_registers(max9286_data, i);
}
通道编号 (i)MAX96705 地址摄像头 alias 地址实际 sensor 地址
10x410x370x30 或 0x36
20x420x380x30 或 0x36
30x430x390x30 或 0x36
40x440x3A0x30 或 0x36

 数据从MIPI-CSI2接口接收后传输给ISI模块处理

 

(5)V4L2 生成生成设备号流程

在 i.MX8QM 平台上,MAX9286 相机的注册到内核的过程主要涉及以下几个关键步骤,结合 V4L2 框架和 NXP 的 BSP 实现:

MAX9286 驱动的相机探测逻辑是:

  1. ​通过 I2C 读取硬件寄存器​​ 检测物理连接和传感器身份。
  2. ​动态配置 I2C 地址和 CSI 通道​​ 适配实际拓扑。
  3. ​为每个相机注册 V4L2 子设备​​,通过媒体框架暴露到用户空间。
<1>检测 MAX9286 自身状态​

读取芯片ID​​:
通过 I2C 读取 MAX9286 的寄存器 0x1E,验证返回值是否为 0x40(芯片标识符)。

retval = max9286_read_reg(max9286_data, 0x1e);
if (retval != 0x40) {
    dev_err(dev, "MAX9286 not found!\n");
    return -ENODEV;
}
<2>检测物理连接状态​
  • ​读取链路状态寄存器 0x49​:
    • ​位 0~3​​:表示本地链路检测(如电缆连接)。
    • ​位 4~7​​:表示远端相机电源状态。
reg = max9286_read_reg(max9286_data, 0x49);
max9286_data->sensor_is_there = ((reg >> 4) & 0xF) | (reg & 0xF);

示例​​:sensor_is_there = 0x05(二进制 0101)表示 ​​链路 0 和 2​​ 连接了相机。

<3>验证相机传感器​

通过 MAX96705 访问 OV10635 的寄存器​​:
动态分配 I2C 地址后,读取传感器的 PID(0x300A)和 VER(0x300B)寄存器:

ov10635_read_reg(max9286_data, index, OV10635_REG_PID, &pid);
if (pid != 0xA6) {
    dev_err(dev, "OV10635 not found on link %d!\n", index);
    return -ENODEV;
}

<4>动态注册检测到的相机​​​
  • ​重映射 I2C 地址​​:
    为每个检测到的 MAX96705 分配唯一的 I2C 地址(如 0x410x42 等):

max96705_write_reg(max9286_data, i, 0x00, (ADDR_MAX96705 + i) << 1);
  •  设置 CSI 虚拟通道(VC)​​:

根据相机数量配置 MAX9286 的通道映射(寄存器 0x0B):

max9286_write_reg(max9286_data, 0x0B, 0xE4); // 单相机映射到 VC0

详细过程 

1. 硬件探测阶段​

​(1) I2C 设备注册​

​MAX9286 驱动加载

// drivers/media/i2c/max9286.c
module_i2c_driver(max9286_i2c_driver); // 注册I2C驱动

内核匹配设备树中的 compatible = "maxim,max9286",调用 probe()

​传感器 I2C 探测​
MAX9286 通过反向通道(Reverse Channel)访问传感器:

max9286_probe() {
    // 1. 初始化GMSL链路
    max9286_write(reg, val); // 配置解串器寄存器
    
    // 2. 探测传感器I2C地址(如0x20)
    i2c_remote = i2c_new_dummy(client->adapter, 0x20);
    sensor_client = devm_kzalloc(dev, sizeof(*sensor_client), GFP_KERNEL);
}

2. V4L2 异步注册流程​

​(1) 主设备(MAX9286)初始化​
max9286_probe() {
    // 1. 注册V4L2主设备
    v4l2_device_register(dev, &max9286->v4l2_dev);

    // 2. 初始化异步通知器
    INIT_LIST_HEAD(&max9286->notifier.waiting);
    max9286->notifier.ops = &max9286_notify_ops;

    // 3. 添加子设备描述(传感器)
    struct v4l2_async_subdev *asd;
    asd = v4l2_async_notifier_add_i2c_subdev(&max9286->notifier, 0x20, sizeof(*asd));
    
    // 4. 注册异步通知器
    v4l2_async_notifier_register(&max9286->v4l2_dev, &max9286->notifier);
}
(2) 子设备(传感器)绑定​
// 传感器驱动(如ov10635.c)
ov10635_probe() {
    // 1. 注册V4L2子设备
    v4l2_i2c_subdev_init(sd, client, &ov10635_ops);
    
    // 2. 实现媒体实体接口
    sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
    pads[0].flags = MEDIA_PAD_FL_SOURCE;
    media_entity_pads_init(&sd->entity, 1, pads);
    
    // 3. 异步注册(由MAX9286的notifier触发)
    v4l2_async_register_subdev(sd);
}
​(3) 异步绑定回调​

当传感器注册完成时,触发 MAX9286 的 bound() 回调:

static const struct v4l2_async_notifier_operations max9286_notify_ops = {
    .bound = max9286_notify_bound, // 绑定成功回调
};

max9286_notify_bound() {
    // 1. 获取传感器子设备指针
    struct v4l2_subdev *sensor_sd = max9286->notifier.subdevs[0]->sd;
    
    // 2. 创建媒体链路(MAX9286 -> 传感器)
    media_create_pad_link(&sensor_sd->entity, SENSOR_PAD_SRC,
                        &max9286->sd.entity, MAX9286_PAD_SINK, 0);
}

3. CSI-2 接口与 Video 节点生成​

​(1) CSI-2 控制器绑定​
// i.MX8QM CSI驱动(mx8qm_csi.c)
csi_probe() {
    // 1. 注册V4L2子设备
    v4l2_subdev_init(&csi->sd, &csi_subdev_ops);
    
    // 2. 创建媒体实体
    pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
    media_entity_pads_init(&csi->sd.entity, 1, pads);
    
    // 3. 与MAX9286建立链路
    media_create_pad_link(&max9286->sd.entity, MAX9286_PAD_SOURCE,
                        &csi->sd.entity, CSI_PAD_SINK, 0);
}
(2) Video 设备节点创建​
// CSI驱动中注册video_device
csi_probe() {
    // 1. 初始化video_device
    csi->vdev = video_device_alloc();
    csi->vdev->fops = &csi_fops;
    csi->vdev->v4l2_dev = &csi->v4l2_dev;
    
    // 2. 注册video设备
    video_register_device(csi->vdev, VFL_TYPE_VIDEO, -1); // 生成/dev/videoX
}

​4. 用户空间访问 /dev/videoX

​(1) 节点生成路径​
MAX9286 I2C探测 → 传感器异步注册 → 媒体链路建立 → CSI-2绑定 → video_register_device()
max9286_probe()
  → v4l2_async_notifier_register()
    → ov10635_probe() [传感器]
      → v4l2_async_register_subdev()
        → max9286_notify_bound() [回调]
          → media_create_pad_link()
            → csi_probe() [i.MX8QM CSI]
              → video_register_device() → /dev/video0

5.IMX8QM Gstreamer 加速

(1)🔄 普通拷贝流程(非零拷贝)

  1. 摄像头驱动将图像写入内核空间 buffer。

  2. 应用程序从内核空间复制数据到用户空间处理。

  3. 编码器再次从用户空间读取数据 → 再拷贝。

问题: 多次拷贝增加 CPU 负担、延迟变大。

(2)DMA加速

gst-launch-1.0 v4l2src device=/dev/video0 num-buffers=300 io-mode=dmabuf ! \
  'video/x-raw,format=(string)NV12,width=1920,height=1080,framerate=(fraction)30/1' ! \
  queue ! v4l2h264enc output-io-mode=dmabuf-import ! avimux ! filesink location=test.avi

✅ 零拷贝流程(使用 dmabuf

  1. 摄像头驱动将图像采集到 DMA buffer(由内核分配的共享内存)。

  2. dmabuf 机制创建一个 文件描述符(fd),表示这块 buffer。

  3. 这个 fd 被 GStreamer 元素之间传递,无需复制实际数据。

  4. 编码器直接通过这个 fd 访问图像数据,完成编码。

[Camera] → [DMA Buffer (dmabuf)] → [H.264 Encoder] → [AVI Mux] → [File]
       无需复制            直接访问              流处理

(3)MMAP加速

gst-launch-1.0 v4l2src io-mode=2 device=/dev/video0 do-timestamp=true ! \
  'video/x-raw, width=1280, height=720, framerate=30/1, format=UYVY' ! autoconvert ! \
  'video/x-raw, width=1280, height=720, framerate=30/1, format=I420' ! autovideosink sync=false

虽然不如 dmabuf 那样灵活,但 MMAP 也可以实现“准零拷贝”,关键在于内核将驱动的 buffer 映射到用户空间,避免实际数据复制。

  • 内核将摄像头驱动分配的一块连续内存通过 mmap() 暴露给用户态。

  • GStreamer 的 v4l2src 使用这块映射内存作为源数据,避免了“内核→用户”的数据复制。

[Camera Driver Buffer] ← mmap() → [User-space GStreamer]
         ↑                     ↑
       共享内存映射          无需拷贝

6. MAX9286 驱动

驱动入口

  • 驱动通过module_i2c_driver(max9286_i2c_driver)宏注册为I2C驱动
  • 实际入口是max9286_probe()函数,当匹配的I2C设备被发现时调用
/*
 * MAX9286 I2C 驱动结构体定义
 * 这个结构体定义了MAX9286解串器芯片的I2C驱动参数和操作函数
 */
static struct i2c_driver max9286_i2c_driver = {
    /* 驱动核心信息 */
    .driver = {
        /* 模块所有者,设置为当前模块 */
        .owner = THIS_MODULE,
        
        /* 驱动名称,必须唯一 */
        .name   = "max9286_mipi",
        
        /* 
         * 设备树匹配表 
         * 用于与设备树(DTS)中的节点匹配
         * of_match_ptr宏确保在没有配置CONFIG_OF时也能安全编译
         */
        .of_match_table = of_match_ptr(max9286_of_match),
    },
    
    /* 设备探测函数,当I2C设备匹配时调用 */
    .probe  = max9286_probe,
    
    /* 设备移除函数,当设备断开或驱动卸载时调用 */
    .remove = max9286_remove,
    
    /* 
     * I2C设备ID表 
     * 用于非设备树情况下的设备匹配
     * 本例中为空表,表示完全依赖设备树匹配
     */
    .id_table = max9286_id,
};

max9286_probe驱动

max9286_probe 是 MAX9286 驱动的主要初始化函数,当内核检测到与驱动匹配的 I2C 设备时调用。下面我将详细分析这个函数的各个部分:

static int max9286_probe(struct i2c_client *client,
        const struct i2c_device_id *id)

初始化和资源分配

struct device *dev = &client->dev;
struct sensor_data *max9286_data;
struct v4l2_subdev *sd;
int retval;
int ret = 0;
int err = -1;
  • 获取设备指针
  • 声明驱动数据结构体指针
  • 声明返回值和错误码变量

内存分配和基础设置

max9286_data = devm_kzalloc(dev, sizeof(*max9286_data), GFP_KERNEL);
if (!max9286_data)
    return -ENOMEM;
  • 使用设备资源管理API分配内存
  • 检查分配是否成功

时钟资源获取

max9286_data->sensor_clk = devm_clk_get(dev, "capture_mclk");
if (IS_ERR(max9286_data->sensor_clk)) {
    dev_err(dev, "clock-frequency missing or invalid\n");
    return PTR_ERR(max9286_data->sensor_clk);
}

设备树参数解析

retval = of_property_read_u32(dev->of_node, "mclk", &(max9286_data->mclk));
retval = of_property_read_u32(dev->of_node, "mclk_source", 
        (u32 *)&(max9286_data->mclk_source));

//从设备树读取时钟频率参数
//读取时钟源配置
max9286_data->pwn_gpio = of_get_named_gpio(dev->of_node, "pwn-gpios", 0);
if (!gpio_is_valid(max9286_data->pwn_gpio)) {
    dev_err(dev, "no sensor pwdn 0 pin available\n");
    return -ENODEV;
}

retval = devm_gpio_request_one(dev, max9286_data->pwn_gpio, 
        GPIOF_OUT_INIT_HIGH, "max9286_pwd");
if (retval < 0)
    return retval;

//从设备树读取时钟频率参数
//读取时钟源配置



SoC GPIO27 ────┬───[电阻]───┬─── MAX9286的PWREN引脚
               │           │
              [电容]       [下拉电阻]
               │           │
              GND         GND

MAX9286-V4L2架构关系图​

 对于MAX9286这种复杂相机系统需要使用   媒体控制器(Media Controller)

​组件​​V4L2​​媒体控制器(Media Controller)​
​主要职责​提供视频设备的统一API(如摄像头、编解码器)描述和管理硬件内部的数据流拓扑关系
​操作对象​设备节点(如/dev/videoX媒体实体(Media Entity)和链接(Link)
​用户空间工具​v4l2-ctlGStreamermedia-ctlv4l2-ctl --list-devices
​适用场景​简单设备(如USB摄像头)复杂多组件设备(如MIPI CSI-2相机管道)

 驱动中媒体控制器和 V4l2的关系

 

  • ​V4L2的角色​​:
    • 提供标准化的视频设备操作接口(如VIDIOC_STREAMONVIDIOC_REQBUFS
    • 管理缓冲区队列(DMA Buffer)和流控制
  • ​媒体控制器的角色​​:
    • 定义硬件内部组件的连接关系(如MAX9286的VC0 Pad → CSI2接收器
    • 动态配置数据流路径(通过media-ctl修改Link状态)

用户查看完整链路  :media-ctl -p -d /dev/media0

​输出示例​​:

Entity: max9286 (1 pads, 1 links)
          Type: V4L2 Subdev
          Pad0: Source [VPAD]
            -> "imx8-csi":0 [ENABLED]

异步注册到V4L2和媒体控制器

为什么需要异步注册?​

​ 硬件依赖问题​

  • ​典型场景​​:MAX9286需要等待CSI主机控制器(如i.MX8的CSI模块)先初始化完成,才能建立MIPI CSI-2链路。
  • ​传统同步注册的缺陷​​:
    • 若按顺序加载驱动,可能因依赖设备未就绪导致初始化失败。
    • 内核模块加载顺序不可控(尤其热插拔场景)。

动态拓扑需求​

  • MAX9286的4个VC(Virtual Channel)Pads可能需要动态连接到不同的接收器(如ISP/CSI/Video Encoder)。
  • 异步机制允许​​运行时​​根据实际需求绑定设备。

异步注册如何工作

// 1. 定义异步子设备
struct v4l2_async_subdev asd = {
    .match_type = V4L2_ASYNC_MATCH_OF, // 使用设备树匹配
};

// 2. 注册异步子设备
v4l2_async_register_subdev(&max9286->subdev);

 

  • 当新增一个GMSL摄像头时,异步机制动态扩展拓扑,无需重启驱动。
  • 异步注册确保所有依赖设备就绪后,才调用pm_runtime_get_sync()上电。

具体到MAX9286的优势​

多摄像头支持​​:

  • 4个VC Pads可独立绑定到不同处理单元:
# VC0 → CSI0 (原始数据)
# VC1 → ISP (图像处理)
# VC2 → Encoder (H.264编码)

解耦初始化顺序​​:避免硬编码依赖关系。

​支持动态拓扑​​:适应多变的硬件连接场景。

​统一管理接口​​:通过V4L2和媒体控制器提供一致的用户空间API。

当开始相机打开流后,驱动如何处理

用户空间触发流启动

// 用户空间示例(如v4l2-ctl或GStreamer)
ioctl(fd, VIDIOC_STREAMON, &buf_type);

V4L2核心层处理​

参数验证​

  • 检查缓冲区类型(V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
  • 确认已通过VIDIOC_REQBUFS分配缓冲区

媒体链路检查​

  • 通过媒体控制器验证数据路径完整性:

 MAX9286驱动处理流程​

 v4l2_ioctl() → __video_do_ioctl() → vb2_ioctl_streamon() → vb2_start_streaming() 
→ v4l2_subdev_call(sd, video, s_stream, 1)

硬件配置(max9286_s_stream实现)​

static int max9286_s_stream(struct v4l2_subdev *sd, int enable) {
    struct max9286_dev *max9286 = to_max9286(sd);
    
    if (enable) {
        // 1. 配置MAX9286视频通道
        regmap_write(max9286->regmap, 0x15, 0x13); // 启用CSI输出
        
        // 2. 配置GMSL串行器(如OV10635摄像头)
        for (i = 0; i < MAX_CAMERAS; i++) {
            ov10635_write_reg(max9286, i, 0x0100, 0x01); // 启动摄像头传感器
        }
        
        // 3. 启用MIPI时钟和数据通道
        regmap_write(max9286->regmap, 0x12, 0xF3); // DDR模式,4数据通道
    } else {
        // 停止流时的反向操作
    }
}

视频帧流式传输​

DMA通道准备 

  • CSI控制器检测MIPI数据有效信号(VSYNC/HSYNC)
  • 通过VB2框架(Videobuf2)初始化DMA传输:

相机时间同步

MAX9286通过专用GPIO引脚输出全局帧同步脉冲,可达±50ns(依赖PCB布线等长设计)

// 配置寄存器0x0A启用硬件同步
regmap_write(max9286->regmap, 0x0A, 0x84); // 启用FRAMESYNC输出
regmap_write(max9286->regmap, 0x1B, 0x01);  // 使用外部同步模式

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值