diff --git a/src/plugins/score-plugin-gfx/CMakeLists.txt b/src/plugins/score-plugin-gfx/CMakeLists.txt index 202aba4d99..10f7a8c8ed 100644 --- a/src/plugins/score-plugin-gfx/CMakeLists.txt +++ b/src/plugins/score-plugin-gfx/CMakeLists.txt @@ -136,7 +136,9 @@ set(HDRS Gfx/CameraSettings.hpp Gfx/CameraDevice.hpp Gfx/WindowDevice.hpp - + Gfx/LibavOutputDevice.hpp + Gfx/LibavOutputSettings.hpp + Gfx/LibavEncoder.hpp # Gfx/graph/hap/source/hap.h score_plugin_gfx.hpp @@ -200,9 +202,12 @@ set(SRCS Gfx/WindowDevice.cpp Gfx/GfxInputDevice.cpp + Gfx/LibavOutputDevice.cpp + Gfx/Settings/Model.cpp Gfx/Settings/Presenter.cpp Gfx/Settings/View.cpp + Gfx/LibavEncoder.cpp score_plugin_gfx.cpp diff --git a/src/plugins/score-plugin-gfx/Gfx/LibavEncoder.cpp b/src/plugins/score-plugin-gfx/Gfx/LibavEncoder.cpp new file mode 100644 index 0000000000..6ceb50b4b5 --- /dev/null +++ b/src/plugins/score-plugin-gfx/Gfx/LibavEncoder.cpp @@ -0,0 +1,712 @@ +#include "LibavEncoder.hpp" + +extern "C" { +#include +} + +#if SCORE_HAS_LIBAV +namespace Gfx +{ +LibavEncoder::LibavEncoder() +{ + av_dict_set(&opt, "fflags", "nobuffer", 0); + av_dict_set(&opt, "flags", "low_delay", 0); +} + +void LibavEncoder::enumerate() +{ +#if 0 + // enumerate all codecs and put into list + std::vector encoderList; + AVCodec* codec = nullptr; + while(codec = av_codec_next(codec)) + { + // try to get an encoder from the system + auto encoder = avcodec_find_encoder(codec->id); + if(encoder) + { + encoderList.push_back(encoder); + } + } + // enumerate all containers + AVOutputFormat* outputFormat = nullptr; + while(outputFormat = av_oformat_next(outputFormat)) + { + for(auto codec : encoderList) + { + // only add the codec if it can be used with this container + if(avformat_query_codec(outputFormat, codec->id, FF_COMPLIANCE_STRICT) == 1) + { + // add codec for container + } + } + } +#endif +} + +void LibavEncoder::encode( + AVCodecContext* enc_ctx, AVFrame* frame, AVPacket* pkt, FILE* outfile) +{ + int ret; + + /* send the frame to the encoder */ + if(frame) + printf("Send frame %3" PRId64 "\n", frame->pts); + + ret = avcodec_send_frame(enc_ctx, frame); + if(ret < 0) + { + fprintf(stderr, "Error sending a frame for encoding\n"); + exit(1); + } + + while(ret >= 0) + { + ret = avcodec_receive_packet(enc_ctx, pkt); + if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return; + else if(ret < 0) + { + fprintf(stderr, "Error during encoding\n"); + exit(1); + } + + printf("Write packet %3" PRId64 " (size=%5d)\n", pkt->pts, pkt->size); + fwrite(pkt->data, 1, pkt->size, outfile); + av_packet_unref(pkt); + } +} + +struct StreamOptions +{ + std::string name; + std::string codec; + ossia::flat_map options; +}; + +struct OutputStream +{ + const AVCodec* codec; + AVStream* st; + AVCodecContext* enc; + + /* pts of the next frame that will be generated */ + int64_t next_pts; + int samples_count; + + AVFrame* frame; + AVFrame* tmp_frame; + + AVPacket* tmp_pkt; + + struct SwsContext* sws_ctx; + struct SwrContext* swr_ctx; + + OutputStream(AVFormatContext* oc, const StreamOptions& opts) + { + /* find the encoder */ + codec = avcodec_find_encoder_by_name(opts.codec.c_str()); + // codec = avcodec_find_encoder(opts.codec_id); + if(!codec) + { + fprintf(stderr, "Could not find encoder for '%s'\n", opts.codec.c_str()); + exit(1); + } + + this->tmp_pkt = av_packet_alloc(); + if(!this->tmp_pkt) + { + fprintf(stderr, "Could not allocate AVPacket\n"); + exit(1); + } + + this->st = avformat_new_stream(oc, NULL); + if(!this->st) + { + fprintf(stderr, "Could not allocate stream\n"); + exit(1); + } + this->st->id = oc->nb_streams - 1; + AVCodecContext* c = avcodec_alloc_context3(codec); + if(!c) + { + fprintf(stderr, "Could not alloc an encoding context\n"); + exit(1); + } + this->enc = c; + + switch(codec->type) + { + case AVMEDIA_TYPE_AUDIO: { + c->sample_fmt = codec->sample_fmts ? codec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; + c->bit_rate = 64000; + c->sample_rate = 44100; + if(codec->supported_samplerates) + { + c->sample_rate = codec->supported_samplerates[0]; + for(int i = 0; codec->supported_samplerates[i]; i++) + { + if(codec->supported_samplerates[i] == 44100) + c->sample_rate = 44100; + } + } + constexpr AVChannelLayout layout = AV_CHANNEL_LAYOUT_STEREO; + av_channel_layout_copy(&c->ch_layout, &layout); + this->st->time_base = (AVRational){1, c->sample_rate}; + break; + } + + case AVMEDIA_TYPE_VIDEO: { + c->codec_id = codec->id; + + c->bit_rate = 400000; + /* Resolution must be a multiple of two. */ + c->width = 352; + c->height = 288; + /* timebase: This is the fundamental unit of time (in seconds) in terms + * of which frame timestamps are represented. For fixed-fps content, + * timebase should be 1/framerate and timestamp increments should be + * identical to 1. */ + this->st->time_base = (AVRational){1, 30}; + c->time_base = this->st->time_base; + c->framerate = AVRational{30, 1}; + + //c->gop_size = 12; /* emit one intra frame every twelve frames at most */ + + // ignored if frame->pict_type is AV_PICTURE_TYPE_I + c->gop_size = 0; + c->max_b_frames = 0; + c->pix_fmt = AV_PIX_FMT_YUV420P; + if(c->codec_id == AV_CODEC_ID_MPEG2VIDEO) + { + /* just for testing, we also add B-frames */ + c->max_b_frames = 2; + } + if(c->codec_id == AV_CODEC_ID_MPEG1VIDEO) + { + /* Needed to avoid using macroblocks in which some coeffs overflow. + * This does not happen with normal video, it just happens here as + * the motion of the chroma plane does not match the luma plane. */ + c->mb_decision = 2; + } + break; + } + + default: + break; + } + + /* Some formats want stream headers to be separate. */ + if(oc->oformat->flags & AVFMT_GLOBALHEADER) + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + void open_audio(AVFormatContext* oc, const AVCodec* codec, AVDictionary* opt_arg) + { + AVCodecContext* c; + int ret; + AVDictionary* opt = NULL; + + c = this->enc; + + /* open it */ + av_dict_copy(&opt, opt_arg, 0); + ret = avcodec_open2(c, codec, &opt); + av_dict_free(&opt); + if(ret < 0) + { + fprintf(stderr, "Could not open audio codec: %s\n", av_err2str(ret)); + exit(1); + } + + // FIXME + // int nb_samples; + // if(c->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) + // nb_samples = 10000; + // else + // nb_samples = c->frame_size; + // + // ost->frame = alloc_audio_frame(c->sample_fmt, &c->ch_layout, c->sample_rate, nb_samples); + // ost->tmp_frame = alloc_audio_frame( AV_SAMPLE_FMT_S16, &c->ch_layout, c->sample_rate, nb_samples); + + /* copy the stream parameters to the muxer */ + ret = avcodec_parameters_from_context(this->st->codecpar, c); + if(ret < 0) + { + fprintf(stderr, "Could not copy the stream parameters\n"); + exit(1); + } + + /* create resampler context */ + { + this->swr_ctx = swr_alloc(); + if(!this->swr_ctx) + { + fprintf(stderr, "Could not allocate resampler context\n"); + exit(1); + } + + auto in_chlayout = c->ch_layout; + auto out_chlayout = c->ch_layout; + auto in_rate = c->sample_rate; + auto out_rate = c->sample_rate; + auto in_fmt = AV_SAMPLE_FMT_FLTP; + auto out_fmt = c->sample_fmt; + + ret = swr_alloc_set_opts2( + &this->swr_ctx, &out_chlayout, out_fmt, out_rate, &in_chlayout, in_fmt, + in_rate, 0, nullptr); + + if(ret < 0) + { + fprintf(stderr, "Failed to initialize the resampling context\n"); + exit(1); + } + } + } + + static AVFrame* alloc_frame(enum AVPixelFormat pix_fmt, int width, int height) + { + AVFrame* frame; + int ret; + + frame = av_frame_alloc(); + if(!frame) + return NULL; + + frame->format = pix_fmt; + frame->width = width; + frame->height = height; + + /* allocate the buffers for the frame data */ + ret = av_frame_get_buffer(frame, 0); + if(ret < 0) + { + fprintf(stderr, "Could not allocate frame data.\n"); + exit(1); + } + + return frame; + } + + void open_video(AVFormatContext* oc, const AVCodec* codec, AVDictionary* opt_arg) + { + int ret; + AVCodecContext* c = this->enc; + AVDictionary* opt = NULL; + + av_dict_copy(&opt, opt_arg, 0); + + /* open the codec */ + ret = avcodec_open2(c, codec, &opt); + av_dict_free(&opt); + if(ret < 0) + { + fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret)); + exit(1); + } + + /* allocate and init a re-usable frame */ + // this->frame = alloc_frame(c->pix_fmt, c->width, c->height); + // if(!this->frame) + // { + // fprintf(stderr, "Could not allocate video frame\n"); + // exit(1); + // } + + /* If the output format is not YUV420P, then a temporary YUV420P + * picture is needed too. It is then converted to the required + * output format. */ + // If conversion is needed : + // this->tmp_frame = NULL; + // if(c->pix_fmt != AV_PIX_FMT_YUV420P) + // { + // this->tmp_frame = alloc_frame(AV_PIX_FMT_YUV420P, c->width, c->height); + // if(!this->tmp_frame) + // { + // fprintf(stderr, "Could not allocate temporary video frame\n"); + // exit(1); + // } + // } + + /* copy the stream parameters to the muxer */ + ret = avcodec_parameters_from_context(this->st->codecpar, c); + if(ret < 0) + { + fprintf(stderr, "Could not copy the stream parameters\n"); + exit(1); + } + } + + void open(AVFormatContext* oc, const AVCodec* codec, AVDictionary* opt_arg) + { + SCORE_ASSERT(oc); + SCORE_ASSERT(codec); + SCORE_ASSERT(opt_arg); + if(codec->type == AVMEDIA_TYPE_AUDIO) + { + open_audio(oc, codec, opt_arg); + } + else if(codec->type == AVMEDIA_TYPE_VIDEO) + { + open_video(oc, codec, opt_arg); + } + } + + void close(AVFormatContext* oc) + { + avcodec_free_context(&enc); + av_frame_free(&frame); + av_frame_free(&tmp_frame); + av_packet_free(&tmp_pkt); + sws_freeContext(sws_ctx); + swr_free(&swr_ctx); + } + + AVFrame* get_video_frame() + { + AVCodecContext* c = this->enc; + + /* check if we want to generate more frames */ + // if (av_compare_ts(ost->next_pts, c->time_base, + // STREAM_DURATION, (AVRational){ 1, 1 }) > 0) + // return NULL; + + /* when we pass a frame to the encoder, it may keep a reference to it + * internally; make sure we do not overwrite it here */ + if(av_frame_make_writable(this->frame) < 0) + exit(1); + + // if(c->pix_fmt != AV_PIX_FMT_YUV420P) + // { + // if(!this->sws_ctx) + // { + // this->sws_ctx = sws_getContext( + // c->width, c->height, AV_PIX_FMT_YUV420P, c->width, c->height, c->pix_fmt, + // SCALE_FLAGS, NULL, NULL, NULL); + // if(!this->sws_ctx) + // { + // fprintf(stderr, "Could not initialize the conversion context\n"); + // exit(1); + // } + // } + // fill_yuv_image(this->tmp_frame, this->next_pts, c->width, c->height); + // sws_scale( + // this->sws_ctx, (const uint8_t* const*)this->tmp_frame->data, + // this->tmp_frame->linesize, 0, c->height, this->frame->data, + // this->frame->linesize); + // } + // else + // { + // fill_yuv_image(this->frame, this->next_pts, c->width, c->height); + // } + + this->frame->pts = this->next_pts++; + + return this->frame; + } + + static int write_frame( + AVFormatContext* fmt_ctx, AVCodecContext* c, AVStream* st, AVFrame* frame, + AVPacket* pkt) + { + int ret; + + // send the frame to the encoder + ret = avcodec_send_frame(c, frame); + if(ret < 0) + { + fprintf(stderr, "Error sending a frame to the encoder: %s\n", av_err2str(ret)); + exit(1); + } + + while(ret >= 0) + { + ret = avcodec_receive_packet(c, pkt); + if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + break; + else if(ret < 0) + { + fprintf(stderr, "Error encoding a frame: %s\n", av_err2str(ret)); + exit(1); + } + + /* rescale output packet timestamp values from codec to stream timebase */ + av_packet_rescale_ts(pkt, c->time_base, st->time_base); + pkt->stream_index = st->index; + + /* Write the compressed frame to the media file. */ + // log_packet(fmt_ctx, pkt); + ret = av_interleaved_write_frame(fmt_ctx, pkt); + /* pkt is now blank (av_interleaved_write_frame() takes ownership of + * its contents and resets pkt), so that no unreferencing is necessary. + * This would be different if one used av_write_frame(). */ + if(ret < 0) + { + fprintf(stderr, "Error while writing output packet: %s\n", av_err2str(ret)); + exit(1); + } + } + + return ret == AVERROR_EOF ? 1 : 0; + } + + int write_video_frame(AVFormatContext* oc) + { + return write_frame(oc, enc, st, get_video_frame(), tmp_pkt); + } +}; +int LibavEncoder::test2() +{ + const char* filename = "/tmp/tata.mkv"; + int ret = avformat_alloc_output_context2(&m_formatContext, NULL, NULL, filename); + + if(ret < 0 || !m_formatContext) + { + fprintf(stderr, "Could not create format for '%s': %s\n", filename, av_err2str(ret)); + return 1; + } + + const AVOutputFormat* fmt = m_formatContext->oformat; + SCORE_ASSERT(fmt); + + qDebug() << fmt->name << fmt->long_name << fmt->audio_codec + << fmt->video_codec; // matroska / Matroska + // fmt->audio_codec: default audio codec for mkv + // fmt->video_codec: default video codec for mkv + auto default_audio_encoder = avcodec_find_encoder(fmt->audio_codec); + auto default_video_encoder = avcodec_find_decoder(fmt->video_codec); + qDebug() << "Codec:" << default_audio_encoder->name << default_video_encoder->name; + + /* Add the audio and video streams using the default format codecs + * and initialize the codecs. */ + if(fmt->video_codec != AV_CODEC_ID_NONE) + { + // add_stream(&video_st, oc, &video_codec, fmt->video_codec); + // have_video = 1; + // encode_video = 1; + } + if(fmt->audio_codec != AV_CODEC_ID_NONE) + { + // add_stream(&audio_st, oc, &audio_codec, fmt->audio_codec); + // have_audio = 1; + // encode_audio = 1; + } + + // For each parameter: + + std::vector streams; + // For all streams: + // Add them + { + StreamOptions opts; + opts.codec = "hevc_nvenc"; + streams.emplace_back(m_formatContext, opts); + } + + // For all streams: + // Open them + for(auto& stream : streams) + { + stream.open(m_formatContext, stream.codec, opt); + } + + // Dump all streams + { + int k = 0; + for(auto& stream : streams) + { + av_dump_format(m_formatContext, k++, filename, true); + } + } + + // If it's a file fopen it + if(!(fmt->flags & AVFMT_NOFILE)) + { + ret = avio_open(&m_formatContext->pb, filename, AVIO_FLAG_WRITE); + if(ret < 0) + { + fprintf(stderr, "Could not open '%s': %s\n", filename, av_err2str(ret)); + return 1; + } + } + + // Init stream header + ret = avformat_write_header(m_formatContext, &opt); + if(ret < 0) + { + fprintf(stderr, "Error occurred when opening output file: %s\n", av_err2str(ret)); + return 1; + } + + //////////////////////: + // Encode streams here + //////////////////////: + + // Close + { + av_write_trailer(m_formatContext); + + // For each stream + for(auto& stream : streams) + stream.close(m_formatContext); + + // if it's a file, fclose it + if(!(fmt->flags & AVFMT_NOFILE)) + avio_closep(&m_formatContext->pb); + + // We're done + avformat_free_context(m_formatContext); + } + return 0; +} + +void LibavEncoder::test() +{ + test2(); + return; +#if 0 + { + const char *filename, *codec_name; + + int i, ret, x, y; + FILE* f; + uint8_t endcode[] = {0, 0, 1, 0xb7}; + + filename = "/tmp/toto.mkv"; + codec_name = "hevc_nvenc"; + + /* find the mpeg1video encoder */ + m_codec = avcodec_find_encoder_by_name(codec_name); + if(!m_codec) + { + fprintf(stderr, "Codec '%s' not found\n", codec_name); + exit(1); + } + + m_codecContext = avcodec_alloc_context3(m_codec); + if(!m_codecContext) + { + fprintf(stderr, "Could not allocate video codec context\n"); + exit(1); + } + + pkt = av_packet_alloc(); + if(!pkt) + exit(1); + + /* put sample parameters */ + m_codecContext->bit_rate = 400000; + /* resolution must be a multiple of two */ + m_codecContext->width = 352; + m_codecContext->height = 288; + /* frames per second */ + m_codecContext->time_base = (AVRational){1, 25}; + m_codecContext->framerate = (AVRational){25, 1}; + + /* emit one intra frame every ten frames + * check frame pict_type before passing frame + * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I + * then gop_size is ignored and the output of encoder + * will always be I frame irrespective to gop_size + */ + m_codecContext->gop_size = 0; + m_codecContext->max_b_frames = 0; + m_codecContext->pix_fmt = AV_PIX_FMT_YUV420P; + + if(m_codec->id == AV_CODEC_ID_H264) + av_opt_set(m_codecContext->priv_data, "preset", "slow", 0); + + /* open it */ + ret = avcodec_open2(m_codecContext, m_codec, NULL); + if(ret < 0) + { + fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret)); + exit(1); + } + + f = fopen(filename, "wb"); + if(!f) + { + fprintf(stderr, "Could not open %s\n", filename); + exit(1); + } + + frame = av_frame_alloc(); + if(!frame) + { + fprintf(stderr, "Could not allocate video frame\n"); + exit(1); + } + frame->format = m_codecContext->pix_fmt; + frame->width = m_codecContext->width; + frame->height = m_codecContext->height; + + ret = av_frame_get_buffer(frame, 0); + if(ret < 0) + { + fprintf(stderr, "Could not allocate the video frame data\n"); + exit(1); + } + + /* encode 1 second of video */ + for(i = 0; i < 25; i++) + { + fflush(stdout); + + /* Make sure the frame data is writable. + On the first round, the frame is fresh from av_frame_get_buffer() + and therefore we know it is writable. + But on the next rounds, encode() will have called + avcodec_send_frame(), and the codec may have kept a reference to + the frame in its internal structures, that makes the frame + unwritable. + av_frame_make_writable() checks that and allocates a new buffer + for the frame only if necessary. + */ + ret = av_frame_make_writable(frame); + if(ret < 0) + exit(1); + + /* Prepare a dummy image. + In real code, this is where you would have your own logic for + filling the frame. FFmpeg does not care what you put in the + frame. + */ + + // m_codecContext->height + // m_codecContext->width + // Y + frame->data[0][0] = rand(); + // Cb Cr + frame->data[1]; // h, w, are divided by 2 + frame->data[2]; + + frame->pts = i; + + /* encode the image */ + encode(m_codecContext, frame, pkt, f); + } + + /* flush the encoder */ + encode(m_codecContext, NULL, pkt, f); + + /* Add sequence end code to have a real MPEG file. + It makes only sense because this tiny examples writes packets + directly. This is called "elementary stream" and only works for some + codecs. To create a valid file, you usually need to write packets + into a proper file format or protocol; see mux.c. + */ + if(m_codec->id == AV_CODEC_ID_MPEG1VIDEO || m_codec->id == AV_CODEC_ID_MPEG2VIDEO) + fwrite(endcode, 1, sizeof(endcode), f); + fclose(f); + + avcodec_free_context(&m_codecContext); + av_frame_free(&frame); + av_packet_free(&pkt); + } +#endif +} + +} +#endif diff --git a/src/plugins/score-plugin-gfx/Gfx/LibavEncoder.hpp b/src/plugins/score-plugin-gfx/Gfx/LibavEncoder.hpp new file mode 100644 index 0000000000..9fa67b0b89 --- /dev/null +++ b/src/plugins/score-plugin-gfx/Gfx/LibavEncoder.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#if SCORE_HAS_LIBAV + +extern "C" { +#include +#include +#include +#include +#include +} +namespace Gfx +{ + +struct LibavEncoder +{ + LibavEncoder(); + void enumerate(); + void encode(AVCodecContext* enc_ctx, AVFrame* frame, AVPacket* pkt, FILE* outfile); + + AVDictionary* opt = NULL; + + int test2(); + void test(); + AVFormatContext* m_formatContext{}; + +#if 0 + AVFrame* frame; + AVPacket* pkt; + + AVStream* m_avstream{}; + const AVCodec* m_codec{}; + AVCodecContext* m_codecContext{}; +#endif +}; + +} +#endif diff --git a/src/plugins/score-plugin-gfx/Gfx/LibavOutputDevice.cpp b/src/plugins/score-plugin-gfx/Gfx/LibavOutputDevice.cpp new file mode 100644 index 0000000000..5f652d2d9d --- /dev/null +++ b/src/plugins/score-plugin-gfx/Gfx/LibavOutputDevice.cpp @@ -0,0 +1,278 @@ +#include "LibavOutputDevice.hpp" +#if SCORE_HAS_LIBAV +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include + +SCORE_SERALIZE_DATASTREAM_DEFINE(Gfx::LibavOutputSettings); +namespace Gfx +{ +// ffmpeg -y -hwaccel cuda -hwaccel_output_format cuda -f video4linux2 -input_format mjpeg -framerate 30 -i /dev/video0 +// -fflags nobuffer +// -c:v hevc_nvenc +// -preset:v llhq +// -rc constqp +// -zerolatency 1 +// -delay 0 +// -forced-idr 1 +// -g 1 +// -cbr 1 +// -qp 10 +// -f matroska - + +struct EncoderConfiguration +{ + AVPixelFormat hardwareAcceleration{AV_PIX_FMT_NONE}; + std::string encoder; + int threads{}; +}; + +class libav_output_protocol : public ossia::net::protocol_base +{ +public: + explicit libav_output_protocol(GfxExecutionAction& ctx) + : protocol_base{flags{}} + , context{&ctx} + { + LibavEncoder encoder; + encoder.test(); + } + ~libav_output_protocol() { } + + GfxExecutionAction* context{}; + bool pull(ossia::net::parameter_base&) override { return false; } + bool push(const ossia::net::parameter_base&, const ossia::value& v) override + { + return false; + } + bool push_raw(const ossia::net::full_parameter_data&) override { return false; } + bool observe(ossia::net::parameter_base&, bool) override { return false; } + bool update(ossia::net::node_base& node_base) override { return false; } + + void start_execution() override + { + // Reset and start streaming + } + void stop_execution() override + { + // Stop streaming + } +}; + +class LibavOutputDevice final : public GfxOutputDevice +{ + W_OBJECT(LibavOutputDevice) +public: + using GfxOutputDevice::GfxOutputDevice; + ~LibavOutputDevice(); + +private: + bool reconnect() override; + ossia::net::device_base* getDevice() const override { return m_dev.get(); } + + mutable std::unique_ptr m_dev; +}; + +class LibavOutputSettingsWidget final : public Device::ProtocolSettingsWidget +{ +public: + LibavOutputSettingsWidget(QWidget* parent = nullptr); + + Device::DeviceSettings getSettings() const override; + void setSettings(const Device::DeviceSettings& settings) override; + +private: + void setDefaults(); + QLineEdit* m_deviceNameEdit{}; + Device::DeviceSettings m_settings; +}; + +LibavOutputDevice::~LibavOutputDevice() { } + +bool LibavOutputDevice::reconnect() +{ + disconnect(); + + try + { + auto set = this->settings().deviceSpecificSettings.value(); + auto plug = m_ctx.findPlugin(); + if(plug) + { + auto cam = std::make_shared<::Video::CameraInput>(); + /* + cam->load( + set.input.toStdString(), set.device.toStdString(), set.size.width(), + set.size.height(), set.fps, set.codec, set.pixelformat); +*/ + auto m_protocol = new libav_output_protocol{plug->exec}; + m_dev = std::make_unique( + std::unique_ptr(m_protocol), + this->settings().name.toStdString()); + } + // TODOengine->reload(&proto); + + // setLogging_impl(Device::get_cur_logging(isLogging())); + } + catch(std::exception& e) + { + qDebug() << "Could not connect: " << e.what(); + } + catch(...) + { + // TODO save the reason of the non-connection. + } + + return connected(); +} + +QString LibavOutputProtocolFactory::prettyName() const noexcept +{ + return QObject::tr("Libav Output"); +} + +QString LibavOutputProtocolFactory::category() const noexcept +{ + return StandardCategories::util; +} + +Device::DeviceInterface* LibavOutputProtocolFactory::makeDevice( + const Device::DeviceSettings& settings, const Explorer::DeviceDocumentPlugin& plugin, + const score::DocumentContext& ctx) +{ + return new LibavOutputDevice(settings, ctx); +} + +const Device::DeviceSettings& +LibavOutputProtocolFactory::defaultSettings() const noexcept +{ + static const Device::DeviceSettings settings = [&]() { + Device::DeviceSettings s; + s.protocol = concreteKey(); + s.name = "Libav Output"; + LibavOutputSettings specif; + s.deviceSpecificSettings = QVariant::fromValue(specif); + return s; + }(); + return settings; +} + +Device::AddressDialog* LibavOutputProtocolFactory::makeAddAddressDialog( + const Device::DeviceInterface& dev, const score::DocumentContext& ctx, + QWidget* parent) +{ + return nullptr; +} + +Device::AddressDialog* LibavOutputProtocolFactory::makeEditAddressDialog( + const Device::AddressSettings& set, const Device::DeviceInterface& dev, + const score::DocumentContext& ctx, QWidget* parent) +{ + return nullptr; +} + +Device::ProtocolSettingsWidget* LibavOutputProtocolFactory::makeSettingsWidget() +{ + return new LibavOutputSettingsWidget; +} + +QVariant LibavOutputProtocolFactory::makeProtocolSpecificSettings( + const VisitorVariant& visitor) const +{ + return makeProtocolSpecificSettings_T(visitor); +} + +void LibavOutputProtocolFactory::serializeProtocolSpecificSettings( + const QVariant& data, const VisitorVariant& visitor) const +{ + serializeProtocolSpecificSettings_T(data, visitor); +} + +bool LibavOutputProtocolFactory::checkCompatibility( + const Device::DeviceSettings& a, const Device::DeviceSettings& b) const noexcept +{ + return a.name != b.name; +} + +LibavOutputSettingsWidget::LibavOutputSettingsWidget(QWidget* parent) + : ProtocolSettingsWidget(parent) +{ + m_deviceNameEdit = new State::AddressFragmentLineEdit{this}; + checkForChanges(m_deviceNameEdit); + + auto layout = new QFormLayout; + layout->addRow(tr("Device Name"), m_deviceNameEdit); + setLayout(layout); + + setDefaults(); +} + +void LibavOutputSettingsWidget::setDefaults() +{ + m_deviceNameEdit->setText("Libav Output"); +} + +Device::DeviceSettings LibavOutputSettingsWidget::getSettings() const +{ + Device::DeviceSettings s = m_settings; + s.name = m_deviceNameEdit->text(); + s.protocol = LibavOutputProtocolFactory::static_concreteKey(); + return s; +} + +void LibavOutputSettingsWidget::setSettings(const Device::DeviceSettings& settings) +{ + m_settings = settings; + + // Clean up the name a bit + auto prettyName = settings.name; + if(!prettyName.isEmpty()) + { + prettyName = prettyName.split(':').front(); + prettyName = prettyName.split('(').front(); + prettyName.remove("/dev/"); + prettyName = prettyName.trimmed(); + ossia::net::sanitize_device_name(prettyName); + } + m_deviceNameEdit->setText(prettyName); +} + +} + +template <> +void DataStreamReader::read(const Gfx::LibavOutputSettings& n) +{ + insertDelimiter(); +} + +template <> +void DataStreamWriter::write(Gfx::LibavOutputSettings& n) +{ + checkDelimiter(); +} + +template <> +void JSONReader::read(const Gfx::LibavOutputSettings& n) +{ +} + +template <> +void JSONWriter::write(Gfx::LibavOutputSettings& n) +{ +} + +W_OBJECT_IMPL(Gfx::LibavOutputDevice) +#endif diff --git a/src/plugins/score-plugin-gfx/Gfx/LibavOutputDevice.hpp b/src/plugins/score-plugin-gfx/Gfx/LibavOutputDevice.hpp new file mode 100644 index 0000000000..fbe23c7c83 --- /dev/null +++ b/src/plugins/score-plugin-gfx/Gfx/LibavOutputDevice.hpp @@ -0,0 +1,61 @@ +#pragma once +#include +#if SCORE_HAS_LIBAV +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +class QComboBox; + +// Score part + +#include +#include +#include +#include + +namespace Gfx +{ +class LibavOutputProtocolFactory final : public Device::ProtocolFactory +{ + SCORE_CONCRETE("c8272026-5c7d-4835-84a3-74bef137761f") + QString prettyName() const noexcept override; + QString category() const noexcept override; + Device::DeviceInterface* makeDevice( + const Device::DeviceSettings& settings, + const Explorer::DeviceDocumentPlugin& plugin, + const score::DocumentContext& ctx) override; + const Device::DeviceSettings& defaultSettings() const noexcept override; + Device::AddressDialog* makeAddAddressDialog( + const Device::DeviceInterface& dev, const score::DocumentContext& ctx, + QWidget* parent) override; + Device::AddressDialog* makeEditAddressDialog( + const Device::AddressSettings&, const Device::DeviceInterface& dev, + const score::DocumentContext& ctx, QWidget*) override; + + Device::ProtocolSettingsWidget* makeSettingsWidget() override; + + QVariant makeProtocolSpecificSettings(const VisitorVariant& visitor) const override; + + void serializeProtocolSpecificSettings( + const QVariant& data, const VisitorVariant& visitor) const override; + + bool checkCompatibility( + const Device::DeviceSettings& a, + const Device::DeviceSettings& b) const noexcept override; +}; + +} + +SCORE_SERIALIZE_DATASTREAM_DECLARE(, Gfx::LibavOutputSettings); +Q_DECLARE_METATYPE(Gfx::LibavOutputSettings) +W_REGISTER_ARGTYPE(Gfx::LibavOutputSettings) +#endif diff --git a/src/plugins/score-plugin-gfx/Gfx/LibavOutputSettings.hpp b/src/plugins/score-plugin-gfx/Gfx/LibavOutputSettings.hpp new file mode 100644 index 0000000000..a35c9b57e4 --- /dev/null +++ b/src/plugins/score-plugin-gfx/Gfx/LibavOutputSettings.hpp @@ -0,0 +1,10 @@ +#pragma once +#include +#include + +namespace Gfx +{ +struct LibavOutputSettings +{ +}; +} diff --git a/src/plugins/score-plugin-gfx/score_plugin_gfx.cpp b/src/plugins/score-plugin-gfx/score_plugin_gfx.cpp index 3fb9299552..e0270b1d38 100644 --- a/src/plugins/score-plugin-gfx/score_plugin_gfx.cpp +++ b/src/plugins/score-plugin-gfx/score_plugin_gfx.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -63,7 +64,8 @@ std::vector score_plugin_gfx::factories( { return instantiate_factories< score::ApplicationContext, - FW