diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 9d51fb18..97dbfe70 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -125,7 +125,7 @@ _scrcpy() { return ;; --record-format) - COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac flac' -- "$cur")) + COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac flac wav' -- "$cur")) return ;; --render-driver) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index c59ac669..4b8a7737 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -65,7 +65,7 @@ arguments=( '--push-target=[Set the target directory for pushing files to the device by drag and drop]' {-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)' + '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)' '--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]' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index cfcfb227..26f53ba4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -355,7 +355,7 @@ Inject key events for all input keys, and ignore text events. .TP .BI "\-\-record\-format " format -Force recording format (mp4, mkv, m4a, mka, opus, aac or flac). +Force recording format (mp4, mkv, m4a, mka, opus, aac, flac or wav). .TP .BI "\-\-render\-driver " name diff --git a/app/src/cli.c b/app/src/cli.c index 0482b233..19097f47 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -594,8 +594,8 @@ static const struct sc_option options[] = { .longopt_id = OPT_RECORD_FORMAT, .longopt = "record-format", .argdesc = "format", - .text = "Force recording format (mp4, mkv, m4a, mka, opus, aac or " - "flac).", + .text = "Force recording format (mp4, mkv, m4a, mka, opus, aac, flac " + "or wav).", }, { .longopt_id = OPT_RENDER_DRIVER, @@ -1630,6 +1630,9 @@ get_record_format(const char *name) { if (!strcmp(name, "flac")) { return SC_RECORD_FORMAT_FLAC; } + if (!strcmp(name, "wav")) { + return SC_RECORD_FORMAT_WAV; + } return 0; } @@ -2373,11 +2376,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } - if (opts->audio_codec == SC_CODEC_RAW) { - LOGE("Recording does not support RAW audio codec"); - return false; - } - if (opts->video && sc_record_format_is_audio_only(opts->record_format)) { LOGE("Audio container does not support video stream"); @@ -2403,6 +2401,20 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], "(try with --audio-codec=flac)"); return false; } + + if (opts->record_format == SC_RECORD_FORMAT_WAV + && opts->audio_codec != SC_CODEC_RAW) { + LOGE("Recording to WAV file requires a RAW audio stream " + "(try with --audio-codec=raw)"); + return false; + } + + if ((opts->record_format == SC_RECORD_FORMAT_MP4 || + opts->record_format == SC_RECORD_FORMAT_M4A) + && opts->audio_codec == SC_CODEC_RAW) { + LOGE("Recording to MP4 container does not support RAW audio"); + return false; + } } if (opts->audio_codec == SC_CODEC_FLAC && opts->audio_bit_rate) { diff --git a/app/src/options.h b/app/src/options.h index 91433894..c702ceeb 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -26,6 +26,7 @@ enum sc_record_format { SC_RECORD_FORMAT_OPUS, SC_RECORD_FORMAT_AAC, SC_RECORD_FORMAT_FLAC, + SC_RECORD_FORMAT_WAV, }; static inline bool @@ -34,7 +35,8 @@ sc_record_format_is_audio_only(enum sc_record_format fmt) { || fmt == SC_RECORD_FORMAT_MKA || fmt == SC_RECORD_FORMAT_OPUS || fmt == SC_RECORD_FORMAT_AAC - || fmt == SC_RECORD_FORMAT_FLAC; + || fmt == SC_RECORD_FORMAT_FLAC + || fmt == SC_RECORD_FORMAT_WAV; } enum sc_codec { diff --git a/app/src/recorder.c b/app/src/recorder.c index d13b122a..8794442b 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -71,6 +71,8 @@ sc_recorder_get_format_name(enum sc_record_format format) { return "opus"; case SC_RECORD_FORMAT_FLAC: return "flac"; + case SC_RECORD_FORMAT_WAV: + return "wav"; default: return NULL; } @@ -168,13 +170,14 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) { } static inline bool -sc_recorder_has_empty_queues(struct sc_recorder *recorder) { +sc_recorder_must_wait_for_config_packets(struct sc_recorder *recorder) { if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) { // The video queue is empty return true; } - if (recorder->audio && sc_vecdeque_is_empty(&recorder->audio_queue)) { + if (recorder->audio && recorder->audio_expects_config_packet + && sc_vecdeque_is_empty(&recorder->audio_queue)) { // The audio queue is empty (when audio is enabled) return true; } @@ -190,7 +193,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) { while (!recorder->stopped && ((recorder->video && !recorder->video_init) || (recorder->audio && !recorder->audio_init) - || sc_recorder_has_empty_queues(recorder))) { + || sc_recorder_must_wait_for_config_packets(recorder))) { sc_cond_wait(&recorder->cond, &recorder->mutex); } @@ -209,7 +212,8 @@ sc_recorder_process_header(struct sc_recorder *recorder) { } AVPacket *audio_pkt = NULL; - if (!sc_vecdeque_is_empty(&recorder->audio_queue)) { + if (recorder->audio_expects_config_packet && + !sc_vecdeque_is_empty(&recorder->audio_queue)) { assert(recorder->audio); audio_pkt = sc_vecdeque_pop(&recorder->audio_queue); } @@ -597,6 +601,10 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, recorder->audio_stream.index = stream->index; + // A config packet is provided for all supported formats except raw audio + recorder->audio_expects_config_packet = + ctx->codec_id != AV_CODEC_ID_PCM_S16LE; + recorder->audio_init = true; sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); @@ -709,6 +717,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->video_init = false; recorder->audio_init = false; + recorder->audio_expects_config_packet = false; + sc_recorder_stream_init(&recorder->video_stream); sc_recorder_stream_init(&recorder->audio_stream); diff --git a/app/src/recorder.h b/app/src/recorder.h index 47fd3f21..16327584 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -50,6 +50,8 @@ struct sc_recorder { bool video_init; bool audio_init; + bool audio_expects_config_packet; + struct sc_recorder_stream video_stream; struct sc_recorder_stream audio_stream; diff --git a/doc/recording.md b/doc/recording.md index 466cf542..c1a8445e 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -19,6 +19,7 @@ To record only the audio: scrcpy --no-video --record=file.opus scrcpy --no-video --audio-codec=aac --record=file.aac scrcpy --no-video --audio-codec=flac --record=file.flac +scrcpy --no-video --audio-codec=raw --record=file.wav # .m4a/.mp4 and .mka/.mkv are also supported for opus, aac and flac ``` @@ -37,6 +38,7 @@ client side. Several formats (containers) are supported: - Matroska (`.mkv`, `.mka`) - OPUS (`.opus`) - FLAC (`.flac`) + - WAV (`.wav`) The container is automatically selected based on the filename.