Cyan Camera App
Download
First time setup
You need to grant all permsissions in order to start using the app

Main controls
You have main controls on the right
- Start/Stop recording
- Take picture
- Flip lens front/back
- Switch lense (wide/main/tele)
- Quick zoom presets
- Flashlight / Flash rim
Pinch to zoom supported as well. Tap to focus keeps a manually locked exposure.
Recording stops only after you hold the record button for about 1.5 s, so a stray tap does not cut a take. A ring fills while you hold.
Lock and Zen mode
- Hold the lock button for 1.5 s to lock the screen.
Settings -> Lock Controlspicks what stops working while locked: record button, photo button, zoom/flash/flip/lens buttons, parameter bar, bottom controls, tap to focus and pinch to zoom. - Hold the controls button to hide the controls (Zen mode).
Settings -> Zen Modepicks what stays on screen: parameter bar, IP and status, bottom controls, scopes, audio levels, grid and frame guide, timecode.
And extra camera app control at the bottom:
- Main settings
- Stream settings
- Stream start / stop
- HDMI out start / stop
- 3D lut settings
- Format picker

If you control camera sensor value via RCP it will go to manual mode and turn indicator to orange. You can make it auto by taping on that value from phone app of via RCP. RCP supported controls:
- ISO
- EV
- Shutter
- Iris
- White balance + tint
- Lens
- Zoom
- Focus
- Flip front/back (custom button)
RCP -> Camera -> Custom - For lens switching and starting stream
Color Shading
You can tap on left bottom corner to enter luts menu.
- You can select
.cubefiles from your device, several at once - The grid view lists every LUT and deletes several at once
- You can chain it with RCP corrections on top
- RCP Corrections toggle synced with RCP -> Camera -> Custom -> CorrOff/CorrOn

Currently supported from RCP:
- White balance RGB
- Gamma RGBM
- Black RGBM
- Master Black
- Hue/Saturation
- Multimatrix
- Local contrast (contrast around a pivot)
RCP corrections are forced off while HDMI is in Broadcast mode, because that output sends the ungraded sensor feed. The RCP hides the correction controls in that state.
| Mode | What is applied | Processing path | Output, HDR LUT off / on | Notes |
|---|---|---|---|---|
| Off | Nothing | Raw buffer passthrough | Source tags at 10-bit, Rec.709 at 8-bit | Log/HLG at 8-bit looks flat |
| RCP only | Lift, gamma, gain, saturation, hue, multimatrix | Core Image sRGB cube; Metal if HDR LUT on | 8-bit Rec.709 / 10-bit BT.2020 HLG | Corrections computed in scene-linear light for the active colour space |
| File LUT | The .cube only, RCP params stored but not applied | Metal two-pass on raw code values | Rec.709 at 8 or 10-bit / BT.2020 HLG at 10-bit | Typical Log to 709 conversion |
| Chain RCP → file | RCP grade, then the .cube | Composed cube (17³ into the file's size), Metal two-pass | Same as file LUT | Default whenever both are active; rebuilt on each RCP tick |
| Studio LUT | Whole Studio flow baked into one .cube | Same as file LUT, source "studio" | Same as file LUT | Re-pushed over the phone's WebSocket on each bound-grade change |
| Studio LUT + RCP | RCP grade, then the Studio cube | Chain, as above | Same as file LUT | RCP may grade twice, see below |
| Scope off for an output | Nothing on that output | Raw buffer for that output only | As "Off" | Per output: Monitor, Record, Stream |
| Source | LUT mode | Resolved | Codec | Stream tags | Look on receiver |
|---|---|---|---|---|---|
| Rec. 709 / P3 D65 | any | 8-bit | H.264 or HEVC | Rec.709 (BT.709 / 709 / 709) | SDR, correct |
| Rec. 2020 HDR (HLG) | No LUT | 10-bit | HEVC Main 10 | BT.2020 / HLG (DVB: TRC 14 + SEI 18, WCG idc 2) | HDR, correct |
| Apple Log / Log 2 | No LUT | 10-bit | HEVC Main 10 | BT.2020 primaries, transfer unspecified (WCG idc 3) | Flat log, receiver applies LUT |
| HDR or Log | LUT, HDR LUT off | 10-bit | HEVC Main 10 | Rec.709 at 10-bit (less banding) | SDR, correct |
| HDR or Log | LUT, HDR LUT on | 10-bit | HEVC Main 10 | BT.2020 / HLG (DVB: TRC 14 + SEI 18, WCG idc 2) | HDR, correct |
| HDR or Log | LUT (either switch) | 8-bit | H.264 or HEVC | Rec.709 | SDR, correct |
| HDR or Log | No LUT | 8-bit * | H.264 or HEVC | Rec.709, no tone mapping | Flat / washed out |
Streaming
Currently supported protocols: RTP & SRT
You can hold rtp/srt indicator and get into stream settings menu: To start stream you you need to tap on indicator once.
Encoder (iOS, per protocol):
| Option | What you get |
|---|---|
| Low Latency (default) | 4:2:0, one frame in flight, lowest delay |
| Standard | 4:2:0 on Apple's normal encoder: better picture per bit, a few frames more delay |
| Full 4:2:2 | HEVC Main 4:2:2 10-bit on the normal encoder. Needs HEVC, 10-bit, Chroma Subsampling set to 4:2:2 and a receiver that decodes HEVC 4:2:2. Greyed out with H.264 |
Above 60 fps the stream always uses the normal encoder. All three options use the iPhone's hardware encoder. Full 4:2:2 can't use the low-latency encoder: it accepts the 4:2:2 profile but writes 8-bit 4:2:0.
RTP has no retransmission. Over Wi-Fi at high bitrates (for example 4K 10-bit, about 26 Mbps) some packets get lost and the picture shows short artifacts. Use SRT, which resends lost packets, or turn on FXVP -> Loss Repair with a receiver that supports it.
SRT resends lost packets only while they are inside the Latency window. The default 120 ms is right for a wired network. On Wi-Fi, 4K Apple Log or HLG (20 Mbps and more) can lose 10 percent of the packets, and 120 ms is then too short: the picture shows artifacts. Set Latency to 500 ms or more on Wi-Fi. Low Latency is the most robust encoder on a weak link; Standard and Full 4:2:2 send larger keyframes.

Here few server examples which require ffmpeg installed:
SRT Server + preview:
srt-live-transmit "srt://0.0.0.0:9000?mode=listener" "udp://127.0.0.1:5000" & ffplay -i "udp://127.0.0.1:5000"
SRT Sever + HDR preview:
mpv 'srt://0.0.0.0:9000?mode=listener' \
--hwdec=videotoolbox \
--vo=gpu-next \
--target-colorspace-hint=yes \
--loop-file=inf \
--keep-open=always \
--idle=yes \
--demuxer-thread=yes \
--network-timeout=0
RTP Server + preview:
ffplay -fflags nobuffer -flags low_delay -framedrop -probesize 32 -analyzeduration 0 -sync ext -protocol_whitelist file,udp,rtp -f sdp -i <(echo -e "v=0\nm=video 4444 RTP/AVP 96\nc=IN IP4 0.0.0.0\na=rtpmap:96 H264/90000")
Recording
Open Settings -> Recording. Recordings are .mov files on iOS and .mp4 on Android.
File name
Build the name from tokens: #camera, #reel, #position, #timestamp. Tap a token chip to add or remove it, or type the format directly. Fill in the Camera, Reel and Position fields below. The footer shows the next file name. Reel goes up by one with every recording; set it back to 1 to restart the count.
Save location
- Photo Library (Gallery on Android) by default.
- External Folder: pick an SSD, iCloud Drive or any Files location. The choice survives app restarts. Recommended for ProRes.
Codec (iOS)
| Codec | Notes |
|---|---|
| HEVC | Default. Efficient, small files. Hardware encoder. |
| H.264 | Widest compatibility, 8-bit only. Hardware encoder. |
| ProRes 422 Proxy | Edit proxy, about 45 Mbps at 1080p |
| ProRes 422 LT | About 100 Mbps at 1080p |
| ProRes 422 | Broadcast standard, about 145 Mbps at 1080p |
| ProRes 422 HQ | Highest quality, about 220 Mbps at 1080p |
| ProRes RAW | Sensor data, iPhone 17 Pro or later, external storage only |
| ProRes RAW HQ | As ProRes RAW, higher data rate |
ProRes needs a ProRes-capable iPhone (13 Pro or later). It records in Rec. 709 as well as HLG and Apple Log. The ProRes rows are greyed out when the current format has no ProRes encoder, for example 4K in 4:3. Files are much larger than HEVC. All ProRes 422 flavours use the hardware encoder; none has a low-latency mode, so they are for recording only. ProRes RAW is encoded in the camera itself.
ProRes RAW (iOS 26, iPhone 17 Pro or later)
- Choose an external drive as the save location first. The RAW rows stay greyed out until you do.
- RAW records the sensor in Apple Log. In 16:9 the file is 4224x2240, in 4:3 it is the full readout 4224x3024.
- When the take starts, the white balance is frozen at its current value. It cannot change until the take ends, from the phone or the RCP. Set it before you record, or adjust it in post: RAW keeps it as metadata.
- The lens cannot change during a take.
- Photos are not available while a RAW codec is selected. The photo button is greyed out.
- If the camera refuses RAW, the codec row says why and RAW stays off until the app restarts.
Bit depth (iOS)
- Auto: matches the source. HLG and Apple Log record 10-bit, Rec. 709 and P3 record 8-bit.
- 8-bit: smaller files, SDR.
- 10-bit: keeps HDR and LUT gradients banding-free. Needs a 10-bit source, otherwise falls back to 8-bit.
The Resolved: line under the options shows what you will actually get, for example 10-bit HEVC.
Bitrate (HEVC)
Auto, 40, 80, 120, 160 or 240 Mbps. The list is capped by resolution: 40 Mbps below 1080p, 120 Mbps at 1080p, 240 Mbps at 4K.
LUT in the recording
The LUT is only baked into the file when the Record scope is on in the LUT panel. With a LUT the file is Rec.709. Without a LUT a 10-bit HLG or Apple Log recording keeps its colour space, so grade it later.
Timecode and genlock (iOS 26, USB-C sync dock such as Blackmagic Camera ProDock)
Genlockin Settings locks the sensor to the dock's reference (iPhone 17 Pro or later). The status line under the switch readscalibratingfirst, thenLocked to reference. The indicators on the camera screen show the same states.TC Sourcepicks the timecode: Internal (phone clock, time of day, not shared with other cameras) or an external source. External sources are LTC from the dock and MIDI timecode (MTC) over USB or the network. A saved source that is unplugged stays listed asNot connected. If the sync fails,Retry Timecode Syncskips the wait.- Network MIDI: turn on
TC Source -> Receive MIDI Timecode. On a Mac connect from Audio MIDI Setup (MIDI Studio > Network), on Windows use rtpMIDI, then play MTC. To test without MIDI software, use the script below. - The timecode is written as a timecode track into every recording.
TC Offset (frames)shifts it if your reference is early or late.
Test MIDI timecode without MIDI software
The script below sends MTC over rtpMIDI from any computer with Python 3, no install needed. Save it as mtc.py.
- On the phone, turn on
TC Source -> Receive MIDI Timecode. - Find the phone's session port, usually
5004. On a Mac:
dns-sd -B _apple-midi._udp
dns-sd -L "<session name>" _apple-midi._udp
- Run the script with the phone's IP. Optional: frame rate (24, 25 or 30, default 25) and a start timecode. Without a start timecode it sends the computer's time of day.
python3 mtc.py 192.168.1.42
python3 mtc.py 192.168.1.42:5004 30 10:00:00:00
- On the phone, pick
MTC TestinTC Source. The status goes to synchronized and the timecode on screen matches the one the script prints. A start timecode like10:00:00:00makes it easy to see that the phone uses the external source.Ctrl+Cends the session.
mtc.py
import datetime, random, socket, struct, sys, threading, time
host, _, port = sys.argv[1].partition(":")
port = int(port or 5004)
fps = int(sys.argv[2]) if len(sys.argv) > 2 else 25
rate = {24: 0, 25: 1, 30: 3}[fps]
ssrc, token = random.getrandbits(32), random.getrandbits(32)
def ts():
return int(time.monotonic() * 10000)
while True:
ctrl = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ctrl.bind(("0.0.0.0", 0))
data = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
data.bind(("0.0.0.0", ctrl.getsockname()[1] + 1))
break
except OSError:
ctrl.close()
invite = struct.pack("!H2sIII", 0xFFFF, b"IN", 2, token, ssrc) + b"MTC Test\0"
for sock, p in ((ctrl, port), (data, port + 1)):
sock.settimeout(1)
for _ in range(10):
sock.sendto(invite, (host, p))
try:
if sock.recvfrom(1024)[0][2:4] == b"OK":
break
except socket.timeout:
pass
else:
sys.exit(f"no answer from {host}:{p}")
sock.settimeout(None)
print("connected")
def ck(count, t1, t2, t3):
return struct.pack("!H2sIB3xQQQ", 0xFFFF, b"CK", ssrc, count, t1, t2, t3)
def listen(sock, p):
while True:
pkt = sock.recvfrom(1024)[0]
if pkt[2:4] == b"CK" and len(pkt) >= 36:
count, t1, t2 = pkt[8], *struct.unpack("!QQ", pkt[12:28])
if count == 0:
sock.sendto(ck(1, t1, ts(), 0), (host, p))
elif count == 1:
sock.sendto(ck(2, t1, t2, ts()), (host, p))
for sock, p in ((ctrl, port), (data, port + 1)):
threading.Thread(target=listen, args=(sock, p), daemon=True).start()
seq = 0
def send(msg):
global seq
seq = (seq + 1) & 0xFFFF
data.sendto(struct.pack("!BBHII", 0x80, 0x61, seq, ts() & 0xFFFFFFFF, ssrc) + bytes([len(msg)]) + msg, (host, port + 1))
def split(frames):
s = frames // fps
return (s // 3600) % 24, (s // 60) % 60, s % 60, frames % fps
if len(sys.argv) > 3:
h, m, s, f = map(int, sys.argv[3].split(":"))
start = ((h * 60 + m) * 60 + s) * fps + f
t0 = time.time()
else:
now = time.time()
tod = (now + datetime.datetime.now().astimezone().utcoffset().total_seconds()) % 86400
start = int(tod * fps) + 1
t0 = now + start / fps - tod
h, m, s, f = split(start)
send(bytes([0xF0, 0x7F, 0x7F, 0x01, 0x01, rate << 5 | h, m, s, f, 0xF7]))
q, last_sync = 0, 0
try:
while True:
wait = t0 + q / (fps * 4) - time.time()
if wait > 0:
time.sleep(wait)
piece = q % 8
h, m, s, f = split(start + (q - piece) // 4)
value = [f, s, m, rate << 5 | h][piece // 2]
send(bytes([0xF1, piece << 4 | (value >> 4 if piece % 2 else value & 0x0F)]))
if piece == 0 and q % (fps * 4) < 8:
print(f"{h:02}:{m:02}:{s:02}:{f:02}", flush=True)
if time.monotonic() - last_sync > 5:
data.sendto(ck(0, ts(), 0, 0), (host, port + 1))
last_sync = time.monotonic()
q += 1
except KeyboardInterrupt:
bye = struct.pack("!H2sIII", 0xFFFF, b"BY", 2, token, ssrc)
ctrl.sendto(bye, (host, port))
data.sendto(bye, (host, port + 1))
Android
Recordings are Rec.709 at a fixed bitrate. Capable devices offer H.264 8-bit or HEVC 10-bit; the external folder works the same way.
HDMI output
Connect a USB-C to HDMI adapter and tap the HDMI button in the bottom row to start or stop the output. The icon lights up while the output is on and a display is connected.
Settings -> HDMI Mode picks one of three outputs:
| Mode | What you get | Latency | LUT, de-squeeze, rotation |
|---|---|---|---|
| Preview | Panel's native refresh, usually 60 Hz. 23.98 content shows as pulldown. | Lowest | Applied |
| Video Feed (default) | The HDMI link is driven at the capture rate, so 23.98 or 59.94 come out with exact cadence (iOS 26). | Adds a little | Applied |
| Broadcast | Sensor feed sent straight to the DisplayPort link with SMPTE ancillary data, including lens metadata. Exact cadence. | Lowest | Not applied, RCP corrections off too |
Broadcast needs iOS 27 and a supported capture format; the option is greyed out otherwise. The format picker marks presets that do not support Broadcast. In Broadcast, paint goes to the ColorBox and de-squeeze applies to the on-device monitor only.
- The LUT on HDMI follows the
Monitorscope in the LUT panel, which covers preview and HDMI together. Without a LUT an HLG source is sent as HDR. HDMI Audioin Settings plays the microphone input through the HDMI audio while the output is on, for monitoring on the external display.- Android : a single mode, the LUT-processed feed full screen on the external display.
REMI setup
REMI is the protocol the CyanView RCP and gateway use to control cameras. The phone runs a REMI server on TCP port 7447; the gateway connects to the phone. One gateway at a time.
Camera number
Settings -> Camera Number (1 to 255) is what the camera shows up as on the RCP. The device is announced as <number> <name>, for example 1 CyanCamera.
Local network
Put the phone on the same network as the gateway and add it as a REMI device with the phone's IP address and port 7447. The phone re-announces itself every few seconds until the gateway subscribes.
Cloud (remote RCP)
When the RCP is not on the same network, Settings -> REMI relays REMI through CyanView's cloud:
Camera Name: the name shown on the RCP.Tag: must match the tag configured on the gateway or RCP side. Several RCPs can share one tag.Region: Auto, EU or US, or Manual to enter your own host and port.Connect/Disconnect. The status reads Disconnected, Connecting or Connected, and the fields lock while connected.
VISCA over IP
Settings -> REMI -> VISCA over IP accepts VISCA lens commands on UDP port 52381 (editable while off).
What the RCP controls
ISO, EV, shutter, iris, white balance and tint, lens, zoom and focus. Taking a value on the RCP puts it in manual and the phone shows it in orange; tap the value on the phone, or use auto on the RCP, to go back to auto. Colour corrections (white balance RGB, gamma, black, hue, saturation, multimatrix, local contrast) go into the phone's LUT engine, see Color Shading.
Settings -> RCP Knob Stepssets how much one RCP knob step changes each value.Settings -> On RCP Losspicks what happens when the RCP disconnects: restore the last manual values, or go back to auto exposure.- The RCP's Gain Limit and Auto Exposure Range set the phone's exposure limits, see Settings.
Custom buttons (RCP -> Camera -> Custom)
| Button | Action |
|---|---|
| RTP, SRT | Start or stop the stream in that protocol |
| 13mm, 24mm, 77mm (your lenses) | Switch lens |
| bit-, bit+ | Stream bitrate down or up by 0.1 Mbps while streaming |
| Flip | Switch between front and back camera |
| CorrON / CorrOFF | RCP colour corrections on or off, shows the current state |
Blackmagic Zoom Demand / Focus Demand (iOS)
Plug a Blackmagic Zoom Demand and/or Focus Demand into the phone over USB-C. They need the firmware released with iPhone support. No setup is needed.
- Zoom Demand rocker: zooms, speed follows how far you push it.
- Speed dial: scales the zoom speed.
- Long press the first button with the rocker at rest: re-centres the rocker.
- Focus Demand wheel: focus.
One Zoom Demand per phone. Turn on Settings -> Zoom -> Soft Zoom Ramp for broadcast-servo style starts and stops.
Settings
Use the search bar at the top of Settings to find any option. Stepper rows repeat when you hold + or -, and a reset button brings the default back.
Sensor Format (iOS)
16:9or4:3 Full Sensor. In 4:3 the stream, recording and HDMI are 4:3 as well, and the preview and frame guide fit the 4:3 picture.- 4:3 is greyed out when the current resolution and frame rate have no 4:3 format, for example 4K 60. See Apple limitations.
- Photos in 4:3 are 24 MP. 48 MP is not offered, see Apple limitations. In 16:9, photos have the video size.
Photos (iOS)
Photos keep the capture EXIF, and the active LUT is rendered on the photo. In Apple Log and HLG a photo is the next video frame at the video size. If no frame arrives within 2 s, the photo fails with No video frame arrived, try again.
Color Space (iOS)
Every color space is listed. The ones the current lens, resolution and frame rate do not support are greyed out with Not available in the current format. The format picker also shows unsupported frame rates greyed out.
Stabilization (iOS)
Fast Preview lets the on-screen preview skip the stabilizer delay. Stream, recording and HDMI stay stabilized.
Chroma Subsampling (iOS)
4:2:2 keeps twice the color detail of 4:2:0. ProRes recording, HDMI output, the LUT and the Full 4:2:2 stream encoder use it.
Slow Preview When Hot (iOS)
When the phone is hot (thermal state serious or critical) and only the preview uses the LUT, the preview LUT runs on every second frame. Recording, stream and HDMI are not affected. On by default.
Exposure Limits (iOS)
Limit Auto Exposure keeps auto exposure inside a Min/Max ISO and Fastest/Slowest shutter window, like an RCP-controlled camera. Manual and RCP control are never limited. An RCP can set the window through Gain Limit and Auto Exposure Range.
Zoom (iOS)
- Base Lens:
Free Zoom(full range, lenses switch while zooming) or a fixed lens that zooms to 2x its focal length. Physical Zoom Only(Free Zoom only): no digital crop, zoom changes only by switching lens.Soft Zoom Ramp: eases start and stop on RCP zoom moves. Pinch zoom stays direct.
Widgets
RGB histogram, RGB parade, vectorscope, audio levels, grid and frame guide (preset or custom ratio, cross marker, mask and mask level). Highlight Scope enlarges one scope. Scope Refresh Rate trades smoothness against battery and heat; 60 Hz needs 60 fps capture.
Monitor
Shutter in Degrees: shows shutter as an angle everywhere, including Exposure Limits.Always Show Controls,Lock Horizontal,Lock Portrait,Monochrome Settings Icons.Swipe Twice to Exit(iOS): a single swipe on the home indicator does not leave the app.
Export, import and reset
Export Settingssaves every setting, the RCP corrections and the active LUT to a JSON file.Import Settingsreplaces all settings with an exported file, then restarts the app.Reset Entire Apperases all settings, imported LUTs, stream and REMI setup. Recordings are kept.
Settings API
Settings -> Settings API runs a REST server on the phone. See Settings API.
Apple limitations (iOS)
These come from Apple's capture system, not from CyanCamera. The app greys out what the phone cannot do and says why. Values were measured on iPhone 17 Pro with iOS 26; other models differ, and the greyed-out options in the app always show what your phone supports.
4:3 Full Sensor
| Limitation | Effect in the app |
|---|---|
| The 4:3 formats stop at 30 fps for 4K-class sizes (4032x3024) and at 60 fps for 1920x1440. There is no 4:3 format at 4K 60 or above. | 4:3 is greyed out at those rates, and the format picker greys out the rates 4:3 cannot do. |
| The 4032x3024 formats offer only Rec. 709 and P3. Apple Log and HLG exist in 4:3 only at 1920x1440. | Apple Log and HLG are greyed out in 4K 4:3. |
| The 4032x3024 formats have no ProRes 422 encoder. | ProRes 422 is greyed out in 4K 4:3. |
| H.264 stops at level 5.2, which allows at most 4096x2304 per frame. | H.264 recordings in 4K 4:3 are scaled to 3536x2652. HEVC records the full 4032x3024. |
Photos
| Limitation | Effect in the app |
|---|---|
| A 48 MP photo fails while a video format runs, on every lens. | Photos are capped at 24 MP. |
| Apple does not support photo capture in Apple Log. | In Apple Log and HLG a photo is the next video frame, at the video size. |
| The RAW sensor formats have no photo pipeline. | No photos while a RAW codec is selected. |
ProRes RAW
| Limitation | Effect in the app |
|---|---|
| Apple allows ProRes RAW only on external storage. | RAW is greyed out until an external drive is the save location. |
| The RAW formats offer only Apple Log and Apple Log 2. | RAW always records Log. |
| White balance must stay locked for the whole take. Apple documents the lock; on the device, any white balance change during the take also makes the take fail, even with the lock held. | White balance is frozen at take start and every change is refused until the take ends. |
| RAW at 120 fps exists only on the main (wide) lens. The other lenses stop at 60 fps. | RAW recording at 120 fps refuses to start on the other lenses and says why. |
Other
| Limitation | Effect in the app |
|---|---|
| Apple's low-latency encoder supports only High profiles, so not HEVC Main 4:2:2. | Full 4:2:2 streaming uses the normal encoder, with a few frames more delay. Encoder -> Standard picks the normal encoder for 4:2:0 too. |
| The capture timecode carries no drop-frame flag. | Drop frame is read from the LTC source. TC Source has Auto, Drop and Non-drop to override it. |
| HDMI Broadcast mode needs iOS 27 and a supported capture format. | Broadcast is greyed out otherwise. |
Settings API
Settings -> Settings API -> REST API runs a REST server on port 8090, at http://<phone-ip>:8090. It is on by default. When you turn it off, it stays off across restarts and imports. Reset Entire App turns it on again. While it is on, the page lists the phone's addresses.
There is no authentication: anyone on the same network can read and replace the settings, including the SRT passphrase. Turn it off when you do not need it.
Endpoints
| Endpoint | Does |
|---|---|
GET /api/settings | Returns the export JSON, the same file as Export Settings without the lut field |
PUT /api/settings | Imports the JSON, then restarts the app. POST is accepted too |
GET /api/lut | Returns the active 3D LUT, read-only |
GET /api/rcp-steps | Returns the RCP knob steps |
PUT /api/rcp-steps | Sets RCP knob steps, no restart. POST is accepted too |
GET /api/status | Battery, temperature and thermal state, read-only |
Export and import
curl -o phone-settings.json http://<phone-ip>:8090/api/settings
curl -X PUT -H 'Content-Type: application/json' --data-binary @phone-settings.json http://<phone-ip>:8090/api/settings
The import answers 200 {"status":"ok","restarting":true}, and the app restarts about 0.5 s later. The API comes back after the restart, so wait a few seconds before the next request. The request must send Content-Length (curl --data-binary does this). Chunked uploads get 411. The body limit is 32 MB.
Export format
| Field | Content |
|---|---|
exportedAt | ISO 8601 time of the export |
app | version, build |
device | platform, osVersion, current video preset |
camera | Live sensor values and RCP corrections (ISO, shutter, white balance, focus, zoom, iris, lift/gamma/gain, saturation, hue). null when the camera screen is not open. Export only, not applied on import |
stream | Effective RTP and SRT settings with defaults filled in: mode, rtp (target, preset, codec, bitrate, bitDepth, control), srt (target, config, preset, bitDepth, autoRetry) |
lut | Active 3D LUT: name, source, uri, encoding (cube text or base64 for Studio binary LUTs), data. null when no LUT is active. data is null when the LUT file is missing. Bundled LUTs have an empty uri and are not copied, every phone has them. Not in GET /api/settings, read it with GET /api/lut |
native | microphoneInput, externalFolderPath |
settings | Every stored app setting by key. JSON values are decoded. This includes the stored manual camera values, the RCP corrections and the RCP knob steps (cyan_encoder_steps) |
Import behaviour
settingsandstreamare written together, then stored settings that are not in the file are removed. A key edited undersettingswins over the same value instream.- From the start of the import until the app restarts, nothing else in the app can save settings, so old values cannot be written back.
- The
lutfield is optional. Without it, the stored LUT settings are imported as they are, so the active LUT works only if its file is already on this phone. - The
lut.datais saved to the app'simported-luts/folder, and every reference to the oldlut.uripoints to the new file. Imported LUTs in the library that do not exist on this phone are removed. - The microphone mode is restored only for
auto,builtinandexternal. A specific input and the external recording folder are tied to the original phone and are not restored. - The REST API on/off switch, the REMI identity, the welcome screen state and the stream auto-resume flag keep the importing phone's values.
- The SRT passphrase is in the export as plain text.
3D LUT
The active LUT is large, so GET /api/settings leaves it out. GET /api/lut returns it: the lut object of the export, or null when no LUT is active.
curl -o phone-lut.json http://<phone-ip>:8090/api/lut
{"name":"Show.cube","uri":"file:///.../Show.cube","source":"file","encoding":"cube","data":"LUT_3D_SIZE 33\n..."}
To copy the settings and the LUT to another phone, read both back to back, so the LUT matches the settings. Add the LUT to the settings JSON as lut, then import it:
jq --slurpfile lut phone-lut.json '.lut = $lut[0]' phone-settings.json > phone-full.json
curl -X PUT -H 'Content-Type: application/json' --data-binary @phone-full.json http://<other-phone-ip>:8090/api/settings
RCP knob steps
The change per knob click, the same values as Settings -> RCP Knob Steps. A PUT can send all keys or only the ones to change. The new steps apply at once, and the response is the full set after the change.
curl http://<phone-ip>:8090/api/rcp-steps
curl -X PUT -H 'Content-Type: application/json' --data '{"wbNormal":500,"ev":0.25}' http://<phone-ip>:8090/api/rcp-steps
{"isoNormal":1,"isoFine":1,"wbNormal":200,"wbFine":1,"tint":0.001,"ev":0.1,"focusNormal":0.02,"focusFine":0.001}
| Key | Unit | Range |
|---|---|---|
isoNormal | 1/3 stop | 1 to 6 |
isoFine | ISO | 1 to 50 |
wbNormal | K | 50 to 1000 |
wbFine | K | 1 to 100 |
tint | tint units | 0.001 to 0.05 |
ev | EV | 0.05 to 1 |
focusNormal | lens position (0 to 1) | 0.005 to 0.1 |
focusFine | lens position (0 to 1) | 0.001 to 0.01 |
The knob steps are also in the full export, so /api/settings and Export Settings / Import Settings include them.
Status
Read-only, for monitoring. Poll it as often as you need.
curl http://<phone-ip>:8090/api/status
{"battery":{"level":82,"state":"charging","lowPowerMode":false},"thermal":"nominal","batteryTemperature":31.5}
| Field | Content |
|---|---|
battery.level | 0 to 100, or null when the phone does not report it |
battery.state | unknown, unplugged, charging or full |
battery.lowPowerMode | Low Power Mode (iOS) or Battery Saver (Android) is on |
thermal | nominal, fair, serious or critical. On Android, light and moderate throttling are fair, severe is serious, critical or worse is critical. Android below 10 always reports nominal |
batteryTemperature | Battery temperature in °C (Android). null on iOS, which does not give apps a temperature |
Errors
The body is always {"error": "<reason>"}.
| Status | Cause |
|---|---|
| 400 | Body is not JSON, JSON without a settings object, or invalid RCP knob steps (unknown key, not a number, out of range) |
| 403 | PUT or POST sent from a browser (has an Origin header), or a Host header that is not an IP address or localhost |
| 404 | Unknown path |
| 405 | Method not allowed (/api/status and /api/lut take only GET) |
| 409 | Settings import while recording, or any PUT while an import is running |
| 411 | Chunked transfer encoding |
| 413 | Body over 32 MB |
| 500 | Import failed while writing settings |
Settings reference
Every key under settings in the export, with its values. The app stores each value as text; the export decodes booleans, numbers and objects, and the import encodes them back, so edit them as plain JSON. A key that is missing from the file is removed on import and the app falls back to the default. Keys without a @ prefix are stored the same way, the prefix is history.
RCP, REMI and lens control
| Key | Values | Default | Notes |
|---|---|---|---|
cyan_encoder_steps | {isoNormal, isoFine, wbNormal, wbFine, tint, ev, focusNormal, focusFine} | see RCP knob steps | Same ranges as /api/rcp-steps |
cameraNumber | integer 1 to 255 | 1 | Settings -> Camera Number |
@remi_camera_number | integer 1 to 255 | 1 | REMI server copy of the camera number, keep equal to cameraNumber |
@remi_camera_name | text | CyanCamera | Name announced to the RCP |
@restore_policy | manual, auto | manual | Settings -> On RCP Loss |
@exposure_limits | {enabled, minIso, maxIso, minShutter, maxShutter} | {false, 50, 800, 0.002, 0.02} | Shutter in seconds: minShutter is the fastest (1/500), maxShutter the slowest (1/50) |
@manual_camera_params | {iso, exposure, autoIso, autoExposure, whiteBalance, whiteBalanceTint, redGain, greenGain, blueGain, autoWhiteBalance, focus, autoFocus, aperture, autoIris} | {} | Only the locked values are present. exposure in ms, whiteBalance 2000 to 10000 K, whiteBalanceTint -1 to 1, gains -1 to 1, focus 0 to 1, aperture f-stop. The auto* flags are only ever false |
@zoom_mode | {mode, softRamp, physicalOnly, maxZoom} | {"free", false, false, 10} | mode is free or a focal length in mm from the lens list (13, 24, 48, 77, 154 on a Pro). maxZoom 1.5 to 10, Free Zoom only |
@zoom_factor | number above 0 | none | Native zoom factor restored at start |
selectedLensIndex | integer from 0 | 0 | Index into the lens list |
@slr_lens_flip | true, false | false | |
@anamorphic_desqueeze | 1, 1.33, 1.55, 2 | 1 | |
@remi_cloud_enabled | true, false | false | Connects at start when true and a tag is set |
@remi_cloud_tag | text | empty | |
@remi_cloud_region | auto, as1, au1, br1, eu1, us1, manual | auto | Old eu and us are read as eu1 and us1 |
@remi_cloud_host | host name | remi.cyanview.com | Used with manual |
@remi_cloud_port | 1 to 65535 | 7887 | |
cyan_visca | {enabled, port} | {false, 52381} | VISCA over IP, UDP |
cyan_camera_server | {enabled, port} | {true, "8080"} | Studio LUT WebSocket server, port as text |
RCP colour corrections
| Key | Values | Default | Notes |
|---|---|---|---|
@rcp_corrections_active | true, false | false | CorrON / CorrOFF |
@rcp_chained | true, false | true | RCP grade before the file LUT |
@rcp_multimatrix_enabled | true, false | false | With false the vectors are kept but render neutral |
@rcp_corrections | object, keys below | {} | Applied only when @rcp_corrections_active is true |
@rcp_corrections keys:
| Key | Range | Neutral |
|---|---|---|
saturation | 0 to 2 | 1 |
hue | -1 to 1 | 0 |
redGain, greenGain, blueGain | -1 to 1 | 0 |
masterPed, redPed, greenPed, bluePed | -1 to 1 | 0 |
masterGamma, redGamma, greenGamma, blueGamma | -1 to 1 | 0 |
contrastEnabled | true, false | true |
contrastPivot | 0 to 1 | 0.5 |
contrastAdjust | -1 to 1 | 0 |
multimatrix | 16 × {hue, sat}, hue -1 to 1, sat 0 to 2 | hue 0, sat 1 |
multimatrixCnt | 16 | 16 |
multimatrixSelect | {mode, sector}, mode normal, gate or peek, sector 0 to 15 | {"normal", 0} |
Camera format
| Key | Values | Default | Notes |
|---|---|---|---|
colorSpace | Rec. 709, P3 D65, Rec. 2020 HDR, Apple Log, Apple Log 2 | Rec. 709 | Falls back when the lens does not support it |
stabilizationMode | off, standard, cinematic, cinematicExtended, previewOptimized, cinematicExtendedEnhanced, lowLatency, auto | off | Falls back when the format does not support it |
fastLogPreview | true, false | true | Stabilization -> Fast Preview |
chromaSubsampling | 420, 422, 444 | format default | Falls back when the format does not support it |
sensorAspect | 16:9, 4:3 | 16:9 | Sensor Format |
thermalPreviewThrottle | true, false | true | Slow Preview When Hot |
The video preset is per stream protocol, see @rtp_preset and @srt_preset. Preset ids are 1080p or 4k followed by the frame rate: 23.98, 24, 25, 29.97, 30, 50, 59.94, 60, 95.904, 119.88, 120, 179.82, 239.76, 240. Example: 4k59.94.
Streaming
| Key | Values | Default | Notes |
|---|---|---|---|
@streaming_mode | rtp, srt | rtp | Protocol used by the stream button |
@rtp_target | {ip, port} | {"192.168.1.100", 4444} | |
@rtp_preset | preset id | 1080p29.97 | |
@rtp_codec | h264, h265 | h264 | |
@rtp_bitrate | bits per second, 0 | 0 | 0 uses the preset default |
@rtp_bit_depth | auto, force8, force10 | auto | |
@rtp_control | {enabled, port} | {false, 4447} | cy-fxvp control channel |
@rtp_encoder | lowLatency, standard, hevc422 | lowLatency | Encoder, iOS only. An older @rtp_hevc422 set to true reads as hevc422 |
@srt_target | {ip, port} | {"192.168.1.100", 9000} | |
@srt_preset | preset id | 1080p29.97 | |
@srt_codec | h264, h265 | h264 | |
@srt_bitrate | bits per second, 0 | 0 | 0 uses the preset default |
@srt_bit_depth | auto, force8, force10 | auto | |
@srt_stream_id | text | empty | |
@srt_latency | milliseconds | 120 | |
@srt_passphrase | text | empty | Plain text in the export |
@srt_connect_mode | caller, listener | caller | |
@srt_library | haivision, robotweax | haivision | iOS only, Android always uses Haivision |
@srt_audio_enabled | true, false | true | |
@srt_audio_bitrate | 64000, 128000, 192000 | 128000 | AAC-LC 48 kHz stereo |
@srt_timecode | true, false | false | NTP timecode SEI for Makito MultiSync, HEVC only |
@srt_ntp_server | host name or IP | pool.ntp.org | |
@srt_auto_retry | true, false | false | |
@srt_encoder | lowLatency, standard, hevc422 | lowLatency | Encoder, iOS only. An older @srt_hevc422 set to true reads as hevc422 |
The stream block of the export repeats these with defaults filled in: mode, rtp.target, rtp.preset, rtp.codec, rtp.bitrate, rtp.bitDepth, rtp.control, srt.target, srt.config (streamId, latency, passphrase, codec, bitrate, audioEnabled, audioBitrate, connectMode, library, timecodeEnabled, ntpServer), srt.preset, srt.bitDepth, srt.autoRetry.
Recording
| Key | Values | Default | Notes |
|---|---|---|---|
@recording_codec | hevc, h264, prores422proxy, prores422lt, prores422, prores422hq, proresraw, proresrawhq | hevc | RAW falls back to hevc without external storage or RAW support |
@recording_bitrate_mbps | 0, 40, 80, 120, 160, 240 | 0 | 0 is Auto. HEVC and H.264 only |
@recording_bit_depth | auto, force8, force10 | auto | |
@recording_name_template | text with #camera, #reel, #position, #timestamp | CYAN_#timestamp | |
@recording_name_camera | text | empty | |
@recording_name_reel | integer | empty | The next reel number, goes up after every recording |
@recording_name_position | text | empty |
LUT
| Key | Values | Default | Notes |
|---|---|---|---|
@lut_config | {lutActive, lutFileName, lutFileUri, lutSource} | inactive | lutSource is file, studio, bundled or null. When the import has lut, it rewrites lutFileUri to the restored file |
@lut_hdr_output | true, false | false | HDR LUT switch |
@lut_active_id | registry id | none | |
@lut_registry_v1 | array of {id, name, source, uri, outputTransfer, visibleInCarousel, createdAt} | bundled LUTs | source bundled or imported, outputTransfer rec709, hlg or pq, createdAt in ms. Imported entries whose file is missing are dropped on import |
@lut_scopes_v1 | {preview, hdmi, recording, rtp, srt} booleans | all true | Missing keys count as true |
HDMI, timecode and genlock
| Key | Values | Default | Notes |
|---|---|---|---|
@hdmi_render_mode | preview, output, broadcast | output | output is Video Feed. broadcast drops to output when unsupported |
@hdmi_audio_enabled | true, false | true | |
@genlock_enabled | true, false | false | |
@timecode_external | true, false | true | false is the internal clock |
@timecode_source_name | text | none | Name of the external source, missing means the first one found |
@network_midi | true, false | false | Receive MIDI Timecode |
@timecode_offset_frames | integer -30 to 30 | 0 |
Monitor, widgets, lock and zen
| Key | Values | Default | Notes |
|---|---|---|---|
showOverlay | true, false | false | Controls shown |
alwaysShowControls | true, false | false | |
showHistogram, showParade, showVectorscope | true, false | false | |
showAudioLevels | true, false | false | |
showGrid | true, false | false | |
frameGuide | {ratio, cross, mask, maskLevel} | {"", true, true, 0.6} | ratio is "" for off, a preset like 2:1 or 9:16, or a decimal. maskLevel 0 to 1 |
highlightedScope | histogram, parade, vectorscope, none | none | |
scopeRate | 5, 15, 30, 60 | 30 | Hz |
zenVisible | {parameterBar, status, actionsRow, scopes, audioLevels, guides, timecode} booleans | all false, timecode true | What stays on screen in Zen mode |
lockTargets | {record, photo, cameraButtons, parameterBar, actionsRow, focus, pinchZoom} booleans | all true, pinchZoom false | What the lock button disables |
lockOverlayHorizontal | true, false | false | Lock Horizontal. Turning one lock on clears the other |
lockOverlayPortrait | true, false | false | Lock Portrait |
shutterAngle | true, false | false | Shutter in Degrees |
monochromeIcons | true, false | true | |
deferHomeGesture | true, false | true | Swipe Twice to Exit |
previewMode | fit, through | through | |
tileOpacity | 0 to 1 | 0.3 |
Device-only keys
These are in the export but the import keeps the importing phone's own values.
| Key | Values | Notes |
|---|---|---|
@settings_api_enabled | true, false | REST API switch |
@remi_device_id | text | REMI device id, from the vendor id or Android id |
@remi_cloud_huid | text | Cloud relay identity |
@welcome_done | true | Permissions screen done |
@streaming_active | rtp, srt | Set while a stream runs, used to resume after a restart |