// Copyright (C) 2017 Sebastian Dröge // // 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 // . // // SPDX-License-Identifier: MPL-2.0 use gst::glib; use gst::prelude::*; use gst::subclass::prelude::*; use once_cell::sync::Lazy; use parking_lot::{Condvar, Mutex}; use std::cmp; use std::collections::HashMap; use std::f64; use std::iter; use std::sync::Arc; const DEFAULT_RECORD: bool = false; const DEFAULT_LIVE: bool = false; #[derive(Debug, Clone, Copy)] struct Settings { record: bool, live: bool, } impl Default for Settings { fn default() -> Self { Settings { record: DEFAULT_RECORD, live: DEFAULT_LIVE, } } } #[derive(Clone)] struct Stream { sinkpad: gst::Pad, srcpad: gst::Pad, state: Arc>, } impl PartialEq for Stream { fn eq(&self, other: &Self) -> bool { self.sinkpad == other.sinkpad && self.srcpad == other.srcpad } } impl Eq for Stream {} impl Stream { fn new(sinkpad: gst::Pad, srcpad: gst::Pad) -> Self { Self { sinkpad, srcpad, state: Arc::new(Mutex::new(StreamState::default())), } } } struct StreamState { in_segment: gst::FormattedSegment, out_segment: gst::FormattedSegment, segment_seqnum: gst::Seqnum, // Start/end running time of the current/last buffer current_running_time: Option, current_running_time_end: Option, eos: bool, flushing: bool, segment_pending: bool, discont_pending: bool, pending_events: Vec, audio_info: Option, video_info: Option, } impl Default for StreamState { fn default() -> Self { Self { in_segment: gst::FormattedSegment::new(), out_segment: gst::FormattedSegment::new(), segment_seqnum: gst::Seqnum::next(), current_running_time: None, current_running_time_end: None, eos: false, flushing: false, segment_pending: false, discont_pending: true, pending_events: Vec::new(), audio_info: None, video_info: None, } } } // Recording behaviour: // // Secondary streams are *always* behind main stream // Main stream EOS stops recording (-> Stopping), makes secondary streams go EOS // // Recording: Passing through all data // Stopping: Main stream remembering current last_recording_stop, waiting for all // other streams to reach this position // Stopped: Dropping all data // Starting: Main stream waiting until next keyframe and setting last_recording_start, waiting // for all other streams to reach this position #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum RecordingState { Recording, Stopping, Stopped, Starting, } #[derive(Debug)] struct State { recording_state: RecordingState, last_recording_start: Option, last_recording_stop: Option, // Accumulated duration of previous recording segments, // updated whenever going to Stopped recording_duration: gst::ClockTime, // Updated whenever going to Recording running_time_offset: i64, live: bool, } impl Default for State { fn default() -> Self { Self { recording_state: RecordingState::Stopped, last_recording_start: None, last_recording_stop: None, recording_duration: gst::ClockTime::ZERO, running_time_offset: 0, live: false, } } } #[derive(Debug, PartialEq, Eq)] enum HandleResult { Pass(T), Drop, Eos(bool), Flushing, } trait HandleData: Sized { fn pts(&self) -> Option; fn dts(&self) -> Option; fn dts_or_pts(&self) -> Option { let dts = self.dts(); if dts.is_some() { dts } else { self.pts() } } fn duration(&self, state: &StreamState) -> Option; fn is_keyframe(&self) -> bool; fn can_clip(&self, state: &StreamState) -> bool; fn clip( self, state: &StreamState, segment: &gst::FormattedSegment, ) -> Option; } impl HandleData for (gst::ClockTime, Option) { fn pts(&self) -> Option { Some(self.0) } fn dts(&self) -> Option { Some(self.0) } fn duration(&self, _state: &StreamState) -> Option { self.1 } fn is_keyframe(&self) -> bool { true } fn can_clip(&self, _state: &StreamState) -> bool { true } fn clip( self, _state: &StreamState, segment: &gst::FormattedSegment, ) -> Option { let stop = self.0 + self.1.unwrap_or(gst::ClockTime::ZERO); segment.clip(self.0, stop).map(|(start, stop)| { let start = start.expect("provided a defined value"); (start, stop.opt_sub(start)) }) } } impl HandleData for gst::Buffer { fn pts(&self) -> Option { gst::BufferRef::pts(self) } fn dts(&self) -> Option { gst::BufferRef::dts(self) } fn duration(&self, state: &StreamState) -> Option { let duration = gst::BufferRef::duration(self); if duration.is_some() { duration } else if let Some(ref video_info) = state.video_info { if video_info.fps() != 0.into() { gst::ClockTime::SECOND.mul_div_floor( video_info.fps().denom() as u64, video_info.fps().numer() as u64, ) } else { gst::ClockTime::NONE } } else if let Some(ref audio_info) = state.audio_info { if audio_info.bpf() == 0 || audio_info.rate() == 0 { return gst::ClockTime::NONE; } let size = self.size() as u64; let num_samples = size / audio_info.bpf() as u64; gst::ClockTime::SECOND.mul_div_floor(num_samples, audio_info.rate() as u64) } else { gst::ClockTime::NONE } } fn is_keyframe(&self) -> bool { !gst::BufferRef::flags(self).contains(gst::BufferFlags::DELTA_UNIT) } fn can_clip(&self, state: &StreamState) -> bool { // Only do actual clipping for raw audio/video if let Some(ref audio_info) = state.audio_info { if audio_info.format() == gst_audio::AudioFormat::Unknown || audio_info.format() == gst_audio::AudioFormat::Encoded || audio_info.rate() == 0 || audio_info.bpf() == 0 { return false; } } else if let Some(ref video_info) = state.video_info { if video_info.format() == gst_video::VideoFormat::Unknown || video_info.format() == gst_video::VideoFormat::Encoded || self.dts_or_pts() != self.pts() { return false; } } else { return false; } true } fn clip( mut self, state: &StreamState, segment: &gst::FormattedSegment, ) -> Option { // Only do actual clipping for raw audio/video if !self.can_clip(state) { return Some(self); } let pts = HandleData::pts(&self); let duration = HandleData::duration(&self, state); let stop = pts.map(|pts| pts + duration.unwrap_or(gst::ClockTime::ZERO)); if let Some(ref audio_info) = state.audio_info { gst_audio::audio_buffer_clip( self, segment.upcast_ref(), audio_info.rate(), audio_info.bpf(), ) } else if state.video_info.is_some() { segment.clip(pts, stop).map(move |(start, stop)| { { let buffer = self.make_mut(); buffer.set_pts(start); buffer.set_duration( stop.opt_checked_sub(start).ok().flatten(), // FIXME we could expect here ); } self }) } else { unreachable!(); } } } pub struct ToggleRecord { settings: Mutex, state: Mutex, main_stream: Stream, // Always must have main_stream.state locked! // If multiple stream states have to be locked, the // main_stream always comes first main_stream_cond: Condvar, other_streams: Mutex<(Vec, u32)>, pads: Mutex>, } static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( "togglerecord", gst::DebugColorFlags::empty(), Some("Toggle Record Element"), ) }); impl ToggleRecord { fn handle_main_stream( &self, pad: &gst::Pad, stream: &Stream, data: T, ) -> Result, gst::FlowError> { let mut state = stream.state.lock(); let mut dts_or_pts = data.dts_or_pts().ok_or_else(|| { gst::element_imp_error!( self, gst::StreamError::Format, ["Buffer without DTS or PTS"] ); gst::FlowError::Error })?; let mut dts_or_pts_end = dts_or_pts + data.duration(&state).unwrap_or(gst::ClockTime::ZERO); let data = match data.clip(&state, &state.in_segment) { Some(data) => data, None => { gst::log!(CAT, obj: pad, "Dropping raw data outside segment"); return Ok(HandleResult::Drop); } }; // This will only do anything for non-raw data dts_or_pts = state.in_segment.start().unwrap().max(dts_or_pts); dts_or_pts_end = state.in_segment.start().unwrap().max(dts_or_pts_end); if let Some(stop) = state.in_segment.stop() { dts_or_pts = stop.min(dts_or_pts); dts_or_pts_end = stop.min(dts_or_pts_end); } let current_running_time = state.in_segment.to_running_time(dts_or_pts); let current_running_time_end = state.in_segment.to_running_time(dts_or_pts_end); state.current_running_time = current_running_time .opt_max(state.current_running_time) .or(current_running_time); state.current_running_time_end = current_running_time_end .opt_max(state.current_running_time_end) .or(current_running_time_end); // FIXME we should probably return if either current_running_time or current_running_time_end // are None at this point // Wake up everybody, we advanced a bit // Important: They will only be able to advance once we're done with this // function or waiting for them to catch up below, otherwise they might // get the wrong state self.main_stream_cond.notify_all(); gst::log!( CAT, obj: pad, "Main stream current running time {}-{} (position: {}-{})", current_running_time.display(), current_running_time_end.display(), dts_or_pts, dts_or_pts_end, ); let settings = *self.settings.lock(); // First check if we have to update our recording state let mut rec_state = self.state.lock(); let settings_changed = match rec_state.recording_state { RecordingState::Recording if !settings.record => { gst::debug!(CAT, obj: pad, "Stopping recording"); rec_state.recording_state = RecordingState::Stopping; true } RecordingState::Stopped if settings.record => { gst::debug!(CAT, obj: pad, "Starting recording"); rec_state.recording_state = RecordingState::Starting; true } _ => false, }; match rec_state.recording_state { RecordingState::Recording => { // Remember where we stopped last, in case of EOS rec_state.last_recording_stop = current_running_time_end; gst::log!(CAT, obj: pad, "Passing buffer (recording)"); Ok(HandleResult::Pass(data)) } RecordingState::Stopping => { if !data.is_keyframe() { // Remember where we stopped last, in case of EOS rec_state.last_recording_stop = current_running_time_end; gst::log!(CAT, obj: pad, "Passing non-keyframe buffer (stopping)"); drop(rec_state); drop(state); if settings_changed { gst::debug!(CAT, obj: pad, "Requesting a new keyframe"); stream .sinkpad .push_event(gst_video::UpstreamForceKeyUnitEvent::builder().build()); } return Ok(HandleResult::Pass(data)); } // Remember the time when we stopped: now, i.e. right before the current buffer! rec_state.last_recording_stop = current_running_time; let last_recording_duration = rec_state .last_recording_stop .opt_checked_sub(rec_state.last_recording_start) .ok() .flatten(); gst::debug!( CAT, obj: pad, "Stopping at {}, started at {}, current duration {}, previous accumulated recording duration {}", rec_state.last_recording_stop.display(), rec_state.last_recording_start.display(), last_recording_duration.display(), rec_state.recording_duration ); // Then unlock and wait for all other streams to reach a buffer that is completely // after/at the recording stop position (i.e. can be dropped completely) or go EOS // instead. drop(rec_state); while !state.flushing && !self.other_streams.lock().0.iter().all(|s| { let s = s.state.lock(); s.eos || s.current_running_time .opt_ge(current_running_time) .unwrap_or(false) }) { gst::log!(CAT, obj: pad, "Waiting for other streams to stop"); self.main_stream_cond.wait(&mut state); } if state.flushing { gst::debug!(CAT, obj: pad, "Flushing"); return Ok(HandleResult::Flushing); } let mut rec_state = self.state.lock(); rec_state.recording_state = RecordingState::Stopped; rec_state.recording_duration += last_recording_duration.unwrap_or(gst::ClockTime::ZERO); rec_state.last_recording_start = None; rec_state.last_recording_stop = None; gst::debug!( CAT, obj: pad, "Stopped at {}, recording duration {}", current_running_time.display(), rec_state.recording_duration.display(), ); // Then become Stopped and drop this buffer. We always stop right before // a keyframe gst::log!(CAT, obj: pad, "Dropping buffer (stopped)"); drop(rec_state); drop(state); self.instance().notify("recording"); Ok(HandleResult::Drop) } RecordingState::Stopped => { gst::log!(CAT, obj: pad, "Dropping buffer (stopped)"); Ok(HandleResult::Drop) } RecordingState::Starting => { // If this is no keyframe, we can directly go out again here and drop the frame if !data.is_keyframe() { gst::log!(CAT, obj: pad, "Dropping non-keyframe buffer (starting)"); drop(rec_state); drop(state); if settings_changed { gst::debug!(CAT, obj: pad, "Requesting a new keyframe"); stream .sinkpad .push_event(gst_video::UpstreamForceKeyUnitEvent::builder().build()); } return Ok(HandleResult::Drop); } // Remember the time when we started: now! rec_state.last_recording_start = current_running_time; rec_state.running_time_offset = current_running_time.map_or(0, |current_running_time| { current_running_time .saturating_sub(rec_state.recording_duration) .nseconds() }) as i64; gst::debug!( CAT, obj: pad, "Starting at {}, previous accumulated recording duration {}", current_running_time.display(), rec_state.recording_duration, ); state.segment_pending = true; state.discont_pending = true; for other_stream in &self.other_streams.lock().0 { let mut other_state = other_stream.state.lock(); other_state.segment_pending = true; other_state.discont_pending = true; } // Then unlock and wait for all other streams to reach a buffer that is completely // after/at the recording start position (i.e. can be passed through completely) or // go EOS instead. drop(rec_state); while !state.flushing && !self.other_streams.lock().0.iter().all(|s| { let s = s.state.lock(); s.eos || s.current_running_time .opt_ge(current_running_time) .unwrap_or(false) }) { gst::log!(CAT, obj: pad, "Waiting for other streams to start"); self.main_stream_cond.wait(&mut state); } if state.flushing { gst::debug!(CAT, obj: pad, "Flushing"); return Ok(HandleResult::Flushing); } let mut rec_state = self.state.lock(); rec_state.recording_state = RecordingState::Recording; gst::debug!( CAT, obj: pad, "Started at {}, recording duration {}", current_running_time.display(), rec_state.recording_duration ); gst::log!(CAT, obj: pad, "Passing buffer (recording)"); drop(rec_state); drop(state); self.instance().notify("recording"); Ok(HandleResult::Pass(data)) } } } #[allow(clippy::blocks_in_if_conditions)] fn handle_secondary_stream( &self, pad: &gst::Pad, stream: &Stream, data: T, ) -> Result, gst::FlowError> { // Calculate end pts & current running time and make sure we stay in the segment let mut state = stream.state.lock(); let mut pts = data.pts().ok_or_else(|| { gst::element_imp_error!(self, gst::StreamError::Format, ["Buffer without PTS"]); gst::FlowError::Error })?; if data.dts().map_or(false, |dts| dts != pts) { gst::element_imp_error!( self, gst::StreamError::Format, ["DTS != PTS not supported for secondary streams"] ); return Err(gst::FlowError::Error); } if !data.is_keyframe() { gst::element_imp_error!( self, gst::StreamError::Format, ["Delta-units not supported for secondary streams"] ); return Err(gst::FlowError::Error); } let mut pts_end = pts + data.duration(&state).unwrap_or(gst::ClockTime::ZERO); let data = match data.clip(&state, &state.in_segment) { None => { gst::log!(CAT, obj: pad, "Dropping raw data outside segment"); return Ok(HandleResult::Drop); } Some(data) => data, }; // This will only do anything for non-raw data pts = state.in_segment.start().unwrap().max(pts); pts_end = state.in_segment.start().unwrap().max(pts_end); if let Some(stop) = state.in_segment.stop() { pts = stop.min(pts); pts_end = stop.min(pts_end); } let current_running_time = state.in_segment.to_running_time(pts); let current_running_time_end = state.in_segment.to_running_time(pts_end); state.current_running_time = current_running_time .opt_max(state.current_running_time) .or(current_running_time); state.current_running_time_end = current_running_time_end .opt_max(state.current_running_time_end) .or(current_running_time_end); gst::log!( CAT, obj: pad, "Secondary stream current running time {}-{} (position: {}-{}", current_running_time.display(), current_running_time_end.display(), pts, pts_end ); drop(state); let mut main_state = self.main_stream.state.lock(); // Wake up, in case the main stream is waiting for us to progress up to here. We progressed // above but all notifying must happen while the main_stream state is locked as per above. self.main_stream_cond.notify_all(); state = stream.state.lock(); let mut rec_state = self.state.lock(); // Wait until the main stream advanced completely past our current running time in // Recording/Stopped modes to make sure we're not already outputting/dropping data that // should actually be dropped/output if recording is started/stopped now. // // In Starting/Stopping mode we wait if we the start of this buffer is after last recording // start/stop as in that case we should be in Recording/Stopped mode already. The main // stream is waiting for us to reach that position to switch to Recording/Stopped mode so // that in those modes we only have to pass through/drop the whole buffers. while (main_state.current_running_time.is_none() || rec_state.recording_state != RecordingState::Starting && rec_state.recording_state != RecordingState::Stopping && main_state .current_running_time_end .opt_lt(current_running_time_end) .unwrap_or(false) || rec_state.recording_state == RecordingState::Starting && (rec_state.last_recording_start.is_none() || rec_state .last_recording_start .opt_le(current_running_time) .unwrap_or(false)) || rec_state.recording_state == RecordingState::Stopping && (rec_state.last_recording_stop.is_none() || rec_state .last_recording_stop .opt_le(current_running_time) .unwrap_or(false))) && !main_state.eos && !state.flushing { gst::log!( CAT, obj: pad, "Waiting at {}-{} in {:?} state, main stream at {}-{}", current_running_time.display(), current_running_time_end.display(), rec_state.recording_state, main_state.current_running_time.display(), main_state.current_running_time_end.display(), ); drop(rec_state); drop(state); self.main_stream_cond.wait(&mut main_state); state = stream.state.lock(); rec_state = self.state.lock(); } if state.flushing { gst::debug!(CAT, obj: pad, "Flushing"); return Ok(HandleResult::Flushing); } // If the main stream is EOS, we are also EOS unless we are // before the final last recording stop running time if main_state.eos { // If we have no start or stop position (we never recorded) then we're EOS too now if rec_state.last_recording_stop.is_none() || rec_state.last_recording_start.is_none() { gst::debug!(CAT, obj: pad, "Main stream EOS and recording never started",); return Ok(HandleResult::Eos(self.check_and_update_eos( pad, stream, &mut state, &mut rec_state, ))); } let last_recording_start = rec_state.last_recording_start.expect("recording started"); // FIXME it would help a lot if we could expect current_running_time // and possibly current_running_time_end at some point. if data.can_clip(&*state) && current_running_time.map_or(false, |cur_rt| cur_rt < last_recording_start) && current_running_time_end .map_or(false, |cur_rt_end| cur_rt_end > last_recording_start) { // Otherwise if we're before the recording start but the end of the buffer is after // the start and we can clip, clip the buffer and pass it onwards. gst::debug!( CAT, obj: pad, "Main stream EOS and we're not EOS yet (overlapping recording start, {} < {} < {})", current_running_time.display(), last_recording_start, current_running_time_end.display(), ); let mut clip_start = state .in_segment .position_from_running_time(rec_state.last_recording_start); if clip_start.is_none() { clip_start = state.in_segment.start(); } let mut clip_stop = state .in_segment .position_from_running_time(rec_state.last_recording_stop); if clip_stop.is_none() { clip_stop = state.in_segment.stop(); } let mut segment = state.in_segment.clone(); segment.set_start(clip_start); segment.set_stop(clip_stop); gst::log!(CAT, obj: pad, "Clipping to segment {:?}", segment); if let Some(data) = data.clip(&*state, &segment) { return Ok(HandleResult::Pass(data)); } else { gst::warning!(CAT, obj: pad, "Complete buffer clipped!"); return Ok(HandleResult::Drop); } } else if current_running_time .opt_lt(last_recording_start) .unwrap_or(false) { // Otherwise if the buffer starts before the recording start, drop it. This // means that we either can't clip, or that the end is also before the // recording start gst::debug!( CAT, obj: pad, "Main stream EOS and we're not EOS yet (before recording start, {} < {})", current_running_time.display(), last_recording_start, ); return Ok(HandleResult::Drop); } else if data.can_clip(&*state) && current_running_time .opt_lt(rec_state.last_recording_stop) .unwrap_or(false) && current_running_time_end .opt_gt(rec_state.last_recording_stop) .unwrap_or(false) { // Similarly if the end is after the recording stop but the start is before and we // can clip, clip the buffer and pass it through. gst::debug!( CAT, obj: pad, "Main stream EOS and we're not EOS yet (overlapping recording end, {} < {} < {})", current_running_time.display(), rec_state.last_recording_stop.display(), current_running_time_end.display(), ); let mut clip_start = state .in_segment .position_from_running_time(rec_state.last_recording_start); if clip_start.is_none() { clip_start = state.in_segment.start(); } let mut clip_stop = state .in_segment .position_from_running_time(rec_state.last_recording_stop); if clip_stop.is_none() { clip_stop = state.in_segment.stop(); } let mut segment = state.in_segment.clone(); segment.set_start(clip_start); segment.set_stop(clip_stop); gst::log!(CAT, obj: pad, "Clipping to segment {:?}", segment,); if let Some(data) = data.clip(&*state, &segment) { return Ok(HandleResult::Pass(data)); } else { gst::warning!(CAT, obj: pad, "Complete buffer clipped!"); return Ok(HandleResult::Eos(self.check_and_update_eos( pad, stream, &mut state, &mut rec_state, ))); } } else if current_running_time_end .opt_gt(rec_state.last_recording_stop) .unwrap_or(false) { // Otherwise if the end of the buffer is after the recording stop, we're EOS // now. This means that we either couldn't clip or that the start is also after // the recording stop gst::debug!( CAT, obj: pad, "Main stream EOS and we're EOS too (after recording end, {} > {})", current_running_time_end.display(), rec_state.last_recording_stop.display(), ); return Ok(HandleResult::Eos(self.check_and_update_eos( pad, stream, &mut state, &mut rec_state, ))); } else { // In all other cases the buffer is fully between recording start and end and // can be passed through as is assert!(current_running_time .opt_ge(last_recording_start) .unwrap_or(false)); assert!(current_running_time_end .opt_le(rec_state.last_recording_stop) .unwrap_or(false)); gst::debug!( CAT, obj: pad, "Main stream EOS and we're not EOS yet (before recording end, {} <= {} <= {})", last_recording_start, current_running_time.display(), rec_state.last_recording_stop.display(), ); return Ok(HandleResult::Pass(data)); } } match rec_state.recording_state { RecordingState::Recording => { // The end of our buffer must be before/at the end of the previous buffer of the main // stream assert!(current_running_time_end .opt_le(main_state.current_running_time_end) .unwrap_or(false)); // We're properly started, must have a start position and // be actually after that start position assert!(current_running_time .opt_ge(rec_state.last_recording_start) .unwrap_or(false)); gst::log!(CAT, obj: pad, "Passing buffer (recording)"); Ok(HandleResult::Pass(data)) } RecordingState::Stopping => { // If we have no start position yet, the main stream is waiting for a key-frame let last_recording_stop = match rec_state.last_recording_stop { Some(last_recording_stop) => last_recording_stop, None => { gst::log!( CAT, obj: pad, "Passing buffer (stopping: waiting for keyframe)", ); return Ok(HandleResult::Pass(data)); } }; // The start of our buffer must be before the last recording stop as // otherwise we would be in Stopped state already assert!(current_running_time.map_or(false, |cur_rt| cur_rt < last_recording_stop)); let current_running_time = current_running_time.expect("checked above"); if current_running_time_end .map_or(false, |cur_rt_end| cur_rt_end <= last_recording_stop) { gst::log!( CAT, obj: pad, "Passing buffer (stopping: {} <= {})", current_running_time_end.display(), last_recording_stop, ); Ok(HandleResult::Pass(data)) } else if data.can_clip(&*state) && current_running_time < last_recording_stop && current_running_time_end .map_or(false, |cur_rt_end| cur_rt_end > last_recording_stop) { gst::log!( CAT, obj: pad, "Passing buffer (stopping: {} < {} < {})", current_running_time, last_recording_stop, current_running_time_end.display(), ); let mut clip_stop = state .in_segment .position_from_running_time(rec_state.last_recording_stop); if clip_stop.is_none() { clip_stop = state.in_segment.stop(); } let mut segment = state.in_segment.clone(); segment.set_stop(clip_stop); gst::log!(CAT, obj: pad, "Clipping to segment {:?}", segment,); if let Some(data) = data.clip(&*state, &segment) { Ok(HandleResult::Pass(data)) } else { gst::warning!(CAT, obj: pad, "Complete buffer clipped!"); Ok(HandleResult::Drop) } } else { gst::log!( CAT, obj: pad, "Dropping buffer (stopping: {} > {})", current_running_time_end.display(), rec_state.last_recording_stop.display(), ); Ok(HandleResult::Drop) } } RecordingState::Stopped => { // The end of our buffer must be before/at the end of the previous buffer of the main // stream assert!(current_running_time_end .opt_le(main_state.current_running_time_end) .unwrap_or(false)); // We're properly stopped gst::log!(CAT, obj: pad, "Dropping buffer (stopped)"); Ok(HandleResult::Drop) } RecordingState::Starting => { // If we have no start position yet, the main stream is waiting for a key-frame let last_recording_start = match rec_state.last_recording_start { Some(last_recording_start) => last_recording_start, None => { gst::log!( CAT, obj: pad, "Dropping buffer (starting: waiting for keyframe)", ); return Ok(HandleResult::Drop); } }; // The start of our buffer must be before the last recording start as // otherwise we would be in Recording state already assert!(current_running_time.map_or(false, |cur_rt| cur_rt < last_recording_start)); let current_running_time = current_running_time.expect("checked_above"); if current_running_time >= last_recording_start { gst::log!( CAT, obj: pad, "Passing buffer (starting: {} >= {})", current_running_time, last_recording_start, ); Ok(HandleResult::Pass(data)) } else if data.can_clip(&*state) && current_running_time < last_recording_start && current_running_time_end .map_or(false, |cur_rt_end| cur_rt_end > last_recording_start) { gst::log!( CAT, obj: pad, "Passing buffer (starting: {} < {} < {})", current_running_time, last_recording_start, current_running_time_end.display(), ); let mut clip_start = state .in_segment .position_from_running_time(rec_state.last_recording_start); if clip_start.is_none() { clip_start = state.in_segment.start(); } let mut segment = state.in_segment.clone(); segment.set_start(clip_start); gst::log!(CAT, obj: pad, "Clipping to segment {:?}", segment); if let Some(data) = data.clip(&*state, &segment) { Ok(HandleResult::Pass(data)) } else { gst::warning!(CAT, obj: pad, "Complete buffer clipped!"); Ok(HandleResult::Drop) } } else { gst::log!( CAT, obj: pad, "Dropping buffer (starting: {} < {})", current_running_time, last_recording_start, ); Ok(HandleResult::Drop) } } } } // should be called only if main stream is in eos state fn check_and_update_eos( &self, pad: &gst::Pad, stream: &Stream, stream_state: &mut StreamState, rec_state: &mut State, ) -> bool { stream_state.eos = true; // Check whether all secondary streams are in eos. If so, update recording // state to Stopped if rec_state.recording_state != RecordingState::Stopped { let mut all_others_eos = true; // Check eos state of all secondary streams self.other_streams.lock().0.iter().all(|s| { if s == stream { return true; } let s = s.state.lock(); if !s.eos { all_others_eos = false; } all_others_eos }); if all_others_eos { gst::debug!( CAT, obj: pad, "All streams are in EOS state, change state to Stopped" ); rec_state.recording_state = RecordingState::Stopped; return true; } } false } // should be called only if main stream stops being in eos state fn check_and_update_stream_start( &self, pad: &gst::Pad, stream: &Stream, stream_state: &mut StreamState, rec_state: &mut State, ) -> bool { stream_state.eos = false; // Check whether no secondary streams are in eos. If so, update recording // state according to the record property if rec_state.recording_state == RecordingState::Stopped { let mut all_others_not_eos = false; // Check eos state of all secondary streams self.other_streams.lock().0.iter().any(|s| { if s == stream { return false; } let s = s.state.lock(); if !s.eos { all_others_not_eos = true; } all_others_not_eos }); if !all_others_not_eos { let settings = self.settings.lock(); if settings.record { gst::debug!(CAT, obj: pad, "Restarting recording after EOS"); rec_state.recording_state = RecordingState::Starting; } } } false } fn sink_chain( &self, pad: &gst::Pad, buffer: gst::Buffer, ) -> Result { let stream = self.pads.lock().get(pad).cloned().ok_or_else(|| { gst::element_imp_error!(self, gst::CoreError::Pad, ["Unknown pad {:?}", pad.name()]); gst::FlowError::Error })?; gst::log!(CAT, obj: pad, "Handling buffer {:?}", buffer); { let state = stream.state.lock(); if state.eos { return Err(gst::FlowError::Eos); } if state.flushing { return Err(gst::FlowError::Flushing); } } let handle_result = if stream != self.main_stream { self.handle_secondary_stream(pad, &stream, buffer) } else { self.handle_main_stream(pad, &stream, buffer) }?; let mut buffer = match handle_result { HandleResult::Drop => { return Ok(gst::FlowSuccess::Ok); } HandleResult::Flushing => { return Err(gst::FlowError::Flushing); } HandleResult::Eos(recording_state_updated) => { stream.srcpad.push_event( gst::event::Eos::builder() .seqnum(stream.state.lock().segment_seqnum) .build(), ); if recording_state_updated { self.instance().notify("recording"); } return Err(gst::FlowError::Eos); } HandleResult::Pass(buffer) => { // Pass through and actually push the buffer buffer } }; let out_running_time = { let main_state = if stream != self.main_stream { Some(self.main_stream.state.lock()) } else { None }; let mut state = stream.state.lock(); if state.discont_pending { gst::debug!(CAT, obj: pad, "Pending discont"); let buffer = buffer.make_mut(); buffer.set_flags(gst::BufferFlags::DISCONT); state.discont_pending = false; } let mut events = Vec::with_capacity(state.pending_events.len() + 1); if state.segment_pending { let rec_state = self.state.lock(); // Adjust so that last_recording_start has running time of // recording_duration state.out_segment = state.in_segment.clone(); if !rec_state.live { state .out_segment .offset_running_time(-rec_state.running_time_offset) .expect("Adjusting record duration"); } events.push( gst::event::Segment::builder(&state.out_segment) .seqnum(state.segment_seqnum) .build(), ); state.segment_pending = false; gst::debug!(CAT, obj: pad, "Pending Segment {:?}", &state.out_segment); } if !state.pending_events.is_empty() { gst::debug!(CAT, obj: pad, "Pushing pending events"); } events.append(&mut state.pending_events); let out_running_time = state.out_segment.to_running_time(buffer.pts()); // Unlock before pushing drop(state); drop(main_state); for e in events.drain(..) { stream.srcpad.push_event(e); } out_running_time }; gst::log!( CAT, obj: pad, "Pushing buffer with running time {}: {:?}", out_running_time.display(), buffer, ); stream.srcpad.push(buffer) } fn sink_event(&self, pad: &gst::Pad, mut event: gst::Event) -> bool { use gst::EventView; let stream = match self.pads.lock().get(pad) { None => { gst::element_imp_error!( self, gst::CoreError::Pad, ["Unknown pad {:?}", pad.name()] ); return false; } Some(stream) => stream.clone(), }; gst::log!(CAT, obj: pad, "Handling event {:?}", event); let mut forward = true; let mut send_pending = false; let mut recording_state_changed = false; match event.view() { EventView::FlushStart(..) => { let _main_state = if stream != self.main_stream { Some(self.main_stream.state.lock()) } else { None }; let mut state = stream.state.lock(); state.flushing = true; self.main_stream_cond.notify_all(); } EventView::FlushStop(..) => { let mut state = stream.state.lock(); state.eos = false; state.flushing = false; state.segment_pending = true; state.discont_pending = true; state.current_running_time = None; state.current_running_time_end = None; } EventView::Caps(c) => { let mut state = stream.state.lock(); let caps = c.caps(); let s = caps.structure(0).unwrap(); if s.name().starts_with("audio/") { state.audio_info = gst_audio::AudioInfo::from_caps(caps).ok(); gst::log!(CAT, obj: pad, "Got audio caps {:?}", state.audio_info); state.video_info = None; } else if s.name().starts_with("video/") { state.audio_info = None; state.video_info = gst_video::VideoInfo::from_caps(caps).ok(); gst::log!(CAT, obj: pad, "Got video caps {:?}", state.video_info); } else { state.audio_info = None; state.video_info = None; } } EventView::Segment(e) => { let mut state = stream.state.lock(); let segment = match e.segment().clone().downcast::() { Err(segment) => { gst::element_imp_error!( self, gst::StreamError::Format, ["Only Time segments supported, got {:?}", segment.format(),] ); return false; } Ok(segment) => segment, }; if (segment.rate() - 1.0).abs() > f64::EPSILON { gst::element_imp_error!( self, gst::StreamError::Format, [ "Only rate==1.0 segments supported, got {:?}", segment.rate(), ] ); return false; } state.in_segment = segment; state.segment_seqnum = event.seqnum(); state.segment_pending = true; state.current_running_time = None; state.current_running_time_end = None; gst::debug!(CAT, obj: pad, "Got new Segment {:?}", state.in_segment); forward = false; } EventView::Gap(e) => { gst::debug!(CAT, obj: pad, "Handling Gap event {:?}", event); let (pts, duration) = e.get(); let handle_result = if stream == self.main_stream { self.handle_main_stream(pad, &stream, (pts, duration)) } else { self.handle_secondary_stream(pad, &stream, (pts, duration)) }; forward = match handle_result { Ok(HandleResult::Pass((new_pts, new_duration))) => { if new_pts != pts || new_duration.is_some() && new_duration != duration { event = gst::event::Gap::builder(new_pts) .duration(new_duration) .build(); } true } _ => false, }; } EventView::StreamStart(..) => { let main_state = if stream != self.main_stream { Some(self.main_stream.state.lock()) } else { None }; let mut state = stream.state.lock(); state.eos = false; let main_is_eos = main_state .as_ref() .map_or(false, |main_state| main_state.eos); if !main_is_eos { let mut rec_state = self.state.lock(); recording_state_changed = self.check_and_update_stream_start( pad, &stream, &mut state, &mut rec_state, ); } self.main_stream_cond.notify_all(); gst::debug!(CAT, obj: pad, "Stream is not EOS now"); } EventView::Eos(..) => { let main_state = if stream != self.main_stream { Some(self.main_stream.state.lock()) } else { None }; let mut state = stream.state.lock(); state.eos = true; let main_is_eos = main_state .as_ref() .map_or(true, |main_state| main_state.eos); if main_is_eos { let mut rec_state = self.state.lock(); recording_state_changed = self.check_and_update_eos(pad, &stream, &mut state, &mut rec_state); } self.main_stream_cond.notify_all(); gst::debug!( CAT, obj: pad, "Stream is EOS now, sending any pending events" ); send_pending = true; } _ => (), }; if recording_state_changed { self.instance().notify("recording"); } // If a serialized event and coming after Segment and a new Segment is pending, // queue up and send at a later time (buffer/gap) after we sent the Segment let type_ = event.type_(); if forward && type_ != gst::EventType::Eos && type_.is_serialized() && type_.partial_cmp(&gst::EventType::Segment) == Some(cmp::Ordering::Greater) { let mut state = stream.state.lock(); if state.segment_pending { gst::log!(CAT, obj: pad, "Storing event for later pushing"); state.pending_events.push(event); return true; } } if send_pending { let mut state = stream.state.lock(); let mut events = Vec::with_capacity(state.pending_events.len() + 1); // Got not a single buffer on this stream before EOS, forward // the input segment if state.segment_pending { events.push( gst::event::Segment::builder(&state.in_segment) .seqnum(state.segment_seqnum) .build(), ); } events.append(&mut state.pending_events); drop(state); for e in events.drain(..) { stream.srcpad.push_event(e); } } if forward { gst::log!(CAT, obj: pad, "Forwarding event {:?}", event); stream.srcpad.push_event(event) } else { gst::log!(CAT, obj: pad, "Dropping event {:?}", event); true } } fn sink_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool { let stream = match self.pads.lock().get(pad) { None => { gst::element_imp_error!( self, gst::CoreError::Pad, ["Unknown pad {:?}", pad.name()] ); return false; } Some(stream) => stream.clone(), }; gst::log!(CAT, obj: pad, "Handling query {:?}", query); stream.srcpad.peer_query(query) } // FIXME `matches!` was introduced in rustc 1.42.0, current MSRV is 1.41.0 // FIXME uncomment when CI can upgrade to 1.47.1 //#[allow(clippy::match_like_matches_macro)] fn src_event(&self, pad: &gst::Pad, mut event: gst::Event) -> bool { use gst::EventView; let stream = match self.pads.lock().get(pad) { None => { gst::element_imp_error!( self, gst::CoreError::Pad, ["Unknown pad {:?}", pad.name()] ); return false; } Some(stream) => stream.clone(), }; gst::log!(CAT, obj: pad, "Handling event {:?}", event); let forward = !matches!(event.view(), EventView::Seek(..)); let rec_state = self.state.lock(); let offset = event.running_time_offset(); event .make_mut() .set_running_time_offset(offset + rec_state.running_time_offset); drop(rec_state); if forward { gst::log!(CAT, obj: pad, "Forwarding event {:?}", event); stream.sinkpad.push_event(event) } else { gst::log!(CAT, obj: pad, "Dropping event {:?}", event); false } } fn src_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool { use gst::QueryViewMut; let stream = match self.pads.lock().get(pad) { None => { gst::element_imp_error!( self, gst::CoreError::Pad, ["Unknown pad {:?}", pad.name()] ); return false; } Some(stream) => stream.clone(), }; gst::log!(CAT, obj: pad, "Handling query {:?}", query); match query.view_mut() { QueryViewMut::Scheduling(q) => { let mut new_query = gst::query::Scheduling::new(); let res = stream.sinkpad.peer_query(&mut new_query); if !res { return res; } gst::log!(CAT, obj: pad, "Downstream returned {:?}", new_query); let (flags, min, max, align) = new_query.result(); q.set(flags, min, max, align); q.add_scheduling_modes( &new_query .scheduling_modes() .iter() .cloned() .filter(|m| m != &gst::PadMode::Pull) .collect::>(), ); gst::log!(CAT, obj: pad, "Returning {:?}", q.query_mut()); true } QueryViewMut::Seeking(q) => { // Seeking is not possible here let format = q.format(); q.set( false, gst::GenericFormattedValue::none_for_format(format), gst::GenericFormattedValue::none_for_format(format), ); gst::log!(CAT, obj: pad, "Returning {:?}", q.query_mut()); true } // Position and duration is always the current recording position QueryViewMut::Position(q) => { if q.format() == gst::Format::Time { let state = stream.state.lock(); let rec_state = self.state.lock(); let mut recording_duration = rec_state.recording_duration; if rec_state.recording_state == RecordingState::Recording || rec_state.recording_state == RecordingState::Stopping { if let Some(delta) = state .current_running_time_end .opt_checked_sub(rec_state.last_recording_start) .ok() .flatten() { gst::debug!( CAT, obj: pad, "Returning position {} = {} - ({} + {})", recording_duration + delta, recording_duration, state.current_running_time_end.display(), rec_state.last_recording_start.display(), ); recording_duration += delta; } } else { gst::debug!(CAT, obj: pad, "Returning position {}", recording_duration); } q.set(recording_duration); true } else { false } } QueryViewMut::Duration(q) => { if q.format() == gst::Format::Time { let state = stream.state.lock(); let rec_state = self.state.lock(); let mut recording_duration = rec_state.recording_duration; if rec_state.recording_state == RecordingState::Recording || rec_state.recording_state == RecordingState::Stopping { if let Some(delta) = state .current_running_time_end .opt_checked_sub(rec_state.last_recording_start) .ok() .flatten() { gst::debug!( CAT, obj: pad, "Returning duration {} = {} - ({} + {})", recording_duration + delta, recording_duration, state.current_running_time_end.display(), rec_state.last_recording_start.display(), ); recording_duration += delta; } } else { gst::debug!(CAT, obj: pad, "Returning duration {}", recording_duration); } q.set(recording_duration); true } else { false } } _ => { gst::log!(CAT, obj: pad, "Forwarding query {:?}", query); stream.sinkpad.peer_query(query) } } } fn iterate_internal_links(&self, pad: &gst::Pad) -> gst::Iterator { let stream = match self.pads.lock().get(pad) { None => { gst::element_imp_error!( self, gst::CoreError::Pad, ["Unknown pad {:?}", pad.name()] ); return gst::Iterator::from_vec(vec![]); } Some(stream) => stream.clone(), }; if pad == &stream.srcpad { gst::Iterator::from_vec(vec![stream.sinkpad]) } else { gst::Iterator::from_vec(vec![stream.srcpad]) } } } #[glib::object_subclass] impl ObjectSubclass for ToggleRecord { const NAME: &'static str = "GstToggleRecord"; type Type = super::ToggleRecord; type ParentType = gst::Element; fn with_class(klass: &Self::Class) -> Self { let templ = klass.pad_template("sink").unwrap(); let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink")) .chain_function(|pad, parent, buffer| { ToggleRecord::catch_panic_pad_function( parent, || Err(gst::FlowError::Error), |togglerecord| togglerecord.sink_chain(pad, buffer), ) }) .event_function(|pad, parent, event| { ToggleRecord::catch_panic_pad_function( parent, || false, |togglerecord| togglerecord.sink_event(pad, event), ) }) .query_function(|pad, parent, query| { ToggleRecord::catch_panic_pad_function( parent, || false, |togglerecord| togglerecord.sink_query(pad, query), ) }) .iterate_internal_links_function(|pad, parent| { ToggleRecord::catch_panic_pad_function( parent, || gst::Iterator::from_vec(vec![]), |togglerecord| togglerecord.iterate_internal_links(pad), ) }) .build(); let templ = klass.pad_template("src").unwrap(); let srcpad = gst::Pad::builder_with_template(&templ, Some("src")) .event_function(|pad, parent, event| { ToggleRecord::catch_panic_pad_function( parent, || false, |togglerecord| togglerecord.src_event(pad, event), ) }) .query_function(|pad, parent, query| { ToggleRecord::catch_panic_pad_function( parent, || false, |togglerecord| togglerecord.src_query(pad, query), ) }) .iterate_internal_links_function(|pad, parent| { ToggleRecord::catch_panic_pad_function( parent, || gst::Iterator::from_vec(vec![]), |togglerecord| togglerecord.iterate_internal_links(pad), ) }) .build(); let main_stream = Stream::new(sinkpad, srcpad); let mut pads = HashMap::new(); pads.insert(main_stream.sinkpad.clone(), main_stream.clone()); pads.insert(main_stream.srcpad.clone(), main_stream.clone()); Self { settings: Mutex::new(Settings::default()), state: Mutex::new(State::default()), main_stream, main_stream_cond: Condvar::new(), other_streams: Mutex::new((Vec::new(), 0)), pads: Mutex::new(pads), } } } impl ObjectImpl for ToggleRecord { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ glib::ParamSpecBoolean::builder("record") .nick("Record") .blurb("Enable/disable recording") .default_value(DEFAULT_RECORD) .mutable_playing() .build(), glib::ParamSpecBoolean::builder("recording") .nick("Recording") .blurb("Whether recording is currently taking place") .default_value(DEFAULT_RECORD) .read_only() .build(), glib::ParamSpecBoolean::builder("is-live") .nick("Live mode") .blurb("Live mode: no \"gap eating\", forward incoming segment") .default_value(DEFAULT_LIVE) .mutable_ready() .build(), ] }); PROPERTIES.as_ref() } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { match pspec.name() { "record" => { let mut settings = self.settings.lock(); let record = value.get().expect("type checked upstream"); gst::debug!( CAT, imp: self, "Setting record from {:?} to {:?}", settings.record, record ); settings.record = record; } "is-live" => { let mut settings = self.settings.lock(); let live = value.get().expect("type checked upstream"); gst::debug!( CAT, imp: self, "Setting live from {:?} to {:?}", settings.live, live ); settings.live = live; } _ => unimplemented!(), } } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "record" => { let settings = self.settings.lock(); settings.record.to_value() } "recording" => { let rec_state = self.state.lock(); (rec_state.recording_state == RecordingState::Recording).to_value() } "is-live" => { let settings = self.settings.lock(); settings.live.to_value() } _ => unimplemented!(), } } fn constructed(&self) { self.parent_constructed(); let obj = self.instance(); obj.add_pad(&self.main_stream.sinkpad).unwrap(); obj.add_pad(&self.main_stream.srcpad).unwrap(); } } impl GstObjectImpl for ToggleRecord {} impl ElementImpl for ToggleRecord { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { gst::subclass::ElementMetadata::new( "Toggle Record", "Generic", "Valve that ensures multiple streams start/end at the same time", "Sebastian Dröge ", ) }); Some(&*ELEMENT_METADATA) } fn pad_templates() -> &'static [gst::PadTemplate] { static PAD_TEMPLATES: Lazy> = Lazy::new(|| { let caps = gst::Caps::new_any(); let src_pad_template = gst::PadTemplate::new( "src", gst::PadDirection::Src, gst::PadPresence::Always, &caps, ) .unwrap(); let sink_pad_template = gst::PadTemplate::new( "sink", gst::PadDirection::Sink, gst::PadPresence::Always, &caps, ) .unwrap(); let secondary_src_pad_template = gst::PadTemplate::new( "src_%u", gst::PadDirection::Src, gst::PadPresence::Sometimes, &caps, ) .unwrap(); let secondary_sink_pad_template = gst::PadTemplate::new( "sink_%u", gst::PadDirection::Sink, gst::PadPresence::Request, &caps, ) .unwrap(); vec![ src_pad_template, sink_pad_template, secondary_src_pad_template, secondary_sink_pad_template, ] }); PAD_TEMPLATES.as_ref() } fn change_state( &self, transition: gst::StateChange, ) -> Result { gst::trace!(CAT, imp: self, "Changing state {:?}", transition); match transition { gst::StateChange::ReadyToPaused => { for s in self .other_streams .lock() .0 .iter() .chain(iter::once(&self.main_stream)) { let mut state = s.state.lock(); *state = StreamState::default(); } let mut rec_state = self.state.lock(); *rec_state = State::default(); let settings = *self.settings.lock(); rec_state.live = settings.live; } gst::StateChange::PausedToReady => { for s in &self.other_streams.lock().0 { let mut state = s.state.lock(); state.flushing = true; } let mut state = self.main_stream.state.lock(); state.flushing = true; self.main_stream_cond.notify_all(); } _ => (), } let success = self.parent_change_state(transition)?; if transition == gst::StateChange::PausedToReady { for s in self .other_streams .lock() .0 .iter() .chain(iter::once(&self.main_stream)) { let mut state = s.state.lock(); state.pending_events.clear(); } let mut rec_state = self.state.lock(); *rec_state = State::default(); drop(rec_state); self.instance().notify("recording"); } Ok(success) } fn request_new_pad( &self, _templ: &gst::PadTemplate, _name: Option<&str>, _caps: Option<&gst::Caps>, ) -> Option { let mut other_streams_guard = self.other_streams.lock(); let (ref mut other_streams, ref mut pad_count) = *other_streams_guard; let mut pads = self.pads.lock(); let id = *pad_count; *pad_count += 1; let templ = self.instance().pad_template("sink_%u").unwrap(); let sinkpad = gst::Pad::builder_with_template(&templ, Some(format!("sink_{}", id).as_str())) .chain_function(|pad, parent, buffer| { ToggleRecord::catch_panic_pad_function( parent, || Err(gst::FlowError::Error), |togglerecord| togglerecord.sink_chain(pad, buffer), ) }) .event_function(|pad, parent, event| { ToggleRecord::catch_panic_pad_function( parent, || false, |togglerecord| togglerecord.sink_event(pad, event), ) }) .query_function(|pad, parent, query| { ToggleRecord::catch_panic_pad_function( parent, || false, |togglerecord| togglerecord.sink_query(pad, query), ) }) .iterate_internal_links_function(|pad, parent| { ToggleRecord::catch_panic_pad_function( parent, || gst::Iterator::from_vec(vec![]), |togglerecord| togglerecord.iterate_internal_links(pad), ) }) .build(); let templ = self.instance().pad_template("src_%u").unwrap(); let srcpad = gst::Pad::builder_with_template(&templ, Some(format!("src_{}", id).as_str())) .event_function(|pad, parent, event| { ToggleRecord::catch_panic_pad_function( parent, || false, |togglerecord| togglerecord.src_event(pad, event), ) }) .query_function(|pad, parent, query| { ToggleRecord::catch_panic_pad_function( parent, || false, |togglerecord| togglerecord.src_query(pad, query), ) }) .iterate_internal_links_function(|pad, parent| { ToggleRecord::catch_panic_pad_function( parent, || gst::Iterator::from_vec(vec![]), |togglerecord| togglerecord.iterate_internal_links(pad), ) }) .build(); sinkpad.set_active(true).unwrap(); srcpad.set_active(true).unwrap(); let stream = Stream::new(sinkpad.clone(), srcpad.clone()); pads.insert(stream.sinkpad.clone(), stream.clone()); pads.insert(stream.srcpad.clone(), stream.clone()); other_streams.push(stream); drop(pads); drop(other_streams_guard); self.instance().add_pad(&sinkpad).unwrap(); self.instance().add_pad(&srcpad).unwrap(); Some(sinkpad) } fn release_pad(&self, pad: &gst::Pad) { let mut other_streams_guard = self.other_streams.lock(); let (ref mut other_streams, _) = *other_streams_guard; let mut pads = self.pads.lock(); let stream = match pads.get(pad) { None => return, Some(stream) => stream.clone(), }; pads.remove(&stream.sinkpad).unwrap(); pads.remove(&stream.srcpad).unwrap(); // TODO: Replace with Vec::remove_item() once stable let pos = other_streams.iter().position(|x| *x == stream); pos.map(|pos| other_streams.swap_remove(pos)); drop(pads); drop(other_streams_guard); let main_state = self.main_stream.state.lock(); self.main_stream_cond.notify_all(); drop(main_state); stream.srcpad.set_active(false).unwrap(); stream.sinkpad.set_active(false).unwrap(); self.instance().remove_pad(&stream.sinkpad).unwrap(); self.instance().remove_pad(&stream.srcpad).unwrap(); } }