From 77447ff6c6f0ac2a91b41a00a15c2a573b7e2587 Mon Sep 17 00:00:00 2001 From: Marco Martinelli <6640057+martinellimarco@users.noreply.github.com> Date: Sun, 4 Apr 2021 00:10:44 +0200 Subject: [PATCH] v4l2loopback support --- app/meson.build | 10 +- app/src/cli.c | 25 ++- app/src/main.c | 11 ++ app/src/scrcpy.c | 31 ++- app/src/scrcpy.h | 2 + app/src/stream.c | 39 +++- app/src/stream.h | 3 +- app/src/v4l2sink.c | 466 +++++++++++++++++++++++++++++++++++++++++++++ app/src/v4l2sink.h | 80 ++++++++ 9 files changed, 661 insertions(+), 6 deletions(-) create mode 100644 app/src/v4l2sink.c create mode 100644 app/src/v4l2sink.h diff --git a/app/meson.build b/app/meson.build index d230702eb1..aa941fe0d3 100644 --- a/app/meson.build +++ b/app/meson.build @@ -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() diff --git a/app/src/cli.c b/app/src/cli.c index 484c48ef6a..e7757d25c3 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -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; } diff --git a/app/src/main.c b/app/src/main.c index e1e44f688a..d7042136e6 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -6,6 +6,9 @@ #include #include #include +#ifdef V4L2SINK +#include +#endif #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem #include @@ -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; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 624ddbca87..b70bab4314 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -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 @@ scrcpy(const struct scrcpy_options *options) { 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); diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 8ee70f606b..f12de1c3d7 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -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 = { \ diff --git a/app/src/stream.c b/app/src/stream.c index ba72f1642e..45d0f05e0b 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -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 @@ run_stream(void *data) { 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 @@ run_stream(void *data) { 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; } diff --git a/app/src/stream.h b/app/src/stream.h index 784e0402e9..fca5ea66a7 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -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); diff --git a/app/src/v4l2sink.c b/app/src/v4l2sink.c new file mode 100644 index 0000000000..96fc8ee73d --- /dev/null +++ b/app/src/v4l2sink.c @@ -0,0 +1,466 @@ +#include "config.h" + +#ifdef V4L2SINK +#include "v4l2sink.h" + +#include +#include + +#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 + 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) + // + 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: +// +#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 diff --git a/app/src/v4l2sink.h b/app/src/v4l2sink.h new file mode 100644 index 0000000000..f1e4f0dbbf --- /dev/null +++ b/app/src/v4l2sink.h @@ -0,0 +1,80 @@ +#ifndef V4L2SINK_H +#define V4L2SINK_H + +#include "config.h" + +#ifdef V4L2SINK + +#include "common.h" + +#include +#include + +#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