Skip to content

Commit a4cbc28

Browse files
megapro17rom1v
andcommitted
Add support for FLAC audio codec
PR #4410 <##4410> Co-authored-by: Romain Vimont <[email protected]> Signed-off-by: Romain Vimont <[email protected]>
1 parent 81d494c commit a4cbc28

File tree

12 files changed

+103
-16
lines changed

12 files changed

+103
-16
lines changed

app/data/bash-completion/scrcpy

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ _scrcpy() {
9797
return
9898
;;
9999
--audio-codec)
100-
COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur"))
100+
COMPREPLY=($(compgen -W 'opus aac flac raw' -- "$cur"))
101101
return
102102
;;
103103
--video-source)
@@ -125,7 +125,7 @@ _scrcpy() {
125125
return
126126
;;
127127
--record-format)
128-
COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac' -- "$cur"))
128+
COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac flac' -- "$cur"))
129129
return
130130
;;
131131
--render-driver)

app/data/zsh-completion/_scrcpy

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ arguments=(
1111
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
1212
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
1313
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
14-
'--audio-codec=[Select the audio codec]:codec:(opus aac raw)'
14+
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
1515
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
1616
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
1717
'--audio-source=[Select the audio source]:source:(output mic)'
@@ -65,7 +65,7 @@ arguments=(
6565
'--push-target=[Set the target directory for pushing files to the device by drag and drop]'
6666
{-r,--record=}'[Record screen to file]:record file:_files'
6767
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
68-
'--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac)'
68+
'--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac)'
6969
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
7070
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
7171
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'

app/scrcpy.1

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Default is 50.
3535

3636
.TP
3737
.BI "\-\-audio\-codec " name
38-
Select an audio codec (opus, aac or raw).
38+
Select an audio codec (opus, aac, flac or raw).
3939

4040
Default is opus.
4141

@@ -355,7 +355,7 @@ Inject key events for all input keys, and ignore text events.
355355

356356
.TP
357357
.BI "\-\-record\-format " format
358-
Force recording format (mp4, mkv, m4a, mka, opus or aac).
358+
Force recording format (mp4, mkv, m4a, mka, opus, aac or flac).
359359

360360
.TP
361361
.BI "\-\-render\-driver " name

app/src/cli.c

+21-3
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ static const struct sc_option options[] = {
152152
.longopt_id = OPT_AUDIO_CODEC,
153153
.longopt = "audio-codec",
154154
.argdesc = "name",
155-
.text = "Select an audio codec (opus, aac or raw).\n"
155+
.text = "Select an audio codec (opus, aac, flac or raw).\n"
156156
"Default is opus.",
157157
},
158158
{
@@ -594,7 +594,8 @@ static const struct sc_option options[] = {
594594
.longopt_id = OPT_RECORD_FORMAT,
595595
.longopt = "record-format",
596596
.argdesc = "format",
597-
.text = "Force recording format (mp4, mkv, m4a, mka, opus or aac).",
597+
.text = "Force recording format (mp4, mkv, m4a, mka, opus, aac or "
598+
"flac).",
598599
},
599600
{
600601
.longopt_id = OPT_RENDER_DRIVER,
@@ -1626,6 +1627,9 @@ get_record_format(const char *name) {
16261627
if (!strcmp(name, "aac")) {
16271628
return SC_RECORD_FORMAT_AAC;
16281629
}
1630+
if (!strcmp(name, "flac")) {
1631+
return SC_RECORD_FORMAT_FLAC;
1632+
}
16291633
return 0;
16301634
}
16311635

@@ -1695,11 +1699,15 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) {
16951699
*codec = SC_CODEC_AAC;
16961700
return true;
16971701
}
1702+
if (!strcmp(optarg, "flac")) {
1703+
*codec = SC_CODEC_FLAC;
1704+
return true;
1705+
}
16981706
if (!strcmp(optarg, "raw")) {
16991707
*codec = SC_CODEC_RAW;
17001708
return true;
17011709
}
1702-
LOGE("Unsupported audio codec: %s (expected opus, aac or raw)", optarg);
1710+
LOGE("Unsupported audio codec: %s (expected opus, aac, flac or raw)", optarg);
17031711
return false;
17041712
}
17051713

@@ -2376,6 +2384,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
23762384
"(try with --audio-codec=aac)");
23772385
return false;
23782386
}
2387+
if (opts->record_format == SC_RECORD_FORMAT_FLAC
2388+
&& opts->audio_codec != SC_CODEC_FLAC) {
2389+
LOGE("Recording to FLAC file requires a FLAC audio stream "
2390+
"(try with --audio-codec=flac)");
2391+
return false;
2392+
}
2393+
}
2394+
2395+
if (opts->audio_codec == SC_CODEC_FLAC && opts->audio_bit_rate) {
2396+
LOGW("--audio-bit-rate is ignored for FLAC audio codec");
23792397
}
23802398

23812399
if (opts->audio_codec == SC_CODEC_RAW) {

app/src/demuxer.c

+9-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) {
2525
#define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII
2626
#define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII
2727
#define SC_CODEC_ID_OPUS UINT32_C(0x6f707573) // "opus" in ASCII
28-
#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac in ASCII"
28+
#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac" in ASCII
29+
#define SC_CODEC_ID_FLAC UINT32_C(0x666c6163) // "flac" in ASCII
2930
#define SC_CODEC_ID_RAW UINT32_C(0x00726177) // "raw" in ASCII
3031
switch (codec_id) {
3132
case SC_CODEC_ID_H264:
@@ -43,6 +44,8 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) {
4344
return AV_CODEC_ID_OPUS;
4445
case SC_CODEC_ID_AAC:
4546
return AV_CODEC_ID_AAC;
47+
case SC_CODEC_ID_FLAC:
48+
return AV_CODEC_ID_FLAC;
4649
case SC_CODEC_ID_RAW:
4750
return AV_CODEC_ID_PCM_S16LE;
4851
default:
@@ -207,6 +210,11 @@ run_demuxer(void *data) {
207210
codec_ctx->channels = 2;
208211
#endif
209212
codec_ctx->sample_rate = 48000;
213+
214+
if (raw_codec_id == SC_CODEC_ID_FLAC) {
215+
// The sample_fmt is not set by the FLAC decoder
216+
codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;
217+
}
210218
}
211219

212220
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {

app/src/options.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ enum sc_record_format {
2525
SC_RECORD_FORMAT_MKA,
2626
SC_RECORD_FORMAT_OPUS,
2727
SC_RECORD_FORMAT_AAC,
28+
SC_RECORD_FORMAT_FLAC,
2829
};
2930

3031
static inline bool
3132
sc_record_format_is_audio_only(enum sc_record_format fmt) {
3233
return fmt == SC_RECORD_FORMAT_M4A
3334
|| fmt == SC_RECORD_FORMAT_MKA
3435
|| fmt == SC_RECORD_FORMAT_OPUS
35-
|| fmt == SC_RECORD_FORMAT_AAC;
36+
|| fmt == SC_RECORD_FORMAT_AAC
37+
|| fmt == SC_RECORD_FORMAT_FLAC;
3638
}
3739

3840
enum sc_codec {
@@ -41,6 +43,7 @@ enum sc_codec {
4143
SC_CODEC_AV1,
4244
SC_CODEC_OPUS,
4345
SC_CODEC_AAC,
46+
SC_CODEC_FLAC,
4447
SC_CODEC_RAW,
4548
};
4649

app/src/recorder.c

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ sc_recorder_get_format_name(enum sc_record_format format) {
6969
return "matroska";
7070
case SC_RECORD_FORMAT_OPUS:
7171
return "opus";
72+
case SC_RECORD_FORMAT_FLAC:
73+
return "flac";
7274
default:
7375
return NULL;
7476
}

app/src/server.c

+2
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ sc_server_get_codec_name(enum sc_codec codec) {
178178
return "opus";
179179
case SC_CODEC_AAC:
180180
return "aac";
181+
case SC_CODEC_FLAC:
182+
return "flac";
181183
case SC_CODEC_RAW:
182184
return "raw";
183185
default:

doc/audio.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,13 @@ scrcpy --audio-source=mic --no-video --no-playback --record=file.opus
6262

6363
## Codec
6464

65-
The audio codec can be selected. The possible values are `opus` (default), `aac`
66-
and `raw` (uncompressed PCM 16-bit LE):
65+
The audio codec can be selected. The possible values are `opus` (default),
66+
`aac`, `flac` and `raw` (uncompressed PCM 16-bit LE):
6767

6868
```bash
6969
scrcpy --audio-codec=opus # default
7070
scrcpy --audio-codec=aac
71+
scrcpy --audio-codec=flac
7172
scrcpy --audio-codec=raw
7273
```
7374

@@ -80,7 +81,14 @@ then your device has no Opus encoder: try `scrcpy --audio-codec=aac`.
8081
For advanced usage, to pass arbitrary parameters to the [`MediaFormat`],
8182
check `--audio-codec-options` in the manpage or in `scrcpy --help`.
8283

84+
For example, to change the [FLAC compression level]:
85+
86+
```bash
87+
scrcpy --audio-codec=flac --audio-codec-options=flac-compression-level=8
88+
```
89+
8390
[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat
91+
[FLAC compression level]: https://developer.android.com/reference/android/media/MediaFormat#KEY_FLAC_COMPRESSION_LEVEL
8492

8593

8694
## Encoder

doc/recording.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ To record only the audio:
1818
```bash
1919
scrcpy --no-video --record=file.opus
2020
scrcpy --no-video --audio-codec=aac --record=file.aac
21-
# .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac
21+
scrcpy --no-video --audio-codec=flac --record=file.flac
22+
# .m4a/.mp4 and .mka/.mkv are also supported for opus, aac and flac
2223
```
2324

2425
Timestamps are captured on the device, so [packet delay variation] does not
@@ -35,6 +36,7 @@ client side. Several formats (containers) are supported:
3536
- MP4 (`.mp4`, `.m4a`, `.aac`)
3637
- Matroska (`.mkv`, `.mka`)
3738
- OPUS (`.opus`)
39+
- FLAC (`.flac`)
3840

3941
The container is automatically selected based on the filename.
4042

server/src/main/java/com/genymobile/scrcpy/AudioCodec.java

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
public enum AudioCodec implements Codec {
66
OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS),
77
AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC),
8+
FLAC(0x66_6c_61_63, "flac", MediaFormat.MIMETYPE_AUDIO_FLAC),
89
RAW(0x00_72_61_77, "raw", MediaFormat.MIMETYPE_AUDIO_RAW);
910

1011
private final int id; // 4-byte ASCII representation of the name

server/src/main/java/com/genymobile/scrcpy/Streamer.java

+45-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.io.FileDescriptor;
66
import java.io.IOException;
77
import java.nio.ByteBuffer;
8+
import java.nio.ByteOrder;
89
import java.util.Arrays;
910

1011
public final class Streamer {
@@ -29,6 +30,7 @@ public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecMeta, boolean s
2930
public Codec getCodec() {
3031
return codec;
3132
}
33+
3234
public void writeAudioHeader() throws IOException {
3335
if (sendCodecMeta) {
3436
ByteBuffer buffer = ByteBuffer.allocate(4);
@@ -61,8 +63,12 @@ public void writeDisableStream(boolean error) throws IOException {
6163
}
6264

6365
public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException {
64-
if (config && codec == AudioCodec.OPUS) {
65-
fixOpusConfigPacket(buffer);
66+
if (config) {
67+
if (codec == AudioCodec.OPUS) {
68+
fixOpusConfigPacket(buffer);
69+
} else if (codec == AudioCodec.FLAC) {
70+
fixFlacConfigPacket(buffer);
71+
}
6672
}
6773

6874
if (sendFrameMeta) {
@@ -140,4 +146,41 @@ private static void fixOpusConfigPacket(ByteBuffer buffer) throws IOException {
140146
// Set the buffer to point to the OPUS header slice
141147
buffer.limit(buffer.position() + size);
142148
}
149+
150+
private static void fixFlacConfigPacket(ByteBuffer buffer) throws IOException {
151+
// 00000000 66 4c 61 43 00 00 00 22 |fLaC..." |
152+
// -------------- BELOW IS THE PART WE MUST PUT AS EXTRADATA -------------------
153+
// 00000000 10 00 10 00 00 00 00 00 | ........|
154+
// 00000010 00 00 0b b8 02 f0 00 00 00 00 00 00 00 00 00 00 |................|
155+
// 00000020 00 00 00 00 00 00 00 00 00 00 |.......... |
156+
// ------------------------------------------------------------------------------
157+
// 00000020 84 00 00 28 20 00 | ...( .|
158+
// 00000030 00 00 72 65 66 65 72 65 6e 63 65 20 6c 69 62 46 |..reference libF|
159+
// 00000040 4c 41 43 20 31 2e 33 2e 32 20 32 30 32 32 31 30 |LAC 1.3.2 202210|
160+
// 00000050 32 32 00 00 00 00 |22....|
161+
//
162+
// <https://developer.android.com/reference/android/media/MediaCodec#CSD>
163+
164+
if (buffer.remaining() < 8) {
165+
throw new IOException("Not enough data in FLAC config packet");
166+
}
167+
168+
final byte[] flacHeaderId = {'f', 'L', 'a', 'C'};
169+
byte[] idBuffer = new byte[4];
170+
buffer.get(idBuffer);
171+
if (!Arrays.equals(idBuffer, flacHeaderId)) {
172+
throw new IOException("FLAC header not found");
173+
}
174+
175+
// The size is in big-endian
176+
buffer.order(ByteOrder.BIG_ENDIAN);
177+
178+
int size = buffer.getInt();
179+
if (buffer.remaining() < size) {
180+
throw new IOException("Not enough data in FLAC header (invalid size: " + size + ")");
181+
}
182+
183+
// Set the buffer to point to the FLAC header slice
184+
buffer.limit(buffer.position() + size);
185+
}
143186
}

0 commit comments

Comments
 (0)