Skip to content

Commit ea35655

Browse files
committed
docs: add camera capture guide
1 parent ee53728 commit ea35655

4 files changed

Lines changed: 386 additions & 1 deletion

File tree

docs/_sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [Audio Device Selection](guide/audio_devices.md)
1010
- [Audio Processing](guide/audio_processing.md)
1111
- [Bitrate and Framerate Constraints](guide/constraints.md)
12+
- [Camera Capture](guide/camera_capture.md)
1213
- [Desktop Capture](guide/desktop_capture.md)
1314
- [Data Channels](guide/data_channels.md)
1415
- [DTMF Sender](guide/dtmf_sender.md)

docs/guide/camera_capture.md

Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
# Camera Video Track
2+
3+
This guide focuses on setting up a peer connection with camera video capture capabilities, which allows you to capture and stream video from connected cameras in your WebRTC connection.
4+
5+
## Camera Video Source Selection
6+
7+
To enable camera video capture, you need to:
8+
9+
1. List available video devices (cameras)
10+
2. Create and configure a VideoDeviceSource
11+
3. Create a video track with the camera source
12+
4. Add the track to your peer connection
13+
14+
### Getting Available Cameras and Capabilities
15+
16+
Before configuring your camera source, you may want to check the supported resolutions and frame rates:
17+
18+
```java
19+
import dev.onvoid.webrtc.media.MediaDevices;
20+
import dev.onvoid.webrtc.media.video.VideoDevice;
21+
import dev.onvoid.webrtc.media.video.VideoCaptureCapability;
22+
import java.util.List;
23+
24+
// Get all cameras
25+
List<VideoDevice> cameras = MediaDevices.getVideoCaptureDevices();
26+
if (cameras.isEmpty()) {
27+
System.out.println("No cameras found");
28+
return;
29+
}
30+
31+
// Get the first camera
32+
VideoDevice camera = cameras.get(0);
33+
System.out.println("Camera: " + camera.getName());
34+
35+
// Get camera capabilities
36+
List<VideoCaptureCapability> capabilities = MediaDevices.getVideoCaptureCapabilities(camera);
37+
38+
// Print camera capabilities
39+
for (VideoCaptureCapability capability : capabilities) {
40+
System.out.println(" Resolution: " + capability.width + "x" + capability.height);
41+
System.out.println(" Frame Rate: " + capability.frameRate + " fps");
42+
}
43+
```
44+
45+
### Creating and Configuring a VideoDeviceSource
46+
47+
The `VideoDeviceSource` class allows you to capture video from a camera:
48+
49+
```java
50+
import dev.onvoid.webrtc.media.video.VideoDeviceSource;
51+
import dev.onvoid.webrtc.media.video.VideoDevice;
52+
import dev.onvoid.webrtc.media.video.VideoCaptureCapability;
53+
54+
// Create a video device source
55+
VideoDeviceSource videoSource = new VideoDeviceSource();
56+
57+
// Select a specific camera to capture from
58+
VideoDevice camera = cameras.get(0); // Use the first available camera
59+
videoSource.setVideoCaptureDevice(camera);
60+
61+
// Configure the capture capability (optional)
62+
// Choose a capability from the list obtained earlier
63+
VideoCaptureCapability capability = capabilities.get(0); // Use the first available capability
64+
videoSource.setVideoCaptureCapability(capability);
65+
66+
// Start capturing
67+
videoSource.start();
68+
```
69+
70+
### Creating a Video Track with the Camera Source
71+
72+
Once you have configured your camera video source, you can create a video track:
73+
74+
```java
75+
import dev.onvoid.webrtc.PeerConnectionFactory;
76+
import dev.onvoid.webrtc.media.video.VideoTrack;
77+
78+
// Create a PeerConnectionFactory
79+
PeerConnectionFactory factory = new PeerConnectionFactory();
80+
81+
// Create a video track with the camera source
82+
VideoTrack videoTrack = factory.createVideoTrack("video0", videoSource);
83+
```
84+
85+
### Adding the Track to a Peer Connection
86+
87+
Add the video track to your peer connection:
88+
89+
```java
90+
import java.util.ArrayList;
91+
import java.util.List;
92+
import dev.onvoid.webrtc.RTCPeerConnection;
93+
94+
// Assuming you already have a configured RTCPeerConnection
95+
RTCPeerConnection peerConnection = factory.createPeerConnection(config, observer);
96+
97+
// Add the track to the peer connection
98+
List<String> streamIds = new ArrayList<>();
99+
streamIds.add("stream1");
100+
peerConnection.addTrack(videoTrack, streamIds);
101+
```
102+
103+
## Additional Features
104+
105+
The `VideoDeviceSource` provides additional methods for controlling the camera capture:
106+
107+
### Resource Management
108+
109+
Always properly dispose of resources when done:
110+
111+
```java
112+
// Dispose of resources when done
113+
videoSource.stop();
114+
videoSource.dispose();
115+
```
116+
117+
### Handling Device Changes
118+
119+
If cameras might change during your application's lifecycle (e.g., cameras connecting or disconnecting), you should implement a device change listener:
120+
121+
```java
122+
import dev.onvoid.webrtc.media.Device;
123+
import dev.onvoid.webrtc.media.DeviceChangeListener;
124+
import dev.onvoid.webrtc.media.MediaDevices;
125+
import dev.onvoid.webrtc.media.video.VideoDevice;
126+
127+
// Create a device change listener
128+
DeviceChangeListener listener = new DeviceChangeListener() {
129+
@Override
130+
public void deviceConnected(Device device) {
131+
if (device instanceof VideoDevice) {
132+
System.out.println("Camera connected: " + device.getName());
133+
// You might want to update your UI or offer the new camera as an option
134+
}
135+
}
136+
137+
@Override
138+
public void deviceDisconnected(Device device) {
139+
if (device instanceof VideoDevice) {
140+
System.out.println("Camera disconnected: " + device.getName());
141+
// Handle the case where the current camera was disconnected
142+
}
143+
}
144+
};
145+
146+
// Register the listener
147+
MediaDevices.addDeviceChangeListener(listener);
148+
149+
// ... later, when you're done listening for events
150+
// Unregister the listener
151+
MediaDevices.removeDeviceChangeListener(listener);
152+
```
153+
154+
## Receiving Video Frames
155+
156+
Once you have set up your camera video source and peer connection, you'll likely want to receive and process the video frames. This section explains how to receive both local and remote video frames.
157+
158+
### Creating a VideoTrackSink
159+
160+
To receive video frames, you need to implement the `VideoTrackSink` interface and add it to a video track:
161+
162+
```java
163+
import dev.onvoid.webrtc.media.video.VideoFrame;
164+
import dev.onvoid.webrtc.media.video.VideoTrackSink;
165+
166+
// Create a class that implements VideoTrackSink
167+
public class MyVideoSink implements VideoTrackSink {
168+
@Override
169+
public void onVideoFrame(VideoFrame frame) {
170+
// Process the video frame
171+
System.out.printf("Received frame: %dx%d%n",
172+
frame.buffer.getWidth(), frame.buffer.getHeight());
173+
174+
// IMPORTANT: Always release the frame when done to prevent memory leaks
175+
frame.release();
176+
}
177+
}
178+
```
179+
180+
### Receiving Local Video Frames
181+
182+
To receive frames from your local camera:
183+
184+
```java
185+
// Create your video sink
186+
MyVideoSink localVideoSink = new MyVideoSink();
187+
188+
// Add the sink to your local video track
189+
videoTrack.addSink(localVideoSink);
190+
191+
// Later, when you're done, remove the sink
192+
videoTrack.removeSink(localVideoSink);
193+
```
194+
195+
### Receiving Remote Video Frames
196+
197+
To receive frames from a remote peer, you need to add a sink to the remote video track when it's received. This is typically done in the `onTrack` method of your `PeerConnectionObserver`:
198+
199+
```java
200+
import dev.onvoid.webrtc.PeerConnectionObserver;
201+
import dev.onvoid.webrtc.RTCRtpTransceiver;
202+
import dev.onvoid.webrtc.media.MediaStreamTrack;
203+
import dev.onvoid.webrtc.media.video.VideoTrack;
204+
205+
public class MyPeerConnectionObserver implements PeerConnectionObserver {
206+
private final MyVideoSink remoteVideoSink = new MyVideoSink();
207+
208+
// Other methods omitted for brevity...
209+
210+
@Override
211+
public void onTrack(RTCRtpTransceiver transceiver) {
212+
MediaStreamTrack track = transceiver.getReceiver().getTrack();
213+
String kind = track.getKind();
214+
215+
if (kind.equals(MediaStreamTrack.VIDEO_TRACK_KIND)) {
216+
VideoTrack videoTrack = (VideoTrack) track;
217+
videoTrack.addSink(remoteVideoSink);
218+
System.out.println("Added sink to remote video track");
219+
}
220+
}
221+
222+
// Make sure to clean up when done
223+
public void dispose() {
224+
// Remove sinks from any tracks they were added to
225+
}
226+
}
227+
```
228+
229+
### Processing Video Frames
230+
231+
When processing video frames, consider these important points:
232+
233+
1. **Frame Properties**: Each `VideoFrame` contains:
234+
- A `VideoFrameBuffer` with the actual pixel data
235+
- Width and height information
236+
- Rotation value
237+
- Timestamp
238+
239+
2. **Memory Management**: Always call `frame.release()` when you're done with a frame to prevent memory leaks.
240+
241+
3. **Performance Considerations**: Frame processing happens on the WebRTC thread, so keep your processing efficient. For heavy processing, consider:
242+
- Copying the frame data and processing it on a separate thread
243+
- Using a frame queue with a dedicated processing thread
244+
- Skipping frames if processing can't keep up with the frame rate
245+
246+
#### Converting VideoFrame to BufferedImage
247+
248+
To display or process video frames in Java applications, you often need to convert the `VideoFrame` to a `BufferedImage`. Here's how to do it:
249+
250+
```java
251+
import dev.onvoid.webrtc.media.FourCC;
252+
import dev.onvoid.webrtc.media.video.VideoBufferConverter;
253+
import dev.onvoid.webrtc.media.video.VideoFrame;
254+
import dev.onvoid.webrtc.media.video.VideoFrameBuffer;
255+
import java.awt.image.BufferedImage;
256+
import java.awt.image.DataBufferByte;
257+
258+
public void onVideoFrame(VideoFrame frame) {
259+
try {
260+
// Get frame dimensions
261+
VideoFrameBuffer frameBuffer = frame.buffer;
262+
int frameWidth = frameBuffer.getWidth();
263+
int frameHeight = frameBuffer.getHeight();
264+
265+
// Create a BufferedImage with ABGR format (compatible with RGBA conversion)
266+
BufferedImage image = new BufferedImage(frameWidth, frameHeight, BufferedImage.TYPE_4BYTE_ABGR);
267+
268+
// Get the underlying byte array from the BufferedImage
269+
byte[] imageBuffer = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
270+
271+
// Convert the frame buffer from I420 format to RGBA format
272+
VideoBufferConverter.convertFromI420(frameBuffer, imageBuffer, FourCC.RGBA);
273+
274+
// Now you can use the BufferedImage for display or further processing
275+
// For example, you could display it in a Swing component:
276+
// myJLabel.setIcon(new ImageIcon(image));
277+
278+
} catch (Exception e) {
279+
e.printStackTrace();
280+
} finally {
281+
// Always release the frame when done
282+
frame.release();
283+
}
284+
}
285+
```
286+
287+
This conversion process works as follows:
288+
289+
1. Create a `BufferedImage` with the same dimensions as the video frame, using `BufferedImage.TYPE_4BYTE_ABGR` format which is compatible with the RGBA format we'll convert to.
290+
291+
2. Get the underlying byte array from the BufferedImage using `((DataBufferByte) image.getRaster().getDataBuffer()).getData()`.
292+
293+
3. Use `VideoBufferConverter.convertFromI420()` to convert the frame buffer from I420 format (which is the internal format used by WebRTC) to RGBA format, storing the result directly in the BufferedImage's byte array.
294+
295+
4. The resulting BufferedImage can be used for display in Swing/JavaFX components or for further image processing.
296+
297+
#### Scaling Video Frames
298+
299+
Sometimes you may need to resize video frames to a different resolution, either to reduce processing requirements or to fit a specific display area. The `VideoFrameBuffer` interface provides a `cropAndScale` method that can be used for both cropping and scaling operations:
300+
301+
```java
302+
import dev.onvoid.webrtc.media.video.VideoFrame;
303+
import dev.onvoid.webrtc.media.video.VideoFrameBuffer;
304+
import dev.onvoid.webrtc.media.video.VideoTrackSink;
305+
306+
public class ScalingVideoSink implements VideoTrackSink {
307+
private final int targetWidth;
308+
private final int targetHeight;
309+
310+
public ScalingVideoSink(int targetWidth, int targetHeight) {
311+
this.targetWidth = targetWidth;
312+
this.targetHeight = targetHeight;
313+
}
314+
315+
@Override
316+
public void onVideoFrame(VideoFrame frame) {
317+
try {
318+
// Get the original frame buffer
319+
VideoFrameBuffer originalBuffer = frame.buffer;
320+
int originalWidth = originalBuffer.getWidth();
321+
int originalHeight = originalBuffer.getHeight();
322+
323+
// Scale the frame to the target resolution
324+
// For scaling only (no cropping), use the original dimensions for the crop region
325+
VideoFrameBuffer scaledBuffer = originalBuffer.cropAndScale(
326+
0, 0, // No cropping from top-left (cropX, cropY)
327+
originalWidth, originalHeight, // Use full frame width and height
328+
targetWidth, targetHeight // Scale to target dimensions
329+
);
330+
331+
// Create a new frame with the scaled buffer
332+
VideoFrame scaledFrame = new VideoFrame(scaledBuffer, frame.rotation, frame.timestampNs);
333+
334+
// Process the scaled frame
335+
System.out.printf("Scaled frame from %dx%d to %dx%d%n",
336+
originalWidth, originalHeight, scaledBuffer.getWidth(), scaledBuffer.getHeight());
337+
338+
// Don't forget to release both frames when done
339+
scaledFrame.release();
340+
} finally {
341+
// Always release the original frame
342+
frame.release();
343+
}
344+
}
345+
}
346+
```
347+
348+
This scaling process works as follows:
349+
350+
1. Get the original frame buffer and its dimensions.
351+
352+
2. Call `cropAndScale` on the original buffer with parameters:
353+
- `cropX = 0`, `cropY = 0`: Start from the top-left corner (no cropping)
354+
- `cropWidth = originalWidth`, `cropHeight = originalHeight`: Use the full frame (no cropping)
355+
- `scaleWidth = targetWidth`, `scaleHeight = targetHeight`: Scale to the desired dimensions
356+
357+
3. Create a new `VideoFrame` with the scaled buffer, preserving the original rotation and timestamp.
358+
359+
4. Process the scaled frame as needed.
360+
361+
5. Release both the scaled frame and the original frame to prevent memory leaks.
362+
363+
Note that scaling is a computationally expensive operation, especially for high-resolution frames. Consider the performance implications when choosing target dimensions and how frequently you scale frames.
364+
365+
## Best Practices
366+
367+
When implementing camera capture in your application, consider these best practices:
368+
369+
1. **Check for available cameras**: Always check if cameras are available before trying to use them.
370+
2. **Handle no cameras gracefully**: Provide a fallback mechanism if no cameras are available.
371+
3. **Let users select cameras**: If multiple cameras are available, let users choose which one to use.
372+
4. **Select appropriate resolution**: Choose a resolution that balances quality and performance.
373+
5. **Properly manage resources**: Always stop and dispose of video sources when they're no longer needed.
374+
6. **Handle device changes**: Implement device change listeners to handle cameras being connected or disconnected.
375+
7. **Consider privacy concerns**: In some applications, you may need to request permission to use the camera.
376+
377+
---
378+
379+
## Conclusion
380+
381+
This guide has demonstrated how to set up a WebRTC peer connection with camera video capture capabilities and how to receive and process both local and remote video frames.
382+
383+
Camera capture is essential for video conferencing applications, video chat, remote interviews, and any other applications where face-to-face communication is important.

0 commit comments

Comments
 (0)