mp4 example: allow in-band h264 params

Fixes #43
This commit is contained in:
Scott Lamb 2022-01-30 21:18:47 -08:00
parent e4be827a63
commit 499d658352

View File

@ -20,10 +20,10 @@
use anyhow::{anyhow, bail, Context, Error}; use anyhow::{anyhow, bail, Context, Error};
use bytes::{Buf, BufMut, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use futures::{Future, StreamExt}; use futures::{Future, StreamExt};
use log::{info, warn}; use log::{debug, info, warn};
use retina::{ use retina::{
client::Transport, client::Transport,
codec::{AudioParameters, CodecItem, VideoParameters}, codec::{AudioParameters, CodecItem, Parameters, VideoParameters},
}; };
use std::num::NonZeroU32; use std::num::NonZeroU32;
@ -530,9 +530,17 @@ impl<W: AsyncWrite + AsyncSeek + Send + Unpin> Mp4Writer<W> {
); );
if let Some(p) = frame.new_parameters.take() { if let Some(p) = frame.new_parameters.take() {
if self.video_trak.samples > 0 { if self.video_trak.samples > 0 {
bail!("parameters change unimplemented. new parameters: {:#?}", p); let old = self.video_params.as_ref().unwrap();
bail!(
"video parameters change unimplemented.\nold: {:#?}\nnew: {:#?}",
old,
p
);
} }
self.video_params = Some(p); self.video_params = Some(p);
} else if self.video_params.is_none() {
debug!("Discarding video frame received before parameters");
return Ok(());
} }
let size = u32::try_from(frame.data().remaining())?; let size = u32::try_from(frame.data().remaining())?;
self.video_trak.add_sample( self.video_trak.add_sample(
@ -630,8 +638,8 @@ async fn copy<'a>(
async fn write_mp4<'a>( async fn write_mp4<'a>(
opts: &'a Opts, opts: &'a Opts,
session: retina::client::Session<retina::client::Described>, session: retina::client::Session<retina::client::Described>,
video_stream: Option<(usize, VideoParameters)>, video_params: Option<Box<VideoParameters>>,
audio_stream: Option<(usize, AudioParameters)>, audio_params: Option<Box<AudioParameters>>,
stop_signal: Pin<Box<dyn Future<Output = Result<(), std::io::Error>>>>, stop_signal: Pin<Box<dyn Future<Output = Result<(), std::io::Error>>>>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut session = session let mut session = session
@ -650,14 +658,7 @@ async fn write_mp4<'a>(
tmp_filename.push(PARTIAL_SUFFIX); // OsString::push doesn't put in a '/', unlike PathBuf::. tmp_filename.push(PARTIAL_SUFFIX); // OsString::push doesn't put in a '/', unlike PathBuf::.
let tmp_filename: PathBuf = tmp_filename.into(); let tmp_filename: PathBuf = tmp_filename.into();
let out = tokio::fs::File::create(&tmp_filename).await?; let out = tokio::fs::File::create(&tmp_filename).await?;
let mut mp4 = Mp4Writer::new( let mut mp4 = Mp4Writer::new(video_params, audio_params, opts.allow_loss, out).await?;
video_stream.map(|(_, p)| Box::new(p)),
audio_stream.map(|(_, p)| Box::new(p)),
opts.allow_loss,
out,
)
.await?;
let result = copy(opts, &mut session, stop_signal, &mut mp4).await; let result = copy(opts, &mut session, stop_signal, &mut mp4).await;
if let Err(e) = result { if let Err(e) = result {
// Log errors about finishing, returning the original error. // Log errors about finishing, returning the original error.
@ -705,38 +706,38 @@ pub async fn run(opts: Opts) -> Result<(), Error> {
.teardown(opts.teardown), .teardown(opts.teardown),
) )
.await?; .await?;
let video_stream = if !opts.no_video { let (video_stream_i, video_params) = if !opts.no_video {
let s = session let s = session.streams().iter().enumerate().find_map(|(i, s)| {
.streams() if s.media == "video" {
.iter() if s.encoding_name == "h264" {
.enumerate() log::info!("Using h264 video stream");
.find_map(|(i, s)| match s.parameters() { return Some((
Some(retina::codec::Parameters::Video(v)) => { i,
log::info!( match s.parameters() {
"Using {} video stream (rfc 6381 codec {})", Some(Parameters::Video(v)) => Some(Box::new(v)),
&s.encoding_name, Some(_) => panic!("expected parameters to match stream type video"),
v.rfc6381_codec() None => None,
); },
Some((i, v.clone())) ));
} }
_ if s.media == "video" => {
log::info!( log::info!(
"Ignoring {} video stream because it's unsupported", "Ignoring {} video stream because it's unsupported",
&s.encoding_name &s.encoding_name
); );
}
None None
}
_ => None,
}); });
if s.is_none() { if let Some((i, p)) = s {
(Some(i), p)
} else {
log::info!("No suitable video stream found"); log::info!("No suitable video stream found");
(None, None)
} }
s
} else { } else {
log::info!("Ignoring video streams (if any) because of --no-video"); log::info!("Ignoring video streams (if any) because of --no-video");
None (None, None)
}; };
if let Some((i, _)) = video_stream { if let Some(i) = video_stream_i {
session.setup(i).await?; session.setup(i).await?;
} }
let audio_stream = if !opts.no_audio { let audio_stream = if !opts.no_audio {
@ -749,7 +750,7 @@ pub async fn run(opts: Opts) -> Result<(), Error> {
// entry. // entry.
Some(retina::codec::Parameters::Audio(a)) if a.sample_entry().is_some() => { Some(retina::codec::Parameters::Audio(a)) if a.sample_entry().is_some() => {
log::info!("Using {} audio stream (rfc 6381 codec {})", &s.encoding_name, a.rfc6381_codec().unwrap()); log::info!("Using {} audio stream (rfc 6381 codec {})", &s.encoding_name, a.rfc6381_codec().unwrap());
Some((i, a.clone())) Some((i, Box::new(a.clone())))
} }
_ if s.media == "audio" => { _ if s.media == "audio" => {
log::info!("Ignoring {} audio stream because it can't be placed into a .mp4 file without transcoding", &s.encoding_name); log::info!("Ignoring {} audio stream because it can't be placed into a .mp4 file without transcoding", &s.encoding_name);
@ -768,10 +769,17 @@ pub async fn run(opts: Opts) -> Result<(), Error> {
if let Some((i, _)) = audio_stream { if let Some((i, _)) = audio_stream {
session.setup(i).await?; session.setup(i).await?;
} }
if video_stream.is_none() && audio_stream.is_none() { if video_stream_i.is_none() && audio_stream.is_none() {
bail!("Exiting because no video or audio stream was selected; see info log messages above"); bail!("Exiting because no video or audio stream was selected; see info log messages above");
} }
let result = write_mp4(&opts, session, video_stream, audio_stream, stop_signal).await; let result = write_mp4(
&opts,
session,
video_params,
audio_stream.map(|(_i, p)| p),
stop_signal,
)
.await;
// Session has now been dropped, on success or failure. A TEARDOWN should // Session has now been dropped, on success or failure. A TEARDOWN should
// be pending if necessary. session_group.await_teardown() will wait for it. // be pending if necessary. session_group.await_teardown() will wait for it.