mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-03-29 08:24:28 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a68500aa13 | ||
|
|
dd32e53ad6 | ||
|
|
96a05383b9 | ||
|
|
048ff837f7 |
@@ -4,6 +4,7 @@ src = [
|
||||
'src/adb/adb_device.c',
|
||||
'src/adb/adb_parser.c',
|
||||
'src/adb/adb_tunnel.c',
|
||||
'src/audio_player.c',
|
||||
'src/cli.c',
|
||||
'src/clock.c',
|
||||
'src/compat.c',
|
||||
@@ -30,6 +31,7 @@ src = [
|
||||
'src/version.c',
|
||||
'src/video_buffer.c',
|
||||
'src/util/acksync.c',
|
||||
'src/util/bytebuf.c',
|
||||
'src/util/file.c',
|
||||
'src/util/intmap.c',
|
||||
'src/util/intr.c',
|
||||
@@ -260,6 +262,10 @@ if get_option('buildtype') == 'debug'
|
||||
['test_binary', [
|
||||
'tests/test_binary.c',
|
||||
]],
|
||||
['test_bytebuf', [
|
||||
'tests/test_bytebuf.c',
|
||||
'src/util/bytebuf.c',
|
||||
]],
|
||||
['test_cbuf', [
|
||||
'tests/test_cbuf.c',
|
||||
]],
|
||||
|
||||
150
app/src/audio_player.c
Normal file
150
app/src/audio_player.c
Normal file
@@ -0,0 +1,150 @@
|
||||
#include "audio_player.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast frame_sink to sc_v4l2_sink */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink)
|
||||
|
||||
void
|
||||
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||
struct sc_audio_player *ap = userdata;
|
||||
|
||||
// This callback is called with the lock used by SDL_AudioDeviceLock(), so
|
||||
// the bytebuf is protected
|
||||
|
||||
assert(len_int > 0);
|
||||
size_t len = len_int;
|
||||
|
||||
size_t read = sc_bytebuf_read_remaining(&ap->buf);
|
||||
if (read) {
|
||||
if (read > len) {
|
||||
read = len;
|
||||
}
|
||||
sc_bytebuf_read(&ap->buf, stream, read);
|
||||
}
|
||||
if (read < len) {
|
||||
// Insert silence
|
||||
memset(stream + read, 0, len - read);
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_AudioFormat
|
||||
sc_audio_player_ffmpeg_to_sdl_format(enum AVSampleFormat format) {
|
||||
switch (format) {
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
return AUDIO_S16;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
return AUDIO_S32;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
return AUDIO_F32;
|
||||
default:
|
||||
LOGE("Unsupported FFmpeg sample format: %s",
|
||||
av_get_sample_fmt_name(format));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
SDL_AudioFormat format =
|
||||
sc_audio_player_ffmpeg_to_sdl_format(ctx->sample_fmt);
|
||||
if (!format) {
|
||||
// error already logged
|
||||
//return false;
|
||||
format = AUDIO_F32; // it's planar, but for now there is only 1 channel
|
||||
}
|
||||
LOGI("%d\n", ctx->sample_rate);
|
||||
|
||||
SDL_AudioSpec desired = {
|
||||
.freq = ctx->sample_rate,
|
||||
.format = format,
|
||||
.channels = 1,
|
||||
.samples = 2048,
|
||||
.callback = sc_audio_player_sdl_callback,
|
||||
.userdata = ap,
|
||||
};
|
||||
SDL_AudioSpec obtained;
|
||||
|
||||
ap->device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
|
||||
if (!ap->device) {
|
||||
LOGE("Could not open audio device: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_PauseAudioDevice(ap->device, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
assert(ap->device);
|
||||
SDL_PauseAudioDevice(ap->device, 1);
|
||||
SDL_CloseAudioDevice(ap->device);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
const uint8_t *data = frame->data[0];
|
||||
size_t size = frame->linesize[0];
|
||||
|
||||
// TODO convert to non planar format
|
||||
// TODO then re-enable stereo
|
||||
// TODO clock drift compensation
|
||||
|
||||
// It should almost always be possible to write without lock
|
||||
bool can_write_without_lock = size <= ap->safe_empty_buffer;
|
||||
if (can_write_without_lock) {
|
||||
sc_bytebuf_prepare_write(&ap->buf, data, size);
|
||||
}
|
||||
|
||||
SDL_LockAudioDevice(ap->device);
|
||||
if (can_write_without_lock) {
|
||||
sc_bytebuf_commit_write(&ap->buf, size);
|
||||
} else {
|
||||
sc_bytebuf_write(&ap->buf, data, size);
|
||||
}
|
||||
|
||||
// The next time, it will remain at least the current empty space
|
||||
ap->safe_empty_buffer = sc_bytebuf_write_remaining(&ap->buf);
|
||||
SDL_UnlockAudioDevice(ap->device);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_audio_player_init(struct sc_audio_player *ap,
|
||||
const struct sc_audio_player_callbacks *cbs,
|
||||
void *cbs_userdata) {
|
||||
bool ok = sc_bytebuf_init(&ap->buf, 128 * 1024);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ap->safe_empty_buffer = sc_bytebuf_write_remaining(&ap->buf);
|
||||
|
||||
assert(cbs && cbs->on_ended);
|
||||
ap->cbs = cbs;
|
||||
ap->cbs_userdata = cbs_userdata;
|
||||
|
||||
static const struct sc_frame_sink_ops ops = {
|
||||
.open = sc_audio_player_frame_sink_open,
|
||||
.close = sc_audio_player_frame_sink_close,
|
||||
.push = sc_audio_player_frame_sink_push,
|
||||
};
|
||||
|
||||
ap->frame_sink.ops = &ops;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_audio_player_destroy(struct sc_audio_player *ap) {
|
||||
sc_bytebuf_destroy(&ap->buf);
|
||||
}
|
||||
40
app/src/audio_player.h
Normal file
40
app/src/audio_player.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef SC_AUDIO_PLAYER_H
|
||||
#define SC_AUDIO_PLAYER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "trait/frame_sink.h"
|
||||
#include <util/bytebuf.h>
|
||||
#include <util/thread.h>
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
struct sc_audio_player {
|
||||
struct sc_frame_sink frame_sink;
|
||||
|
||||
SDL_AudioDeviceID device;
|
||||
|
||||
// protected by SDL_AudioDeviceLock()
|
||||
struct sc_bytebuf buf;
|
||||
// Number of bytes which could be written without locking
|
||||
size_t safe_empty_buffer;
|
||||
|
||||
const struct sc_audio_player_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct sc_audio_player_callbacks {
|
||||
void (*on_ended)(struct sc_audio_player *ap, bool success, void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
sc_audio_player_init(struct sc_audio_player *ap,
|
||||
const struct sc_audio_player_callbacks *cbs,
|
||||
void *cbs_userdata);
|
||||
|
||||
void
|
||||
sc_audio_player_destroy(struct sc_audio_player *ap);
|
||||
|
||||
#endif
|
||||
@@ -25,10 +25,10 @@ sc_decoder_close_sinks(struct sc_decoder *decoder) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_open_sinks(struct sc_decoder *decoder) {
|
||||
sc_decoder_open_sinks(struct sc_decoder *decoder, const AVCodecContext *ctx) {
|
||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||
if (!sink->ops->open(sink)) {
|
||||
if (!sink->ops->open(sink, ctx)) {
|
||||
sc_decoder_close_first_sinks(decoder, i);
|
||||
return false;
|
||||
}
|
||||
@@ -47,6 +47,11 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
||||
|
||||
decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
|
||||
if (codec->type == AVMEDIA_TYPE_VIDEO) {
|
||||
// Only YUV 4:2:0 is supported, hardcode it
|
||||
decoder->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
}
|
||||
|
||||
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
|
||||
LOGE("Decoder '%s': could not open codec", decoder->name);
|
||||
avcodec_free_context(&decoder->codec_ctx);
|
||||
@@ -61,7 +66,7 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_decoder_open_sinks(decoder)) {
|
||||
if (!sc_decoder_open_sinks(decoder, decoder->codec_ctx)) {
|
||||
av_frame_free(&decoder->frame);
|
||||
avcodec_close(decoder->codec_ctx);
|
||||
avcodec_free_context(&decoder->codec_ctx);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "audio_player.h"
|
||||
#include "controller.h"
|
||||
#include "decoder.h"
|
||||
#include "demuxer.h"
|
||||
@@ -40,6 +41,7 @@
|
||||
struct scrcpy {
|
||||
struct sc_server server;
|
||||
struct sc_screen screen;
|
||||
struct sc_audio_player audio_player;
|
||||
struct sc_demuxer video_demuxer;
|
||||
struct sc_demuxer audio_demuxer;
|
||||
struct sc_decoder video_decoder;
|
||||
@@ -215,6 +217,17 @@ sc_recorder_on_ended(struct sc_recorder *recorder, bool success,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_audio_player_on_ended(struct sc_audio_player *ap, bool success,
|
||||
void *userdata) {
|
||||
(void) ap;
|
||||
(void) userdata;
|
||||
|
||||
if (!success) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos,
|
||||
void *userdata) {
|
||||
@@ -301,6 +314,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
bool file_pusher_initialized = false;
|
||||
bool recorder_initialized = false;
|
||||
bool recorder_started = false;
|
||||
bool audio_player_initialized = false;
|
||||
#ifdef HAVE_V4L2
|
||||
bool v4l2_sink_initialized = false;
|
||||
#endif
|
||||
@@ -383,9 +397,16 @@ scrcpy(struct scrcpy_options *options) {
|
||||
}
|
||||
|
||||
// Initialize SDL video in addition if display is enabled
|
||||
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
|
||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
||||
goto end;
|
||||
if (options->display) {
|
||||
if (SDL_Init(SDL_INIT_VIDEO)) {
|
||||
LOGE("Could not initialize SDL video: %s", SDL_GetError());
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (options->audio && SDL_Init(SDL_INIT_AUDIO)) {
|
||||
LOGE("Could not initialize SDL audio: %s", SDL_GetError());
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
sdl_configure(options->display, options->disable_screensaver);
|
||||
@@ -663,6 +684,19 @@ aoa_hid_end:
|
||||
screen_initialized = true;
|
||||
|
||||
sc_decoder_add_sink(&s->video_decoder, &s->screen.frame_sink);
|
||||
|
||||
if (options->audio) {
|
||||
static const struct sc_audio_player_callbacks audio_player_cbs = {
|
||||
.on_ended = sc_audio_player_on_ended,
|
||||
};
|
||||
if (!sc_audio_player_init(&s->audio_player,
|
||||
&audio_player_cbs, NULL)) {
|
||||
goto end;
|
||||
}
|
||||
audio_player_initialized = true;
|
||||
|
||||
sc_decoder_add_sink(&s->audio_decoder, &s->audio_player.frame_sink);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_V4L2
|
||||
@@ -783,6 +817,10 @@ end:
|
||||
sc_recorder_destroy(&s->recorder);
|
||||
}
|
||||
|
||||
if (audio_player_initialized) {
|
||||
sc_audio_player_destroy(&s->audio_player);
|
||||
}
|
||||
|
||||
if (file_pusher_initialized) {
|
||||
sc_file_pusher_join(&s->file_pusher);
|
||||
sc_file_pusher_destroy(&s->file_pusher);
|
||||
|
||||
@@ -330,7 +330,10 @@ event_watcher(void *data, SDL_Event *event) {
|
||||
#endif
|
||||
|
||||
static bool
|
||||
sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
|
||||
sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
|
||||
|
||||
struct sc_screen *screen = DOWNCAST(sink);
|
||||
(void) screen;
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
@@ -18,7 +19,7 @@ struct sc_frame_sink {
|
||||
};
|
||||
|
||||
struct sc_frame_sink_ops {
|
||||
bool (*open)(struct sc_frame_sink *sink);
|
||||
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx);
|
||||
void (*close)(struct sc_frame_sink *sink);
|
||||
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
|
||||
};
|
||||
|
||||
114
app/src/util/bytebuf.c
Normal file
114
app/src/util/bytebuf.c
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "bytebuf.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size) {
|
||||
assert(alloc_size);
|
||||
// sufficient, but use more for alignment.
|
||||
buf->data = malloc(alloc_size);
|
||||
if (!buf->data) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
buf->alloc_size = alloc_size;
|
||||
buf->head = 0;
|
||||
buf->tail = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_destroy(struct sc_bytebuf *buf) {
|
||||
free(buf->data);
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) {
|
||||
assert(len);
|
||||
assert(sc_bytebuf_read_remaining(buf) >= len);
|
||||
assert(buf->tail != buf->head); // the buffer could not be empty
|
||||
|
||||
size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size;
|
||||
size_t right_len = right_limit - buf->tail;
|
||||
if (len < right_len) {
|
||||
right_len = len;
|
||||
}
|
||||
memcpy(to, buf->data + buf->tail, right_len);
|
||||
|
||||
if (len > right_len) {
|
||||
memcpy(to + right_len, buf->data, len - right_len);
|
||||
}
|
||||
|
||||
buf->tail = (buf->tail + len) % buf->alloc_size;
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) {
|
||||
assert(len);
|
||||
assert(sc_bytebuf_read_remaining(buf) >= len);
|
||||
assert(buf->tail != buf->head); // the buffer could not be empty
|
||||
|
||||
buf->tail = (buf->tail + len) % buf->alloc_size;
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) {
|
||||
assert(len);
|
||||
|
||||
size_t max_len = buf->alloc_size - 1;
|
||||
if (len >= max_len) {
|
||||
// Copy only the right-most bytes
|
||||
memcpy(buf->data, from + len - max_len, max_len);
|
||||
buf->tail = 0;
|
||||
buf->head = max_len;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t right_limit = buf->head < buf->tail ? buf->tail : buf->alloc_size;
|
||||
size_t right_len = right_limit - buf->head;
|
||||
if (len < right_len) {
|
||||
right_len = len;
|
||||
}
|
||||
memcpy(buf->data + buf->head, from, right_len);
|
||||
if (len > right_len) {
|
||||
memcpy(buf->data, from + right_len, len - right_len);
|
||||
}
|
||||
|
||||
size_t empty_space = sc_bytebuf_write_remaining(buf);
|
||||
if (len > empty_space) {
|
||||
buf->tail = (buf->tail + len - empty_space) % buf->alloc_size;
|
||||
}
|
||||
buf->head = (buf->head + len) % buf->alloc_size;
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
||||
size_t len) {
|
||||
// *This function MUST NOT access buf->tail (even in assert()).*
|
||||
// The purpose of this function is to allow a reader and a writer to access
|
||||
// different parts of the buffer in parallel simultaneously. It is intended
|
||||
// to be called without lock (only sc_bytebuf_commit_write() is intended to
|
||||
// be called with lock held).
|
||||
|
||||
assert(len < buf->alloc_size - 1);
|
||||
|
||||
size_t right_len = buf->alloc_size - buf->head;
|
||||
if (len < right_len) {
|
||||
right_len = len;
|
||||
}
|
||||
|
||||
memcpy(buf->data + buf->head, from, right_len);
|
||||
if (len > right_len) {
|
||||
memcpy(buf->data, from + right_len, len - right_len);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) {
|
||||
assert(len <= sc_bytebuf_write_remaining(buf));
|
||||
buf->head = (buf->head + len) % buf->alloc_size;
|
||||
}
|
||||
104
app/src/util/bytebuf.h
Normal file
104
app/src/util/bytebuf.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#ifndef SC_BYTEBUF_H
|
||||
#define SC_BYTEBUF_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct sc_bytebuf {
|
||||
uint8_t *data;
|
||||
// The actual capacity is (allocated - 1) so that head == tail is
|
||||
// non-ambiguous
|
||||
size_t alloc_size;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
// empty: tail == head
|
||||
// full: (tail + 1) % allocated == head
|
||||
};
|
||||
|
||||
bool
|
||||
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size);
|
||||
|
||||
/**
|
||||
* Copy from the bytebuf to a user-provided array
|
||||
*
|
||||
* The caller must check that len <= buf->len (it is an error to attempt to read
|
||||
* more bytes than available).
|
||||
*
|
||||
* This function is guaranteed to not change the head.
|
||||
*/
|
||||
void
|
||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len);
|
||||
|
||||
/**
|
||||
* Drop len bytes from the buffer
|
||||
*
|
||||
* The caller must check that len <= buf->len (it is an error to attempt to skip
|
||||
* more bytes than available).
|
||||
*
|
||||
* This function is guaranteed to not change the head.
|
||||
*
|
||||
* It is equivalent to call sc_bytebuf_read() to some array and discard the
|
||||
* array (but more efficient since there is no copy).
|
||||
*/
|
||||
void
|
||||
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len);
|
||||
|
||||
/**
|
||||
* Copy the user-provided array to the bytebuf
|
||||
*
|
||||
* The length of the input array is not restricted:
|
||||
* if len >= sc_bytebuf_write_remaining(buf), then the excessive input bytes
|
||||
* will overwrite the oldest bytes in the buffer.
|
||||
*/
|
||||
void
|
||||
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len);
|
||||
|
||||
/**
|
||||
* Copy the user-provided array to the bytebuf, but do not advance the cursor
|
||||
*
|
||||
* The caller must check that len <= buf->len (it is an error to attempt to
|
||||
* write more bytes than available).
|
||||
*
|
||||
* After this function is called, the write must be committed with
|
||||
* sc_bytebuf_commit_write().
|
||||
*
|
||||
* The purpose of this mechanism is to acquire a lock only to commit the write,
|
||||
* but not to perform the actual copy.
|
||||
*/
|
||||
void
|
||||
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* Commit a prepared write
|
||||
*/
|
||||
void
|
||||
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len);
|
||||
|
||||
/**
|
||||
* Return the number of bytes which can be read
|
||||
*
|
||||
* It is an error to read more bytes than available.
|
||||
*/
|
||||
static inline size_t
|
||||
sc_bytebuf_read_remaining(struct sc_bytebuf *buf) {
|
||||
return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of bytes which can be written without overwriting
|
||||
*
|
||||
* It is not an error to write more bytes than the available space, but this
|
||||
* would overwrite the oldest bytes in the buffer.
|
||||
*/
|
||||
static inline size_t
|
||||
sc_bytebuf_write_remaining(struct sc_bytebuf *buf) {
|
||||
return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size;
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_destroy(struct sc_bytebuf *buf);
|
||||
|
||||
#endif
|
||||
@@ -156,7 +156,9 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
||||
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
||||
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
|
||||
|
||||
static const struct sc_video_buffer_callbacks cbs = {
|
||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||
};
|
||||
|
||||
154
app/tests/test_bytebuf.c
Normal file
154
app/tests/test_bytebuf.c
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/bytebuf.h"
|
||||
|
||||
void test_bytebuf_simple(void) {
|
||||
struct sc_bytebuf buf;
|
||||
uint8_t data[20];
|
||||
|
||||
bool ok = sc_bytebuf_init(&buf, 20);
|
||||
assert(ok);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 5);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 4);
|
||||
assert(!strncmp((char *) data, "hell", 4));
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 7);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 8);
|
||||
|
||||
sc_bytebuf_read(&buf, &data[4], 8);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
|
||||
data[12] = '\0';
|
||||
assert(!strcmp((char *) data, "hello world!"));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
|
||||
sc_bytebuf_destroy(&buf);
|
||||
}
|
||||
|
||||
void test_bytebuf_boundaries(void) {
|
||||
struct sc_bytebuf buf;
|
||||
uint8_t data[20];
|
||||
|
||||
bool ok = sc_bytebuf_init(&buf, 20);
|
||||
assert(ok);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 6);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 18);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 9);
|
||||
assert(!strncmp((char *) data, "hello hel", 9));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 14);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 15);
|
||||
|
||||
sc_bytebuf_skip(&buf, 3);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 12);
|
||||
data[12] = '\0';
|
||||
assert(!strcmp((char *) data, "hello world!"));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
|
||||
sc_bytebuf_destroy(&buf);
|
||||
}
|
||||
|
||||
void test_bytebuf_overwrite(void) {
|
||||
struct sc_bytebuf buf;
|
||||
uint8_t data[10];
|
||||
|
||||
bool ok = sc_bytebuf_init(&buf, 10); // so actual capacity is 9
|
||||
assert(ok);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 6);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "abcdef", sizeof("abcdef") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 9);
|
||||
assert(!strncmp((char *) data, "lo abcdef", 9));
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "a very big buffer",
|
||||
sizeof("a very big buffer") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 9);
|
||||
assert(!strncmp((char *) data, "ig buffer", 9));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
|
||||
sc_bytebuf_destroy(&buf);
|
||||
}
|
||||
|
||||
void test_bytebuf_two_steps_write(void) {
|
||||
struct sc_bytebuf buf;
|
||||
uint8_t data[20];
|
||||
|
||||
bool ok = sc_bytebuf_init(&buf, 20);
|
||||
assert(ok);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 6);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||
|
||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12); // write not committed yet
|
||||
|
||||
sc_bytebuf_read(&buf, data, 9);
|
||||
assert(!strncmp((char *) data, "hello hel", 3));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 3);
|
||||
|
||||
sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||
|
||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9); // write not committed yet
|
||||
|
||||
sc_bytebuf_commit_write(&buf, sizeof("world") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 14);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 15);
|
||||
|
||||
sc_bytebuf_skip(&buf, 3);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 12);
|
||||
data[12] = '\0';
|
||||
assert(!strcmp((char *) data, "hello world!"));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
|
||||
sc_bytebuf_destroy(&buf);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
test_bytebuf_simple();
|
||||
test_bytebuf_boundaries();
|
||||
test_bytebuf_overwrite();
|
||||
test_bytebuf_two_steps_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public final class AudioEncoder {
|
||||
}
|
||||
|
||||
private static final int SAMPLE_RATE = 48000;
|
||||
private static final int CHANNELS = 2;
|
||||
private static final int CHANNELS = 1;
|
||||
|
||||
private static final int BUFFER_MS = 10; // milliseconds
|
||||
private static final int BUFFER_SIZE = SAMPLE_RATE * CHANNELS * BUFFER_MS / 1000;
|
||||
|
||||
Reference in New Issue
Block a user