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

# こんな感じで

Rust で書くと

```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 あたりをちゃんと見ておく必要がある

```rust
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 で読む

```rust
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)?;
```