Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ thiserror = "2"
# Concurrent data structures
crossbeam = "0.8"

# Better RwLock implementation (no poisoning, better perf)
parking_lot = "0.12"

# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", default-features = false, features = [
Expand Down
7 changes: 6 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,12 @@ export declare class VideoFrame {
* Options can specify target format and rect for cropped copy.
*/
copyTo(destination: Uint8Array, options?: VideoFrameCopyToOptions | undefined | null): Promise<Array<PlaneLayout>>
/** Clone this VideoFrame */
/**
* Clone this VideoFrame
*
* This creates a new VideoFrame that shares the underlying pixel data with the original.
* Both frames will reference the same Arc<RwLock<Frame>>, so no pixel data is copied.
*/
clone(): VideoFrame
/**
* Close and release resources
Expand Down
51 changes: 44 additions & 7 deletions src/codec/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ use crate::ffi::{
ffframe_set_sample_rate,
ffframe_set_width,
},
avutil::{av_frame_alloc, av_frame_clone, av_frame_free, av_frame_get_buffer, av_frame_unref},
avutil::{
av_frame_alloc, av_frame_copy, av_frame_copy_props, av_frame_free, av_frame_get_buffer,
av_frame_unref,
},
};
use parking_lot::RwLock;
use std::ptr::NonNull;
use std::sync::Arc;

use super::CodecError;

Expand Down Expand Up @@ -759,12 +764,44 @@ impl Frame {
unsafe { av_frame_unref(self.as_mut_ptr()) }
}

/// Clone the frame (creates a new reference to the same data)
pub fn try_clone(&self) -> Result<Self, CodecError> {
let ptr = unsafe { av_frame_clone(self.as_ptr()) };
NonNull::new(ptr)
.map(|ptr| Self { ptr })
.ok_or(CodecError::AllocationFailed("frame clone"))
/// Deep clone frame data (clones all pixel/audio data, not just reference).
///
/// Use this when you need an independent clone that can be mutated
/// without affecting the original frame. This is more expensive than
/// Arc-based sharing but provides complete isolation.
pub fn deep_clone(&self) -> Result<Self, CodecError> {
// Create a new frame with the same dimensions/format
let mut new_frame = if self.is_video() {
Frame::new_video(self.width(), self.height(), self.format())?
} else if self.is_audio() {
Frame::new_audio(
self.nb_samples(),
self.channels(),
self.sample_rate(),
self.sample_format(),
)?
} else {
return Err(CodecError::InvalidConfig("Frame has no data".into()));
};

// Copy frame data (pixel/audio samples)
let ret = unsafe { av_frame_copy(new_frame.as_mut_ptr(), self.as_ptr()) };
ffi::check_error(ret)?;

// Copy frame properties (pts, duration, color info, etc.)
let ret = unsafe { av_frame_copy_props(new_frame.as_mut_ptr(), self.as_ptr()) };
ffi::check_error(ret)?;

Ok(new_frame)
}

/// Wrap this frame in Arc<RwLock<>> for shared access.
///
/// This is the preferred way to share a frame between multiple owners.
/// Use RwLock::read() for concurrent read access, RwLock::write() for
/// exclusive mutable access.
pub fn into_shared(self) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(self))
}
}

Expand Down
16 changes: 1 addition & 15 deletions src/codec/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::ffi::{
},
avcodec::{
av_new_packet, av_packet_alloc, av_packet_free, av_packet_get_side_data,
av_packet_new_side_data, av_packet_ref, av_packet_unref,
av_packet_new_side_data, av_packet_unref,
},
pkt_flag, pkt_side_data_type,
};
Expand Down Expand Up @@ -228,14 +228,6 @@ impl Packet {
unsafe { av_packet_unref(self.as_mut_ptr()) }
}

/// Create a new reference to this packet's data
pub fn try_clone(&self) -> Result<Self, CodecError> {
let new_pkt = Self::new()?;
let ret = unsafe { av_packet_ref(new_pkt.ptr.as_ptr(), self.as_ptr()) };
ffi::check_error(ret)?;
Ok(new_pkt)
}

/// Copy packet data to a new Vec
pub fn to_vec(&self) -> Vec<u8> {
self.as_slice().to_vec()
Expand Down Expand Up @@ -291,12 +283,6 @@ impl std::fmt::Debug for Packet {
}
}

impl Clone for Packet {
fn clone(&self) -> Self {
self.try_clone().expect("Failed to clone packet")
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
75 changes: 44 additions & 31 deletions src/webcodecs/audio_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::webcodecs::error::{
};
use napi::bindgen_prelude::*;
use napi_derive::napi;
use parking_lot::RwLock as ParkingLotRwLock;
use std::sync::{Arc, Mutex};

/// Audio sample format (WebCodecs spec)
Expand Down Expand Up @@ -235,7 +236,8 @@ pub struct AudioDataCopyToOptions {

/// Internal state for AudioData
struct AudioDataInner {
frame: Frame,
/// Shared reference to the frame data (via Arc for Rust-level sharing)
frame: Arc<ParkingLotRwLock<Frame>>,
format: AudioSampleFormat,
timestamp_us: i64,
closed: bool,
Expand Down Expand Up @@ -334,7 +336,7 @@ impl AudioData {
frame.set_pts(init.timestamp);

let inner = AudioDataInner {
frame,
frame: frame.into_shared(),
format: init.format,
timestamp_us: init.timestamp,
closed: false,
Expand All @@ -352,7 +354,7 @@ impl AudioData {
let format = AudioSampleFormat::from_av_format(av_format).unwrap_or(AudioSampleFormat::F32);

let inner = AudioDataInner {
frame,
frame: frame.into_shared(),
format,
timestamp_us,
closed: false,
Expand Down Expand Up @@ -440,7 +442,7 @@ impl AudioData {
.map_err(|_| Error::new(Status::GenericFailure, "Lock poisoned"))?;

match &*inner {
Some(i) => Ok(i.frame.sample_rate() as f64),
Some(i) => Ok(i.frame.read().sample_rate() as f64),
None => Ok(0.0), // Return 0 after close per W3C spec
}
}
Expand All @@ -455,7 +457,7 @@ impl AudioData {
.map_err(|_| Error::new(Status::GenericFailure, "Lock poisoned"))?;

match &*inner {
Some(i) => Ok(i.frame.nb_samples()),
Some(i) => Ok(i.frame.read().nb_samples()),
None => Ok(0), // Return 0 after close per W3C spec
}
}
Expand All @@ -470,7 +472,7 @@ impl AudioData {
.map_err(|_| Error::new(Status::GenericFailure, "Lock poisoned"))?;

match &*inner {
Some(i) => Ok(i.frame.channels()),
Some(i) => Ok(i.frame.read().channels()),
None => Ok(0), // Return 0 after close per W3C spec
}
}
Expand All @@ -486,8 +488,9 @@ impl AudioData {

match &*inner {
Some(i) => {
let frames = i.frame.nb_samples() as i64;
let sample_rate = i.frame.sample_rate() as i64;
let frame_guard = i.frame.read();
let frames = frame_guard.nb_samples() as i64;
let sample_rate = frame_guard.sample_rate() as i64;
if sample_rate > 0 {
Ok((frames * 1_000_000) / sample_rate)
} else {
Expand Down Expand Up @@ -530,7 +533,7 @@ impl AudioData {
match &*inner {
Some(i) => {
if i.format.is_planar() {
Ok(i.frame.channels())
Ok(i.frame.read().channels())
} else {
Ok(1)
}
Expand Down Expand Up @@ -559,9 +562,12 @@ impl AudioData {

let format = options.format.unwrap_or(inner.format);

// Acquire read lock on the shared frame
let frame_guard = inner.frame.read();

// Validate planeIndex (throws RangeError per W3C spec)
let num_planes = if format.is_planar() {
inner.frame.channels()
frame_guard.channels()
} else {
1
};
Expand All @@ -585,7 +591,7 @@ impl AudioData {
let frame_offset = options.frame_offset.unwrap_or(0);
let num_frames = options
.frame_count
.unwrap_or(inner.frame.nb_samples() - frame_offset);
.unwrap_or(frame_guard.nb_samples() - frame_offset);

let bytes_per_sample = format.bytes_per_sample() as u32;

Expand All @@ -594,7 +600,7 @@ impl AudioData {
Ok(num_frames * bytes_per_sample)
} else {
// Interleaved: all channels in one buffer
Ok(num_frames * inner.frame.channels() * bytes_per_sample)
Ok(num_frames * frame_guard.channels() * bytes_per_sample)
}
}

Expand All @@ -621,12 +627,16 @@ impl AudioData {
let format = options.format.unwrap_or(inner.format);
let plane_index = options.plane_index as usize;
let frame_offset = options.frame_offset.unwrap_or(0) as usize;

// Acquire read lock on the shared frame
let frame_guard = inner.frame.read();

let num_frames = options
.frame_count
.unwrap_or(inner.frame.nb_samples() - frame_offset as u32) as usize;
.unwrap_or(frame_guard.nb_samples() - frame_offset as u32) as usize;

let bytes_per_sample = format.bytes_per_sample();
let channels = inner.frame.channels() as usize;
let channels = frame_guard.channels() as usize;

// Validate planeIndex (throws RangeError per W3C spec)
let num_planes = if format.is_planar() { channels } else { 1 };
Expand Down Expand Up @@ -700,13 +710,13 @@ impl AudioData {
// Get source data
if inner.format.is_planar() {
// Source is planar too
if let Some(src) = inner.frame.audio_channel_data(plane_index) {
if let Some(src) = frame_guard.audio_channel_data(plane_index) {
let src_offset = frame_offset * bytes_per_sample;
dest_slice[..copy_size].copy_from_slice(&src[src_offset..src_offset + copy_size]);
}
} else {
// Source is interleaved, need to extract one channel
if let Some(src) = inner.frame.audio_channel_data(0) {
if let Some(src) = frame_guard.audio_channel_data(0) {
for i in 0..num_frames {
let src_offset = ((frame_offset + i) * channels + plane_index) * bytes_per_sample;
let dst_offset = i * bytes_per_sample;
Expand Down Expand Up @@ -737,7 +747,7 @@ impl AudioData {
// Source is planar, need to interleave
for i in 0..num_frames {
for ch in 0..channels {
if let Some(src) = inner.frame.audio_channel_data(ch) {
if let Some(src) = frame_guard.audio_channel_data(ch) {
let src_offset = (frame_offset + i) * bytes_per_sample;
let dst_offset = (i * channels + ch) * bytes_per_sample;
dest_slice[dst_offset..dst_offset + bytes_per_sample]
Expand All @@ -747,7 +757,7 @@ impl AudioData {
}
} else {
// Both interleaved
if let Some(src) = inner.frame.audio_channel_data(0) {
if let Some(src) = frame_guard.audio_channel_data(0) {
let src_offset = frame_offset * channels * bytes_per_sample;
dest_slice[..copy_size].copy_from_slice(&src[src_offset..src_offset + copy_size]);
}
Expand All @@ -770,12 +780,8 @@ impl AudioData {
None => return throw_invalid_state_error(&env, "AudioData is closed"),
};

let cloned_frame = inner.frame.try_clone().map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to clone frame: {}", e),
)
})?;
// Clone the Arc to share the underlying frame data (no pixel copy)
let cloned_frame = inner.frame.clone();

Ok(AudioData {
inner: Arc::new(Mutex::new(Some(AudioDataInner {
Expand Down Expand Up @@ -815,7 +821,10 @@ impl AudioData {
.map_err(|_| Error::new(Status::GenericFailure, "Lock poisoned"))?;

match &*inner {
Some(i) if !i.closed => Ok(f(&i.frame)),
Some(i) if !i.closed => {
let frame_guard = i.frame.read();
Ok(f(&frame_guard))
}
Some(_) => Err(invalid_state_error("AudioData is closed")),
None => Err(invalid_state_error("AudioData is closed")),
}
Expand All @@ -832,13 +841,16 @@ impl AudioData {
.as_ref()
.ok_or_else(|| invalid_state_error("AudioData is closed"))?;

let num_frames = inner.frame.nb_samples() as usize;
let channels = inner.frame.channels() as usize;
// Acquire read lock on the shared frame
let frame_guard = inner.frame.read();

let num_frames = frame_guard.nb_samples() as usize;
let channels = frame_guard.channels() as usize;
let bytes_per_sample = inner.format.bytes_per_sample();
let total_size = num_frames * channels * bytes_per_sample;

let mut buffer = vec![0u8; total_size];
inner.frame.copy_audio_to_buffer(&mut buffer).map_err(|e| {
frame_guard.copy_audio_to_buffer(&mut buffer).map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to copy audio: {}", e),
Expand All @@ -854,12 +866,13 @@ impl std::fmt::Debug for AudioData {
if let Ok(inner) = self.inner.lock()
&& let Some(ref i) = *inner
{
let frame_guard = i.frame.read();
return f
.debug_struct("AudioData")
.field("format", &i.format)
.field("sample_rate", &i.frame.sample_rate())
.field("number_of_frames", &i.frame.nb_samples())
.field("number_of_channels", &i.frame.channels())
.field("sample_rate", &frame_guard.sample_rate())
.field("number_of_frames", &frame_guard.nb_samples())
.field("number_of_channels", &frame_guard.channels())
.field("timestamp", &i.timestamp_us)
.finish();
}
Expand Down
6 changes: 3 additions & 3 deletions src/webcodecs/audio_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1523,11 +1523,11 @@ impl AudioEncoder {
}
}

// Get frame from AudioData
let frame = match data.with_frame(|f| f.try_clone()) {
// Get frame from AudioData (deep copy for encoder mutation)
let frame = match data.with_frame(|f| f.deep_clone()) {
Ok(Ok(f)) => f,
Ok(Err(e)) => {
Self::report_error(&mut inner, &format!("Failed to clone frame: {}", e));
Self::report_error(&mut inner, &format!("Failed to copy frame: {}", e));
return Ok(());
}
Err(e) => {
Expand Down
Loading
Loading