diff --git a/src/MultiFileRecorder.cpp b/src/MultiFileRecorder.cpp index 5686a2f..9da8a39 100644 --- a/src/MultiFileRecorder.cpp +++ b/src/MultiFileRecorder.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include "Log.h" #include "GstToolkit.h" #include "BaseToolkit.h" @@ -16,7 +14,7 @@ #include "MultiFileRecorder.h" MultiFileRecorder::MultiFileRecorder() : - fps_(0), width_(0), height_(0), bpp_(3), + fps_(0), width_(0), height_(0), pipeline_(nullptr), src_(nullptr), frame_count_(0), timestamp_(0), frame_duration_(0), cancel_(false), endofstream_(false), accept_buffer_(false), progress_(0.f) { @@ -66,55 +64,85 @@ void MultiFileRecorder::callback_enough_data (GstAppSrc *, gpointer p) grabber->accept_buffer_ = false; } -bool MultiFileRecorder::add_image (const std::string &image_filename) +bool MultiFileRecorder::add_image (const std::string &image_filename, GstCaps *caps) { - if (image_filename.empty()) + std::string uri = GstToolkit::filename_to_uri(image_filename); + if (uri.empty()) return false; - // read pix - int c = 0; - int w = 0; - int h = 0; - unsigned char* rgb = stbi_load(image_filename.c_str(), &w, &h, &c, bpp_); + // create playbin + GstElement *img_pipeline = gst_element_factory_make("playbin", "imgreader"); - if ( rgb && w == width_ && h == height_ && c == bpp_) { + // set uri of file to open + g_object_set(G_OBJECT(img_pipeline), "uri", uri.c_str(), NULL); - // new buffer - guint size = width_ * height_ * bpp_; - GstBuffer *buffer = gst_buffer_new_and_alloc (size); + // set flag to only read VIDEO + g_object_set(G_OBJECT(img_pipeline), "flags", 0x00000001, NULL); - // map gst buffer into a memory WRITE target - GstMapInfo map; - gst_buffer_map (buffer, &map, GST_MAP_WRITE); + // instruct sink to use the required caps (without framerate) + GstCaps *sinkcaps = gst_caps_copy(caps); + GValue v = {GST_TYPE_FRACTION, {{0}, {1}}}; + gst_caps_set_value(sinkcaps, "framerate", &v); - // transfer pixels from memory to buffer memory - memmove(map.data, rgb, size); + GstElement *sink = gst_element_factory_make("appsink", "imgsink"); + gst_app_sink_set_caps(GST_APP_SINK(sink), sinkcaps); - // un-map - gst_buffer_unmap (buffer, &map); + // set playbin sink + g_object_set(G_OBJECT(img_pipeline), "video-sink", sink, NULL); - // free stbi memory - stbi_image_free( rgb ); + /* Start the pipeline */ + gst_element_set_state(img_pipeline, GST_STATE_PLAYING); - //g_print("frame_added @ timestamp = %ld\n", timestamp_); - GST_BUFFER_DTS(buffer) = GST_BUFFER_PTS(buffer) = timestamp_; + /* Wait for the pipeline to preroll, i.e., wait for the image to be loaded */ + gst_element_get_state(img_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); - // set frame duration - buffer->duration = frame_duration_; + /* Get the sample from appsink */ + GstSample *sample; + g_signal_emit_by_name(sink, "pull-sample", &sample, NULL); - // monotonic time increment to keep fixed FPS - timestamp_ += frame_duration_; + /* Extract the buffer */ + GstBuffer *buffer_read = gst_sample_get_buffer(sample); - // push frame - if ( gst_app_src_push_buffer (src_, buffer) != GST_FLOW_OK ) - return false; + bool ret = false; + // map the buffer to access the data + GstMapInfo map_read; + if ( gst_buffer_map(buffer_read, &map_read, GST_MAP_READ) && map_read.size > 0 ) { + // map a new gst buffer into memory to WRITE target + GstMapInfo map_write; + GstBuffer *buffer_write = gst_buffer_new_and_alloc(map_read.size); + if ( gst_buffer_map(buffer_write, &map_write, GST_MAP_WRITE) ) { + + // transfer pixels from map_read memory to map_write memory (buffer to write to) + memmove(map_write.data, map_read.data, map_read.size); + + // un-map buffer + gst_buffer_unmap(buffer_write, &map_write); + + //g_print("frame_added @ timestamp = %ld\n", timestamp_); + GST_BUFFER_DTS(buffer_write) = GST_BUFFER_PTS(buffer_write) = timestamp_; + + // set frame duration + buffer_write->duration = frame_duration_; + + // monotonic time increment to keep fixed FPS + timestamp_ += frame_duration_; + + // push buffer as new frame in appsrc + ret = gst_app_src_push_buffer(src_, buffer_write) == GST_FLOW_OK; + } + + // unmap read buffer + gst_buffer_unmap(buffer_read, &map_read); } - else - return false; + /* Clean up */ + gst_caps_unref(caps); + gst_sample_unref(sample); + gst_element_set_state(img_pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(img_pipeline)); - return true; + return ret; } @@ -132,7 +160,19 @@ bool MultiFileRecorder::start_record (const std::string &video_filename) // create a gstreamer pipeline std::string description = "appsrc name=src ! queue ! videoconvert ! videoscale ! "; - description += VideoRecorder::profile_description[ profile_ ]; + + // test for a hardware accelerated encoder + if (Settings::application.render.gpu_decoding && (int) VideoRecorder::hardware_encoder.size() > 0 && + GstToolkit::has_feature(VideoRecorder::hardware_encoder[profile_]) ) { + + description += VideoRecorder::hardware_profile_description[Settings::application.record.profile]; + Log::Info("MultiFileRecorder use hardware accelerated encoder (%s)", VideoRecorder::hardware_encoder[profile_].c_str()); + } + // revert to software encoder + else + description += VideoRecorder::profile_description[profile_]; + + // qt muxer in .mov file description += "qtmux ! filesink name=sink"; // parse pipeline descriptor @@ -179,7 +219,7 @@ bool MultiFileRecorder::start_record (const std::string &video_filename) // specify recorder resolution and framerate in the source caps GstCaps *caps = gst_caps_new_simple ("video/x-raw", - "format", G_TYPE_STRING, bpp_ < 4 ? "RGB" : "RGBA", + "format", G_TYPE_STRING, "RGB", "width", G_TYPE_INT, width_ - width_%2, "height", G_TYPE_INT, height_ - height_%2, "framerate", GST_TYPE_FRACTION, fps_, 1, @@ -204,35 +244,7 @@ bool MultiFileRecorder::start_record (const std::string &video_filename) int max = 100; accept_buffer_ = false; while (!accept_buffer_ && --max > 0) - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - - -// // send request key frame upstream -// GstEvent* event = gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE, TRUE, 1); -// if (!gst_element_send_event( GST_ELEMENT(sink), event) ) -// Log::Warning("MultiFileRecorder: Failed to request key unit."); - -// GstPad *padsrc = gst_element_get_static_pad ( GST_ELEMENT (sink), "sink"); -// gst_pad_push_event(padsrc, gst_event_new_custom(GST_EVENT_CUSTOM_UPSTREAM, gst_structure_new("GstForceKeyUnit", "all-headers", -// G_TYPE_BOOLEAN, TRUE, NULL))); -// gst_object_unref (padsrc); - -// // send request key frame downstream -// GstPad *padsrc = gst_element_get_static_pad ( GST_ELEMENT (src_), "src"); -// GstStructure *s = gst_structure_new("GstForceKeyUnit", -// "timestamp", G_TYPE_UINT64, 0, -// "stream-time", G_TYPE_UINT64, 0, -// "running-time", G_TYPE_UINT64, 0, -// "all-headers", G_TYPE_BOOLEAN, TRUE, -// NULL); -// if ( !gst_pad_push_event(padsrc, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, s )) ) -// Log::Warning("MultiFileRecorder: Failed to force key unit."); -// gst_object_unref (padsrc); - - GstEvent* event = gst_video_event_new_downstream_force_key_unit (GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, TRUE, 1); - if (!gst_element_send_event( GST_ELEMENT(src_), event) ) - Log::Warning("MultiFileRecorder: Failed to force key unit."); - + std::this_thread::sleep_for(std::chrono::milliseconds(4)); return true; } @@ -324,25 +336,31 @@ std::string MultiFileRecorder::assemble (MultiFileRecorder *rec) rec->progress_ = 0.f; rec->width_ = 0; rec->height_ = 0; - rec->bpp_ = 0; // input files if ( rec->files_.size() < 1 ) { - Log::Warning("MultiFileRecorder: No image given."); + Log::Warning("MultiFileRecorder No image given."); + return filename; + } + + // get info first file + std::string uri = GstToolkit::filename_to_uri(rec->files_.front()); + MediaInfo media = MediaPlayer::UriDiscoverer(uri); + if (!media.valid || !media.isimage || media.width < 10 || media.height < 10) { + Log::Warning("MultiFileRecorder Invalid file %s.", rec->files_.front().c_str()); return filename; } // set recorder resolution from first image - stbi_info( rec->files_.front().c_str(), &rec->width_, &rec->height_, &rec->bpp_); - - if ( rec->width_ < 10 || rec->height_ < 10 || rec->bpp_ < 3 ) { - Log::Warning("MultiFileRecorder: Invalid image %s.", rec->files_.front().c_str()); - return filename; - } + rec->width_ = media.width; + rec->height_ = media.height; // progress increment float inc_ = 1.f / ( (float) rec->files_.size() + 2.f); + // keyframe increment + guint64 keyf_ = MAXI( 2, rec->files_.size() / 20); + // initialize rec->frame_count_ = 0; filename = BaseToolkit::common_prefix (rec->files_); @@ -350,7 +368,7 @@ std::string MultiFileRecorder::assemble (MultiFileRecorder *rec) filename += "image"; filename += "_sequence.mov"; - Log::Info("MultiFileRecorder creating %s, %d x %d px.", filename.c_str(), rec->width_, rec->height_); + Log::Info("MultiFileRecorder Creating %s, %d x %d px.", filename.c_str(), rec->width_, rec->height_); if ( rec->start_record( filename ) ) { @@ -363,16 +381,25 @@ std::string MultiFileRecorder::assemble (MultiFileRecorder *rec) if ( rec->cancel_ ) break; - if ( rec->add_image( *file ) ) + if ( rec->add_image( *file, gst_app_src_get_caps(rec->src_) ) ) { // validate file rec->frame_count_++; + + // add a key frame every + if ( rec->frame_count_%keyf_ < 1 ) { + GstEvent *event = gst_video_event_new_downstream_force_key_unit( + rec->timestamp_, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, FALSE, rec->frame_count_ / keyf_); + if (!gst_element_send_event(GST_ELEMENT(rec->src_), event)) + Log::Info("MultiFileRecorder Failed to force key unit %l.", rec->timestamp_); + } + } else - Log::Info("MultiFileRecorder could not add %s.", file->c_str()); + Log::Info("MultiFileRecorder Could not add %s.", file->c_str()); // pause in case appsrc buffer is full int max = 100; while (!rec->accept_buffer_ && --max > 0) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(4)); // progressing rec->progress_ += inc_; @@ -380,7 +407,7 @@ std::string MultiFileRecorder::assemble (MultiFileRecorder *rec) // Give more explanation for possible errors if ( rec->frame_count_ < rec->files_.size()) - Log::Info("MultiFileRecorder not fully successful; are all images %d x %d px?",rec->width_, rec->height_); + Log::Info("MultiFileRecorder Not fully successful; are all images %d x %d px?",rec->width_, rec->height_); // close file properly if ( rec->end_record() ) diff --git a/src/MultiFileRecorder.h b/src/MultiFileRecorder.h index 4359d98..e8b9777 100644 --- a/src/MultiFileRecorder.h +++ b/src/MultiFileRecorder.h @@ -44,7 +44,7 @@ protected: // gstreamer functions static std::string assemble (MultiFileRecorder *rec); bool start_record (const std::string &video_filename); - bool add_image (const std::string &image_filename); + bool add_image (const std::string &image_filename, GstCaps *caps); bool end_record(); // gstreamer callbacks @@ -59,7 +59,6 @@ private: int fps_; int width_; int height_; - int bpp_; // encoder std::list files_;