Skip to content

Commit 4feb1da

Browse files
wysaidCopilot
andcommitted
feat: add camera-to-video recording demo and CLI --record support
- examples/desktop/6-record_video.cpp: new demo that opens camera and records ~5 seconds of frames to an MP4 file using ccap::VideoWriter. Guarded with #ifdef CCAP_ENABLE_VIDEO_WRITER for non-supported platforms. - cli: add --record <file> option to record camera frames to a video file. Integrated into captureFrames() guarded by #ifdef CCAP_ENABLE_VIDEO_WRITER. Warns if used without -c/--count or --timeout (would run indefinitely). Warns if used with --video mode (not supported). - .vscode/tasks.json: add 'Run ccap CLI --record camera (Debug/Release)' tasks that record 5 seconds from the default camera to camera_capture.mp4. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ba6a43a commit 4feb1da

6 files changed

Lines changed: 250 additions & 0 deletions

File tree

.vscode/tasks.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,62 @@
15471547
},
15481548
"problemMatcher": "$msCompile"
15491549
}
1550+
},
1551+
{
1552+
"label": "Run ccap CLI --record camera (Debug)",
1553+
"type": "shell",
1554+
"command": "bash",
1555+
"args": [
1556+
"-l",
1557+
"-c",
1558+
"( if [[ $(pwd) =~ ^/mnt ]]; then ./ccap.exe --record ./camera_capture.mp4 --timeout 5; else ./ccap --record ./camera_capture.mp4 --timeout 5; fi )"
1559+
],
1560+
"options": {
1561+
"cwd": "${workspaceFolder}/build/Debug"
1562+
},
1563+
"group": "build",
1564+
"problemMatcher": "$gcc",
1565+
"dependsOn": [
1566+
"Config: Enable CLI Tool",
1567+
"Build Project (Debug)"
1568+
],
1569+
"dependsOrder": "sequence",
1570+
"windows": {
1571+
"command": ".\\ccap.exe",
1572+
"args": ["--record", ".\\camera_capture.mp4", "--timeout", "5"],
1573+
"options": {
1574+
"cwd": "${workspaceFolder}/build/Debug"
1575+
},
1576+
"problemMatcher": "$msCompile"
1577+
}
1578+
},
1579+
{
1580+
"label": "Run ccap CLI --record camera (Release)",
1581+
"type": "shell",
1582+
"command": "bash",
1583+
"args": [
1584+
"-l",
1585+
"-c",
1586+
"( if [[ $(pwd) =~ ^/mnt ]]; then ./ccap.exe --record ./camera_capture.mp4 --timeout 5; else ./ccap --record ./camera_capture.mp4 --timeout 5; fi )"
1587+
],
1588+
"options": {
1589+
"cwd": "${workspaceFolder}/build/Release"
1590+
},
1591+
"group": "build",
1592+
"problemMatcher": "$gcc",
1593+
"dependsOn": [
1594+
"Config: Enable CLI Tool",
1595+
"Build Project (Release)"
1596+
],
1597+
"dependsOrder": "sequence",
1598+
"windows": {
1599+
"command": ".\\ccap.exe",
1600+
"args": ["--record", ".\\camera_capture.mp4", "--timeout", "5"],
1601+
"options": {
1602+
"cwd": "${workspaceFolder}/build/Release"
1603+
},
1604+
"problemMatcher": "$msCompile"
1605+
}
15501606
}
15511607
]
15521608
}

cli/args_parser.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,14 @@ void printUsage(const char* programName) {
172172
#endif
173173
<< "\n";
174174

175+
#ifdef CCAP_ENABLE_VIDEO_WRITER
176+
std::cout << "Video recording options (camera mode only):\n"
177+
<< " --record file record camera frames to a video file (e.g., output.mp4)\n"
178+
<< " Use -c to limit the number of frames, or --timeout for duration\n"
179+
<< " Supported formats: .mp4, .mov\n"
180+
<< "\n";
181+
#endif
182+
175183
#ifdef CCAP_CLI_WITH_GLFW
176184
std::cout << "Preview options:\n"
177185
<< " -p, --preview enable window preview\n"
@@ -391,6 +399,10 @@ CLIOptions parseArgs(int argc, char* argv[]) {
391399
if (i + 1 < argc) {
392400
opts.videoFilePath = argv[++i];
393401
}
402+
} else if (arg == "--record") {
403+
if (i + 1 < argc) {
404+
opts.recordVideoPath = argv[++i];
405+
}
394406
} else if (arg == "-w" || arg == "--width") {
395407
if (i + 1 < argc) {
396408
opts.width = std::atoi(argv[++i]);

cli/args_parser.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ struct CLIOptions {
8484
double playbackSpeed = 0.0; // 0.0 = no frame rate control, 1.0 = normal speed
8585
bool playbackSpeedSpecified = false;
8686

87+
// Video recording settings
88+
std::string recordVideoPath; ///< Output video file path for --record (camera mode only)
89+
8790
// Conversion settings
8891
std::string convertInput;
8992
std::string convertOutput;

cli/ccap_cli.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ int main(int argc, char* argv[]) {
8282
return 1;
8383
}
8484

85+
// --record without a frame limit will run indefinitely
86+
if (!opts.recordVideoPath.empty() && !opts.captureCountSpecified && opts.timeoutSeconds == 0) {
87+
std::cerr << "Warning: --record specified without -c/--count or --timeout. "
88+
"Use Ctrl+C to stop recording." << std::endl;
89+
}
90+
8591
// Set log level based on options
8692
if (opts.verbose) {
8793
ccap::setLogLevel(ccap::LogLevel::Verbose);

cli/ccap_cli_utils.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
#include <ccap_convert.h>
1111
#include <ccap_utils.h>
1212

13+
#ifdef CCAP_ENABLE_VIDEO_WRITER
14+
#include <ccap_writer.h>
15+
#endif
16+
1317
#include <algorithm>
1418
#include <chrono>
1519
#include <climits>
@@ -663,6 +667,34 @@ int captureFrames(const CLIOptions& opts) {
663667
return 1;
664668
}
665669

670+
// Setup video writer for --record (camera mode only)
671+
#ifdef CCAP_ENABLE_VIDEO_WRITER
672+
std::unique_ptr<ccap::VideoWriter> videoWriter;
673+
if (!opts.recordVideoPath.empty()) {
674+
if (isVideoMode) {
675+
std::cerr << "Warning: --record is not supported in video file mode. Ignoring." << std::endl;
676+
} else {
677+
int camWidth = static_cast<int>(provider.get(ccap::PropertyName::Width));
678+
int camHeight = static_cast<int>(provider.get(ccap::PropertyName::Height));
679+
double camFps = provider.get(ccap::PropertyName::FrameRate);
680+
681+
ccap::WriterConfig writerConfig;
682+
writerConfig.width = static_cast<uint32_t>(camWidth);
683+
writerConfig.height = static_cast<uint32_t>(camHeight);
684+
writerConfig.frameRate = camFps > 0.0 ? camFps : 30.0;
685+
686+
videoWriter = std::make_unique<ccap::VideoWriter>();
687+
if (!videoWriter->open(opts.recordVideoPath, writerConfig)) {
688+
std::cerr << "Failed to open video writer for: " << opts.recordVideoPath << std::endl;
689+
return 1;
690+
}
691+
if (ccap::infoLogEnabled()) {
692+
std::cout << "Recording to: " << opts.recordVideoPath << std::endl;
693+
}
694+
}
695+
}
696+
#endif
697+
666698
// Create output directory if saving frames
667699
bool shouldSave = opts.saveFrames && !opts.outputDir.empty();
668700
if (shouldSave) {
@@ -724,6 +756,15 @@ int captureFrames(const CLIOptions& opts) {
724756
std::cout << "Frame " << frame->frameIndex << ": " << frame->width << "x" << frame->height
725757
<< " format=" << ccap::pixelFormatToString(frame->pixelFormat) << std::endl;
726758

759+
// Write frame to video file if recording
760+
#ifdef CCAP_ENABLE_VIDEO_WRITER
761+
if (videoWriter && videoWriter->isOpened()) {
762+
if (!videoWriter->writeFrame(*frame)) {
763+
std::cerr << "Warning: Failed to write frame " << frame->frameIndex << " to video." << std::endl;
764+
}
765+
}
766+
#endif
767+
727768
// Save frame if enabled
728769
if (shouldSave) {
729770
// Generate output filename
@@ -747,6 +788,15 @@ int captureFrames(const CLIOptions& opts) {
747788

748789
std::cout << "Captured " << capturedCount << " frame(s)." << std::endl;
749790

791+
#ifdef CCAP_ENABLE_VIDEO_WRITER
792+
if (videoWriter && videoWriter->isOpened()) {
793+
videoWriter->close();
794+
if (ccap::infoLogEnabled()) {
795+
std::cout << "Video saved to: " << opts.recordVideoPath << std::endl;
796+
}
797+
}
798+
#endif
799+
750800
if (timeoutOccurred) {
751801
return opts.timeoutExitCode;
752802
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* @file 6-record_video.cpp
3+
* @author wysaid (this@wysaid.org)
4+
* @brief Example: open a camera and record frames to a video file.
5+
* @date 2025-05
6+
*
7+
* Usage:
8+
* ./6-record_video [output_path.mp4]
9+
*
10+
* Records ~5 seconds (150 frames at 30 fps) from the first available camera
11+
* and saves them to output_path.mp4 (default: camera_capture.mp4 next to the binary).
12+
*/
13+
14+
#include "utils/helper.h"
15+
16+
#include <ccap.h>
17+
#include <cstdio>
18+
#include <filesystem>
19+
#include <iostream>
20+
#include <string>
21+
22+
#ifndef CCAP_ENABLE_VIDEO_WRITER
23+
24+
int main() {
25+
std::cerr << "[WARNING] Video writing is not supported on this platform.\n"
26+
<< "Rebuild with -DCCAP_ENABLE_VIDEO_WRITER=ON (requires Windows or macOS).\n";
27+
return 0;
28+
}
29+
30+
#else
31+
32+
#include <ccap_writer.h>
33+
34+
int main(int argc, char** argv) {
35+
ExampleCommandLine commandLine{};
36+
initExampleCommandLine(&commandLine, argc, argv);
37+
applyExampleCameraBackend(&commandLine);
38+
39+
ccap::setLogLevel(ccap::LogLevel::Verbose);
40+
41+
ccap::setErrorCallback([](ccap::ErrorCode errorCode, std::string_view description) {
42+
std::cerr << "Error - Code: " << static_cast<int>(errorCode)
43+
<< ", Description: " << description << "\n";
44+
});
45+
46+
// Determine output path
47+
std::string outputPath;
48+
if (commandLine.argc >= 2) {
49+
outputPath = commandLine.argv[1];
50+
} else {
51+
std::string exeDir = commandLine.argv[0];
52+
if (auto pos = exeDir.find_last_of("/\\"); pos != std::string::npos && exeDir[0] != '.') {
53+
exeDir = exeDir.substr(0, pos);
54+
} else {
55+
exeDir = std::filesystem::current_path().string();
56+
}
57+
outputPath = exeDir + "/camera_capture.mp4";
58+
}
59+
60+
std::cout << "Output video: " << outputPath << "\n";
61+
62+
// Open camera
63+
ccap::Provider cameraProvider;
64+
cameraProvider.set(ccap::PropertyName::Width, 1280);
65+
cameraProvider.set(ccap::PropertyName::Height, 720);
66+
cameraProvider.set(ccap::PropertyName::FrameRate, 30.0);
67+
68+
int deviceIndex = selectCamera(cameraProvider, &commandLine);
69+
cameraProvider.open(deviceIndex, true);
70+
71+
if (!cameraProvider.isStarted()) {
72+
std::cerr << "Failed to start camera!\n";
73+
return -1;
74+
}
75+
76+
int realWidth = static_cast<int>(cameraProvider.get(ccap::PropertyName::Width));
77+
int realHeight = static_cast<int>(cameraProvider.get(ccap::PropertyName::Height));
78+
double realFps = cameraProvider.get(ccap::PropertyName::FrameRate);
79+
80+
printf("Camera started: %dx%d @ %.2f fps\n", realWidth, realHeight, realFps);
81+
82+
// Configure and open video writer
83+
ccap::WriterConfig writerConfig;
84+
writerConfig.width = static_cast<uint32_t>(realWidth);
85+
writerConfig.height = static_cast<uint32_t>(realHeight);
86+
writerConfig.frameRate = realFps > 0.0 ? realFps : 30.0;
87+
88+
ccap::VideoWriter writer;
89+
if (!writer.open(outputPath, writerConfig)) {
90+
std::cerr << "Failed to open video writer!\n";
91+
return -1;
92+
}
93+
94+
// Record ~5 seconds
95+
constexpr int kMaxFrames = 150;
96+
int recorded = 0;
97+
std::cout << "Recording " << kMaxFrames << " frames (~5 seconds)...\n";
98+
99+
while (recorded < kMaxFrames) {
100+
auto frame = cameraProvider.grab(3000);
101+
if (!frame) {
102+
std::cerr << "Timeout waiting for camera frame.\n";
103+
break;
104+
}
105+
106+
if (!writer.writeFrame(*frame)) {
107+
std::cerr << "Failed to write frame " << recorded << "\n";
108+
}
109+
110+
if (++recorded % 30 == 0) {
111+
printf(" Recorded %d/%d frames...\n", recorded, kMaxFrames);
112+
}
113+
}
114+
115+
writer.close();
116+
cameraProvider.stop();
117+
cameraProvider.close();
118+
119+
printf("Done! %d frames saved to: %s\n", recorded, outputPath.c_str());
120+
return 0;
121+
}
122+
123+
#endif // CCAP_ENABLE_VIDEO_WRITER

0 commit comments

Comments
 (0)