-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmd_camera.go
More file actions
336 lines (293 loc) · 9.97 KB
/
cmd_camera.go
File metadata and controls
336 lines (293 loc) · 9.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
//go:build linux
package main
import (
"context"
"fmt"
"io"
"os"
"runtime/debug"
"sync"
"time"
gfxCommon "github.com/AndroidGoLab/binder/android/hardware/graphics/common"
fwkDevice "github.com/AndroidGoLab/binder/android/frameworks/cameraservice/device"
fwkService "github.com/AndroidGoLab/binder/android/frameworks/cameraservice/service"
"github.com/AndroidGoLab/binder/binder"
"github.com/AndroidGoLab/binder/camera"
"github.com/AndroidGoLab/binder/gralloc"
"github.com/AndroidGoLab/binder/igbp"
"github.com/spf13/cobra"
)
func newCameraCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "camera",
Short: "Camera capture commands",
}
cmd.AddCommand(newCameraRecordCmd())
return cmd
}
func newCameraRecordCmd() *cobra.Command {
var (
width int
height int
cameraID string
duration time.Duration
)
cmd := &cobra.Command{
Use: "record",
Short: "Record raw YUV frames from a camera to stdout",
Long: `Record captures camera frames and writes raw YUV (NV12/YCbCr_420_888)
data to stdout. Status messages go to stderr.
Example:
bindercli camera record --width 1920 --height 1920 --duration 5s > output.yuv
bindercli camera record | ffmpeg -f rawvideo -pix_fmt nv12 -s 1920x1920 -i - output.mp4`,
RunE: func(cmd *cobra.Command, _ []string) error {
return runCameraRecord(
cmd,
int32(width),
int32(height),
cameraID,
duration,
os.Stdout,
)
},
}
cmd.Flags().IntVar(&width, "width", 1920, "capture width in pixels")
cmd.Flags().IntVar(&height, "height", 1920, "capture height in pixels")
cmd.Flags().StringVar(&cameraID, "camera", "0", "camera device ID")
cmd.Flags().DurationVar(&duration, "duration", 10*time.Second, "recording duration")
return cmd
}
// cameraCallback implements fwkDevice.ICameraDeviceCallback with
// stderr logging suitable for the CLI recording flow.
type cameraCallback struct {
mu sync.Mutex
framesReceived int
}
func (c *cameraCallback) OnCaptureStarted(
_ context.Context,
extras fwkDevice.CaptureResultExtras,
ts int64,
) error {
c.mu.Lock()
defer c.mu.Unlock()
c.framesReceived++
fmt.Fprintf(os.Stderr, " >> OnCaptureStarted: requestId=%d timestamp=%d (total=%d)\n",
extras.RequestId, ts, c.framesReceived)
return nil
}
func (c *cameraCallback) OnDeviceError(
_ context.Context,
code fwkDevice.ErrorCode,
extras fwkDevice.CaptureResultExtras,
) error {
fmt.Fprintf(os.Stderr, " >> OnDeviceError: code=%d requestId=%d\n", code, extras.RequestId)
return nil
}
func (c *cameraCallback) OnDeviceIdle(_ context.Context) error {
fmt.Fprintln(os.Stderr, " >> OnDeviceIdle")
return nil
}
func (c *cameraCallback) OnPrepared(_ context.Context, streamId int32) error {
fmt.Fprintf(os.Stderr, " >> OnPrepared: stream %d\n", streamId)
return nil
}
func (c *cameraCallback) OnRepeatingRequestError(
_ context.Context,
lastFrame int64,
reqId int32,
) error {
fmt.Fprintf(os.Stderr, " >> OnRepeatingRequestError: frame=%d req=%d\n", lastFrame, reqId)
return nil
}
func (c *cameraCallback) OnResultReceived(
_ context.Context,
meta fwkDevice.CaptureMetadataInfo,
extras fwkDevice.CaptureResultExtras,
_ []fwkDevice.PhysicalCaptureResultInfo,
) error {
fmt.Fprintf(os.Stderr, " >> OnResultReceived: requestId=%d frameNumber=%d tag=%d\n",
extras.RequestId, extras.FrameNumber, meta.Tag)
return nil
}
func (c *cameraCallback) OnClientSharedAccessPriorityChanged(
_ context.Context,
primary bool,
) error {
fmt.Fprintf(os.Stderr, " >> OnClientSharedAccessPriorityChanged: %v\n", primary)
return nil
}
// runCameraRecord implements the recording flow: allocates gralloc
// buffers, connects to camera, configures a stream with the IGBP stub,
// and writes raw YUV frames to output until the duration expires.
func runCameraRecord(
cmd *cobra.Command,
width int32,
height int32,
cameraID string,
duration time.Duration,
output io.Writer,
) (_err error) {
ctx := cmd.Context()
// Disable GC early to prevent startup allocations (DEX parsing,
// binder setup) from triggering expensive heap scans. The entire
// camera record flow is short-lived and allocation patterns are
// bounded, so GC is not needed.
debug.SetGCPercent(-1)
defer debug.SetGCPercent(100)
// Use a larger map size for camera buffers.
conn, err := OpenConn(ctx, cmd)
if err != nil {
return fmt.Errorf("opening binder connection: %w", err)
}
defer conn.Close(ctx)
transport := conn.Transport
// Step 0: Pre-allocate gralloc buffers (4 slots).
fmt.Fprintln(os.Stderr, "=== Step 0: Allocate gralloc buffers ===")
var grallocBufs [4]*gralloc.Buffer
for i := range grallocBufs {
buf, allocErr := gralloc.Allocate(
ctx,
conn.SM,
width,
height,
gfxCommon.PixelFormatYcbcr420888,
gfxCommon.BufferUsageCpuReadOften|gfxCommon.BufferUsageCameraOutput,
)
if allocErr != nil {
return fmt.Errorf("allocating gralloc buffer %d: %w", i, allocErr)
}
// Pre-mmap the dmabuf so we can read frames without per-frame
// mmap/munmap syscalls.
if mmapErr := buf.Mmap(); mmapErr != nil {
return fmt.Errorf("mmap gralloc buffer %d: %w", i, mmapErr)
}
grallocBufs[i] = buf
}
defer func() {
for _, buf := range grallocBufs {
if buf != nil {
buf.Munmap()
}
}
}()
fmt.Fprintf(os.Stderr, "Allocated and mmap'd %d gralloc buffers\n", len(grallocBufs))
// Connect to camera service.
svc, err := conn.SM.GetService(ctx, "android.frameworks.cameraservice.service.ICameraService/default")
if err != nil {
return fmt.Errorf("getting camera service: %w", err)
}
fmt.Fprintln(os.Stderr, "Got frameworks camera service")
proxy := fwkService.NewCameraServiceProxy(svc)
cb := &cameraCallback{}
stub := fwkDevice.NewCameraDeviceCallbackStub(cb)
stubBinder := stub.AsBinder().(*binder.StubBinder)
stubBinder.RegisterWithTransport(ctx, transport)
time.Sleep(100 * time.Millisecond)
fmt.Fprintln(os.Stderr, "Callback stub registered")
// ConnectDevice.
fmt.Fprintln(os.Stderr, "\nCalling ConnectDevice...")
deviceUser, err := proxy.ConnectDevice(ctx, stub, cameraID)
if err != nil {
return fmt.Errorf("ConnectDevice: %w", err)
}
fmt.Fprintln(os.Stderr, "ConnectDevice succeeded!")
defer func() {
fmt.Fprintln(os.Stderr, "\n=== Disconnect ===")
if disconnectErr := deviceUser.Disconnect(ctx); disconnectErr != nil {
fmt.Fprintf(os.Stderr, "Disconnect: %v\n", disconnectErr)
} else {
fmt.Fprintln(os.Stderr, "Disconnect OK")
}
}()
// Step 1: BeginConfigure.
fmt.Fprintln(os.Stderr, "\n=== Step 1: BeginConfigure ===")
if err = deviceUser.BeginConfigure(ctx); err != nil {
return fmt.Errorf("BeginConfigure: %w", err)
}
fmt.Fprintln(os.Stderr, "BeginConfigure OK")
// Step 2: CreateDefaultRequest.
fmt.Fprintln(os.Stderr, "\n=== Step 2: CreateDefaultRequest (PREVIEW) ===")
metadataBytes, err := camera.CreateDefaultRequest(ctx, deviceUser, fwkDevice.TemplateIdPREVIEW)
if err != nil {
return fmt.Errorf("CreateDefaultRequest: %w", err)
}
fmt.Fprintf(os.Stderr, "CreateDefaultRequest OK: metadata len=%d\n", len(metadataBytes))
// Step 3: Create IGBP and CreateStream.
fmt.Fprintln(os.Stderr, "\n=== Step 3: CreateStream with IGBP Surface ===")
igbpStub := igbp.NewProducerStub(uint32(width), uint32(height), grallocBufs)
igbpStubBinder := binder.NewStubBinder(igbpStub)
igbpStubBinder.RegisterWithTransport(ctx, transport)
streamId, err := camera.CreateStreamWithSurface(ctx, deviceUser, transport, igbpStubBinder, width, height)
if err != nil {
return fmt.Errorf("CreateStream: %w", err)
}
fmt.Fprintf(os.Stderr, "CreateStream OK: streamId=%d\n", streamId)
// Step 4: EndConfigure.
fmt.Fprintln(os.Stderr, "\n=== Step 4: EndConfigure ===")
if err = deviceUser.EndConfigure(ctx, fwkDevice.StreamConfigurationModeNormalMode, fwkDevice.CameraMetadata{Metadata: []byte{}}, 0); err != nil {
return fmt.Errorf("EndConfigure: %w", err)
}
fmt.Fprintln(os.Stderr, "EndConfigure OK")
// Step 5: SubmitRequestList.
fmt.Fprintln(os.Stderr, "\n=== Step 5: SubmitRequestList (repeating) ===")
captureReq := fwkDevice.CaptureRequest{
PhysicalCameraSettings: []fwkDevice.PhysicalCameraSettings{
{
Id: cameraID,
Settings: fwkDevice.CaptureMetadataInfo{
Tag: fwkDevice.CaptureMetadataInfoTagMetadata,
Metadata: fwkDevice.CameraMetadata{Metadata: metadataBytes},
},
},
},
StreamAndWindowIds: []fwkDevice.StreamAndWindowId{
{StreamId: streamId, WindowId: 0},
},
}
submitInfo, err := camera.SubmitRequest(ctx, deviceUser, captureReq, true)
if err != nil {
fmt.Fprintf(os.Stderr, "SubmitRequestList (repeating) FAILED: %v\n", err)
submitInfo, err = camera.SubmitRequest(ctx, deviceUser, captureReq, false)
if err != nil {
return fmt.Errorf("SubmitRequestList: %w", err)
}
}
fmt.Fprintf(os.Stderr, "SubmitRequestList OK: requestId=%d lastFrame=%d\n",
submitInfo.RequestId, submitInfo.LastFrameNumber)
// Wait for frames and write them to output.
fmt.Fprintf(os.Stderr, "\nRecording for %s...\n", duration)
frameCount := 0
deadline := time.After(duration)
// Use a reusable ticker instead of time.After per iteration,
// which would allocate a new timer+channel each loop.
pollTicker := time.NewTicker(200 * time.Millisecond)
defer pollTicker.Stop()
for {
select {
case <-deadline:
fmt.Fprintf(os.Stderr, "Duration reached. Total frames written: %d\n", frameCount)
return nil
case slot := <-igbpStub.QueuedFrames():
buf := igbpStub.SlotBuffer(slot)
if buf == nil {
fmt.Fprintf(os.Stderr, " Frame from slot %d: no buffer\n", slot)
continue
}
// Write directly from the persistent mmap to output,
// avoiding an intermediate copy through a frame buffer.
frameData := buf.MmapData
if frameData == nil {
fmt.Fprintf(os.Stderr, " Frame from slot %d: buffer not mmap'd\n", slot)
continue
}
if _, writeErr := output.Write(frameData); writeErr != nil {
return fmt.Errorf("writing frame data: %w", writeErr)
}
frameCount++
fmt.Fprintf(os.Stderr, " Frame %d written (%d bytes)\n", frameCount, len(frameData))
case <-pollTicker.C:
// Periodic check: if no frames arrive at all, keep waiting
// until the deadline.
}
}
}