Skip to content

Forward audio #3757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 118 commits into from
Mar 10, 2023
Merged

Forward audio #3757

merged 118 commits into from
Mar 10, 2023

Conversation

rom1v
Copy link
Collaborator

@rom1v rom1v commented Feb 26, 2023

Last month, @yume-chan submitted a working PoC to capture audio on Android. Big thanks for this discovery!

Since then I have worked to properly integrate audio support in scrcpy (capture, encode, transmit, record, decode, resample, play…), and my branch reached a working state (at least on my machine) 🎉

By default, audio is enabled (if supported):

  • On Android >= 12, it works out-of-the-box;
  • On Android 11, the device screen must be unlocked (because of a dirty workaround to make the system think the app is foreground, again thank you to @yume-chan for the tip);
  • On Android <= 10, it does not work, audio is automatically disabled.

If audio capture fails, then mirroring continues with video only (since audio is enabled by default, it is not acceptable to make scrcpy fail if audio is not available), unless --require-audio is set.

To disable audio:

scrcpy --no-audio

If audio is enabled, it is also recorded when recording is enabled:

scrcpy --record=file.mp4
scrcpy --record=file.mkv

To play audio in real-time, the audio player implements drift compensation (it attempts to keep audio sample buffering to a certain level, quite low to get low-latency, but not too low to avoid underflow). It is experimental (I just tinkered with this thing today) (I've made a lot of improvements since the initial MR, and it should work pretty fine), parameters and behavior are hardcoded, some configuration variables might need to be exposed in the future (or not).

If you record, the timestamps are computed on the device, so they are not impacted by any jitter/underflow (this is the same as for the video), so the recording is always clean (if you use --record of course, not if you capture your audio output on the computer).

Like the video, it works over TCP/IP (typically Wifi).

The following options have been renamed:

  • --codec -> --video-codec
  • --bit-rate -> --video-bitrate
  • --codec-options -> --video-codec-options
  • --encoder -> --video-encoder

And similar options for audio have been added:

  • --audio-codec
  • --audio-bit-rate
  • --audio-codec-options
  • --audio-encoder

Three audio formats are supported:

  • Opus: scrcpy --audio-codec=opus
  • AAC: scrcpy --audio-codece=aac
  • Raw (uncompressed) PCM 16-bit LE: scrcpy --audio-codec=raw (more bandwidth, typically just for testing)

It is possible to list the encoders available on the device (video and audio):

scrcpy --list-encoders

If you need a specific encoder, use --audio-encoder:

scrcpy --audio-codec=aac --audio-encoder='OMX.google.aac.encoder'

When recording, raw audio is not possible (it automatically switches to OPUS). In the future, I might add audio encoding on the client-side specifically for recording, but I don't plan to make it for this audio feature.

I also implemented --audio-buffer= similar to existing --display-buffer= and --v4l2-buffer=.

This branch is in working state.

Since I have refactored A LOT of code, I may have broken things (please tell me!).

Please test, review (good luck, that's a big PR 😉), and report any problem :)

Fixes #14


old

Here is a release build for the current MR for Windows (audio.115):

Here is a release build for the current MR for Windows (audio.145):

@rom1v rom1v mentioned this pull request Feb 26, 2023
@yume-chan yume-chan mentioned this pull request Feb 27, 2023
@rom1v rom1v force-pushed the audio branch 4 times, most recently from 6e948fe to 2b2cf0a Compare February 27, 2023 20:42
@parkerlreed
Copy link

Are the BUILD instructions outdated?

(deck@steamdeck build)$ git clone https://github.com/Genymobile/scrcpy
Cloning into 'scrcpy'...
remote: Enumerating objects: 23178, done.
remote: Counting objects: 100% (3736/3736), done.
remote: Compressing objects: 100% (921/921), done.
remote: Total 23178 (delta 2443), reused 3435 (delta 2189), pack-reused 19442
Receiving objects: 100% (23178/23178), 4.74 MiB | 9.34 MiB/s, done.
Resolving deltas: 100% (14698/14698), done.
(deck@steamdeck build)$ cd scrcpy/
(deck@steamdeck scrcpy)$ git checkout audio
branch 'audio' set up to track 'origin/audio'.
Switched to a new branch 'audio'
(deck@steamdeck scrcpy)$ meson setup x --buildtype=release --strip -Db_lto=true
The Meson build system
Version: 1.0.1
Source dir: /home/deck/build/scrcpy
Build dir: /home/deck/build/scrcpy/x
Build type: native build
Project name: scrcpy
Project version: 1.25
C compiler for the host machine: cc (gcc 12.2.0 "cc (GCC) 12.2.0")
C linker for the host machine: cc ld.bfd 2.39.0
Host machine cpu family: x86_64
Host machine cpu: x86_64
Found pkg-config: /usr/bin/pkg-config (1.8.0)
Run-time dependency libavformat found: YES 59.27.100
Run-time dependency libavcodec found: YES 59.37.100
Run-time dependency libavutil found: YES 57.28.100
Run-time dependency libswresample found: YES 4.7.100
Run-time dependency sdl2 found: YES 2.24.1
Run-time dependency libavdevice found: YES 59.7.100
Run-time dependency libusb-1.0 found: YES 1.0.26
Checking for function "strdup" : YES 
Checking for function "asprintf" : YES 
Checking for function "vasprintf" : YES 
Checking for function "nrand48" : YES 
Checking for function "jrand48" : YES 
Header "sys/socket.h" has symbol "SOCK_CLOEXEC" : YES 
Configuring config.h using configuration

app/meson.build:226:0: ERROR: File src/util/average.c does not exist.

A full log can be found at /home/deck/build/scrcpy/x/meson-logs/meson-log.txt

@parkerlreed
Copy link

parkerlreed commented Feb 27, 2023

Seems to be nowhere in the sources? I don't even see a src folder

(deck@steamdeck scrcpy)$ find . -name "average.c"
(deck@steamdeck scrcpy)$ 

EDIT: Oh the folder is there just not that file in app/src/util

@rom1v
Copy link
Collaborator Author

rom1v commented Feb 27, 2023

app/meson.build:226:0: ERROR: File src/util/average.c does not exist.

Oops, it was not committed, sorry. Fixed.

@parkerlreed
Copy link

ninja: Entering directory `x'
[48/59] Generating server/scrcpy-server with a custom command

FAILURE: Build failed with an exception.

* What went wrong:
Could not open settings generic class cache for settings file '/home/deck/build/scrcpy/settings.gradle' (/home/deck/.gradle/caches/7.5/scripts/4g6ucengw406w5p3xne7pf3a4).
> BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 63

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 752ms
[57/59] Linking target app/scrcpy
FAILED: server/scrcpy-server 
/home/deck/build/scrcpy/server/./scripts/build-wrapper.sh /home/deck/build/scrcpy/server server/scrcpy-server release
[59/59] Linking target app/scrcpy
ninja: build stopped: subcommand failed.

@parkerlreed
Copy link

Aha java is too new (jdk 19). Trying an older version

@parkerlreed
Copy link

It's alive!

Note 10 Plus on Android 12. Working well!

@parkerlreed
Copy link

test.mp4

@reitowo
Copy link

reitowo commented Mar 3, 2023

Looking forward to this PR!

rom1v added a commit that referenced this pull request Mar 3, 2023
Rename VideoStreamer to Streamer, and extract a Codec interface which
will also support audio codecs.

PR #3757 <#3757>
rom1v added a commit that referenced this pull request Mar 3, 2023
This will allow to use "codec" for the Codec type.

PR #3757 <#3757>
rom1v added a commit that referenced this pull request Mar 3, 2023
The provided encoder name depends on the selected codec. Improve the
error message and the suggestions.

PR #3757 <#3757>
rom1v added a commit that referenced this pull request Mar 3, 2023
Since scrcpy-server is not an Android application (it's a java
executable), it has no Context.

Some features will require a Context instance to get the package name
and the UID. Add a FakeContext for this purpose.

PR #3757 <#3757>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Mar 3, 2023
FakeContext already provides an AttributeSource instance.

PR #3757 <#3757>

Co-authored-by: Simon Chan <[email protected]>
rom1v added a commit that referenced this pull request Mar 3, 2023
Remove duplicated constant.

PR #3757 <#3757>
rom1v added a commit that referenced this pull request Mar 3, 2023
Remove USER_ID from ServiceManager, and replace it by a constant in
FakeContext.

This is the same as android.os.Process.ROOT_UID, but this constant has
been introduced in API 29.

PR #3757 <#3757>
rom1v added a commit that referenced this pull request Mar 3, 2023
rom1v added a commit that referenced this pull request Mar 3, 2023
This will expose the correct package name and UID to the application
context.

PR #3757 <#3757>
rom1v added a commit that referenced this pull request Mar 3, 2023
Audio will be enabled by default (when supported). Add an option to
disable it.

PR #3757 <#3757>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Mar 3, 2023
When audio is enabled, open a new socket to send the audio stream from
the device to the client.

PR #3757 <#3757>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Mar 3, 2023
Create an AudioRecorder to capture the audio source REMOTE_SUBMIX.

For now, the captured packets are just logged into the console.

PR #3757 <#3757>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
@megapro17
Copy link
Contributor

Yeah I patched an exe just for fun
Android has c2.android.flac.encoder. You just need to add another encoder and enter it everywhere?

@rom1v
Copy link
Collaborator Author

rom1v commented Nov 7, 2023

Yes, it is possible to add flac support.

On Windows, you need to build an FFmpeg with flac decoding support: https://github.com/rom1v/scrcpy-deps/blob/231e4e819127dc55f1bf394943ba9887c1cb6224/build_ffmpeg_windows.sh#L40-L41

And in scrcpy, you add flac everywhere in addition to opus and aac. Here is the commit for aac: 4601735

PR welcome 😉

@rom1v rom1v mentioned this pull request Nov 7, 2023
@rom1v rom1v mentioned this pull request Apr 25, 2024
2 tasks
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Similar to --video-codec-options, but for audio.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
This will allow to reuse the same code for audio encoder selection.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Similar to --video-encoder, but for audio.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Print the logs on the caller side. This will allow to call the function
in another context without printing the logs.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Add an option to list the device encoders properly.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
When audio capture fails on the device, scrcpy continue mirroring the
video stream. This allows to enable audio by default only when
supported.

However, if an audio configuration occurs (for example the user
explicitly selected an unknown audio encoder), this must be treated as
an error and scrcpy must exit.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
This prepares the introduction of audio_decoder.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
This will be useful in logs.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Frame consumers may need details about the frame format.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Add a ring-buffer for bytes. It will be useful for buffering audio.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
If there is exactly one producer, then it can assume that the remaining
space in the buffer will only increase until it write something.

This assumption may allow the producer to write to the buffer (up to a
known safe size) without any synchronization mechanism, thus allowing
to read and write different parts of the buffer in parallel.

The producer can then commit the write with lock held, and update its
knowledge of the safe empty remaining space.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Play the decoded audio using SDL.

The audio player frame sink receives the audio frames, resample them
and write them to a byte buffer (introduced by this commit).

On SDL audio callback (from an internal SDL thread), copy samples from
this byte buffer to the SDL audio buffer.

The byte buffer is protected by the SDL_AudioDeviceLock(), but it has
been designed so that the producer and the consumer may write and read
in parallel, provided that they don't access the same slices of the
ring-buffer buffer.

PR #3757 <Genymobile/scrcpy#3757>

Co-authored-by: Simon Chan <[email protected]>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
On Android 11, it is possible to start the capture only when the running
app is in foreground. But scrcpy is not an app, it's a Java application
started from shell.

As a workaround, start an existing Android shell existing activity just
to start the capture, then close it immediately.

PR #3757 <Genymobile/scrcpy#3757>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
The new chlayout API has been introduced in FFmpeg 5.1. Use the old
channel_layout API on older versions.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
By default, scrcpy mirrors only the video when audio capture fails on
the device. Add a flag to force scrcpy to fail if audio is enabled but
does not work.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
The components needing delayed frames (sc_screen and sc_v4l2_sink)
managed a sc_video_buffer instance, which itself embedded a
sc_frame_buffer instance (to keep only the most recent frame).

In theory, these components should not be aware of delaying: they should
just receive AVFrames later, and only handle a sc_frame_buffer.

Therefore, refactor sc_delay_buffer as a frame source (it consumes)
frames) and a frame sink (it produces frames, after some delay), and
plug an instance in the pipeline only when a delay is requested.

This also removes the need for a specific sc_video_buffer.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
If there is only one point, assume the slope is 1.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
A delay buffer delayed all the frames except the first one, to open the
scrcpy window immediately and get a picture.

Make this feature optional, so that the delay buffer might also be used
for audio.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Expose an option to add a buffering delay (in milliseconds) before
playing audio.

This is similar to the options --display-buffer and --v4l2-buffer for
video frames.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
The audio capture was implemented in AudioEncoder.

In order to reuse it without encoding, extract it to a separate class.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
In order to support both encoded and raw audio stream, extract a
interface (very minimal, but sufficient to just start and stop).

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Add an alternative AudioRecorder to stream raw packets without encoding.

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
Add support for raw (PCM S16 LE) audio codec (a raw decoder is included
in FFmpeg).

PR #3757 <Genymobile/scrcpy#3757>
RaKastro pushed a commit to RaKastro/ScrPy that referenced this pull request Feb 22, 2025
For raw audio codec, some audio options are ignored.

PR #3757 <Genymobile/scrcpy#3757>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants