mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-25 21:11:00 +00:00
gst-plugin-mp4: Add new MP4 plugin with a non-fragmented MP4 muxer
This commit is contained in:
parent
a5f3197651
commit
c2f403f998
11 changed files with 3386 additions and 0 deletions
|
@ -16,6 +16,7 @@ members = [
|
||||||
|
|
||||||
"mux/flavors",
|
"mux/flavors",
|
||||||
"mux/fmp4",
|
"mux/fmp4",
|
||||||
|
"mux/mp4",
|
||||||
|
|
||||||
"net/aws",
|
"net/aws",
|
||||||
"net/hlssink3",
|
"net/hlssink3",
|
||||||
|
@ -63,6 +64,7 @@ default-members = [
|
||||||
"generic/threadshare",
|
"generic/threadshare",
|
||||||
|
|
||||||
"mux/fmp4",
|
"mux/fmp4",
|
||||||
|
"mux/mp4",
|
||||||
|
|
||||||
"net/aws",
|
"net/aws",
|
||||||
"net/hlssink3",
|
"net/hlssink3",
|
||||||
|
|
|
@ -2497,6 +2497,129 @@
|
||||||
"tracers": {},
|
"tracers": {},
|
||||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
},
|
},
|
||||||
|
"mp4": {
|
||||||
|
"description": "GStreamer Rust MP4 Plugin",
|
||||||
|
"elements": {
|
||||||
|
"isomp4mux": {
|
||||||
|
"author": "Sebastian Dröge <sebastian@centricular.com>",
|
||||||
|
"description": "ISO MP4 muxer",
|
||||||
|
"hierarchy": [
|
||||||
|
"GstISOMP4Mux",
|
||||||
|
"GstRsMP4Mux",
|
||||||
|
"GstAggregator",
|
||||||
|
"GstElement",
|
||||||
|
"GstObject",
|
||||||
|
"GInitiallyUnowned",
|
||||||
|
"GObject"
|
||||||
|
],
|
||||||
|
"klass": "Codec/Muxer",
|
||||||
|
"pad-templates": {
|
||||||
|
"sink_%%u": {
|
||||||
|
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\n",
|
||||||
|
"direction": "sink",
|
||||||
|
"presence": "request",
|
||||||
|
"type": "GstRsMP4MuxPad"
|
||||||
|
},
|
||||||
|
"src": {
|
||||||
|
"caps": "video/quicktime:\n variant: iso\n",
|
||||||
|
"direction": "src",
|
||||||
|
"presence": "always"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rank": "marginal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filename": "gstmp4",
|
||||||
|
"license": "MPL",
|
||||||
|
"other-types": {
|
||||||
|
"GstRsMP4Mux": {
|
||||||
|
"hierarchy": [
|
||||||
|
"GstRsMP4Mux",
|
||||||
|
"GstAggregator",
|
||||||
|
"GstElement",
|
||||||
|
"GstObject",
|
||||||
|
"GInitiallyUnowned",
|
||||||
|
"GObject"
|
||||||
|
],
|
||||||
|
"kind": "object",
|
||||||
|
"properties": {
|
||||||
|
"interleave-bytes": {
|
||||||
|
"blurb": "Interleave between streams in bytes",
|
||||||
|
"conditionally-available": false,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "0",
|
||||||
|
"max": "18446744073709551615",
|
||||||
|
"min": "0",
|
||||||
|
"mutable": "ready",
|
||||||
|
"readable": true,
|
||||||
|
"type": "guint64",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
"interleave-time": {
|
||||||
|
"blurb": "Interleave between streams in nanoseconds",
|
||||||
|
"conditionally-available": false,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "500000000",
|
||||||
|
"max": "18446744073709551615",
|
||||||
|
"min": "0",
|
||||||
|
"mutable": "ready",
|
||||||
|
"readable": true,
|
||||||
|
"type": "guint64",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
"movie-timescale": {
|
||||||
|
"blurb": "Timescale to use for the movie (units per second, 0 is automatic)",
|
||||||
|
"conditionally-available": false,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "0",
|
||||||
|
"max": "-1",
|
||||||
|
"min": "0",
|
||||||
|
"mutable": "ready",
|
||||||
|
"readable": true,
|
||||||
|
"type": "guint",
|
||||||
|
"writable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GstRsMP4MuxPad": {
|
||||||
|
"hierarchy": [
|
||||||
|
"GstRsMP4MuxPad",
|
||||||
|
"GstAggregatorPad",
|
||||||
|
"GstPad",
|
||||||
|
"GstObject",
|
||||||
|
"GInitiallyUnowned",
|
||||||
|
"GObject"
|
||||||
|
],
|
||||||
|
"kind": "object",
|
||||||
|
"properties": {
|
||||||
|
"trak-timescale": {
|
||||||
|
"blurb": "Timescale to use for the track (units per second, 0 is automatic)",
|
||||||
|
"conditionally-available": false,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "0",
|
||||||
|
"max": "-1",
|
||||||
|
"min": "0",
|
||||||
|
"mutable": "ready",
|
||||||
|
"readable": true,
|
||||||
|
"type": "guint",
|
||||||
|
"writable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"package": "gst-plugin-mp4",
|
||||||
|
"source": "gst-plugin-mp4",
|
||||||
|
"tracers": {},
|
||||||
|
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
|
},
|
||||||
"ndi": {
|
"ndi": {
|
||||||
"description": "GStreamer NewTek NDI Plugin",
|
"description": "GStreamer NewTek NDI Plugin",
|
||||||
"elements": {
|
"elements": {
|
||||||
|
|
|
@ -49,6 +49,7 @@ plugins = {
|
||||||
# sodium has an external dependency, see below
|
# sodium has an external dependency, see below
|
||||||
'gst-plugin-threadshare': 'libgstthreadshare',
|
'gst-plugin-threadshare': 'libgstthreadshare',
|
||||||
|
|
||||||
|
'gst-plugin-mp4': 'libgstmp4',
|
||||||
'gst-plugin-fmp4': 'libgstfmp4',
|
'gst-plugin-fmp4': 'libgstfmp4',
|
||||||
|
|
||||||
'gst-plugin-aws': 'libgstaws',
|
'gst-plugin-aws': 'libgstaws',
|
||||||
|
|
50
mux/mp4/Cargo.toml
Normal file
50
mux/mp4/Cargo.toml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
[package]
|
||||||
|
name = "gst-plugin-mp4"
|
||||||
|
version = "0.10.0-alpha.1"
|
||||||
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
|
license = "MPL-2.0"
|
||||||
|
description = "GStreamer Rust MP4 Plugin"
|
||||||
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.63"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
|
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
|
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
|
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
|
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
|
once_cell = "1.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "gstmp4"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3"
|
||||||
|
url = "1"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
gst-plugin-version-helper = { path="../../version-helper" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["v1_18"]
|
||||||
|
static = []
|
||||||
|
capi = []
|
||||||
|
v1_18 = ["gst-video/v1_18"]
|
||||||
|
doc = ["gst/v1_18"]
|
||||||
|
|
||||||
|
[package.metadata.capi]
|
||||||
|
min_version = "0.8.0"
|
||||||
|
|
||||||
|
[package.metadata.capi.header]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[package.metadata.capi.library]
|
||||||
|
install_subdir = "gstreamer-1.0"
|
||||||
|
versioning = false
|
||||||
|
|
||||||
|
[package.metadata.capi.pkg_config]
|
||||||
|
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-audio-1.0, gstreamer-video-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
1
mux/mp4/LICENSE
Symbolic link
1
mux/mp4/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE-MPL-2.0
|
3
mux/mp4/build.rs
Normal file
3
mux/mp4/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
gst_plugin_version_helper::info()
|
||||||
|
}
|
34
mux/mp4/src/lib.rs
Normal file
34
mux/mp4/src/lib.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (C) 2022 Sebastian Dröge <sebastian@centricular.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
#![allow(clippy::non_send_fields_in_send_ty, unused_doc_comments)]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* plugin-mp4:
|
||||||
|
*
|
||||||
|
* Since: plugins-rs-0.10.0
|
||||||
|
*/
|
||||||
|
use gst::glib;
|
||||||
|
|
||||||
|
mod mp4mux;
|
||||||
|
|
||||||
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
mp4mux::register(plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::plugin_define!(
|
||||||
|
mp4,
|
||||||
|
env!("CARGO_PKG_DESCRIPTION"),
|
||||||
|
plugin_init,
|
||||||
|
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
|
||||||
|
// FIXME: MPL-2.0 is only allowed since 1.18.3 (as unknown) and 1.20 (as known)
|
||||||
|
"MPL",
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_REPOSITORY"),
|
||||||
|
env!("BUILD_REL_DATE")
|
||||||
|
);
|
1601
mux/mp4/src/mp4mux/boxes.rs
Normal file
1601
mux/mp4/src/mp4mux/boxes.rs
Normal file
File diff suppressed because it is too large
Load diff
1305
mux/mp4/src/mp4mux/imp.rs
Normal file
1305
mux/mp4/src/mp4mux/imp.rs
Normal file
File diff suppressed because it is too large
Load diff
134
mux/mp4/src/mp4mux/mod.rs
Normal file
134
mux/mp4/src/mp4mux/mod.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// Copyright (C) 2022 Sebastian Dröge <sebastian@centricular.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
mod boxes;
|
||||||
|
mod imp;
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub(crate) struct MP4MuxPad(ObjectSubclass<imp::MP4MuxPad>) @extends gst_base::AggregatorPad, gst::Pad, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub(crate) struct MP4Mux(ObjectSubclass<imp::MP4Mux>) @extends gst_base::Aggregator, gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub(crate) struct ISOMP4Mux(ObjectSubclass<imp::ISOMP4Mux>) @extends MP4Mux, gst_base::Aggregator, gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
#[cfg(feature = "doc")]
|
||||||
|
{
|
||||||
|
MP4Mux::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||||
|
MP4MuxPad::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||||
|
}
|
||||||
|
gst::Element::register(
|
||||||
|
Some(plugin),
|
||||||
|
"isomp4mux",
|
||||||
|
gst::Rank::Marginal,
|
||||||
|
ISOMP4Mux::static_type(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub(crate) enum DeltaFrames {
|
||||||
|
/// Only single completely decodable frames
|
||||||
|
IntraOnly,
|
||||||
|
/// Frames may depend on past frames
|
||||||
|
PredictiveOnly,
|
||||||
|
/// Frames may depend on past or future frames
|
||||||
|
Bidirectional,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeltaFrames {
|
||||||
|
/// Whether dts is required to order samples differently from presentation order
|
||||||
|
pub(crate) fn requires_dts(&self) -> bool {
|
||||||
|
matches!(self, Self::Bidirectional)
|
||||||
|
}
|
||||||
|
/// Whether this coding structure does not allow delta flags on samples
|
||||||
|
pub(crate) fn intra_only(&self) -> bool {
|
||||||
|
matches!(self, Self::IntraOnly)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Sample {
|
||||||
|
/// Sync point
|
||||||
|
sync_point: bool,
|
||||||
|
|
||||||
|
/// Sample duration
|
||||||
|
duration: gst::ClockTime,
|
||||||
|
|
||||||
|
/// Composition time offset
|
||||||
|
///
|
||||||
|
/// This is `None` for streams that have no concept of DTS.
|
||||||
|
composition_time_offset: Option<i64>,
|
||||||
|
|
||||||
|
/// Size
|
||||||
|
size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Chunk {
|
||||||
|
/// Chunk start offset
|
||||||
|
offset: u64,
|
||||||
|
|
||||||
|
/// Samples of this stream that are part of this chunk
|
||||||
|
samples: Vec<Sample>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Stream {
|
||||||
|
/// Caps of this stream
|
||||||
|
caps: gst::Caps,
|
||||||
|
|
||||||
|
/// If this stream has delta frames, and if so if it can have B frames.
|
||||||
|
delta_frames: DeltaFrames,
|
||||||
|
|
||||||
|
/// Pre-defined trak timescale if not 0.
|
||||||
|
trak_timescale: u32,
|
||||||
|
|
||||||
|
/// Start DTS
|
||||||
|
///
|
||||||
|
/// If this is negative then an edit list entry is needed to
|
||||||
|
/// make all sample times positive.
|
||||||
|
///
|
||||||
|
/// This is `None` for streams that have no concept of DTS.
|
||||||
|
start_dts: Option<gst::Signed<gst::ClockTime>>,
|
||||||
|
|
||||||
|
/// Earliest PTS
|
||||||
|
///
|
||||||
|
/// If this is >0 then an edit list entry is needed to shift
|
||||||
|
earliest_pts: gst::ClockTime,
|
||||||
|
|
||||||
|
/// End PTS
|
||||||
|
end_pts: gst::ClockTime,
|
||||||
|
|
||||||
|
/// All the chunks stored for this stream
|
||||||
|
chunks: Vec<Chunk>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Header {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
variant: Variant,
|
||||||
|
/// Pre-defined movie timescale if not 0.
|
||||||
|
movie_timescale: u32,
|
||||||
|
streams: Vec<Stream>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(crate) enum Variant {
|
||||||
|
ISO,
|
||||||
|
}
|
132
mux/mp4/tests/tests.rs
Normal file
132
mux/mp4/tests/tests.rs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright (C) 2022 Sebastian Dröge <sebastian@centricular.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use gst::prelude::*;
|
||||||
|
use gst_pbutils::prelude::*;
|
||||||
|
|
||||||
|
fn init() {
|
||||||
|
use std::sync::Once;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| {
|
||||||
|
gst::init().unwrap();
|
||||||
|
gstmp4::plugin_register_static().unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
struct Pipeline(gst::Pipeline);
|
||||||
|
impl std::ops::Deref for Pipeline {
|
||||||
|
type Target = gst::Pipeline;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for Pipeline {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.0.set_state(gst::State::Null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pipeline = match gst::parse_launch(
|
||||||
|
"videotestsrc num-buffers=99 ! x264enc ! mux. \
|
||||||
|
audiotestsrc num-buffers=140 ! fdkaacenc ! mux. \
|
||||||
|
isomp4mux name=mux ! filesink name=sink \
|
||||||
|
",
|
||||||
|
) {
|
||||||
|
Ok(pipeline) => Pipeline(pipeline.downcast::<gst::Pipeline>().unwrap()),
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let dir = tempfile::TempDir::new().unwrap();
|
||||||
|
let mut location = dir.path().to_owned();
|
||||||
|
location.push("test.mp4");
|
||||||
|
|
||||||
|
let sink = pipeline.by_name("sink").unwrap();
|
||||||
|
sink.set_property("location", location.to_str().expect("Non-UTF8 filename"));
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Playing)
|
||||||
|
.expect("Unable to set the pipeline to the `Playing` state");
|
||||||
|
|
||||||
|
for msg in pipeline.bus().unwrap().iter_timed(gst::ClockTime::NONE) {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
|
match msg.view() {
|
||||||
|
MessageView::Eos(..) => break,
|
||||||
|
MessageView::Error(err) => {
|
||||||
|
panic!(
|
||||||
|
"Error from {:?}: {} ({:?})",
|
||||||
|
err.src().map(|s| s.path_string()),
|
||||||
|
err.error(),
|
||||||
|
err.debug()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Null)
|
||||||
|
.expect("Unable to set the pipeline to the `Null` state");
|
||||||
|
|
||||||
|
drop(pipeline);
|
||||||
|
|
||||||
|
let discoverer = gst_pbutils::Discoverer::new(gst::ClockTime::from_seconds(5))
|
||||||
|
.expect("Failed to create discoverer");
|
||||||
|
let info = discoverer
|
||||||
|
.discover_uri(
|
||||||
|
url::Url::from_file_path(&location)
|
||||||
|
.expect("Failed to convert filename to URL")
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.expect("Failed to discover MP4 file");
|
||||||
|
|
||||||
|
assert_eq!(info.duration(), Some(gst::ClockTime::from_mseconds(3_300)));
|
||||||
|
|
||||||
|
let audio_streams = info.audio_streams();
|
||||||
|
assert_eq!(audio_streams.len(), 1);
|
||||||
|
let audio_stream = audio_streams[0]
|
||||||
|
.downcast_ref::<gst_pbutils::DiscovererAudioInfo>()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(audio_stream.channels(), 1);
|
||||||
|
assert_eq!(audio_stream.sample_rate(), 44_100);
|
||||||
|
let caps = audio_stream.caps().unwrap();
|
||||||
|
assert!(
|
||||||
|
caps.can_intersect(
|
||||||
|
&gst::Caps::builder("audio/mpeg")
|
||||||
|
.any_features()
|
||||||
|
.field("mpegversion", 4i32)
|
||||||
|
.build()
|
||||||
|
),
|
||||||
|
"Unexpected audio caps {:?}",
|
||||||
|
caps
|
||||||
|
);
|
||||||
|
|
||||||
|
let video_streams = info.video_streams();
|
||||||
|
assert_eq!(video_streams.len(), 1);
|
||||||
|
let video_stream = video_streams[0]
|
||||||
|
.downcast_ref::<gst_pbutils::DiscovererVideoInfo>()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(video_stream.width(), 320);
|
||||||
|
assert_eq!(video_stream.height(), 240);
|
||||||
|
assert_eq!(video_stream.framerate(), gst::Fraction::new(30, 1));
|
||||||
|
assert_eq!(video_stream.par(), gst::Fraction::new(1, 1));
|
||||||
|
assert!(!video_stream.is_interlaced());
|
||||||
|
let caps = video_stream.caps().unwrap();
|
||||||
|
assert!(
|
||||||
|
caps.can_intersect(&gst::Caps::builder("video/x-h264").any_features().build()),
|
||||||
|
"Unexpected video caps {:?}",
|
||||||
|
caps
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue