├── .gitignore
├── CMakeLists.txt
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── build.sh
├── example
├── .gitignore
├── README.md
├── app.js
├── package.json
├── public
│ └── index.html
└── yarn.lock
├── open_xcode.sh
├── run.sh
└── src
├── Banner.cpp
├── Banner.hpp
├── Frame.hpp
├── FrameListener.cpp
├── FrameListener.hpp
├── JpegEncoder.cpp
├── JpegEncoder.hpp
├── SimpleServer.cpp
├── SimpleServer.hpp
├── StreamClient.h
├── StreamClient.mm
└── minicap.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | CMakeCache.txt
3 | CMakeFiles
4 | CMakeScripts
5 | CTestTestfile.cmake
6 | Makefile
7 | build
8 | cmake_install.cmake
9 | install_manifest.txt
10 | xcode
11 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.5.2)
2 | project(ios_minicap)
3 |
4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall -g")
5 |
6 | set(SOURCE_FILES
7 | src/minicap.cpp
8 | src/SimpleServer.cpp src/SimpleServer.hpp
9 | src/FrameListener.cpp src/FrameListener.hpp
10 | src/Banner.cpp src/Banner.hpp
11 | src/JpegEncoder.cpp src/JpegEncoder.hpp
12 | src/StreamClient.mm src/StreamClient.h
13 | src/Frame.hpp)
14 |
15 | add_executable(ios_minicap ${SOURCE_FILES})
16 |
17 | include_directories(
18 | /usr/local/opt/jpeg-turbo/include
19 | )
20 |
21 | target_link_libraries (ios_minicap
22 | "-framework Foundation"
23 | "-framework CoreFoundation"
24 | "-framework CoreMedia"
25 | "-framework CoreVideo"
26 | "-framework CoreMediaIO"
27 | "-framework AVFoundation"
28 | /usr/local/opt/jpeg-turbo/lib/libturbojpeg.a)
29 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **Steps to reproduce**
2 |
3 | **Expected results**
4 |
5 | **Actual results**
6 |
7 | **Environment(version of libraries, mac OS, iOS, etc)**
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright © 2013 CyberAgent, Inc.
2 | Copyright © 2016 The OpenSTF Project
3 | Copyright © 2016 Igor Pavlov
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Warning
2 |
3 | # This project along with other ones in [OpenSTF](https://github.com/openstf) organisation is provided as is for community, without active development.
4 | # You can check any other forks that may be actively developed and offer new/different features [here](https://github.com/openstf/stf/network).
5 | # Active development has been moved to [DeviceFarmer](https://github.com/DeviceFarmer) organisation.
6 |
7 | # ios-minicap
8 |
9 | iOS Minicap provides a socket interface for streaming realtime screen capture data out of iOS devices. It is built with AVFoundation and iOS Screen mirroring feature.
10 |
11 |
12 | ## Requirements
13 |
14 | * brew install libjpeg-turbo (>=1.5 is required)
15 | * Xcode (for the Frameworks)
16 | * [cmake](https://cmake.org/)
17 | * OS X Yosemite (10.9) or higher
18 | * iOS 8 or higher
19 | * [Lightning cable](https://en.wikipedia.org/wiki/Lightning_(connector)). See the list of devices.
20 |
21 | ## Building
22 |
23 | ```
24 | ./build.sh
25 | ```
26 |
27 | You should now have the binaries available in `./build`.
28 |
29 | ## Usage
30 |
31 | The minicap protocol is a simple push-based binary protocol. When you first connect to the socket, you get a global header followed by the first frame. The global header will not appear again. More frames keep getting sent until you stop minicap.
32 |
33 | Before run, please, check that:
34 |
35 | * Node.js 6+ is used (required to run example app.js)
36 | * the computer is trusted by the phone
37 | * the phone screen is not turned off
38 |
39 | You can try it using:
40 |
41 | ```
42 | ./run.sh
43 | ```
44 |
45 | And in another window:
46 |
47 |
48 | ```
49 | cd example
50 | npm install
51 | node app.js
52 | ```
53 |
54 | Then open http://localhost:9002 in browser
55 |
56 | **When device have big FPS, minicap is sending frames to example app trough the localhost connection too quickly, so it could not draw it quick enough. That could cause frames to delay.**
57 |
58 | ### Global header binary format
59 |
60 | Appears once.
61 |
62 | | Bytes | Length | Type | Explanation |
63 | |-------|--------|------|-------------|
64 | | 0 | 1 | unsigned char | Version (currently 1) |
65 | | 1 | 1 | unsigned char | Size of the header (from byte 0) |
66 | | 2-5 | 4 | uint32 (low endian) | Pid of the process |
67 | | 6-9 | 4 | uint32 (low endian) | Real display width in pixels |
68 | | 10-13 | 4 | uint32 (low endian) | Real display height in pixels |
69 | | 14-17 | 4 | uint32 (low endian) | Virtual display width in pixels |
70 | | 18-21 | 4 | uint32 (low endian) | Virtual display height in pixels |
71 | | 22 | 1 | unsigned char | Display orientation |
72 | | 23 | 1 | unsigned char | Quirk bitflags (see below) |
73 |
74 | #### Quirk bitflags
75 |
76 | Currently, the following quirks may be reported:
77 |
78 | | Value | Name | Explanation |
79 | |-------|------|-------------|
80 | | 1 | QUIRK_DUMB | Frames will get sent even if there are no changes from the previous frame. Informative, doesn't require any actions on your part. You can limit the capture rate by reading frame data slower in your own code if you wish. |
81 | | 2 | QUIRK_ALWAYS_UPRIGHT | The frame will always be in upright orientation regardless of the device orientation. This needs to be taken into account when rendering the image. |
82 | | 4 | QUIRK_TEAR | Frame tear might be visible. Informative, no action required. Neither of our current two methods exhibit this behavior. |
83 |
84 | ### Frame binary format
85 |
86 | Appears a potentially unlimited number of times.
87 |
88 | | Bytes | Length | Type | Explanation |
89 | |-------|--------|------|-------------|
90 | | 0-3 | 4 | uint32 (low endian) | Frame size in bytes (=n) |
91 | | 4-(n+4) | n | unsigned char[] | Frame in JPG format |
92 |
93 |
94 | ## Generation for Xcode
95 |
96 | Optionally you may want to use Xcode for developing or building. But still, you should use cmake as a build system.
97 |
98 | ```
99 | ./open_xcode.sh
100 | ```
101 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | mkdir build
4 | cd build
5 | cmake ../
6 | make
7 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Example: minicap over WebSockets
2 |
3 | A quick and dirty example to show how minicap might be used as part of an application. Also useful for testing.
4 |
5 | ## Requirements
6 |
7 | * [Node.js](https://nodejs.org/) >= 0.12 (for this example only)
8 |
9 | ## Running
10 |
11 | 1. Check that your device is connected and ADB is running with `adb devices`. The following steps may not work properly if you don't.
12 | ```
13 | adb devices
14 | ```
15 | 2. Set up a forward for the server we'll soon have running inside the device. Note that due to laziness the port is currently fixed to 1717.
16 | ```
17 | adb forward tcp:1717 localabstract:minicap
18 | ```
19 | 3. Get information about your display. Unfortunately the easy API methods we could use for automatic detection segfault on some Samsung devices, presumably due to maker customizations. You'll need to know the display width and height in pixels. Here are some ways to do it:
20 | ```
21 | adb shell wm size
22 | adb shell dumpsys display
23 | ```
24 | 4. Start the minicap server. The most convenient way is to use the helper script at the root of this repo.
25 | ```
26 | # Try ./run.sh -h for help
27 | ./run.sh -P 720x1280@720x1280/0
28 | ```
29 | The first set is the true size of your display, and the second set is the size of the desired projection. Larger projections require more processing power and bandwidth. The final argument is the rotation of the display. Note that this is not the rotation you want it to have, it simply specifies the display's current rotation, which is used to normalize the output frames between Android versions. If the rotation changes you have to restart the server.
30 | 5. Start the example app.
31 | ```
32 | PORT=9002 node app.js
33 | ```
34 | 6. Open http://localhost:9002 in your browser.
35 |
--------------------------------------------------------------------------------
/example/app.js:
--------------------------------------------------------------------------------
1 | const http = require('http')
2 | const path = require('path')
3 | const net = require('net')
4 |
5 | const express = require('express')
6 | const WebSocketServer = require('ws').Server
7 | const debug = require('debug')('example')
8 | const { Parser } = require('minicap')
9 |
10 | const app = express()
11 |
12 | const PORT = process.env.PORT || 9002
13 | const MINICAP_PORT = process.env.MINICAP_PORT || 12345
14 |
15 | app.use(express.static(path.join(__dirname, '/public')))
16 | app.get('/config.js', (req, res) => {
17 | res.status(200)
18 | .type('js')
19 | .send(`var WSURL = "ws://localhost:${PORT}"`)
20 | })
21 |
22 | const server = http.createServer(app)
23 | const wss = new WebSocketServer({ server: server })
24 |
25 | wss.on('connection', (ws) => {
26 | console.info('Got a client')
27 |
28 | const stream = net.connect({
29 | port: MINICAP_PORT
30 | })
31 |
32 | stream.on('error', (err) => {
33 | console.error(err)
34 | console.error('Be sure to run ios-minicap on port ' + MINICAP_PORT)
35 | process.exit(1)
36 | })
37 |
38 | function onBannerAvailable (banner) {
39 | debug('banner', banner)
40 | }
41 |
42 | function onFrameAvailable (frame) {
43 | ws.send(frame.buffer, {
44 | binary: true
45 | })
46 | }
47 |
48 | const parser = new Parser({
49 | onBannerAvailable,
50 | onFrameAvailable
51 | })
52 |
53 | function tryParse () {
54 | for (let chunk; (chunk = stream.read());) {
55 | parser.parse(chunk)
56 | }
57 | }
58 |
59 | stream.on('readable', tryParse)
60 | tryParse()
61 |
62 | ws.on('close', () => {
63 | console.info('Lost a client')
64 | stream.end()
65 | })
66 | })
67 |
68 | server.listen(PORT)
69 | console.info(`Listening on port ${PORT}`)
70 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "debug": "^2.3.2",
4 | "express": "^4.12.3",
5 | "minicap": "^0.1.0",
6 | "ws": "^1.1.1"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
194 |
--------------------------------------------------------------------------------
/example/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | accepts@~1.3.3:
6 | version "1.3.3"
7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
8 | dependencies:
9 | mime-types "~2.1.11"
10 | negotiator "0.6.1"
11 |
12 | array-flatten@1.1.1:
13 | version "1.1.1"
14 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
15 |
16 | content-disposition@0.5.1:
17 | version "0.5.1"
18 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.1.tgz#87476c6a67c8daa87e32e87616df883ba7fb071b"
19 |
20 | content-type@~1.0.2:
21 | version "1.0.2"
22 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
23 |
24 | cookie-signature@1.0.6:
25 | version "1.0.6"
26 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
27 |
28 | cookie@0.3.1:
29 | version "0.3.1"
30 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
31 |
32 | debug@^2.3.2:
33 | version "2.3.2"
34 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.2.tgz#94cb466ef7d6d2c7e5245cdd6e4104f2d0d70d30"
35 | dependencies:
36 | ms "0.7.2"
37 |
38 | debug@~2.2.0:
39 | version "2.2.0"
40 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
41 | dependencies:
42 | ms "0.7.1"
43 |
44 | depd@~1.1.0:
45 | version "1.1.0"
46 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
47 |
48 | destroy@~1.0.4:
49 | version "1.0.4"
50 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
51 |
52 | ee-first@1.1.1:
53 | version "1.1.1"
54 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
55 |
56 | encodeurl@~1.0.1:
57 | version "1.0.1"
58 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
59 |
60 | escape-html@~1.0.3:
61 | version "1.0.3"
62 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
63 |
64 | etag@~1.7.0:
65 | version "1.7.0"
66 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8"
67 |
68 | express@^4.12.3:
69 | version "4.14.0"
70 | resolved "https://registry.yarnpkg.com/express/-/express-4.14.0.tgz#c1ee3f42cdc891fb3dc650a8922d51ec847d0d66"
71 | dependencies:
72 | accepts "~1.3.3"
73 | array-flatten "1.1.1"
74 | content-disposition "0.5.1"
75 | content-type "~1.0.2"
76 | cookie "0.3.1"
77 | cookie-signature "1.0.6"
78 | debug "~2.2.0"
79 | depd "~1.1.0"
80 | encodeurl "~1.0.1"
81 | escape-html "~1.0.3"
82 | etag "~1.7.0"
83 | finalhandler "0.5.0"
84 | fresh "0.3.0"
85 | merge-descriptors "1.0.1"
86 | methods "~1.1.2"
87 | on-finished "~2.3.0"
88 | parseurl "~1.3.1"
89 | path-to-regexp "0.1.7"
90 | proxy-addr "~1.1.2"
91 | qs "6.2.0"
92 | range-parser "~1.2.0"
93 | send "0.14.1"
94 | serve-static "~1.11.1"
95 | type-is "~1.6.13"
96 | utils-merge "1.0.0"
97 | vary "~1.1.0"
98 |
99 | finalhandler@0.5.0:
100 | version "0.5.0"
101 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7"
102 | dependencies:
103 | debug "~2.2.0"
104 | escape-html "~1.0.3"
105 | on-finished "~2.3.0"
106 | statuses "~1.3.0"
107 | unpipe "~1.0.0"
108 |
109 | forwarded@~0.1.0:
110 | version "0.1.0"
111 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
112 |
113 | fresh@0.3.0:
114 | version "0.3.0"
115 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f"
116 |
117 | http-errors@~1.5.0:
118 | version "1.5.1"
119 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750"
120 | dependencies:
121 | inherits "2.0.3"
122 | setprototypeof "1.0.2"
123 | statuses ">= 1.3.1 < 2"
124 |
125 | inherits@2.0.3:
126 | version "2.0.3"
127 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
128 |
129 | ipaddr.js@1.1.1:
130 | version "1.1.1"
131 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.1.1.tgz#c791d95f52b29c1247d5df80ada39b8a73647230"
132 |
133 | media-typer@0.3.0:
134 | version "0.3.0"
135 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
136 |
137 | merge-descriptors@1.0.1:
138 | version "1.0.1"
139 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
140 |
141 | methods@~1.1.2:
142 | version "1.1.2"
143 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
144 |
145 | mime-db@~1.24.0:
146 | version "1.24.0"
147 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.24.0.tgz#e2d13f939f0016c6e4e9ad25a8652f126c467f0c"
148 |
149 | mime-types@~2.1.11:
150 | version "2.1.12"
151 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.12.tgz#152ba256777020dd4663f54c2e7bc26381e71729"
152 | dependencies:
153 | mime-db "~1.24.0"
154 |
155 | mime@1.3.4:
156 | version "1.3.4"
157 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
158 |
159 | minicap@^0.1.0:
160 | version "0.1.0"
161 | resolved "https://registry.yarnpkg.com/minicap/-/minicap-0.1.0.tgz#ba193aa24b4ef4f1b00155b672d13710a666b397"
162 |
163 | ms@0.7.1:
164 | version "0.7.1"
165 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
166 |
167 | ms@0.7.2:
168 | version "0.7.2"
169 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
170 |
171 | negotiator@0.6.1:
172 | version "0.6.1"
173 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
174 |
175 | on-finished@~2.3.0:
176 | version "2.3.0"
177 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
178 | dependencies:
179 | ee-first "1.1.1"
180 |
181 | options@>=0.0.5:
182 | version "0.0.6"
183 | resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
184 |
185 | parseurl@~1.3.1:
186 | version "1.3.1"
187 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
188 |
189 | path-to-regexp@0.1.7:
190 | version "0.1.7"
191 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
192 |
193 | proxy-addr@~1.1.2:
194 | version "1.1.2"
195 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.2.tgz#b4cc5f22610d9535824c123aef9d3cf73c40ba37"
196 | dependencies:
197 | forwarded "~0.1.0"
198 | ipaddr.js "1.1.1"
199 |
200 | qs@6.2.0:
201 | version "6.2.0"
202 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b"
203 |
204 | range-parser@~1.2.0:
205 | version "1.2.0"
206 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
207 |
208 | send@0.14.1:
209 | version "0.14.1"
210 | resolved "https://registry.yarnpkg.com/send/-/send-0.14.1.tgz#a954984325392f51532a7760760e459598c89f7a"
211 | dependencies:
212 | debug "~2.2.0"
213 | depd "~1.1.0"
214 | destroy "~1.0.4"
215 | encodeurl "~1.0.1"
216 | escape-html "~1.0.3"
217 | etag "~1.7.0"
218 | fresh "0.3.0"
219 | http-errors "~1.5.0"
220 | mime "1.3.4"
221 | ms "0.7.1"
222 | on-finished "~2.3.0"
223 | range-parser "~1.2.0"
224 | statuses "~1.3.0"
225 |
226 | serve-static@~1.11.1:
227 | version "1.11.1"
228 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.1.tgz#d6cce7693505f733c759de57befc1af76c0f0805"
229 | dependencies:
230 | encodeurl "~1.0.1"
231 | escape-html "~1.0.3"
232 | parseurl "~1.3.1"
233 | send "0.14.1"
234 |
235 | setprototypeof@1.0.2:
236 | version "1.0.2"
237 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08"
238 |
239 | "statuses@>= 1.3.1 < 2", statuses@~1.3.0:
240 | version "1.3.1"
241 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
242 |
243 | type-is@~1.6.13:
244 | version "1.6.13"
245 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.13.tgz#6e83ba7bc30cd33a7bb0b7fb00737a2085bf9d08"
246 | dependencies:
247 | media-typer "0.3.0"
248 | mime-types "~2.1.11"
249 |
250 | ultron@1.0.x:
251 | version "1.0.2"
252 | resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
253 |
254 | unpipe@~1.0.0:
255 | version "1.0.0"
256 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
257 |
258 | utils-merge@1.0.0:
259 | version "1.0.0"
260 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
261 |
262 | vary@~1.1.0:
263 | version "1.1.0"
264 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140"
265 |
266 | ws@^1.1.1:
267 | version "1.1.1"
268 | resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018"
269 | dependencies:
270 | options ">=0.0.5"
271 | ultron "1.0.x"
272 |
--------------------------------------------------------------------------------
/open_xcode.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | mkdir xcode
4 | cd xcode
5 | cmake -G Xcode ..
6 | open ios_minicap.xcodeproj
7 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -exo pipefail
4 |
5 | UDID=$(system_profiler SPUSBDataType | sed -n -E -e '/(iPhone|iPad)/,/Serial/s/ *Serial Number: *(.+)/\1/p')
6 | PORT=12345
7 | RESOLUTION="400x600"
8 |
9 | ./build/ios_minicap \
10 | --udid $UDID \
11 | --port $PORT \
12 | --resolution $RESOLUTION
13 |
--------------------------------------------------------------------------------
/src/Banner.cpp:
--------------------------------------------------------------------------------
1 | #include "Banner.hpp"
2 |
3 | #include
4 | #include
5 |
6 |
7 | Banner::Banner(DeviceInfo realInfo, DeviceInfo desiredInfo) {
8 | mSize = BANNER_SIZE;
9 | unsigned char quirks = 0;
10 | quirks |= QUIRK_DUMB;
11 |
12 | // Prepare banner for clients.
13 | mData = new unsigned char[mSize];
14 | mData[0] = (unsigned char) BANNER_VERSION;
15 | mData[1] = (unsigned char) BANNER_SIZE;
16 | putUInt32LE(mData + 2, getpid());
17 | putUInt32LE(mData + 6, realInfo.width);
18 | putUInt32LE(mData + 10, realInfo.height);
19 | putUInt32LE(mData + 14, desiredInfo.width);
20 | putUInt32LE(mData + 18, desiredInfo.height);
21 | mData[22] = (unsigned char) desiredInfo.orientation;
22 | mData[23] = quirks;
23 |
24 | std::cout << "== Banner ==" << std::endl;
25 | std::cout << "version: " << BANNER_VERSION << std::endl;
26 | std::cout << "size: " << BANNER_SIZE << std::endl;
27 | std::cout << "pid: " << getpid() << std::endl;
28 | std::cout << "real width: " << realInfo.width << std::endl;
29 | std::cout << "real height: " << realInfo.height << std::endl;
30 | std::cout << "desired width: " << desiredInfo.width << std::endl;
31 | std::cout << "desired height: " << desiredInfo.height << std::endl;
32 | std::cout << "orientation: " << desiredInfo.orientation << std::endl;
33 | std::cout << "quirks: " << (int) quirks << std::endl;
34 |
35 | printf("banner: ");
36 | for (int i = 0; i < mSize; i++) {
37 | printf("%x", mData[i]);
38 | }
39 | printf("\n");
40 | }
41 |
42 |
43 | unsigned char* Banner::getData() {
44 | return mData;
45 | }
46 |
47 | size_t Banner::getSize() {
48 | return mSize;
49 | }
50 |
51 | Banner::~Banner() {
52 | delete [] mData;
53 | }
54 |
--------------------------------------------------------------------------------
/src/Banner.hpp:
--------------------------------------------------------------------------------
1 | #ifndef IOS_MINICAP_BANNER_HPP
2 | #define IOS_MINICAP_BANNER_HPP
3 |
4 | #include
5 | #include
6 |
7 | #define BANNER_VERSION 1
8 | #define BANNER_SIZE 24
9 |
10 |
11 | static void putUInt32LE(unsigned char* data, int value) {
12 | data[0] = (value >> 0) & 0xFF;
13 | data[1] = (value >> 8) & 0xFF;
14 | data[2] = (value >> 16) & 0xFF;
15 | data[3] = (value >> 24) & 0xFF;
16 | }
17 |
18 |
19 | struct DeviceInfo {
20 | uint32_t width = 0;
21 | uint32_t height = 0;
22 | uint8_t orientation = 0;
23 | float fps;
24 | float density;
25 | float xdpi;
26 | float ydpi;
27 | bool secure;
28 | float size;
29 | };
30 |
31 |
32 | enum {
33 | QUIRK_DUMB = 1,
34 | QUIRK_ALWAYS_UPRIGHT = 2,
35 | QUIRK_TEAR = 4,
36 | };
37 |
38 |
39 | class Banner {
40 | public:
41 | Banner(DeviceInfo realInfo, DeviceInfo desiredInfo);
42 |
43 | ~Banner();
44 |
45 | unsigned char * getData();
46 | size_t getSize();
47 |
48 | private:
49 | unsigned char* mData;
50 | size_t mSize;
51 | };
52 |
53 | #endif //IOS_MINICAP_BANNER_HPP
54 |
--------------------------------------------------------------------------------
/src/Frame.hpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by pavlov on 16/11/16.
3 | //
4 |
5 | #ifndef IOS_MINICAP_FRAME_HPP
6 | #define IOS_MINICAP_FRAME_HPP
7 |
8 | #include
9 |
10 |
11 | enum Format {
12 | FORMAT_BGRA_8888 = 0x01,
13 | FORMAT_UNKNOWN = 0x00,
14 | };
15 |
16 |
17 | struct Frame {
18 | void const* data;
19 | Format format;
20 | uint32_t width;
21 | uint32_t height;
22 | uint32_t bytesPerRow;
23 | size_t size;
24 | };
25 |
26 | #endif //IOS_MINICAP_FRAME_HPP
27 |
--------------------------------------------------------------------------------
/src/FrameListener.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "FrameListener.hpp"
3 |
4 | FrameListener::FrameListener() {
5 | mPendingFrames = 0;
6 | mTimeout = std::chrono::milliseconds(100);
7 | mRunning = true;
8 | }
9 |
10 | void FrameListener::stop() {
11 | mRunning = false;
12 | }
13 |
14 | bool FrameListener::isRunning() {
15 | return mRunning;
16 | }
17 |
18 | void FrameListener::onFrameAvailable() {
19 | std::unique_lock lock(mMutex);
20 | mPendingFrames += 1;
21 | mCondition.notify_one();
22 | }
23 |
24 | int FrameListener::waitForFrame() {
25 | std::unique_lock lock(mMutex);
26 |
27 | while (mRunning) {
28 | if (mCondition.wait_for(lock, mTimeout, [this]{return mPendingFrames > 0;})) {
29 | return mPendingFrames--;
30 | }
31 | }
32 |
33 | return 0;
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/src/FrameListener.hpp:
--------------------------------------------------------------------------------
1 | #ifndef IOS_MINICAP_FRAMELISTENER_HPP
2 | #define IOS_MINICAP_FRAMELISTENER_HPP
3 |
4 | #include
5 | #include
6 | #include
7 |
8 |
9 | class FrameListener {
10 | public:
11 | FrameListener();
12 | void stop();
13 | bool isRunning();
14 | void onFrameAvailable();
15 | int waitForFrame();
16 |
17 | private:
18 | bool mRunning;
19 | std::mutex mMutex;
20 | std::condition_variable mCondition;
21 | std::chrono::milliseconds mTimeout;
22 | int mPendingFrames;
23 | };
24 |
25 |
26 | #endif //IOS_MINICAP_FRAMELISTENER_HPP
27 |
--------------------------------------------------------------------------------
/src/JpegEncoder.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "JpegEncoder.hpp"
3 |
4 |
5 | JpegEncoder::JpegEncoder(Frame *frame) {
6 | mCompressor = tjInitCompress();
7 | mQuality = 80;
8 | mSubsampling = TJSAMP_420;
9 | mFormat = TJPF_BGRA;
10 | mBufferSize = tjBufSize(
11 | frame->width,
12 | frame->height,
13 | mSubsampling
14 | );
15 | std::cout << "Allocating " << mBufferSize << " bytes for JPEG encoder" << std::endl;
16 |
17 | mEncodedData = tjAlloc(mBufferSize);
18 | mEncodedSize = 0;
19 | }
20 |
21 | JpegEncoder::~JpegEncoder() {
22 | tjDestroy(mCompressor);
23 | tjFree(mEncodedData);
24 | }
25 |
26 |
27 | void JpegEncoder::encode(Frame *frame) {
28 | if ( tjCompress2(
29 | mCompressor,
30 | (unsigned char*)frame->data,
31 | frame->width,
32 | frame->bytesPerRow,
33 | frame->height,
34 | mFormat,
35 | &mEncodedData,
36 | &mEncodedSize,
37 | mSubsampling,
38 | mQuality,
39 | TJFLAG_FASTDCT | TJFLAG_NOREALLOC) < 0 ) {
40 | std::cout << "Compress to JPEG failed: " << tjGetErrorStr() << std::endl;
41 |
42 | };
43 | }
44 |
45 | unsigned char *JpegEncoder::getEncodedData() {
46 | return mEncodedData;
47 | }
48 |
49 | size_t JpegEncoder::getEncodedSize() {
50 | return mEncodedSize;
51 | }
52 |
53 | unsigned long JpegEncoder::getBufferSize() {
54 | return mBufferSize;
55 | }
56 |
--------------------------------------------------------------------------------
/src/JpegEncoder.hpp:
--------------------------------------------------------------------------------
1 | #ifndef IOS_MINICAP_JPEGENCODER_HPP
2 | #define IOS_MINICAP_JPEGENCODER_HPP
3 |
4 |
5 | #include
6 |
7 | #include
8 | #include "Frame.hpp"
9 |
10 | class JpegEncoder {
11 | public:
12 | JpegEncoder(Frame *frame);
13 | ~JpegEncoder();
14 |
15 | void encode(Frame *frame);
16 | unsigned char* getEncodedData();
17 | size_t getEncodedSize();
18 | unsigned long getBufferSize();
19 |
20 | private:
21 | tjhandle mCompressor;
22 | int mQuality;
23 | TJSAMP mSubsampling;
24 | TJPF mFormat;
25 |
26 | unsigned char* mEncodedData;
27 | size_t mEncodedSize;
28 | unsigned long mBufferSize;
29 |
30 | };
31 |
32 |
33 | #endif //IOS_MINICAP_JPEGENCODER_HPP
34 |
--------------------------------------------------------------------------------
/src/SimpleServer.cpp:
--------------------------------------------------------------------------------
1 | #include "SimpleServer.hpp"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 |
11 | SimpleServer::SimpleServer(): mFd(0) {
12 |
13 | }
14 |
15 | SimpleServer::~SimpleServer() {
16 | if (mFd > 0) {
17 | ::close(mFd);
18 | }
19 | }
20 |
21 | int SimpleServer::start(int port) {
22 | int s_fd = socket(AF_INET, SOCK_STREAM, 0);
23 |
24 | if (s_fd < 0) {
25 | return s_fd;
26 | }
27 |
28 | int reuse = 1;
29 | if (setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) {
30 | printf("setsockopt(SO_REUSEADDR) failed");
31 | }
32 |
33 | struct sockaddr_in addr;
34 | memset(&addr, 0, sizeof(addr));
35 | addr.sin_family = AF_INET;
36 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
37 | addr.sin_port = htons(port);
38 |
39 | if (::bind(s_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
40 | return -1;
41 | }
42 |
43 | ::listen(s_fd, 1);
44 |
45 | mFd = s_fd;
46 | return mFd;
47 | }
48 |
49 | int SimpleServer::accept() {
50 | struct sockaddr_in addr;
51 | socklen_t addr_len = sizeof(addr);
52 | return ::accept(mFd, (struct sockaddr *) &addr, &addr_len);
53 | }
54 |
--------------------------------------------------------------------------------
/src/SimpleServer.hpp:
--------------------------------------------------------------------------------
1 | #ifndef MINICAP_SIMPLE_SERVER_HPP
2 | #define MINICAP_SIMPLE_SERVER_HPP
3 |
4 | class SimpleServer {
5 | public:
6 | SimpleServer();
7 | ~SimpleServer();
8 |
9 | int start(int port);
10 | int accept();
11 |
12 | private:
13 | int mFd;
14 | };
15 |
16 | #endif
17 |
--------------------------------------------------------------------------------
/src/StreamClient.h:
--------------------------------------------------------------------------------
1 | #ifndef IOS_MINICAP_STREAMCLIENT_HPP
2 | #define IOS_MINICAP_STREAMCLIENT_HPP
3 |
4 | typedef struct opaqueCMSampleBuffer *CMSampleBufferRef;
5 |
6 | #include
7 | #include
8 |
9 | #include "FrameListener.hpp"
10 | #include "Frame.hpp"
11 |
12 | struct StreamClientImpl;
13 |
14 | class StreamClient {
15 | public:
16 | StreamClient();
17 | ~StreamClient();
18 | void start();
19 | void stop();
20 | void captureOutput(CMSampleBufferRef buffer);
21 | bool setupDevice(const char *udid);
22 | void setResolution(uint32_t width, uint32_t height);
23 | void setFrameListener(FrameListener *listener);
24 |
25 | void lockFrame(Frame *frame);
26 | void releaseFrame(Frame *frame);
27 |
28 | private:
29 | StreamClientImpl *impl;
30 | FrameListener *mFrameListener;
31 | std::mutex mMutex;
32 | CMSampleBufferRef mBuffer;
33 | CMSampleBufferRef mLockedBuffer;
34 | };
35 |
36 |
37 | #endif //IOS_MINICAP_STREAMCLIENT_HPP
38 |
--------------------------------------------------------------------------------
/src/StreamClient.mm:
--------------------------------------------------------------------------------
1 | #import "StreamClient.h"
2 | #import
3 | #import
4 | #import
5 | #import
6 |
7 | #include
8 | #include
9 |
10 | #if TARGET_RT_BIG_ENDIAN
11 | # define FourCC2Str(fourcc) (const char[]){*((char*)&fourcc), *(((char*)&fourcc)+1), *(((char*)&fourcc)+2), *(((char*)&fourcc)+3),0}
12 | #else
13 | # define FourCC2Str(fourcc) (const char[]){*(((char*)&fourcc)+3), *(((char*)&fourcc)+2), *(((char*)&fourcc)+1), *(((char*)&fourcc)+0),0}
14 | #endif
15 |
16 |
17 | @interface VideoSource : NSObject
18 |
19 | @property (assign) AVCaptureSession *mSession;
20 | @property (assign) AVCaptureDevice *mDevice;
21 | @property (assign) AVCaptureDeviceInput *mDeviceInput;
22 | @property (assign) AVCaptureVideoDataOutput *mDeviceOutput;
23 |
24 | @property (assign) StreamClient *mClient;
25 |
26 | - (id) init:(StreamClient *)client;
27 |
28 | @end
29 |
30 | @implementation VideoSource
31 |
32 | - (id) init:(StreamClient *)client ; {
33 | [super init];
34 |
35 | self.mClient = client;
36 | self.mSession = [[AVCaptureSession alloc] init];
37 |
38 | return self;
39 | }
40 |
41 | - (void)dealloc ; {
42 | [self.mSession release];
43 | [self.mDevice release];
44 | [self.mDeviceOutput release];
45 | [self.mDeviceInput release];
46 | [super dealloc];
47 | }
48 |
49 | - (bool) setupDevice:(NSString *)udid ; {
50 | // Waiting for iOS devices to appear after enabling DAL plugins.
51 | // This is a really ugly place and should be refactored
52 | for (int i = 0 ; i < 10 and [AVCaptureDevice deviceWithUniqueID: udid] == nil ; i++) {
53 | [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
54 | }
55 |
56 | NSLog(@"Available devices:");
57 | for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType: AVMediaTypeMuxed]) {
58 | NSLog(@"%@", device.uniqueID);
59 | }
60 | for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo]) {
61 | NSLog(@"%@", device.uniqueID);
62 | }
63 |
64 | self.mDevice = [AVCaptureDevice deviceWithUniqueID: udid];
65 |
66 | if (self.mDevice == nil) {
67 | NSLog(@"device with udid '%@' not found", udid);
68 | return false;
69 | }
70 |
71 | [self.mSession beginConfiguration];
72 |
73 | // Add session input
74 | NSError *error;
75 | self.mDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:self.mDevice error:&error];
76 | if (self.mDeviceInput == nil) {
77 | NSLog(@"%@", error);
78 | return false;
79 | } else {
80 | [self.mSession addInput:self.mDeviceInput];
81 | }
82 |
83 | // Add session output
84 | self.mDeviceOutput = [[AVCaptureVideoDataOutput alloc] init];
85 | self.mDeviceOutput.alwaysDiscardsLateVideoFrames = YES;
86 | self.mDeviceOutput.videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
87 | AVVideoScalingModeResizeAspect, (id)AVVideoScalingModeKey,
88 | // [NSNumber numberWithUnsignedInt:400], (id)kCVPixelBufferWidthKey,
89 | // [NSNumber numberWithUnsignedInt:600], (id)kCVPixelBufferHeightKey,
90 | [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA], (id)kCVPixelBufferPixelFormatTypeKey,
91 | nil];
92 |
93 | dispatch_queue_t videoQueue = dispatch_queue_create("videoQueue", DISPATCH_QUEUE_SERIAL);
94 |
95 | [self.mDeviceOutput setSampleBufferDelegate:self queue:videoQueue];
96 |
97 | [self.mSession addOutput:self.mDeviceOutput];
98 | [self.mSession commitConfiguration];
99 | return true;
100 | }
101 |
102 | - (void) captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
103 | self.mClient->captureOutput(sampleBuffer);
104 | }
105 |
106 | @end
107 |
108 |
109 | struct StreamClientImpl {
110 | VideoSource* mVideoSource;
111 | };
112 |
113 | void EnableDALDevices()
114 | {
115 | std::cout << "EnableDALDevices" << std::endl;
116 | CMIOObjectPropertyAddress prop = {
117 | kCMIOHardwarePropertyAllowScreenCaptureDevices,
118 | kCMIOObjectPropertyScopeGlobal,
119 | kCMIOObjectPropertyElementMaster
120 | };
121 | UInt32 allow = 1;
122 | CMIOObjectSetPropertyData(kCMIOObjectSystemObject,
123 | &prop, 0, NULL,
124 | sizeof(allow), &allow );
125 | }
126 |
127 |
128 | StreamClient::StreamClient() {
129 | EnableDALDevices();
130 |
131 | impl = new StreamClientImpl();
132 | impl->mVideoSource = [[VideoSource alloc] init: this];
133 |
134 | mBuffer = 0;
135 | mLockedBuffer = 0;
136 | }
137 |
138 | StreamClient::~StreamClient() {
139 | if (impl) {
140 | [impl->mVideoSource release];
141 | }
142 | delete impl;
143 | if (mBuffer) {
144 | CFRetain(mBuffer);
145 | }
146 | if (mLockedBuffer) {
147 | CFRetain(mLockedBuffer);
148 | }
149 | }
150 |
151 | bool StreamClient::setupDevice(const char *udid) {
152 | NSString *_udid = [NSString stringWithUTF8String:udid];
153 | return [impl->mVideoSource setupDevice:_udid];
154 | }
155 |
156 | void StreamClient::start() {
157 | [impl->mVideoSource.mSession startRunning];
158 | }
159 |
160 | void StreamClient::stop() {
161 | [impl->mVideoSource.mSession stopRunning];
162 | }
163 |
164 | void StreamClient::captureOutput(CMSampleBufferRef buffer) {
165 | FrameListener *listener = 0;
166 |
167 | CFRetain(buffer);
168 |
169 | { // scope for the lock
170 | std::lock_guard lock(mMutex);
171 | if (!mBuffer) {
172 | listener = mFrameListener;
173 | } else {
174 | CFRelease(mBuffer);
175 | }
176 | mBuffer = buffer;
177 | }
178 |
179 | if (listener) {
180 | listener->onFrameAvailable();
181 | }
182 | }
183 |
184 | void StreamClient::setFrameListener(FrameListener *listener) {
185 | mFrameListener = listener;
186 | }
187 |
188 | void StreamClient::lockFrame(Frame *frame) {
189 | std::lock_guard lock(mMutex);
190 |
191 | if (!mBuffer) {
192 | // TODO: handle don't have buffer to lock
193 | std::cout << "Trying to lockFrame without buffer" << std::endl;
194 | return;
195 | }
196 |
197 | if (mLockedBuffer) {
198 | // TODO: handle already have locked buffer
199 | std::cout << "Trying to lockFrame, but already have a locked buffer" << std::endl;
200 | return;
201 | }
202 |
203 | mLockedBuffer = mBuffer;
204 | mBuffer = 0;
205 |
206 | CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(mLockedBuffer);
207 |
208 | CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
209 | frame->width = CVPixelBufferGetWidth(imageBuffer);
210 | frame->height = CVPixelBufferGetHeight(imageBuffer);
211 | frame->data = CVPixelBufferGetBaseAddress(imageBuffer);
212 | frame->size = CVPixelBufferGetDataSize(imageBuffer);
213 | frame->bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
214 | // OSType format = CVPixelBufferGetPixelFormatType(imageBuffer);
215 | // NSLog(@"%s", FourCC2Str(format));
216 | // frame->format = convertFormat(format);
217 | }
218 |
219 | void StreamClient::releaseFrame(Frame *frame) {
220 | std::lock_guard lock(mMutex);
221 |
222 | if (!mLockedBuffer) {
223 | // TODO: handle releasing frame without locked buffer
224 | std::cout << "Trying to releaseFrame without locked buffer" << std::endl;
225 | return;
226 | }
227 |
228 | CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(mLockedBuffer);
229 | CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
230 | CFRelease(mLockedBuffer);
231 | mLockedBuffer = 0;
232 | }
233 |
234 | void StreamClient::setResolution(uint32_t width, uint32_t height) {
235 | [impl->mVideoSource.mSession beginConfiguration];
236 | NSMutableDictionary *settings = [impl->mVideoSource.mDeviceOutput.videoSettings mutableCopy];
237 | [settings setObject:[NSNumber numberWithUnsignedInt:width] forKey:(id)kCVPixelBufferWidthKey];
238 | [settings setObject:[NSNumber numberWithUnsignedInt:height] forKey:(id)kCVPixelBufferHeightKey];
239 | impl->mVideoSource.mDeviceOutput.videoSettings = settings;
240 | [impl->mVideoSource.mSession commitConfiguration];
241 | }
242 |
243 |
--------------------------------------------------------------------------------
/src/minicap.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | #include "SimpleServer.hpp"
11 | #include "FrameListener.hpp"
12 | #include "Banner.hpp"
13 | #include "JpegEncoder.hpp"
14 | #include "StreamClient.h"
15 |
16 | // MSG_NOSIGNAL does not exists on OS X
17 | #if defined(__APPLE__) || defined(__MACH__)
18 | # ifndef MSG_NOSIGNAL
19 | # define MSG_NOSIGNAL SO_NOSIGPIPE
20 | # endif
21 | #endif
22 |
23 | static FrameListener gWaiter;
24 |
25 |
26 | void print_usage(char **argv) {
27 | char *name = NULL;
28 | name = strrchr(argv[0], '/');
29 |
30 | printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
31 | printf("Stream video from a device.\n");
32 | printf(" -u, --udid UDID\t\ttarget specific device by its 40-digit device UDID\n");
33 | printf(" -p, --port PORT\t\tport to run server on\n");
34 | printf(" -r, --resolution RESOLUTION\tdesired resolution x\n");
35 | printf(" -h, --help\t\t\tprints usage information\n");
36 | printf("\n");
37 | }
38 |
39 |
40 | bool parse_args(int argc, char **argv, const char **udid, int *port, const char **resolution) {
41 | if ( argc < 7 ) {
42 | // Currently the easiest way to make all arguments required
43 | print_usage(argv);
44 | return false;
45 | }
46 | for (int i = 1; i < argc; i++) {
47 | if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--udid")) {
48 | i++;
49 | if (!argv[i]) {
50 | print_usage(argv);
51 | return false;
52 | }
53 | *udid = argv[i];
54 | continue;
55 | }
56 | else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--port")) {
57 | i++;
58 | if (!argv[i]) {
59 | print_usage(argv);
60 | return false;
61 | }
62 | *port = atoi(argv[i]);
63 | continue;
64 | }
65 | else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--resolution")) {
66 | i++;
67 | if (!argv[i]) {
68 | print_usage(argv);
69 | return false;
70 | }
71 | *resolution = argv[i];
72 | continue;
73 | }
74 | else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
75 | print_usage(argv);
76 | return false;
77 |
78 | }
79 | else {
80 | print_usage(argv);
81 | return false;
82 | }
83 | }
84 | return true;
85 | }
86 |
87 |
88 | static void signal_handler(int signum) {
89 | switch (signum) {
90 | case SIGINT:
91 | printf("Received SIGINT, stopping\n");
92 | gWaiter.stop();
93 | break;
94 | case SIGTERM:
95 | printf("Received SIGTERM, stopping\n");
96 | gWaiter.stop();
97 | break;
98 | default:
99 | abort();
100 | }
101 | }
102 |
103 |
104 | static void setup_signal_handler() {
105 | struct sigaction sa;
106 | memset(&sa, 0, sizeof(sa));
107 | sa.sa_handler = signal_handler;
108 | sigemptyset(&sa.sa_mask);
109 | sigaction(SIGTERM, &sa, NULL);
110 | sigaction(SIGINT, &sa, NULL);
111 | // we want to just ignore the SIGPIPE and get a EPIPE when socket is closed
112 | signal(SIGPIPE, SIG_IGN);
113 | }
114 |
115 |
116 | static ssize_t pumps(int fd, unsigned char* data, size_t length) {
117 | do {
118 | // SIGPIPE is set to ignored so we will just get EPIPE instead
119 | ssize_t wrote = send(fd, data, length, 0);
120 |
121 | if (wrote < 0) {
122 | return wrote;
123 | }
124 |
125 | data += wrote;
126 | length -= wrote;
127 | }
128 | while (length > 0);
129 |
130 | return 0;
131 | }
132 |
133 |
134 | void parseResolution(const char* resolution, uint32_t* width, uint32_t* height) {
135 | std::string _resolution(resolution);
136 | size_t sep = _resolution.find("x");
137 | *width = std::stoul(_resolution.substr(0, sep).c_str());
138 | *height = std::stoul(_resolution.substr(sep+1, _resolution.length()).c_str());
139 | }
140 |
141 |
142 | int main(int argc, char **argv) {
143 | const char *udid = NULL;
144 | const char *resolution = NULL;
145 | int port = 0;
146 |
147 | setup_signal_handler();
148 | if ( !parse_args(argc, argv, &udid, &port, &resolution) ) {
149 | return EXIT_FAILURE;
150 | }
151 |
152 | uint32_t width = 0, height = 0;
153 | parseResolution(resolution, &width, &height);
154 |
155 | StreamClient client;
156 | if (!client.setupDevice(udid)) {
157 | return EXIT_FAILURE;
158 | }
159 | client.setResolution(width, height);
160 | client.setFrameListener(&gWaiter);
161 | client.start();
162 |
163 | if (!gWaiter.waitForFrame()) {
164 | return EXIT_SUCCESS;
165 | }
166 | client.stop();
167 |
168 | Frame frame;
169 |
170 | client.lockFrame(&frame);
171 | std::cout << "resolution: " << frame.width << "x" << frame.height << std::endl;
172 | JpegEncoder encoder(&frame);
173 |
174 | DeviceInfo realInfo, desiredInfo;
175 | realInfo.orientation = 0;
176 | realInfo.height = frame.height;
177 | realInfo.width = frame.width;
178 | desiredInfo.orientation = 0;
179 | desiredInfo.height = frame.height;
180 | desiredInfo.width = frame.width;
181 |
182 | Banner banner(realInfo, desiredInfo);
183 | client.releaseFrame(&frame);
184 |
185 |
186 | SimpleServer server;
187 | server.start(port);
188 | int socket;
189 |
190 | unsigned char frameSize[4];
191 | while (gWaiter.isRunning() and (socket = server.accept()) > 0) {
192 | std::cout << "New client connection" << std::endl;
193 |
194 | send(socket, banner.getData(), banner.getSize(), 0);
195 |
196 | client.start();
197 | while (gWaiter.isRunning() and gWaiter.waitForFrame() > 0) {
198 | client.lockFrame(&frame);
199 | encoder.encode(&frame);
200 | client.releaseFrame(&frame);
201 | putUInt32LE(frameSize, encoder.getEncodedSize());
202 | if ( pumps(socket, frameSize, 4) < 0 ) {
203 | break;
204 | }
205 | if ( pumps(socket, encoder.getEncodedData(), encoder.getEncodedSize()) < 0 ) {
206 | break;
207 | }
208 | }
209 | client.stop();
210 | }
211 |
212 | return EXIT_SUCCESS;
213 | }
214 |
--------------------------------------------------------------------------------