Add support for RAW audio (WAV) recording

RAW audio forwarding was supported but not for recording.

Add support for recording a raw audio stream to a `.wav` file (and
`.mkv`).
This commit is contained in:
Romain Vimont 2023-11-13 09:35:18 +01:00
parent 2722865ce6
commit ef9dc85da4
8 changed files with 43 additions and 15 deletions

View file

@ -125,7 +125,7 @@ _scrcpy() {
return return
;; ;;
--record-format) --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 return
;; ;;
--render-driver) --render-driver)

View file

@ -65,7 +65,7 @@ arguments=(
'--push-target=[Set the target directory for pushing files to the device by drag and drop]' '--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' {-r,--record=}'[Record screen to file]:record file:_files'
'--raw-key-events[Inject key events for all input keys, and ignore text events]' '--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)' '--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]' '--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)' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'

View file

@ -355,7 +355,7 @@ Inject key events for all input keys, and ignore text events.
.TP .TP
.BI "\-\-record\-format " format .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 .TP
.BI "\-\-render\-driver " name .BI "\-\-render\-driver " name

View file

@ -594,8 +594,8 @@ static const struct sc_option options[] = {
.longopt_id = OPT_RECORD_FORMAT, .longopt_id = OPT_RECORD_FORMAT,
.longopt = "record-format", .longopt = "record-format",
.argdesc = "format", .argdesc = "format",
.text = "Force recording format (mp4, mkv, m4a, mka, opus, aac or " .text = "Force recording format (mp4, mkv, m4a, mka, opus, aac, flac "
"flac).", "or wav).",
}, },
{ {
.longopt_id = OPT_RENDER_DRIVER, .longopt_id = OPT_RENDER_DRIVER,
@ -1630,6 +1630,9 @@ get_record_format(const char *name) {
if (!strcmp(name, "flac")) { if (!strcmp(name, "flac")) {
return SC_RECORD_FORMAT_FLAC; return SC_RECORD_FORMAT_FLAC;
} }
if (!strcmp(name, "wav")) {
return SC_RECORD_FORMAT_WAV;
}
return 0; 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 if (opts->video
&& sc_record_format_is_audio_only(opts->record_format)) { && sc_record_format_is_audio_only(opts->record_format)) {
LOGE("Audio container does not support video stream"); 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)"); "(try with --audio-codec=flac)");
return false; 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) { if (opts->audio_codec == SC_CODEC_FLAC && opts->audio_bit_rate) {

View file

@ -26,6 +26,7 @@ enum sc_record_format {
SC_RECORD_FORMAT_OPUS, SC_RECORD_FORMAT_OPUS,
SC_RECORD_FORMAT_AAC, SC_RECORD_FORMAT_AAC,
SC_RECORD_FORMAT_FLAC, SC_RECORD_FORMAT_FLAC,
SC_RECORD_FORMAT_WAV,
}; };
static inline bool 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_MKA
|| fmt == SC_RECORD_FORMAT_OPUS || fmt == SC_RECORD_FORMAT_OPUS
|| fmt == SC_RECORD_FORMAT_AAC || fmt == SC_RECORD_FORMAT_AAC
|| fmt == SC_RECORD_FORMAT_FLAC; || fmt == SC_RECORD_FORMAT_FLAC
|| fmt == SC_RECORD_FORMAT_WAV;
} }
enum sc_codec { enum sc_codec {

View file

@ -71,6 +71,8 @@ sc_recorder_get_format_name(enum sc_record_format format) {
return "opus"; return "opus";
case SC_RECORD_FORMAT_FLAC: case SC_RECORD_FORMAT_FLAC:
return "flac"; return "flac";
case SC_RECORD_FORMAT_WAV:
return "wav";
default: default:
return NULL; return NULL;
} }
@ -168,13 +170,14 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) {
} }
static inline bool 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)) { if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) {
// The video queue is empty // The video queue is empty
return true; 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) // The audio queue is empty (when audio is enabled)
return true; return true;
} }
@ -190,7 +193,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
while (!recorder->stopped && while (!recorder->stopped &&
((recorder->video && !recorder->video_init) ((recorder->video && !recorder->video_init)
|| (recorder->audio && !recorder->audio_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); sc_cond_wait(&recorder->cond, &recorder->mutex);
} }
@ -209,7 +212,8 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
} }
AVPacket *audio_pkt = NULL; 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); assert(recorder->audio);
audio_pkt = sc_vecdeque_pop(&recorder->audio_queue); 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; recorder->audio_stream.index = stream->index;
// A config packet is provided for all formats supported except raw audio
recorder->audio_expects_config_packet =
ctx->codec_id != AV_CODEC_ID_PCM_S16LE;
recorder->audio_init = true; recorder->audio_init = true;
sc_cond_signal(&recorder->cond); sc_cond_signal(&recorder->cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
@ -709,6 +717,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
recorder->video_init = false; recorder->video_init = false;
recorder->audio_init = false; recorder->audio_init = false;
recorder->audio_expects_config_packet = false;
sc_recorder_stream_init(&recorder->video_stream); sc_recorder_stream_init(&recorder->video_stream);
sc_recorder_stream_init(&recorder->audio_stream); sc_recorder_stream_init(&recorder->audio_stream);

View file

@ -50,6 +50,8 @@ struct sc_recorder {
bool video_init; bool video_init;
bool audio_init; bool audio_init;
bool audio_expects_config_packet;
struct sc_recorder_stream video_stream; struct sc_recorder_stream video_stream;
struct sc_recorder_stream audio_stream; struct sc_recorder_stream audio_stream;

View file

@ -19,6 +19,7 @@ To record only the audio:
scrcpy --no-video --record=file.opus scrcpy --no-video --record=file.opus
scrcpy --no-video --audio-codec=aac --record=file.aac scrcpy --no-video --audio-codec=aac --record=file.aac
scrcpy --no-video --audio-codec=flac --record=file.flac 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 # .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`) - Matroska (`.mkv`, `.mka`)
- OPUS (`.opus`) - OPUS (`.opus`)
- FLAC (`.flac`) - FLAC (`.flac`)
- WAV (`.wav`)
The container is automatically selected based on the filename. The container is automatically selected based on the filename.