Skip to content

Commit ff8e2b0

Browse files
committed
feat: match camera control support with rpicam-app
1 parent 037c03f commit ff8e2b0

File tree

9 files changed

+381
-123
lines changed

9 files changed

+381
-123
lines changed

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ sudo apt install libcamera0.5 libmosquitto1 pulseaudio libavformat59 libswscale6
7171

7272
### 3. Download & Unpack
7373

74-
Prepare the binary file from [Releases](https://github.com/TzuHuanTai/RaspberryPi_WebRTC/releases).
74+
Prepare the binary file from [Releases](https://github.com/TzuHuanTai/RaspberryPi-WebRTC/releases).
7575
```bash
7676
wget https://github.com/TzuHuanTai/RaspberryPi_WebRTC/releases/latest/download/pi_webrtc-v1.0.6_raspios-bookworm-arm64.tar.gz
7777
tar -xzf pi_webrtc-v1.0.6_raspios-bookworm-arm64.tar.gz
@@ -102,18 +102,18 @@ You can use a free cloud MQTT service like [HiveMQ](https://www.hivemq.com) or
102102
--fps=30 \
103103
--width=1280 \
104104
--height=960 \
105-
--use_mqtt \
106-
--mqtt_host=your.mqtt.cloud \
107-
--mqtt_port=8883 \
108-
--mqtt_username=hakunamatata \
109-
--mqtt_password=Wonderful \
105+
--use-mqtt \
106+
--mqtt-host=your.mqtt.cloud \
107+
--mqtt-port=8883 \
108+
--mqtt-username=hakunamatata \
109+
--mqtt-password=Wonderful \
110110
--uid=your-custom-uid \
111-
--no_audio \
112-
--hw_accel # Only Pi Zero 2W, 3B, 4B support hw encoding
111+
--no-audio \
112+
--hw-accel # Only Pi Zero 2W, 3B, 4B support hw encoding
113113
```
114114

115115
> [!IMPORTANT]
116-
> Use `--hw_accel` on Pi Zero 2W, 3B/3B+, and 4B only. Remove it on Pi 5 or devices without HW encoder.
116+
> Use `--hw-accel` on Pi Zero 2W, 3B/3B+, and 4B only. Remove it on Pi 5 or devices without HW encoder.
117117

118118
# [Advance](https://github.com/TzuHuanTai/RaspberryPi_WebRTC/wiki/Advanced-Settings)
119119

doc/BUILD.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ make -j
3333
3434
Run `pi_webrtc` to start the service.
3535
```bash
36-
./pi_webrtc --camera=libcamera:0 --fps=30 --width=1280 --height=720 --use_mqtt --mqtt_host=<hostname> --mqtt_port=1883 --mqtt_username=<username> --mqtt_password=<password> --hw_accel
36+
./pi_webrtc --camera=libcamera:0 --fps=30 --width=1280 --height=720 --use-mqtt --mqtt-host=<hostname> --mqtt-port=1883 --mqtt-username=<username> --mqtt-password=<password> --hw-accel
3737
```

src/args.h

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,58 @@
11
#ifndef ARGS_H_
22
#define ARGS_H_
33

4+
#include <chrono>
45
#include <cstdint>
6+
#include <optional>
57
#include <string>
8+
#include <unordered_map>
69

710
#include <linux/videodev2.h>
811

12+
template <typename DEFAULT> struct TimeVal {
13+
TimeVal()
14+
: value(0) {}
15+
16+
void set(const std::string &s) {
17+
static const std::unordered_map<std::string, std::chrono::nanoseconds> match{
18+
{"min", std::chrono::minutes(1)}, {"sec", std::chrono::seconds(1)},
19+
{"s", std::chrono::seconds(1)}, {"ms", std::chrono::milliseconds(1)},
20+
{"us", std::chrono::microseconds(1)}, {"ns", std::chrono::nanoseconds(1)},
21+
};
22+
23+
try {
24+
std::size_t end_pos;
25+
float f = std::stof(s, &end_pos);
26+
value = std::chrono::duration_cast<std::chrono::nanoseconds>(f * DEFAULT{1});
27+
28+
for (const auto &m : match) {
29+
auto found = s.find(m.first, end_pos);
30+
if (found != end_pos || found + m.first.length() != s.length())
31+
continue;
32+
value = std::chrono::duration_cast<std::chrono::nanoseconds>(f * m.second);
33+
break;
34+
}
35+
} catch (std::exception const &e) {
36+
throw std::runtime_error("Invalid time string provided");
37+
}
38+
}
39+
40+
template <typename C = DEFAULT> int64_t get() const {
41+
return std::chrono::duration_cast<C>(value).count();
42+
}
43+
44+
explicit constexpr operator bool() const { return !!value.count(); }
45+
46+
std::chrono::nanoseconds value;
47+
};
48+
949
struct Args {
1050
// video input
1151
int cameraId = 0;
1252
int fps = 30;
1353
int width = 640;
1454
int height = 480;
15-
int rotation_angle = 0;
55+
int rotation = 0;
1656
bool use_libcamera = false;
1757
uint32_t format = V4L2_PIX_FMT_MJPEG;
1858
std::string camera = "libcamera:0";
@@ -22,6 +62,42 @@ struct Args {
2262
int sample_rate = 44100;
2363
bool no_audio = false;
2464

65+
// libcamera control options
66+
float sharpness = 1.0f;
67+
float contrast = 1.0f;
68+
float brightness = 0.0f;
69+
float saturation = 0.0f;
70+
float ev = 0.0f;
71+
std::string shutter_ = "0";
72+
TimeVal<std::chrono::microseconds> shutter;
73+
float gain = 0.0f;
74+
std::string ae_metering = "centre";
75+
int ae_metering_mode = 0;
76+
std::string exposure = "normal";
77+
int ae_mode = 0;
78+
std::string awb = "auto";
79+
int awb_mode = 0;
80+
std::string autofocus_mode = "default";
81+
int af_mode = -1;
82+
std::string awbgains = "0,0";
83+
float awb_gain_r = 0.0f;
84+
float awb_gain_b = 0.0f;
85+
std::string denoise = "auto";
86+
int denoise_mode = 0;
87+
std::string tuning_file = "-";
88+
std::string af_range = "normal";
89+
int af_range_mode = 0;
90+
std::string af_speed = "normal";
91+
int af_speed_mode = 0;
92+
std::string af_window = "0,0,0,0";
93+
float af_window_x = 0.0f;
94+
float af_window_y = 0.0f;
95+
float af_window_width = 0.0f;
96+
float af_window_height = 0.0f;
97+
std::string lens_position_ = "";
98+
std::optional<float> lens_position;
99+
bool set_default_lens_position = false;
100+
25101
// recording
26102
std::string record_path = "";
27103
int file_duration = 60;

src/capturer/libcamera_capturer.cpp

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,76 @@
33
#include <sys/mman.h>
44

55
#include "common/logging.h"
6+
#include <libcamera/geometry.h>
67

78
std::shared_ptr<LibcameraCapturer> LibcameraCapturer::Create(Args args) {
89
auto ptr = std::make_shared<LibcameraCapturer>(args);
910
ptr->Init(args.cameraId);
10-
ptr->SetFps(args.fps)
11-
.SetRotation(args.rotation_angle)
12-
.SetFormat(args.width, args.height)
13-
.StartCapture();
11+
ptr->SetFps(args.fps).SetRotation(args.rotation).SetResolution(args.width, args.height);
12+
13+
if (args.gain) {
14+
ptr->SetControls(libcamera::controls::ANALOGUE_GAIN_MODE,
15+
libcamera::controls::AnalogueGainModeManual)
16+
.SetControls(libcamera::controls::ANALOGUE_GAIN, args.gain);
17+
}
18+
19+
ptr->SetControls(libcamera::controls::SHARPNESS, args.sharpness)
20+
.SetControls(libcamera::controls::CONTRAST, args.contrast)
21+
.SetControls(libcamera::controls::BRIGHTNESS, args.brightness)
22+
.SetControls(libcamera::controls::SATURATION, args.saturation)
23+
.SetControls(libcamera::controls::EXPOSURE_VALUE, args.ev)
24+
.SetControls(libcamera::controls::EXPOSURE_TIME,
25+
args.shutter.get<std::chrono::microseconds>())
26+
.SetControls(libcamera::controls::AE_METERING_MODE, args.ae_metering_mode)
27+
.SetControls(libcamera::controls::AE_EXPOSURE_MODE, args.ae_mode)
28+
.SetControls(libcamera::controls::AWB_MODE, args.awb_mode)
29+
.SetControls(libcamera::controls::COLOUR_GAINS,
30+
libcamera::Span<const float, 2>({args.awb_gain_r, args.awb_gain_b}))
31+
.SetControls(libcamera::controls::draft::NOISE_REDUCTION_MODE, args.denoise_mode);
32+
33+
if (args.af_mode == -1) {
34+
if (args.lens_position || args.set_default_lens_position) {
35+
args.af_mode = libcamera::controls::AfModeManual;
36+
} else {
37+
args.af_mode =
38+
ptr->camera_->controls().at(&libcamera::controls::AfMode).max().get<int>();
39+
}
40+
}
41+
ptr->SetControls(libcamera::controls::AF_MODE, args.af_mode)
42+
.SetControls(libcamera::controls::AF_RANGE, args.af_range_mode)
43+
.SetControls(libcamera::controls::AF_SPEED, args.af_speed_mode);
44+
45+
if (args.af_window_width != 0 && args.af_window_height != 0) {
46+
libcamera::Rectangle sensor_area = ptr->camera_->controls()
47+
.at(&libcamera::controls::ScalerCrop)
48+
.max()
49+
.get<libcamera::Rectangle>();
50+
int x = args.af_window_x * sensor_area.width;
51+
int y = args.af_window_y * sensor_area.height;
52+
int w = args.af_window_width * sensor_area.width;
53+
int h = args.af_window_height * sensor_area.height;
54+
libcamera::Rectangle afwindows_rectangle[1];
55+
afwindows_rectangle[0] = libcamera::Rectangle(x, y, w, h);
56+
afwindows_rectangle[0].translateBy(sensor_area.topLeft());
57+
58+
ptr->SetControls(libcamera::controls::AF_METERING, libcamera::controls::AfMeteringWindows)
59+
.SetControls(libcamera::controls::AF_WINDOWS,
60+
libcamera::Span<const libcamera::Rectangle>(afwindows_rectangle));
61+
}
62+
63+
if (args.af_mode == libcamera::controls::AfModeEnum::AfModeAuto) {
64+
ptr->SetControls(libcamera::controls::AF_TRIGGER, libcamera::controls::AfTriggerStart);
65+
} else if (args.lens_position || args.set_default_lens_position) {
66+
float f;
67+
if (args.lens_position) {
68+
f = args.lens_position.value();
69+
} else {
70+
f = ptr->camera_->controls().at(&libcamera::controls::LensPosition).def().get<float>();
71+
}
72+
ptr->SetControls(libcamera::controls::LENS_POSITION, f);
73+
}
74+
75+
ptr->StartCapture();
1476
return ptr;
1577
}
1678

@@ -19,7 +81,7 @@ LibcameraCapturer::LibcameraCapturer(Args args)
1981
format_(args.format),
2082
config_(args) {}
2183

22-
void LibcameraCapturer::Init(int deviceId) {
84+
void LibcameraCapturer::Init(int cameraId) {
2385
cm_ = std::make_unique<libcamera::CameraManager>();
2486
int ret = cm_->start();
2587
if (ret) {
@@ -31,11 +93,11 @@ void LibcameraCapturer::Init(int deviceId) {
3193
throw std::runtime_error("No camera is available via libcamera.");
3294
}
3395

34-
if (config_.cameraId >= cameras.size()) {
96+
if (cameraId >= cameras.size()) {
3597
throw std::runtime_error("Selected camera is not available.");
3698
}
3799

38-
std::string const &cam_id = cameras[config_.cameraId]->id();
100+
std::string const &cam_id = cameras[cameraId]->id();
39101
INFO_PRINT("camera id: %s", cam_id.c_str());
40102
camera_ = cm_->get(cam_id);
41103
camera_->acquire();
@@ -64,7 +126,7 @@ uint32_t LibcameraCapturer::format() const { return format_; }
64126

65127
Args LibcameraCapturer::config() const { return config_; }
66128

67-
LibcameraCapturer &LibcameraCapturer::SetFormat(int width, int height) {
129+
LibcameraCapturer &LibcameraCapturer::SetResolution(int width, int height) {
68130
DEBUG_PRINT("camera original format: %s", camera_config_->at(0).toString().c_str());
69131

70132
if (width && height) {
@@ -109,10 +171,17 @@ LibcameraCapturer &LibcameraCapturer::SetFps(int fps) {
109171
return *this;
110172
}
111173

112-
LibcameraCapturer &LibcameraCapturer::SetControls(const int key, const int value) {
174+
LibcameraCapturer &LibcameraCapturer::SetControls(int key, ControlValue value) {
113175
std::lock_guard<std::mutex> lock(control_mutex_);
114-
DEBUG_PRINT("Set camera controls, key: %d, value: %d", key, value);
115-
controls_.set(key, value);
176+
177+
if (controls_.contains(key) && camera_->controls().count(key) > 0) {
178+
std::visit(
179+
[&](auto &&v) {
180+
controls_.set(key, v);
181+
},
182+
value);
183+
}
184+
116185
return *this;
117186
}
118187

src/capturer/libcamera_capturer.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class LibcameraCapturer : public VideoCapturer {
2626
uint32_t format() const override;
2727
Args config() const override;
2828

29-
LibcameraCapturer &SetControls(const int key, const int value) override;
29+
LibcameraCapturer &SetControls(int key, ControlValue value) override;
3030
rtc::scoped_refptr<webrtc::I420BufferInterface> GetI420Frame() override;
3131
void StartCapture() override;
3232

@@ -52,9 +52,9 @@ class LibcameraCapturer : public VideoCapturer {
5252
rtc::scoped_refptr<V4L2FrameBuffer> frame_buffer_;
5353
void NextBuffer(V4L2Buffer &raw_buffer);
5454

55-
LibcameraCapturer &SetFormat(int width, int height);
56-
LibcameraCapturer &SetFps(int fps);
57-
LibcameraCapturer &SetRotation(int angle);
55+
LibcameraCapturer &SetResolution(int width, int height) override;
56+
LibcameraCapturer &SetFps(int fps) override;
57+
LibcameraCapturer &SetRotation(int angle) override;
5858

5959
void Init(int deviceId);
6060
void AllocateBuffer();

src/capturer/v4l2_capturer.cpp

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,21 @@
1414
std::shared_ptr<V4L2Capturer> V4L2Capturer::Create(Args args) {
1515
auto ptr = std::make_shared<V4L2Capturer>(args);
1616
ptr->Init(args.cameraId);
17+
18+
if (args.format == V4L2_PIX_FMT_H264) {
19+
ptr->SetControls(V4L2_CID_MPEG_VIDEO_BITRATE_MODE, V4L2_MPEG_VIDEO_BITRATE_MODE_VBR)
20+
.SetControls(V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)
21+
.SetControls(V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, true)
22+
.SetControls(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0)
23+
.SetControls(V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, 60) /* trick */
24+
.SetControls(V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, 1)
25+
.SetControls(V4L2_CID_MPEG_VIDEO_BITRATE, 2500 * 1000);
26+
}
27+
1728
ptr->SetFps(args.fps)
18-
.SetRotation(args.rotation_angle)
19-
.SetFormat(args.width, args.height)
29+
.SetRotation(args.rotation)
30+
.SetResolution(args.width, args.height)
31+
.SetControls(V4L2_CID_MPEG_VIDEO_BITRATE, 10000 * 1000)
2032
.StartCapture();
2133
return ptr;
2234
}
@@ -85,24 +97,10 @@ int V4L2Capturer::GetCameraIndex(webrtc::VideoCaptureModule::DeviceInfo *device_
8597
return -1;
8698
}
8799

88-
V4L2Capturer &V4L2Capturer::SetFormat(int width, int height) {
100+
V4L2Capturer &V4L2Capturer::SetResolution(int width, int height) {
89101
width_ = width;
90102
height_ = height;
91-
92103
V4L2Util::SetFormat(fd_, &capture_, width, height, format_);
93-
V4L2Util::SetExtCtrl(fd_, V4L2_CID_MPEG_VIDEO_BITRATE, 10000 * 1000);
94-
95-
if (format_ == V4L2_PIX_FMT_H264) {
96-
V4L2Util::SetExtCtrl(fd_, V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
97-
V4L2_MPEG_VIDEO_BITRATE_MODE_VBR);
98-
V4L2Util::SetExtCtrl(fd_, V4L2_CID_MPEG_VIDEO_H264_PROFILE,
99-
V4L2_MPEG_VIDEO_H264_PROFILE_HIGH);
100-
V4L2Util::SetExtCtrl(fd_, V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, true);
101-
V4L2Util::SetExtCtrl(fd_, V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
102-
V4L2Util::SetExtCtrl(fd_, V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, 60); /* trick */
103-
V4L2Util::SetExtCtrl(fd_, V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, 1);
104-
V4L2Util::SetExtCtrl(fd_, V4L2_CID_MPEG_VIDEO_BITRATE, 2500 * 1000);
105-
}
106104
return *this;
107105
}
108106

@@ -154,6 +152,18 @@ void V4L2Capturer::CaptureImage() {
154152
}
155153
}
156154

155+
V4L2Capturer &V4L2Capturer::SetControls(int key, ControlValue value) {
156+
std::visit(overloaded{[&](int v) {
157+
V4L2Util::SetExtCtrl(fd_, key, v);
158+
},
159+
[&](auto &&v) {
160+
std::cerr << "Unsupported control value type: " << typeid(v).name()
161+
<< "\n";
162+
}},
163+
value);
164+
return *this;
165+
}
166+
157167
rtc::scoped_refptr<webrtc::I420BufferInterface> V4L2Capturer::GetI420Frame() {
158168
return frame_buffer_->ToI420();
159169
}

0 commit comments

Comments
 (0)