1use std::fs::{self, File};
2use std::path::Path;
3use std::sync::atomic::{self, AtomicBool};
4use std::sync::{Arc, mpsc};
5use std::thread::{self, JoinHandle};
6use std::time::Duration;
7
8use parking_lot::Mutex;
9use windows::Foundation::{TimeSpan, TypedEventHandler};
10use windows::Graphics::DirectX::Direct3D11::IDirect3DSurface;
11use windows::Graphics::Imaging::{BitmapAlphaMode, BitmapEncoder, BitmapPixelFormat};
12use windows::Media::Core::{
13 AudioStreamDescriptor, MediaStreamSample, MediaStreamSource, MediaStreamSourceSampleRequestedEventArgs,
14 MediaStreamSourceStartingEventArgs, VideoStreamDescriptor,
15};
16use windows::Media::MediaProperties::{
17 AudioEncodingProperties, ContainerEncodingProperties, MediaEncodingProfile, MediaEncodingSubtypes,
18 VideoEncodingProperties,
19};
20use windows::Media::Transcoding::MediaTranscoder;
21use windows::Security::Cryptography::CryptographicBuffer;
22use windows::Storage::Streams::{DataReader, IRandomAccessStream, InMemoryRandomAccessStream};
23use windows::Storage::{FileAccessMode, StorageFile};
24use windows::System::Threading::{ThreadPool, WorkItemHandler, WorkItemOptions, WorkItemPriority};
25use windows::Win32::Graphics::Direct3D11::{
26 D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE, D3D11_BOX, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT,
27 ID3D11Device, ID3D11RenderTargetView, ID3D11Texture2D,
28};
29use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC};
30use windows::Win32::Graphics::Dxgi::IDXGISurface;
31use windows::Win32::System::WinRT::Direct3D11::CreateDirect3D11SurfaceFromDXGISurface;
32use windows::core::{HSTRING, Interface};
33
34use crate::d3d11::SendDirectX;
35use crate::frame::Frame;
36use crate::settings::ColorFormat;
37
38type VideoFrameReceiver = Arc<Mutex<mpsc::Receiver<Option<(VideoEncoderSource, TimeSpan)>>>>;
39type AudioFrameReceiver = Arc<Mutex<mpsc::Receiver<Option<(AudioEncoderSource, TimeSpan)>>>>;
40
41#[derive(thiserror::Error, Debug)]
42pub enum ImageEncoderError {
44 #[error("This color format is not supported for saving as an image")]
48 UnsupportedFormat,
49 #[error("I/O error: {0}")]
53 IoError(#[from] std::io::Error),
54 #[error("Integer conversion error: {0}")]
58 IntConversionError(#[from] std::num::TryFromIntError),
59 #[error("Windows API error: {0}")]
63 WindowsError(#[from] windows::core::Error),
64}
65
66#[derive(Eq, PartialEq, Clone, Copy, Debug)]
67pub enum ImageFormat {
69 Jpeg,
71 Png,
73 Gif,
75 Tiff,
77 Bmp,
79 JpegXr,
81}
82
83#[derive(Eq, PartialEq, Clone, Copy, Debug)]
85pub enum ImageEncoderPixelFormat {
86 Rgb16F,
88 Bgra8,
90 Rgba8,
92}
93
94pub struct ImageEncoder {
116 encoder: windows::core::GUID,
117 pixel_format: BitmapPixelFormat,
118}
119
120impl ImageEncoder {
121 #[inline]
123 pub fn new(format: ImageFormat, pixel_format: ImageEncoderPixelFormat) -> Result<Self, ImageEncoderError> {
124 let encoder = match format {
125 ImageFormat::Jpeg => BitmapEncoder::JpegEncoderId()?,
126 ImageFormat::Png => BitmapEncoder::PngEncoderId()?,
127 ImageFormat::Gif => BitmapEncoder::GifEncoderId()?,
128 ImageFormat::Tiff => BitmapEncoder::TiffEncoderId()?,
129 ImageFormat::Bmp => BitmapEncoder::BmpEncoderId()?,
130 ImageFormat::JpegXr => BitmapEncoder::JpegXREncoderId()?,
131 };
132
133 let pixel_format = match pixel_format {
134 ImageEncoderPixelFormat::Bgra8 => BitmapPixelFormat::Bgra8,
135 ImageEncoderPixelFormat::Rgba8 => BitmapPixelFormat::Rgba8,
136 ImageEncoderPixelFormat::Rgb16F => BitmapPixelFormat::Rgba16,
137 };
138
139 Ok(Self { pixel_format, encoder })
140 }
141
142 #[inline]
155 pub fn encode(&self, image_buffer: &[u8], width: u32, height: u32) -> Result<Vec<u8>, ImageEncoderError> {
156 let stream = InMemoryRandomAccessStream::new()?;
157
158 let encoder = BitmapEncoder::CreateAsync(self.encoder, &stream)?.join()?;
159
160 encoder.SetPixelData(
161 self.pixel_format,
162 BitmapAlphaMode::Premultiplied,
163 width,
164 height,
165 1.0,
166 1.0,
167 image_buffer,
168 )?;
169 encoder.FlushAsync()?.join()?;
170
171 let size = stream.Size()?;
172 let input = stream.GetInputStreamAt(0)?;
173 let reader = DataReader::CreateDataReader(&input)?;
174 reader.LoadAsync(size as u32)?.join()?;
175
176 let mut bytes = vec![0u8; size as usize];
177 reader.ReadBytes(&mut bytes)?;
178
179 Ok(bytes)
180 }
181}
182
183#[derive(thiserror::Error, Debug)]
184pub enum VideoEncoderError {
186 #[error("Windows API error: {0}")]
190 WindowsError(#[from] windows::core::Error),
191 #[error("Failed to send frame: {0}")]
195 FrameSendError(#[from] mpsc::SendError<Option<(VideoEncoderSource, TimeSpan)>>),
196 #[error("Failed to send audio: {0}")]
200 AudioSendError(#[from] mpsc::SendError<Option<(AudioEncoderSource, TimeSpan)>>),
201 #[error("Video encoding is disabled")]
203 VideoDisabled,
204 #[error("Audio encoding is disabled")]
206 AudioDisabled,
207 #[error("I/O error: {0}")]
211 IoError(#[from] std::io::Error),
212 #[error("Unsupported frame color format: {0:?}")]
216 UnsupportedFrameFormat(ColorFormat),
217}
218
219unsafe impl Send for VideoEncoderError {}
220unsafe impl Sync for VideoEncoderError {}
221
222pub enum VideoEncoderSource {
229 DirectX(SendDirectX<IDirect3DSurface>),
231 Buffer(Vec<u8>),
233}
234
235pub enum AudioEncoderSource {
237 Buffer(Vec<u8>),
239}
240
241struct CachedSurface {
242 width: u32,
243 height: u32,
244 format: ColorFormat,
245 texture: SendDirectX<ID3D11Texture2D>,
246 surface: SendDirectX<IDirect3DSurface>,
247 render_target_view: Option<SendDirectX<ID3D11RenderTargetView>>,
248}
249
250pub struct VideoSettingsBuilder {
252 sub_type: VideoSettingsSubType,
253 bitrate: u32,
254 width: u32,
255 height: u32,
256 frame_rate: u32,
257 pixel_aspect_ratio: (u32, u32),
258 disabled: bool,
259}
260
261impl VideoSettingsBuilder {
262 pub const fn new(width: u32, height: u32) -> Self {
271 Self {
272 bitrate: 15_000_000,
273 frame_rate: 60,
274 pixel_aspect_ratio: (1, 1),
275 sub_type: VideoSettingsSubType::HEVC,
276 width,
277 height,
278 disabled: false,
279 }
280 }
281
282 pub const fn sub_type(mut self, sub_type: VideoSettingsSubType) -> Self {
284 self.sub_type = sub_type;
285 self
286 }
287 pub const fn bitrate(mut self, bitrate: u32) -> Self {
289 self.bitrate = bitrate;
290 self
291 }
292 pub const fn width(mut self, width: u32) -> Self {
294 self.width = width;
295 self
296 }
297 pub const fn height(mut self, height: u32) -> Self {
299 self.height = height;
300 self
301 }
302 pub const fn frame_rate(mut self, frame_rate: u32) -> Self {
304 self.frame_rate = frame_rate;
305 self
306 }
307 pub const fn pixel_aspect_ratio(mut self, par: (u32, u32)) -> Self {
309 self.pixel_aspect_ratio = par;
310 self
311 }
312 pub const fn disabled(mut self, disabled: bool) -> Self {
316 self.disabled = disabled;
317 self
318 }
319
320 fn build(self) -> Result<(VideoEncodingProperties, bool), VideoEncoderError> {
321 let properties = VideoEncodingProperties::new()?;
322 properties.SetSubtype(&self.sub_type.to_hstring())?;
323 properties.SetBitrate(self.bitrate)?;
324 properties.SetWidth(self.width)?;
325 properties.SetHeight(self.height)?;
326 properties.FrameRate()?.SetNumerator(self.frame_rate)?;
327 properties.FrameRate()?.SetDenominator(1)?;
328 properties.PixelAspectRatio()?.SetNumerator(self.pixel_aspect_ratio.0)?;
329 properties.PixelAspectRatio()?.SetDenominator(self.pixel_aspect_ratio.1)?;
330 Ok((properties, self.disabled))
331 }
332}
333
334pub struct AudioSettingsBuilder {
336 bitrate: u32,
337 channel_count: u32,
338 sample_rate: u32,
339 bit_per_sample: u32,
340 sub_type: AudioSettingsSubType,
341 disabled: bool,
342}
343
344impl AudioSettingsBuilder {
345 pub const fn new() -> Self {
355 Self {
356 bitrate: 192_000,
357 channel_count: 2,
358 sample_rate: 48_000,
359 bit_per_sample: 16,
360 sub_type: AudioSettingsSubType::AAC,
361 disabled: false,
362 }
363 }
364 pub const fn bitrate(mut self, bitrate: u32) -> Self {
366 self.bitrate = bitrate;
367 self
368 }
369 pub const fn channel_count(mut self, channel_count: u32) -> Self {
371 self.channel_count = channel_count;
372 self
373 }
374 pub const fn sample_rate(mut self, sample_rate: u32) -> Self {
376 self.sample_rate = sample_rate;
377 self
378 }
379 pub const fn bit_per_sample(mut self, bit_per_sample: u32) -> Self {
381 self.bit_per_sample = bit_per_sample;
382 self
383 }
384 pub const fn sub_type(mut self, sub_type: AudioSettingsSubType) -> Self {
386 self.sub_type = sub_type;
387 self
388 }
389 pub const fn disabled(mut self, disabled: bool) -> Self {
391 self.disabled = disabled;
392 self
393 }
394
395 fn build(self) -> Result<(AudioEncodingProperties, bool), VideoEncoderError> {
396 let properties = AudioEncodingProperties::new()?;
397 properties.SetBitrate(self.bitrate)?;
398 properties.SetChannelCount(self.channel_count)?;
399 properties.SetSampleRate(self.sample_rate)?;
400 properties.SetBitsPerSample(self.bit_per_sample)?;
401 properties.SetSubtype(&self.sub_type.to_hstring())?;
402 Ok((properties, self.disabled))
403 }
404}
405
406impl Default for AudioSettingsBuilder {
407 fn default() -> Self {
408 Self::new()
409 }
410}
411
412pub struct ContainerSettingsBuilder {
414 sub_type: ContainerSettingsSubType,
415}
416impl ContainerSettingsBuilder {
417 pub const fn new() -> Self {
421 Self { sub_type: ContainerSettingsSubType::MPEG4 }
422 }
423 pub const fn sub_type(mut self, sub_type: ContainerSettingsSubType) -> Self {
425 self.sub_type = sub_type;
426 self
427 }
428 fn build(self) -> Result<ContainerEncodingProperties, VideoEncoderError> {
429 let properties = ContainerEncodingProperties::new()?;
430 properties.SetSubtype(&self.sub_type.to_hstring())?;
431 Ok(properties)
432 }
433}
434impl Default for ContainerSettingsBuilder {
435 fn default() -> Self {
436 Self::new()
437 }
438}
439
440#[derive(Eq, PartialEq, Clone, Copy, Debug)]
442pub enum VideoSettingsSubType {
443 ARGB32,
445 BGRA8,
447 D16,
449 H263,
451 H264,
453 H264ES,
455 HEVC,
457 HEVCES,
459 IYUV,
461 L8,
463 L16,
465 MJPG,
467 NV12,
469 MPEG1,
471 MPEG2,
473 RGB24,
475 RGB32,
477 WMV3,
479 WVC1,
481 VP9,
483 YUY2,
485 YV12,
487}
488impl VideoSettingsSubType {
489 pub fn to_hstring(&self) -> HSTRING {
491 let s = match self {
492 Self::ARGB32 => "ARGB32",
493 Self::BGRA8 => "BGRA8",
494 Self::D16 => "D16",
495 Self::H263 => "H263",
496 Self::H264 => "H264",
497 Self::H264ES => "H264ES",
498 Self::HEVC => "HEVC",
499 Self::HEVCES => "HEVCES",
500 Self::IYUV => "IYUV",
501 Self::L8 => "L8",
502 Self::L16 => "L16",
503 Self::MJPG => "MJPG",
504 Self::NV12 => "NV12",
505 Self::MPEG1 => "MPEG1",
506 Self::MPEG2 => "MPEG2",
507 Self::RGB24 => "RGB24",
508 Self::RGB32 => "RGB32",
509 Self::WMV3 => "WMV3",
510 Self::WVC1 => "WVC1",
511 Self::VP9 => "VP9",
512 Self::YUY2 => "YUY2",
513 Self::YV12 => "YV12",
514 };
515 HSTRING::from(s)
516 }
517}
518
519#[derive(Eq, PartialEq, Clone, Copy, Debug)]
521pub enum AudioSettingsSubType {
522 AAC,
524 AC3,
526 AACADTS,
528 AACHDCP,
530 AC3SPDIF,
532 AC3HDCP,
534 ADTS,
536 ALAC,
538 AMRNB,
540 AWRWB,
542 DTS,
544 EAC3,
546 FLAC,
548 Float,
550 MP3,
552 MPEG,
554 OPUS,
556 PCM,
558 WMA8,
560 WMA9,
562 Vorbis,
564}
565impl AudioSettingsSubType {
566 pub fn to_hstring(&self) -> HSTRING {
568 let s = match self {
569 Self::AAC => "AAC",
570 Self::AC3 => "AC3",
571 Self::AACADTS => "AACADTS",
572 Self::AACHDCP => "AACHDCP",
573 Self::AC3SPDIF => "AC3SPDIF",
574 Self::AC3HDCP => "AC3HDCP",
575 Self::ADTS => "ADTS",
576 Self::ALAC => "ALAC",
577 Self::AMRNB => "AMRNB",
578 Self::AWRWB => "AWRWB",
579 Self::DTS => "DTS",
580 Self::EAC3 => "EAC3",
581 Self::FLAC => "FLAC",
582 Self::Float => "Float",
583 Self::MP3 => "MP3",
584 Self::MPEG => "MPEG",
585 Self::OPUS => "OPUS",
586 Self::PCM => "PCM",
587 Self::WMA8 => "WMA8",
588 Self::WMA9 => "WMA9",
589 Self::Vorbis => "Vorbis",
590 };
591 HSTRING::from(s)
592 }
593}
594
595#[derive(Eq, PartialEq, Clone, Copy, Debug)]
597pub enum ContainerSettingsSubType {
598 ASF,
600 MP3,
602 MPEG4,
604 AVI,
606 MPEG2,
608 WAVE,
610 AACADTS,
612 ADTS,
614 GP3,
616 AMR,
618 FLAC,
620}
621impl ContainerSettingsSubType {
622 pub fn to_hstring(&self) -> HSTRING {
625 match self {
626 Self::ASF => HSTRING::from("ASF"),
627 Self::MP3 => HSTRING::from("MP3"),
628 Self::MPEG4 => HSTRING::from("MPEG4"),
629 Self::AVI => HSTRING::from("AVI"),
630 Self::MPEG2 => HSTRING::from("MPEG2"),
631 Self::WAVE => HSTRING::from("WAVE"),
632 Self::AACADTS => HSTRING::from("AACADTS"),
633 Self::ADTS => HSTRING::from("ADTS"),
634 Self::GP3 => HSTRING::from("3GP"),
635 Self::AMR => HSTRING::from("AMR"),
636 Self::FLAC => HSTRING::from("FLAC"),
637 }
638 }
639}
640
641pub struct VideoEncoder {
675 first_timestamp: Option<TimeSpan>,
677
678 frame_sender: mpsc::Sender<Option<(VideoEncoderSource, TimeSpan)>>,
680 audio_sender: mpsc::Sender<Option<(AudioEncoderSource, TimeSpan)>>,
681
682 sample_requested: i64,
684 media_stream_source: MediaStreamSource,
685 starting: i64,
686
687 transcode_thread: Option<JoinHandle<Result<(), VideoEncoderError>>>,
689 error_notify: Arc<AtomicBool>,
690
691 is_video_disabled: bool,
693 is_audio_disabled: bool,
694
695 audio_sample_rate: u32, audio_block_align: u32, audio_samples_sent: u64, target_width: u32,
702 target_height: u32,
703 target_color_format: ColorFormat,
704
705 cached_surface: Option<CachedSurface>,
706}
707
708impl VideoEncoder {
709 fn create_cached_surface(
710 device: &ID3D11Device,
711 width: u32,
712 height: u32,
713 format: ColorFormat,
714 ) -> Result<CachedSurface, VideoEncoderError> {
715 let texture_desc = D3D11_TEXTURE2D_DESC {
716 Width: width,
717 Height: height,
718 MipLevels: 1,
719 ArraySize: 1,
720 Format: DXGI_FORMAT(format as i32),
721 SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
722 Usage: D3D11_USAGE_DEFAULT,
723 BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
724 CPUAccessFlags: 0,
725 MiscFlags: 0,
726 };
727
728 let mut texture = None;
729 unsafe {
730 device.CreateTexture2D(&texture_desc, None, Some(&mut texture))?;
731 }
732 let texture = texture.expect("CreateTexture2D returned None");
733
734 let mut render_target = None;
735 unsafe {
736 device.CreateRenderTargetView(&texture, None, Some(&mut render_target))?;
737 }
738 let render_target_view = render_target.map(SendDirectX::new);
739
740 let dxgi_surface: IDXGISurface = texture.cast()?;
741 let inspectable = unsafe { CreateDirect3D11SurfaceFromDXGISurface(&dxgi_surface)? };
742 let surface: IDirect3DSurface = inspectable.cast()?;
743
744 Ok(CachedSurface {
745 width,
746 height,
747 format,
748 texture: SendDirectX::new(texture),
749 surface: SendDirectX::new(surface),
750 render_target_view,
751 })
752 }
753
754 fn attach_sample_requested_handlers(
755 media_stream_source: &MediaStreamSource,
756 is_video_disabled: bool,
757 is_audio_disabled: bool,
758 frame_receiver: VideoFrameReceiver,
759 audio_receiver: AudioFrameReceiver,
760 audio_block_align: u32,
761 audio_sample_rate: u32,
762 ) -> Result<i64, VideoEncoderError> {
763 let token = media_stream_source.SampleRequested(&TypedEventHandler::<
764 MediaStreamSource,
765 MediaStreamSourceSampleRequestedEventArgs,
766 >::new(move |_, sample_requested| {
767 let sample_requested = sample_requested
768 .as_ref()
769 .expect("MediaStreamSource SampleRequested parameter was None. This should not happen.");
770
771 let request = sample_requested.Request()?;
772 let is_audio = request.StreamDescriptor()?.cast::<AudioStreamDescriptor>().is_ok();
773
774 let deferral = request.GetDeferral()?;
777
778 if is_audio {
779 if is_audio_disabled {
780 request.SetSample(None)?;
781 deferral.Complete()?;
782 } else {
783 let request_clone = request;
784 let audio_receiver = audio_receiver.clone();
785 ThreadPool::RunWithPriorityAndOptionsAsync(
786 &WorkItemHandler::new(move |_| {
787 let value = audio_receiver.lock().recv();
788 match value {
789 Ok(Some((source, timestamp))) => {
790 let sample = match source {
791 AudioEncoderSource::Buffer(bytes) => {
792 let buf = CryptographicBuffer::CreateFromByteArray(&bytes)?;
793 let sample = MediaStreamSample::CreateFromBuffer(&buf, timestamp)?;
794 let frames = (bytes.len() as u32) / audio_block_align;
797 let duration_ticks =
798 (frames as i64) * 10_000_000i64 / (audio_sample_rate as i64);
799 sample.SetDuration(TimeSpan { Duration: duration_ticks })?;
800 sample
801 }
802 };
803 request_clone.SetSample(&sample)?;
804 }
805 Ok(None) | Err(_) => {
806 request_clone.SetSample(None)?;
807 }
808 }
809 deferral.Complete()?;
810 Ok(())
811 }),
812 WorkItemPriority::Normal,
813 WorkItemOptions::None,
814 )?;
815 }
816 } else if is_video_disabled {
817 request.SetSample(None)?;
818 deferral.Complete()?;
819 } else {
820 let request_clone = request;
821 let frame_receiver = frame_receiver.clone();
822 ThreadPool::RunWithPriorityAndOptionsAsync(
823 &WorkItemHandler::new(move |_| {
824 let value = frame_receiver.lock().recv();
825 match value {
826 Ok(Some((source, timestamp))) => {
827 let sample = match source {
828 VideoEncoderSource::DirectX(surface) => {
829 MediaStreamSample::CreateFromDirect3D11Surface(&surface.0, timestamp)?
830 }
831 VideoEncoderSource::Buffer(bytes) => {
832 let buf = CryptographicBuffer::CreateFromByteArray(&bytes)?;
833 MediaStreamSample::CreateFromBuffer(&buf, timestamp)?
834 }
835 };
836 request_clone.SetSample(&sample)?;
837 }
838 Ok(None) | Err(_) => {
839 request_clone.SetSample(None)?;
840 }
841 }
842 deferral.Complete()?;
843 Ok(())
844 }),
845 WorkItemPriority::Normal,
846 WorkItemOptions::None,
847 )?;
848 }
849
850 Ok(())
851 }))?;
852 Ok(token)
853 }
854
855 #[inline]
857 pub fn new<P: AsRef<Path>>(
858 video_settings: VideoSettingsBuilder,
859 audio_settings: AudioSettingsBuilder,
860 container_settings: ContainerSettingsBuilder,
861 path: P,
862 ) -> Result<Self, VideoEncoderError> {
863 let path = path.as_ref();
864 let media_encoding_profile = MediaEncodingProfile::new()?;
865
866 let (video_encoding_properties_cfg, is_video_disabled) = video_settings.build()?;
867 media_encoding_profile.SetVideo(&video_encoding_properties_cfg)?;
868 let (audio_encoding_properties_cfg, is_audio_disabled) = audio_settings.build()?;
869 media_encoding_profile.SetAudio(&audio_encoding_properties_cfg)?;
870 let container_encoding_properties = container_settings.build()?;
871 media_encoding_profile.SetContainer(&container_encoding_properties)?;
872
873 let target_width = video_encoding_properties_cfg.Width()?;
874 let target_height = video_encoding_properties_cfg.Height()?;
875 let target_color_format = ColorFormat::Bgra8;
876
877 let video_encoding_properties = VideoEncodingProperties::CreateUncompressed(
878 &MediaEncodingSubtypes::Bgra8()?,
879 video_encoding_properties_cfg.Width()?,
880 video_encoding_properties_cfg.Height()?,
881 )?;
882 let video_stream_descriptor = VideoStreamDescriptor::Create(&video_encoding_properties)?;
883
884 let audio_desc_props = AudioEncodingProperties::CreatePcm(
886 audio_encoding_properties_cfg.SampleRate()?,
887 audio_encoding_properties_cfg.ChannelCount()?,
888 audio_encoding_properties_cfg.BitsPerSample()?,
889 )?;
890 let audio_stream_descriptor = AudioStreamDescriptor::Create(&audio_desc_props)?;
891
892 let audio_sr = audio_desc_props.SampleRate()?;
894 let audio_ch = audio_desc_props.ChannelCount()?;
895 let audio_bps = audio_desc_props.BitsPerSample()?;
896 let audio_block_align = (audio_bps / 8) * audio_ch;
897
898 let media_stream_source =
899 MediaStreamSource::CreateFromDescriptors(&video_stream_descriptor, &audio_stream_descriptor)?;
900 media_stream_source.SetBufferTime(Duration::from_millis(30).into())?;
902
903 let starting = media_stream_source.Starting(&TypedEventHandler::<
904 MediaStreamSource,
905 MediaStreamSourceStartingEventArgs,
906 >::new(move |_, stream_start| {
907 let stream_start =
908 stream_start.as_ref().expect("MediaStreamSource Starting parameter was None. This should not happen.");
909 stream_start.Request()?.SetActualStartPosition(TimeSpan { Duration: 0 })?;
910 Ok(())
911 }))?;
912
913 let (frame_sender, frame_receiver_raw) = mpsc::channel::<Option<(VideoEncoderSource, TimeSpan)>>();
914 let (audio_sender, audio_receiver_raw) = mpsc::channel::<Option<(AudioEncoderSource, TimeSpan)>>();
915
916 let frame_receiver = Arc::new(Mutex::new(frame_receiver_raw));
917 let audio_receiver = Arc::new(Mutex::new(audio_receiver_raw));
918
919 let sample_requested = Self::attach_sample_requested_handlers(
920 &media_stream_source,
921 is_video_disabled,
922 is_audio_disabled,
923 frame_receiver,
924 audio_receiver,
925 audio_block_align,
926 audio_sr,
927 )?;
928
929 let media_transcoder = MediaTranscoder::new()?;
930 media_transcoder.SetHardwareAccelerationEnabled(true)?;
931
932 File::create(path)?;
933 let path = fs::canonicalize(path)?.to_string_lossy()[4..].to_string();
934 let path = Path::new(&path);
935 let path = &HSTRING::from(path.as_os_str().to_os_string());
936
937 let file = StorageFile::GetFileFromPathAsync(path)?.join()?;
938 let media_stream_output = file.OpenAsync(FileAccessMode::ReadWrite)?.join()?;
939
940 let transcode = media_transcoder
941 .PrepareMediaStreamSourceTranscodeAsync(
942 &media_stream_source,
943 &media_stream_output,
944 &media_encoding_profile,
945 )?
946 .join()?;
947
948 let error_notify = Arc::new(AtomicBool::new(false));
949 let transcode_thread = thread::spawn({
950 let error_notify = error_notify.clone();
951 move || -> Result<(), VideoEncoderError> {
952 let result = transcode.TranscodeAsync();
953 if result.is_err() {
954 error_notify.store(true, atomic::Ordering::Relaxed);
955 }
956 result?.join()?;
957 drop(media_transcoder);
958 Ok(())
959 }
960 });
961
962 Ok(Self {
963 first_timestamp: None,
964 frame_sender,
965 audio_sender,
966 sample_requested,
967 media_stream_source,
968 starting,
969 transcode_thread: Some(transcode_thread),
970 error_notify,
971 is_video_disabled,
972 is_audio_disabled,
973 audio_sample_rate: audio_sr,
974 audio_block_align,
975 audio_samples_sent: 0,
976 target_width,
977 target_height,
978 target_color_format,
979 cached_surface: None,
980 })
981 }
982
983 #[inline]
1008 pub fn new_from_stream(
1009 video_settings: VideoSettingsBuilder,
1010 audio_settings: AudioSettingsBuilder,
1011 container_settings: ContainerSettingsBuilder,
1012 stream: IRandomAccessStream,
1013 ) -> Result<Self, VideoEncoderError> {
1014 let media_encoding_profile = MediaEncodingProfile::new()?;
1015
1016 let (video_encoding_properties_cfg, is_video_disabled) = video_settings.build()?;
1017 media_encoding_profile.SetVideo(&video_encoding_properties_cfg)?;
1018 let (audio_encoding_properties_cfg, is_audio_disabled) = audio_settings.build()?;
1019 media_encoding_profile.SetAudio(&audio_encoding_properties_cfg)?;
1020 let container_encoding_properties = container_settings.build()?;
1021 media_encoding_profile.SetContainer(&container_encoding_properties)?;
1022
1023 let target_width = video_encoding_properties_cfg.Width()?;
1024 let target_height = video_encoding_properties_cfg.Height()?;
1025 let target_color_format = ColorFormat::Bgra8;
1026
1027 let video_encoding_properties = VideoEncodingProperties::CreateUncompressed(
1028 &MediaEncodingSubtypes::Bgra8()?,
1029 video_encoding_properties_cfg.Width()?,
1030 video_encoding_properties_cfg.Height()?,
1031 )?;
1032 let video_stream_descriptor = VideoStreamDescriptor::Create(&video_encoding_properties)?;
1033
1034 let audio_desc_props = AudioEncodingProperties::CreatePcm(
1035 audio_encoding_properties_cfg.SampleRate()?,
1036 audio_encoding_properties_cfg.ChannelCount()?,
1037 audio_encoding_properties_cfg.BitsPerSample()?,
1038 )?;
1039 let audio_stream_descriptor = AudioStreamDescriptor::Create(&audio_desc_props)?;
1040
1041 let audio_sr = audio_desc_props.SampleRate()?;
1043 let audio_ch = audio_desc_props.ChannelCount()?;
1044 let audio_bps = audio_desc_props.BitsPerSample()?;
1045 let audio_block_align = (audio_bps / 8) * audio_ch;
1046
1047 let media_stream_source =
1048 MediaStreamSource::CreateFromDescriptors(&video_stream_descriptor, &audio_stream_descriptor)?;
1049 media_stream_source.SetBufferTime(Duration::from_millis(30).into())?;
1051
1052 let starting = media_stream_source.Starting(&TypedEventHandler::<
1053 MediaStreamSource,
1054 MediaStreamSourceStartingEventArgs,
1055 >::new(move |_, stream_start| {
1056 let stream_start =
1057 stream_start.as_ref().expect("MediaStreamSource Starting parameter was None. This should not happen.");
1058 stream_start.Request()?.SetActualStartPosition(TimeSpan { Duration: 0 })?;
1059 Ok(())
1060 }))?;
1061
1062 let (frame_sender, frame_receiver_raw) = mpsc::channel::<Option<(VideoEncoderSource, TimeSpan)>>();
1063 let (audio_sender, audio_receiver_raw) = mpsc::channel::<Option<(AudioEncoderSource, TimeSpan)>>();
1064
1065 let frame_receiver = Arc::new(Mutex::new(frame_receiver_raw));
1066 let audio_receiver = Arc::new(Mutex::new(audio_receiver_raw));
1067
1068 let sample_requested = Self::attach_sample_requested_handlers(
1069 &media_stream_source,
1070 is_video_disabled,
1071 is_audio_disabled,
1072 frame_receiver,
1073 audio_receiver,
1074 audio_block_align,
1075 audio_sr,
1076 )?;
1077
1078 let media_transcoder = MediaTranscoder::new()?;
1079 media_transcoder.SetHardwareAccelerationEnabled(true)?;
1080
1081 let transcode = media_transcoder
1082 .PrepareMediaStreamSourceTranscodeAsync(&media_stream_source, &stream, &media_encoding_profile)?
1083 .join()?;
1084
1085 let error_notify = Arc::new(AtomicBool::new(false));
1086 let transcode_thread = thread::spawn({
1087 let error_notify = error_notify.clone();
1088 move || -> Result<(), VideoEncoderError> {
1089 let result = transcode.TranscodeAsync();
1090 if result.is_err() {
1091 error_notify.store(true, atomic::Ordering::Relaxed);
1092 }
1093 result?.join()?;
1094 drop(media_transcoder);
1095 Ok(())
1096 }
1097 });
1098
1099 Ok(Self {
1100 first_timestamp: None,
1101 frame_sender,
1102 audio_sender,
1103 sample_requested,
1104 media_stream_source,
1105 starting,
1106 transcode_thread: Some(transcode_thread),
1107 error_notify,
1108 is_video_disabled,
1109 is_audio_disabled,
1110 audio_sample_rate: audio_sr,
1111 audio_block_align,
1112 audio_samples_sent: 0,
1113 target_width,
1114 target_height,
1115 target_color_format,
1116 cached_surface: None,
1117 })
1118 }
1119
1120 fn build_padded_surface(&mut self, frame: &Frame) -> Result<SendDirectX<IDirect3DSurface>, VideoEncoderError> {
1121 let frame_format = frame.color_format();
1122 let needs_recreate = self.cached_surface.as_ref().is_none_or(|cache| {
1123 cache.format != frame_format || cache.width != self.target_width || cache.height != self.target_height
1124 });
1125
1126 if needs_recreate {
1127 let surface =
1128 Self::create_cached_surface(frame.device(), self.target_width, self.target_height, frame_format)?;
1129 self.cached_surface = Some(surface);
1130 self.target_color_format = frame_format;
1131 }
1132
1133 let cache = self.cached_surface.as_mut().expect("cached_surface must be populated before use");
1134 let context = frame.device_context();
1135
1136 if let Some(rtv) = &cache.render_target_view {
1137 let clear_color = [0.0f32, 0.0, 0.0, 1.0];
1138 unsafe {
1139 context.ClearRenderTargetView(&rtv.0, &clear_color);
1140 }
1141 }
1142
1143 let copy_width = self.target_width.min(frame.width());
1144 let copy_height = self.target_height.min(frame.height());
1145
1146 if copy_width > 0 && copy_height > 0 {
1147 let source_box = D3D11_BOX { left: 0, top: 0, front: 0, right: copy_width, bottom: copy_height, back: 1 };
1148 unsafe {
1149 context.CopySubresourceRegion(
1150 &cache.texture.0,
1151 0,
1152 0,
1153 0,
1154 0,
1155 frame.as_raw_texture(),
1156 0,
1157 Some(&source_box),
1158 );
1159 }
1160 }
1161
1162 unsafe {
1163 context.Flush();
1164 }
1165
1166 Ok(SendDirectX::new(cache.surface.0.clone()))
1167 }
1168
1169 #[inline]
1171 pub fn send_frame(&mut self, frame: &Frame) -> Result<(), VideoEncoderError> {
1172 if self.is_video_disabled {
1173 return Err(VideoEncoderError::VideoDisabled);
1174 }
1175
1176 let timestamp = match self.first_timestamp {
1177 Some(t0) => TimeSpan { Duration: frame.timestamp()?.Duration - t0.Duration },
1178 None => {
1179 let ts = frame.timestamp()?;
1180 self.first_timestamp = Some(ts);
1181 TimeSpan { Duration: 0 }
1182 }
1183 };
1184
1185 let surface = if frame.width() == self.target_width && frame.height() == self.target_height {
1186 SendDirectX::new(frame.as_raw_surface().clone())
1187 } else {
1188 self.build_padded_surface(frame)?
1189 };
1190
1191 self.frame_sender.send(Some((VideoEncoderSource::DirectX(surface), timestamp)))?;
1192
1193 if self.error_notify.load(atomic::Ordering::Relaxed)
1194 && let Some(t) = self.transcode_thread.take()
1195 {
1196 t.join().expect("Failed to join transcode thread")?;
1197 }
1198
1199 Ok(())
1200 }
1201
1202 #[inline]
1205 pub fn send_frame_with_audio(&mut self, frame: &mut Frame, audio_buffer: &[u8]) -> Result<(), VideoEncoderError> {
1206 if self.is_video_disabled {
1207 return Err(VideoEncoderError::VideoDisabled);
1208 }
1209 if self.is_audio_disabled {
1210 return Err(VideoEncoderError::AudioDisabled);
1211 }
1212
1213 let video_ts = match self.first_timestamp {
1215 Some(t0) => TimeSpan { Duration: frame.timestamp()?.Duration - t0.Duration },
1216 None => {
1217 let ts = frame.timestamp()?;
1218 self.first_timestamp = Some(ts);
1219 TimeSpan { Duration: 0 }
1220 }
1221 };
1222
1223 let surface = if frame.width() == self.target_width && frame.height() == self.target_height {
1224 SendDirectX::new(frame.as_raw_surface().clone())
1225 } else {
1226 self.build_padded_surface(frame)?
1227 };
1228
1229 self.frame_sender.send(Some((VideoEncoderSource::DirectX(surface), video_ts)))?;
1230
1231 let frames_in_buf = (audio_buffer.len() as u32) / self.audio_block_align;
1233 let audio_ts_ticks = ((self.audio_samples_sent as i128) * 10_000_000i128) / (self.audio_sample_rate as i128);
1234 let audio_ts = TimeSpan { Duration: audio_ts_ticks as i64 };
1235
1236 self.audio_sender.send(Some((AudioEncoderSource::Buffer(audio_buffer.to_vec()), audio_ts)))?;
1237
1238 self.audio_samples_sent = self.audio_samples_sent.saturating_add(frames_in_buf as u64);
1240
1241 if self.error_notify.load(atomic::Ordering::Relaxed)
1242 && let Some(t) = self.transcode_thread.take()
1243 {
1244 t.join().expect("Failed to join transcode thread")?;
1245 }
1246
1247 Ok(())
1248 }
1249
1250 #[inline]
1253 pub fn send_frame_buffer(&mut self, buffer: &[u8], timestamp: i64) -> Result<(), VideoEncoderError> {
1254 if self.is_video_disabled {
1255 return Err(VideoEncoderError::VideoDisabled);
1256 }
1257
1258 let frame_timestamp = timestamp;
1259 let timestamp = match self.first_timestamp {
1260 Some(t0) => TimeSpan { Duration: frame_timestamp - t0.Duration },
1261 None => {
1262 self.first_timestamp = Some(TimeSpan { Duration: frame_timestamp });
1263 TimeSpan { Duration: 0 }
1264 }
1265 };
1266
1267 self.frame_sender.send(Some((VideoEncoderSource::Buffer(buffer.to_vec()), timestamp)))?;
1268
1269 if self.error_notify.load(atomic::Ordering::Relaxed)
1270 && let Some(t) = self.transcode_thread.take()
1271 {
1272 t.join().expect("Failed to join transcode thread")?;
1273 }
1274
1275 Ok(())
1276 }
1277
1278 #[inline]
1281 pub fn send_audio_buffer(
1282 &mut self,
1283 buffer: &[u8],
1284 _timestamp: i64, ) -> Result<(), VideoEncoderError> {
1286 if self.is_audio_disabled {
1287 return Err(VideoEncoderError::AudioDisabled);
1288 }
1289
1290 let frames_in_buf = (buffer.len() as u32) / self.audio_block_align;
1291 let audio_ts_ticks = ((self.audio_samples_sent as i128) * 10_000_000i128) / (self.audio_sample_rate as i128);
1292 let timestamp = TimeSpan { Duration: audio_ts_ticks as i64 };
1293
1294 self.audio_sender.send(Some((AudioEncoderSource::Buffer(buffer.to_vec()), timestamp)))?;
1295
1296 self.audio_samples_sent = self.audio_samples_sent.saturating_add(frames_in_buf as u64);
1297
1298 if self.error_notify.load(atomic::Ordering::Relaxed)
1299 && let Some(t) = self.transcode_thread.take()
1300 {
1301 t.join().expect("Failed to join transcode thread")?;
1302 }
1303
1304 Ok(())
1305 }
1306
1307 #[inline]
1309 pub fn finish(mut self) -> Result<(), VideoEncoderError> {
1310 let _ = self.frame_sender.send(None);
1312 let _ = self.audio_sender.send(None);
1313
1314 {
1317 let (dummy_tx_v, _dummy_rx_v) = mpsc::channel::<Option<(VideoEncoderSource, TimeSpan)>>();
1318 let (dummy_tx_a, _dummy_rx_a) = mpsc::channel::<Option<(AudioEncoderSource, TimeSpan)>>();
1319
1320 let old_v = std::mem::replace(&mut self.frame_sender, dummy_tx_v);
1321 let old_a = std::mem::replace(&mut self.audio_sender, dummy_tx_a);
1322 drop(old_v);
1323 drop(old_a);
1324 }
1325
1326 if let Some(transcode_thread) = self.transcode_thread.take() {
1328 transcode_thread.join().expect("Failed to join transcode thread")?;
1329 }
1330
1331 self.media_stream_source.RemoveStarting(self.starting)?;
1333 self.media_stream_source.RemoveSampleRequested(self.sample_requested)?;
1334
1335 Ok(())
1336 }
1337}
1338
1339impl Drop for VideoEncoder {
1340 #[inline]
1341 fn drop(&mut self) {
1342 let _ = self.frame_sender.send(None);
1344 let _ = self.audio_sender.send(None);
1345
1346 let (dummy_tx_v, _dummy_rx_v) = mpsc::channel::<Option<(VideoEncoderSource, TimeSpan)>>();
1348 let (dummy_tx_a, _dummy_rx_a) = mpsc::channel::<Option<(AudioEncoderSource, TimeSpan)>>();
1349
1350 let old_v = std::mem::replace(&mut self.frame_sender, dummy_tx_v);
1351 let old_a = std::mem::replace(&mut self.audio_sender, dummy_tx_a);
1352 drop(old_v);
1353 drop(old_a);
1354
1355 if let Some(transcode_thread) = self.transcode_thread.take() {
1356 let _ = transcode_thread.join();
1357 }
1358
1359 let _ = self.media_stream_source.RemoveStarting(self.starting);
1360 let _ = self.media_stream_source.RemoveSampleRequested(self.sample_requested);
1361 }
1362}
1363
1364#[allow(clippy::non_send_fields_in_send_ty)]
1365unsafe impl Send for VideoEncoder {}