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
288 pub const fn bitrate(mut self, bitrate: u32) -> Self {
290 self.bitrate = bitrate;
291 self
292 }
293
294 pub const fn width(mut self, width: u32) -> Self {
296 self.width = width;
297 self
298 }
299
300 pub const fn height(mut self, height: u32) -> Self {
302 self.height = height;
303 self
304 }
305
306 pub const fn frame_rate(mut self, frame_rate: u32) -> Self {
308 self.frame_rate = frame_rate;
309 self
310 }
311
312 pub const fn pixel_aspect_ratio(mut self, par: (u32, u32)) -> Self {
314 self.pixel_aspect_ratio = par;
315 self
316 }
317 pub const fn disabled(mut self, disabled: bool) -> Self {
321 self.disabled = disabled;
322 self
323 }
324
325 fn build(self) -> Result<(VideoEncodingProperties, bool), VideoEncoderError> {
326 let properties = VideoEncodingProperties::new()?;
327 properties.SetSubtype(&self.sub_type.to_hstring())?;
328 properties.SetBitrate(self.bitrate)?;
329 properties.SetWidth(self.width)?;
330 properties.SetHeight(self.height)?;
331 properties.FrameRate()?.SetNumerator(self.frame_rate)?;
332 properties.FrameRate()?.SetDenominator(1)?;
333 properties.PixelAspectRatio()?.SetNumerator(self.pixel_aspect_ratio.0)?;
334 properties.PixelAspectRatio()?.SetDenominator(self.pixel_aspect_ratio.1)?;
335 Ok((properties, self.disabled))
336 }
337}
338
339pub struct AudioSettingsBuilder {
341 bitrate: u32,
342 channel_count: u32,
343 sample_rate: u32,
344 bit_per_sample: u32,
345 sub_type: AudioSettingsSubType,
346 disabled: bool,
347}
348
349impl AudioSettingsBuilder {
350 pub const fn new() -> Self {
360 Self {
361 bitrate: 192_000,
362 channel_count: 2,
363 sample_rate: 48_000,
364 bit_per_sample: 16,
365 sub_type: AudioSettingsSubType::AAC,
366 disabled: false,
367 }
368 }
369 pub const fn bitrate(mut self, bitrate: u32) -> Self {
371 self.bitrate = bitrate;
372 self
373 }
374 pub const fn channel_count(mut self, channel_count: u32) -> Self {
376 self.channel_count = channel_count;
377 self
378 }
379 pub const fn sample_rate(mut self, sample_rate: u32) -> Self {
381 self.sample_rate = sample_rate;
382 self
383 }
384 pub const fn bit_per_sample(mut self, bit_per_sample: u32) -> Self {
386 self.bit_per_sample = bit_per_sample;
387 self
388 }
389 pub const fn sub_type(mut self, sub_type: AudioSettingsSubType) -> Self {
391 self.sub_type = sub_type;
392 self
393 }
394 pub const fn disabled(mut self, disabled: bool) -> Self {
396 self.disabled = disabled;
397 self
398 }
399
400 fn build(self) -> Result<(AudioEncodingProperties, bool), VideoEncoderError> {
401 let properties = AudioEncodingProperties::new()?;
402 properties.SetBitrate(self.bitrate)?;
403 properties.SetChannelCount(self.channel_count)?;
404 properties.SetSampleRate(self.sample_rate)?;
405 properties.SetBitsPerSample(self.bit_per_sample)?;
406 properties.SetSubtype(&self.sub_type.to_hstring())?;
407 Ok((properties, self.disabled))
408 }
409}
410
411impl Default for AudioSettingsBuilder {
412 fn default() -> Self {
413 Self::new()
414 }
415}
416
417pub struct ContainerSettingsBuilder {
419 sub_type: ContainerSettingsSubType,
420}
421impl ContainerSettingsBuilder {
422 pub const fn new() -> Self {
426 Self { sub_type: ContainerSettingsSubType::MPEG4 }
427 }
428 pub const fn sub_type(mut self, sub_type: ContainerSettingsSubType) -> Self {
430 self.sub_type = sub_type;
431 self
432 }
433 fn build(self) -> Result<ContainerEncodingProperties, VideoEncoderError> {
434 let properties = ContainerEncodingProperties::new()?;
435 properties.SetSubtype(&self.sub_type.to_hstring())?;
436 Ok(properties)
437 }
438}
439impl Default for ContainerSettingsBuilder {
440 fn default() -> Self {
441 Self::new()
442 }
443}
444
445#[derive(Eq, PartialEq, Clone, Copy, Debug)]
447pub enum VideoSettingsSubType {
448 ARGB32,
450 BGRA8,
452 D16,
454 H263,
456 H264,
458 H264ES,
460 HEVC,
462 HEVCES,
464 IYUV,
466 L8,
468 L16,
470 MJPG,
472 NV12,
474 MPEG1,
476 MPEG2,
478 RGB24,
480 RGB32,
482 WMV3,
484 WVC1,
486 VP9,
488 YUY2,
490 YV12,
492}
493impl VideoSettingsSubType {
494 pub fn to_hstring(&self) -> HSTRING {
496 let s = match self {
497 Self::ARGB32 => "ARGB32",
498 Self::BGRA8 => "BGRA8",
499 Self::D16 => "D16",
500 Self::H263 => "H263",
501 Self::H264 => "H264",
502 Self::H264ES => "H264ES",
503 Self::HEVC => "HEVC",
504 Self::HEVCES => "HEVCES",
505 Self::IYUV => "IYUV",
506 Self::L8 => "L8",
507 Self::L16 => "L16",
508 Self::MJPG => "MJPG",
509 Self::NV12 => "NV12",
510 Self::MPEG1 => "MPEG1",
511 Self::MPEG2 => "MPEG2",
512 Self::RGB24 => "RGB24",
513 Self::RGB32 => "RGB32",
514 Self::WMV3 => "WMV3",
515 Self::WVC1 => "WVC1",
516 Self::VP9 => "VP9",
517 Self::YUY2 => "YUY2",
518 Self::YV12 => "YV12",
519 };
520 HSTRING::from(s)
521 }
522}
523
524#[derive(Eq, PartialEq, Clone, Copy, Debug)]
526pub enum AudioSettingsSubType {
527 AAC,
529 AC3,
531 AACADTS,
533 AACHDCP,
535 AC3SPDIF,
537 AC3HDCP,
539 ADTS,
541 ALAC,
543 AMRNB,
545 AWRWB,
547 DTS,
549 EAC3,
551 FLAC,
553 Float,
555 MP3,
557 MPEG,
559 OPUS,
561 PCM,
563 WMA8,
565 WMA9,
567 Vorbis,
569}
570impl AudioSettingsSubType {
571 pub fn to_hstring(&self) -> HSTRING {
573 let s = match self {
574 Self::AAC => "AAC",
575 Self::AC3 => "AC3",
576 Self::AACADTS => "AACADTS",
577 Self::AACHDCP => "AACHDCP",
578 Self::AC3SPDIF => "AC3SPDIF",
579 Self::AC3HDCP => "AC3HDCP",
580 Self::ADTS => "ADTS",
581 Self::ALAC => "ALAC",
582 Self::AMRNB => "AMRNB",
583 Self::AWRWB => "AWRWB",
584 Self::DTS => "DTS",
585 Self::EAC3 => "EAC3",
586 Self::FLAC => "FLAC",
587 Self::Float => "Float",
588 Self::MP3 => "MP3",
589 Self::MPEG => "MPEG",
590 Self::OPUS => "OPUS",
591 Self::PCM => "PCM",
592 Self::WMA8 => "WMA8",
593 Self::WMA9 => "WMA9",
594 Self::Vorbis => "Vorbis",
595 };
596 HSTRING::from(s)
597 }
598}
599
600#[derive(Eq, PartialEq, Clone, Copy, Debug)]
602pub enum ContainerSettingsSubType {
603 ASF,
605 MP3,
607 MPEG4,
609 AVI,
611 MPEG2,
613 WAVE,
615 AACADTS,
617 ADTS,
619 GP3,
621 AMR,
623 FLAC,
625}
626impl ContainerSettingsSubType {
627 pub fn to_hstring(&self) -> HSTRING {
630 match self {
631 Self::ASF => HSTRING::from("ASF"),
632 Self::MP3 => HSTRING::from("MP3"),
633 Self::MPEG4 => HSTRING::from("MPEG4"),
634 Self::AVI => HSTRING::from("AVI"),
635 Self::MPEG2 => HSTRING::from("MPEG2"),
636 Self::WAVE => HSTRING::from("WAVE"),
637 Self::AACADTS => HSTRING::from("AACADTS"),
638 Self::ADTS => HSTRING::from("ADTS"),
639 Self::GP3 => HSTRING::from("3GP"),
640 Self::AMR => HSTRING::from("AMR"),
641 Self::FLAC => HSTRING::from("FLAC"),
642 }
643 }
644}
645
646pub struct VideoEncoder {
680 first_timestamp: Option<TimeSpan>,
682
683 frame_sender: mpsc::Sender<Option<(VideoEncoderSource, TimeSpan)>>,
685 audio_sender: mpsc::Sender<Option<(AudioEncoderSource, TimeSpan)>>,
686
687 sample_requested: i64,
689 media_stream_source: MediaStreamSource,
690 starting: i64,
691
692 transcode_thread: Option<JoinHandle<Result<(), VideoEncoderError>>>,
694 error_notify: Arc<AtomicBool>,
695
696 is_video_disabled: bool,
698 is_audio_disabled: bool,
699
700 audio_sample_rate: u32, audio_block_align: u32, audio_samples_sent: u64, target_width: u32,
707 target_height: u32,
708 target_color_format: ColorFormat,
709
710 cached_surface: Option<CachedSurface>,
711}
712
713impl VideoEncoder {
714 fn create_cached_surface(
715 device: &ID3D11Device,
716 width: u32,
717 height: u32,
718 format: ColorFormat,
719 ) -> Result<CachedSurface, VideoEncoderError> {
720 let texture_desc = D3D11_TEXTURE2D_DESC {
721 Width: width,
722 Height: height,
723 MipLevels: 1,
724 ArraySize: 1,
725 Format: DXGI_FORMAT(format as i32),
726 SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
727 Usage: D3D11_USAGE_DEFAULT,
728 BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
729 CPUAccessFlags: 0,
730 MiscFlags: 0,
731 };
732
733 let mut texture = None;
734 unsafe {
735 device.CreateTexture2D(&texture_desc, None, Some(&mut texture))?;
736 }
737 let texture = texture.expect("CreateTexture2D returned None");
738
739 let mut render_target = None;
740 unsafe {
741 device.CreateRenderTargetView(&texture, None, Some(&mut render_target))?;
742 }
743 let render_target_view = render_target.map(SendDirectX::new);
744
745 let dxgi_surface: IDXGISurface = texture.cast()?;
746 let inspectable = unsafe { CreateDirect3D11SurfaceFromDXGISurface(&dxgi_surface)? };
747 let surface: IDirect3DSurface = inspectable.cast()?;
748
749 Ok(CachedSurface {
750 width,
751 height,
752 format,
753 texture: SendDirectX::new(texture),
754 surface: SendDirectX::new(surface),
755 render_target_view,
756 })
757 }
758
759 fn attach_sample_requested_handlers(
760 media_stream_source: &MediaStreamSource,
761 is_video_disabled: bool,
762 is_audio_disabled: bool,
763 frame_receiver: VideoFrameReceiver,
764 audio_receiver: AudioFrameReceiver,
765 audio_block_align: u32,
766 audio_sample_rate: u32,
767 ) -> Result<i64, VideoEncoderError> {
768 let token = media_stream_source.SampleRequested(&TypedEventHandler::<
769 MediaStreamSource,
770 MediaStreamSourceSampleRequestedEventArgs,
771 >::new(move |_, sample_requested| {
772 let sample_requested = sample_requested
773 .as_ref()
774 .expect("MediaStreamSource SampleRequested parameter was None. This should not happen.");
775
776 let request = sample_requested.Request()?;
777 let is_audio = request.StreamDescriptor()?.cast::<AudioStreamDescriptor>().is_ok();
778
779 let deferral = request.GetDeferral()?;
782
783 if is_audio {
784 if is_audio_disabled {
785 request.SetSample(None)?;
786 deferral.Complete()?;
787 } else {
788 let request_clone = request;
789 let audio_receiver = audio_receiver.clone();
790 ThreadPool::RunWithPriorityAndOptionsAsync(
791 &WorkItemHandler::new(move |_| {
792 let value = audio_receiver.lock().recv();
793 match value {
794 Ok(Some((source, timestamp))) => {
795 let sample = match source {
796 AudioEncoderSource::Buffer(bytes) => {
797 let buf = CryptographicBuffer::CreateFromByteArray(&bytes)?;
798 let sample = MediaStreamSample::CreateFromBuffer(&buf, timestamp)?;
799 let frames = (bytes.len() as u32) / audio_block_align;
802 let duration_ticks =
803 (frames as i64) * 10_000_000i64 / (audio_sample_rate as i64);
804 sample.SetDuration(TimeSpan { Duration: duration_ticks })?;
805 sample
806 }
807 };
808 request_clone.SetSample(&sample)?;
809 }
810 Ok(None) | Err(_) => {
811 request_clone.SetSample(None)?;
812 }
813 }
814 deferral.Complete()?;
815 Ok(())
816 }),
817 WorkItemPriority::Normal,
818 WorkItemOptions::None,
819 )?;
820 }
821 } else if is_video_disabled {
822 request.SetSample(None)?;
823 deferral.Complete()?;
824 } else {
825 let request_clone = request;
826 let frame_receiver = frame_receiver.clone();
827 ThreadPool::RunWithPriorityAndOptionsAsync(
828 &WorkItemHandler::new(move |_| {
829 let value = frame_receiver.lock().recv();
830 match value {
831 Ok(Some((source, timestamp))) => {
832 let sample = match source {
833 VideoEncoderSource::DirectX(surface) => {
834 MediaStreamSample::CreateFromDirect3D11Surface(&surface.0, timestamp)?
835 }
836 VideoEncoderSource::Buffer(bytes) => {
837 let buf = CryptographicBuffer::CreateFromByteArray(&bytes)?;
838 MediaStreamSample::CreateFromBuffer(&buf, timestamp)?
839 }
840 };
841 request_clone.SetSample(&sample)?;
842 }
843 Ok(None) | Err(_) => {
844 request_clone.SetSample(None)?;
845 }
846 }
847 deferral.Complete()?;
848 Ok(())
849 }),
850 WorkItemPriority::Normal,
851 WorkItemOptions::None,
852 )?;
853 }
854
855 Ok(())
856 }))?;
857 Ok(token)
858 }
859
860 #[inline]
862 pub fn new<P: AsRef<Path>>(
863 video_settings: VideoSettingsBuilder,
864 audio_settings: AudioSettingsBuilder,
865 container_settings: ContainerSettingsBuilder,
866 path: P,
867 ) -> Result<Self, VideoEncoderError> {
868 let path = path.as_ref();
869 let media_encoding_profile = MediaEncodingProfile::new()?;
870
871 let (video_encoding_properties_cfg, is_video_disabled) = video_settings.build()?;
872 media_encoding_profile.SetVideo(&video_encoding_properties_cfg)?;
873 let (audio_encoding_properties_cfg, is_audio_disabled) = audio_settings.build()?;
874 media_encoding_profile.SetAudio(&audio_encoding_properties_cfg)?;
875 let container_encoding_properties = container_settings.build()?;
876 media_encoding_profile.SetContainer(&container_encoding_properties)?;
877
878 let target_width = video_encoding_properties_cfg.Width()?;
879 let target_height = video_encoding_properties_cfg.Height()?;
880 let target_color_format = ColorFormat::Bgra8;
881
882 let video_encoding_properties = VideoEncodingProperties::CreateUncompressed(
883 &MediaEncodingSubtypes::Bgra8()?,
884 video_encoding_properties_cfg.Width()?,
885 video_encoding_properties_cfg.Height()?,
886 )?;
887 let video_stream_descriptor = VideoStreamDescriptor::Create(&video_encoding_properties)?;
888
889 let audio_desc_props = AudioEncodingProperties::CreatePcm(
891 audio_encoding_properties_cfg.SampleRate()?,
892 audio_encoding_properties_cfg.ChannelCount()?,
893 audio_encoding_properties_cfg.BitsPerSample()?,
894 )?;
895 let audio_stream_descriptor = AudioStreamDescriptor::Create(&audio_desc_props)?;
896
897 let audio_sr = audio_desc_props.SampleRate()?;
899 let audio_ch = audio_desc_props.ChannelCount()?;
900 let audio_bps = audio_desc_props.BitsPerSample()?;
901 let audio_block_align = (audio_bps / 8) * audio_ch;
902
903 let media_stream_source =
904 MediaStreamSource::CreateFromDescriptors(&video_stream_descriptor, &audio_stream_descriptor)?;
905 media_stream_source.SetBufferTime(Duration::from_millis(30).into())?;
907
908 let starting = media_stream_source.Starting(&TypedEventHandler::<
909 MediaStreamSource,
910 MediaStreamSourceStartingEventArgs,
911 >::new(move |_, stream_start| {
912 let stream_start =
913 stream_start.as_ref().expect("MediaStreamSource Starting parameter was None. This should not happen.");
914 stream_start.Request()?.SetActualStartPosition(TimeSpan { Duration: 0 })?;
915 Ok(())
916 }))?;
917
918 let (frame_sender, frame_receiver_raw) = mpsc::channel::<Option<(VideoEncoderSource, TimeSpan)>>();
919 let (audio_sender, audio_receiver_raw) = mpsc::channel::<Option<(AudioEncoderSource, TimeSpan)>>();
920
921 let frame_receiver = Arc::new(Mutex::new(frame_receiver_raw));
922 let audio_receiver = Arc::new(Mutex::new(audio_receiver_raw));
923
924 let sample_requested = Self::attach_sample_requested_handlers(
925 &media_stream_source,
926 is_video_disabled,
927 is_audio_disabled,
928 frame_receiver,
929 audio_receiver,
930 audio_block_align,
931 audio_sr,
932 )?;
933
934 let media_transcoder = MediaTranscoder::new()?;
935 media_transcoder.SetHardwareAccelerationEnabled(true)?;
936
937 File::create(path)?;
938 let path = fs::canonicalize(path)?.to_string_lossy()[4..].to_string();
939 let path = Path::new(&path);
940 let path = &HSTRING::from(path.as_os_str().to_os_string());
941
942 let file = StorageFile::GetFileFromPathAsync(path)?.join()?;
943 let media_stream_output = file.OpenAsync(FileAccessMode::ReadWrite)?.join()?;
944
945 let transcode = media_transcoder
946 .PrepareMediaStreamSourceTranscodeAsync(
947 &media_stream_source,
948 &media_stream_output,
949 &media_encoding_profile,
950 )?
951 .join()?;
952
953 let error_notify = Arc::new(AtomicBool::new(false));
954 let transcode_thread = thread::spawn({
955 let error_notify = error_notify.clone();
956 move || -> Result<(), VideoEncoderError> {
957 let result = transcode.TranscodeAsync();
958 if result.is_err() {
959 error_notify.store(true, atomic::Ordering::Relaxed);
960 }
961 result?.join()?;
962 drop(media_transcoder);
963 Ok(())
964 }
965 });
966
967 Ok(Self {
968 first_timestamp: None,
969 frame_sender,
970 audio_sender,
971 sample_requested,
972 media_stream_source,
973 starting,
974 transcode_thread: Some(transcode_thread),
975 error_notify,
976 is_video_disabled,
977 is_audio_disabled,
978 audio_sample_rate: audio_sr,
979 audio_block_align,
980 audio_samples_sent: 0,
981 target_width,
982 target_height,
983 target_color_format,
984 cached_surface: None,
985 })
986 }
987
988 #[inline]
1013 pub fn new_from_stream(
1014 video_settings: VideoSettingsBuilder,
1015 audio_settings: AudioSettingsBuilder,
1016 container_settings: ContainerSettingsBuilder,
1017 stream: IRandomAccessStream,
1018 ) -> Result<Self, VideoEncoderError> {
1019 let media_encoding_profile = MediaEncodingProfile::new()?;
1020
1021 let (video_encoding_properties_cfg, is_video_disabled) = video_settings.build()?;
1022 media_encoding_profile.SetVideo(&video_encoding_properties_cfg)?;
1023 let (audio_encoding_properties_cfg, is_audio_disabled) = audio_settings.build()?;
1024 media_encoding_profile.SetAudio(&audio_encoding_properties_cfg)?;
1025 let container_encoding_properties = container_settings.build()?;
1026 media_encoding_profile.SetContainer(&container_encoding_properties)?;
1027
1028 let target_width = video_encoding_properties_cfg.Width()?;
1029 let target_height = video_encoding_properties_cfg.Height()?;
1030 let target_color_format = ColorFormat::Bgra8;
1031
1032 let video_encoding_properties = VideoEncodingProperties::CreateUncompressed(
1033 &MediaEncodingSubtypes::Bgra8()?,
1034 video_encoding_properties_cfg.Width()?,
1035 video_encoding_properties_cfg.Height()?,
1036 )?;
1037 let video_stream_descriptor = VideoStreamDescriptor::Create(&video_encoding_properties)?;
1038
1039 let audio_desc_props = AudioEncodingProperties::CreatePcm(
1040 audio_encoding_properties_cfg.SampleRate()?,
1041 audio_encoding_properties_cfg.ChannelCount()?,
1042 audio_encoding_properties_cfg.BitsPerSample()?,
1043 )?;
1044 let audio_stream_descriptor = AudioStreamDescriptor::Create(&audio_desc_props)?;
1045
1046 let audio_sr = audio_desc_props.SampleRate()?;
1048 let audio_ch = audio_desc_props.ChannelCount()?;
1049 let audio_bps = audio_desc_props.BitsPerSample()?;
1050 let audio_block_align = (audio_bps / 8) * audio_ch;
1051
1052 let media_stream_source =
1053 MediaStreamSource::CreateFromDescriptors(&video_stream_descriptor, &audio_stream_descriptor)?;
1054 media_stream_source.SetBufferTime(Duration::from_millis(30).into())?;
1056
1057 let starting = media_stream_source.Starting(&TypedEventHandler::<
1058 MediaStreamSource,
1059 MediaStreamSourceStartingEventArgs,
1060 >::new(move |_, stream_start| {
1061 let stream_start =
1062 stream_start.as_ref().expect("MediaStreamSource Starting parameter was None. This should not happen.");
1063 stream_start.Request()?.SetActualStartPosition(TimeSpan { Duration: 0 })?;
1064 Ok(())
1065 }))?;
1066
1067 let (frame_sender, frame_receiver_raw) = mpsc::channel::<Option<(VideoEncoderSource, TimeSpan)>>();
1068 let (audio_sender, audio_receiver_raw) = mpsc::channel::<Option<(AudioEncoderSource, TimeSpan)>>();
1069
1070 let frame_receiver = Arc::new(Mutex::new(frame_receiver_raw));
1071 let audio_receiver = Arc::new(Mutex::new(audio_receiver_raw));
1072
1073 let sample_requested = Self::attach_sample_requested_handlers(
1074 &media_stream_source,
1075 is_video_disabled,
1076 is_audio_disabled,
1077 frame_receiver,
1078 audio_receiver,
1079 audio_block_align,
1080 audio_sr,
1081 )?;
1082
1083 let media_transcoder = MediaTranscoder::new()?;
1084 media_transcoder.SetHardwareAccelerationEnabled(true)?;
1085
1086 let transcode = media_transcoder
1087 .PrepareMediaStreamSourceTranscodeAsync(&media_stream_source, &stream, &media_encoding_profile)?
1088 .join()?;
1089
1090 let error_notify = Arc::new(AtomicBool::new(false));
1091 let transcode_thread = thread::spawn({
1092 let error_notify = error_notify.clone();
1093 move || -> Result<(), VideoEncoderError> {
1094 let result = transcode.TranscodeAsync();
1095 if result.is_err() {
1096 error_notify.store(true, atomic::Ordering::Relaxed);
1097 }
1098 result?.join()?;
1099 drop(media_transcoder);
1100 Ok(())
1101 }
1102 });
1103
1104 Ok(Self {
1105 first_timestamp: None,
1106 frame_sender,
1107 audio_sender,
1108 sample_requested,
1109 media_stream_source,
1110 starting,
1111 transcode_thread: Some(transcode_thread),
1112 error_notify,
1113 is_video_disabled,
1114 is_audio_disabled,
1115 audio_sample_rate: audio_sr,
1116 audio_block_align,
1117 audio_samples_sent: 0,
1118 target_width,
1119 target_height,
1120 target_color_format,
1121 cached_surface: None,
1122 })
1123 }
1124
1125 fn build_padded_surface(&mut self, frame: &Frame) -> Result<SendDirectX<IDirect3DSurface>, VideoEncoderError> {
1126 let frame_format = frame.color_format();
1127 let needs_recreate = self.cached_surface.as_ref().is_none_or(|cache| {
1128 cache.format != frame_format || cache.width != self.target_width || cache.height != self.target_height
1129 });
1130
1131 if needs_recreate {
1132 let surface =
1133 Self::create_cached_surface(frame.device(), self.target_width, self.target_height, frame_format)?;
1134 self.cached_surface = Some(surface);
1135 self.target_color_format = frame_format;
1136 }
1137
1138 let cache = self.cached_surface.as_mut().expect("cached_surface must be populated before use");
1139 let context = frame.device_context();
1140
1141 if let Some(rtv) = &cache.render_target_view {
1142 let clear_color = [0.0f32, 0.0, 0.0, 1.0];
1143 unsafe {
1144 context.ClearRenderTargetView(&rtv.0, &clear_color);
1145 }
1146 }
1147
1148 let copy_width = self.target_width.min(frame.width());
1149 let copy_height = self.target_height.min(frame.height());
1150
1151 if copy_width > 0 && copy_height > 0 {
1152 let source_box = D3D11_BOX { left: 0, top: 0, front: 0, right: copy_width, bottom: copy_height, back: 1 };
1153 unsafe {
1154 context.CopySubresourceRegion(
1155 &cache.texture.0,
1156 0,
1157 0,
1158 0,
1159 0,
1160 frame.as_raw_texture(),
1161 0,
1162 Some(&source_box),
1163 );
1164 }
1165 }
1166
1167 unsafe {
1168 context.Flush();
1169 }
1170
1171 Ok(SendDirectX::new(cache.surface.0.clone()))
1172 }
1173
1174 #[inline]
1176 pub fn send_frame(&mut self, frame: &Frame) -> Result<(), VideoEncoderError> {
1177 if self.is_video_disabled {
1178 return Err(VideoEncoderError::VideoDisabled);
1179 }
1180
1181 let timestamp = match self.first_timestamp {
1182 Some(t0) => TimeSpan { Duration: frame.timestamp()?.Duration - t0.Duration },
1183 None => {
1184 let ts = frame.timestamp()?;
1185 self.first_timestamp = Some(ts);
1186 TimeSpan { Duration: 0 }
1187 }
1188 };
1189
1190 let surface = if frame.width() == self.target_width && frame.height() == self.target_height {
1191 SendDirectX::new(frame.as_raw_surface().clone())
1192 } else {
1193 self.build_padded_surface(frame)?
1194 };
1195
1196 self.frame_sender.send(Some((VideoEncoderSource::DirectX(surface), timestamp)))?;
1197
1198 if self.error_notify.load(atomic::Ordering::Relaxed)
1199 && let Some(t) = self.transcode_thread.take()
1200 {
1201 t.join().expect("Failed to join transcode thread")?;
1202 }
1203
1204 Ok(())
1205 }
1206
1207 #[inline]
1210 pub fn send_frame_with_audio(&mut self, frame: &mut Frame, audio_buffer: &[u8]) -> Result<(), VideoEncoderError> {
1211 if self.is_video_disabled {
1212 return Err(VideoEncoderError::VideoDisabled);
1213 }
1214 if self.is_audio_disabled {
1215 return Err(VideoEncoderError::AudioDisabled);
1216 }
1217
1218 let video_ts = match self.first_timestamp {
1220 Some(t0) => TimeSpan { Duration: frame.timestamp()?.Duration - t0.Duration },
1221 None => {
1222 let ts = frame.timestamp()?;
1223 self.first_timestamp = Some(ts);
1224 TimeSpan { Duration: 0 }
1225 }
1226 };
1227
1228 let surface = if frame.width() == self.target_width && frame.height() == self.target_height {
1229 SendDirectX::new(frame.as_raw_surface().clone())
1230 } else {
1231 self.build_padded_surface(frame)?
1232 };
1233
1234 self.frame_sender.send(Some((VideoEncoderSource::DirectX(surface), video_ts)))?;
1235
1236 let frames_in_buf = (audio_buffer.len() as u32) / self.audio_block_align;
1238 let audio_ts_ticks = ((self.audio_samples_sent as i128) * 10_000_000i128) / (self.audio_sample_rate as i128);
1239 let audio_ts = TimeSpan { Duration: audio_ts_ticks as i64 };
1240
1241 self.audio_sender.send(Some((AudioEncoderSource::Buffer(audio_buffer.to_vec()), audio_ts)))?;
1242
1243 self.audio_samples_sent = self.audio_samples_sent.saturating_add(frames_in_buf as u64);
1245
1246 if self.error_notify.load(atomic::Ordering::Relaxed)
1247 && let Some(t) = self.transcode_thread.take()
1248 {
1249 t.join().expect("Failed to join transcode thread")?;
1250 }
1251
1252 Ok(())
1253 }
1254
1255 #[inline]
1258 pub fn send_frame_buffer(&mut self, buffer: &[u8], timestamp: i64) -> Result<(), VideoEncoderError> {
1259 if self.is_video_disabled {
1260 return Err(VideoEncoderError::VideoDisabled);
1261 }
1262
1263 let frame_timestamp = timestamp;
1264 let timestamp = match self.first_timestamp {
1265 Some(t0) => TimeSpan { Duration: frame_timestamp - t0.Duration },
1266 None => {
1267 self.first_timestamp = Some(TimeSpan { Duration: frame_timestamp });
1268 TimeSpan { Duration: 0 }
1269 }
1270 };
1271
1272 self.frame_sender.send(Some((VideoEncoderSource::Buffer(buffer.to_vec()), timestamp)))?;
1273
1274 if self.error_notify.load(atomic::Ordering::Relaxed)
1275 && let Some(t) = self.transcode_thread.take()
1276 {
1277 t.join().expect("Failed to join transcode thread")?;
1278 }
1279
1280 Ok(())
1281 }
1282
1283 #[inline]
1286 pub fn send_audio_buffer(
1287 &mut self,
1288 buffer: &[u8],
1289 _timestamp: i64, ) -> Result<(), VideoEncoderError> {
1291 if self.is_audio_disabled {
1292 return Err(VideoEncoderError::AudioDisabled);
1293 }
1294
1295 let frames_in_buf = (buffer.len() as u32) / self.audio_block_align;
1296 let audio_ts_ticks = ((self.audio_samples_sent as i128) * 10_000_000i128) / (self.audio_sample_rate as i128);
1297 let timestamp = TimeSpan { Duration: audio_ts_ticks as i64 };
1298
1299 self.audio_sender.send(Some((AudioEncoderSource::Buffer(buffer.to_vec()), timestamp)))?;
1300
1301 self.audio_samples_sent = self.audio_samples_sent.saturating_add(frames_in_buf as u64);
1302
1303 if self.error_notify.load(atomic::Ordering::Relaxed)
1304 && let Some(t) = self.transcode_thread.take()
1305 {
1306 t.join().expect("Failed to join transcode thread")?;
1307 }
1308
1309 Ok(())
1310 }
1311
1312 #[inline]
1314 pub fn finish(mut self) -> Result<(), VideoEncoderError> {
1315 let _ = self.frame_sender.send(None);
1317 let _ = self.audio_sender.send(None);
1318
1319 {
1322 let (dummy_tx_v, _dummy_rx_v) = mpsc::channel::<Option<(VideoEncoderSource, TimeSpan)>>();
1323 let (dummy_tx_a, _dummy_rx_a) = mpsc::channel::<Option<(AudioEncoderSource, TimeSpan)>>();
1324
1325 let old_v = std::mem::replace(&mut self.frame_sender, dummy_tx_v);
1326 let old_a = std::mem::replace(&mut self.audio_sender, dummy_tx_a);
1327 drop(old_v);
1328 drop(old_a);
1329 }
1330
1331 if let Some(transcode_thread) = self.transcode_thread.take() {
1333 transcode_thread.join().expect("Failed to join transcode thread")?;
1334 }
1335
1336 self.media_stream_source.RemoveStarting(self.starting)?;
1338 self.media_stream_source.RemoveSampleRequested(self.sample_requested)?;
1339
1340 Ok(())
1341 }
1342}
1343
1344impl Drop for VideoEncoder {
1345 #[inline]
1346 fn drop(&mut self) {
1347 let _ = self.frame_sender.send(None);
1349 let _ = self.audio_sender.send(None);
1350
1351 let (dummy_tx_v, _dummy_rx_v) = mpsc::channel::<Option<(VideoEncoderSource, TimeSpan)>>();
1353 let (dummy_tx_a, _dummy_rx_a) = mpsc::channel::<Option<(AudioEncoderSource, TimeSpan)>>();
1354
1355 let old_v = std::mem::replace(&mut self.frame_sender, dummy_tx_v);
1356 let old_a = std::mem::replace(&mut self.audio_sender, dummy_tx_a);
1357 drop(old_v);
1358 drop(old_a);
1359
1360 if let Some(transcode_thread) = self.transcode_thread.take() {
1361 let _ = transcode_thread.join();
1362 }
1363
1364 let _ = self.media_stream_source.RemoveStarting(self.starting);
1365 let _ = self.media_stream_source.RemoveSampleRequested(self.sample_requested);
1366 }
1367}
1368
1369#[allow(clippy::non_send_fields_in_send_ty)]
1370unsafe impl Send for VideoEncoder {}