Controla uma webcam Insta360 Link no Linux, incluindo os recursos que a Insta360 só expõe pelo Link Controller, aplicativo que existe apenas para Windows e macOS.
Não há dependência externa: só a biblioteca padrão do Python 3.11 ou mais novo.
Nada de compilar, nada de Lazarus, nada de v4l-utils.
A Link funciona como webcam comum no Linux através do uvcvideo, e não falta
driver nenhum — o kernel 6.10 e posteriores até trazem uma correção específica
para este modelo (UVC_QUIRK_DISABLE_AUTOSUSPEND). O que falta é o programa que
fala os comandos proprietários. Sem ele, o rastreamento por IA fica desligado,
que é justamente o recurso que distingue esta câmera de uma webcam de 200 reais.
git clone <este repositório> ~/Projects/insta360-link-ctl
ln -s ~/Projects/insta360-link-ctl/bin/linkctl ~/.local/bin/linkctlO acesso a /dev/video* costuma vir da ACL da sessão gráfica, e nesse caso nada
mais é preciso. Se linkctl info reclamar de permissão, entre no grupo video:
sudo usermod -aG video "$USER" # exige sair e entrar de novo na sessãolinkctl info # firmware, modo, rastreamento, enquadramento
linkctl list # câmeras Insta360 conectadas
linkctl tracking on # liga o rastreamento por IA
linkctl tracking off
linkctl framing half # head | half | full
linkctl home # recentra o gimbal
linkctl detect # caixa do sujeito detectado
linkctl formats # resoluções e taxas oferecidas
linkctl ctl # lista os controles V4L2
linkctl ctl saturation 60 # altera um controle
linkctl pan 36000 # pan absoluto em arcsec (±522000)
linkctl tilt -18000
linkctl pan -r 3600 # relativo à posição atual
linkctl zoom 200 # 100 = 1x, 400 = 4x
linkctl mode deskview # normal | tracking | whiteboard | overhead | deskview
linkctl preset save mesa # guarda pan, tilt e zoom
linkctl preset recall mesa
linkctl preset list
linkctl dump -o estado.json # instantâneo (oculta identificadores do aparelho)
linkctl raw info 0x13 # lê um seletor cruEste é o comportamento mais importante de entender, e o que mais confunde.
A câmera só aceita o comando enquanto está capturando vídeo. Escrito em repouso, o ajuste parece funcionar — a leitura de volta confirma na hora — mas o firmware o descarta cerca de um segundo depois, sem erro nenhum, e ele não sobrevive à captura seguinte. O procedimento certo, portanto, é:
- Abra primeiro o que vai usar a câmera: a reunião, o OBS, a gravação.
- Com o vídeo já rodando, execute
linkctl tracking on.
Feito assim, o rastreamento engata em cerca de dois segundos e a câmera passa a lembrar da preferência: nas capturas seguintes ele volta sozinho, sem precisar repetir o comando.
O linkctl não confia na leitura imediata: ele escreve, espera o tempo de
descarte e reconfere. Se o valor não sobreviveu, ele avisa em vez de mentir que
deu certo, e sai com código 3 para que um script perceba que o ajuste não pegou.
Essa verificação empírica substituiu uma tentativa anterior de detectar o
repouso pelo primeiro byte do seletor de modo. Não funciona: em repouso esse
byte lê ora 0x00, ora 0xff, e não há sinal em banda confiável para "há
captura em andamento".
Os comandos proprietários trafegam por três unidades de extensão UVC. As GUIDs foram lidas dos descritores USB desta câmera; os seletores vêm de engenharia reversa da comunidade, e cada linha abaixo diz o quanto foi confirmado aqui.
| Unidade | GUID | Papel |
|---|---|---|
| info | FAF1672D-B71B-4793-8C91-7B1C9B7F95F8 |
modo, enquadramento, dados do aparelho |
| image | E307E649-4618-A3FF-82FC-2D8B5F216773 |
papel não confirmado; 6 seletores responsivos |
| ai | A8BD5DF2-1A98-474E-8DD0-D92672D194FA |
rastreamento por IA |
A busca é sempre por GUID, nunca pelo número da unidade, porque o número pode mudar entre versões de firmware. Nesta câmera eles resolvem para 9, 10 e 11.
| Recurso | Onde | Situação |
|---|---|---|
| Rastreamento por IA | ai, seletor 0x02 |
Confirmado: o gimbal se moveu e travou no sujeito |
| Dados do aparelho | info, seletor 0x03 |
Confirmado: série, UUID e firmware legíveis |
| Detecção do sujeito | info, seletor 0x14 |
Confirmado: caixa coerente com alguém em quadro |
| Enquadramento | info, seletor 0x13 |
Escrita aceita e lida de volta exata; efeito visual não validado |
| Formato do fluxo | info, seletores 0x1c e 0x1d |
Confirmado, somente leitura: espelham largura, altura e taxa do fluxo ativo |
| Recentrar gimbal | info, seletor 0x0E |
Só escrita, sem leitura de volta possível |
| Whiteboard, Overhead, DeskView | info, seletor 0x02 |
Confirmado na gen 1: aceitos durante captura, com mudança estrutural medida na imagem |
| Exposição | ver abaixo | Não encontrada: nenhuma escrita testada surtiu efeito |
O mapa de seletores vem do projeto de vrwallace,
que o levantou monitorando o aplicativo oficial no Windows. Sem esse trabalho
este projeto não existiria. O README de lá declara licença MIT, embora o
repositório não traga um arquivo LICENSE. A ideia de manter as posições
memorizadas do lado do programa, e não da câmera, também é de lá.
Outras fontes: fmontes/insta360-link-cli e a issue #55 do cameractrls.
O seletor de modo (info 0x02) não deve ser escrito à mão para ligar o
rastreamento. A câmera o atualiza sozinha para 0x01 quando começa a rastrear.
Escrever nele produz apenas leituras enganosas, e por isso set_tracking toca
somente a unidade de IA.
A escrita só é aceita durante uma captura de vídeo. Em repouso o firmware aceita a requisição sem erro, a leitura de volta confirma, e cerca de um segundo depois o valor volta ao anterior. Nenhum dos projetos existentes documenta isso, e é a explicação mais provável para relatos de "o comando não faz nada".
Na issue #55 do cameractrls,
jorgecastro05 relatou em abril de 2025 ter localizado a exposição por captura
de tráfego USB no aplicativo do Windows: unidade 9, seletor 0x19, valor de
dois bytes little-endian, com 400, 500 e 640 surtindo efeito visível.
No firmware v1.4.3.8_build5 essa escrita não funciona. Testamos com o gimbal parado e a cena fixa, alternando 400 e 8000 em seis ciclos de quatro segundos e medindo a luminância média dos quadros. A luminância não acompanhou o valor escrito: ela decaiu de forma monótona de 72,9 para 28,4 e estabilizou, que é o comportamento do ganho automático assentando. A leitura de volta do seletor também nunca ecoa o que foi escrito e só decai, o que sugere telemetria e não um registrador gravável.
Isso não desmente o achado dele. As explicações plausíveis são firmware diferente, ou a necessidade de desligar antes a exposição automática por algum seletor que não localizamos. Fica registrado para quem for tentar de novo não repetir o mesmo caminho.
Os seletores 0x16 a 0x1e da unidade info existem e respondem, e nenhum deles
está mapeado. Quem quiser investigar pode partir de linkctl dump.
pan_absolute e tilt_absolute funcionam bem, mas até a primeira escrita a
câmera reporta um valor obsoleto e fora do intervalo — aqui, 34083580 num
controle que vai de −522000 a 522000. Depois de uma escrita qualquer, a leitura
passa a devolver exatamente o valor escrito.
Isso é fácil de confundir com leitura corrompida, e nós mesmos concluímos isso
antes de testar direito. O linkctl detecta o valor fora do intervalo e explica
o que fazer, em vez de repassar o número.
Os seletores 0x1c e 0x1d da unidade info espelham o formato do fluxo
ativo, o que confirmamos em quatro capturas diferentes:
| Capturando | 0x1c |
Decodificado |
|---|---|---|
| 1280x720@30 | 00050000 d0020000 1e00 |
1280 × 720 @ 30 |
| 1920x1080@30 | 80070000 38040000 1e00 |
1920 × 1080 @ 30 |
| 3840x2160@24 | 000f0000 70080000 1800 |
3840 × 2160 @ 24 |
| 1280x960@25 | 00050000 c0030000 1900 |
1280 × 960 @ 25 |
O arranjo é largura em u32, altura em u32 e taxa em u16, tudo
little-endian. Parecia a chave dos 60 fps, e não é: escrever 60 em 0x1d,
escrever 1920x1080@60 em 0x1c e mexer em 0x16 são todos aceitos sem erro
e não mudam nada — nem o valor lido de volta, nem a lista de formatos, nem o
devnum do dispositivo, que continuaria igual se a câmera tivesse reenumerado.
São telemetria.
Restam sem mapear os seletores 0x18, 0x1a, 0x1b e 0x1e da unidade info.
Não fomos adiante por força bruta: os seletores 0x04 e 0x05 da unidade de IA
são só-escrita, e mandar valor arbitrário para um seletor cego numa câmera não é
experimento, é aposta.
Duas, medidas na gen 1 com firmware v1.4.3.8_build5:
- Seletor 20 é a caixa do sujeito, não o giroscópio. O mapa dele descreve
240 bytes de "IMU/gyroscope floats". Aqui o byte 0 vale 1 quando há alguém em
quadro e 0 quando não há, e os quatro floats seguintes ficam sempre em [0,1],
acompanham a pessoa ao se mover e obedecem a
x+largura < 1ey+altura < 1, com proporções de pessoa em pé. Um giroscópio não zeraria na ausência de gente nem ficaria confinado a [0,1]. - O byte de flag do modo não é ecoado como escrito. Pedir Overhead
(
05 03) lê de volta05 00; voltar a Normal (00 00) depois do DeskView lê00 10. Quem determina o modo é o byte 0, e comparar o par leva a relatar "desconhecido" para um modo que está correto.
Estas vêm do firmware e nenhum programa em Linux resolve:
- Não existe controle de exposição pelo V4L2. O firmware não declara
exposição no Camera Terminal (
bmControls = 20 7a 02), então o kernel não tem o que expor. O mesmo vale para ganho, compensação de contraluz e gama. - Pan/Tilt Speed são desativados pelo kernel no arranque. A câmera anuncia
o controle e depois recusa a requisição com erro de entrada e saída, e o
uvcvideoregistraUVC non compliancee o desativa em definitivo. Não há movimento suave de gimbal pelo V4L2. - 50 e 60 fps não aparecem. A especificação oficial da Link lista
1080p@50/60fps, e a câmera só oferece 24, 25 e 30 ao Linux. No Windows isso é uma chave do Link Controller que faz a câmera reenumerar. Procuramos o seletor e não achamos; veja abaixo o que já foi descartado.
python3 -m unittest discover -s tests -t . # 47 testes, sem hardware
python3 tests/generate_fixtures.py # regenera os fixtures (exige a câmera)A camada pura (protocol.py, usbdesc.py) é a única testada, e de propósito:
é onde mora a lógica. As camadas xu.py e v4l2.py são invólucros finos de
ioctl, cujo comportamento só se verifica contra o hardware de verdade.
Os valores esperados nos testes vêm de bytes capturados da câmera real, não recalculados pela implementação — do contrário o teste passaria por construção e não provaria nada.