wip
This commit is contained in:
parent
fe171a56be
commit
2684e58bbf
15 changed files with 1872 additions and 116 deletions
1251
Cargo.lock
generated
1251
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()) }
|
||||
}
|
||||
}
|
||||
|
|
126
src/main.rs
126
src/main.rs
|
@ -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>,
|
||||
}
|
||||
|
|
256
src/transcoder_manager.rs
Normal file
256
src/transcoder_manager.rs
Normal file
|
@ -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…
Reference in a new issue