diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 0ecace96..f08df996 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -62,6 +62,7 @@ _scrcpy() { -r --record= --raw-key-events --record-format= + --record-orientation= --render-driver= --require-audio --rotation= @@ -117,6 +118,10 @@ _scrcpy() { COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) return ;; + --record-orientation) + COMPREPLY=($(compgen -> '0 90 180 270' -- "$cur")) + return + ;; --lock-video-orientation) COMPREPLY=($(compgen -W 'unlocked initial 0 90 180 270' -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 3f65cb4e..0e39f96b 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -67,6 +67,7 @@ arguments=( {-r,--record=}'[Record screen to file]:record file:_files' '--raw-key-events[Inject key events for all input keys, and ignore text events]' '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)' + '--record-orientation=[Set the record orientation]:orientation values:(0 90 180 270)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 266ba1f4..1a9386ee 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -367,6 +367,14 @@ Inject key events for all input keys, and ignore text events. .BI "\-\-record\-format " format Force recording format (mp4, mkv, m4a, mka, opus, aac, flac or wav). +.TP +.BI "\-\-record\-orientation " value +Set the record orientation. + +Possible values are 0, 90, 180 and 270. The number represents the clockwise rotation in degrees. + +Default is 0. + .TP .BI "\-\-render\-driver " name Request SDL to use the given render driver (this is just a hint). diff --git a/app/src/cli.c b/app/src/cli.c index 37c2274c..fb0f43d5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -91,6 +91,7 @@ enum { OPT_CAMERA_FPS, OPT_CAMERA_HIGH_SPEED, OPT_DISPLAY_ORIENTATION, + OPT_RECORD_ORIENTATION, }; struct sc_option { @@ -609,6 +610,15 @@ static const struct sc_option options[] = { .text = "Force recording format (mp4, mkv, m4a, mka, opus, aac, flac " "or wav).", }, + { + .longopt_id = OPT_RECORD_ORIENTATION, + .longopt = "record-orientation", + .argdesc = "value", + .text = "Set the record orientation.\n" + "Possible values are 0, 90, 180 and 270. The number represents " + "the clockwise rotation in degrees.\n" + "Default is 0.", + }, { .longopt_id = OPT_RENDER_DRIVER, .longopt = "render-driver", @@ -2131,6 +2141,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_RECORD_ORIENTATION: + if (!parse_orientation(optarg, &opts->record_orientation)) { + return false; + } + break; case OPT_RENDER_DRIVER: opts->render_driver = optarg; break; @@ -2497,6 +2512,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } + if (opts->record_orientation != SC_ORIENTATION_0) { + if (sc_orientation_is_mirror(opts->record_orientation)) { + LOGE("Record orientation only supports rotation, not " + "flipping: %s", + sc_orientation_get_name(opts->record_orientation)); + return false; + } + } + if (opts->video && sc_record_format_is_audio_only(opts->record_format)) { LOGE("Audio container does not support video stream"); diff --git a/app/src/compat.h b/app/src/compat.h index e80a9dd2..fd610c02 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -3,7 +3,9 @@ #include "config.h" +#include #include +#include #include #ifndef __WIN32 @@ -50,6 +52,15 @@ # define SCRCPY_LAVU_HAS_CHLAYOUT #endif +// In ffmpeg/doc/APIchanges: +// 2023-10-06 - 5432d2aacad - lavc 60.15.100 - avformat.h +// Deprecate AVFormatContext.{nb_,}side_data, av_stream_add_side_data(), +// av_stream_new_side_data(), and av_stream_get_side_data(). Side data fields +// from AVFormatContext.codecpar should be used from now on. +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 15, 100) +# define SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA +#endif + #if SDL_VERSION_ATLEAST(2, 0, 6) // # define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS diff --git a/app/src/options.c b/app/src/options.c index 1454147a..a13df585 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -40,6 +40,7 @@ const struct scrcpy_options scrcpy_options_default = { .max_fps = 0, .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, .display_orientation = SC_ORIENTATION_0, + .record_orientation = SC_ORIENTATION_0, .window_x = SC_WINDOW_POSITION_UNDEFINED, .window_y = SC_WINDOW_POSITION_UNDEFINED, .window_width = 0, diff --git a/app/src/options.h b/app/src/options.h index 4fb45840..11e64fa1 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -220,6 +220,7 @@ struct scrcpy_options { uint16_t max_fps; enum sc_lock_video_orientation lock_video_orientation; enum sc_orientation display_orientation; + enum sc_orientation record_orientation; int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; diff --git a/app/src/recorder.c b/app/src/recorder.c index c9d5f131..9e0b3395 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "util/log.h" #include "util/str.h" @@ -493,6 +494,42 @@ run_recorder(void *data) { return 0; } +static bool +sc_recorder_set_orientation(AVStream *stream, enum sc_orientation orientation) { + assert(!sc_orientation_is_mirror(orientation)); + + uint8_t *raw_data; +#ifdef SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA + AVPacketSideData *sd = + av_packet_side_data_new(&stream->codecpar->coded_side_data, + &stream->codecpar->nb_coded_side_data, + AV_PKT_DATA_DISPLAYMATRIX, + sizeof(int32_t) * 9, 0); + if (!sd) { + LOG_OOM(); + return false; + } + + raw_data = sd->data; +#else + raw_data = av_stream_new_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX, + sizeof(int32_t) * 9); + if (!raw_data) { + LOG_OOM(); + return false; + } +#endif + + int32_t *matrix = (int32_t *) raw_data; + + unsigned rotation = orientation; + unsigned angle = rotation * 90; + + av_display_rotation_set(matrix, angle); + + return true; +} + static bool sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) { @@ -520,6 +557,16 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, recorder->video_stream.index = stream->index; + if (recorder->orientation != SC_ORIENTATION_0) { + if (!sc_recorder_set_orientation(stream, recorder->orientation)) { + sc_mutex_unlock(&recorder->mutex); + return false; + } + + LOGI("Record orientation set to %s", + sc_orientation_get_name(recorder->orientation)); + } + recorder->video_init = true; sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); @@ -689,7 +736,10 @@ sc_recorder_stream_init(struct sc_recorder_stream *stream) { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, bool video, bool audio, + enum sc_orientation orientation, const struct sc_recorder_callbacks *cbs, void *cbs_userdata) { + assert(!sc_orientation_is_mirror(orientation)); + recorder->filename = strdup(filename); if (!recorder->filename) { LOG_OOM(); @@ -710,6 +760,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->video = video; recorder->audio = audio; + recorder->orientation = orientation; + sc_vecdeque_init(&recorder->video_queue); sc_vecdeque_init(&recorder->audio_queue); recorder->stopped = false; diff --git a/app/src/recorder.h b/app/src/recorder.h index 16327584..d096e79a 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -34,6 +34,8 @@ struct sc_recorder { bool audio; bool video; + enum sc_orientation orientation; + char *filename; enum sc_record_format format; AVFormatContext *ctx; @@ -67,6 +69,7 @@ struct sc_recorder_callbacks { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, bool video, bool audio, + enum sc_orientation orientation, const struct sc_recorder_callbacks *cbs, void *cbs_userdata); bool diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9bbe14b8..d62a5f52 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -507,7 +507,8 @@ scrcpy(struct scrcpy_options *options) { }; if (!sc_recorder_init(&s->recorder, options->record_filename, options->record_format, options->video, - options->audio, &recorder_cbs, NULL)) { + options->audio, options->record_orientation, + &recorder_cbs, NULL)) { goto end; } recorder_initialized = true;