mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-04-20 19:45:00 +00:00
Adds ability to capture a screenshot to PNG
This commit is contained in:
parent
158a3e5d2b
commit
d16a0d260c
11 changed files with 337 additions and 7 deletions
|
@ -1,5 +1,6 @@
|
|||
src = [
|
||||
'src/main.c',
|
||||
'src/capture.c',
|
||||
'src/cli.c',
|
||||
'src/command.c',
|
||||
'src/control_msg.c',
|
||||
|
@ -30,6 +31,8 @@ if not get_option('crossbuild_windows')
|
|||
dependency('libavformat'),
|
||||
dependency('libavcodec'),
|
||||
dependency('libavutil'),
|
||||
dependency('libswscale'),
|
||||
dependency('libpng'),
|
||||
dependency('sdl2'),
|
||||
]
|
||||
|
||||
|
@ -168,7 +171,8 @@ if get_option('buildtype') == 'debug'
|
|||
'tests/test_queue.c',
|
||||
]],
|
||||
['test_strutil', [
|
||||
'tests/test_strutil.c',
|
||||
'tests/test_s
|
||||
til.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
]
|
||||
|
|
254
app/src/capture.c
Normal file
254
app/src/capture.c
Normal file
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* Copyright (c) 2001 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* video decoding with libavcodec API example
|
||||
*
|
||||
* @example decode_video.c
|
||||
*/
|
||||
|
||||
#include "capture.h"
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include "events.h"
|
||||
#include "util/log.h"
|
||||
|
||||
static const char *string_error(int err) {
|
||||
switch (err) {
|
||||
case AVERROR_EOF: return "End of File";
|
||||
case AVERROR_BUG: return "Bug";
|
||||
case AVERROR_BUG2: return "Bug2";
|
||||
case AVERROR_BUFFER_TOO_SMALL: return "Buffer Too Small";
|
||||
case AVERROR_EXIT: return "Exit Requested";
|
||||
case AVERROR_DECODER_NOT_FOUND: return "Decoder Not Found";
|
||||
case AVERROR_DEMUXER_NOT_FOUND: return "Demuxer Not Found";
|
||||
case AVERROR_ENCODER_NOT_FOUND: return "Encoder Not Found";
|
||||
case AVERROR_EXTERNAL: return "External Error";
|
||||
case AVERROR_FILTER_NOT_FOUND: return "Filter Not Found";
|
||||
case AVERROR_MUXER_NOT_FOUND: return "Muxer Not Found";
|
||||
case AVERROR_OPTION_NOT_FOUND: return "Option Not Found";
|
||||
case AVERROR_PATCHWELCOME: return "Patch Welcome";
|
||||
case AVERROR_PROTOCOL_NOT_FOUND: return "Protocol Not Found";
|
||||
case AVERROR_STREAM_NOT_FOUND: return "Stream Not Found";
|
||||
case AVERROR_INVALIDDATA: return "Invalid Data (waiting for index frame?)";
|
||||
case AVERROR(EINVAL): return "Invalid";
|
||||
case AVERROR(ENOMEM): return "No Memory";
|
||||
case AVERROR(EAGAIN): return "Try Again";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void notify_complete() {
|
||||
static SDL_Event new_frame_event = {
|
||||
.type = EVENT_SCREEN_CAPTURE_COMPLETE,
|
||||
};
|
||||
SDL_PushEvent(&new_frame_event);
|
||||
}
|
||||
|
||||
// Thanks, https://stackoverflow.com/a/18313791
|
||||
// Also, https://raw.githubusercontent.com/FFmpeg/FFmpeg/master/doc/examples/decode_video.c
|
||||
|
||||
static bool in_frame_to_png(
|
||||
AVIOContext *output_context,
|
||||
AVCodecContext *codecCtx, AVFrame *inframe) {
|
||||
struct SwsContext * swCtx = sws_getContext(codecCtx->width,
|
||||
codecCtx->height,
|
||||
codecCtx->pix_fmt,
|
||||
codecCtx->width,
|
||||
codecCtx->height,
|
||||
AV_PIX_FMT_RGB24,
|
||||
SWS_FAST_BILINEAR, 0, 0, 0);
|
||||
|
||||
AVFrame * rgbFrame = av_frame_alloc();
|
||||
LOGV("Image frame width: %d height: %d", codecCtx->width, codecCtx->height);
|
||||
rgbFrame->width = codecCtx->width;
|
||||
rgbFrame->height = codecCtx->height;
|
||||
rgbFrame->format = AV_PIX_FMT_RGB24;
|
||||
av_image_alloc(
|
||||
rgbFrame->data, rgbFrame->linesize, codecCtx->width,
|
||||
codecCtx->height, AV_PIX_FMT_RGB24, 1);
|
||||
sws_scale(
|
||||
swCtx, inframe->data, inframe->linesize, 0,
|
||||
inframe->height, rgbFrame->data, rgbFrame->linesize);
|
||||
|
||||
AVCodec *outCodec = avcodec_find_encoder(AV_CODEC_ID_PNG);
|
||||
if (!outCodec) {
|
||||
LOGE("Failed to find PNG codec");
|
||||
return false;
|
||||
}
|
||||
AVCodecContext *outCodecCtx = avcodec_alloc_context3(outCodec);
|
||||
if (!outCodecCtx) {
|
||||
LOGE("Unable to allocate PNG codec context");
|
||||
return false;
|
||||
}
|
||||
|
||||
outCodecCtx->width = codecCtx->width;
|
||||
outCodecCtx->height = codecCtx->height;
|
||||
outCodecCtx->pix_fmt = AV_PIX_FMT_RGB24;
|
||||
outCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
if (codecCtx->time_base.num && codecCtx->time_base.num) {
|
||||
outCodecCtx->time_base.num = codecCtx->time_base.num;
|
||||
outCodecCtx->time_base.den = codecCtx->time_base.den;
|
||||
} else {
|
||||
outCodecCtx->time_base.num = 1;
|
||||
outCodecCtx->time_base.den = 30; // FPS
|
||||
}
|
||||
|
||||
int ret = avcodec_open2(outCodecCtx, outCodec, NULL);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to open PNG codec: %s", string_error(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = avcodec_send_frame(outCodecCtx, rgbFrame);
|
||||
av_frame_free(&rgbFrame);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to send frame to output codex");
|
||||
return false;
|
||||
}
|
||||
|
||||
AVPacket outPacket;
|
||||
av_init_packet(&outPacket);
|
||||
outPacket.size = 0;
|
||||
outPacket.data = NULL;
|
||||
ret = avcodec_receive_packet(outCodecCtx, &outPacket);
|
||||
avcodec_close(outCodecCtx);
|
||||
av_free(outCodecCtx);
|
||||
if (ret >= 0) {
|
||||
// Dump packet
|
||||
avio_write(output_context, outPacket.data, outPacket.size);
|
||||
notify_complete();
|
||||
av_packet_unref(&outPacket);
|
||||
return true;
|
||||
} else {
|
||||
LOGE("Failed to receive packet");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Decodes a video packet into PNG image, if possible.
|
||||
// Sends a new video packet `packet` into the active decoding
|
||||
// context `dec_ctx`.
|
||||
// Returns `true` when the decoding yielded a PNG.
|
||||
static bool decode_packet_to_png(
|
||||
AVIOContext *output_context,
|
||||
AVCodecContext *dec_ctx,
|
||||
const AVPacket *packet,
|
||||
AVFrame *working_frame) {
|
||||
LOGV("Sending video packet: %p Size: %d", packet, packet ? packet->size : 0);
|
||||
|
||||
int ret = avcodec_send_packet(dec_ctx, packet);
|
||||
if (ret < 0) {
|
||||
LOGW( "Error sending a packet for decoding: %s", string_error(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
while (ret >= 0) {
|
||||
ret = avcodec_receive_frame(dec_ctx, working_frame);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
LOGV("No frame received: %s", string_error(ret));
|
||||
break; // We're done
|
||||
} else if (ret < 0) {
|
||||
LOGW("Error during decoding: %s", string_error(ret));
|
||||
}
|
||||
|
||||
LOGI("Found frame: %d", dec_ctx->frame_number);
|
||||
if (in_frame_to_png(output_context, dec_ctx, working_frame)) {
|
||||
found = true;
|
||||
break;
|
||||
} else {
|
||||
LOGE("Failed to generate PNG");
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool capture_init(struct capture *capture, const char *filename) {
|
||||
capture->filename = SDL_strdup(filename);
|
||||
if (!capture->filename) {
|
||||
LOGE("Could not strdup filename");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = avio_open(&capture->file_context, capture->filename,
|
||||
AVIO_FLAG_WRITE);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to open output file: %s", capture->filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* find the MPEG-1 video decoder */
|
||||
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
if (!codec) {
|
||||
LOGE("Codec not found: %d", AV_CODEC_ID_H264);
|
||||
return false;
|
||||
}
|
||||
|
||||
capture->context = avcodec_alloc_context3(codec);
|
||||
if (!capture->context) {
|
||||
LOGE("Could not allocate video codec context");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For some codecs, such as msmpeg4 and mpeg4, width and height
|
||||
MUST be initialized there because this information is not
|
||||
available in the bitstream. */
|
||||
|
||||
/* open it */
|
||||
if (avcodec_open2(capture->context, codec, NULL) < 0) {
|
||||
LOGE("Could not open codec");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a reusable frame buffer.
|
||||
capture->working_frame = av_frame_alloc();
|
||||
if (!capture->working_frame) {
|
||||
LOGE("Could not allocate video frame");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void capture_destroy(struct capture *capture) {
|
||||
av_frame_free(&capture->working_frame);
|
||||
avcodec_free_context(&capture->context);
|
||||
avio_close(capture->file_context);
|
||||
}
|
||||
|
||||
bool capture_push(struct capture *capture, const AVPacket *packet) {
|
||||
if (decode_packet_to_png(capture->file_context, capture->context, packet, capture->working_frame)) {
|
||||
LOGI("Parsed PNG from incoming packets.");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
21
app/src/capture.h
Normal file
21
app/src/capture.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef CAPTURE_H
|
||||
#define CAPTURE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
struct capture {
|
||||
char *filename;
|
||||
AVIOContext *file_context;
|
||||
AVCodecContext *context;
|
||||
AVFrame *working_frame;
|
||||
};
|
||||
|
||||
bool capture_init(struct capture *capture, const char *filename);
|
||||
|
||||
void capture_destroy(struct capture *capture);
|
||||
|
||||
bool capture_push(struct capture *capture, const AVPacket *packet);
|
||||
|
||||
#endif // CAPTURE_H
|
|
@ -363,6 +363,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
|
||||
{"render-expired-frames", no_argument, NULL,
|
||||
OPT_RENDER_EXPIRED_FRAMES},
|
||||
{"screen-capture", required_argument, NULL, 'C'},
|
||||
{"serial", required_argument, NULL, 's'},
|
||||
{"show-touches", no_argument, NULL, 't'},
|
||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||
|
@ -383,7 +384,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||
optind = 0; // reset to start from the first argument in tests
|
||||
|
||||
int c;
|
||||
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
|
||||
while ((c = getopt_long(argc, argv, "b:cC:fF:hm:nNp:r:s:StTv", long_options,
|
||||
NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'b':
|
||||
|
@ -391,6 +392,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||
return false;
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
opts->capture_filename = optarg;
|
||||
break;
|
||||
case 'c':
|
||||
LOGW("Deprecated option -c. Use --crop instead.");
|
||||
// fall through
|
||||
|
@ -494,8 +498,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!opts->display && !opts->record_filename) {
|
||||
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
||||
if (!opts->display && !opts->record_filename && !opts->capture_filename) {
|
||||
LOGE("-N/--no-display requires screen recording (-r/--record) or screen capture (-C/--screen-capture)");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#define EVENT_NEW_SESSION SDL_USEREVENT
|
||||
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
|
||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)
|
||||
#define EVENT_SCREEN_CAPTURE_COMPLETE (SDL_USEREVENT + 3)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
@ -27,6 +28,9 @@ print_version(void) {
|
|||
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
|
||||
LIBAVUTIL_VERSION_MINOR,
|
||||
LIBAVUTIL_VERSION_MICRO);
|
||||
fprintf(stderr, " - libswscale %d.%d.%d\n", LIBSWSCALE_VERSION_MAJOR,
|
||||
LIBSWSCALE_VERSION_MINOR,
|
||||
LIBSWSCALE_VERSION_MICRO);
|
||||
}
|
||||
|
||||
int
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "file_handler.h"
|
||||
#include "fps_counter.h"
|
||||
#include "input_manager.h"
|
||||
#include "capture.h"
|
||||
#include "recorder.h"
|
||||
#include "screen.h"
|
||||
#include "server.h"
|
||||
|
@ -35,6 +36,7 @@ static struct video_buffer video_buffer;
|
|||
static struct stream stream;
|
||||
static struct decoder decoder;
|
||||
static struct recorder recorder;
|
||||
static struct capture capture;
|
||||
static struct controller controller;
|
||||
static struct file_handler file_handler;
|
||||
|
||||
|
@ -131,6 +133,9 @@ handle_event(SDL_Event *event, bool control) {
|
|||
case EVENT_STREAM_STOPPED:
|
||||
LOGD("Video stream stopped");
|
||||
return EVENT_RESULT_STOPPED_BY_EOS;
|
||||
case EVENT_SCREEN_CAPTURE_COMPLETE:
|
||||
LOGD("Screen capture completed");
|
||||
return EVENT_RESULT_STOPPED_BY_USER;
|
||||
case SDL_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
return EVENT_RESULT_STOPPED_BY_USER;
|
||||
|
@ -277,6 +282,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||
|
||||
bool
|
||||
scrcpy(const struct scrcpy_options *options) {
|
||||
bool captures = !!options->capture_filename;
|
||||
bool record = !!options->record_filename;
|
||||
struct server_params params = {
|
||||
.crop = options->crop,
|
||||
|
@ -304,6 +310,7 @@ scrcpy(const struct scrcpy_options *options) {
|
|||
bool video_buffer_initialized = false;
|
||||
bool file_handler_initialized = false;
|
||||
bool recorder_initialized = false;
|
||||
bool capture_initialized = false;
|
||||
bool stream_started = false;
|
||||
bool controller_initialized = false;
|
||||
bool controller_started = false;
|
||||
|
@ -363,9 +370,19 @@ scrcpy(const struct scrcpy_options *options) {
|
|||
recorder_initialized = true;
|
||||
}
|
||||
|
||||
struct capture *cap = NULL;
|
||||
if (captures) {
|
||||
if (!capture_init(&capture,
|
||||
options->capture_filename)) {
|
||||
goto end;
|
||||
}
|
||||
cap = &capture;
|
||||
capture_initialized = true;
|
||||
}
|
||||
|
||||
av_log_set_callback(av_log_callback);
|
||||
|
||||
stream_init(&stream, server.video_socket, dec, rec);
|
||||
stream_init(&stream, server.video_socket, dec, rec, cap);
|
||||
|
||||
// now we consumed the header values, the socket receives the video stream
|
||||
// start the stream
|
||||
|
@ -460,6 +477,9 @@ end:
|
|||
recorder_destroy(&recorder);
|
||||
}
|
||||
|
||||
if (capture_initialized) {
|
||||
capture_destroy(&capture);
|
||||
}
|
||||
if (file_handler_initialized) {
|
||||
file_handler_join(&file_handler);
|
||||
file_handler_destroy(&file_handler);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
struct scrcpy_options {
|
||||
const char *serial;
|
||||
const char *crop;
|
||||
const char *capture_filename;
|
||||
const char *record_filename;
|
||||
const char *window_title;
|
||||
const char *push_target;
|
||||
|
@ -37,6 +38,7 @@ struct scrcpy_options {
|
|||
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||
.serial = NULL, \
|
||||
.crop = NULL, \
|
||||
.capture_filename = NULL, \
|
||||
.record_filename = NULL, \
|
||||
.window_title = NULL, \
|
||||
.push_target = NULL, \
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "compat.h"
|
||||
#include "decoder.h"
|
||||
#include "events.h"
|
||||
#include "capture.h"
|
||||
#include "recorder.h"
|
||||
#include "util/buffer_util.h"
|
||||
#include "util/log.h"
|
||||
|
@ -71,6 +72,10 @@ notify_stopped(void) {
|
|||
|
||||
static bool
|
||||
process_config_packet(struct stream *stream, AVPacket *packet) {
|
||||
if (stream->capture && !capture_push(stream->capture, packet)) {
|
||||
LOGW("Could not send config packet to capture");
|
||||
return true; // We're Ok. Keep sending packets.
|
||||
}
|
||||
if (stream->recorder && !recorder_push(stream->recorder, packet)) {
|
||||
LOGE("Could not send config packet to recorder");
|
||||
return false;
|
||||
|
@ -93,6 +98,15 @@ process_frame(struct stream *stream, AVPacket *packet) {
|
|||
}
|
||||
}
|
||||
|
||||
if (stream->capture) {
|
||||
packet->dts = packet->pts;
|
||||
|
||||
if (!capture_push(stream->capture, packet)) {
|
||||
LOGE("Could not send packet to capture");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -270,9 +284,11 @@ end:
|
|||
|
||||
void
|
||||
stream_init(struct stream *stream, socket_t socket,
|
||||
struct decoder *decoder, struct recorder *recorder) {
|
||||
struct decoder *decoder, struct recorder *recorder,
|
||||
struct capture *capture) {
|
||||
stream->socket = socket;
|
||||
stream->decoder = decoder,
|
||||
stream->capture = capture;
|
||||
stream->recorder = recorder;
|
||||
stream->has_pending = false;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ struct stream {
|
|||
SDL_Thread *thread;
|
||||
struct decoder *decoder;
|
||||
struct recorder *recorder;
|
||||
struct capture *capture;
|
||||
AVCodecContext *codec_ctx;
|
||||
AVCodecParserContext *parser;
|
||||
// successive packets may need to be concatenated, until a non-config
|
||||
|
@ -28,7 +29,8 @@ struct stream {
|
|||
|
||||
void
|
||||
stream_init(struct stream *stream, socket_t socket,
|
||||
struct decoder *decoder, struct recorder *recorder);
|
||||
struct decoder *decoder, struct recorder *recorder,
|
||||
struct capture *capture);
|
||||
|
||||
bool
|
||||
stream_start(struct stream *stream);
|
||||
|
|
|
@ -55,6 +55,7 @@ static void test_options(void) {
|
|||
"--record", "file",
|
||||
"--record-format", "mkv",
|
||||
"--render-expired-frames",
|
||||
"--screen-capture", "filename.png",
|
||||
"--serial", "0123456789abcdef",
|
||||
"--show-touches",
|
||||
"--turn-screen-off",
|
||||
|
@ -83,6 +84,7 @@ static void test_options(void) {
|
|||
assert(!strcmp(opts->record_filename, "file"));
|
||||
assert(opts->record_format == RECORDER_FORMAT_MKV);
|
||||
assert(opts->render_expired_frames);
|
||||
assert(!strcmp(opts->capture_filename, "filename.png"));
|
||||
assert(!strcmp(opts->serial, "0123456789abcdef"));
|
||||
assert(opts->show_touches);
|
||||
assert(opts->turn_screen_off);
|
||||
|
|
Loading…
Add table
Reference in a new issue