mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-09-03 10:13:47 +00:00
Implement metadata handling
This commit is contained in:
parent
d804590106
commit
7ef2679cb5
3 changed files with 232 additions and 52 deletions
|
@ -12,7 +12,7 @@ url = "1.1"
|
||||||
bitflags = "0.7"
|
bitflags = "0.7"
|
||||||
reqwest = "0.2"
|
reqwest = "0.2"
|
||||||
nom = "2.0"
|
nom = "2.0"
|
||||||
flavors = {git = "https://github.com/Geal/flavors.git"}
|
flavors = {git = "https://github.com/sdroege/flavors.git"}
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gcc = "0.3"
|
gcc = "0.3"
|
||||||
|
|
269
src/flvdemux.rs
269
src/flvdemux.rs
|
@ -50,13 +50,8 @@ struct StreamingState {
|
||||||
expect_video: bool,
|
expect_video: bool,
|
||||||
got_all_streams: bool,
|
got_all_streams: bool,
|
||||||
last_position: Option<u64>,
|
last_position: Option<u64>,
|
||||||
duration: Option<u64>,
|
|
||||||
|
|
||||||
creation_date: Option<String>,
|
metadata: Option<Metadata>,
|
||||||
creator: Option<String>,
|
|
||||||
title: Option<String>,
|
|
||||||
metadata_creator: Option<String>, /* TODO: seek_table: _,
|
|
||||||
* filepositions / times metadata arrays */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamingState {
|
impl StreamingState {
|
||||||
|
@ -68,11 +63,7 @@ impl StreamingState {
|
||||||
expect_video: video,
|
expect_video: video,
|
||||||
got_all_streams: false,
|
got_all_streams: false,
|
||||||
last_position: None,
|
last_position: None,
|
||||||
duration: None,
|
metadata: None,
|
||||||
creation_date: None,
|
|
||||||
creator: None,
|
|
||||||
title: None,
|
|
||||||
metadata_creator: None, // seek_table: None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,13 +86,8 @@ impl PartialEq for AudioFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFormat {
|
impl AudioFormat {
|
||||||
fn new(format: flavors::SoundFormat,
|
fn new(data_header: &flavors::AudioDataHeader, metadata: &Option<Metadata>) -> AudioFormat {
|
||||||
rate: flavors::SoundRate,
|
let numeric_rate = match (data_header.sound_format, data_header.sound_rate) {
|
||||||
width: flavors::SoundSize,
|
|
||||||
channels: flavors::SoundType,
|
|
||||||
bitrate: Option<u32>)
|
|
||||||
-> AudioFormat {
|
|
||||||
let numeric_rate = match (format, rate) {
|
|
||||||
(flavors::SoundFormat::NELLYMOSER_16KHZ_MONO, _) => 16000,
|
(flavors::SoundFormat::NELLYMOSER_16KHZ_MONO, _) => 16000,
|
||||||
(flavors::SoundFormat::NELLYMOSER_8KHZ_MONO, _) => 8000,
|
(flavors::SoundFormat::NELLYMOSER_8KHZ_MONO, _) => 8000,
|
||||||
(flavors::SoundFormat::MP3_8KHZ, _) => 8000,
|
(flavors::SoundFormat::MP3_8KHZ, _) => 8000,
|
||||||
|
@ -111,25 +97,36 @@ impl AudioFormat {
|
||||||
(_, flavors::SoundRate::_44KHZ) => 44100,
|
(_, flavors::SoundRate::_44KHZ) => 44100,
|
||||||
};
|
};
|
||||||
|
|
||||||
let numeric_width = match width {
|
let numeric_width = match data_header.sound_size {
|
||||||
flavors::SoundSize::Snd8bit => 8,
|
flavors::SoundSize::Snd8bit => 8,
|
||||||
flavors::SoundSize::Snd16bit => 16,
|
flavors::SoundSize::Snd16bit => 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
let numeric_channels = match channels {
|
let numeric_channels = match data_header.sound_type {
|
||||||
flavors::SoundType::SndMono => 1,
|
flavors::SoundType::SndMono => 1,
|
||||||
flavors::SoundType::SndStereo => 2,
|
flavors::SoundType::SndStereo => 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioFormat {
|
AudioFormat {
|
||||||
format: format,
|
format: data_header.sound_format,
|
||||||
rate: numeric_rate,
|
rate: numeric_rate,
|
||||||
width: numeric_width,
|
width: numeric_width,
|
||||||
channels: numeric_channels,
|
channels: numeric_channels,
|
||||||
bitrate: bitrate,
|
bitrate: metadata.as_ref().and_then(|m| m.audio_bitrate),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_with_metadata(&mut self, metadata: &Metadata) -> bool {
|
||||||
|
let mut changed = false;
|
||||||
|
|
||||||
|
if self.bitrate != metadata.audio_bitrate {
|
||||||
|
self.bitrate = metadata.audio_bitrate;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
|
||||||
fn to_string(&self) -> Option<String> {
|
fn to_string(&self) -> Option<String> {
|
||||||
match self.format {
|
match self.format {
|
||||||
flavors::SoundFormat::MP3 => {
|
flavors::SoundFormat::MP3 => {
|
||||||
|
@ -159,23 +156,48 @@ struct VideoFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoFormat {
|
impl VideoFormat {
|
||||||
fn new(format: flavors::CodecId,
|
fn new(data_header: &flavors::VideoDataHeader, metadata: &Option<Metadata>) -> VideoFormat {
|
||||||
width: Option<u32>,
|
|
||||||
height: Option<u32>,
|
|
||||||
pixel_aspect_ratio: Option<(u32, u32)>,
|
|
||||||
framerate: Option<(u32, u32)>,
|
|
||||||
bitrate: Option<u32>)
|
|
||||||
-> VideoFormat {
|
|
||||||
VideoFormat {
|
VideoFormat {
|
||||||
format: format,
|
format: data_header.codec_id,
|
||||||
width: width,
|
width: metadata.as_ref().and_then(|m| m.video_width),
|
||||||
height: height,
|
height: metadata.as_ref().and_then(|m| m.video_height),
|
||||||
pixel_aspect_ratio: pixel_aspect_ratio,
|
pixel_aspect_ratio: metadata.as_ref().and_then(|m| m.video_pixel_aspect_ratio),
|
||||||
framerate: framerate,
|
framerate: metadata.as_ref().and_then(|m| m.video_framerate),
|
||||||
bitrate: bitrate,
|
bitrate: metadata.as_ref().and_then(|m| m.video_bitrate),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_with_metadata(&mut self, metadata: &Metadata) -> bool {
|
||||||
|
let mut changed = false;
|
||||||
|
|
||||||
|
if self.width != metadata.video_width {
|
||||||
|
self.width = metadata.video_width;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.height != metadata.video_height {
|
||||||
|
self.height = metadata.video_height;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pixel_aspect_ratio != metadata.video_pixel_aspect_ratio {
|
||||||
|
self.pixel_aspect_ratio = metadata.video_pixel_aspect_ratio;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.framerate != metadata.video_framerate {
|
||||||
|
self.framerate = metadata.video_framerate;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.bitrate != metadata.video_bitrate {
|
||||||
|
self.bitrate = metadata.video_bitrate;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
|
||||||
fn to_string(&self) -> Option<String> {
|
fn to_string(&self) -> Option<String> {
|
||||||
match self.format {
|
match self.format {
|
||||||
flavors::CodecId::VP6 => {
|
flavors::CodecId::VP6 => {
|
||||||
|
@ -213,6 +235,104 @@ impl PartialEq for VideoFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
struct Metadata {
|
||||||
|
duration: Option<u64>,
|
||||||
|
|
||||||
|
creation_date: Option<String>,
|
||||||
|
creator: Option<String>,
|
||||||
|
title: Option<String>,
|
||||||
|
metadata_creator: Option<String>, /* TODO: seek_table: _,
|
||||||
|
* filepositions / times metadata arrays */
|
||||||
|
|
||||||
|
audio_bitrate: Option<u32>,
|
||||||
|
|
||||||
|
video_width: Option<u32>,
|
||||||
|
video_height: Option<u32>,
|
||||||
|
video_pixel_aspect_ratio: Option<(u32, u32)>,
|
||||||
|
video_framerate: Option<(u32, u32)>,
|
||||||
|
video_bitrate: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metadata {
|
||||||
|
fn new(script_data: &flavors::ScriptData) -> Metadata {
|
||||||
|
assert_eq!(script_data.name, "onMetaData");
|
||||||
|
|
||||||
|
let mut metadata = Metadata {
|
||||||
|
duration: None,
|
||||||
|
creation_date: None,
|
||||||
|
creator: None,
|
||||||
|
title: None,
|
||||||
|
metadata_creator: None,
|
||||||
|
audio_bitrate: None,
|
||||||
|
video_width: None,
|
||||||
|
video_height: None,
|
||||||
|
video_pixel_aspect_ratio: None,
|
||||||
|
video_framerate: None,
|
||||||
|
video_bitrate: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = match script_data.arguments {
|
||||||
|
flavors::ScriptDataValue::Object(ref objects) => objects,
|
||||||
|
flavors::ScriptDataValue::ECMAArray(ref objects) => objects,
|
||||||
|
flavors::ScriptDataValue::StrictArray(ref objects) => objects,
|
||||||
|
_ => return metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut par_n = None;
|
||||||
|
let mut par_d = None;
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
match (arg.name, &arg.data) {
|
||||||
|
("duration", &flavors::ScriptDataValue::Number(duration)) => {
|
||||||
|
metadata.duration = Some((duration * 1000.0 * 1000.0 * 1000.0) as u64);
|
||||||
|
}
|
||||||
|
("creationdate", &flavors::ScriptDataValue::String(date)) => {
|
||||||
|
metadata.creation_date = Some(String::from(date));
|
||||||
|
}
|
||||||
|
("creator", &flavors::ScriptDataValue::String(creator)) => {
|
||||||
|
metadata.creator = Some(String::from(creator));
|
||||||
|
}
|
||||||
|
("title", &flavors::ScriptDataValue::String(title)) => {
|
||||||
|
metadata.title = Some(String::from(title));
|
||||||
|
}
|
||||||
|
("metadatacreator", &flavors::ScriptDataValue::String(creator)) => {
|
||||||
|
metadata.metadata_creator = Some(String::from(creator));
|
||||||
|
}
|
||||||
|
("audiodatarate", &flavors::ScriptDataValue::Number(datarate)) => {
|
||||||
|
metadata.audio_bitrate = Some((datarate * 1024.0) as u32);
|
||||||
|
}
|
||||||
|
("width", &flavors::ScriptDataValue::Number(width)) => {
|
||||||
|
metadata.video_width = Some(width as u32);
|
||||||
|
}
|
||||||
|
("height", &flavors::ScriptDataValue::Number(height)) => {
|
||||||
|
metadata.video_height = Some(height as u32);
|
||||||
|
}
|
||||||
|
("framerate", &flavors::ScriptDataValue::Number(framerate)) => {
|
||||||
|
// FIXME: Convert properly to a fraction
|
||||||
|
metadata.video_framerate = Some((framerate as u32, 1));
|
||||||
|
}
|
||||||
|
("AspectRatioX", &flavors::ScriptDataValue::Number(par_x)) => {
|
||||||
|
par_n = Some(par_x as u32);
|
||||||
|
}
|
||||||
|
("AspectRatioY", &flavors::ScriptDataValue::Number(par_y)) => {
|
||||||
|
par_d = Some(par_y as u32);
|
||||||
|
}
|
||||||
|
("videodatarate", &flavors::ScriptDataValue::Number(datarate)) => {
|
||||||
|
metadata.video_bitrate = Some((datarate * 1024.0) as u32);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(par_n), Some(par_d)) = (par_n, par_d) {
|
||||||
|
metadata.video_pixel_aspect_ratio = Some((par_n, par_d));
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FlvDemux {
|
pub struct FlvDemux {
|
||||||
state: State,
|
state: State,
|
||||||
|
@ -237,7 +357,62 @@ impl FlvDemux {
|
||||||
fn handle_script_tag(&mut self,
|
fn handle_script_tag(&mut self,
|
||||||
tag_header: &flavors::TagHeader)
|
tag_header: &flavors::TagHeader)
|
||||||
-> Result<HandleBufferResult, FlowError> {
|
-> Result<HandleBufferResult, FlowError> {
|
||||||
self.adapter.flush((15 + tag_header.data_size) as usize).unwrap();
|
if self.adapter.get_available() < (15 + tag_header.data_size) as usize {
|
||||||
|
return Ok(HandleBufferResult::NeedMoreData);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.adapter.flush(15).unwrap();
|
||||||
|
|
||||||
|
let buffer = self.adapter.get_buffer(tag_header.data_size as usize).unwrap();
|
||||||
|
let map = buffer.map_read().unwrap();
|
||||||
|
let data = map.as_slice();
|
||||||
|
|
||||||
|
match flavors::script_data(data) {
|
||||||
|
IResult::Done(_, ref script_data) if script_data.name == "onMetaData" => {
|
||||||
|
let streaming_state = self.streaming_state.as_mut().unwrap();
|
||||||
|
let metadata = Metadata::new(script_data);
|
||||||
|
|
||||||
|
let audio_changed = streaming_state.audio
|
||||||
|
.as_mut()
|
||||||
|
.map(|a| a.update_with_metadata(&metadata))
|
||||||
|
.unwrap_or(false);
|
||||||
|
let video_changed = streaming_state.video
|
||||||
|
.as_mut()
|
||||||
|
.map(|v| v.update_with_metadata(&metadata))
|
||||||
|
.unwrap_or(false);
|
||||||
|
streaming_state.metadata = Some(metadata);
|
||||||
|
|
||||||
|
if audio_changed || video_changed {
|
||||||
|
let mut streams = Vec::new();
|
||||||
|
|
||||||
|
if audio_changed {
|
||||||
|
if let Some(format) = streaming_state.audio
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|a| a.to_string()) {
|
||||||
|
streams.push(Stream::new(AUDIO_STREAM_ID,
|
||||||
|
format,
|
||||||
|
String::from("audio")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if video_changed {
|
||||||
|
if let Some(format) = streaming_state.video
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| v.to_string()) {
|
||||||
|
streams.push(Stream::new(VIDEO_STREAM_ID,
|
||||||
|
format,
|
||||||
|
String::from("video")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(HandleBufferResult::StreamsChanged(streams));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IResult::Done(_, _) |
|
||||||
|
IResult::Error(_) |
|
||||||
|
IResult::Incomplete(_) => {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(HandleBufferResult::Again)
|
Ok(HandleBufferResult::Again)
|
||||||
}
|
}
|
||||||
|
@ -247,12 +422,7 @@ impl FlvDemux {
|
||||||
-> Result<HandleBufferResult, FlowError> {
|
-> Result<HandleBufferResult, FlowError> {
|
||||||
let streaming_state = self.streaming_state.as_mut().unwrap();
|
let streaming_state = self.streaming_state.as_mut().unwrap();
|
||||||
|
|
||||||
let new_audio_format =
|
let new_audio_format = AudioFormat::new(data_header, &streaming_state.metadata);
|
||||||
AudioFormat::new(data_header.sound_format,
|
|
||||||
data_header.sound_rate,
|
|
||||||
data_header.sound_size,
|
|
||||||
data_header.sound_type,
|
|
||||||
streaming_state.audio.as_ref().and_then(|s| s.bitrate));
|
|
||||||
|
|
||||||
if streaming_state.audio.as_ref() != Some(&new_audio_format) {
|
if streaming_state.audio.as_ref() != Some(&new_audio_format) {
|
||||||
let new_stream = streaming_state.audio == None;
|
let new_stream = streaming_state.audio == None;
|
||||||
|
@ -309,13 +479,7 @@ impl FlvDemux {
|
||||||
-> Result<HandleBufferResult, FlowError> {
|
-> Result<HandleBufferResult, FlowError> {
|
||||||
let streaming_state = self.streaming_state.as_mut().unwrap();
|
let streaming_state = self.streaming_state.as_mut().unwrap();
|
||||||
|
|
||||||
let new_video_format =
|
let new_video_format = VideoFormat::new(data_header, &streaming_state.metadata);
|
||||||
VideoFormat::new(data_header.codec_id,
|
|
||||||
streaming_state.video.as_ref().and_then(|s| s.width),
|
|
||||||
streaming_state.video.as_ref().and_then(|s| s.height),
|
|
||||||
streaming_state.video.as_ref().and_then(|s| s.pixel_aspect_ratio),
|
|
||||||
streaming_state.video.as_ref().and_then(|s| s.framerate),
|
|
||||||
streaming_state.video.as_ref().and_then(|s| s.bitrate));
|
|
||||||
|
|
||||||
if streaming_state.video.as_ref() != Some(&new_video_format) {
|
if streaming_state.video.as_ref() != Some(&new_video_format) {
|
||||||
let new_stream = streaming_state.video == None;
|
let new_stream = streaming_state.video == None;
|
||||||
|
@ -407,7 +571,7 @@ impl FlvDemux {
|
||||||
IResult::Incomplete(_) => {
|
IResult::Incomplete(_) => {
|
||||||
// fall through
|
// fall through
|
||||||
}
|
}
|
||||||
IResult::Done(_, header) => {
|
IResult::Done(_, ref header) => {
|
||||||
let skip = if header.offset < 9 {
|
let skip = if header.offset < 9 {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
|
@ -565,6 +729,11 @@ impl Demuxer for FlvDemux {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_duration(&self) -> Option<u64> {
|
fn get_duration(&self) -> Option<u64> {
|
||||||
|
if let Some(StreamingState { metadata: Some(Metadata { duration, .. }), .. }) =
|
||||||
|
self.streaming_state {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub enum HandleBufferResult {
|
||||||
HaveAllStreams,
|
HaveAllStreams,
|
||||||
StreamChanged(Stream),
|
StreamChanged(Stream),
|
||||||
// StreamsAdded(Vec<Stream>), // Implies HaveAllStreams
|
// StreamsAdded(Vec<Stream>), // Implies HaveAllStreams
|
||||||
// StreamsChanged(Vec<Stream>),
|
StreamsChanged(Vec<Stream>),
|
||||||
// TODO need something to replace/add new streams
|
// TODO need something to replace/add new streams
|
||||||
// TODO should probably directly implement the GstStreams new world order
|
// TODO should probably directly implement the GstStreams new world order
|
||||||
BufferForStream(StreamIndex, Buffer),
|
BufferForStream(StreamIndex, Buffer),
|
||||||
|
@ -272,6 +272,17 @@ impl DemuxerWrapper {
|
||||||
format_cstr.as_ptr());
|
format_cstr.as_ptr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HandleBufferResult::StreamsChanged(streams) => {
|
||||||
|
for stream in streams {
|
||||||
|
let format_cstr = CString::new(stream.format.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gst_rs_demuxer_stream_format_changed(self.raw,
|
||||||
|
stream.index,
|
||||||
|
format_cstr.as_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
HandleBufferResult::BufferForStream(index, buffer) => {
|
HandleBufferResult::BufferForStream(index, buffer) => {
|
||||||
let flow_ret = unsafe {
|
let flow_ret = unsafe {
|
||||||
gst_rs_demuxer_stream_push_buffer(self.raw, index, buffer.into_ptr())
|
gst_rs_demuxer_stream_push_buffer(self.raw, index, buffer.into_ptr())
|
||||||
|
|
Loading…
Reference in a new issue