Tutorial: Getting stream from Chinese sport camera

In my spare time I'm working on a quadcopter as hobby. I started by the basics, a remote controlled quadcopter easy to flight. A magnometer and barometer helped me to avoid crash the drone at me and I could go forward to the next stage.

See what the quadcopter sees

An FPV camera is coming but is not here now. So, what can I do?
chinesecam
Buy a cheap sport camera on the local commerce

This camera above is frequently rebranded by the local seller. It is widely known as SJ400 [1] but here in Chile is known as Ultra [2].

Video stream

According to the included manual, wifi support works in companion to GoPlus Cam App (iOS/Android), but guess what, I don't want to use my cellphone to see it, I want my computer/notebook to do that.

manual_1200

So, let's see the cam network

nmap it

I connected to an AP WEP protected with ESSID "BOLD3 " (BOLD3 is the model). The IP address assigned was one in the 192.168.25.0/24 subnet:

   wlp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.25.101  netmask 255.255.255.0  broadcast 192.168.25.255

I suspect 192.168.25.1 should be the cam. Let's try:

Command:

nmap 192.168.25.1

Output:

Starting Nmap 7.60 ( https://nmap.org ) at 2018-08-18 15:31 -03
Nmap scan report for _gateway (192.168.25.1)
Host is up (0.043s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE
80/tcp   open  http
8080/tcp open  http-proxy
8081/tcp open  blackice-icecap

Is the stream in port 80 ?

Command:

curl -v http://192.168.25.1/

Output:

*   Trying 192.168.25.1...
* TCP_NODELAY set
* Connected to 192.168.25.1 (192.168.25.1) port 80 (#0)
> GET / HTTP/1.1
> Host: 192.168.25.1
> User-Agent: curl/7.58.0
> Accept: */*
> 
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: lwIP/1.3.1 (http://savannah.nongnu.org/projects/lwip)
< Content-type: text/html

At least I know the cam is powered by open source software :) but what it is ?

My browser shows:
login
In this point I close my eyes and I let the flow going throught every button there.

Interesting! At first I got a run status page after clicking some button:

run_status.shtml:

config1

But there is even more:

wireless_config.shtml

config2

net_config.shtml

config3

system_config.shtml

config4

Maybe if I would have guessed the user/password at the beginnig, something would be showed on system config page.

What about another ports 8080 and 8081 ?

Command:

curl -v http://192.168.25.1:8080/

Output:

*   Trying 192.168.25.1...
* TCP_NODELAY set
* Connected to 192.168.25.1 (192.168.25.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: 192.168.25.1:8080
> User-Agent: curl/7.58.0
> Accept: */*
> 
* Empty reply from server
* Connection #0 to host 192.168.25.1 left intact

8080 is discarded because of the empty response

Command:

curl -v http://192.168.25.1:8081/

Output:

*   Trying 192.168.25.1...
* TCP_NODELAY set
* Connected to 192.168.25.1 (192.168.25.1) port 8081 (#0)
> GET / HTTP/1.1
> Host: 192.168.25.1:8081
> User-Agent: curl/7.58.0
> Accept: */*
> 

Connection is stuck by a long time. This port is discarded too.

I did not get my precious stream so I should try another approach.

The App

What I know is the app works.
image1
Thereby I have two ways for getting my precious stream source:

  • Sniff the communication between the app and the cam
  • Decompile the Android App and find URLs (very easy)

Decompiling an Android App

The first step is download the "GoPlus Cam" APK. After googling I found one here:
https://www.apkmonk.com/app/generalplus.com.GPCamDemo/

The second step is to get a tool for doing the job:
https://ibotpeaches.github.io/Apktool/

Apktool is able to convert Dalvik dex code into a human readable one called smali. dex and smali are in the same level, they just differ in the representation.

The third step is run the tool:

Command:

java -jar apktool_2.3.3.jar d generalplus.com.GPCamDemo_2017-12-18.apk

Output:

I: Using Apktool 2.3.3 on generalplus.com.GPCamDemo_2017-12-18.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
S: WARNING: Could not write to (/home/felipe/.local/share/apktool/framework), using /tmp instead...
S: Please be aware this is a volatile directory and frameworks could go missing, please utilize --frame-path if the default storage directory is unavailable
I: Loading resource table from file: /tmp/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

Here it is!

Command:

generalplus.com.GPCamDemo_2017-12-18$ ls

Output:

AndroidManifest.xml  apktool.yml  assets  lib  original  res  smali

Finding URLs on decoded resources

The lazy way for lazy people like me is let the machine do the hard work:

Command:

generalplus.com.GPCamDemo_2017-12-18$ find . -name *.smali -exec egrep http {} \;

Output:

    const-string v10, "http://%s:8080/?action=stream"
    const-string v1, "http://%s:8080/?action=stream"
    const-string v2, "http://%s:8080/?action=stream"
.field public static final STREAMING_URL:Ljava/lang/String; = "http://%s:8080/?action=stream"

Finally!!
Do you remember 8080 port gives me an empty response ? just a query param action was missing

Command:

ffprobe http://192.168.25.1:8080/?action=stream

Output:

ffprobe version 3.4.2-2 Copyright (c) 2007-2018 the FFmpeg developers
  built with gcc 7 (Ubuntu 7.3.0-16ubuntu2)
Input #0, mpjpeg, from 'http://192.168.25.1:8080/?action=stream':
  Duration: N/A, bitrate: N/A
    Stream #0:0: Video: mjpeg, yuvj422p(pc, bt470bg/unknown/unknown), 480x272, 25 tbr, 25 tbn, 25 tbc

The resolution is very disappointing and there is no audio stream, but it's something.

ffplay

Camera mounting

The weight in a quadcopter is important and every gram counts so I stripped down parts that are not useful on the flight:

IMG_1892_1200

The quadcopter looks like this:

IMG_1896_1200

IMG_1894_1200

IMG_1897_1200

qc

Conclusion

Beyond the specific steps made here, what have value for me in this tutorial is the behind reasoning, for example, take the easier way and let the most hard work to the machine, it's part of it.

References

[1] https://www.aliexpress.com/item/SJ4000-WIFI-Action-Camera-Diving-30M-Waterproof-1080P-Full-HD-Go-Underwater-Helmet-Sport-Camera-Sport/32802552443.html?spm=2114.search0104.8.4.88597feajsXq3F&priceBeautifyAB=0

[2] https://web.archive.org/web/20180818173611/https://www.casaroyal.cl/producto/camara-deportiva-full-hd-1080p-wifi/