diff --git a/MediaPlayer.cpp b/MediaPlayer.cpp index efe9502..f105eb5 100644 --- a/MediaPlayer.cpp +++ b/MediaPlayer.cpp @@ -282,8 +282,8 @@ void MediaPlayer::reopen() } } -void MediaPlayer::execute_open() -{ +void MediaPlayer::execute_open() +{ // Create gstreamer pipeline : // "uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! appsink " // equivalent to command line @@ -341,7 +341,7 @@ void MediaPlayer::execute_open() g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL); gst_pipeline_set_auto_flush_bus( GST_PIPELINE(pipeline_), true); - // GstCaps *caps = gst_static_caps_get (&frame_render_caps); + // GstCaps *caps = gst_static_caps_get (&frame_render_caps); std::string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(media_.width) + ",height=" + std::to_string(media_.height); GstCaps *caps = gst_caps_from_string(capstring.c_str()); @@ -403,7 +403,7 @@ void MediaPlayer::execute_open() gst_caps_unref (caps); #ifdef USE_GST_OPENGL_SYNC_HANDLER - // capture bus signals to force a unique opengl context for all GST elements + // capture bus signals to force a unique opengl context for all GST elements Rendering::LinkPipeline(GST_PIPELINE (pipeline_)); #endif @@ -684,7 +684,7 @@ MediaPlayer::LoopMode MediaPlayer::loop() const { return loop_; } - + void MediaPlayer::setLoop(MediaPlayer::LoopMode mode) { loop_ = mode; @@ -1034,7 +1034,7 @@ void MediaPlayer::execute_loop_command() { if (loop_==LOOP_REWIND) { rewind(); - } + } else if (loop_==LOOP_BIDIRECTIONAL) { rate_ *= - 1.f; execute_seek_command(); @@ -1053,7 +1053,7 @@ void MediaPlayer::execute_seek_command(GstClockTime target, bool force) GstClockTime seek_pos = target; // no target given - if (target == GST_CLOCK_TIME_NONE) + if (target == GST_CLOCK_TIME_NONE) // create seek event with current position (rate changed ?) seek_pos = position_; // target is given but useless @@ -1106,12 +1106,12 @@ void MediaPlayer::setPlaySpeed(double s) if (media_.isimage) return; - // bound to interval [-MAX_PLAY_SPEED MAX_PLAY_SPEED] + // bound to interval [-MAX_PLAY_SPEED MAX_PLAY_SPEED] rate_ = CLAMP(s, -MAX_PLAY_SPEED, MAX_PLAY_SPEED); // skip interval [-MIN_PLAY_SPEED MIN_PLAY_SPEED] if (ABS(rate_) < MIN_PLAY_SPEED) rate_ = SIGN(rate_) * MIN_PLAY_SPEED; - + // apply with seek execute_seek_command(); } diff --git a/Stream.cpp b/Stream.cpp index 85e75d8..6bba9f8 100644 --- a/Stream.cpp +++ b/Stream.cpp @@ -92,23 +92,107 @@ guint Stream::texture() const return textureindex_; } +GstFlowReturn callback_stream_discoverer (GstAppSink *sink, gpointer p) +{ + GstFlowReturn ret = GST_FLOW_OK; + + // blocking read pre-roll sample + GstSample *sample = gst_app_sink_pull_preroll(sink); + if (sample != NULL) { + // access info structure + StreamInfo *info = static_cast(p); + // get caps of the sample + GstVideoInfo v_frame_video_info_; + GstCaps *caps = gst_sample_get_caps(sample); + if (gst_video_info_from_caps (&v_frame_video_info_, caps)) { + // fill the info + info->width = v_frame_video_info_.width; + info->height = v_frame_video_info_.height; + // release info to let StreamDiscoverer go forward + info->discovered.notify_all(); + } + gst_caps_unref(caps); + } + else + ret = GST_FLOW_FLUSHING; + + gst_sample_unref (sample); + + return ret; +} + +StreamInfo StreamDiscoverer(const std::string &description, guint w, guint h) +{ + // the stream info to return + StreamInfo info; + + // obvious fast answer: valid values are provided in argument + if (w > 0 && h > 0 ) { + info.width = w; + info.height = h; + } + // otherwise, run a test pipeline to discover the size of the stream + else { + // complete the pipeline description with an appsink (to add a callback) + std::string _description = description; + _description += " ! appsink name=sink"; + + // try to launch the pipeline + GError *error = NULL; + GstElement *_pipeline = gst_parse_launch (_description.c_str(), &error); + if (error == NULL) { + + // some sanity config + gst_pipeline_set_auto_flush_bus( GST_PIPELINE(_pipeline), true); + + // get the appsink + GstElement *sink = gst_bin_get_by_name (GST_BIN (_pipeline), "sink"); + if (sink) { + + // add a preroll callback + GstAppSinkCallbacks callbacks; + callbacks.new_preroll = callback_stream_discoverer; + gst_app_sink_set_callbacks (GST_APP_SINK(sink), &callbacks, &info, NULL); + + // start to play the pipeline + gst_element_set_state (_pipeline, GST_STATE_PLAYING); + + // wait for the callback_stream_discoverer to return + std::mutex mtx; + std::unique_lock lck(mtx); + // if waited more than 2 seconds, its dead :( + if ( info.discovered.wait_for(lck,std::chrono::seconds(2)) == std::cv_status::timeout) + Log::Warning("Failed to discover stream size."); + + // stop and delete pipeline + GstStateChangeReturn ret = gst_element_set_state (_pipeline, GST_STATE_NULL); + if (ret == GST_STATE_CHANGE_ASYNC) { + GstState state; + gst_element_get_state (_pipeline, &state, NULL, GST_CLOCK_TIME_NONE); + } + gst_object_unref (_pipeline); + } + } + } + // at this point, the info should be filled + return info; +} void Stream::open(const std::string &gstreamer_description, guint w, guint h) { - if (w != width_ || h != height_ ) + if ( w != width_ || h != height_ ) textureinitialized_ = false; // set gstreamer pipeline source description_ = gstreamer_description; - width_ = w; - height_ = h; // close before re-openning if (isOpen()) close(); - // open the stream - execute_open(); + // open when ready + discoverer_ = std::async(StreamDiscoverer, description_, w, h); + } @@ -233,8 +317,13 @@ void Stream::Frame::unmap() void Stream::close() { // not openned? - if (!opened_) + if (!opened_) { + // wait for loading to finish + if (discoverer_.valid()) + discoverer_.wait(); + // nothing else to change return; + } // un-ready opened_ = false; @@ -523,6 +612,17 @@ void Stream::update() // not ready yet if (!opened_){ + if (discoverer_.valid()) { + // try to get info from discoverer + if (discoverer_.wait_for( std::chrono::milliseconds(4) ) == std::future_status::ready ) + { + // got all info needed for openning ! + StreamInfo i(discoverer_.get()); + width_ = i.width; + height_ = i.height; + execute_open(); + } + } // wait next frame to display return; } diff --git a/Stream.h b/Stream.h index 0161bca..465e7b2 100644 --- a/Stream.h +++ b/Stream.h @@ -5,6 +5,7 @@ #include #include #include +#include // GStreamer #include @@ -15,6 +16,23 @@ class Visitor; #define N_FRAME 3 +struct StreamInfo { + + guint width; + guint height; + std::condition_variable discovered; + + StreamInfo() { + width = 640; + height = 480; + } + + StreamInfo(const StreamInfo& b) { + width = b.width; + height = b.height; + } +}; + class Stream { public: @@ -33,7 +51,7 @@ public: /** * Open a media using gstreamer pipeline keyword * */ - void open(const std::string &gstreamer_description, guint w = 1024, guint h = 576); + void open(const std::string &gstreamer_description, guint w = 0, guint h = 0); /** * Get description string * */ @@ -132,6 +150,7 @@ protected: guint height_; bool single_frame_; bool live_; + std::future discoverer_; // GST & Play status GstClockTime position_; diff --git a/StreamSource.cpp b/StreamSource.cpp index 77e7241..2773891 100644 --- a/StreamSource.cpp +++ b/StreamSource.cpp @@ -17,6 +17,7 @@ * along with this program. If not, see . **/ +#include #include #include @@ -45,8 +46,11 @@ void GenericStreamSource::setDescription(const std::string &desc) { Log::Notify("Creating Source with Stream description '%s'", desc.c_str()); + std::string pipeline = desc; + pipeline.append(" ! queue max-size-buffers=10 ! videoconvert"); + // open gstreamer - stream_->open(desc); + stream_->open(pipeline); stream_->play(true); // will be ready after init and one frame rendered diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index d83d365..2c658a1 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -5819,13 +5819,24 @@ void ShowSandbox(bool* p_open) ImGui::Separator(); - static char buf1[1280] = "videotestsrc pattern=smpte"; + static Source *tmp = nullptr; +// static char buf1[1280] = "videotestsrc pattern=smpte"; +// static char buf1[1280] = "udpsrc port=5000 buffer-size=200000 ! h264parse ! avdec_h264"; + static char buf1[1280] = "srtsrc uri=\"srt://192.168.0.37:5000?mode=listener\" ! decodebin "; ImGui::InputText("gstreamer pipeline", buf1, 1280); if (ImGui::Button("Create Generic Stream Source") ) { - Mixer::manager().addSource(Mixer::manager().createSourceStream(buf1)); + tmp = Mixer::manager().createSourceStream(buf1); + Mixer::manager().addSource( tmp ); + } + ImGui::SameLine(); + if ( tmp && ImGui::Button("delete") ) + { + Mixer::manager().deleteSource( tmp ); + tmp = nullptr; } + ImGui::Separator(); static char str[128] = ""; ImGui::InputText("Command", str, IM_ARRAYSIZE(str)); if ( ImGui::Button("Execute") )