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]
|
[dependencies]
|
||||||
num-rational = "0.3.0"
|
num-rational = "0.3.0"
|
||||||
byteorder = "1.3.4"
|
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]
|
[dependencies.ffmpeg-sys-next]
|
||||||
version = "4.3.0"
|
version = "4.3.0"
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::av::as_ptr::{AsMutPtr, AsPtr};
|
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::cmp::min;
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AVIO<T> {
|
pub struct AVIO<T> {
|
||||||
ctx: *mut AVIOContext,
|
ctx: *mut AVIOContext,
|
||||||
inner: Box<AVIOInner<T>>,
|
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 {
|
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());
|
let mut avio: Box<AVIOInner<Box<dyn AVIOSeekable>>> = Box::from_raw(ctx.cast());
|
||||||
avio.data.seek(offset, whence);
|
avio.data.seek(offset, whence);
|
||||||
std::mem::forget(avio);
|
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> {
|
impl<T> AsMutPtr<AVIOContext> for AVIO<T> {
|
||||||
fn as_mut_ptr(&self) -> *mut AVIOContext {
|
fn as_mut_ptr(&self) -> *mut AVIOContext {
|
||||||
self.ctx
|
self.ctx
|
||||||
|
@ -124,37 +133,43 @@ impl<T> AsPtr<AVIOContext> for AVIO<T> {
|
||||||
|
|
||||||
impl<T: AVIOReader + AVIOWriter + AVIOSeekable> AVIO<T> {
|
impl<T: AVIOReader + AVIOWriter + AVIOSeekable> AVIO<T> {
|
||||||
pub fn duplex_with_seek(item: T) -> 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> {
|
impl<T: AVIOReader + AVIOWriter> AVIO<T> {
|
||||||
pub fn duplex(item: T) -> 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> {
|
impl<T: AVIOReader + AVIOSeekable> AVIO<T> {
|
||||||
pub fn reader_with_seek(item: T) -> 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> {
|
impl<T: AVIOReader> AVIO<T> {
|
||||||
pub fn reader(item: T) -> 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> {
|
impl<T: AVIOWriter + AVIOSeekable> AVIO<T> {
|
||||||
pub fn writer_with_seek(item: T) -> 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> {
|
impl<T: AVIOWriter> AVIO<T> {
|
||||||
pub fn writer(item: T) -> 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::{decoder_from_name, verify_response};
|
||||||
use crate::av_err2str;
|
use crate::av_err2str;
|
||||||
use ffmpeg_sys_next::{
|
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);
|
pub struct Decoder(pub *mut AVCodecContext);
|
||||||
|
|
||||||
impl Decoder {
|
impl Decoder {
|
||||||
|
@ -56,3 +56,9 @@ impl XCoder for Decoder {
|
||||||
Ok(())
|
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::as_ptr::AsMutPtr;
|
||||||
use crate::av::frame::Frame;
|
use crate::av::frame::Frame;
|
||||||
use crate::av::packet::Packet;
|
use crate::av::packet::Packet;
|
||||||
use crate::av::scaler::Scaler;
|
|
||||||
use crate::av::stream::Stream;
|
use crate::av::stream::Stream;
|
||||||
use crate::av::xcoder::{process_return, XCoder};
|
use crate::av::xcoder::{process_return, XCoder};
|
||||||
use crate::av::{encoder_from_name, verify_response};
|
use crate::av::{encoder_from_name, verify_response};
|
||||||
use crate::av_err2str;
|
use crate::av_err2str;
|
||||||
use ffmpeg_sys_next::{
|
use ffmpeg_sys_next::{
|
||||||
avcodec_parameters_from_context, avcodec_receive_packet, avcodec_send_frame, sws_freeContext,
|
avcodec_free_context, avcodec_parameters_from_context, avcodec_receive_packet,
|
||||||
AVCodecContext,
|
avcodec_send_frame, AVCodecContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Encoder(pub *mut 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) {
|
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::options::Options;
|
||||||
use crate::av::packet::Packet;
|
use crate::av::packet::Packet;
|
||||||
use crate::av::stream::Stream;
|
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::{
|
use ffmpeg_sys_next::{
|
||||||
av_interleaved_write_frame, av_read_frame, av_write_frame, av_write_trailer,
|
av_find_best_stream, av_interleaved_write_frame, av_read_frame, av_seek_frame, av_write_frame,
|
||||||
avformat_alloc_context, avformat_alloc_output_context2, avformat_find_stream_info,
|
av_write_trailer, avformat_alloc_context, avformat_alloc_output_context2,
|
||||||
avformat_init_output, avformat_new_stream, avformat_open_input, avformat_write_header,
|
avformat_find_stream_info, avformat_free_context, avformat_init_output, avformat_new_stream,
|
||||||
avio_open2, AVFormatContext, AVMediaType, AVIO_FLAG_WRITE,
|
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::ffi::CString;
|
||||||
use std::fmt::{Debug, Error, Formatter};
|
use std::fmt::{Debug, Error, Formatter};
|
||||||
use std::os::raw::{c_int, c_void};
|
use std::os::raw::{c_int, c_void};
|
||||||
use std::ptr::{null, null_mut};
|
use std::ptr::{null, null_mut};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Format<T> {
|
pub struct Format<T> {
|
||||||
pub ctx: *mut AVFormatContext,
|
pub ctx: *mut AVFormatContext,
|
||||||
pub avio: Option<AVIO<T>>,
|
pub avio: Option<AVIO<T>>,
|
||||||
|
@ -39,16 +39,45 @@ impl<T> Format<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
unsafe {
|
||||||
let ctx = self.ctx;
|
let ctx = self.ctx;
|
||||||
let mut stream = (*ctx).streams;
|
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 {
|
for _ in 0..(*ctx).nb_streams {
|
||||||
let curr_stream = *stream;
|
let curr_stream = *stream;
|
||||||
if (*(*curr_stream).codecpar).codec_type == stream_type {
|
if (*(*curr_stream).codecpar).codec_type == stream_type {
|
||||||
if index.is_none() || Some((*curr_stream).index) == index {
|
if (*curr_stream).index == index {
|
||||||
return Some(Stream(curr_stream));
|
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 {
|
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> {
|
pub fn write_header(&self, options: Option<Dictionary>) -> Result<(), String> {
|
||||||
verify_response("failed to write header to output", unsafe {
|
verify_response("failed to write header to output", unsafe {
|
||||||
avformat_write_header(
|
avformat_write_header(
|
||||||
|
@ -280,3 +313,16 @@ impl<T> AsMutVoidPtr for Format<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Options 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::as_ptr::{AsMutPtr, AsPtr};
|
||||||
use crate::av::verify_response;
|
use crate::av::verify_response;
|
||||||
use ffmpeg_sys_next::{
|
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,
|
AVSampleFormat,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,12 +25,12 @@ impl Frame {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn own(&mut self) {
|
pub fn own(&mut self) {
|
||||||
self.owned = true
|
self.owned = true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn disown(&mut self) {
|
pub fn disown(&mut self) {
|
||||||
self.owned = false
|
self.owned = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +177,6 @@ impl Drop for Frame {
|
||||||
if !self.owned {
|
if !self.owned {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
unsafe { av_frame_unref(self.ptr) }
|
unsafe { av_frame_free(&mut self.ptr) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::av_err2str;
|
use crate::av_err2str;
|
||||||
use ffmpeg_sys_next::AVCodecID::AV_CODEC_ID_HEVC;
|
use ffmpeg_sys_next::AVCodecID::AV_CODEC_ID_HEVC;
|
||||||
use ffmpeg_sys_next::{
|
use ffmpeg_sys_next::{
|
||||||
av_codec_is_decoder, av_codec_is_encoder, av_codec_next, av_inv_q, av_register_all,
|
av_codec_is_decoder, av_codec_is_encoder, av_codec_next, av_inv_q, av_log_set_level,
|
||||||
avcodec_alloc_context3, avcodec_find_decoder_by_name, avcodec_find_encoder_by_name, AVCodec,
|
av_register_all, avcodec_alloc_context3, avcodec_find_decoder_by_name,
|
||||||
AVCodecContext, AVCodecID, AVRational, AV_CODEC_CAP_HARDWARE,
|
avcodec_find_encoder_by_name, AVCodec, AVCodecContext, AVCodecID, AVRational,
|
||||||
|
AV_CODEC_CAP_HARDWARE,
|
||||||
};
|
};
|
||||||
use num_rational::Ratio;
|
use num_rational::Ratio;
|
||||||
use std::any::type_name;
|
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() {
|
pub fn init() {
|
||||||
unsafe {
|
unsafe {
|
||||||
av_register_all();
|
av_register_all();
|
||||||
|
av_log_set_level(-8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::av::Rational;
|
use crate::av::Rational;
|
||||||
use ffmpeg_sys_next::AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA;
|
use ffmpeg_sys_next::AVPacketSideDataType::AV_PKT_DATA_NEW_EXTRADATA;
|
||||||
use ffmpeg_sys_next::{
|
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);
|
pub struct Packet(pub *mut AVPacket);
|
||||||
|
@ -98,7 +98,7 @@ impl Packet {
|
||||||
impl Drop for Packet {
|
impl Drop for Packet {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
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::verify_response;
|
||||||
use crate::av::xcoder::XCoder;
|
use crate::av::xcoder::XCoder;
|
||||||
use ffmpeg_sys_next::{
|
use ffmpeg_sys_next::{
|
||||||
swr_alloc, swr_alloc_set_opts, swr_convert_frame, swr_get_delay, AVFrame, AVSampleFormat,
|
swr_alloc, swr_alloc_set_opts, swr_convert_frame, swr_drop_output, swr_free, swr_get_delay,
|
||||||
SwrContext,
|
AVSampleFormat, SwrContext,
|
||||||
};
|
};
|
||||||
use std::ptr::{null, null_mut};
|
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::frame::Frame;
|
||||||
use crate::av::verify_response;
|
use crate::av::verify_response;
|
||||||
use crate::av::xcoder::XCoder;
|
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::os::raw::c_int;
|
||||||
use std::ptr::null_mut;
|
use std::ptr::null_mut;
|
||||||
|
|
||||||
|
@ -150,3 +150,9 @@ impl AsMutPtr<SwsContext> for Scaler {
|
||||||
self.ctx
|
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)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use crate::av::init;
|
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 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::ffi::{CStr, CString};
|
||||||
|
use std::option::Option::Some;
|
||||||
use std::os::raw::{c_char, c_int};
|
use std::os::raw::{c_char, c_int};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use tide::{Request, StatusCode};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub mod av;
|
pub mod av;
|
||||||
pub mod transcoder;
|
pub mod transcoder;
|
||||||
|
pub mod transcoder_manager;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
// const INPUT_PATH: &str = "/tank/ephemeral/anime/series/Witch Hunter Robin - 2ndfire/[2ndfire]Witch_Hunter_Robin_-_01[91DCE49A].mkv";
|
// 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() {
|
lazy_static! {
|
||||||
println!("> transotf: ffmpeg {}", unsafe {
|
static ref HOME_PAGE: String = {
|
||||||
CStr::from_ptr(av_version_info()).to_str().unwrap()
|
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();
|
init();
|
||||||
|
|
||||||
let mut transcoder = Transcoder::create(INPUT_PATH, OUTPUT_PATH, None, None)
|
let mut app = tide::with_state(State::default());
|
||||||
.expect("Failed to create transcoder");
|
app.at("/").get(|_| async { Ok(HOME_PAGE.clone()) });
|
||||||
transcoder.transcode().expect("Failed to transcode");
|
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::AVMediaType::{AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO};
|
||||||
use ffmpeg_sys_next::AVPictureType::AV_PICTURE_TYPE_I;
|
use ffmpeg_sys_next::AVPictureType::AV_PICTURE_TYPE_I;
|
||||||
use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_YUV420P;
|
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 num_rational::Ratio;
|
||||||
|
use std::cmp::min;
|
||||||
use std::fs::{DirBuilder, File};
|
use std::fs::{DirBuilder, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::ops::Mul;
|
||||||
use std::option::Option::Some;
|
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 {
|
pub struct AudioTranscoder {
|
||||||
encoder: Encoder,
|
encoder: Encoder,
|
||||||
decoder: Decoder,
|
decoder: Decoder,
|
||||||
|
@ -50,8 +34,11 @@ pub struct AudioTranscoder {
|
||||||
segment: u32,
|
segment: u32,
|
||||||
last_pts: i64,
|
last_pts: i64,
|
||||||
current_pts: i64,
|
current_pts: i64,
|
||||||
|
seconds_per_segment: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SECONDS_PER_SEGMENT: u32 = 5;
|
||||||
|
|
||||||
impl AudioTranscoder {
|
impl AudioTranscoder {
|
||||||
fn process_packet(&mut self, packet: Packet) -> Result<(), String> {
|
fn process_packet(&mut self, packet: Packet) -> Result<(), String> {
|
||||||
self.decoder
|
self.decoder
|
||||||
|
@ -75,6 +62,11 @@ impl AudioTranscoder {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn input_stream(&self) -> &Stream {
|
||||||
|
&self.input_stream
|
||||||
|
}
|
||||||
|
|
||||||
fn start_segment(&self, force: bool) {
|
fn start_segment(&self, force: bool) {
|
||||||
if self.segment == 0 && !force {
|
if self.segment == 0 && !force {
|
||||||
return;
|
return;
|
||||||
|
@ -122,6 +114,7 @@ impl AudioTranscoder {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write_all(&mut segment)
|
.write_all(&mut segment)
|
||||||
.map_err(|_| format!("Failed to write audio segment {}", self.segment))?;
|
.map_err(|_| format!("Failed to write audio segment {}", self.segment))?;
|
||||||
|
|
||||||
self.start_segment(false);
|
self.start_segment(false);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -134,12 +127,10 @@ impl AudioTranscoder {
|
||||||
println!("WARN: new frame out of order");
|
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()?;
|
self.format.write_packet_null()?;
|
||||||
println!(
|
|
||||||
"Next audio segment (current: {}, pts: {}/{})!",
|
|
||||||
self.segment, self.last_pts, self.current_pts,
|
|
||||||
);
|
|
||||||
self.write_segment()?;
|
self.write_segment()?;
|
||||||
self.segment += 1;
|
self.segment += 1;
|
||||||
self.current_pts =
|
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 {
|
impl Transcoder {
|
||||||
pub fn create(
|
pub fn create(
|
||||||
input_path: &str,
|
input_path: &str,
|
||||||
|
@ -179,10 +192,13 @@ impl Transcoder {
|
||||||
let output_path = output_path.to_string();
|
let output_path = output_path.to_string();
|
||||||
|
|
||||||
let input_video = input
|
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())?;
|
.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() {
|
if audio_index.is_some() && input_audio.is_none() {
|
||||||
return Err("Failed to find audio stream".to_string());
|
return Err("Failed to find audio stream".to_string());
|
||||||
}
|
}
|
||||||
|
@ -233,30 +249,11 @@ impl Transcoder {
|
||||||
encoder.set_frame_size(decoder.frame_size() * 4);
|
encoder.set_frame_size(decoder.frame_size() * 4);
|
||||||
encoder.open()?;
|
encoder.open()?;
|
||||||
encoder.configure(&output_stream)?;
|
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 resampler = Resampler::from_coder(&decoder, &encoder);
|
||||||
let mut audio_options = Dictionary::new();
|
let mut audio_options = Dictionary::new();
|
||||||
audio_options.copy_from(&mut options);
|
audio_options.copy_from(&mut options)?;
|
||||||
format.init_output(audio_options);
|
format.init_output(audio_options)?;
|
||||||
|
|
||||||
Some(AudioTranscoder {
|
Some(AudioTranscoder {
|
||||||
frame_buffer: SortedFrameBuffer::new(),
|
frame_buffer: SortedFrameBuffer::new(),
|
||||||
|
@ -270,6 +267,7 @@ impl Transcoder {
|
||||||
last_pts: 0,
|
last_pts: 0,
|
||||||
segment: 0,
|
segment: 0,
|
||||||
resampler,
|
resampler,
|
||||||
|
seconds_per_segment: SECONDS_PER_SEGMENT,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -297,12 +295,6 @@ impl Transcoder {
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
println!(
|
|
||||||
"video decoder[{}] -> video encoder[{}]",
|
|
||||||
video_decoder.name(),
|
|
||||||
video_encoder.name()
|
|
||||||
);
|
|
||||||
|
|
||||||
let param = output_video.params();
|
let param = output_video.params();
|
||||||
param.set_height(video_decoder.height());
|
param.set_height(video_decoder.height());
|
||||||
param.set_width(video_decoder.width());
|
param.set_width(video_decoder.width());
|
||||||
|
@ -319,6 +311,8 @@ impl Transcoder {
|
||||||
video_output,
|
video_output,
|
||||||
output_video,
|
output_video,
|
||||||
audio,
|
audio,
|
||||||
|
segment_target: None,
|
||||||
|
input_video_index: None,
|
||||||
output_path,
|
output_path,
|
||||||
video_encoder,
|
video_encoder,
|
||||||
video_decoder,
|
video_decoder,
|
||||||
|
@ -328,30 +322,75 @@ impl Transcoder {
|
||||||
current_pts: 0,
|
current_pts: 0,
|
||||||
video_segment: 0,
|
video_segment: 0,
|
||||||
extra_data: None,
|
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()
|
DirBuilder::new()
|
||||||
.recursive(true)
|
.recursive(true)
|
||||||
.create(self.output_path.to_string())
|
.create(self.output_path.to_string())
|
||||||
.map_err(|_| "Failed to create target directory".to_string())?;
|
.map_err(|_| "Failed to create target directory".to_string())?;
|
||||||
|
|
||||||
self.video_output.write_header(None).unwrap();
|
self.video_output.write_header(None).unwrap();
|
||||||
let input_video_index = self.input_video.index();
|
self.input_video_index = Some(self.input_video.index());
|
||||||
let input_audio_index = self.audio.as_ref().map(|ia| ia.input_stream.index());
|
self.input_audio_index = self.audio.as_ref().map(|ia| ia.input_stream.index());
|
||||||
|
|
||||||
while let Some(packet) = self.input.next_packet() {
|
Ok(())
|
||||||
if input_video_index == packet.stream() {
|
}
|
||||||
|
|
||||||
|
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)?;
|
self.video_process_packet(packet)?;
|
||||||
} else if input_audio_index == Some(packet.stream()) {
|
} else if self.input_audio_index == Some(packet.stream()) {
|
||||||
// Safe assumption
|
// Safe assumption
|
||||||
if let Some(ref mut audio) = &mut self.audio {
|
if let Some(ref mut audio) = &mut self.audio {
|
||||||
audio.process_packet(packet)?;
|
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.encode_video()?;
|
||||||
self.video_output.write_trailer()?;
|
self.video_output.write_trailer()?;
|
||||||
self.video_write_segment()?;
|
self.video_write_segment()?;
|
||||||
|
@ -362,7 +401,21 @@ impl Transcoder {
|
||||||
fn video_process_packet(&mut self, packet: Packet) -> Result<(), String> {
|
fn video_process_packet(&mut self, packet: Packet) -> Result<(), String> {
|
||||||
self.video_decoder.send_packet(&packet)?;
|
self.video_decoder.send_packet(&packet)?;
|
||||||
while let Some(frame) = self.video_decoder.read_frame()? {
|
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 pts = frame.pts();
|
||||||
|
|
||||||
let frame = self
|
let frame = self
|
||||||
.frame_scaler
|
.frame_scaler
|
||||||
.scale(frame)
|
.scale(frame)
|
||||||
|
@ -407,21 +460,17 @@ impl Transcoder {
|
||||||
.write_all(&mut segment)
|
.write_all(&mut segment)
|
||||||
.map_err(|_| format!("Failed to write video segment {}", self.video_segment))?;
|
.map_err(|_| format!("Failed to write video segment {}", self.video_segment))?;
|
||||||
self.start_segment(false);
|
self.start_segment(false);
|
||||||
|
|
||||||
self.video_encoder.flush();
|
self.video_encoder.flush();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_video(&mut self) -> Result<(), String> {
|
fn encode_video(&mut self) -> Result<(), String> {
|
||||||
while let Some(frame) = self.video_frame_buffer.pop_first() {
|
while let Some(frame) = self.video_frame_buffer.pop_first() {
|
||||||
let pts_passed = frame.pts() - self.last_pts;
|
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()?;
|
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_write_segment()?;
|
||||||
self.video_segment += 1;
|
self.video_segment += 1;
|
||||||
self.current_pts =
|
self.current_pts =
|
||||||
|
@ -492,9 +541,11 @@ impl Transcoder {
|
||||||
ffio_wfourcc(pb, b"msix");
|
ffio_wfourcc(pb, b"msix");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stop(self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
struct Buffer {
|
struct Buffer {
|
||||||
buffer: Vec<u8>,
|
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">
|
<Period start="PT0.0S">
|
||||||
<BaseURL>data/</BaseURL>
|
<BaseURL>data/</BaseURL>
|
||||||
<AdaptationSet contentType="video">
|
<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"
|
<SegmentTemplate media="video-segment-$Number%05d$.m4s"
|
||||||
initialization="video-init.mp4" duration="5005" timescale="1001"
|
initialization="video-init.mp4" duration="5005" timescale="1001"
|
||||||
startNumber="0"/>
|
startNumber="0"/>
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<AdaptationSet contentType="audio">
|
<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"
|
<SegmentTemplate media="audio-segment-$Number%05d$.m4s"
|
||||||
initialization="audio-init.mp4" duration="5000" timescale="1000"
|
initialization="audio-init.mp4" duration="5000" timescale="1000"
|
||||||
startNumber="0"/>
|
startNumber="0"/>
|
||||||
|
|
Loading…
Reference in a new issue