Save
This commit is contained in:
parent
13cde58ffb
commit
4fbd56d13a
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
|
||||||
|
# IDE related files
|
||||||
|
gd-extension-rs-ffmepg.iml
|
||||||
|
.idea/*
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "gd-extension-rs-ffmepg"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = "0.2.144"
|
||||||
|
anyhow = "1.0.71"
|
||||||
|
rsmpeg = "0.14.1"
|
||||||
|
godot = { git = "https://github.com/godot-rust/gdext", branch = "master" }
|
101
src/lib.rs
Normal file
101
src/lib.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
use godot::prelude::*;
|
||||||
|
use godot::engine::{Node, NodeVirtual, Texture};
|
||||||
|
use crate::metadata::VideoMetadata;
|
||||||
|
|
||||||
|
mod metadata;
|
||||||
|
mod playback;
|
||||||
|
|
||||||
|
struct Main;
|
||||||
|
|
||||||
|
#[gdextension]
|
||||||
|
unsafe impl ExtensionLibrary for Main {}
|
||||||
|
|
||||||
|
|
||||||
|
// A Video Player Node.
|
||||||
|
#[derive(GodotClass)]
|
||||||
|
#[class(base=Node)]
|
||||||
|
struct VideoPlayer {
|
||||||
|
metadata : Option<VideoMetadata>,
|
||||||
|
|
||||||
|
#[export(get = get_has_video)]
|
||||||
|
has_video: bool,
|
||||||
|
#[export(get = get_has_audio)]
|
||||||
|
has_audio: bool,
|
||||||
|
#[export(get = get_duration)]
|
||||||
|
duration: i64,
|
||||||
|
|
||||||
|
#[base]
|
||||||
|
base: Base<Node>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[godot_api]
|
||||||
|
impl NodeVirtual for VideoPlayer {
|
||||||
|
//fn init(base: Base<Node>) -> Self {
|
||||||
|
fn init(base: Base<Node>) -> Self {
|
||||||
|
Self {
|
||||||
|
metadata: None,
|
||||||
|
has_video: false,
|
||||||
|
has_audio: false,
|
||||||
|
duration: -1,
|
||||||
|
base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[godot_api]
|
||||||
|
impl VideoPlayer {
|
||||||
|
|
||||||
|
/// Load a video file from `path`.
|
||||||
|
/// Return true if successfully loaded else false.
|
||||||
|
#[func]
|
||||||
|
fn load_file(&mut self, path : GodotString) -> bool {
|
||||||
|
if let Ok(m) = VideoMetadata::get_metadata(path.to_string().as_str()) {
|
||||||
|
self.metadata = Some(m);
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the version string for ffmepg
|
||||||
|
#[func]
|
||||||
|
fn ffmpeg_version(&mut self) -> GodotString {
|
||||||
|
VideoMetadata::get_version().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the license string for ffmepg
|
||||||
|
#[func]
|
||||||
|
fn ffmpeg_license(&mut self) -> GodotString {
|
||||||
|
VideoMetadata::get_license().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return true if audio stream is loaded
|
||||||
|
#[func]
|
||||||
|
fn get_has_audio(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return true if video stream is loaded
|
||||||
|
#[func]
|
||||||
|
fn get_has_video(&self) -> bool {
|
||||||
|
if let Some(_) = &self.metadata {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the duration en micro second of the video or -1 if no video is loaded
|
||||||
|
#[func]
|
||||||
|
fn get_duration(&self) -> i64 {
|
||||||
|
if let Some(m) = &self.metadata {
|
||||||
|
return m.duration
|
||||||
|
}
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Return the duration en micro second of the video or -1 if no video is loaded
|
||||||
|
#[func]
|
||||||
|
fn get_texture(&self) -> Gd<Texture>{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
105
src/metadata.rs
Normal file
105
src/metadata.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use std::ffi::{c_char, CStr, CString};
|
||||||
|
use rsmpeg::avcodec::AVCodecContext;
|
||||||
|
use rsmpeg::avformat::AVFormatContextInput;
|
||||||
|
use rsmpeg::avutil::av_q2d;
|
||||||
|
use rsmpeg::ffi;
|
||||||
|
use anyhow::Context;
|
||||||
|
use rsmpeg::ffi::{av_version_info,avutil_license};
|
||||||
|
|
||||||
|
pub struct VideoMetadata{
|
||||||
|
pub duration : i64,
|
||||||
|
pub frame_rate: f64,
|
||||||
|
pub width: i32,
|
||||||
|
pub height: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMetadata {
|
||||||
|
pub fn get_metadata(path: &str) -> anyhow::Result<VideoMetadata> {
|
||||||
|
let path = &CString::new(path.to_string())?;
|
||||||
|
let input_format_context = AVFormatContextInput::open(path)?;
|
||||||
|
let duration = input_format_context.duration;
|
||||||
|
|
||||||
|
let (video_stream_index, decoder) = input_format_context
|
||||||
|
.find_best_stream(ffi::AVMediaType_AVMEDIA_TYPE_VIDEO)?
|
||||||
|
.context("Failed to find video stream")?;
|
||||||
|
|
||||||
|
let video_stream = input_format_context
|
||||||
|
.streams()
|
||||||
|
.get(video_stream_index)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let frame_rate = av_q2d(video_stream.r_frame_rate) as f64;
|
||||||
|
|
||||||
|
// Get `width` and `height` from `decode_context`
|
||||||
|
let mut decode_context = AVCodecContext::new(&decoder);
|
||||||
|
decode_context.apply_codecpar(&video_stream.codecpar())?;
|
||||||
|
decode_context.open(None)?;
|
||||||
|
let width = decode_context.width;
|
||||||
|
let height = decode_context.height;
|
||||||
|
|
||||||
|
let (video_stream_index, decoder) = input_format_context
|
||||||
|
.find_best_stream(ffi::AVMediaType_AVMEDIA_TYPE_AUDIO)?
|
||||||
|
.context("Failed to find audio stream")?;
|
||||||
|
|
||||||
|
Ok(VideoMetadata{
|
||||||
|
duration,
|
||||||
|
frame_rate,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_version() -> String {
|
||||||
|
let c_buf: *const c_char = unsafe { av_version_info() };
|
||||||
|
let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) };
|
||||||
|
let str_slice: &str = c_str.to_str().unwrap();
|
||||||
|
str_slice.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_license() -> String {
|
||||||
|
let c_buf: *const c_char = unsafe { avutil_license() };
|
||||||
|
let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) };
|
||||||
|
let str_slice: &str = c_str.to_str().unwrap();
|
||||||
|
str_slice.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_metadata_video_and_audio_file() {
|
||||||
|
let metadata = VideoMetadata::get_metadata("tests/assets/video1.mp4").unwrap();
|
||||||
|
assert_eq!(metadata.duration, 25406667);
|
||||||
|
assert_eq!(metadata.frame_rate, 29.97002997002997);
|
||||||
|
assert_eq!(metadata.width, 1920);
|
||||||
|
assert_eq!(metadata.height, 1080);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_get_metadata_video_only_file() {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_metadata_audio_only_file() {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_metadata_invalid_file() {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_version() {
|
||||||
|
assert!(!VideoMetadata::get_version().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_license() {
|
||||||
|
assert!(!VideoMetadata::get_license().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
49
src/playback/constant.rs
Normal file
49
src/playback/constant.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
pub const MAX_QUEUE_SIZE: usize = (15 * 1024 * 1024);
|
||||||
|
pub const MIN_FRAMES : usize = 25;
|
||||||
|
pub const EXTERNAL_CLOCK_MIN_FRAMES : usize = 2;
|
||||||
|
pub const EXTERNAL_CLOCK_MAX_FRAMES: usize = 10;
|
||||||
|
|
||||||
|
/* Minimum SDL audio buffer size, in samples. */
|
||||||
|
pub const SDL_AUDIO_MIN_BUFFER_SIZE : usize = 512;
|
||||||
|
/* Calculate actual buffer size keeping in mind not cause too frequent audio callbacks */
|
||||||
|
pub const SDL_AUDIO_MAX_CALLBACKS_PER_SEC: usize = 30;
|
||||||
|
|
||||||
|
/* Step size for volume control in dB */
|
||||||
|
pub const SDL_VOLUME_STEP : f32 = (0.75);
|
||||||
|
|
||||||
|
/* no AV sync correction is done if below the minimum AV sync threshold */
|
||||||
|
pub const AV_SYNC_THRESHOLD_MIN : f32 = 0.04;
|
||||||
|
/* AV sync correction is done if above the maximum AV sync threshold */
|
||||||
|
pub const AV_SYNC_THRESHOLD_MAX : f32 = 0.1;
|
||||||
|
/* If a frame duration is longer than this, it will not be duplicated to compensate AV sync */
|
||||||
|
pub const AV_SYNC_FRAMEDUP_THRESHOLD : f32 =0.1;
|
||||||
|
/* no AV correction is done if too big error */
|
||||||
|
pub const AV_NOSYNC_THRESHOLD : f32 = 10.0;
|
||||||
|
|
||||||
|
/* maximum audio speed change to get correct sync */
|
||||||
|
pub const SAMPLE_CORRECTION_PERCENT_MAX : usize = 10;
|
||||||
|
|
||||||
|
/* external clock speed adjustment constants for realtime sources based on buffer fullness */
|
||||||
|
pub const EXTERNAL_CLOCK_SPEED_MIN : f32 = 0.900;
|
||||||
|
pub const EXTERNAL_CLOCK_SPEED_MAX : f32 = 1.010;
|
||||||
|
pub const EXTERNAL_CLOCK_SPEED_STEP : f32 = 0.001;
|
||||||
|
|
||||||
|
/* we use about AUDIO_DIFF_AVG_NB A-V differences to make the average */
|
||||||
|
pub const AUDIO_DIFF_AVG_NB : usize = 20;
|
||||||
|
|
||||||
|
/* polls for possible required screen refresh at least this often, should be less than 1/fps */
|
||||||
|
pub const REFRESH_RATE : f32 = 0.01;
|
||||||
|
|
||||||
|
/* NOTE: the size must be big enough to compensate the hardware audio buffersize size */
|
||||||
|
/* TODO: We assume that a decoded and resampled frame fits into this buffer */
|
||||||
|
pub const SAMPLE_ARRAY_SIZE : usize = (8 * 65536);
|
||||||
|
|
||||||
|
pub const CURSOR_HIDE_DELAY : usize = 1000000;
|
||||||
|
|
||||||
|
pub const USE_ONEPASS_SUBTITLE_RENDER : bool = true;
|
||||||
|
|
||||||
|
pub const VIDEO_PICTURE_QUEUE_SIZE : usize = 3;
|
||||||
|
pub const SUBPICTURE_QUEUE_SIZE : usize = 16;
|
||||||
|
pub const SAMPLE_QUEUE_SIZE : usize = 9;
|
||||||
|
// FIXME const FRAME_QUEUE_SIZE ffmax(SAMPLE_QUEUE_SIZE, ffmax(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))
|
||||||
|
pub const FRAME_QUEUE_SIZE : usize = ffmax!(SAMPLE_QUEUE_SIZE, (ffmax!(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE)));
|
180
src/playback/decoder.rs
Normal file
180
src/playback/decoder.rs
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
use std::ptr::null_mut;
|
||||||
|
use std::sync::Condvar;
|
||||||
|
use std::thread::ThreadId;
|
||||||
|
use libc::{EAGAIN, endservent};
|
||||||
|
use rsmpeg::avcodec::{AVCodecContext, AVPacket};
|
||||||
|
use rsmpeg::avutil::{av_rescale_q, AVFrame};
|
||||||
|
use rsmpeg::error::RsmpegError;
|
||||||
|
use rsmpeg::error::RsmpegError::AVError;
|
||||||
|
use rsmpeg::ffi::{AV_NOPTS_VALUE, AVERROR_EOF};
|
||||||
|
use crate::playback::DECODER_REORDER_PTS;
|
||||||
|
use crate::playback::packet::MutexPacketQueue;
|
||||||
|
use rsmpeg::ffi::{ AVRational, avcodec_flush_buffers };
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Decoder {
|
||||||
|
pkt : Box<AVPacket>,
|
||||||
|
queue : Box<MutexPacketQueue>,
|
||||||
|
avctx : Box<AVCodecContext>,
|
||||||
|
pkt_serial : Box<i64>,
|
||||||
|
finished: i64,
|
||||||
|
packet_pending: bool,
|
||||||
|
empty_queue_cond: Box<Condvar>,
|
||||||
|
start_pts : i64,
|
||||||
|
start_pts_tb : AVRational,
|
||||||
|
next_pts : i64,
|
||||||
|
next_pts_tb: AVRational,
|
||||||
|
decoder_tid: Box<ThreadId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder {
|
||||||
|
fn new(avctx: AVCodecContext, queue: MutexPacketQueue, tid: ThreadId) -> Self {
|
||||||
|
Decoder {
|
||||||
|
pkt: Box::new(Default::default()),
|
||||||
|
queue: Box::new(queue),
|
||||||
|
avctx: Box::new(avctx),
|
||||||
|
pkt_serial: Box::new(0),
|
||||||
|
finished: 0,
|
||||||
|
packet_pending: false,
|
||||||
|
empty_queue_cond: Box::new(Default::default()),
|
||||||
|
start_pts: 0,
|
||||||
|
start_pts_tb: AVRational {num: 1, den : 1},
|
||||||
|
next_pts: 0,
|
||||||
|
next_pts_tb: AVRational {num : 1, den : 1},
|
||||||
|
decoder_tid: Box::new(tid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_frame(&mut self, av_frame: &mut AVFrame, sub: &mut rsmpeg::ffi::AVSubtitle) -> Result<(),RsmpegError> { // TODO Recheck
|
||||||
|
let mut ret = Err(AVError(EAGAIN));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if self.queue.data.serial == *self.pkt_serial {
|
||||||
|
loop {
|
||||||
|
if self.queue.data.abort_request {
|
||||||
|
return Err(AVError(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
let codec_type = self.avctx.extract_codecpar().codec_type();
|
||||||
|
ret = if codec_type.is_video() {
|
||||||
|
let ret_receive = self.avctx.receive_frame();
|
||||||
|
if let Ok(mut frame) = ret_receive {
|
||||||
|
if ! DECODER_REORDER_PTS {
|
||||||
|
frame.set_pts(frame.best_effort_timestamp);
|
||||||
|
} else {
|
||||||
|
frame.set_pts(frame.pkt_dts);
|
||||||
|
}
|
||||||
|
*av_frame = frame;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ret_receive.unwrap_err())
|
||||||
|
}
|
||||||
|
} else if codec_type.is_audio() {
|
||||||
|
let ret_receive = self.avctx.receive_frame();
|
||||||
|
if let Ok(mut frame) = ret_receive {
|
||||||
|
let tb = AVRational {num: 1, den: frame.sample_rate};
|
||||||
|
if frame.pts != AV_NOPTS_VALUE {
|
||||||
|
frame.set_pts(av_rescale_q(frame.pts, self.avctx.pkt_timebase, tb))
|
||||||
|
} else if self.next_pts != AV_NOPTS_VALUE {
|
||||||
|
frame.set_pts(av_rescale_q(self.next_pts, self.next_pts_tb, tb));
|
||||||
|
}
|
||||||
|
if frame.pts != AV_NOPTS_VALUE {
|
||||||
|
self.next_pts = frame.pts + frame.nb_samples as i64;
|
||||||
|
self.next_pts_tb = tb;
|
||||||
|
}
|
||||||
|
*av_frame = frame;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ret_receive.unwrap_err())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret
|
||||||
|
};
|
||||||
|
|
||||||
|
if Some(&AVError(AVERROR_EOF)) == ret.as_ref().err(){
|
||||||
|
self.finished = *self.pkt_serial;
|
||||||
|
unsafe { avcodec_flush_buffers(self.avctx.as_mut_ptr())};
|
||||||
|
return Ok(()); // TODO
|
||||||
|
}
|
||||||
|
if let Some(&AVError(x)) = ret.as_ref().err() {
|
||||||
|
if x >= 0 {
|
||||||
|
return Err(AVError(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Some(&AVError(EAGAIN)) == ret.as_ref().err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
let mut pq = self.queue.mutex.lock().unwrap();
|
||||||
|
while self.queue.data.queue.len() == 0 {
|
||||||
|
pq = self.empty_queue_cond.wait(pq).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! self.packet_pending {
|
||||||
|
let old_serial = *self.pkt_serial;
|
||||||
|
// if packet_queue_get(&d.queue, &mut d.pkt, true, Some(&mut d.pkt_serial)) < 0 {// TODO
|
||||||
|
if let Some(AVError(x)) = &mut self.queue.get( &mut self.pkt, true, Some(&mut self.pkt_serial)).err(){// TODO
|
||||||
|
if (*x as i32) < 0 {
|
||||||
|
return Err(AVError(-1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if old_serial != *self.pkt_serial {
|
||||||
|
unsafe { avcodec_flush_buffers(self.avctx.as_mut_ptr())};
|
||||||
|
self.finished = 0;
|
||||||
|
self.next_pts = self.start_pts;
|
||||||
|
self.next_pts_tb = self.start_pts_tb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
if self.queue.data.serial == *self.pkt_serial {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// TODO av_packet_unref(d->pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.avctx.extract_codecpar().codec_type().is_subtitle() {
|
||||||
|
let got_frame = 0;
|
||||||
|
|
||||||
|
let mut ret = self.avctx.decode_subtitle(Some(&mut *self.pkt)); // TODO no return ?
|
||||||
|
if ret.is_err() {
|
||||||
|
ret = Err(AVError(EAGAIN));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ret.as_ref().unwrap().is_some() && self.pkt.data != null_mut() {
|
||||||
|
// *sub = *ret.unwrap().unwrap(); FIXME
|
||||||
|
self.packet_pending = true;
|
||||||
|
}
|
||||||
|
ret = if ret.as_ref().unwrap().is_some() { Ok(None) } else {
|
||||||
|
if self.pkt.data != null_mut() {
|
||||||
|
Err(AVError(EAGAIN))
|
||||||
|
} else {
|
||||||
|
Err(AVError(AVERROR_EOF))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//av_packet_unref(d->pkt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.avctx.send_packet(Some(&self.pkt)).err() == Some(AVError(EAGAIN)) {
|
||||||
|
//av_log(&d.avctx, AV_LOG_ERROR,
|
||||||
|
// "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
|
||||||
|
self.packet_pending = true;
|
||||||
|
} else {
|
||||||
|
//av_packet_unref(d->pkt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
91
src/playback/frame.rs
Normal file
91
src/playback/frame.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use std::ptr::{null, null_mut};
|
||||||
|
use rsmpeg::avcodec::AVSubtitle;
|
||||||
|
use rsmpeg::avutil::AVFrame;
|
||||||
|
use crate::playback::packet::PacketQueue;
|
||||||
|
use rsmpeg::ffi::AVRational;
|
||||||
|
use crate::playback::constant::FRAME_QUEUE_SIZE;
|
||||||
|
use crate::playback::mutex::{MutexQueue, New};
|
||||||
|
|
||||||
|
pub struct FrameData {
|
||||||
|
pub pkt_pos: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Common struct for handling all types of decoded data and allocated render buffers. */
|
||||||
|
pub struct Frame {
|
||||||
|
pub frame: Box<AVFrame>,
|
||||||
|
pub sub: Option<Box<AVSubtitle>>,
|
||||||
|
pub serial: i64,
|
||||||
|
pub pts: f32, /* presentation timestamp for the frame */
|
||||||
|
pub duration: f32, /* estimated duration of the frame */
|
||||||
|
pub pos: i64, /* byte position of the frame in the input file */
|
||||||
|
pub width: i64,
|
||||||
|
pub height : i64,
|
||||||
|
pub format : i64,
|
||||||
|
pub sar : AVRational,
|
||||||
|
pub uploaded : bool,
|
||||||
|
pub flip_v : bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type MutexFrameQueue = MutexQueue<FrameQueue>;
|
||||||
|
|
||||||
|
pub struct FrameQueue {
|
||||||
|
pub queue : [Option<Frame>; FRAME_QUEUE_SIZE],
|
||||||
|
pub rindex : usize,
|
||||||
|
pub windex: usize,
|
||||||
|
pub size : usize,
|
||||||
|
pub max_size: usize,
|
||||||
|
pub keep_last: i64,
|
||||||
|
pub rindex_shown: usize,
|
||||||
|
pub pktq : Box<PacketQueue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl New for FrameQueue {
|
||||||
|
fn new() -> Self {
|
||||||
|
FrameQueue {
|
||||||
|
queue: Default::default(),
|
||||||
|
rindex: 0,
|
||||||
|
windex: 0,
|
||||||
|
size: 0,
|
||||||
|
max_size: 0,
|
||||||
|
keep_last: 0,
|
||||||
|
rindex_shown: 0,
|
||||||
|
pktq: Box::new(PacketQueue::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl MutexFrameQueue {
|
||||||
|
pub fn signal(&self) {
|
||||||
|
let _unused = self.mutex.lock().unwrap();
|
||||||
|
self.cond.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek(&self) -> &Option<Frame>
|
||||||
|
{
|
||||||
|
return &self.data.queue[(self.data.rindex + self.data.rindex_shown) % self.data.max_size];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn peek_next(&self) -> &Option<Frame>
|
||||||
|
{
|
||||||
|
return &self.data.queue[(self.data.rindex + self.data.rindex_shown + 1) % self.data.max_size];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek_last(&self) -> &Option<Frame>{
|
||||||
|
&self.data.queue[self.data.rindex]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek_writable(&mut self) -> &Option<Frame> {
|
||||||
|
/* wait until we have space to put a new frame */
|
||||||
|
let mut m = self.mutex.lock().unwrap();
|
||||||
|
let pq = &mut self.data;
|
||||||
|
while pq.size >= pq.max_size && !pq.pktq.abort_request {
|
||||||
|
m = self.cond.wait(m).unwrap();
|
||||||
|
}
|
||||||
|
if pq.pktq.abort_request {
|
||||||
|
return &None;
|
||||||
|
}
|
||||||
|
& pq.queue[pq.windex]
|
||||||
|
}
|
||||||
|
}
|
500
src/playback/mod.rs
Normal file
500
src/playback/mod.rs
Normal file
@ -0,0 +1,500 @@
|
|||||||
|
use std::ffi::c_int;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::ptr::{null_mut};
|
||||||
|
use std::sync::{Condvar};
|
||||||
|
use std::thread::ThreadId;
|
||||||
|
use godot::engine::{Image, ImageTexture, Texture};
|
||||||
|
use godot::engine::image::Format;
|
||||||
|
use godot::prelude::{GdRef, real, Rect2};
|
||||||
|
use libc::EAGAIN;
|
||||||
|
use rsmpeg::avcodec::{AVCodecContext, AVPacket};
|
||||||
|
use rsmpeg::avformat::{AVInputFormat, AVStream};
|
||||||
|
use rsmpeg::avutil::{av_cmp_q, av_make_q, av_mul_q, av_rescale_q, AVFrame, AVSampleFormat, AVPixelFormat, AVImage};
|
||||||
|
use rsmpeg::error::RsmpegError;
|
||||||
|
use rsmpeg::error::RsmpegError::AVError;
|
||||||
|
use rsmpeg::swresample::SwrContext;
|
||||||
|
use rsmpeg::ffi::{AV_NOPTS_VALUE, av_image_alloc, sws_scale, av_get_pix_fmt_name, AVChannelLayout, avcodec_flush_buffers, av_image_get_buffer_size, AVERROR_EOF, AVFormatContext, AVRational, av_rescale, AVSubtitleRect, sws_getCachedContext, AV_LOG_FATAL, av_log};
|
||||||
|
use rsmpeg::ffi::{AVPixelFormat_AV_PIX_FMT_RGB8, AVPixelFormat_AV_PIX_FMT_RGBA, AVPixelFormat_AV_PIX_FMT_RGB24};
|
||||||
|
use rsmpeg::swscale::SwsContext;
|
||||||
|
use crate::playback::decoder::Decoder;
|
||||||
|
use crate::playback::frame::{Frame, FrameQueue, MutexFrameQueue};
|
||||||
|
use crate::playback::packet::{MutexPacketQueue, PacketQueue};
|
||||||
|
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! ffmax {
|
||||||
|
( $a:tt , $b:tt ) => {if $a > $b { $a } else { $b }};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod constant;
|
||||||
|
mod decoder;
|
||||||
|
mod frame;
|
||||||
|
mod mutex;
|
||||||
|
mod packet;
|
||||||
|
|
||||||
|
struct AudioParams {
|
||||||
|
freq : i64,
|
||||||
|
ch_layout : AVChannelLayout,
|
||||||
|
fmt : AVSampleFormat,
|
||||||
|
frame_size: i64,
|
||||||
|
bytes_per_sec: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Clock {
|
||||||
|
pts : f32, /* clock base */
|
||||||
|
pts_drift: f32, /* clock base minus time at which we updated the clock */
|
||||||
|
last_updated: f32,
|
||||||
|
speed: f32,
|
||||||
|
serial : i64 , /* clock is based on a packet with this serial */
|
||||||
|
paused : bool,
|
||||||
|
queue_serial: *const i64, /* pointer to the current packet queue serial, used for obsolete clock detection */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum AvSyncMode {
|
||||||
|
AV_SYNC_AUDIO_MASTER, /* default choice */
|
||||||
|
AV_SYNC_VIDEO_MASTER,
|
||||||
|
AV_SYNC_EXTERNAL_CLOCK, /* synchronize to an external clock */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct VideoState {
|
||||||
|
read_tid: Box<ThreadId>,
|
||||||
|
iformat: Box<AVInputFormat>,
|
||||||
|
abort_request : bool,
|
||||||
|
force_refresh : bool,
|
||||||
|
paused: bool,
|
||||||
|
last_paused : i64,
|
||||||
|
queue_attachments_req: i64,
|
||||||
|
seek_req : i64,
|
||||||
|
seek_flags : i64,
|
||||||
|
seek_pos: i64,
|
||||||
|
seek_rel : i64,
|
||||||
|
read_pause_return : i64,
|
||||||
|
ic : Box<AVFormatContext>,
|
||||||
|
realtime: i64,
|
||||||
|
|
||||||
|
audclk : Clock,
|
||||||
|
vidclk : Clock,
|
||||||
|
extclk : Clock,
|
||||||
|
|
||||||
|
pictq : MutexFrameQueue,
|
||||||
|
subpq : MutexFrameQueue,
|
||||||
|
sampq : MutexFrameQueue,
|
||||||
|
|
||||||
|
auddec : Decoder,
|
||||||
|
viddec : Decoder,
|
||||||
|
subdec : Decoder,
|
||||||
|
|
||||||
|
audio_stream: i64,
|
||||||
|
|
||||||
|
av_sync_type: i64,
|
||||||
|
|
||||||
|
audio_clock: f32,
|
||||||
|
audio_clock_serial : i64,
|
||||||
|
audio_diff_cum : f32, /* used for AV difference average computation */
|
||||||
|
audio_diff_avg_coef : f32,
|
||||||
|
audio_diff_threshold: f32,
|
||||||
|
audio_diff_avg_count: i64,
|
||||||
|
audio_st: *const AVStream,
|
||||||
|
audioq : PacketQueue,
|
||||||
|
audio_hw_buf_size: i64,
|
||||||
|
audio_buf: *const i8,
|
||||||
|
audio_buf1 : *const i8,
|
||||||
|
audio_buf_size : usize, /* in bytes */
|
||||||
|
audio_buf1_size : usize,
|
||||||
|
audio_buf_index: i64, /* in bytes */
|
||||||
|
audio_write_buf_size : i64,
|
||||||
|
audio_volume : i64,
|
||||||
|
muted : bool,
|
||||||
|
audio_src : AudioParams,
|
||||||
|
audio_filter_src: AudioParams,
|
||||||
|
audio_tgt : AudioParams,
|
||||||
|
swr_ctx: *const SwrContext,
|
||||||
|
frame_drops_early : bool,
|
||||||
|
frame_drops_late : bool,
|
||||||
|
|
||||||
|
subtitle_stream: usize,
|
||||||
|
subtitle_st :Option<Box<AVStream>>,
|
||||||
|
subtitleq: PacketQueue,
|
||||||
|
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
xleft: usize,
|
||||||
|
ytop: usize,
|
||||||
|
|
||||||
|
img_convert_ctx: Box<SwsContext>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EShowMode {
|
||||||
|
SHOW_MODE_NONE = -1, SHOW_MODE_VIDEO = 0, SHOW_MODE_WAVES, SHOW_MODE_RDFT, SHOW_MODE_NB}
|
||||||
|
|
||||||
|
// static sample_array: [i16 ; SAMPLE_ARRAY_SIZE as usize] = [];
|
||||||
|
// int sample_array_index;
|
||||||
|
// int last_i_start;
|
||||||
|
// RDFTContext *rdft;
|
||||||
|
// int rdft_bits;
|
||||||
|
// FFTSample *rdft_data;
|
||||||
|
// int xpos;
|
||||||
|
// double last_vis_time;
|
||||||
|
// SDL_Texture *vis_texture;
|
||||||
|
// SDL_Texture *sub_texture;
|
||||||
|
// SDL_Texture *vid_texture;
|
||||||
|
//
|
||||||
|
// int subtitle_stream;
|
||||||
|
// AVStream *subtitle_st;
|
||||||
|
// PacketQueue subtitleq;
|
||||||
|
//
|
||||||
|
// double frame_timer;
|
||||||
|
// double frame_last_returned_time;
|
||||||
|
// double frame_last_filter_delay;
|
||||||
|
// int video_stream;
|
||||||
|
// AVStream *video_st;
|
||||||
|
// PacketQueue videoq;
|
||||||
|
// double max_frame_duration; // maximum duration of a frame - above this, we consider the jump a timestamp discontinuity
|
||||||
|
// struct SwsContext *sub_convert_ctx;
|
||||||
|
// int eof;
|
||||||
|
//
|
||||||
|
// char *filename;
|
||||||
|
// int width, height, xleft, ytop;
|
||||||
|
// int step;
|
||||||
|
//
|
||||||
|
// int vfilter_idx;
|
||||||
|
// AVFilterContext *in_video_filter; // the first filter in the video chain
|
||||||
|
// AVFilterContext *out_video_filter; // the last filter in the video chain
|
||||||
|
// AVFilterContext *in_audio_filter; // the first filter in the audio chain
|
||||||
|
// AVFilterContext *out_audio_filter; // the last filter in the audio chain
|
||||||
|
// AVFilterGraph *agraph; // audio filter graph
|
||||||
|
//
|
||||||
|
// int last_video_stream, last_audio_stream, last_subtitle_stream;
|
||||||
|
//
|
||||||
|
// SDL_cond *continue_read_thread;
|
||||||
|
// } VideoState;
|
||||||
|
//
|
||||||
|
// /* options specified by the user */
|
||||||
|
// static const AVInputFormat *file_iformat;
|
||||||
|
// static const char *input_filename;
|
||||||
|
// static const char *window_title;
|
||||||
|
// static int default_width = 640;
|
||||||
|
// static int default_height = 480;
|
||||||
|
// static int screen_width = 0;
|
||||||
|
// static int screen_height = 0;
|
||||||
|
// static int screen_left = SDL_WINDOWPOS_CENTERED;
|
||||||
|
// static int screen_top = SDL_WINDOWPOS_CENTERED;
|
||||||
|
// static int audio_disable;
|
||||||
|
// static int video_disable;
|
||||||
|
// static int subtitle_disable;
|
||||||
|
// static const char* wanted_stream_spec[AVMEDIA_TYPE_NB] = {0};
|
||||||
|
// static int seek_by_bytes = -1;
|
||||||
|
// static float seek_interval = 10;
|
||||||
|
// static int display_disable;
|
||||||
|
// static int borderless;
|
||||||
|
// static int alwaysontop;
|
||||||
|
// static int startup_volume = 100;
|
||||||
|
// static int show_status = -1;
|
||||||
|
// static int av_sync_type = AV_SYNC_AUDIO_MASTER;
|
||||||
|
// static int64_t start_time = AV_NOPTS_VALUE;
|
||||||
|
// static int64_t duration = AV_NOPTS_VALUE;
|
||||||
|
// static int fast = 0;
|
||||||
|
// static int genpts = 0;
|
||||||
|
// static int lowres = 0;
|
||||||
|
static DECODER_REORDER_PTS: bool = false;
|
||||||
|
// static int autoexit;
|
||||||
|
// static int exit_on_keydown;
|
||||||
|
// static int exit_on_mousedown;
|
||||||
|
// static int loop = 1;
|
||||||
|
// static int framedrop = -1;
|
||||||
|
// static int infinite_buffer = -1;
|
||||||
|
// static enum ShowMode show_mode = SHOW_MODE_NONE;
|
||||||
|
// static const char *audio_codec_name;
|
||||||
|
// static const char *subtitle_codec_name;
|
||||||
|
// static const char *video_codec_name;
|
||||||
|
// double rdftspeed = 0.02;
|
||||||
|
// static int64_t cursor_last_shown;
|
||||||
|
// static int cursor_hidden = 0;
|
||||||
|
// static const char **vfilters_list = NULL;
|
||||||
|
// static int nb_vfilters = 0;
|
||||||
|
// static char *afilters = NULL;
|
||||||
|
// static int autorotate = 1;
|
||||||
|
// static int find_stream_info = 1;
|
||||||
|
// static int filter_nbthreads = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fn calculate_display_rect(
|
||||||
|
rect: &mut Rect2,
|
||||||
|
scr_xleft: i64,
|
||||||
|
scr_ytop: i64,
|
||||||
|
scr_width: i64,
|
||||||
|
scr_height: i64,
|
||||||
|
pic_width: i64,
|
||||||
|
pic_height: i64,
|
||||||
|
pic_sar: AVRational,
|
||||||
|
) {
|
||||||
|
let mut aspect_ratio = pic_sar;
|
||||||
|
let (mut width, mut height, mut x, mut y): (i64, i64, i64, i64);
|
||||||
|
|
||||||
|
if av_cmp_q(aspect_ratio, av_make_q(0, 1)) <= 0 {
|
||||||
|
aspect_ratio = av_make_q(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
aspect_ratio = av_mul_q(aspect_ratio, av_make_q(pic_width as c_int, pic_height as c_int));
|
||||||
|
|
||||||
|
// XXX: we suppose the screen has a 1.0 pixel ratio
|
||||||
|
height = scr_height;
|
||||||
|
width = unsafe { av_rescale(height, aspect_ratio.num.into(), aspect_ratio.den.into()) & !1 };
|
||||||
|
if width > scr_width {
|
||||||
|
width = scr_width;
|
||||||
|
height = unsafe { av_rescale(width, aspect_ratio.den.into(), aspect_ratio.num.into()) & !1 };
|
||||||
|
}
|
||||||
|
x = (scr_width - width) / 2;
|
||||||
|
y = (scr_height - height) / 2;
|
||||||
|
rect.position.x = (scr_xleft + x) as real;
|
||||||
|
rect.position.y = (scr_ytop + y) as real;
|
||||||
|
rect.size.x = ffmax!((width as f32), 1.0);
|
||||||
|
rect.size.y = ffmax!((height as f32), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct VideoStreamPlayback {
|
||||||
|
video_state : VideoState,
|
||||||
|
|
||||||
|
vid_texture : Box<ImageTexture>,
|
||||||
|
image_buffer : Vec<u8>,
|
||||||
|
transfer_frame: Option<Box<AVFrame>>,
|
||||||
|
sws_flag: usize,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoStreamPlayback {
|
||||||
|
fn video_image_display(&mut self) {
|
||||||
|
let mut vp = self.video_state.pictq.peek_last().as_mut().unwrap(); //Fixme
|
||||||
|
let mut sp = self.video_state.subpq.peek();
|
||||||
|
let mut rect: Rect2 = Rect2::default(); //Fixme
|
||||||
|
|
||||||
|
// if self.subtitle_st.is_some() {
|
||||||
|
// if let Some(sp) = sp {
|
||||||
|
// if vp.pts >= (*sp).pts + ((*sp).sub.unwrap().start_display_time as f32 / 1000.0) { // FIXME
|
||||||
|
// if !(*sp).uploaded {
|
||||||
|
// let mut pixels: [*mut u8; 4] = [std::ptr::null_mut(); 4];
|
||||||
|
// let mut pitch: [i32; 4] = [0; 4];
|
||||||
|
// let mut i: i32;
|
||||||
|
// if sp.width == 0 || sp.height == 0 {
|
||||||
|
// sp.width = vp.width;
|
||||||
|
// sp.height = vp.height;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // if (realloc_texture(&vstate->sub_texture, SDL_PIXELFORMAT_ARGB8888, sp->width, sp->height, SDL_BLENDMODE_BLEND, 1) < 0)
|
||||||
|
// // return;
|
||||||
|
//
|
||||||
|
// for i in 0..sp.sub.unwrap().num_rects { // FIXME
|
||||||
|
// let sub_rect: *mut AVSubtitleRect = sp.sub.unwrap().rects[i as usize]; // FIXME
|
||||||
|
//
|
||||||
|
// unsafe {
|
||||||
|
//
|
||||||
|
// (*sub_rect).x = av_clip((*sub_rect).x, 0, sp.width);
|
||||||
|
// (*sub_rect).y = av_clip((*sub_rect).y, 0, sp.height);
|
||||||
|
// (*sub_rect).w = av_clip((*sub_rect).w, 0, sp.width - (*sub_rect).x);
|
||||||
|
// (*sub_rect).h = av_clip((*sub_rect).h, 0, sp.height - (*sub_rect).y);
|
||||||
|
//
|
||||||
|
// self.sub_convert_ctx = sws_getCachedContext(
|
||||||
|
// self.sub_convert_ctx,
|
||||||
|
// (*sub_rect).w,
|
||||||
|
// (*sub_rect).h,
|
||||||
|
// AVPixelFormat::AV_PIX_FMT_PAL8,
|
||||||
|
// (*sub_rect).w,
|
||||||
|
// (*sub_rect).h,
|
||||||
|
// AVPixelFormat::AV_PIX_FMT_BGRA,
|
||||||
|
// 0,
|
||||||
|
// null_mut(),
|
||||||
|
// null_mut(),
|
||||||
|
// null_mut(),
|
||||||
|
// );
|
||||||
|
// if self.vstate.sub_convert_ctx.is_null() {
|
||||||
|
// av_log(
|
||||||
|
// null_mut(),
|
||||||
|
// AV_LOG_FATAL,
|
||||||
|
// "Cannot initialize the conversion context\n\0".as_ptr()
|
||||||
|
// );
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// println!("sws_scale!");
|
||||||
|
// // if !SDL_LockTexture(vstate->sub_texture, (SDL_Rect *)sub_rect, (void **)pixels, pitch) {
|
||||||
|
// // sws_scale(vstate->sub_convert_ctx, (const uint8_t * const *)sub_rect->data, sub_rect->linesize,
|
||||||
|
// // 0, sub_rect->h, pixels, pitch);
|
||||||
|
// // SDL_UnlockTexture(vstate->sub_texture);
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
// (*sp).uploaded = 1;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// sp = &None;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
calculate_display_rect(&mut rect, self.video_state.xleft as i64, self.video_state.ytop as i64, self.video_state.width as i64, self.video_state.height as i64, vp.width as i64, vp.height as i64, vp.sar);
|
||||||
|
if !vp.uploaded {
|
||||||
|
if self.upload_texture(&vp.frame, &self.video_state.img_convert_ctx).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vp.uploaded = true;
|
||||||
|
vp.flip_v = vp.frame.linesize[0] < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (sp) {
|
||||||
|
// int i;
|
||||||
|
// double xratio = (double) rect.size.x / (double) sp->width;
|
||||||
|
// double yratio = (double) rect.size.y / (double) sp->height;
|
||||||
|
// for (i = 0; i < sp->sub.num_rects; i++) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_texture(&mut self, frame: &AVFrame, img_convert_ctx: &SwsContext ) -> Result<(), RsmpegError> {
|
||||||
|
let mut ret: c_int = 0;
|
||||||
|
let mut godot_pix_fmt: c_int = 0;
|
||||||
|
// get_godot_pix_fmt((*frame).format, &mut godot_pix_fmt);
|
||||||
|
let godot_pix_fmt = Self::convert_px_format_godot(frame.format);
|
||||||
|
|
||||||
|
let mut init_texture: bool = false;
|
||||||
|
let use_conversion: bool = godot_pix_fmt == Format::FORMAT_MAX;
|
||||||
|
|
||||||
|
//if !self.vid_texture.as_ref().is_valid() { // FIXME ???
|
||||||
|
// self.vid_texture.as_ref().instance();
|
||||||
|
//}
|
||||||
|
|
||||||
|
if self.vid_texture.get_width() != (*frame).width.into() || self.vid_texture.get_height() != (*frame).height.into() {
|
||||||
|
init_texture = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if init_texture {
|
||||||
|
// self.vid_texture.create((*frame).width, (*frame).height, Image::FORMAT_RGBA8, Texture::FLAG_FILTER | Texture::FLAG_VIDEO_SURFACE); // FIXME
|
||||||
|
println!("ffplay::upload_texture, texture allocated: {}x{}", (*frame).width, (*frame).height);
|
||||||
|
|
||||||
|
let image_buffer_size: c_int = av_image_get_buffer_size(AVPixelFormat_AV_PIX_FMT_RGBA, (*frame).width, (*frame).height, 1);
|
||||||
|
self.image_buffer.resize(image_buffer_size as usize, 0);
|
||||||
|
if self.transfer_frame.is_some() {
|
||||||
|
self.transfer_frame = None
|
||||||
|
//av_freep(&mut self.transfer_frame.data[0]);
|
||||||
|
//av_frame_free(&mut transfer_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
if use_conversion {
|
||||||
|
self.transfer_frame = Some(Box::from(AVFrame::new()));
|
||||||
|
self.transfer_frame.unwrap().format = AVPixelFormat_AV_PIX_FMT_RGBA;
|
||||||
|
self.transfer_frame.unwrap().width = frame.width;
|
||||||
|
self.transfer_frame.unwrap().height = frame.height;
|
||||||
|
|
||||||
|
let image = AVImage::new(
|
||||||
|
self.transfer_frame.unwrap().format,
|
||||||
|
self.transfer_frame.unwrap().width,
|
||||||
|
self.transfer_frame.unwrap().height,
|
||||||
|
32 );
|
||||||
|
|
||||||
|
// ret = av_image_alloc(
|
||||||
|
// self.transfer_frame.unwrap().data,
|
||||||
|
// self.transfer_frame.unwrap().linesize,
|
||||||
|
// self.transfer_frame.unwrap().width,
|
||||||
|
// self.transfer_frame.unwrap().height,
|
||||||
|
// self.transfer_frame.unwrap().format,
|
||||||
|
// 32,
|
||||||
|
// );
|
||||||
|
if image.is_none(){
|
||||||
|
eprintln!("Could not allocate raw picture buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// println!(
|
||||||
|
// "converting pixel format from {} to {}",
|
||||||
|
// av_get_pix_fmt_name(AVPixelFormat((*frame).format)),
|
||||||
|
// av_get_pix_fmt_name(AVPixelFormat_AV_PIX_FMT_RGBA)
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// println!(
|
||||||
|
// "using direct pixel format: {}",
|
||||||
|
// av_get_pix_fmt_name(AVPixelFormat((*frame).format))
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
if !use_conversion {
|
||||||
|
let mut img = Image::new();
|
||||||
|
img.set_data((*frame).width.into(), (*frame).height.into(), false, Format::FORMAT_RGBA8, self.image_buffer.as_slice().into());
|
||||||
|
*self.vid_texture= *ImageTexture::create_from_image(img).unwrap();
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// // This should only happen if we are not using avfilter...
|
||||||
|
// *img_convert_ctx = sws_getCachedContext(
|
||||||
|
// *img_convert_ctx,
|
||||||
|
// (*frame).width,
|
||||||
|
// (*frame).height,
|
||||||
|
// AVPixelFormat((*frame).format),
|
||||||
|
// (*frame).width,
|
||||||
|
// (*frame).height,
|
||||||
|
// AVPixelFormat::AV_PIX_FMT_RGBA,
|
||||||
|
// self.sws_flags,
|
||||||
|
// null_mut(),
|
||||||
|
// null_mut(),
|
||||||
|
// null_mut(),
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// if !(*img_convert_ctx).is_null() {
|
||||||
|
// sws_scale(
|
||||||
|
// *img_convert_ctx,
|
||||||
|
// (*frame).data,
|
||||||
|
// (*frame).linesize,
|
||||||
|
// 0,
|
||||||
|
// (*frame).height,
|
||||||
|
// self.transfer_frame.data,
|
||||||
|
// self.transfer_frame.linesize,
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// let pixels: *mut u8 = self.image_buffer.write().ptr();
|
||||||
|
// let fdata: *mut *mut u8 = self.transfer_frame.data;
|
||||||
|
// let lines: c_int = self.transfer_frame.linesize[0];
|
||||||
|
// for y in 0..self.transfer_frame.height {
|
||||||
|
// let src_slice: &[u8] = unsafe {
|
||||||
|
// slice::from_raw_parts((*(fdata.offset(0))).offset((y * lines) as isize), self.transfer_frame.width as usize * 4)
|
||||||
|
// };
|
||||||
|
// let dest_slice: &mut [u8] = unsafe {
|
||||||
|
// slice::from_raw_parts_mut(pixels.offset((y * self.transfer_frame.width * 4) as isize), self.transfer_frame.width as usize * 4)
|
||||||
|
// };
|
||||||
|
// dest_slice.copy_from_slice(src_slice);
|
||||||
|
// }
|
||||||
|
// let img = Image::new((*frame).width, (*frame).height, 0, Image::FORMAT_RGBA8, self.image_buffer);
|
||||||
|
// self.vid_texture.set_data(img);
|
||||||
|
// } else {
|
||||||
|
// av_log(
|
||||||
|
// ptr::null_mut(),
|
||||||
|
// AV_LOG_FATAL,
|
||||||
|
// "Cannot initialize the conversion context\n\0".as_ptr() as *const i8,
|
||||||
|
// );
|
||||||
|
// ret = -1;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_px_format_godot(frmt: AVPixelFormat ) -> Format {
|
||||||
|
match frmt {
|
||||||
|
AVPixelFormat_AV_PIX_FMT_RGB8 => Format::FORMAT_RGB8,
|
||||||
|
AVPixelFormat_AV_PIX_FMT_RGBA => Format::FORMAT_RGBA8,
|
||||||
|
AVPixelFormat_AV_PIX_FMT_RGB24 => Format::FORMAT_RGB8,
|
||||||
|
AVPixelFormat_AV_PIX_FMT_RGB0 => Format::FORMAT_RGB8,
|
||||||
|
_ => Format::FORMAT_MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
21
src/playback/mutex.rs
Normal file
21
src/playback/mutex.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
|
|
||||||
|
pub trait New {
|
||||||
|
fn new() -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MutexQueue<T : New> {
|
||||||
|
pub mutex : Arc<Mutex<()>>,
|
||||||
|
pub cond: Arc<Condvar>,
|
||||||
|
pub data: T
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <T : New> MutexQueue<T> {
|
||||||
|
fn new() -> MutexQueue<T> {
|
||||||
|
MutexQueue {
|
||||||
|
mutex : Arc::new(Mutex::new(())),
|
||||||
|
cond: Arc::new(Condvar::new()),
|
||||||
|
data: T::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
194
src/playback/packet.rs
Normal file
194
src/playback/packet.rs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use rsmpeg::error::RsmpegError;
|
||||||
|
use rsmpeg::error::RsmpegError::AVError;
|
||||||
|
use std::sync::Condvar;
|
||||||
|
use rsmpeg::avcodec::AVPacket;
|
||||||
|
use crate::playback::mutex::{MutexQueue, New};
|
||||||
|
|
||||||
|
pub struct MyAVPacketList {
|
||||||
|
pub pkt : Box<AVPacket>,
|
||||||
|
pub next: Option<Box<MyAVPacketList>>,
|
||||||
|
pub serial: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MyAVPacketList {
|
||||||
|
fn default() -> Self {
|
||||||
|
MyAVPacketList {
|
||||||
|
pkt: Default::default(),
|
||||||
|
next: Default::default(),
|
||||||
|
serial: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MyAVPacketList {
|
||||||
|
fn new(pkt: AVPacket, serial: i64) -> Self {
|
||||||
|
MyAVPacketList {
|
||||||
|
pkt: Box::new(pkt),
|
||||||
|
next: None,
|
||||||
|
serial,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type MutexPacketQueue = MutexQueue<PacketQueue>;
|
||||||
|
|
||||||
|
pub struct PacketQueue {
|
||||||
|
pub queue : VecDeque<MyAVPacketList>,
|
||||||
|
pub size: i64,
|
||||||
|
pub duration: i64,
|
||||||
|
pub abort_request: bool,
|
||||||
|
pub serial: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl New for PacketQueue {
|
||||||
|
fn new() -> PacketQueue {
|
||||||
|
PacketQueue {
|
||||||
|
queue: VecDeque::new(),
|
||||||
|
size: 0,
|
||||||
|
duration: 0,
|
||||||
|
abort_request: false,
|
||||||
|
serial: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl PacketQueue {
|
||||||
|
/**
|
||||||
|
* Put the given AVPacket in the given PacketQueue.
|
||||||
|
*
|
||||||
|
* @param queue the queue to be used for the insert
|
||||||
|
* @param packet the AVPacket to be inserted in the queue
|
||||||
|
*
|
||||||
|
* @return 0 if the AVPacket is correctly inserted in the given PacketQueue.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn put(&mut self, cond: &Condvar, packet: AVPacket) -> Result<(), RsmpegError>
|
||||||
|
{
|
||||||
|
if self.abort_request
|
||||||
|
{
|
||||||
|
return Err(AVError(-1));
|
||||||
|
}
|
||||||
|
let mut pkt = MyAVPacketList::new(packet, self.serial);
|
||||||
|
let pkt_duration = pkt.pkt.duration;
|
||||||
|
let pkt_size : i64 = pkt.pkt.size.into();
|
||||||
|
//if (packet == &flush_pkt) // TODO
|
||||||
|
// queue->serial++;
|
||||||
|
|
||||||
|
self.queue.push_back(pkt);
|
||||||
|
// queue->size += pkt1->pkt.size + sizeof(*pkt1);
|
||||||
|
// queue->duration += pkt1->pkt.duration;
|
||||||
|
self.size += pkt_size; // TODO
|
||||||
|
self.duration += pkt_duration;
|
||||||
|
|
||||||
|
/* XXX: should duplicate packet data in DV case */
|
||||||
|
cond.notify_one();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MutexPacketQueue {
|
||||||
|
pub fn put_nullpacket(&mut self, stream_index: i64) -> Result<(), RsmpegError>
|
||||||
|
{
|
||||||
|
let mut pkt = AVPacket::new();
|
||||||
|
pkt.set_stream_index(stream_index as i32);
|
||||||
|
self.put(pkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts the given AVPacket in the given PacketQueue.
|
||||||
|
///
|
||||||
|
/// param : `q` the packet queue to be used for the insert
|
||||||
|
/// param : `packet` the AVPacket to be inserted in the queue
|
||||||
|
///
|
||||||
|
/// return : true if the AVPacket is correctly inserted in the given PacketQueue.
|
||||||
|
|
||||||
|
fn put(&mut self, packet: AVPacket) -> Result<(), RsmpegError>
|
||||||
|
{
|
||||||
|
//SDL_LockMutex(queue->mutex);
|
||||||
|
let _pq = self.mutex.lock().unwrap();
|
||||||
|
let ret = self.data.put(&self.cond, packet);
|
||||||
|
//SDL_UnlockMutex(queue->mutex);
|
||||||
|
// if (packet != &flush_pkt && ret < 0) // TODO
|
||||||
|
// av_packet_unref(packet);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Flush the given packet queue
|
||||||
|
fn flush(&mut self)
|
||||||
|
{
|
||||||
|
//SDL_LockMutex(q->mutex);
|
||||||
|
let _m = self.mutex.lock().unwrap();
|
||||||
|
let pq = &mut self.data;
|
||||||
|
pq.queue.clear();
|
||||||
|
pq.size = 0;
|
||||||
|
pq.duration = 0;
|
||||||
|
pq.serial += pq.serial;
|
||||||
|
//SDL_UnlockMutex(q->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&mut self)
|
||||||
|
{
|
||||||
|
self.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn abort(q: &mut MutexPacketQueue)
|
||||||
|
{
|
||||||
|
//SDL_LockMutex(q->mutex);
|
||||||
|
let _m = q.mutex.lock().unwrap();
|
||||||
|
let pq = &mut q.data;
|
||||||
|
pq.abort_request = true;
|
||||||
|
//SDL_CondSignal(q->cond);
|
||||||
|
q.cond.notify_one()
|
||||||
|
//SDL_UnlockMutex(q->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(q: &mut MutexPacketQueue)
|
||||||
|
{
|
||||||
|
//SDL_LockMutex(q->mutex);
|
||||||
|
let _m = q.mutex.lock().unwrap();
|
||||||
|
let pq = &mut q.data;
|
||||||
|
pq.abort_request = false;
|
||||||
|
pq.serial += 1;
|
||||||
|
//SDL_UnlockMutex(q->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get(&mut self, packet: &mut AVPacket, block : bool, serial: Option<&mut Box<i64>>) -> Result<(),RsmpegError>
|
||||||
|
{
|
||||||
|
// SDL_LockMutex(q->mutex);
|
||||||
|
let mut m = self.mutex.lock().unwrap();
|
||||||
|
let mut pq = &mut self.data;
|
||||||
|
let mut ret = Ok(());
|
||||||
|
loop {
|
||||||
|
if pq.abort_request {
|
||||||
|
ret = Err(AVError(-1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let pkt_opt = pq.queue.pop_front();
|
||||||
|
if let Some (pkt) = pkt_opt {
|
||||||
|
pq.size = pkt.pkt.as_ref().size.into();
|
||||||
|
*packet = *pkt.pkt;
|
||||||
|
if let Some(mut s) = serial {
|
||||||
|
*s = Box::new(pkt.serial)
|
||||||
|
}
|
||||||
|
ret = Ok(());
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else if block {
|
||||||
|
ret = Ok(());
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
m = self.cond.wait(m).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
5
src/stream.rs
Normal file
5
src/stream.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
pub struct Stream {
|
||||||
|
pub cur_time : u64
|
||||||
|
}
|
BIN
tests/assets/video1.mp4
Normal file
BIN
tests/assets/video1.mp4
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user