Added Stream Discoverer to detect frame size from a gstreamer pipeline

Previous use of Stream are not affected (the discoverer is passively returning the given width and height). But if the Stream is created without dimensions, it will run a discoverer to try to get preroll frames and detect width and height from there.
This commit is contained in:
Bruno Herbelin
2021-12-30 00:15:43 +01:00
parent bc540044ac
commit 033d41863a
5 changed files with 153 additions and 19 deletions

View File

@@ -282,8 +282,8 @@ void MediaPlayer::reopen()
} }
} }
void MediaPlayer::execute_open() void MediaPlayer::execute_open()
{ {
// Create gstreamer pipeline : // Create gstreamer pipeline :
// "uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! appsink " // "uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! appsink "
// equivalent to command line // 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); g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL);
gst_pipeline_set_auto_flush_bus( GST_PIPELINE(pipeline_), true); 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) + std::string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(media_.width) +
",height=" + std::to_string(media_.height); ",height=" + std::to_string(media_.height);
GstCaps *caps = gst_caps_from_string(capstring.c_str()); GstCaps *caps = gst_caps_from_string(capstring.c_str());
@@ -403,7 +403,7 @@ void MediaPlayer::execute_open()
gst_caps_unref (caps); gst_caps_unref (caps);
#ifdef USE_GST_OPENGL_SYNC_HANDLER #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_)); Rendering::LinkPipeline(GST_PIPELINE (pipeline_));
#endif #endif
@@ -684,7 +684,7 @@ MediaPlayer::LoopMode MediaPlayer::loop() const
{ {
return loop_; return loop_;
} }
void MediaPlayer::setLoop(MediaPlayer::LoopMode mode) void MediaPlayer::setLoop(MediaPlayer::LoopMode mode)
{ {
loop_ = mode; loop_ = mode;
@@ -1034,7 +1034,7 @@ void MediaPlayer::execute_loop_command()
{ {
if (loop_==LOOP_REWIND) { if (loop_==LOOP_REWIND) {
rewind(); rewind();
} }
else if (loop_==LOOP_BIDIRECTIONAL) { else if (loop_==LOOP_BIDIRECTIONAL) {
rate_ *= - 1.f; rate_ *= - 1.f;
execute_seek_command(); execute_seek_command();
@@ -1053,7 +1053,7 @@ void MediaPlayer::execute_seek_command(GstClockTime target, bool force)
GstClockTime seek_pos = target; GstClockTime seek_pos = target;
// no target given // no target given
if (target == GST_CLOCK_TIME_NONE) if (target == GST_CLOCK_TIME_NONE)
// create seek event with current position (rate changed ?) // create seek event with current position (rate changed ?)
seek_pos = position_; seek_pos = position_;
// target is given but useless // target is given but useless
@@ -1106,12 +1106,12 @@ void MediaPlayer::setPlaySpeed(double s)
if (media_.isimage) if (media_.isimage)
return; 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); rate_ = CLAMP(s, -MAX_PLAY_SPEED, MAX_PLAY_SPEED);
// skip interval [-MIN_PLAY_SPEED MIN_PLAY_SPEED] // skip interval [-MIN_PLAY_SPEED MIN_PLAY_SPEED]
if (ABS(rate_) < MIN_PLAY_SPEED) if (ABS(rate_) < MIN_PLAY_SPEED)
rate_ = SIGN(rate_) * MIN_PLAY_SPEED; rate_ = SIGN(rate_) * MIN_PLAY_SPEED;
// apply with seek // apply with seek
execute_seek_command(); execute_seek_command();
} }

View File

@@ -92,23 +92,107 @@ guint Stream::texture() const
return textureindex_; 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<StreamInfo *>(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<std::mutex> 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) void Stream::open(const std::string &gstreamer_description, guint w, guint h)
{ {
if (w != width_ || h != height_ ) if ( w != width_ || h != height_ )
textureinitialized_ = false; textureinitialized_ = false;
// set gstreamer pipeline source // set gstreamer pipeline source
description_ = gstreamer_description; description_ = gstreamer_description;
width_ = w;
height_ = h;
// close before re-openning // close before re-openning
if (isOpen()) if (isOpen())
close(); close();
// open the stream // open when ready
execute_open(); discoverer_ = std::async(StreamDiscoverer, description_, w, h);
} }
@@ -233,8 +317,13 @@ void Stream::Frame::unmap()
void Stream::close() void Stream::close()
{ {
// not openned? // not openned?
if (!opened_) if (!opened_) {
// wait for loading to finish
if (discoverer_.valid())
discoverer_.wait();
// nothing else to change
return; return;
}
// un-ready // un-ready
opened_ = false; opened_ = false;
@@ -523,6 +612,17 @@ void Stream::update()
// not ready yet // not ready yet
if (!opened_){ 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 // wait next frame to display
return; return;
} }

View File

@@ -5,6 +5,7 @@
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <future> #include <future>
#include <condition_variable>
// GStreamer // GStreamer
#include <gst/pbutils/pbutils.h> #include <gst/pbutils/pbutils.h>
@@ -15,6 +16,23 @@ class Visitor;
#define N_FRAME 3 #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 { class Stream {
public: public:
@@ -33,7 +51,7 @@ public:
/** /**
* Open a media using gstreamer pipeline keyword * 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 * Get description string
* */ * */
@@ -132,6 +150,7 @@ protected:
guint height_; guint height_;
bool single_frame_; bool single_frame_;
bool live_; bool live_;
std::future<StreamInfo> discoverer_;
// GST & Play status // GST & Play status
GstClockTime position_; GstClockTime position_;

View File

@@ -17,6 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
**/ **/
#include <string>
#include <sstream> #include <sstream>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
@@ -45,8 +46,11 @@ void GenericStreamSource::setDescription(const std::string &desc)
{ {
Log::Notify("Creating Source with Stream description '%s'", desc.c_str()); 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 // open gstreamer
stream_->open(desc); stream_->open(pipeline);
stream_->play(true); stream_->play(true);
// will be ready after init and one frame rendered // will be ready after init and one frame rendered

View File

@@ -5819,13 +5819,24 @@ void ShowSandbox(bool* p_open)
ImGui::Separator(); 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); ImGui::InputText("gstreamer pipeline", buf1, 1280);
if (ImGui::Button("Create Generic Stream Source") ) 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] = ""; static char str[128] = "";
ImGui::InputText("Command", str, IM_ARRAYSIZE(str)); ImGui::InputText("Command", str, IM_ARRAYSIZE(str));
if ( ImGui::Button("Execute") ) if ( ImGui::Button("Execute") )