Post 03: Creating a video with gstreamer and C++/Rust
في المقال السابق أنشأنا ملف فيديو بصيغة MKV يحتوي على فيديو مضغوط باستخدام H.264 باستخدام أداة gst-launch-1.0 .
تُعد أداة gst-launch-1.0 وسيلة ممتازة لتجربة خطوط المعالجة واختبارها بسرعة، لكنها ليست الطريقة المستخدمة داخل التطبيقات الحقيقية. في هذا المقال سنبني نفس خط المعالجة السابق ولكن باستخدام لغة C++ ، وهي الخطوة الأولى نحو تطوير تطبيقات الوسائط المتعددة باستخدام GStreamer .
المتطلبات
إذا كنت قد اتبعت المقالات السابقة فمن المفترض أن معظم الحزم المطلوبة مثبتة بالفعل.
لغة C++
sudo apt-get install -y gcc pkg-config curl
sudo dnf install -y gcc pkgconf-pkg-config curl
sudo pacman -S --needed gcc pkgconf curl
لغة Rust
sudo apt-get install rustc cargo
كود البرنامج
mkdir gstreamer-cpp
cd gstreamer-cpp
code main.cpp
تستطيع الان كتابه التالي في main.cpp
#include <gst/gst.h>
int main(int argc, char *argv[]) {
gst_init(&argc, &argv);
GstElement *pipeline = gst_pipeline_new("test-pipeline");
GstElement *src = gst_element_factory_make("videotestsrc", "src");
GstElement *capsfilter = gst_element_factory_make("capsfilter", "capsfilter");
GstElement *enc = gst_element_factory_make("x264enc", "enc");
GstElement *parse = gst_element_factory_make("h264parse", "parse");
GstElement *mux = gst_element_factory_make("matroskamux", "mux");
GstElement *sink = gst_element_factory_make("filesink", "sink");
if (!pipeline || !src || !capsfilter || !enc || !parse || !mux || !sink) {
g_printerr("Failed to create elements\n");
return -1;
}
g_object_set(src, "num-buffers", 90, nullptr);
g_object_set(sink, "location", "test.mkv", nullptr);
GstCaps *caps = gst_caps_new_simple("video/x-raw",
"width", G_TYPE_INT, 1280,
"height", G_TYPE_INT, 720,
"framerate", GST_TYPE_FRACTION, 30, 1,
nullptr);
g_object_set(capsfilter, "caps", caps, nullptr);
gst_caps_unref(caps);
gst_bin_add_many(GST_BIN(pipeline), src, capsfilter, enc, parse, mux, sink, nullptr);
if (!gst_element_link_many(src, capsfilter, enc, parse, mux, sink, nullptr)) {
g_printerr("Failed to link elements\n");
gst_object_unref(pipeline);
return -1;
}
GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr("Failed to set pipeline to PLAYING\n");
gst_object_unref(pipeline);
return -1;
}
GstBus *bus = gst_element_get_bus(pipeline);
GstMessage *msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
(GstMessageType)(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));
if (msg != nullptr) {
GError *err;
gchar *debug_info;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error(msg, &err, &debug_info);
g_printerr("Error: %s\n", err->message);
g_clear_error(&err);
g_free(debug_info);
break;
case GST_MESSAGE_EOS:
g_print("End of stream\n");
break;
default:
break;
}
gst_message_unref(msg);
}
// -e flag behavior: send EOS before stopping
gst_element_send_event(pipeline, gst_event_new_eos());
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
return 0;
}
الان تستطيع انتاج البرنامج من ثم شغله
g++ main.cpp -o gstreamer-cpp `pkg-config --cflags --libs gstreamer-1.0`
./gstreamer-cpp
cargo new gstreamer-rust
cd gstreamer-rust
cargo add gstreamer
code src/main.rs
تستطيع الان كتابه التالي في main.rs
use gst::prelude::*;
use gstreamer as gst;
fn main() {
gst::init().unwrap();
let pipeline = gst::Pipeline::new();
let src = gst::ElementFactory::make("videotestsrc")
.property("num-buffers", 90)
.build()
.expect("Failed to create videotestsrc");
let capsfilter = gst::ElementFactory::make("capsfilter")
.property(
"caps",
gst::Caps::builder("video/x-raw")
.field("width", 1280)
.field("height", 720)
.field("framerate", gst::Fraction::new(30, 1))
.build(),
)
.build()
.expect("Failed to create capsfilter");
let enc = gst::ElementFactory::make("x264enc")
.build()
.expect("Failed to create x264enc");
let parse = gst::ElementFactory::make("h264parse")
.build()
.expect("Failed to create h264parse");
let mux = gst::ElementFactory::make("matroskamux")
.build()
.expect("Failed to create matroskamux");
let sink = gst::ElementFactory::make("filesink")
.property("location", "test.mkv")
.build()
.expect("Failed to create filesink");
pipeline
.add_many([&src, &capsfilter, &enc, &parse, &mux, &sink])
.unwrap();
gst::Element::link_many([&src, &capsfilter, &enc, &parse, &mux, &sink])
.expect("Failed to link elements");
pipeline
.set_state(gst::State::Playing)
.expect("Unable to set pipeline to Playing");
let bus = pipeline.bus().unwrap();
for msg in bus.iter_timed(gst::ClockTime::NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Eos(..) => {
println!("End of stream");
break;
}
MessageView::Error(err) => {
eprintln!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
break;
}
_ => (),
}
}
pipeline
.set_state(gst::State::Null)
.expect("Unable to set pipeline to Null");
}
الان تستطيع انتاج البرنامج من ثم شغله
cargo run
تهيئة GStreamer
أول خطوة في أي برنامج يستخدم GStreamer هي استدعاء الدالة gst_init .
gst_init(&argc, &argv);
gst::init().unwrap();
تقوم هذه الدالة بتهيئة مكتبة GStreamer وتحضير المكونات الداخلية اللازمة للعمل، لذلك يجب استدعاؤها قبل استخدام أي جزء آخر من المكتبة.
إنشاء الـ Pipeline
تُعتبر Pipeline الحاوية الرئيسية التي تحتوي جميع العناصر المستخدمة في البرنامج.
في الواقع، فإن GstPipeline هو نوع خاص من GstBin يضيف إمكانيات إضافية مثل إدارة الحالات وإدارة الساعة وناقل الرسائل.
يمكن إنشاء Pipeline جديدة باستخدام:
auto* pipeline = gst_pipeline_new("pipeline");
let pipeline = gst::Pipeline::new();
بعد ذلك نقوم بإنشاء العناصر المطلوبة كما فعلنا سابقًا باستخدام gst-launch-1.0 .
إضافة العناصر إلى الـ Pipeline
بعد إنشاء العناصر يجب إضافتها إلى Pipeline حتى تصبح جزءًا من خط المعالجة.
gst_bin_add_many(
GST_BIN(pipeline),
source,
encoder,
muxer,
sink,
nullptr);
pipeline
.add_many([&src, &capsfilter, &enc, &parse, &mux, &sink])
.unwrap();
في هذه المرحلة أصبحت Pipeline مسؤولة عن إدارة هذه العناصر، لكنها لا تعرف بعد كيفية اتصالها ببعضها البعض.
ربط العناصر
لربط العناصر معًا نستخدم الدالة gst_element_link_many .
gst_element_link_many(
source,
encoder,
muxer,
sink,
nullptr);
gst::Element::link_many([&src, &capsfilter, &enc, &parse, &mux, &sink])
.expect("Failed to link elements");
تقوم هذه الدالة بربط المنافذ بين العناصر بحيث يمكن للبيانات التدفق من عنصر إلى العنصر الذي يليه.
ضبط الخصائص
بعض العناصر توفر خصائص يمكن تعديلها أثناء الإنشاء أو قبل بدء التشغيل.
على سبيل المثال يمكن تغيير النمط المستخدم بواسطة عنصر videotestsrc باستخدام:
g_object_set(
source,
"pattern",
1,
nullptr);
let src = gst::ElementFactory::make("videotestsrc")
.property("num-buffers", 90)
.build()
.expect("Failed to create videotestsrc");
تُستخدم الدالة g_object_set لأن معظم عناصر GStreamer مبنية على نظام الكائنات GObject .
تشغيل الـ Pipeline
بعد إنشاء جميع العناصر وربطها وضبط خصائصها، يمكن بدء التشغيل بتغيير حالة Pipeline إلى PLAYING .
gst_element_set_state(
pipeline,
GST_STATE_PLAYING);
pipeline
.set_state(gst::State::Playing)
.expect("Unable to set pipeline to Playing");
عندها يبدأ تدفق البيانات داخل خط المعالجة وتنفيذ العمل المطلوب.
انتظار الرسائل
أثناء التشغيل ترسل GStreamer رسائل مختلفة عبر Bus مثل:
- نهاية الملف EOS
- الأخطاء ERROR
- التحذيرات WARNING
- تغييرات الحالة STATE_CHANGED
يقوم التطبيق عادة بالاستماع إلى هذه الرسائل لمعرفة ما يحدث داخل Pipeline .
لن نتعمق في Bus في هذا المقال، وسنخصص له مقالًا مستقلًا لاحقًا لأنه من أهم الأجزاء في GStreamer .
الخلاصة
في هذا المقال تعرفنا على الهيكل الأساسي لأي تطبيق يستخدم GStreamer :
- تهيئة المكتبة باستخدام gst_init .
- إنشاء Pipeline .
- إنشاء العناصر المطلوبة.
- إضافة العناصر إلى Pipeline .
- ربط العناصر معًا.
- ضبط الخصائص عند الحاجة.
- تشغيل Pipeline .
- متابعة الرسائل القادمة من Bus .
هذه الخطوات تمثل الأساس الذي ستُبنى عليه معظم تطبيقات GStreamer مهما ازدادت درجة تعقيدها.
تمارين
التمرين الأول
أنشئ برنامجًا يعرض فيديو الاختبار على الشاشة باستخدام خط المعالجة التالي:
videotestsrc ! autovideosink
التمرين الثاني
جرّب تغيير خاصية pattern في عنصر videotestsrc ولاحظ تأثيرها على الفيديو المعروض.
التمرين الثالث
أنشئ ملف فيديو بصيغة MKV باستخدام نفس خط المعالجة الذي استخدمناه في المقال السابق ولكن من خلال برنامج C++ بدلًا من gst-launch-1.0 .
التمرين الرابع
أضف معالجة لرسائل الأخطاء القادمة من Bus وقم بطباعة تفاصيل الخطأ على الشاشة.