From 0ed23739529b3651c640a75a065d081ac9f4d588 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 9 Feb 2019 15:20:07 +0100 Subject: [PATCH] Support recording to MKV Implement recording to Matroska files. The format to use is determined by the option -F/--record-format if set, or by the file extension (".mp4" or ".mkv"). --- README.md | 2 +- app/src/main.c | 62 +++++++++++++++++++++++++++++++++++++++++++++- app/src/recorder.c | 29 ++++++++++++++++------ app/src/recorder.h | 11 +++++++- app/src/scrcpy.c | 5 +++- app/src/scrcpy.h | 2 ++ 6 files changed, 100 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5c6960d0..40569d71 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ It is possible to record the screen while mirroring: ```bash scrcpy --record file.mp4 -scrcpy -r file.mp4 +scrcpy -r file.mkv ``` "Skipped frames" are recorded, even if they are not displayed in real time (for diff --git a/app/src/main.c b/app/src/main.c index 67ae8b4d..412dd40a 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -7,11 +7,13 @@ #include "config.h" #include "log.h" +#include "recorder.h" struct args { const char *serial; const char *crop; const char *record_filename; + enum recorder_format record_format; SDL_bool fullscreen; SDL_bool help; SDL_bool version; @@ -42,6 +44,9 @@ static void usage(const char *arg0) { " -f, --fullscreen\n" " Start in fullscreen.\n" "\n" + " -F, --record-format\n" + " Force recording format (either mp4 or mkv).\n" + "\n" " -h, --help\n" " Print this help.\n" "\n" @@ -57,6 +62,8 @@ static void usage(const char *arg0) { "\n" " -r, --record file.mp4\n" " Record screen to file.\n" + " The format is determined by the -F/--record-format option if\n" + " set, or by the file extension (.mp4 or .mkv).\n" "\n" " -s, --serial\n" " The device serial number. Mandatory only if several devices\n" @@ -208,6 +215,36 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) { return SDL_TRUE; } +static SDL_bool +parse_record_format(const char *optarg, enum recorder_format *format) { + if (!strcmp(optarg, "mp4")) { + *format = RECORDER_FORMAT_MP4; + return SDL_TRUE; + } + if (!strcmp(optarg, "mkv")) { + *format = RECORDER_FORMAT_MKV; + return SDL_TRUE; + } + LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg); + return SDL_FALSE; +} + +static enum recorder_format +guess_record_format(const char *filename) { + size_t len = strlen(filename); + if (len < 4) { + return 0; + } + const char *ext = &filename[len - 4]; + if (!strcmp(ext, ".mp4")) { + return RECORDER_FORMAT_MP4; + } + if (!strcmp(ext, ".mkv")) { + return RECORDER_FORMAT_MKV; + } + return 0; +} + static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { static const struct option long_options[] = { {"always-on-top", no_argument, NULL, 'T'}, @@ -218,13 +255,14 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { {"max-size", required_argument, NULL, 'm'}, {"port", required_argument, NULL, 'p'}, {"record", required_argument, NULL, 'r'}, + {"record-format", required_argument, NULL, 'f'}, {"serial", required_argument, NULL, 's'}, {"show-touches", no_argument, NULL, 't'}, {"version", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0 }, }; int c; - while ((c = getopt_long(argc, argv, "b:c:fhm:p:r:s:tTv", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "b:c:fF:hm:p:r:s:tTv", long_options, NULL)) != -1) { switch (c) { case 'b': if (!parse_bit_rate(optarg, &args->bit_rate)) { @@ -237,6 +275,11 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { case 'f': args->fullscreen = SDL_TRUE; break; + case 'F': + if (!parse_record_format(optarg, &args->record_format)) { + return SDL_FALSE; + } + break; case 'h': args->help = SDL_TRUE; break; @@ -276,6 +319,21 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { LOGE("Unexpected additional argument: %s", argv[index]); return SDL_FALSE; } + + if (args->record_format && !args->record_filename) { + LOGE("Record format specified without recording"); + return SDL_FALSE; + } + + if (args->record_filename && !args->record_format) { + args->record_format = guess_record_format(args->record_filename); + if (!args->record_format) { + LOGE("No format specified for \"%s\" (try with -F mkv)", + args->record_filename); + return SDL_FALSE; + } + } + return SDL_TRUE; } @@ -290,6 +348,7 @@ int main(int argc, char *argv[]) { .serial = NULL, .crop = NULL, .record_filename = NULL, + .record_format = 0, .help = SDL_FALSE, .version = SDL_FALSE, .show_touches = SDL_FALSE, @@ -329,6 +388,7 @@ int main(int argc, char *argv[]) { .crop = args.crop, .port = args.port, .record_filename = args.record_filename, + .record_format = args.record_format, .max_size = args.max_size, .bit_rate = args.bit_rate, .show_touches = args.show_touches, diff --git a/app/src/recorder.c b/app/src/recorder.c index e3ed9d4f..24e02ff8 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -1,6 +1,7 @@ #include "recorder.h" #include +#include #include "config.h" #include "log.h" @@ -17,7 +18,7 @@ static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us -static const AVOutputFormat *find_mp4_muxer(void) { +static const AVOutputFormat *find_muxer(const char *name) { #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100) void *opaque = NULL; #endif @@ -29,11 +30,13 @@ static const AVOutputFormat *find_mp4_muxer(void) { oformat = av_oformat_next(oformat); #endif // until null or with name "mp4" - } while (oformat && strcmp(oformat->name, "mp4")); + } while (oformat && strcmp(oformat->name, name)); return oformat; } -SDL_bool recorder_init(struct recorder *recorder, const char *filename, +SDL_bool recorder_init(struct recorder *recorder, + const char *filename, + enum recorder_format format, struct size declared_frame_size) { recorder->filename = SDL_strdup(filename); if (!recorder->filename) { @@ -41,6 +44,7 @@ SDL_bool recorder_init(struct recorder *recorder, const char *filename, return SDL_FALSE; } + recorder->format = format; recorder->declared_frame_size = declared_frame_size; recorder->header_written = SDL_FALSE; @@ -51,10 +55,21 @@ void recorder_destroy(struct recorder *recorder) { SDL_free(recorder->filename); } +static const char * +recorder_get_format_name(enum recorder_format format) { + switch (format) { + case RECORDER_FORMAT_MP4: return "mp4"; + case RECORDER_FORMAT_MKV: return "matroska"; + default: return NULL; + } +} + SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) { - const AVOutputFormat *mp4 = find_mp4_muxer(); - if (!mp4) { - LOGE("Could not find mp4 muxer"); + const char *format_name = recorder_get_format_name(recorder->format); + SDL_assert(format_name); + const AVOutputFormat *format = find_muxer(format_name); + if (!format) { + LOGE("Could not find muxer"); return SDL_FALSE; } @@ -68,7 +83,7 @@ SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) { // returns (on purpose) a pointer-to-const, but AVFormatContext.oformat // still expects a pointer-to-non-const (it has not be updated accordingly) // - recorder->ctx->oformat = (AVOutputFormat *) mp4; + recorder->ctx->oformat = (AVOutputFormat *) format; AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); if (!ostream) { diff --git a/app/src/recorder.h b/app/src/recorder.h index 4959c7ab..203c51b4 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -6,15 +6,24 @@ #include "common.h" +enum recorder_format { + RECORDER_FORMAT_MP4 = 1, + RECORDER_FORMAT_MKV, +}; + struct recorder { char *filename; + enum recorder_format format; AVFormatContext *ctx; struct size declared_frame_size; SDL_bool header_written; }; -SDL_bool recorder_init(struct recorder *recoder, const char *filename, +SDL_bool recorder_init(struct recorder *recoder, + const char *filename, + enum recorder_format format, struct size declared_frame_size); + void recorder_destroy(struct recorder *recorder); SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6c986653..9a1c29d5 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -229,7 +229,10 @@ SDL_bool scrcpy(const struct scrcpy_options *options) { struct recorder *rec = NULL; if (options->record_filename) { - if (!recorder_init(&recorder, options->record_filename, frame_size)) { + if (!recorder_init(&recorder, + options->record_filename, + options->record_format, + frame_size)) { ret = SDL_FALSE; server_stop(&server); goto finally_destroy_file_handler; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 1906467d..aba1f336 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -2,11 +2,13 @@ #define SCRCPY_H #include +#include struct scrcpy_options { const char *serial; const char *crop; const char *record_filename; + enum recorder_format record_format; Uint16 port; Uint16 max_size; Uint32 bit_rate;