Compare commits

..

4 Commits

Author SHA1 Message Date
Romain Vimont
6139230828 Increase default audio buffer for FLAC
FLAC is not low latency: the default encoder produces blocks of 4096
samples, which represent ~85.333ms.

Increase the audio buffer by default so that audio playback works.
2023-11-13 09:30:55 +01:00
megapro17
27bb15b32e Add support for FLAC audio codec
PR #4410 <#https://github.com/Genymobile/scrcpy/pull/4410>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-11-12 18:59:04 +01:00
Romain Vimont
409192cdff Upgrade FFmpeg build to 6.1-scrcpy
Upgrade to FFmpeg 6.1, and with FLAC support enabled.
2023-11-12 18:10:56 +01:00
Romain Vimont
5d6df2e744 Fix OPUS packet in an endian-independent way
Reading the header id as an int assumed that the current endianness was
little endian. Read to a byte array to remove this assumption.
2023-11-12 18:10:49 +01:00
15 changed files with 51 additions and 81 deletions

View File

@@ -125,7 +125,7 @@ _scrcpy() {
return return
;; ;;
--record-format) --record-format)
COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac flac wav' -- "$cur")) COMPREPLY=($(compgen -W 'mkv mp4' -- "$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 wav)' '--record-format=[Force recording format]:format:(mp4 mkv)'
'--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

@@ -6,11 +6,11 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR" mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR"
VERSION=6.1-scrcpy-2 VERSION=6.1-scrcpy
DEP_DIR="ffmpeg-$VERSION" DEP_DIR="ffmpeg-$VERSION"
FILENAME="$DEP_DIR".7z FILENAME="$DEP_DIR".7z
SHA256SUM=7f25f638dc24a0f5d4af07a088b6a604cf33548900bbfd2f6ce0bae050b7664d SHA256SUM=b41726e603f4624bb9ed7d2836e3e59d9d20b000e22a9ebd27055f4e99e48219
if [[ -d "$DEP_DIR" ]] if [[ -d "$DEP_DIR" ]]
then then

View File

@@ -347,7 +347,7 @@ Record screen to
The format is determined by the The format is determined by the
.B \-\-record\-format .B \-\-record\-format
option if set, or by the file extension. option if set, or by the file extension (.mp4 or .mkv).
.TP .TP
.B \-\-raw\-key\-events .B \-\-raw\-key\-events
@@ -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, flac or wav). Force recording format (either mp4 or mkv).
.TP .TP
.BI "\-\-render\-driver " name .BI "\-\-render\-driver " name

View File

@@ -583,7 +583,7 @@ static const struct sc_option options[] = {
.argdesc = "file.mp4", .argdesc = "file.mp4",
.text = "Record screen to file.\n" .text = "Record screen to file.\n"
"The format is determined by the --record-format option if " "The format is determined by the --record-format option if "
"set, or by the file extension.", "set, or by the file extension (.mp4 or .mkv).",
}, },
{ {
.longopt_id = OPT_RAW_KEY_EVENTS, .longopt_id = OPT_RAW_KEY_EVENTS,
@@ -594,8 +594,7 @@ 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, flac " .text = "Force recording format (either mp4 or mkv).",
"or wav).",
}, },
{ {
.longopt_id = OPT_RENDER_DRIVER, .longopt_id = OPT_RENDER_DRIVER,
@@ -1630,9 +1629,6 @@ 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;
} }
@@ -2376,6 +2372,11 @@ 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");
@@ -2401,20 +2402,6 @@ 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,7 +26,6 @@ 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
@@ -35,8 +34,7 @@ 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,8 +71,6 @@ 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;
} }
@@ -105,7 +103,7 @@ sc_recorder_write_stream(struct sc_recorder *recorder,
AVStream *stream = recorder->ctx->streams[st->index]; AVStream *stream = recorder->ctx->streams[st->index];
sc_recorder_rescale_packet(stream, packet); sc_recorder_rescale_packet(stream, packet);
if (st->last_pts != AV_NOPTS_VALUE && packet->pts <= st->last_pts) { if (st->last_pts != AV_NOPTS_VALUE && packet->pts <= st->last_pts) {
LOGD("Fixing PTS non monotonically increasing in stream %d " LOGW("Fixing PTS non monotonically increasing in stream %d "
"(%" PRIi64 " >= %" PRIi64 ")", "(%" PRIi64 " >= %" PRIi64 ")",
st->index, st->last_pts, packet->pts); st->index, st->last_pts, packet->pts);
packet->pts = ++st->last_pts; packet->pts = ++st->last_pts;
@@ -170,14 +168,13 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) {
} }
static inline bool static inline bool
sc_recorder_must_wait_for_config_packets(struct sc_recorder *recorder) { sc_recorder_has_empty_queues(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 && recorder->audio_expects_config_packet if (recorder->audio && sc_vecdeque_is_empty(&recorder->audio_queue)) {
&& 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;
} }
@@ -193,7 +190,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_must_wait_for_config_packets(recorder))) { || sc_recorder_has_empty_queues(recorder))) {
sc_cond_wait(&recorder->cond, &recorder->mutex); sc_cond_wait(&recorder->cond, &recorder->mutex);
} }
@@ -212,8 +209,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
} }
AVPacket *audio_pkt = NULL; AVPacket *audio_pkt = NULL;
if (recorder->audio_expects_config_packet && if (!sc_vecdeque_is_empty(&recorder->audio_queue)) {
!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);
} }
@@ -601,10 +597,6 @@ 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);
@@ -717,8 +709,6 @@ 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,8 +50,6 @@ 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

@@ -16,6 +16,6 @@ cpu = 'i686'
endian = 'little' endian = 'little'
[properties] [properties]
prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy-2/win32' prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy/win32'
prebuilt_sdl2 = 'SDL2-2.28.4/i686-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.28.4/i686-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32'

View File

@@ -16,6 +16,6 @@ cpu = 'x86_64'
endian = 'little' endian = 'little'
[properties] [properties]
prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy-2/win64' prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy/win64'
prebuilt_sdl2 = 'SDL2-2.28.4/x86_64-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.28.4/x86_64-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64'

View File

@@ -19,7 +19,6 @@ 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
``` ```
@@ -33,17 +32,14 @@ course, not if you capture your scrcpy window and audio output on the computer).
## Format ## Format
The video and audio streams are encoded on the device, but are muxed on the The video and audio streams are encoded on the device, but are muxed on the
client side. Several formats (containers) are supported: client side. Two formats (containers) are supported:
- MP4 (`.mp4`, `.m4a`, `.aac`) - Matroska (`.mkv`)
- Matroska (`.mkv`, `.mka`) - MP4 (`.mp4`)
- OPUS (`.opus`)
- FLAC (`.flac`)
- WAV (`.wav`)
The container is automatically selected based on the filename. The container is automatically selected based on the filename.
It is also possible to explicitly select a container (in that case the filename It is also possible to explicitly select a container (in that case the filename
needs not end with a known extension): needs not end with `.mkv` or `.mp4`):
``` ```
scrcpy --record=file --record-format=mkv scrcpy --record=file --record-format=mkv

View File

@@ -94,10 +94,10 @@ dist-win32: build-server build-win32
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@@ -112,10 +112,10 @@ dist-win64: build-server build-win64
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

View File

@@ -24,19 +24,11 @@ public final class AudioCapture {
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
public static final int BYTES_PER_SAMPLE = 2; public static final int BYTES_PER_SAMPLE = 2;
// Never read more than 1024 samples, even if the buffer is bigger (that would increase latency).
// A lower value is useless, since the system captures audio samples by blocks of 1024 (so for example if we read by blocks of 256 samples, we
// receive 4 successive blocks without waiting, then we wait for the 4 next ones).
public static final int MAX_READ_SIZE = 1024 * CHANNELS * BYTES_PER_SAMPLE;
private static final long ONE_SAMPLE_US = (1000000 + SAMPLE_RATE - 1) / SAMPLE_RATE; // 1 sample in microseconds (used for fixing PTS)
private final int audioSource; private final int audioSource;
private AudioRecord recorder; private AudioRecord recorder;
private final AudioTimestamp timestamp = new AudioTimestamp(); private final AudioTimestamp timestamp = new AudioTimestamp();
private long previousRecorderTimestamp = -1;
private long previousPts = 0; private long previousPts = 0;
private long nextPts = 0; private long nextPts = 0;
@@ -44,6 +36,10 @@ public final class AudioCapture {
this.audioSource = audioSource.value(); this.audioSource = audioSource.value();
} }
public static int millisToBytes(int millis) {
return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000;
}
private static AudioFormat createAudioFormat() { private static AudioFormat createAudioFormat() {
AudioFormat.Builder builder = new AudioFormat.Builder(); AudioFormat.Builder builder = new AudioFormat.Builder();
builder.setEncoding(ENCODING); builder.setEncoding(ENCODING);
@@ -139,8 +135,8 @@ public final class AudioCapture {
} }
@TargetApi(Build.VERSION_CODES.N) @TargetApi(Build.VERSION_CODES.N)
public int read(ByteBuffer directBuffer, MediaCodec.BufferInfo outBufferInfo) { public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) {
int r = recorder.read(directBuffer, MAX_READ_SIZE); int r = recorder.read(directBuffer, size);
if (r <= 0) { if (r <= 0) {
return r; return r;
} }
@@ -148,9 +144,8 @@ public final class AudioCapture {
long pts; long pts;
int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC); int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC);
if (ret == AudioRecord.SUCCESS && timestamp.nanoTime != previousRecorderTimestamp) { if (ret == AudioRecord.SUCCESS) {
pts = timestamp.nanoTime / 1000; pts = timestamp.nanoTime / 1000;
previousRecorderTimestamp = timestamp.nanoTime;
} else { } else {
if (nextPts == 0) { if (nextPts == 0) {
Ln.w("Could not get any audio timestamp"); Ln.w("Could not get any audio timestamp");
@@ -162,13 +157,13 @@ public final class AudioCapture {
long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE); long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE);
nextPts = pts + durationUs; nextPts = pts + durationUs;
if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) { if (previousPts != 0 && pts < previousPts) {
// Audio PTS may come from two sources: // Audio PTS may come from two sources:
// - recorder.getTimestamp() if the call works; // - recorder.getTimestamp() if the call works;
// - an estimation from the previous PTS and the packet size as a fallback. // - an estimation from the previous PTS and the packet size as a fallback.
// //
// Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it. // Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it.
pts = previousPts + ONE_SAMPLE_US; pts = previousPts + 1;
} }
previousPts = pts; previousPts = pts;

View File

@@ -37,6 +37,9 @@ public final class AudioEncoder implements AsyncProcessor {
private static final int SAMPLE_RATE = AudioCapture.SAMPLE_RATE; private static final int SAMPLE_RATE = AudioCapture.SAMPLE_RATE;
private static final int CHANNELS = AudioCapture.CHANNELS; private static final int CHANNELS = AudioCapture.CHANNELS;
private static final int READ_MS = 5; // milliseconds
private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS);
private final AudioCapture capture; private final AudioCapture capture;
private final Streamer streamer; private final Streamer streamer;
private final int bitRate; private final int bitRate;
@@ -90,7 +93,7 @@ public final class AudioEncoder implements AsyncProcessor {
while (!Thread.currentThread().isInterrupted()) { while (!Thread.currentThread().isInterrupted()) {
InputTask task = inputTasks.take(); InputTask task = inputTasks.take();
ByteBuffer buffer = mediaCodec.getInputBuffer(task.index); ByteBuffer buffer = mediaCodec.getInputBuffer(task.index);
int r = capture.read(buffer, bufferInfo); int r = capture.read(buffer, READ_SIZE, bufferInfo);
if (r <= 0) { if (r <= 0) {
throw new IOException("Could not read audio: " + r); throw new IOException("Could not read audio: " + r);
} }

View File

@@ -13,6 +13,9 @@ public final class AudioRawRecorder implements AsyncProcessor {
private Thread thread; private Thread thread;
private static final int READ_MS = 5; // milliseconds
private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS);
public AudioRawRecorder(AudioCapture capture, Streamer streamer) { public AudioRawRecorder(AudioCapture capture, Streamer streamer) {
this.capture = capture; this.capture = capture;
this.streamer = streamer; this.streamer = streamer;
@@ -25,7 +28,7 @@ public final class AudioRawRecorder implements AsyncProcessor {
return; return;
} }
final ByteBuffer buffer = ByteBuffer.allocateDirect(AudioCapture.MAX_READ_SIZE); final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE);
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
try { try {
@@ -40,7 +43,7 @@ public final class AudioRawRecorder implements AsyncProcessor {
streamer.writeAudioHeader(); streamer.writeAudioHeader();
while (!Thread.currentThread().isInterrupted()) { while (!Thread.currentThread().isInterrupted()) {
buffer.position(0); buffer.position(0);
int r = capture.read(buffer, bufferInfo); int r = capture.read(buffer, READ_SIZE, bufferInfo);
if (r < 0) { if (r < 0) {
throw new IOException("Could not read audio: " + r); throw new IOException("Could not read audio: " + r);
} }