Add ffmpeg capture to swaygrab (with limitations)

This needs to be multithreaded to have any sort of realistic expectation
of performance, due to issues with syncronous I/O.
This commit is contained in:
Drew DeVault 2015-11-27 14:21:38 -05:00
parent 89906f4ba1
commit 2ef7cf9e97
3 changed files with 115 additions and 14 deletions

View file

@ -31,6 +31,13 @@ Options
Use the specified socket path. Otherwise, swaymsg will ask sway where the Use the specified socket path. Otherwise, swaymsg will ask sway where the
socket is (which is the value of $SWAYSOCK, then of $I3SOCK). socket is (which is the value of $SWAYSOCK, then of $I3SOCK).
*-r, --rate* <rate>::
Specify a framerate (in frames per second). Used in combination with -c.
Default is 30. Must be an integer.
*--raw*::
Instead of invoking ImageMagick or ffmpeg, dump raw rgba data to stdout.
Examples Examples
-------- --------

View file

@ -10,6 +10,8 @@ add_executable(swaygrab
${common} ${common}
) )
TARGET_LINK_LIBRARIES(swaygrab rt)
install( install(
TARGETS swaygrab TARGETS swaygrab
RUNTIME DESTINATION bin RUNTIME DESTINATION bin

View file

@ -4,6 +4,7 @@
#include <getopt.h> #include <getopt.h>
#include <unistd.h> #include <unistd.h>
#include <math.h> #include <math.h>
#include <time.h>
#include "log.h" #include "log.h"
#include "ipc-client.h" #include "ipc-client.h"
@ -21,7 +22,47 @@ int numlen(int n) {
return 1; return 1;
} }
void grab_and_apply_magick(const char *file, const char *output, int socketfd) { void grab_and_apply_magick(const char *file, const char *output,
int socketfd, int raw) {
uint32_t len = strlen(output);
char *pixels = ipc_single_command(socketfd,
IPC_SWAY_GET_PIXELS, output, &len);
uint32_t *u32pixels = (uint32_t *)(pixels + 1);
uint32_t width = u32pixels[0];
uint32_t height = u32pixels[1];
len -= 9;
pixels += 9;
if (width == 0 || height == 0) {
sway_abort("Unknown output %s.", output);
}
if (raw) {
fwrite(pixels, 1, len, stdout);
fflush(stdout);
free(pixels - 9);
return;
}
const char *fmt = "convert -depth 8 -size %dx%d+0 rgba:- -flip %s";
char *cmd = malloc(strlen(fmt) - 6 /*args*/
+ numlen(width) + numlen(height) + strlen(file) + 1);
sprintf(cmd, fmt, width, height, file);
FILE *f = popen(cmd, "w");
fwrite(pixels, 1, len, f);
fflush(f);
fclose(f);
free(pixels - 9);
free(cmd);
}
void grab_and_apply_movie_magic(const char *file, const char *output,
int socketfd, int raw, int framerate) {
if (raw) {
sway_log(L_ERROR, "Raw capture data is not yet supported. Proceeding with ffmpeg normally.");
}
uint32_t len = strlen(output); uint32_t len = strlen(output);
char *pixels = ipc_single_command(socketfd, char *pixels = ipc_single_command(socketfd,
IPC_SWAY_GET_PIXELS, output, &len); IPC_SWAY_GET_PIXELS, output, &len);
@ -34,22 +75,54 @@ void grab_and_apply_magick(const char *file, const char *output, int socketfd) {
sway_abort("Unknown output %s.", output); sway_abort("Unknown output %s.", output);
} }
const char *fmt = "convert -depth 8 -size %dx%d+0 rgba:- -flip %s"; const char *fmt = "ffmpeg -f rawvideo -framerate %d "
char *cmd = malloc(strlen(fmt) - 6 /*args*/ "-video_size %dx%d -pixel_format argb "
+ numlen(width) + numlen(height) + strlen(file) + 1); "-i pipe:0 -r %d -vf vflip %s";
sprintf(cmd, fmt, width, height, file); char *cmd = malloc(strlen(fmt) - 8 /*args*/
+ numlen(width) + numlen(height) + numlen(framerate) * 2
+ strlen(file) + 1);
sprintf(cmd, fmt, framerate, width, height, framerate, file);
long ns = (long)(1000000000 * (1.0 / framerate));
struct timespec start, finish, ts;
ts.tv_sec = 0;
FILE *f = popen(cmd, "w"); FILE *f = popen(cmd, "w");
fwrite(pixels, 1, len, f); fwrite(pixels, 1, len, f);
free(pixels - 9);
int sleep = 0;
while (sleep != -1) {
clock_gettime(CLOCK_MONOTONIC, &start);
len = strlen(output);
pixels = ipc_single_command(socketfd,
IPC_SWAY_GET_PIXELS, output, &len);
pixels += 9;
len -= 9;
fwrite(pixels, 1, len, f);
clock_gettime(CLOCK_MONOTONIC, &finish);
ts.tv_nsec = ns;
double fts = (double)finish.tv_sec + 1.0e-9*finish.tv_nsec;
double sts = (double)start.tv_sec + 1.0e-9*start.tv_nsec;
long diff = (fts - sts) * 1000000000;
sway_log(L_INFO, "%f %f %ld", sts, fts, diff);
ts.tv_nsec = ns - diff;
if (ts.tv_nsec < 0) {
ts.tv_nsec = 0;
}
sleep = nanosleep(&ts, NULL);
}
fflush(f); fflush(f);
fclose(f); fclose(f);
free(pixels);
free(cmd); free(cmd);
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
static int capture = 0; static int capture = 0, raw = 0;
char *socket_path = NULL; char *socket_path = NULL;
int framerate = 30;
init_log(L_INFO); init_log(L_INFO);
@ -57,13 +130,15 @@ int main(int argc, char **argv) {
{"capture", no_argument, &capture, 'c'}, {"capture", no_argument, &capture, 'c'},
{"version", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'v'},
{"socket", required_argument, NULL, 's'}, {"socket", required_argument, NULL, 's'},
{"raw", no_argument, &raw, 'r'},
{"rate", required_argument, NULL, 'R'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
int c; int c;
while (1) { while (1) {
int option_index = 0; int option_index = 0;
c = getopt_long(argc, argv, "cvs:", long_options, &option_index); c = getopt_long(argc, argv, "cvs:r", long_options, &option_index);
if (c == -1) { if (c == -1) {
break; break;
} }
@ -73,6 +148,15 @@ int main(int argc, char **argv) {
case 's': // Socket case 's': // Socket
socket_path = strdup(optarg); socket_path = strdup(optarg);
break; break;
case 'r':
raw = 1;
break;
case 'c':
capture = 1;
break;
case 'R': // Frame rate
framerate = atoi(optarg);
break;
case 'v': case 'v':
#if defined SWAY_GIT_VERSION && defined SWAY_GIT_BRANCH && defined SWAY_VERSION_DATE #if defined SWAY_GIT_VERSION && defined SWAY_GIT_BRANCH && defined SWAY_VERSION_DATE
fprintf(stdout, "sway version %s (%s, branch \"%s\")\n", SWAY_GIT_VERSION, SWAY_VERSION_DATE, SWAY_GIT_BRANCH); fprintf(stdout, "sway version %s (%s, branch \"%s\")\n", SWAY_GIT_VERSION, SWAY_VERSION_DATE, SWAY_GIT_BRANCH);
@ -91,19 +175,27 @@ int main(int argc, char **argv) {
} }
} }
if (optind >= argc - 1) { char *file, *output;
sway_abort("Expected output and file on command line. See `man swaygrab`"); if (raw) {
if (optind >= argc) {
sway_abort("Invalid usage. See `man swaygrab` %d %d", argc, optind);
}
output = argv[optind];
} else {
if (optind >= argc - 1) {
sway_abort("Invalid usage. See `man swaygrab`");
}
file = argv[optind + 1];
output = argv[optind];
} }
char *file = argv[optind + 1];
char *output = argv[optind];
int socketfd = ipc_open_socket(socket_path); int socketfd = ipc_open_socket(socket_path);
free(socket_path); free(socket_path);
if (!capture) { if (!capture) {
grab_and_apply_magick(file, output, socketfd); grab_and_apply_magick(file, output, socketfd, raw);
} else { } else {
sway_abort("Capture is not yet supported"); grab_and_apply_movie_magic(file, output, socketfd, raw, framerate);
} }
close(socketfd); close(socketfd);