mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-04-21 03:55:05 +00:00
v4l2loopback support
This commit is contained in:
parent
1d615a0d51
commit
77447ff6c6
9 changed files with 661 additions and 6 deletions
|
@ -21,12 +21,15 @@ src = [
|
|||
'src/stream.c',
|
||||
'src/tiny_xpm.c',
|
||||
'src/video_buffer.c',
|
||||
'src/v4l2sink.c',
|
||||
'src/util/net.c',
|
||||
'src/util/process.c',
|
||||
'src/util/str_util.c',
|
||||
'src/util/thread.c',
|
||||
]
|
||||
|
||||
conf = configuration_data()
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
src += [ 'src/sys/win/process.c' ]
|
||||
else
|
||||
|
@ -49,6 +52,11 @@ if not get_option('crossbuild_windows')
|
|||
dependency('sdl2'),
|
||||
]
|
||||
|
||||
if host_machine.system() == 'linux'
|
||||
dependencies += dependency('libavdevice')
|
||||
conf.set('V4L2SINK', '1')
|
||||
endif
|
||||
|
||||
else
|
||||
|
||||
# cross-compile mingw32 build (from Linux to Windows)
|
||||
|
@ -90,8 +98,6 @@ if host_machine.system() == 'windows'
|
|||
dependencies += cc.find_library('ws2_32')
|
||||
endif
|
||||
|
||||
conf = configuration_data()
|
||||
|
||||
foreach f : check_functions
|
||||
if cc.has_function(f)
|
||||
define = 'HAVE_' + f.underscorify().to_upper()
|
||||
|
|
|
@ -179,6 +179,11 @@ scrcpy_print_usage(const char *arg0) {
|
|||
" on exit.\n"
|
||||
" It only shows physical touches (not clicks from scrcpy).\n"
|
||||
"\n"
|
||||
#ifdef V4L2SINK
|
||||
" --v4l2sink /dev/videoN\n"
|
||||
" Output to v4l2loopback device. It will lock video orientation. Use --lock-video-orientation to override the rotation.\n"
|
||||
"\n"
|
||||
#endif
|
||||
" -v, --version\n"
|
||||
" Print the version of scrcpy.\n"
|
||||
"\n"
|
||||
|
@ -667,6 +672,7 @@ guess_record_format(const char *filename) {
|
|||
#define OPT_LEGACY_PASTE 1024
|
||||
#define OPT_ENCODER_NAME 1025
|
||||
#define OPT_POWER_OFF_ON_CLOSE 1026
|
||||
#define OPT_V4L2SINK 1027
|
||||
|
||||
bool
|
||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
|
@ -708,6 +714,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||
{"show-touches", no_argument, NULL, 't'},
|
||||
{"stay-awake", no_argument, NULL, 'w'},
|
||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||
#ifdef V4L2SINK
|
||||
{"v4l2sink", required_argument, NULL, OPT_V4L2SINK},
|
||||
#endif
|
||||
{"verbosity", required_argument, NULL, 'V'},
|
||||
{"version", no_argument, NULL, 'v'},
|
||||
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
||||
|
@ -890,14 +899,28 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||
case OPT_POWER_OFF_ON_CLOSE:
|
||||
opts->power_off_on_close = true;
|
||||
break;
|
||||
#ifdef V4L2SINK
|
||||
case OPT_V4L2SINK:
|
||||
opts->v4l2sink_device = optarg;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts->display && !opts->record_filename) {
|
||||
//v4l2loopback can't handle resolution changes so we must lock video orientation in case this is used
|
||||
if (opts->v4l2sink_device && opts->lock_video_orientation < 0) {
|
||||
opts->lock_video_orientation = 0;
|
||||
}
|
||||
|
||||
if (!opts->display && !opts->record_filename && !opts->v4l2sink_device) {
|
||||
#ifdef V4L2SINK
|
||||
LOGE("-N/--no-display requires screen recording (-r/--record) or sink to v4l2loopback device (--v4l2sink)");
|
||||
#else
|
||||
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#ifdef V4L2SINK
|
||||
#include <libavdevice/avdevice.h>
|
||||
#endif
|
||||
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
@ -28,6 +31,11 @@ print_version(void) {
|
|||
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
|
||||
LIBAVUTIL_VERSION_MINOR,
|
||||
LIBAVUTIL_VERSION_MICRO);
|
||||
#ifdef V4L2SINK
|
||||
fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
|
||||
LIBAVDEVICE_VERSION_MINOR,
|
||||
LIBAVDEVICE_VERSION_MICRO);
|
||||
#endif
|
||||
}
|
||||
|
||||
static SDL_LogPriority
|
||||
|
@ -89,6 +97,9 @@ main(int argc, char *argv[]) {
|
|||
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||
av_register_all();
|
||||
#endif
|
||||
#ifdef V4L2SINK
|
||||
avdevice_register_all();
|
||||
#endif
|
||||
|
||||
if (avformat_network_init()) {
|
||||
return 1;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "stream.h"
|
||||
#include "tiny_xpm.h"
|
||||
#include "video_buffer.h"
|
||||
#include "v4l2sink.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
|
||||
|
@ -38,6 +39,9 @@ static struct decoder decoder;
|
|||
static struct recorder recorder;
|
||||
static struct controller controller;
|
||||
static struct file_handler file_handler;
|
||||
#ifdef V4L2SINK
|
||||
static struct v4l2sink v4l2sink;
|
||||
#endif
|
||||
|
||||
static struct input_manager input_manager = {
|
||||
.controller = &controller,
|
||||
|
@ -285,6 +289,12 @@ scrcpy(const struct scrcpy_options *options) {
|
|||
bool controller_started = false;
|
||||
|
||||
bool record = !!options->record_filename;
|
||||
|
||||
#ifdef V4L2SINK
|
||||
bool v4l2sink_initialized = false;
|
||||
bool v4l2 = !!options->v4l2sink_device;
|
||||
#endif
|
||||
|
||||
struct server_params params = {
|
||||
.log_level = options->log_level,
|
||||
.crop = options->crop,
|
||||
|
@ -363,9 +373,22 @@ scrcpy(const struct scrcpy_options *options) {
|
|||
recorder_initialized = true;
|
||||
}
|
||||
|
||||
struct v4l2sink *sink = NULL;
|
||||
#ifdef V4L2SINK
|
||||
if (v4l2) {
|
||||
if (!v4l2sink_init(&v4l2sink,
|
||||
options->v4l2sink_device,
|
||||
frame_size)) {
|
||||
goto end;
|
||||
}
|
||||
sink = &v4l2sink;
|
||||
v4l2sink_initialized = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
av_log_set_callback(av_log_callback);
|
||||
|
||||
stream_init(&stream, server.video_socket, dec, rec);
|
||||
stream_init(&stream, server.video_socket, dec, rec, sink);
|
||||
|
||||
if (options->display) {
|
||||
if (options->control) {
|
||||
|
@ -467,6 +490,12 @@ end:
|
|||
recorder_destroy(&recorder);
|
||||
}
|
||||
|
||||
#ifdef V4L2SINK
|
||||
if (v4l2sink_initialized) {
|
||||
v4l2sink_destroy(&v4l2sink);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (file_handler_initialized) {
|
||||
file_handler_join(&file_handler);
|
||||
file_handler_destroy(&file_handler);
|
||||
|
|
|
@ -52,6 +52,7 @@ struct scrcpy_options {
|
|||
const char *render_driver;
|
||||
const char *codec_options;
|
||||
const char *encoder_name;
|
||||
const char *v4l2sink_device;
|
||||
enum sc_log_level log_level;
|
||||
enum sc_record_format record_format;
|
||||
struct sc_port_range port_range;
|
||||
|
@ -94,6 +95,7 @@ struct scrcpy_options {
|
|||
.render_driver = NULL, \
|
||||
.codec_options = NULL, \
|
||||
.encoder_name = NULL, \
|
||||
.v4l2sink_device = NULL, \
|
||||
.log_level = SC_LOG_LEVEL_INFO, \
|
||||
.record_format = SC_RECORD_FORMAT_AUTO, \
|
||||
.port_range = { \
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "decoder.h"
|
||||
#include "events.h"
|
||||
#include "recorder.h"
|
||||
#include "v4l2sink.h"
|
||||
#include "util/buffer_util.h"
|
||||
#include "util/log.h"
|
||||
|
||||
|
@ -89,6 +90,17 @@ process_frame(struct stream *stream, AVPacket *packet) {
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef V4L2SINK
|
||||
if (stream->v4l2sink) {
|
||||
packet->dts = packet->pts;
|
||||
|
||||
if (!v4l2sink_push(stream->v4l2sink, packet)) {
|
||||
LOGE("Could not send packet to v4l2sink");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -210,6 +222,20 @@ run_stream(void *data) {
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef V4L2SINK
|
||||
if (stream->v4l2sink) {
|
||||
if (!v4l2sink_open(stream->v4l2sink, codec)) {
|
||||
LOGE("Could not open v4l2sink");
|
||||
goto finally_close_decoder;
|
||||
}
|
||||
|
||||
if (!v4l2sink_start(stream->v4l2sink)) {
|
||||
LOGE("Could not start v4l2sink");
|
||||
goto finally_close_v4l2sink;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
stream->parser = av_parser_init(AV_CODEC_ID_H264);
|
||||
if (!stream->parser) {
|
||||
LOGE("Could not initialize parser");
|
||||
|
@ -253,6 +279,16 @@ finally_close_recorder:
|
|||
if (stream->recorder) {
|
||||
recorder_close(stream->recorder);
|
||||
}
|
||||
#ifdef V4L2SINK
|
||||
finally_close_v4l2sink:
|
||||
if (stream->v4l2sink) {
|
||||
v4l2sink_stop(stream->v4l2sink);
|
||||
LOGI("Finishing v4l2sink...");
|
||||
v4l2sink_join(stream->v4l2sink);
|
||||
|
||||
v4l2sink_close(stream->v4l2sink);
|
||||
}
|
||||
#endif
|
||||
finally_close_decoder:
|
||||
if (stream->decoder) {
|
||||
decoder_close(stream->decoder);
|
||||
|
@ -266,10 +302,11 @@ end:
|
|||
|
||||
void
|
||||
stream_init(struct stream *stream, socket_t socket,
|
||||
struct decoder *decoder, struct recorder *recorder) {
|
||||
struct decoder *decoder, struct recorder *recorder, struct v4l2sink *v4l2sink) {
|
||||
stream->socket = socket;
|
||||
stream->decoder = decoder,
|
||||
stream->recorder = recorder;
|
||||
stream->v4l2sink = v4l2sink;
|
||||
stream->has_pending = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ struct stream {
|
|||
sc_thread thread;
|
||||
struct decoder *decoder;
|
||||
struct recorder *recorder;
|
||||
struct v4l2sink *v4l2sink;
|
||||
AVCodecContext *codec_ctx;
|
||||
AVCodecParserContext *parser;
|
||||
// successive packets may need to be concatenated, until a non-config
|
||||
|
@ -28,7 +29,7 @@ struct stream {
|
|||
|
||||
void
|
||||
stream_init(struct stream *stream, socket_t socket,
|
||||
struct decoder *decoder, struct recorder *recorder);
|
||||
struct decoder *decoder, struct recorder *recorder, struct v4l2sink *v4l2sink);
|
||||
|
||||
bool
|
||||
stream_start(struct stream *stream);
|
||||
|
|
466
app/src/v4l2sink.c
Normal file
466
app/src/v4l2sink.c
Normal file
|
@ -0,0 +1,466 @@
|
|||
#include "config.h"
|
||||
|
||||
#ifdef V4L2SINK
|
||||
#include "v4l2sink.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <libavutil/time.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||
|
||||
static const AVOutputFormat *
|
||||
find_muxer(const char *name) {
|
||||
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||
void *opaque = NULL;
|
||||
#endif
|
||||
const AVOutputFormat *oformat = NULL;
|
||||
do {
|
||||
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||
oformat = av_muxer_iterate(&opaque);
|
||||
#else
|
||||
oformat = av_oformat_next(oformat);
|
||||
#endif
|
||||
// until null or with name "v4l2"
|
||||
} while (oformat && !strstr(oformat->name, name));
|
||||
return oformat;
|
||||
}
|
||||
|
||||
static struct v4l2sink_packet *
|
||||
v4l2sink_packet_new(const AVPacket *packet) {
|
||||
struct v4l2sink_packet *rec = malloc(sizeof(*rec));
|
||||
if (!rec) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// av_packet_ref() does not initialize all fields in old FFmpeg versions
|
||||
// See <https://github.com/Genymobile/scrcpy/issues/707>
|
||||
av_init_packet(&rec->packet);
|
||||
|
||||
if (av_packet_ref(&rec->packet, packet)) {
|
||||
free(rec);
|
||||
return NULL;
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
static void
|
||||
v4l2sink_packet_delete(struct v4l2sink_packet *rec) {
|
||||
av_packet_unref(&rec->packet);
|
||||
free(rec);
|
||||
}
|
||||
|
||||
static void
|
||||
v4l2sink_queue_clear(struct v4l2sink_queue *queue) {
|
||||
while (!queue_is_empty(queue)) {
|
||||
struct v4l2sink_packet *rec;
|
||||
queue_take(queue, next, &rec);
|
||||
v4l2sink_packet_delete(rec);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
v4l2sink_init(struct v4l2sink *v4l2sink,
|
||||
const char *devicename,
|
||||
struct size declared_frame_size) {
|
||||
v4l2sink->devicename = strdup(devicename);
|
||||
if (!v4l2sink->devicename) {
|
||||
LOGE("Could not strdup devicename for v4l2sink");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = sc_mutex_init(&v4l2sink->mutex);
|
||||
if (!ok) {
|
||||
LOGC("Could not create mutex for v4l2sink");
|
||||
free(v4l2sink->devicename);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&v4l2sink->queue_cond);
|
||||
if (!ok) {
|
||||
LOGC("Could not create cond for v4l2sink");
|
||||
sc_mutex_destroy(&v4l2sink->mutex);
|
||||
free(v4l2sink->devicename);
|
||||
return false;
|
||||
}
|
||||
|
||||
queue_init(&v4l2sink->queue);
|
||||
v4l2sink->stopped = false;
|
||||
v4l2sink->failed = false;
|
||||
v4l2sink->declared_frame_size = declared_frame_size;
|
||||
v4l2sink->header_written = false;
|
||||
v4l2sink->previous = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
v4l2sink_destroy(struct v4l2sink *v4l2sink) {
|
||||
sc_cond_destroy(&v4l2sink->queue_cond);
|
||||
sc_mutex_destroy(&v4l2sink->mutex);
|
||||
free(v4l2sink->devicename);
|
||||
}
|
||||
|
||||
bool
|
||||
v4l2sink_open(struct v4l2sink *v4l2sink, const AVCodec *codec) {
|
||||
v4l2sink->decoder_ctx = avcodec_alloc_context3(codec);
|
||||
if (!v4l2sink->decoder_ctx) {
|
||||
LOGC("Could not allocate decoder context for v4l2sink");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (avcodec_open2(v4l2sink->decoder_ctx, codec, NULL) < 0) {
|
||||
LOGE("Could not open decoder codec for v4l2sink");
|
||||
avcodec_free_context(&v4l2sink->decoder_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
const AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_RAWVIDEO);
|
||||
if (!encoder) {
|
||||
LOGE("RAWVIDEO encoder not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
v4l2sink->encoder_ctx = avcodec_alloc_context3(encoder);
|
||||
if (!v4l2sink->encoder_ctx) {
|
||||
LOGC("Could not allocate encoder context for v4l2sink");
|
||||
return false;
|
||||
}
|
||||
|
||||
v4l2sink->encoder_ctx->width=v4l2sink->declared_frame_size.width;
|
||||
v4l2sink->encoder_ctx->height=v4l2sink->declared_frame_size.height;
|
||||
v4l2sink->encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
v4l2sink->encoder_ctx->time_base.num =1;
|
||||
|
||||
if (avcodec_open2(v4l2sink->encoder_ctx, encoder, NULL) < 0) {
|
||||
LOGE("Could not open encoder codec for v4l2sink");
|
||||
avcodec_free_context(&v4l2sink->encoder_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
const AVOutputFormat *format = find_muxer("v4l2");
|
||||
if (!format) {
|
||||
LOGE("Could not find muxer for v4l2sink");
|
||||
return false;
|
||||
}
|
||||
|
||||
v4l2sink->ctx = avformat_alloc_context();
|
||||
if (!v4l2sink->ctx) {
|
||||
LOGE("Could not allocate output context for v4l2sink");
|
||||
return false;
|
||||
}
|
||||
|
||||
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
||||
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
|
||||
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
||||
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
||||
v4l2sink->ctx->oformat = (AVOutputFormat *) format;
|
||||
|
||||
av_dict_set(&v4l2sink->ctx->metadata, "comment",
|
||||
"Recorded by scrcpy " SCRCPY_VERSION, 0);
|
||||
|
||||
AVStream *ostream = avformat_new_stream(v4l2sink->ctx, encoder);
|
||||
if (!ostream) {
|
||||
avformat_free_context(v4l2sink->ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
|
||||
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
ostream->codecpar->codec_id = encoder->id;
|
||||
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
|
||||
ostream->codecpar->width = v4l2sink->declared_frame_size.width;
|
||||
ostream->codecpar->height = v4l2sink->declared_frame_size.height;
|
||||
#else
|
||||
ostream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
ostream->codec->codec_id = encoder->id;
|
||||
ostream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
ostream->codec->width = v4l2sink->declared_frame_size.width;
|
||||
ostream->codec->height = v4l2sink->declared_frame_size.height;
|
||||
#endif
|
||||
|
||||
int ret = avio_open(&v4l2sink->ctx->pb, v4l2sink->devicename,
|
||||
AVIO_FLAG_WRITE);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to open output device: %s", v4l2sink->devicename);
|
||||
// ostream will be cleaned up during context cleaning
|
||||
avformat_free_context(v4l2sink->ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
v4l2sink->decoded_frame = av_frame_alloc();
|
||||
v4l2sink->raw_packet = av_packet_alloc();
|
||||
|
||||
LOGI("V4l2sink started to device: %s", v4l2sink->devicename);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
v4l2sink_close(struct v4l2sink *v4l2sink) {
|
||||
avcodec_close(v4l2sink->decoder_ctx);
|
||||
avcodec_free_context(&v4l2sink->decoder_ctx);
|
||||
|
||||
avcodec_close(v4l2sink->encoder_ctx);
|
||||
avcodec_free_context(&v4l2sink->encoder_ctx);
|
||||
|
||||
if (v4l2sink->header_written) {
|
||||
int ret = av_write_trailer(v4l2sink->ctx);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to write trailer to %s", v4l2sink->devicename);
|
||||
v4l2sink->failed = true;
|
||||
}
|
||||
} else {
|
||||
v4l2sink->failed = true;
|
||||
}
|
||||
avio_close(v4l2sink->ctx->pb);
|
||||
avformat_free_context(v4l2sink->ctx);
|
||||
|
||||
if (v4l2sink->failed) {
|
||||
LOGE("Sink failed to %s", v4l2sink->devicename);
|
||||
} else {
|
||||
LOGI("Sink completed device: %s", v4l2sink->devicename);
|
||||
}
|
||||
|
||||
if (v4l2sink->decoded_frame) {
|
||||
av_frame_free(&v4l2sink->decoded_frame);
|
||||
}
|
||||
|
||||
if (v4l2sink->raw_packet) {
|
||||
av_packet_free(&v4l2sink->raw_packet);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
v4l2sink_write_header(struct v4l2sink *v4l2sink, const AVPacket *packet) {
|
||||
AVStream *ostream = v4l2sink->ctx->streams[0];
|
||||
|
||||
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
||||
if (!extradata) {
|
||||
LOGC("Could not allocate extradata");
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy the first packet to the extra data
|
||||
memcpy(extradata, packet->data, packet->size);
|
||||
|
||||
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
|
||||
ostream->codecpar->extradata = extradata;
|
||||
ostream->codecpar->extradata_size = packet->size;
|
||||
#else
|
||||
ostream->codec->extradata = extradata;
|
||||
ostream->codec->extradata_size = packet->size;
|
||||
#endif
|
||||
|
||||
v4l2sink->ctx->url = strdup(v4l2sink->devicename);
|
||||
|
||||
int ret = avformat_write_header(v4l2sink->ctx, NULL);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to write header to %s", v4l2sink->devicename);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
v4l2sink_rescale_packet(struct v4l2sink *v4l2sink, AVPacket *packet) {
|
||||
AVStream *ostream = v4l2sink->ctx->streams[0];
|
||||
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||
}
|
||||
|
||||
bool
|
||||
v4l2sink_write(struct v4l2sink *v4l2sink, AVPacket *packet) {
|
||||
if (!v4l2sink->header_written) {
|
||||
bool ok = v4l2sink_write_header(v4l2sink, packet);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
v4l2sink->header_written = true;
|
||||
}
|
||||
|
||||
if (packet->pts == AV_NOPTS_VALUE) {
|
||||
// ignore config packets
|
||||
return true;
|
||||
}
|
||||
|
||||
v4l2sink_rescale_packet(v4l2sink, packet);
|
||||
return av_write_frame(v4l2sink->ctx, packet) >= 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_v4l2sink(void *data) {
|
||||
struct v4l2sink *v4l2sink = data;
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&v4l2sink->mutex);
|
||||
|
||||
while (!v4l2sink->stopped && queue_is_empty(&v4l2sink->queue)) {
|
||||
sc_cond_wait(&v4l2sink->queue_cond, &v4l2sink->mutex);
|
||||
}
|
||||
|
||||
// if stopped is set, continue to process the remaining events before actually stopping
|
||||
|
||||
if (v4l2sink->stopped && queue_is_empty(&v4l2sink->queue)) {
|
||||
sc_mutex_unlock(&v4l2sink->mutex);
|
||||
struct v4l2sink_packet *last = v4l2sink->previous;
|
||||
if (last) {
|
||||
// assign an arbitrary duration to the last packet
|
||||
last->packet.duration = 100000;
|
||||
bool ok = v4l2sink_write(v4l2sink, &last->packet);
|
||||
if (!ok) {
|
||||
// failing to write the last frame is not very serious, no
|
||||
// future frame may depend on it, so the resulting file
|
||||
// will still be valid
|
||||
LOGW("Could not send last packet to v4l2sink");
|
||||
}
|
||||
v4l2sink_packet_delete(last);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
struct v4l2sink_packet *rec;
|
||||
queue_take(&v4l2sink->queue, next, &rec);
|
||||
|
||||
sc_mutex_unlock(&v4l2sink->mutex);
|
||||
|
||||
// v4l2sink->previous is only written from this thread, no need to lock
|
||||
struct v4l2sink_packet *previous = v4l2sink->previous;
|
||||
v4l2sink->previous = rec;
|
||||
|
||||
if (!previous) {
|
||||
// we just received the first packet
|
||||
continue;
|
||||
}
|
||||
|
||||
// config packets have no PTS, we must ignore them
|
||||
if (rec->packet.pts != AV_NOPTS_VALUE
|
||||
&& previous->packet.pts != AV_NOPTS_VALUE) {
|
||||
// we now know the duration of the previous packet
|
||||
previous->packet.duration = rec->packet.pts - previous->packet.pts;
|
||||
}
|
||||
|
||||
bool ok = v4l2sink_write(v4l2sink, &previous->packet);
|
||||
v4l2sink_packet_delete(previous);
|
||||
if (!ok) {
|
||||
LOGE("V4l2sink: Could not decode packet");
|
||||
|
||||
sc_mutex_lock(&v4l2sink->mutex);
|
||||
v4l2sink->failed = true;
|
||||
// discard pending packets
|
||||
v4l2sink_queue_clear(&v4l2sink->queue);
|
||||
sc_mutex_unlock(&v4l2sink->mutex);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LOGD("V4l2sink thread ended");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
v4l2sink_start(struct v4l2sink *v4l2sink) {
|
||||
LOGD("Starting v4l2sink thread");
|
||||
|
||||
bool ok = sc_thread_create(&v4l2sink->thread, run_v4l2sink, "v4l2sink",
|
||||
v4l2sink);
|
||||
if (!ok) {
|
||||
LOGC("Could not start v4l2sink thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
v4l2sink_stop(struct v4l2sink *v4l2sink) {
|
||||
sc_mutex_lock(&v4l2sink->mutex);
|
||||
v4l2sink->stopped = true;
|
||||
sc_cond_signal(&v4l2sink->queue_cond);
|
||||
sc_mutex_unlock(&v4l2sink->mutex);
|
||||
}
|
||||
|
||||
void
|
||||
v4l2sink_join(struct v4l2sink *v4l2sink) {
|
||||
sc_thread_join(&v4l2sink->thread, NULL);
|
||||
}
|
||||
|
||||
bool
|
||||
v4l2sink_push(struct v4l2sink *v4l2sink, const AVPacket *packet) {
|
||||
// the new decoding/encoding API has been introduced by:
|
||||
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
|
||||
#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
|
||||
int ret;
|
||||
if ((ret = avcodec_send_packet(v4l2sink->decoder_ctx, packet)) < 0) {
|
||||
LOGE("Could not send video packet x: %d", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((ret = avcodec_receive_frame(v4l2sink->decoder_ctx, v4l2sink->decoded_frame)) < 0) {
|
||||
LOGE("Could not receive video frame: %d", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((ret = avcodec_send_frame(v4l2sink->encoder_ctx, v4l2sink->decoded_frame)) < 0) {
|
||||
LOGE("Could not send video frame: %d", ret);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((ret = avcodec_receive_packet(v4l2sink->encoder_ctx, v4l2sink->raw_packet)) < 0) {
|
||||
LOGE("Could not receive video packet: %d", ret);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
int got_picture;
|
||||
int len = avcodec_decode_video2(v4l2sink->decoder_ctx,
|
||||
v4l2sink->decoded_frame,
|
||||
&got_picture,
|
||||
packet);
|
||||
if (len < 0) {
|
||||
LOGE("Could not decode video packet: %d", len);
|
||||
return false;
|
||||
}
|
||||
if (!got_picture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
len = avcodec_encode_video2(v4l2sink->encoder_ctx,
|
||||
v4l2sink->raw_packet,
|
||||
v4l2sink->decoded_frame,
|
||||
&got_picture);
|
||||
if (len < 0) {
|
||||
LOGE("Could not encode video packet: %d", len);
|
||||
return false;
|
||||
}
|
||||
if (!got_picture) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
sc_mutex_lock(&v4l2sink->mutex);
|
||||
assert(!v4l2sink->stopped);
|
||||
|
||||
if (v4l2sink->failed) {
|
||||
// reject any new packet (this will stop the stream)
|
||||
sc_mutex_unlock(&v4l2sink->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct v4l2sink_packet *rec = v4l2sink_packet_new(v4l2sink->raw_packet);
|
||||
if (!rec) {
|
||||
LOGC("Could not allocate v4l2sink packet");
|
||||
sc_mutex_unlock(&v4l2sink->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
queue_push(&v4l2sink->queue, next, rec);
|
||||
sc_cond_signal(&v4l2sink->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&v4l2sink->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
80
app/src/v4l2sink.h
Normal file
80
app/src/v4l2sink.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
#ifndef V4L2SINK_H
|
||||
#define V4L2SINK_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef V4L2SINK
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "scrcpy.h"
|
||||
#include "util/queue.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
struct v4l2sink_packet {
|
||||
AVPacket packet;
|
||||
struct v4l2sink_packet *next;
|
||||
};
|
||||
|
||||
struct v4l2sink_queue QUEUE(struct v4l2sink_packet);
|
||||
|
||||
struct v4l2sink {
|
||||
AVCodecContext *decoder_ctx;
|
||||
AVCodecContext *encoder_ctx;
|
||||
AVFrame *decoded_frame;
|
||||
AVPacket *raw_packet;
|
||||
|
||||
char *devicename;
|
||||
char *filename;
|
||||
enum sc_record_format format;
|
||||
AVFormatContext *ctx;
|
||||
struct size declared_frame_size;
|
||||
bool header_written;
|
||||
|
||||
sc_thread thread;
|
||||
sc_mutex mutex;
|
||||
sc_cond queue_cond;
|
||||
bool stopped; // set on recorder_stop() by the stream reader
|
||||
bool failed; // set on packet write failure
|
||||
struct v4l2sink_queue queue;
|
||||
|
||||
// we can write a packet only once we received the next one so that we can
|
||||
// set its duration (next_pts - current_pts)
|
||||
// "previous" is only accessed from the recorder thread, so it does not
|
||||
// need to be protected by the mutex
|
||||
struct v4l2sink_packet *previous;
|
||||
};
|
||||
|
||||
bool
|
||||
v4l2sink_init(struct v4l2sink *v4l2sink, const char *devicename, struct size declared_frame_size);
|
||||
|
||||
void
|
||||
v4l2sink_destroy(struct v4l2sink *v4l2sink);
|
||||
|
||||
bool
|
||||
v4l2sink_open(struct v4l2sink *v4l2sink, const AVCodec *input_codec);
|
||||
|
||||
void
|
||||
v4l2sink_close(struct v4l2sink *v4l2sink);
|
||||
|
||||
bool
|
||||
v4l2sink_start(struct v4l2sink *v4l2sink);
|
||||
|
||||
bool
|
||||
v4l2sink_push(struct v4l2sink *v4l2sink, const AVPacket *packet);
|
||||
|
||||
void
|
||||
v4l2sink_stop(struct v4l2sink *v4l2sink);
|
||||
|
||||
void
|
||||
v4l2sink_join(struct v4l2sink *v4l2sink);
|
||||
|
||||
#else
|
||||
struct v4l2sink { /* dummy struct for systems that don't support v4l2 */ };
|
||||
#endif //V4L2SINK
|
||||
|
||||
#endif //V4L2SINK_H
|
Loading…
Add table
Reference in a new issue