跳到主要内容

相机控制

通过 camera::CameraClient 查询能力、开/停推流、调整热控件,以及修改分辨率 / 帧率 / 码率。配套命令行示例在 latentos_sdk_examplecamera/ 模块;本页先讲调用流程,再对照官方示例命令。

机器狗上往往有多个相机,用「方向 + index」区分,例如前后各一颗时为 front_0back_0。下文以 front_0 为例。

API 类型与结果码见 Camera API;模块总览见 C++ SDK 指南

先完成 安装连接配置。后文 $CLIENT 含义见 示例总览

1. 配置连接和网络

修改示例中的 config/echo/mode/client.yaml,将 connect.endpoints 指向机器狗可达的 IP 和 Echo 监听端口。保留文件中的其他配置:

mode: client
connect:
endpoints:
- tcp/192.168.30.245:7447

通过图传通道连接时,常用地址为 192.168.30.*;通过机器狗热点连接时,常用地址为 10.13.1.1,对应配置为 tcp/10.13.1.1:7447。以实际交付网络为准,7447 是 SDK 控制端口。

在开发机检查控制通道:

ROBOT_IP=192.168.30.245 # 热点连接时改为 10.13.1.1
ping -c 3 "$ROBOT_IP"
nc -vz -w 2 "$ROBOT_IP" 7447

打开视频流时还需要传入 rtsp_ip,通常也使用所选通道的上述 IP。机器狗视频服务会据此选择同网段的本地接口;任意填写一个 IP 并不能建立视频连接。

2. 查询相机能力

相机能力由机器狗端提供,客户端不需要自行注册或声明能力。创建 core::Sessioncamera::CameraClient 后,调用 QueryCapabilities("front_0") 获取 CameraCapabilities

下面是一次 front_0 实测能力的整理,其他相机或固件版本应以实际查询结果为准:

采集格式支持的分辨率与帧率
MJPEG1920×1080@60、1280×720@60、800×600@60、640×480@60
YUYV1920×1080@5、1280×720@10、800×600@20、640×480@30

streaming=false current=0x0@0 fmt=none bitrate_kbps=0 表示当前未推流,不表示相机没有能力。message=cached 表示返回缓存的能力;需要刷新时可调用 QueryCapabilities("front_0", true, 3000)

控件FEATURE 整数 / C++ 枚举后缀当前值范围默认值flags
亮度100 / Brightness0-128~1270none
对比度101 / Contrast340~6434none
饱和度102 / Saturation560~12856none
色调103 / Hue0-180~1800none
Gamma104 / Gamma1001~500100none
增益105 / Gain00~6332none
锐度106 / Sharpness60~256none
背光补偿107 / BacklightCompensation00~10none
抗频闪108 / PowerLineFrequency10~21none
自动白平衡109 / WhiteBalanceTemperatureAuto10~11none
白平衡色温110 / WhiteBalanceTemperature40002800~65004000inactive
自动曝光模式111 / ExposureAuto30~33none
手动曝光112 / ExposureAbsolute3131~5000313inactive

这次查询中的控件步长均为 1。设置时遵守实际返回的 minmaxstepflags;默认值不一定等于当前值。inactive 表示当前模式下不可设置,例如自动白平衡启用时手动色温不可用。先调整对应自动模式,收到成功回复后刷新能力,确认手动控件已可用再设置。

3. 完整 C++ 示例

将下面代码保存为 camera_demo.cc。程序按顺序发送请求,每次等待相同 camera_idseq 的结果;先建立状态订阅,再发送命令。示例适用于由本程序独占控制的相机,会修改亮度和流参数,并在结束时关闭流;这些设置不会自动恢复。

#include <latentos/sdk/camera/camera_client.h>
#include <latentos/sdk/core/session.h>

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <thread>
#include <utility>

namespace sdk = latentos::sdk;
using namespace std::chrono_literals;

// 单相机、串行请求示例。并发控制时应在回调中按 camera_id + seq 保存结果,
// 避免 GetLatest*() 的缓存被其他回复覆盖。
template <typename ReadStatus>
auto WaitStatus(ReadStatus read, const std::string& id, std::int64_t seq) {
const auto deadline = std::chrono::steady_clock::now() + 5s;
while (std::chrono::steady_clock::now() < deadline) {
auto status = read();
if (status && status->camera_id == id && status->seq == seq) {
std::cout << "seq=" << seq << " ok=" << status->ok
<< " result_code=" << sdk::ToString(status->result_code)
<< " message=" << status->result_message << '\n';
if (!status->ok) {
throw std::runtime_error("相机操作失败:" + status->result_message);
}
return *status;
}
std::this_thread::sleep_for(50ms);
}
throw std::runtime_error("等待相机回复超时,seq=" + std::to_string(seq));
}

// 打开流需要给出 ip 地址,一般为 192.168.30.*(图传通道)和 10.13.1.1(热点)
// 执行该请求后会给出回复,回复中包含流地址 - uri 字段
int main(int argc, char** argv) {
if (argc != 3) {
std::cerr << "用法:camera_demo <client.yaml> <rtsp_ip>\n";
return 2;
}
try {
const std::string id = "front_0";
const std::string rtsp_ip = argv[2];
sdk::SdkOptions options;
options.client_config_path = argv[1];
sdk::core::Session session(std::move(options));
sdk::camera::CameraClient camera(session);

camera.SubscribeStreamStatus({});
camera.SubscribeControlStatus({});
camera.SubscribeStreamParamsStatus({});

// 声明能力结果对象并查询。不要把某次实测能力当作所有设备的固定能力。
const sdk::CameraCapabilities caps = camera.QueryCapabilities(id, true, 3000);
if (!caps.ok || caps.result_code != sdk::CameraCapabilitiesResultCode::Ok) {
throw std::runtime_error("能力查询失败:" + caps.result_message);
}
const auto info = std::find_if(caps.cameras.begin(), caps.cameras.end(),
[&](const auto& c) { return c.id == id; });
if (info == caps.cameras.end()) {
throw std::runtime_error("能力回复中没有 front_0");
}
for (const auto& p : info->profiles) {
std::cout << sdk::ToString(p.format) << ' ' << p.width << 'x'
<< p.height << '@' << p.fps << '\n';
}
for (const auto& c : info->controls) {
std::cout << sdk::ToString(c.feature) << " feature="
<< static_cast<std::int32_t>(c.feature) << " value=" << c.value
<< " range=" << c.min << ".." << c.max << " step=" << c.step
<< " flags=" << sdk::CameraControlFlagsToString(c.flags) << '\n';
}
if (info->streaming) {
throw std::runtime_error("相机已在推流,请先停止后再运行此演示");
}

auto streamCommand = [&](sdk::CameraOperation op) {
const auto seq = camera.SendStreamCommand(id, op, rtsp_ip);
return WaitStatus([&] { return camera.GetLatestStreamStatus(); }, id, seq);
};
auto stopStream = [&] {
const auto stopped = streamCommand(sdk::CameraOperation::Stop);
if (stopped.enabled || stopped.stream_state != sdk::CameraStreamState::Stopped) {
throw std::runtime_error("尚未确认流已停止");
}
std::cout << "流已关闭\n";
};

// 即使开流回复超时,请求也可能已执行,因此异常路径同样尝试关闭。
try {
const auto started = streamCommand(sdk::CameraOperation::Start);
if (!started.enabled || started.stream_state != sdk::CameraStreamState::Running
|| started.uri.empty()) {
throw std::runtime_error("未获得运行中的视频流和有效 URI");
}
std::cout << "请用 RTSP 播放器打开:" << started.uri << '\n';

// 热控件:亮度设为 10,立即生效,不重启视频流。
const auto live = camera.QueryCapabilities(id, true, 3000);
if (!live.ok) throw std::runtime_error("开流后刷新能力失败");
bool writable = false;
for (const auto& cam : live.cameras) {
if (cam.id != id) continue;
for (const auto& c : cam.controls) {
if (c.feature != sdk::CameraFeature::Brightness) continue;
writable = 10 >= c.min && 10 <= c.max && c.step > 0
&& (10 - c.min) % c.step == 0
&& !sdk::CameraHasControlFlag(c.flags, sdk::CameraControlFlag::Inactive)
&& !sdk::CameraHasControlFlag(c.flags, sdk::CameraControlFlag::ReadOnly)
&& !sdk::CameraHasControlFlag(c.flags, sdk::CameraControlFlag::Disabled)
&& !sdk::CameraHasControlFlag(c.flags, sdk::CameraControlFlag::Grabbed);
}
}
if (!writable) throw std::runtime_error("当前相机不允许将亮度设为 10");
auto seq = camera.SetControl(id, sdk::CameraFeature::Brightness, 10);
const auto control = WaitStatus(
[&] { return camera.GetLatestControlStatus(); }, id, seq);
std::cout << "实际亮度=" << control.effective_value << '\n';

// 使用当前采集格式下明确支持的组合;本次能力表两种格式均支持 640×480。
int fps = 0;
for (const auto& cam : live.cameras) {
if (cam.id != id) continue;
for (const auto& p : cam.profiles) {
if (p.format == cam.current.format && p.width == 640 && p.height == 480) {
fps = p.fps;
break;
}
}
}
if (fps <= 0) throw std::runtime_error("当前采集格式没有 640×480 可用档位");
// bitrate_kbps=0 表示保留当前码率。修改参数会短暂重启流。
seq = camera.SetStreamParams(id, 640, 480, fps, 0);
const auto params = WaitStatus(
[&] { return camera.GetLatestStreamParamsStatus(); }, id, seq);
std::cout << "实际参数=" << params.effective_width << 'x'
<< params.effective_height << '@' << params.effective_fps
<< " bitrate_kbps=" << params.effective_bitrate_kbps << '\n';

std::cout << "可查看画面;按 Enter 关闭流并退出。" << std::endl;
std::cin.get();
stopStream();
} catch (...) {
const auto error = std::current_exception();
try {
stopStream();
} catch (const std::exception& e) {
std::cerr << "关闭流未确认成功:" << e.what() << '\n';
}
std::rethrow_exception(error);
}
return 0;
} catch (const std::exception& e) {
std::cerr << e.what() << '\n';
return 1;
}
}

同目录创建 CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(camera_demo LANGUAGES CXX)
find_package(latentos_sdk_camera CONFIG REQUIRED)
find_package(Threads REQUIRED)
add_executable(camera_demo camera_demo.cc)
target_compile_features(camera_demo PRIVATE cxx_std_20)
target_link_libraries(camera_demo PRIVATE latentos::sdk_camera Threads::Threads)

按 SDK 默认安装位置编译并运行:

cmake -S . -B build \
-DCMAKE_PREFIX_PATH="/data/latentos/sdk_cpp;/data/latentos/sdk_runtime;/data/latentos/sdk_runtime/third_party"
cmake --build build -j
./build/camera_demo /绝对路径/config/echo/mode/client.yaml 192.168.30.*
# 热点连接:同时确认 client.yaml 指向热点控制地址
# ./build/camera_demo /绝对路径/config/echo/mode/client.yaml 10.13.1.1

4. 官方示例命令

具体环境安装请参见 latentos_sdk_example 的 README。编译方式见 示例总览 的 C++ 小节。

camera_capabilities

查询相机能力(清单 / 支持档位 / 可调控件)。camera_id 留空=全部相机。

用法:camera_capabilities [camera_id] [--client-config PATH]

./build/examples/camera_capabilities --client-config "$CLIENT"
./build/examples/camera_capabilities front_0 --client-config "$CLIENT"

camera_set_control

设置一个热控件(立即生效,不断流)。命令异步下发,示例按 seq 等回执(约 3s)。feature 取能力表里的 FEATURE_* 整数(如 100=亮度、108=抗频闪)。

用法:camera_set_control <camera_id> <feature> <value> [--client-config PATH]

./build/examples/camera_set_control front_0 100 128 --client-config "$CLIENT"

camera_set_stream

开 / 停 / 重启推流。示例按 seq 等回执(约 5s),成功时打印 uri。可选 rtsp_ip 用于选本机同网段网卡。

用法:camera_set_stream <camera_id> <start|stop|restart> [rtsp_ip] [--client-config PATH]

./build/examples/camera_set_stream front_0 start 192.168.144.1 --client-config "$CLIENT"
./build/examples/camera_set_stream front_0 stop --client-config "$CLIENT"

camera_set_stream_params

改分辨率 / 帧率 / 码率(校验后短时 Stop+Start)。每项 0=不改。示例按 seq 等回执并打印生效档。

用法:camera_set_stream_params <camera_id> <width> <height> <fps> <bitrate_kbps> [--client-config PATH]

./build/examples/camera_set_stream_params front_0 1280 720 30 4000 --client-config "$CLIENT"