master
eater 4 years ago
parent fe171a56be
commit 2684e58bbf
Signed by: eater
GPG Key ID: AD2560A0F84F0759

1457
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -9,6 +9,11 @@ edition = "2018"
[dependencies]
num-rational = "0.3.0"
byteorder = "1.3.4"
tide = "0.11.0"
lazy_static = "1.4.0"
async-std = { version = "1.6.2", features = ["attributes"] }
uuid = { version = "0.4", features = ["serde", "v4"] }
http-types = "2.2.1"
[dependencies.ffmpeg-sys-next]
version = "4.3.0"

@ -1,10 +1,11 @@
use crate::av::as_ptr::{AsMutPtr, AsPtr};
use ffmpeg_sys_next::{av_malloc, avio_alloc_context, avio_flush, AVIOContext};
use ffmpeg_sys_next::{
av_free, av_malloc, avio_alloc_context, avio_context_free, avio_flush, AVIOContext,
};
use std::cmp::min;
use std::os::raw::c_void;
use std::vec::Vec;
#[derive(Clone)]
pub struct AVIO<T> {
ctx: *mut AVIOContext,
inner: Box<AVIOInner<T>>,
@ -50,7 +51,6 @@ unsafe extern "C" fn write_packet_with_inner(ctx: *mut c_void, buffer: *mut u8,
}
unsafe extern "C" fn seek_with_inner(ctx: *mut c_void, offset: i64, whence: i32) -> i64 {
println!("ok");
let mut avio: Box<AVIOInner<Box<dyn AVIOSeekable>>> = Box::from_raw(ctx.cast());
avio.data.seek(offset, whence);
std::mem::forget(avio);
@ -110,6 +110,15 @@ impl<T> AVIO<T> {
}
}
impl<T> Drop for AVIO<T> {
fn drop(&mut self) {
unsafe {
av_free((*self.ctx).buffer.cast());
avio_context_free(&mut self.ctx);
}
}
}
impl<T> AsMutPtr<AVIOContext> for AVIO<T> {
fn as_mut_ptr(&self) -> *mut AVIOContext {
self.ctx
@ -124,37 +133,43 @@ impl<T> AsPtr<AVIOContext> for AVIO<T> {
impl<T: AVIOReader + AVIOWriter + AVIOSeekable> AVIO<T> {
pub fn duplex_with_seek(item: T) -> AVIO<T> {
AVIO::new(4096, item, Some(T::read), Some(T::write), Some(T::seek))
AVIO::new(
4096 * 4096,
item,
Some(T::read),
Some(T::write),
Some(T::seek),
)
}
}
impl<T: AVIOReader + AVIOWriter> AVIO<T> {
pub fn duplex(item: T) -> AVIO<T> {
AVIO::new(4096, item, Some(T::read), Some(T::write), None)
AVIO::new(4096 * 4096, item, Some(T::read), Some(T::write), None)
}
}
impl<T: AVIOReader + AVIOSeekable> AVIO<T> {
pub fn reader_with_seek(item: T) -> AVIO<T> {
AVIO::new(4096, item, Some(T::read), None, Some(T::seek))
AVIO::new(4096 * 4096, item, Some(T::read), None, Some(T::seek))
}
}
impl<T: AVIOReader> AVIO<T> {
pub fn reader(item: T) -> AVIO<T> {
AVIO::new(4096, item, Some(T::read), None, None)
AVIO::new(4096 * 4096, item, Some(T::read), None, None)
}
}
impl<T: AVIOWriter + AVIOSeekable> AVIO<T> {
pub fn writer_with_seek(item: T) -> AVIO<T> {
AVIO::new(4096, item, None, Some(T::write), Some(T::seek))
AVIO::new(4096 * 4096, item, None, Some(T::write), Some(T::seek))
}
}
impl<T: AVIOWriter> AVIO<T> {
pub fn writer(item: T) -> AVIO<T> {
AVIO::new(4096, item, None, Some(T::write), None)
AVIO::new(4096 * 4096, item, None, Some(T::write), None)
}
}

@ -6,10 +6,10 @@ use crate::av::xcoder::{process_return, XCoder};
use crate::av::{decoder_from_name, verify_response};
use crate::av_err2str;
use ffmpeg_sys_next::{
avcodec_parameters_to_context, avcodec_receive_frame, avcodec_send_packet, AVCodecContext,
avcodec_free_context, avcodec_parameters_to_context, avcodec_receive_frame,
avcodec_send_packet, AVCodecContext,
};
#[derive(Copy, Clone)]
pub struct Decoder(pub *mut AVCodecContext);
impl Decoder {
@ -56,3 +56,9 @@ impl XCoder for Decoder {
Ok(())
}
}
impl Drop for Decoder {
fn drop(&mut self) {
unsafe { avcodec_free_context(&mut self.as_mut_ptr()) }
}
}

@ -1,14 +1,13 @@
use crate::av::as_ptr::AsMutPtr;
use crate::av::frame::Frame;
use crate::av::packet::Packet;
use crate::av::scaler::Scaler;
use crate::av::stream::Stream;
use crate::av::xcoder::{process_return, XCoder};
use crate::av::{encoder_from_name, verify_response};
use crate::av_err2str;
use ffmpeg_sys_next::{
avcodec_parameters_from_context, avcodec_receive_packet, avcodec_send_frame, sws_freeContext,
AVCodecContext,
avcodec_free_context, avcodec_parameters_from_context, avcodec_receive_packet,
avcodec_send_frame, AVCodecContext,
};
pub struct Encoder(pub *mut AVCodecContext);
@ -88,8 +87,8 @@ impl XCoder for Encoder {
}
}
impl Drop for Scaler {
impl Drop for Encoder {
fn drop(&mut self) {
unsafe { sws_freeContext(self.as_mut_ptr()) }
unsafe { avcodec_free_context(&mut self.as_mut_ptr()) }
}
}

@ -4,19 +4,19 @@ use crate::av::dictionary::Dictionary;
use crate::av::options::Options;
use crate::av::packet::Packet;
use crate::av::stream::Stream;
use crate::av::{decoder_codec_from_name, verify_response};
use crate::av::{decoder_codec_from_name, verify_response, Rational};
use ffmpeg_sys_next::{
av_interleaved_write_frame, av_read_frame, av_write_frame, av_write_trailer,
avformat_alloc_context, avformat_alloc_output_context2, avformat_find_stream_info,
avformat_init_output, avformat_new_stream, avformat_open_input, avformat_write_header,
avio_open2, AVFormatContext, AVMediaType, AVIO_FLAG_WRITE,
av_find_best_stream, av_interleaved_write_frame, av_read_frame, av_seek_frame, av_write_frame,
av_write_trailer, avformat_alloc_context, avformat_alloc_output_context2,
avformat_find_stream_info, avformat_free_context, avformat_init_output, avformat_new_stream,
avformat_open_input, avformat_write_header, avio_context_free, avio_open2, AVFormatContext,
AVMediaType, AVIO_FLAG_WRITE, AVSEEK_FLAG_BACKWARD, AV_TIME_BASE,
};
use std::ffi::CString;
use std::fmt::{Debug, Error, Formatter};
use std::os::raw::{c_int, c_void};
use std::ptr::{null, null_mut};
#[derive(Clone)]
pub struct Format<T> {
pub ctx: *mut AVFormatContext,
pub avio: Option<AVIO<T>>,
@ -39,16 +39,45 @@ impl<T> Format<T> {
}
impl<T> Format<T> {
pub fn stream(&self, stream_type: AVMediaType, index: Option<i32>) -> Option<Stream> {
pub fn seek(&self, seconds: u32, stream: Option<&Stream>) -> Result<(), String> {
let seconds: i32 = seconds as i32;
let time_base = stream.map(|s| s.time_base().den()).unwrap_or(AV_TIME_BASE);
verify_response("Failed to seek", unsafe {
av_seek_frame(
self.ctx,
stream.map(|s| s.index()).unwrap_or(-1),
(seconds * time_base) as i64,
AVSEEK_FLAG_BACKWARD,
)
})?;
Ok(())
}
pub fn stream(
&self,
stream_type: AVMediaType,
index: Option<i32>,
related: Option<i32>,
) -> Result<Option<Stream>, String> {
unsafe {
let ctx = self.ctx;
let mut stream = (*ctx).streams;
let index = if let Some(index) = index {
index
} else {
verify_response(
"Failed finding best stream",
av_find_best_stream(ctx, stream_type, -1, related.unwrap_or(-1), null_mut(), 0),
)?
};
for _ in 0..(*ctx).nb_streams {
let curr_stream = *stream;
if (*(*curr_stream).codecpar).codec_type == stream_type {
if index.is_none() || Some((*curr_stream).index) == index {
return Some(Stream(curr_stream));
if (*curr_stream).index == index {
return Ok(Some(Stream(curr_stream)));
}
}
@ -56,7 +85,7 @@ impl<T> Format<T> {
}
}
None
Ok(None)
}
pub fn as_mut_ptr(&self) -> *mut AVFormatContext {
@ -72,6 +101,10 @@ impl<T> Format<T> {
};
}
pub fn duration(&self) -> i64 {
unsafe { (*self.ctx).duration }
}
pub fn write_header(&self, options: Option<Dictionary>) -> Result<(), String> {
verify_response("failed to write header to output", unsafe {
avformat_write_header(
@ -280,3 +313,16 @@ impl<T> AsMutVoidPtr for Format<T> {
}
impl<T> Options for Format<T> {}
impl<T> Drop for Format<T> {
fn drop(&mut self) {
unsafe {
let mut pb = (*self.ctx).pb;
avformat_free_context(self.ctx);
if self.avio.is_none() {
avio_context_free(&mut pb);
}
}
}
}

@ -1,7 +1,7 @@
use crate::av::as_ptr::{AsMutPtr, AsPtr};
use crate::av::verify_response;
use ffmpeg_sys_next::{
av_frame_alloc, av_frame_get_buffer, av_frame_unref, AVFrame, AVPictureType, AVPixelFormat,
av_frame_alloc, av_frame_free, av_frame_get_buffer, AVFrame, AVPictureType, AVPixelFormat,
AVSampleFormat,
};
@ -25,12 +25,12 @@ impl Frame {
}
#[inline]
fn own(&mut self) {
pub fn own(&mut self) {
self.owned = true
}
#[inline]
fn disown(&mut self) {
pub fn disown(&mut self) {
self.owned = false
}
@ -177,6 +177,6 @@ impl Drop for Frame {
if !self.owned {
return;
}
unsafe { av_frame_unref(self.ptr) }
unsafe { av_frame_free(&mut self.ptr) }
}
}

@ -1,9 +1,10 @@
use crate::av_err2str;
use ffmpeg_sys_next::AVCodecID::AV_CODEC_ID_HEVC;
use ffmpeg_sys_next::{
av_codec_is_decoder, av_codec_is_encoder, av_codec_next, av_inv_q, av_register_all,
avcodec_alloc_context3, avcodec_find_decoder_by_name, avcodec_find_encoder_by_name, AVCodec,
AVCodecContext, AVCodecID, AVRational, AV_CODEC_CAP_HARDWARE,
av_codec_is_decoder, av_codec_is_encoder, av_codec_next, av_inv_q, av_log_set_level,
av_register_all, avcodec_alloc_context3, avcodec_find_decoder_by_name,
avcodec_find_encoder_by_name, AVCodec, AVCodecContext, AVCodecID, AVRational,
AV_CODEC_CAP_HARDWARE,
};
use num_rational::Ratio;
use std::any::type_name;
@ -213,6 +214,7 @@ fn remove_if_exists(list: &mut Vec<FfmpegCodec>, needle: &str, to_remove: Vec<&s
pub fn init() {
unsafe {
av_register_all();
av_log_set_level(-8);
}
}

@ -1,7 +1,7 @@
use crate::av::Rational;
use ffmpeg_sys_next::AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA;
use ffmpeg_sys_next::{
av_packet_alloc, av_packet_get_side_data, av_packet_rescale_ts, av_packet_unref, AVPacket,
av_packet_alloc, av_packet_free, av_packet_get_side_data, av_packet_rescale_ts, AVPacket,
};
pub struct Packet(pub *mut AVPacket);
@ -98,7 +98,7 @@ impl Packet {
impl Drop for Packet {
fn drop(&mut self) {
unsafe {
av_packet_unref(self.0);
av_packet_free(&mut self.0);
}
}
}

@ -5,8 +5,8 @@ use crate::av::frame::Frame;
use crate::av::verify_response;
use crate::av::xcoder::XCoder;
use ffmpeg_sys_next::{
swr_alloc, swr_alloc_set_opts, swr_convert_frame, swr_get_delay, AVFrame, AVSampleFormat,
SwrContext,
swr_alloc, swr_alloc_set_opts, swr_convert_frame, swr_drop_output, swr_free, swr_get_delay,
AVSampleFormat, SwrContext,
};
use std::ptr::{null, null_mut};
@ -96,3 +96,12 @@ impl Resampler {
}
}
}
impl Drop for Resampler {
fn drop(&mut self) {
unsafe {
swr_drop_output(self.ptr, i32::MAX);
swr_free(&mut self.ptr)
}
}
}

@ -4,7 +4,7 @@ use crate::av::encoder::Encoder;
use crate::av::frame::Frame;
use crate::av::verify_response;
use crate::av::xcoder::XCoder;
use ffmpeg_sys_next::{sws_getContext, sws_scale, AVPixelFormat, SwsContext};
use ffmpeg_sys_next::{sws_freeContext, sws_getContext, sws_scale, AVPixelFormat, SwsContext};
use std::os::raw::c_int;
use std::ptr::null_mut;
@ -150,3 +150,9 @@ impl AsMutPtr<SwsContext> for Scaler {
self.ctx
}
}
impl Drop for Scaler {
fn drop(&mut self) {
unsafe { sws_freeContext(self.as_mut_ptr()) }
}
}

@ -1,13 +1,23 @@
#![allow(dead_code)]
use crate::av::init;
use crate::transcoder::Transcoder;
use crate::transcoder_manager::{TranscoderInstance, TranscoderManager};
use async_std::fs::File;
use async_std::sync::{Arc, RwLock};
use ffmpeg_sys_next::{av_make_error_string, av_malloc, av_version_info};
use http_types::Error as HttpError;
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::option::Option::Some;
use std::os::raw::{c_char, c_int};
use std::str::FromStr;
use tide::{Request, StatusCode};
use uuid::Uuid;
pub mod av;
pub mod transcoder;
pub mod transcoder_manager;
pub mod utils;
// const INPUT_PATH: &str = "/tank/ephemeral/anime/series/Witch Hunter Robin - 2ndfire/[2ndfire]Witch_Hunter_Robin_-_01[91DCE49A].mkv";
@ -22,14 +32,114 @@ fn av_err2str(error_code: c_int) -> String {
}
}
fn main() {
println!("> transotf: ffmpeg {}", unsafe {
CStr::from_ptr(av_version_info()).to_str().unwrap()
});
lazy_static! {
static ref HOME_PAGE: String = {
format!(
"{}: {}\nffmpeg: {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
unsafe { CStr::from_ptr(av_version_info()).to_str().unwrap_or("n/a") },
)
};
}
#[derive(Default)]
struct State {
manager: RwLock<TranscoderManager>,
}
async fn create_transcode(req: Request<State>) -> Result<String, HttpError> {
let params = req.query::<HashMap<String, String>>()?;
let target = params.get(&"target".to_string());
let target = if let Some(target) = target {
target
} else {
return Err(HttpError::from_str(
StatusCode::BadRequest,
"no target given",
));
};
{
File::open(target).await.map_err(|err| {
HttpError::from_str(
StatusCode::BadRequest,
format!("Failed to open file: {}", err),
)
})?;
}
let id = req
.state()
.manager
.write()
.await
.start(target.clone(), None, None)
.map_err(|err| {
HttpError::from_str(
StatusCode::InternalServerError,
format!("Failed to start transcoder: {}", err),
)
})?;
Ok(format!("{}", id))
}
async fn get_init(req: Request<State>) -> Result<String, HttpError> {
let _manager = req.state().manager.read().await;
// manager.get()
Err(HttpError::from_str(StatusCode::NotFound, ":3"))
}
async fn get_instance(req: &Request<State>) -> Result<Arc<TranscoderInstance>, HttpError> {
let id = req.param::<String>("id")?;
let id = Uuid::from_str(&id)?;
let manager = req.state().manager.read().await;
if let Some(instance) = manager.get(id) {
Ok(instance)
} else {
Err(HttpError::from_str(
StatusCode::NotFound,
format!("Can't find transcoder with id {}", id),
))
}
}
async fn get_manifest(req: Request<State>) -> Result<String, HttpError> {
let transcoder_instance = get_instance(&req).await?;
let (duration, segments, init_written) = transcoder_instance
.state(|item| {
(
item.duration(),
item.segments().clone(),
item.init_written(),
)
.clone()
})
.await;
Ok(format!("{} {:?} {}", duration, segments, init_written))
}
// async fn get_status(req: Request<State>) -> Result<String, HttpError> {
// let id = req.param::<String>("id")?;
// let id = Uuid::from_str(&id)?;
// let manager = req.state().manager.read().await;
// }
#[async_std::main]
async fn main() -> std::io::Result<()> {
println!("{}", HOME_PAGE.clone());
init();
let mut transcoder = Transcoder::create(INPUT_PATH, OUTPUT_PATH, None, None)
.expect("Failed to create transcoder");
transcoder.transcode().expect("Failed to transcode");
let mut app = tide::with_state(State::default());
app.at("/").get(|_| async { Ok(HOME_PAGE.clone()) });
app.at("/transcode").get(create_transcode);
// app.at("/session/:id").get(get_status);
app.at("/session/:id/manifest").get(get_manifest);
// app.at("/session/:id/{type}/init.mp4");
// app.at("/session/:id/{type}/{nr}.m4s");
app.listen("0:8000").await
}

@ -14,30 +14,14 @@ use crate::utils::SortedFrameBuffer;
use ffmpeg_sys_next::AVMediaType::{AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO};
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, SWS_BILINEAR};
use ffmpeg_sys_next::{avio_wb32, AVIOContext, AVPixelFormat, AV_TIME_BASE, SWS_BILINEAR};
use num_rational::Ratio;
use std::cmp::min;
use std::fs::{DirBuilder, File};
use std::io::Write;
use std::ops::Mul;
use std::option::Option::Some;
pub struct Transcoder {
input: Format<()>,
input_video: Stream,
input_frame_rate: Ratio<i32>,
video_output: Format<Buffer>,
output_path: String,
output_video: Stream,
video_frame_buffer: SortedFrameBuffer,
video_encoder: Encoder,
video_decoder: Decoder,
frame_scaler: Scaler,
last_pts: i64,
current_pts: i64,
video_segment: u32,
extra_data: Option<Vec<u8>>,
audio: Option<AudioTranscoder>,
}
pub struct AudioTranscoder {
encoder: Encoder,
decoder: Decoder,
@ -50,8 +34,11 @@ pub struct AudioTranscoder {
segment: u32,
last_pts: i64,
current_pts: i64,
seconds_per_segment: u32,
}
const SECONDS_PER_SEGMENT: u32 = 5;
impl AudioTranscoder {
fn process_packet(&mut self, packet: Packet) -> Result<(), String> {
self.decoder
@ -75,6 +62,11 @@ impl AudioTranscoder {
Ok(())
}
#[inline]
fn input_stream(&self) -> &Stream {
&self.input_stream
}
fn start_segment(&self, force: bool) {
if self.segment == 0 && !force {
return;
@ -122,6 +114,7 @@ impl AudioTranscoder {
.unwrap()
.write_all(&mut segment)
.map_err(|_| format!("Failed to write audio segment {}", self.segment))?;
self.start_segment(false);
Ok(())
@ -134,12 +127,10 @@ impl AudioTranscoder {
println!("WARN: new frame out of order");
}
if (pts_passed + self.current_pts) > (5 * self.input_stream.time_base().den()) as i64 {
if (pts_passed + self.current_pts)
> (self.seconds_per_segment as i32 * self.input_stream.time_base().den()) as i64
{
self.format.write_packet_null()?;
println!(
"Next audio segment (current: {}, pts: {}/{})!",
self.segment, self.last_pts, self.current_pts,
);
self.write_segment()?;
self.segment += 1;
self.current_pts =
@ -168,6 +159,28 @@ impl AudioTranscoder {
}
}
pub struct Transcoder {
input: Format<()>,
input_video: Stream,
input_frame_rate: Ratio<i32>,
video_output: Format<Buffer>,
output_path: String,
output_video: Stream,
video_frame_buffer: SortedFrameBuffer,
video_encoder: Encoder,
video_decoder: Decoder,
frame_scaler: Scaler,
last_pts: i64,
current_pts: i64,
video_segment: 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,
}
impl Transcoder {
pub fn create(
input_path: &str,
@ -179,10 +192,13 @@ impl Transcoder {
let output_path = output_path.to_string();
let input_video = input
.stream(AVMEDIA_TYPE_VIDEO, video_index)
.stream(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);
let input_audio = input
.stream(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());
}
@ -233,30 +249,11 @@ impl Transcoder {
encoder.set_frame_size(decoder.frame_size() * 4);
encoder.open()?;
encoder.configure(&output_stream)?;
println!(
"audio input stream: {}/{}",
input_stream.time_base().num(),
input_stream.time_base().den()
);
println!(
"audio decoder[{}] ({:?} {}hz | {}/{}) -> audio encoder[{}] ({:?} {}hz | {}/{})",
decoder.name(),
decoder.codec(),
decoder.sample_rate(),
decoder.time_base().num(),
decoder.time_base().den(),
encoder.name(),
encoder.codec(),
encoder.sample_rate(),
encoder.time_base().num(),
encoder.time_base().den(),
);
let resampler = Resampler::from_coder(&decoder, &encoder);
let mut audio_options = Dictionary::new();
audio_options.copy_from(&mut options);
format.init_output(audio_options);
audio_options.copy_from(&mut options)?;
format.init_output(audio_options)?;
Some(AudioTranscoder {
frame_buffer: SortedFrameBuffer::new(),
@ -270,6 +267,7 @@ impl Transcoder {
last_pts: 0,
segment: 0,
resampler,
seconds_per_segment: SECONDS_PER_SEGMENT,
})
} else {
None
@ -297,12 +295,6 @@ impl Transcoder {
Ok(())
})?;
println!(
"video decoder[{}] -> video encoder[{}]",
video_decoder.name(),
video_encoder.name()
);
let param = output_video.params();
param.set_height(video_decoder.height());
param.set_width(video_decoder.width());
@ -319,6 +311,8 @@ impl Transcoder {
video_output,
output_video,
audio,
segment_target: None,
input_video_index: None,
output_path,
video_encoder,
video_decoder,
@ -328,30 +322,75 @@ impl Transcoder {
current_pts: 0,
video_segment: 0,
extra_data: None,
input_audio_index: None,
seconds_per_segment: SECONDS_PER_SEGMENT,
})
}
pub fn transcode(&mut self) -> Result<(), String> {
pub fn seek(&self, segment: u32, stream: Option<&Stream>) -> Result<(), String> {
self.input.seek(segment * self.seconds_per_segment, stream)
}
pub fn open(&mut self) -> Result<(), String> {
DirBuilder::new()
.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 input_video_index = self.input_video.index();
let input_audio_index = self.audio.as_ref().map(|ia| ia.input_stream.index());
self.input_video_index = Some(self.input_video.index());
self.input_audio_index = self.audio.as_ref().map(|ia| ia.input_stream.index());
while let Some(packet) = self.input.next_packet() {
if input_video_index == packet.stream() {
Ok(())
}
pub fn duration(&self) -> i64 {
self.input.duration()
}
pub fn duration_secs(&self) -> f64 {
self.input.duration() as f64 / AV_TIME_BASE as f64
}
pub fn transcode(&mut self) -> Result<bool, String> {
Ok(if let Some(packet) = self.input.next_packet() {
if self.input_video_index == Some(packet.stream()) {
self.video_process_packet(packet)?;
} else if input_audio_index == Some(packet.stream()) {
} 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
})
}
#[inline]
pub fn video_stream(&self) -> &Stream {
&self.input_video
}
#[inline]
pub fn audio_stream(&self) -> Option<&Stream> {
if let Some(ref audio) = self.audio {
Some(audio.input_stream())
} else {
None
}
}
pub fn segment(&self) -> u32 {
min(
self.video_segment,
self.audio.as_ref().map_or(u32::MAX, |audio| audio.segment),
)
}
pub fn finish(&mut self) -> Result<(), String> {
self.encode_video()?;
self.video_output.write_trailer()?;
self.video_write_segment()?;
@ -362,7 +401,21 @@ impl Transcoder {
fn video_process_packet(&mut self, packet: Packet) -> Result<(), String> {
self.video_decoder.send_packet(&packet)?;
while let Some(frame) = self.video_decoder.read_frame()? {
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 {
continue;
}
}
let pts = frame.pts();
let frame = self
.frame_scaler
.scale(frame)
@ -407,21 +460,17 @@ impl Transcoder {
.write_all(&mut segment)
.map_err(|_| format!("Failed to write video segment {}", self.video_segment))?;
self.start_segment(false);
self.video_encoder.flush();
Ok(())
}
fn encode_video(&mut self) -> Result<(), String> {
while let Some(frame) = self.video_frame_buffer.pop_first() {
let pts_passed = frame.pts() - self.last_pts;
if (pts_passed + self.current_pts) > (5 * self.input_frame_rate.den()) as i64 {
if (pts_passed + self.current_pts)
> (self.seconds_per_segment * self.input_frame_rate.den() as u32) as i64
{
self.video_output.write_packet_null()?;
println!(
"Next video segment (current: {}, pts: {}/{})!",
self.video_segment, self.last_pts, self.current_pts,
);
self.video_write_segment()?;
self.video_segment += 1;
self.current_pts =
@ -492,9 +541,11 @@ impl Transcoder {
ffio_wfourcc(pb, b"msix");
}
}
pub fn stop(self) {}
}
#[derive(Clone, Default, Debug)]
#[derive(Default, Debug)]
struct Buffer {
buffer: Vec<u8>,
}

@ -0,0 +1,256 @@
use crate::transcoder::Transcoder;
use async_std::sync::{Arc, Receiver, RecvError, RwLock, RwLockReadGuard, Sender, TryRecvError};
use async_std::task;
use std::collections::{HashMap, HashSet};
use std::option::Option::Some;
use std::thread::Builder;
use std::thread::JoinHandle;
use uuid::Uuid;
#[derive(Debug)]
pub struct TranscoderInstance {
id: Uuid,
channel: Channel<Message>,
state: RwLock<TranscoderInstanceState>,
command: TranscoderCommand,
}
impl TranscoderInstance {
pub async fn state<T>(&self, block: fn(RwLockReadGuard<TranscoderInstanceState>) -> T) -> T {
block(self.state.read().await)
}
}
#[derive(Debug, Default)]
pub struct TranscoderInstanceState {
thread: Option<JoinHandle<()>>,
duration_secs: f64,
segments: HashSet<u32>,
init_written: bool,
}
impl TranscoderInstanceState {
#[inline]
pub fn duration(&self) -> f64 {
self.duration_secs
}
#[inline]
pub fn segments(&self) -> &HashSet<u32> {
&self.segments
}
#[inline]
pub fn init_written(&self) -> bool {
self.init_written
}
}
#[derive(Debug, Default)]
pub struct TranscoderManager {
instances: HashMap<Uuid, Arc<TranscoderInstance>>,
}
#[derive(Debug, Clone)]
pub struct Channel<T> {
receiver: Receiver<T>,
sender: Sender<T>,
}
impl<T> Channel<T> {
async fn send(&self, msg: T) {
self.sender.send(msg).await
}
fn send_blocking(&self, msg: T) {
task::block_on(self.sender.send(msg))
}
async fn recv(&self) -> Result<T, RecvError> {
self.receiver.recv().await
}
fn recv_blocking(&self) -> Result<T, RecvError> {
task::block_on(self.receiver.recv())
}
fn try_recv(&self) -> Result<T, TryRecvError> {
self.receiver.try_recv()
}
}
fn new_channel() -> (Channel<Message>, Channel<Message>) {
let (sender_a, receiver_a) = async_std::sync::channel(25);
let (sender_b, receiver_b) = async_std::sync::channel(25);
(
Channel {
sender: sender_a,
receiver: receiver_b,
},
Channel {
sender: sender_b,
receiver: receiver_a,
},
)
}
#[derive(Debug, Clone)]
pub struct TranscoderCommand {
input: String,
output: String,
audio: Option<i32>,
video: Option<i32>,
}
#[derive(Debug)]
pub enum Message {
Info { duration_secs: f64 },
SegmentReady(u32),
Seek(u32),
Failure(String),
Stop,
Finished,
}
fn transcoder_thread_main(command: TranscoderCommand, channel: Channel<Message>) {
if let Err(err) = transcoder_thread(command, &channel) {
channel.send_blocking(Message::Failure(err));
return;
}
}
fn transcoder_thread(command: TranscoderCommand, channel: &Channel<Message>) -> Result<(), String> {
let transcoder = Transcoder::create(
&command.input,
&command.output,
command.video,
command.audio,
);
let mut transcoder = match transcoder {
Ok(t) => t,
Err(e) => {
channel.send_blocking(Message::Failure(e));
return Ok(());
}
};
transcoder.open()?;
channel.send_blocking(Message::Info {
duration_secs: transcoder.duration_secs(),
});
let mut last_segment = transcoder.segment();
while transcoder.transcode()? {
while let Ok(msg) = channel.try_recv() {
match msg {
Message::Seek(segment) => {
transcoder.seek(segment, Some(transcoder.video_stream()))?;
}
Message::Stop => {
transcoder.stop();
return Ok(());
}
_ => {}
};
}
if last_segment < transcoder.segment() {
channel.send_blocking(Message::SegmentReady(last_segment));
last_segment = transcoder.segment();
}
}
transcoder.finish()?;
channel.send_blocking(Message::SegmentReady(transcoder.segment()));
transcoder.stop();
channel.send_blocking(Message::Finished);
Ok(())
}
impl TranscoderManager {
pub fn start(
&mut self,
input: String,
audio: Option<i32>,
video: Option<i32>,
) -> Result<Uuid, std::io::Error> {
let uuid = Uuid::new_v4();
let command = TranscoderCommand {
input: input.clone(),
output: format!("/tmp/transotf/{}/", uuid),
audio,
video,
};
let (thread_channel, own_channel) = new_channel();
let thread = {
let command = command.clone();
Builder::new()
.name(format!("t#{}", uuid))
.spawn(|| transcoder_thread_main(command, thread_channel))?
};
let instance = TranscoderInstance {
id: uuid,
command,
state: RwLock::new(TranscoderInstanceState {
thread: Some(thread),
init_written: false,
segments: HashSet::new(),
duration_secs: 0.0,
}),
channel: own_channel,
};
let instance = Arc::new(instance);
self.instances.insert(uuid, instance.clone());
task::spawn(async move {
loop {
let fut = instance.channel.recv();
if let Ok(x) = fut.await {
match x {
Message::Finished => {
let mut state = instance.state.write().await;
if let Some(join_handle) = std::mem::replace(&mut state.thread, None) {
join_handle.join().unwrap();
}
break;
}
Message::Info { duration_secs } => {
let mut state = instance.state.write().await;
state.duration_secs = duration_secs
}
Message::SegmentReady(segment) => {
let mut state = instance.state.write().await;
if segment == 0 {
state.init_written = true
}
state.segments.insert(segment);
}
_ => {}
}
println!("> {:?}", x);
} else {
return;
}
}
});
Ok(uuid)
}
pub fn get(&self, id: Uuid) -> Option<Arc<TranscoderInstance>> {
self.instances.get(&id).cloned()
}
}

@ -7,14 +7,14 @@
<Period start="PT0.0S">
<BaseURL>data/</BaseURL>
<AdaptationSet contentType="video">
<Representation mimeType="video/mp4" codecs="avc1.640028" id="test" bandwidth="1654197" width="1920" height="1080" frameRate="24000/1001">
<Representation mimeType="video/mp4" codecs="avc1.640028" id="video" bandwidth="1654197" width="1920" height="1080" frameRate="24000/1001">
<SegmentTemplate media="video-segment-$Number%05d$.m4s"
initialization="video-init.mp4" duration="5005" timescale="1001"
startNumber="0"/>
</Representation>
</AdaptationSet>
<AdaptationSet contentType="audio">
<Representation mimeType="audio/mp4" codecs="mp4a.40.2" >
<Representation id="audio" mimeType="audio/mp4" codecs="mp4a.40.2" >
<SegmentTemplate media="audio-segment-$Number%05d$.m4s"
initialization="audio-init.mp4" duration="5000" timescale="1000"
startNumber="0"/>

Loading…
Cancel
Save