Audio and MIDI library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
9.4 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Windows;
  6. using System.Windows.Input;
  7. using NAudio.MediaFoundation;
  8. using NAudio.Wave;
  9. using NAudioWpfDemo.ViewModel;
  10. using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
  11. using SaveFileDialog = Microsoft.Win32.SaveFileDialog;
  12. namespace NAudioWpfDemo.MediaFoundationEncode
  13. {
  14. internal class MediaFoundationEncodeViewModel : ViewModelBase, IDisposable
  15. {
  16. private readonly Dictionary<Guid, List<MediaTypeViewModel>> allMediaTypes;
  17. private EncoderViewModel selectedOutputFormat;
  18. private MediaTypeViewModel selectedMediaType;
  19. private string inputFile;
  20. private string inputFormat;
  21. private WaveFormat inputWaveFormat;
  22. public List<EncoderViewModel> OutputFormats { get; }
  23. public List<MediaTypeViewModel> SupportedMediaTypes { get; private set; }
  24. public ICommand EncodeCommand { get; }
  25. public ICommand SelectInputFileCommand { get; }
  26. public MediaFoundationEncodeViewModel()
  27. {
  28. MediaFoundationApi.Startup();
  29. allMediaTypes = new Dictionary<Guid, List<MediaTypeViewModel>>();
  30. SupportedMediaTypes = new List<MediaTypeViewModel>();
  31. EncodeCommand = new DelegateCommand(Encode);
  32. SelectInputFileCommand = new DelegateCommand(SelectInputFile);
  33. // TODO: fill this by asking the encoders what they can do
  34. OutputFormats = new List<EncoderViewModel>
  35. {
  36. new EncoderViewModel() { Name = "AAC", Guid = AudioSubtypes.MFAudioFormat_AAC, Extension = ".mp4" }, // Windows 8 can do a .aac extension as well
  37. new EncoderViewModel() { Name = "Windows Media Audio", Guid = AudioSubtypes.MFAudioFormat_WMAudioV8, Extension = ".wma" },
  38. new EncoderViewModel() { Name = "Windows Media Audio Professional", Guid = AudioSubtypes.MFAudioFormat_WMAudioV9, Extension = ".wma" },
  39. new EncoderViewModel() { Name = "MP3", Guid = AudioSubtypes.MFAudioFormat_MP3, Extension = ".mp3" },
  40. new EncoderViewModel() { Name = "Windows Media Audio Voice", Guid = AudioSubtypes.MFAudioFormat_MSP1, Extension = ".wma" },
  41. new EncoderViewModel() { Name = "Windows Media Audio Lossless", Guid = AudioSubtypes.MFAudioFormat_WMAudio_Lossless, Extension = ".wma" },
  42. new EncoderViewModel() { Name = "FLAC", Guid = AudioSubtypes.MFAudioFormat_FLAC, Extension = ".flac" },
  43. new EncoderViewModel() { Name = "Apple Lossless (ALAC)", Guid = AudioSubtypes.MFAudioFormat_ALAC, Extension = ".m4a" },
  44. new EncoderViewModel() { Name = "Fake for testing", Guid = Guid.NewGuid(), Extension = ".xyz" }
  45. };
  46. SelectedOutputFormat = OutputFormats[0];
  47. }
  48. private void SelectInputFile()
  49. {
  50. var ofd = new OpenFileDialog();
  51. ofd.Filter = "Audio files|*.mp3;*.wav;*.wma;*.aiff;*.aac";
  52. if (ofd.ShowDialog() == true)
  53. {
  54. if (TryOpenInputFile(ofd.FileName))
  55. {
  56. InputFile = ofd.FileName;
  57. SetMediaTypes();
  58. }
  59. }
  60. }
  61. private bool TryOpenInputFile(string file)
  62. {
  63. bool isValid = false;
  64. try
  65. {
  66. using (var reader = new MediaFoundationReader(file))
  67. {
  68. InputFormat = reader.WaveFormat.ToString();
  69. inputWaveFormat = reader.WaveFormat;
  70. }
  71. isValid = true;
  72. }
  73. catch (Exception e)
  74. {
  75. MessageBox.Show($"Not a supported input file ({e.Message})");
  76. }
  77. return isValid;
  78. }
  79. public string InputFile
  80. {
  81. get => inputFile;
  82. set
  83. {
  84. if (inputFile != value)
  85. {
  86. inputFile = value;
  87. OnPropertyChanged(nameof(InputFile));
  88. }
  89. }
  90. }
  91. public string InputFormat
  92. {
  93. get => inputFormat;
  94. set
  95. {
  96. if (inputFormat != value)
  97. {
  98. inputFormat = value;
  99. OnPropertyChanged(nameof(InputFormat));
  100. }
  101. }
  102. }
  103. public EncoderViewModel SelectedOutputFormat
  104. {
  105. get => selectedOutputFormat;
  106. set {
  107. if (selectedOutputFormat != value)
  108. {
  109. selectedOutputFormat = value;
  110. SetMediaTypes();
  111. OnPropertyChanged("SelectedOutputFormat");
  112. }
  113. }
  114. }
  115. private void SetMediaTypes()
  116. {
  117. if (!allMediaTypes.ContainsKey(SelectedOutputFormat.Guid))
  118. {
  119. TryGetSupportedMediaTypes();
  120. }
  121. FilterSupportedMediaTypes();
  122. OnPropertyChanged(nameof(SupportedMediaTypes));
  123. SelectedMediaType = SupportedMediaTypes.FirstOrDefault();
  124. }
  125. private void FilterSupportedMediaTypes()
  126. {
  127. //SupportedMediaTypes.Clear();
  128. SupportedMediaTypes = new List<MediaTypeViewModel>();
  129. if (inputWaveFormat == null)
  130. {
  131. SupportedMediaTypes.Add(new MediaTypeViewModel() {Name="Select an input file"});
  132. return;
  133. }
  134. SupportedMediaTypes = allMediaTypes[SelectedOutputFormat.Guid]
  135. .Where(m => m.MediaType != null)
  136. .Where(m => m.MediaType.SampleRate == inputWaveFormat.SampleRate)
  137. .Where(m => m.MediaType.ChannelCount == inputWaveFormat.Channels)
  138. .ToList();
  139. }
  140. private void TryGetSupportedMediaTypes()
  141. {
  142. var list = MediaFoundationEncoder.GetOutputMediaTypes(SelectedOutputFormat.Guid)
  143. .Select(mf => new MediaTypeViewModel(mf))
  144. .ToList();
  145. if (list.Count == 0)
  146. {
  147. list.Add(new MediaTypeViewModel() {Name = "Not Supported", Description = "No encoder found for this output type"});
  148. }
  149. allMediaTypes[SelectedOutputFormat.Guid] = list;
  150. }
  151. public MediaTypeViewModel SelectedMediaType
  152. {
  153. get => selectedMediaType;
  154. set
  155. {
  156. if (selectedMediaType != value)
  157. {
  158. selectedMediaType = value;
  159. OnPropertyChanged(nameof(SelectedMediaType));
  160. }
  161. }
  162. }
  163. private void Encode()
  164. {
  165. if (String.IsNullOrEmpty(InputFile) || !File.Exists(InputFile))
  166. {
  167. MessageBox.Show("Please select a valid input file to convert");
  168. return;
  169. }
  170. if (SelectedMediaType == null || SelectedMediaType.MediaType == null)
  171. {
  172. MessageBox.Show("Please select a valid output format");
  173. return;
  174. }
  175. using (var reader = new MediaFoundationReader(InputFile))
  176. {
  177. string outputUrl = SelectSaveFile(SelectedOutputFormat.Name, SelectedOutputFormat.Extension);
  178. if (outputUrl == null) return;
  179. using (var encoder = new MediaFoundationEncoder(SelectedMediaType.MediaType))
  180. {
  181. try
  182. {
  183. encoder.Encode(outputUrl, reader);
  184. }
  185. catch (Exception e)
  186. {
  187. MessageBox.Show(e.Message, "Failed to encode");
  188. }
  189. }
  190. }
  191. }
  192. private string SelectSaveFile(string formatName, string extension)
  193. {
  194. var sfd = new SaveFileDialog();
  195. sfd.FileName = Path.GetFileNameWithoutExtension(InputFile) + " converted" + extension;
  196. sfd.Filter = formatName + "|*" + extension;
  197. //return (sfd.ShowDialog() == true) ? new Uri(sfd.FileName).AbsoluteUri : null;
  198. return (sfd.ShowDialog() == true) ? sfd.FileName : null;
  199. }
  200. public void Dispose()
  201. {
  202. MediaFoundationApi.Shutdown();
  203. }
  204. }
  205. enum AacPayloadType
  206. {
  207. /// <summary>
  208. /// The stream contains raw_data_block elements only.
  209. /// </summary>
  210. RawData = 0,
  211. /// <summary>
  212. /// Audio Data Transport Stream (ADTS). The stream contains an adts_sequence, as defined by MPEG-2.
  213. /// </summary>
  214. Adts = 1,
  215. /// <summary>
  216. /// Audio Data Interchange Format (ADIF). The stream contains an adif_sequence, as defined by MPEG-2.
  217. /// </summary>
  218. Adif = 2,
  219. /// <summary>
  220. /// The stream contains an MPEG-4 audio transport stream with a synchronization layer (LOAS) and a multiplex layer (LATM).
  221. /// </summary>
  222. LoasLatm = 3
  223. }
  224. internal class EncoderViewModel : ViewModelBase
  225. {
  226. public string Name { get; set; }
  227. public Guid Guid { get; set; }
  228. public string Extension { get; set; }
  229. }
  230. }