|
|
|
@ -16,7 +16,7 @@ use ffmpeg_sys_next::AVPictureType::AV_PICTURE_TYPE_I;
|
|
|
|
|
use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_YUV420P;
|
|
|
|
|
use ffmpeg_sys_next::{avio_wb32, AVIOContext, AVPixelFormat, AV_TIME_BASE, SWS_BILINEAR};
|
|
|
|
|
use num_rational::Ratio;
|
|
|
|
|
use std::cmp::min;
|
|
|
|
|
use std::cmp::{max, min};
|
|
|
|
|
use std::fs::{DirBuilder, File};
|
|
|
|
|
use std::io::Write;
|
|
|
|
|
use std::ops::Mul;
|
|
|
|
@ -31,13 +31,17 @@ pub struct AudioTranscoder {
|
|
|
|
|
format: Format<Buffer>,
|
|
|
|
|
output_path: String,
|
|
|
|
|
resampler: Resampler,
|
|
|
|
|
segment: u32,
|
|
|
|
|
segment: Option<u32>,
|
|
|
|
|
last_pts: i64,
|
|
|
|
|
current_pts: i64,
|
|
|
|
|
seconds_per_segment: u32,
|
|
|
|
|
segment_target: Option<u32>,
|
|
|
|
|
header_written: bool,
|
|
|
|
|
last_written_segment: Option<u32>,
|
|
|
|
|
latest_written_segment: Option<u32>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SECONDS_PER_SEGMENT: u32 = 5;
|
|
|
|
|
pub const SECONDS_PER_SEGMENT: u32 = 5;
|
|
|
|
|
|
|
|
|
|
impl AudioTranscoder {
|
|
|
|
|
fn process_packet(&mut self, packet: Packet) -> Result<(), String> {
|
|
|
|
@ -45,10 +49,22 @@ impl AudioTranscoder {
|
|
|
|
|
.send_packet(&packet)
|
|
|
|
|
.map_err(|err| format!("Failed sending audio packet: {}", err))?;
|
|
|
|
|
while let Some(frame) = self.decoder.read_frame()? {
|
|
|
|
|
if let Some(target) = self.segment_target {
|
|
|
|
|
let segment = (self
|
|
|
|
|
.input_stream
|
|
|
|
|
.time_base()
|
|
|
|
|
.mul(frame.pts() as i32)
|
|
|
|
|
.to_integer()
|
|
|
|
|
/ self.seconds_per_segment as i32) as u32;
|
|
|
|
|
if (target as i64 - 1) > (segment as i64) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let resampled = self.resampler.convert(&frame)?;
|
|
|
|
|
let resampled_pts = resampled.pts();
|
|
|
|
|
self.frame_buffer.insert_frame(resampled);
|
|
|
|
|
let mut offset: f64 = 0f64;
|
|
|
|
|
let mut offset: f64 = 0.0;
|
|
|
|
|
while let Some(drain_resampled) = self.resampler.drain()? {
|
|
|
|
|
offset += (drain_resampled.nb_samples() as f64 / self.encoder.sample_rate() as f64)
|
|
|
|
|
* self.input_stream.time_base().den() as f64;
|
|
|
|
@ -63,12 +79,25 @@ impl AudioTranscoder {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn input_stream(&self) -> &Stream {
|
|
|
|
|
pub fn input_stream(&self) -> &Stream {
|
|
|
|
|
&self.input_stream
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn output_stream(&self) -> &Stream {
|
|
|
|
|
&self.output_stream
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn encoder(&self) -> &Encoder {
|
|
|
|
|
&self.encoder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn decoder(&self) -> &Decoder {
|
|
|
|
|
&self.decoder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn start_segment(&self, force: bool) {
|
|
|
|
|
if self.segment == 0 && !force {
|
|
|
|
|
if self.segment == Some(0) && !force {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -97,8 +126,16 @@ impl AudioTranscoder {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn write_segment(&mut self) -> Result<(), String> {
|
|
|
|
|
if self.segment == 0 {
|
|
|
|
|
if self.segment == Some(0) {
|
|
|
|
|
self.write_header()?;
|
|
|
|
|
self.header_written = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !self.header_written {
|
|
|
|
|
self.format.avio_inner_mut().unwrap().buffer();
|
|
|
|
|
self.start_segment(true);
|
|
|
|
|
self.format.write_packet_null()?;
|
|
|
|
|
self.header_written = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(avio) = self.format.avio.as_mut() {
|
|
|
|
@ -107,32 +144,82 @@ impl AudioTranscoder {
|
|
|
|
|
|
|
|
|
|
let mut segment = self.format.avio_inner_mut().unwrap().buffer();
|
|
|
|
|
|
|
|
|
|
File::create(format!(
|
|
|
|
|
"{}/audio-segment-{:0>5}.m4s",
|
|
|
|
|
self.output_path, self.segment
|
|
|
|
|
))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.write_all(&mut segment)
|
|
|
|
|
.map_err(|_| format!("Failed to write audio segment {}", self.segment))?;
|
|
|
|
|
if self.segment_target.unwrap_or(self.segment.unwrap()) == self.segment.unwrap() {
|
|
|
|
|
self.segment_target = None;
|
|
|
|
|
File::create(format!(
|
|
|
|
|
"{}/audio-segment-{:0>5}.m4s",
|
|
|
|
|
self.output_path,
|
|
|
|
|
self.segment.unwrap()
|
|
|
|
|
))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.write_all(&mut segment)
|
|
|
|
|
.map_err(|_| format!("Failed to write audio segment {}", self.segment.unwrap()))?;
|
|
|
|
|
println!("Wrote segment (audio) {}", self.segment.unwrap());
|
|
|
|
|
self.last_written_segment = self.segment;
|
|
|
|
|
self.latest_written_segment =
|
|
|
|
|
max(self.latest_written_segment, Some(self.segment.unwrap()));
|
|
|
|
|
} else {
|
|
|
|
|
println!(
|
|
|
|
|
"Won't write segment (audio) {} (searching for {})",
|
|
|
|
|
self.segment.unwrap(),
|
|
|
|
|
self.segment_target.unwrap()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.start_segment(false);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn encode(&mut self) -> Result<(), String> {
|
|
|
|
|
while let Some(frame) = self.frame_buffer.pop_first() {
|
|
|
|
|
let pts_passed = frame.pts() - self.last_pts;
|
|
|
|
|
if pts_passed <= 0 && self.last_pts != 0 {
|
|
|
|
|
println!("WARN: new frame out of order");
|
|
|
|
|
if self.last_pts > frame.pts() {
|
|
|
|
|
println!("WARN: out of order frame");
|
|
|
|
|
self.last_pts = frame.pts();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pts_passed + self.current_pts)
|
|
|
|
|
> (self.seconds_per_segment as i32 * self.input_stream.time_base().den()) as i64
|
|
|
|
|
if self.last_pts == 0 {
|
|
|
|
|
self.last_pts = frame.pts();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.current_pts = frame.pts()
|
|
|
|
|
% (self.input_stream.time_base().den() as u32 * self.seconds_per_segment) as i64;
|
|
|
|
|
self.segment = Some(
|
|
|
|
|
(frame.pts() as f64 / self.input_stream.time_base().den() as f64).floor() as u32
|
|
|
|
|
/ self.seconds_per_segment,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if self.current_pts == 0 && Some(0) < self.segment {
|
|
|
|
|
self.segment = Some(self.segment.unwrap() - 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (should_skip, peek) = if let Some(peek) = self.frame_buffer.first_key() {
|
|
|
|
|
(
|
|
|
|
|
(self.current_pts + (peek - frame.pts()))
|
|
|
|
|
< (self.seconds_per_segment * self.input_stream.time_base().den() as u32)
|
|
|
|
|
as i64,
|
|
|
|
|
peek,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
(false, frame.pts())
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let pts_passed = peek - frame.pts();
|
|
|
|
|
|
|
|
|
|
if self.segment == Some(2) {
|
|
|
|
|
println!(
|
|
|
|
|
">>>>>> {} {} {} {:?} {}",
|
|
|
|
|
self.current_pts, self.last_pts, pts_passed, should_skip, peek
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!should_skip || self.current_pts == 0)
|
|
|
|
|
&& ((pts_passed + self.current_pts)
|
|
|
|
|
> (self.seconds_per_segment * self.input_stream.time_base().den() as u32)
|
|
|
|
|
as i64
|
|
|
|
|
|| (self.current_pts == 0 && Some(0) < self.segment))
|
|
|
|
|
{
|
|
|
|
|
self.format.write_packet_null()?;
|
|
|
|
|
self.write_segment()?;
|
|
|
|
|
self.segment += 1;
|
|
|
|
|
self.current_pts =
|
|
|
|
|
(pts_passed + self.current_pts) % self.input_stream.time_base().den() as i64;
|
|
|
|
|
} else {
|
|
|
|
@ -157,6 +244,27 @@ impl AudioTranscoder {
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn static_create_output(input: &Format<()>) -> Result<(Format<Buffer>, Stream), String> {
|
|
|
|
|
let audio_avio = AVIO::writer(Buffer::default());
|
|
|
|
|
let format = Format::output_avio(audio_avio, "mp4")?;
|
|
|
|
|
let output_stream = format.new_stream("aac")?;
|
|
|
|
|
format.set_flags(input.flags());
|
|
|
|
|
|
|
|
|
|
Ok((format, output_stream))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_output(&mut self, input: &Format<()>) -> Result<(), String> {
|
|
|
|
|
let (format, stream) = Self::static_create_output(input)?;
|
|
|
|
|
self.output_stream = stream;
|
|
|
|
|
self.format = format;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn open_output(&self) -> Result<(), String> {
|
|
|
|
|
Transcoder::static_open_output(&self.format, None)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct Transcoder {
|
|
|
|
@ -172,13 +280,17 @@ pub struct Transcoder {
|
|
|
|
|
frame_scaler: Scaler,
|
|
|
|
|
last_pts: i64,
|
|
|
|
|
current_pts: i64,
|
|
|
|
|
video_segment: u32,
|
|
|
|
|
highest_pts: i64,
|
|
|
|
|
video_segment: Option<u32>,
|
|
|
|
|
extra_data: Option<Vec<u8>>,
|
|
|
|
|
audio: Option<AudioTranscoder>,
|
|
|
|
|
segment_target: Option<u32>,
|
|
|
|
|
input_video_index: Option<i32>,
|
|
|
|
|
input_audio_index: Option<i32>,
|
|
|
|
|
seconds_per_segment: u32,
|
|
|
|
|
header_written: bool,
|
|
|
|
|
last_written_segment: Option<u32>,
|
|
|
|
|
latest_written_segment: Option<u32>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Transcoder {
|
|
|
|
@ -192,12 +304,16 @@ impl Transcoder {
|
|
|
|
|
let output_path = output_path.to_string();
|
|
|
|
|
|
|
|
|
|
let input_video = input
|
|
|
|
|
.stream(AVMEDIA_TYPE_VIDEO, video_index, None)
|
|
|
|
|
.stream(Some(AVMEDIA_TYPE_VIDEO), video_index, None)
|
|
|
|
|
.map_err(|err| format!("Failed to find video stream: {}", err))?
|
|
|
|
|
.ok_or("Failed to find video stream".to_string())?;
|
|
|
|
|
|
|
|
|
|
let input_audio = input
|
|
|
|
|
.stream(AVMEDIA_TYPE_AUDIO, audio_index, Some(input_video.index()))
|
|
|
|
|
.stream(
|
|
|
|
|
Some(AVMEDIA_TYPE_AUDIO),
|
|
|
|
|
audio_index,
|
|
|
|
|
Some(input_video.index()),
|
|
|
|
|
)
|
|
|
|
|
.map_err(|err| format!("Failed to find audio stream: {}", err))?;
|
|
|
|
|
if audio_index.is_some() && input_audio.is_none() {
|
|
|
|
|
return Err("Failed to find audio stream".to_string());
|
|
|
|
@ -205,23 +321,9 @@ impl Transcoder {
|
|
|
|
|
|
|
|
|
|
let input_frame_rate = input_video.avg_frame_rate(&input).unwrap().to_num();
|
|
|
|
|
|
|
|
|
|
let mut options = Dictionary::new();
|
|
|
|
|
options.set("movflags", "+dash+delay_moov+skip_trailer+frag_custom");
|
|
|
|
|
|
|
|
|
|
let video_avio = AVIO::writer(Buffer::default());
|
|
|
|
|
let video_output = Format::output_avio(video_avio, "mp4")?;
|
|
|
|
|
let output_video = video_output.new_stream("h264")?;
|
|
|
|
|
output_video.params().copy_from(&input_video.params())?;
|
|
|
|
|
output_video.set_sample_aspect_ratio(input_video.sample_aspect_ratio());
|
|
|
|
|
video_output.set_flags(input.flags());
|
|
|
|
|
|
|
|
|
|
let (video_output, output_video) = Self::create_streams(&input, &input_video)?;
|
|
|
|
|
let audio = if let Some(input_stream) = input_audio {
|
|
|
|
|
let audio_avio = AVIO::writer(Buffer::default());
|
|
|
|
|
let format = Format::output_avio(audio_avio, "mp4")?;
|
|
|
|
|
let output_stream = format.new_stream("aac")?;
|
|
|
|
|
|
|
|
|
|
format.set_flags(input.flags());
|
|
|
|
|
|
|
|
|
|
let (format, output_stream) = AudioTranscoder::static_create_output(&input)?;
|
|
|
|
|
let decoder = input_stream.decoder(None).ok_or(format!(
|
|
|
|
|
"Couldn't find encoder for input audio with codec: {:?}",
|
|
|
|
|
input_stream.codec()
|
|
|
|
@ -251,9 +353,7 @@ impl Transcoder {
|
|
|
|
|
encoder.configure(&output_stream)?;
|
|
|
|
|
|
|
|
|
|
let resampler = Resampler::from_coder(&decoder, &encoder);
|
|
|
|
|
let mut audio_options = Dictionary::new();
|
|
|
|
|
audio_options.copy_from(&mut options)?;
|
|
|
|
|
format.init_output(audio_options)?;
|
|
|
|
|
Transcoder::static_open_output(&format, None)?;
|
|
|
|
|
|
|
|
|
|
Some(AudioTranscoder {
|
|
|
|
|
frame_buffer: SortedFrameBuffer::new(),
|
|
|
|
@ -265,9 +365,13 @@ impl Transcoder {
|
|
|
|
|
output_path: output_path.to_string(),
|
|
|
|
|
current_pts: 0,
|
|
|
|
|
last_pts: 0,
|
|
|
|
|
segment: 0,
|
|
|
|
|
segment: None,
|
|
|
|
|
resampler,
|
|
|
|
|
seconds_per_segment: SECONDS_PER_SEGMENT,
|
|
|
|
|
segment_target: None,
|
|
|
|
|
header_written: false,
|
|
|
|
|
latest_written_segment: None,
|
|
|
|
|
last_written_segment: None,
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
@ -288,18 +392,13 @@ impl Transcoder {
|
|
|
|
|
encoder.set_height(video_decoder.height());
|
|
|
|
|
encoder.set_width(video_decoder.width());
|
|
|
|
|
encoder.set_time_base(input_frame_rate.invert());
|
|
|
|
|
output_video.set_time_base(encoder.time_base());
|
|
|
|
|
encoder.open()?;
|
|
|
|
|
encoder.configure(&output_video)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
let param = output_video.params();
|
|
|
|
|
param.set_height(video_decoder.height());
|
|
|
|
|
param.set_width(video_decoder.width());
|
|
|
|
|
param.set_pixel_format(AV_PIX_FMT_YUV420P);
|
|
|
|
|
video_output.init_output(options)?;
|
|
|
|
|
Self::static_open_output(&video_output, Some((&input_video, &video_decoder)))?;
|
|
|
|
|
|
|
|
|
|
let frame_scaler = Scaler::from_coder(&video_decoder, &video_encoder, SWS_BILINEAR);
|
|
|
|
|
let video_frame_buffer = SortedFrameBuffer::new();
|
|
|
|
@ -320,15 +419,121 @@ impl Transcoder {
|
|
|
|
|
video_frame_buffer,
|
|
|
|
|
last_pts: 0,
|
|
|
|
|
current_pts: 0,
|
|
|
|
|
video_segment: 0,
|
|
|
|
|
video_segment: None,
|
|
|
|
|
extra_data: None,
|
|
|
|
|
input_audio_index: None,
|
|
|
|
|
seconds_per_segment: SECONDS_PER_SEGMENT,
|
|
|
|
|
header_written: false,
|
|
|
|
|
latest_written_segment: None,
|
|
|
|
|
last_written_segment: None,
|
|
|
|
|
highest_pts: 0,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn seek(&self, segment: u32, stream: Option<&Stream>) -> Result<(), String> {
|
|
|
|
|
self.input.seek(segment * self.seconds_per_segment, stream)
|
|
|
|
|
fn create_output(&mut self) -> Result<(), String> {
|
|
|
|
|
let (video_output, output_video) = Self::create_streams(&self.input, &self.input_video)?;
|
|
|
|
|
self.video_output = video_output;
|
|
|
|
|
self.output_video = output_video;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_streams(
|
|
|
|
|
input: &Format<()>,
|
|
|
|
|
input_video: &Stream,
|
|
|
|
|
) -> Result<(Format<Buffer>, Stream), String> {
|
|
|
|
|
let video_avio = AVIO::writer(Buffer::default());
|
|
|
|
|
let video_output = Format::output_avio(video_avio, "mp4")?;
|
|
|
|
|
let output_video = video_output.new_stream("h264")?;
|
|
|
|
|
output_video.params().copy_from(&input_video.params())?;
|
|
|
|
|
output_video.set_sample_aspect_ratio(input_video.sample_aspect_ratio());
|
|
|
|
|
video_output.set_flags(input.flags());
|
|
|
|
|
|
|
|
|
|
Ok((video_output, output_video))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn open_output(&self) -> Result<(), String> {
|
|
|
|
|
Self::static_open_output(
|
|
|
|
|
&self.video_output,
|
|
|
|
|
Some((&self.output_video, &self.video_decoder)),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn static_open_output(
|
|
|
|
|
output: &Format<Buffer>,
|
|
|
|
|
video: Option<(&Stream, &Decoder)>,
|
|
|
|
|
) -> Result<(), String> {
|
|
|
|
|
let mut options = Dictionary::new();
|
|
|
|
|
options.set("movflags", "+dash+delay_moov+skip_trailer+frag_custom");
|
|
|
|
|
|
|
|
|
|
if let Some((video, video_decoder)) = video {
|
|
|
|
|
let param = video.params();
|
|
|
|
|
param.set_height(video_decoder.height());
|
|
|
|
|
param.set_width(video_decoder.width());
|
|
|
|
|
param.set_pixel_format(AV_PIX_FMT_YUV420P);
|
|
|
|
|
}
|
|
|
|
|
output.init_output(options)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn video_encoder(&self) -> &Encoder {
|
|
|
|
|
&self.video_encoder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn video_decoder(&self) -> &Decoder {
|
|
|
|
|
&self.video_decoder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn reset_output(&mut self) -> Result<(), String> {
|
|
|
|
|
let time_base = self.output_video.time_base();
|
|
|
|
|
self.create_output()?;
|
|
|
|
|
self.output_video
|
|
|
|
|
.set_time_base(self.video_encoder.time_base());
|
|
|
|
|
self.video_encoder.configure(&self.output_video)?;
|
|
|
|
|
self.open_output()?;
|
|
|
|
|
self.video_encoder.flush();
|
|
|
|
|
self.output_video.set_time_base(time_base);
|
|
|
|
|
self.video_segment = None;
|
|
|
|
|
self.header_written = false;
|
|
|
|
|
let mut options = Dictionary::new();
|
|
|
|
|
options.set("movflags", "+dash+delay_moov+skip_trailer+frag_custom");
|
|
|
|
|
self.video_output.write_header(Some(options)).unwrap();
|
|
|
|
|
|
|
|
|
|
if let Some(audio) = self.audio.as_mut() {
|
|
|
|
|
let time_base = audio.output_stream.time_base();
|
|
|
|
|
audio.create_output(&self.input)?;
|
|
|
|
|
audio.output_stream.set_time_base(audio.encoder.time_base());
|
|
|
|
|
audio.encoder.configure(&audio.output_stream)?;
|
|
|
|
|
audio.open_output()?;
|
|
|
|
|
audio.encoder.flush();
|
|
|
|
|
audio.output_stream.set_time_base(time_base);
|
|
|
|
|
audio.segment = None;
|
|
|
|
|
audio.header_written = false;
|
|
|
|
|
let mut options = Dictionary::new();
|
|
|
|
|
options.set("movflags", "+dash+delay_moov+skip_trailer+frag_custom");
|
|
|
|
|
audio.format.write_header(Some(options)).unwrap();
|
|
|
|
|
audio.last_pts = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn seek(&mut self, segment: u32, stream_index: Option<i32>) -> Result<(), String> {
|
|
|
|
|
self.input.seek(
|
|
|
|
|
segment * self.seconds_per_segment,
|
|
|
|
|
stream_index,
|
|
|
|
|
segment < self.last_written_segment().unwrap_or(0),
|
|
|
|
|
)?;
|
|
|
|
|
self.reset_output()?;
|
|
|
|
|
self.video_frame_buffer.clear();
|
|
|
|
|
self.segment_target = Some(segment);
|
|
|
|
|
self.video_segment = None;
|
|
|
|
|
if let Some(audio) = self.audio.as_mut() {
|
|
|
|
|
audio.frame_buffer.clear();
|
|
|
|
|
audio.segment_target = Some(segment);
|
|
|
|
|
audio.segment = None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn open(&mut self) -> Result<(), String> {
|
|
|
|
@ -336,37 +541,56 @@ impl Transcoder {
|
|
|
|
|
.recursive(true)
|
|
|
|
|
.create(self.output_path.to_string())
|
|
|
|
|
.map_err(|_| "Failed to create target directory".to_string())?;
|
|
|
|
|
|
|
|
|
|
self.video_output.write_header(None).unwrap();
|
|
|
|
|
let mut options = Dictionary::new();
|
|
|
|
|
options.set("movflags", "+dash+delay_moov+skip_trailer+frag_custom");
|
|
|
|
|
self.video_output.write_header(Some(options)).unwrap();
|
|
|
|
|
self.input_video_index = Some(self.input_video.index());
|
|
|
|
|
self.input_audio_index = self.audio.as_ref().map(|ia| ia.input_stream.index());
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn duration(&self) -> i64 {
|
|
|
|
|
self.input.duration()
|
|
|
|
|
pub fn last_frame_secs(&self) -> f64 {
|
|
|
|
|
self.last_pts as f64 / self.input_video.time_base().den() as f64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn latest_frame_secs(&self) -> f64 {
|
|
|
|
|
self.highest_pts as f64 / self.input_video.time_base().den() as f64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn duration_secs(&self) -> f64 {
|
|
|
|
|
self.input.duration() as f64 / AV_TIME_BASE as f64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn has_audio(&self) -> bool {
|
|
|
|
|
self.audio.is_some()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn transcode(&mut self) -> Result<bool, String> {
|
|
|
|
|
Ok(if let Some(packet) = self.input.next_packet() {
|
|
|
|
|
if let Some(packet) = self.input.next_packet() {
|
|
|
|
|
if self.input_video_index == Some(packet.stream()) {
|
|
|
|
|
self.video_process_packet(packet)?;
|
|
|
|
|
} else if self.input_audio_index == Some(packet.stream()) {
|
|
|
|
|
// Safe assumption
|
|
|
|
|
if let Some(ref mut audio) = &mut self.audio {
|
|
|
|
|
audio.process_packet(packet)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
})
|
|
|
|
|
return Ok(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.video_frame_buffer.close();
|
|
|
|
|
self.encode_video()?;
|
|
|
|
|
if let Some(audio) = self.audio.as_mut() {
|
|
|
|
|
audio.frame_buffer.close();
|
|
|
|
|
audio.encode()?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn avg_frame_rate(&self) -> Option<impl Rational> {
|
|
|
|
|
self.video_stream().avg_frame_rate(&self.input)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
@ -374,6 +598,11 @@ impl Transcoder {
|
|
|
|
|
&self.input_video
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn audio(&self) -> Option<&AudioTranscoder> {
|
|
|
|
|
self.audio.as_ref()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn audio_stream(&self) -> Option<&Stream> {
|
|
|
|
|
if let Some(ref audio) = self.audio {
|
|
|
|
@ -383,10 +612,36 @@ impl Transcoder {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn seeking(&self) -> bool {
|
|
|
|
|
self.segment_target.is_some()
|
|
|
|
|
|| self
|
|
|
|
|
.audio
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(false, |audio| audio.segment_target.is_some())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn last_written_audio_segment(&self) -> Option<u32> {
|
|
|
|
|
self.audio.as_ref().and_then(|x| x.last_written_segment)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn last_written_segment(&self) -> Option<u32> {
|
|
|
|
|
self.last_written_segment
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn latest_written_segment(&self) -> Option<u32> {
|
|
|
|
|
if let Some(audio) = self.audio.as_ref() {
|
|
|
|
|
min(self.latest_written_segment, audio.latest_written_segment)
|
|
|
|
|
} else {
|
|
|
|
|
self.latest_written_segment
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn segment(&self) -> u32 {
|
|
|
|
|
min(
|
|
|
|
|
self.video_segment,
|
|
|
|
|
self.audio.as_ref().map_or(u32::MAX, |audio| audio.segment),
|
|
|
|
|
self.video_segment.unwrap_or(0),
|
|
|
|
|
self.audio
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map_or(u32::MAX, |audio| audio.segment.unwrap_or(u32::MAX)),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -395,6 +650,12 @@ impl Transcoder {
|
|
|
|
|
self.video_output.write_trailer()?;
|
|
|
|
|
self.video_write_segment()?;
|
|
|
|
|
|
|
|
|
|
if let Some(audio) = self.audio.as_mut() {
|
|
|
|
|
audio.encode()?;
|
|
|
|
|
audio.format.write_trailer()?;
|
|
|
|
|
audio.write_segment()?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -404,12 +665,11 @@ impl Transcoder {
|
|
|
|
|
let segment: u32 = (self
|
|
|
|
|
.input_video
|
|
|
|
|
.time_base()
|
|
|
|
|
.to_num()
|
|
|
|
|
.mul(packet.pts() as i32)
|
|
|
|
|
.to_integer()
|
|
|
|
|
/ self.seconds_per_segment as i32) as u32;
|
|
|
|
|
if let Some(target) = self.segment_target {
|
|
|
|
|
if target - 1 < segment {
|
|
|
|
|
if (target as i64 - 1) > (segment as i64) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -441,8 +701,16 @@ impl Transcoder {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn video_write_segment(&mut self) -> Result<(), String> {
|
|
|
|
|
if self.video_segment == 0 {
|
|
|
|
|
if self.video_segment == Some(0) {
|
|
|
|
|
self.video_write_header()?;
|
|
|
|
|
self.header_written = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !self.header_written {
|
|
|
|
|
self.video_output.avio_inner_mut().unwrap().buffer();
|
|
|
|
|
self.start_segment(true);
|
|
|
|
|
self.video_output.write_packet_null()?;
|
|
|
|
|
self.header_written = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(avio) = self.video_output.avio.as_mut() {
|
|
|
|
@ -450,15 +718,48 @@ impl Transcoder {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let segment = self.video_output.avio_inner_mut().unwrap().buffer();
|
|
|
|
|
let mut segment = annex_b_to_avc(segment, 4)?;
|
|
|
|
|
|
|
|
|
|
File::create(format!(
|
|
|
|
|
"{}/video-segment-{:0>5}.m4s",
|
|
|
|
|
self.output_path, self.video_segment
|
|
|
|
|
))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.write_all(&mut segment)
|
|
|
|
|
.map_err(|_| format!("Failed to write video segment {}", self.video_segment))?;
|
|
|
|
|
if self.segment_target.unwrap_or(self.video_segment.unwrap()) == self.video_segment.unwrap()
|
|
|
|
|
{
|
|
|
|
|
self.segment_target = None;
|
|
|
|
|
let segment_res = annex_b_to_avc(segment, 4);
|
|
|
|
|
if let Err(_) = segment_res {
|
|
|
|
|
println!("oh");
|
|
|
|
|
}
|
|
|
|
|
let mut segment = segment_res?;
|
|
|
|
|
|
|
|
|
|
File::create(format!(
|
|
|
|
|
"{}/video-segment-{:0>5}.m4s",
|
|
|
|
|
self.output_path,
|
|
|
|
|
self.video_segment.unwrap()
|
|
|
|
|
))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.write_all(&mut segment)
|
|
|
|
|
.map_err(|_| {
|
|
|
|
|
format!(
|
|
|
|
|
"Failed to write video segment {}",
|
|
|
|
|
self.video_segment.unwrap()
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
self.last_written_segment = self.video_segment;
|
|
|
|
|
self.latest_written_segment = max(
|
|
|
|
|
self.latest_written_segment,
|
|
|
|
|
Some(self.video_segment.unwrap()),
|
|
|
|
|
);
|
|
|
|
|
println!(
|
|
|
|
|
"Wrote segment (video) {} ({} -> {} / coder: {} {})",
|
|
|
|
|
self.video_segment.unwrap(),
|
|
|
|
|
self.input_video.time_base(),
|
|
|
|
|
self.output_video.time_base(),
|
|
|
|
|
self.video_decoder.time_base(),
|
|
|
|
|
self.video_encoder.time_base()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
println!(
|
|
|
|
|
"Won't write segment (video) {} (searching for {})",
|
|
|
|
|
self.video_segment.unwrap(),
|
|
|
|
|
self.segment_target.unwrap()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
self.start_segment(false);
|
|
|
|
|
self.video_encoder.flush();
|
|
|
|
|
Ok(())
|
|
|
|
@ -466,21 +767,42 @@ impl Transcoder {
|
|
|
|
|
|
|
|
|
|
fn encode_video(&mut self) -> Result<(), String> {
|
|
|
|
|
while let Some(frame) = self.video_frame_buffer.pop_first() {
|
|
|
|
|
// if self.video_segment == None || self.last_pts > frame.pts() {
|
|
|
|
|
self.last_pts = frame.pts()
|
|
|
|
|
- (self
|
|
|
|
|
.avg_frame_rate()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.invert()
|
|
|
|
|
.to_num()
|
|
|
|
|
.mul(self.input_video.time_base().den() as i32))
|
|
|
|
|
.as_f64() as i64;
|
|
|
|
|
self.current_pts = frame.pts()
|
|
|
|
|
% (self.input_video.time_base().den() as u32 * self.seconds_per_segment) as i64;
|
|
|
|
|
self.video_segment = Some(
|
|
|
|
|
(frame.pts() as f64 / self.input_video.time_base().den() as f64).floor() as u32
|
|
|
|
|
/ self.seconds_per_segment,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if self.current_pts == 0 && Some(0) < self.video_segment {
|
|
|
|
|
self.video_segment = Some(self.video_segment.unwrap() - 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let pts_passed = frame.pts() - self.last_pts;
|
|
|
|
|
if (pts_passed + self.current_pts)
|
|
|
|
|
> (self.seconds_per_segment * self.input_frame_rate.den() as u32) as i64
|
|
|
|
|
> (self.seconds_per_segment * self.input_video.time_base().den() as u32) as i64
|
|
|
|
|
|| (self.current_pts == 0 && Some(0) < self.video_segment)
|
|
|
|
|
{
|
|
|
|
|
self.video_output.write_packet_null()?;
|
|
|
|
|
self.video_write_segment()?;
|
|
|
|
|
self.video_segment += 1;
|
|
|
|
|
self.current_pts =
|
|
|
|
|
(pts_passed + self.current_pts) % self.input_frame_rate.den() as i64;
|
|
|
|
|
(pts_passed + self.current_pts) % self.input_video.time_base().den() as i64;
|
|
|
|
|
frame.set_pict_type(AV_PICTURE_TYPE_I);
|
|
|
|
|
} else {
|
|
|
|
|
self.current_pts += pts_passed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.last_pts = frame.pts();
|
|
|
|
|
self.highest_pts = max(self.highest_pts, self.last_pts);
|
|
|
|
|
self.video_encoder.send_video_frame(&frame)?;
|
|
|
|
|
|
|
|
|
|
while let Some(new_packet) = self.video_encoder.read_packet()? {
|
|
|
|
@ -525,8 +847,16 @@ impl Transcoder {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn video_stream_output(&self) -> &Stream {
|
|
|
|
|
&self.output_video
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn audio_stream_output(&self) -> Option<&Stream> {
|
|
|
|
|
self.audio.as_ref().map(|audio| &audio.output_stream)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn start_segment(&self, force: bool) {
|
|
|
|
|
if self.video_segment == 0 && !force {
|
|
|
|
|
if self.video_segment == Some(0) && !force {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|