0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Gstreamer で raw video file を書き出す時の細かい話

Posted at

こんな感じで

Rust で書くと

use gstreamer as gst;
use gst::prelude::*;

let pipeline = gst::parse_launch("filesrc name=filesrc ! decodebin force-sw-decoders=true name=dec ! capsfilter caps=video/x-raw ! filesink sync=false name=filesink")?;
let bus = pipeline.bus()?;

let filesrc = pipeline.by_name("filesrc")?;
filesrc.set_property("location", src_path);

let filesink = pipeline.by_name("filesink")?;
filesink.set_property("location", sink_path);

pipeline.set_state(gst::State::Playing)?;

for message in bus.iter_timed(None) {
    match message.view() {
        gst::MessageView::Error(err) => todo!(),
        gst::MessageView::Eos(_) => break,
        _ => (),
    }
}

pipeline.set_state(gst::State::Null)?;

適当に書いてるので動かないかもだけど、多分こんなん

上のコードの問題点

もし任意の raw format を受け付けるなら VideoInfo などの情報もどこかに保存しておかなければならない

あと、途中で format が変わることが考慮されてない

基本的には appsink とかで、細かくイベントハンドリングすればいいと思うが、以下のように pad_probe で雑に実装することもできる。

以下のように caps と flush_stop と buffer あたりをちゃんと見ておく必要がある

use gstreamer_video as gst_video;

struct ThreadData {
    video_info: Option<gst_video::VideoInfo>,
}

let pad = filesnk.static_pad("sink").unwrap();
let thread_data = Arc::new(Mutex::new(ThreadData { video_info: None }))

{
    let thread_data_weak = Arc::downgrade(&thread_data);
    pad.add_probe(gst::PadProbeType::EVENT_FLUSH, move |_pad, info| {
        let Some(thread_data) = thread_data_weak.upgrade() else {
            return gst::PadProbeReturn::Remove;
        };

        match &info.data {
            Some(gst::PadProbeData::Event(event)) => match event.view() {
                gst::EventView::FlushStop(_) => {
                    todo!() // seek したりなどしたときに発生し filesink で fseek(fp, 0) のようなことが起こるので、対処が必要なことがある
                _ => (),
            },
            _ => unreachable!(),
        };
    });
}

{
    let thread_data_weak = Arc::downgrade(&thread_data);
    pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, move |_pad, _info| {
        // caps から VideoInfo を取得して、この raw file を読む的に必要な raw format の情報を得る
        let Some(thread_data) = thread_data_weak.upgrade() else {
            return gst::PadProbeReturn::Remove;
        };
        let mut thread_data = thread_data.lock().unwrap();

        match &info.data {
            Some(gst::PadProbeData::Event(event)) => match event.view() {
                gst::EventView::Caps(caps) => {
                    let caps = caps.caps_owned();
                    let thread_data = thread_data.deref();
                    let mut thread_data = thread_data.lock().unwrap();
                    thread_data.video_info = gst_video::VideoInfo::from_caps(&caps).unwrap();
                },
                _ => (),
            },
            _ => unreachable!(),
        }
        gst::PadProbeReturn::Ok
    });
}

{
    let thread_data_weak = Arc::downgrade(&thread_data);
    pad.add_probe(gst::PadProbeType::BUFFER, move |_pad, _info| {
        // buffer を保存する前に video_info が変化していないかなどの考慮が必要
        // preroll 時の buffer がきて、そのあと seek が起こってすれられるパターンなどもあり、
        // ここにくる buffer が全て filesink で書かれるということでもないので注意
        let Some(thread_data) = thread_data_weak.upgrade() else {
            return gst::PadProbeReturn::Remove;
        };
        let thread_data = thread_data.deref();
        let mut thread_data = thread_data.lock().unwrap();

        todo!(); // ここにバッファを保存するかなど video_info を見て何か処理をする
    });
}

raw file を読むときは rawvideoparse で読む

let pipeline = gst::parse_launch("filesrc name=filesrc ! rawvideoparse name=rawvideoparse ! ...省略")?;

let filesrc = pipeline.by_name("filesrc")?;
filesrc.set_property("location", src_path);

// 書き出した時に caps から取得した VideoInfo を使う

let rawvideoparse = pipeline.by_name("rawvideoparse")?;
rawvideoparse.set_property("width", video_info.width() as i32);
rawvideoparse.set_property("height", video_info.height() as i32);
rawvideoparse.set_property("format", video_info.format());
let framerate = video_info.fps();
if framerate.numer() != 0 {
    rawvideoparse.set_property("framerate", framerate);
}
rawvideoparse.set_property("pixel-aspect-ratio", video_info.par());
if video_info.is_interlaced() {
    rawvideoparse.set_property("interlaced", true);
    rawvideoparse.set_property("ttf", video_info.field_order() == gst_video::VideoFieldOrder::TopFieldFirst);
} else {
    rawvideoparse.set_property("interlaced", false);
}
rawvideoparse.set_property("plane-strides", gst::Array::new(video_info.stride().into_iter().map(|n| *n as i32)).to_value());
rawvideoparse.set_property("plane-offsets", gst::Array::new(video_info.offset().into_iter().map(|n| *n as i32)).to_value());
rawvideoparse.set_property("frame-size", video_info.size() as u32);
rawvideoparse.set_property("colorimetry", video_info.colorimetry().to_string());

pipeline.set_state(gst::State::Playing)?;

for message in bus.iter_timed(None) {
    match message.view() {
        gst::MessageView::Error(err) => todo!(),
        gst::MessageView::Eos(_) => break,
        _ => (),
    }
}

pipeline.set_state(gst::State::Null)?;
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?