@@ -6,8 +6,10 @@ import (
6
6
"os"
7
7
"os/exec"
8
8
"os/signal"
9
+ "regexp"
9
10
"runtime"
10
11
"strconv"
12
+ "strings"
11
13
"syscall"
12
14
)
13
15
@@ -61,41 +63,86 @@ func (camera *Camera) SetFrameBuffer(buffer []byte) error {
61
63
return nil
62
64
}
63
65
64
- // Returns the webcam device name.
65
- // On windows, ffmpeg output from the -list_devices command is parsed to find the device name.
66
- func getDevicesWindows () ([]string , error ) {
67
- // Run command to get list of devices.
68
- cmd := exec .Command (
69
- "ffmpeg" ,
70
- "-hide_banner" ,
71
- "-list_devices" , "true" ,
72
- "-f" , "dshow" ,
73
- "-i" , "dummy" ,
74
- )
75
- pipe , err := cmd .StderrPipe ()
76
- if err != nil {
66
+ // Creates a new camera struct that can read from the device with the given stream index.
67
+ func NewCamera (stream int ) (* Camera , error ) {
68
+ // Check if ffmpeg is installed on the users machine.
69
+ if err := checkExists ("ffmpeg" ); err != nil {
77
70
return nil , err
78
71
}
79
- if err := cmd .Start (); err != nil {
72
+
73
+ var device string
74
+ switch runtime .GOOS {
75
+ case "linux" :
76
+ device = "/dev/video" + strconv .Itoa (stream )
77
+ case "darwin" :
78
+ device = strconv .Itoa (stream )
79
+ case "windows" :
80
+ // If OS is windows, we need to parse the listed devices to find which corresponds to the
81
+ // given "stream" index.
82
+ devices , err := getDevicesWindows ()
83
+ if err != nil {
84
+ return nil , err
85
+ }
86
+ if stream >= len (devices ) {
87
+ return nil , fmt .Errorf ("could not find device with index: %d" , stream )
88
+ }
89
+ device = "video=" + devices [stream ]
90
+ default :
91
+ return nil , fmt .Errorf ("unsupported OS: %s" , runtime .GOOS )
92
+ }
93
+
94
+ camera := & Camera {name : device , depth : 3 }
95
+ if err := camera .getCameraData (device ); err != nil {
80
96
return nil , err
81
97
}
82
- // Read list devices from Stdout.
83
- buffer := make ([]byte , 2 << 10 )
84
- total := 0
85
- for {
86
- n , err := pipe .Read (buffer [total :])
87
- total += n
88
- if err == io .EOF {
89
- break
98
+ return camera , nil
99
+ }
100
+
101
+ // Parses the webcam metadata (width, height, fps, codec) from ffmpeg output.
102
+ func (camera * Camera ) parseWebcamData (buffer []byte ) {
103
+ bufferstr := string (buffer )
104
+ index := strings .Index (bufferstr , "Stream #" )
105
+ if index == - 1 {
106
+ index ++
107
+ }
108
+ bufferstr = bufferstr [index :]
109
+ // Dimensions. widthxheight.
110
+ regex := regexp .MustCompile (`\d{2,}x\d{2,}` )
111
+ match := regex .FindString (bufferstr )
112
+ if len (match ) > 0 {
113
+ split := strings .Split (match , "x" )
114
+ camera .width = int (parse (split [0 ]))
115
+ camera .height = int (parse (split [1 ]))
116
+ }
117
+ // FPS.
118
+ regex = regexp .MustCompile (`\d+(.\d+)? fps` )
119
+ match = regex .FindString (bufferstr )
120
+ if len (match ) > 0 {
121
+ index = strings .Index (match , " fps" )
122
+ if index != - 1 {
123
+ match = match [:index ]
90
124
}
125
+ camera .fps = parse (match )
126
+ }
127
+ // Codec.
128
+ regex = regexp .MustCompile ("Video: .+," )
129
+ match = regex .FindString (bufferstr )
130
+ if len (match ) > 0 {
131
+ match = match [len ("Video: " ):]
132
+ index = strings .Index (match , "(" )
133
+ if index != - 1 {
134
+ match = match [:index ]
135
+ }
136
+ index = strings .Index (match , "," )
137
+ if index != - 1 {
138
+ match = match [:index ]
139
+ }
140
+ camera .codec = strings .TrimSpace (match )
91
141
}
92
- cmd .Wait ()
93
- devices := parseDevices (buffer )
94
- return devices , nil
95
142
}
96
143
97
144
// Get camera meta data such as width, height, fps and codec.
98
- func getCameraData ( device string , camera * Camera ) error {
145
+ func ( camera * Camera ) getCameraData ( device string ) error {
99
146
// Run command to get camera data.
100
147
// Webcam will turn on and then off in quick succession.
101
148
webcamDeviceName , err := webcam ()
@@ -131,48 +178,13 @@ func getCameraData(device string, camera *Camera) error {
131
178
// Wait for the command to finish.
132
179
cmd .Wait ()
133
180
134
- parseWebcamData (buffer [:total ], camera )
181
+ camera . parseWebcamData (buffer [:total ])
135
182
return nil
136
183
}
137
184
138
- // Creates a new camera struct that can read from the device with the given stream index.
139
- func NewCamera (stream int ) (* Camera , error ) {
140
- // Check if ffmpeg is installed on the users machine.
141
- if err := checkExists ("ffmpeg" ); err != nil {
142
- return nil , err
143
- }
144
-
145
- var device string
146
- switch runtime .GOOS {
147
- case "linux" :
148
- device = "/dev/video" + strconv .Itoa (stream )
149
- case "darwin" :
150
- device = strconv .Itoa (stream )
151
- case "windows" :
152
- // If OS is windows, we need to parse the listed devices to find which corresponds to the
153
- // given "stream" index.
154
- devices , err := getDevicesWindows ()
155
- if err != nil {
156
- return nil , err
157
- }
158
- if stream >= len (devices ) {
159
- return nil , fmt .Errorf ("could not find device with index: %d" , stream )
160
- }
161
- device = "video=" + devices [stream ]
162
- default :
163
- return nil , fmt .Errorf ("unsupported OS: %s" , runtime .GOOS )
164
- }
165
-
166
- camera := Camera {name : device , depth : 3 }
167
- if err := getCameraData (device , & camera ); err != nil {
168
- return nil , err
169
- }
170
- return & camera , nil
171
- }
172
-
173
185
// Once the user calls Read() for the first time on a Camera struct,
174
186
// the ffmpeg command which is used to read the camera device is started.
175
- func initCamera (camera * Camera ) error {
187
+ func (camera * Camera ) init ( ) error {
176
188
// If user exits with Ctrl+C, stop ffmpeg process.
177
189
camera .cleanup ()
178
190
@@ -190,7 +202,8 @@ func initCamera(camera *Camera) error {
190
202
"-i" , camera .name ,
191
203
"-f" , "image2pipe" ,
192
204
"-pix_fmt" , "rgb24" ,
193
- "-vcodec" , "rawvideo" , "-" ,
205
+ "-vcodec" , "rawvideo" ,
206
+ "-" ,
194
207
)
195
208
196
209
camera .cmd = cmd
@@ -215,7 +228,7 @@ func initCamera(camera *Camera) error {
215
228
func (camera * Camera ) Read () bool {
216
229
// If cmd is nil, video reading has not been initialized.
217
230
if camera .cmd == nil {
218
- if err := initCamera ( camera ); err != nil {
231
+ if err := camera . init ( ); err != nil {
219
232
return false
220
233
}
221
234
}
0 commit comments