From 40f6482077d39b38cd26d8f03b77d88efb4319c1 Mon Sep 17 00:00:00 2001 From: Grigory Chudov Date: Sun, 14 Apr 2013 13:54:58 -0400 Subject: [PATCH] First stage of channnelMask support. WMA encoder now supports multichannel audio and WMA 10 codec in VBR mode. --- CUETools.Codecs.WMA/WMAWriter.cs | 180 ++++++++++++++++++++++-------- CUETools.Codecs/AudioPCMConfig.cs | 58 ++++++++++ 2 files changed, 192 insertions(+), 46 deletions(-) diff --git a/CUETools.Codecs.WMA/WMAWriter.cs b/CUETools.Codecs.WMA/WMAWriter.cs index 08746c1..8fb25a6 100644 --- a/CUETools.Codecs.WMA/WMAWriter.cs +++ b/CUETools.Codecs.WMA/WMAWriter.cs @@ -12,13 +12,12 @@ namespace CUETools.Codecs.WMA { public abstract class WMAWriterSettings : AudioEncoderSettings { - public WMAWriterSettings(Guid subType) + public WMAWriterSettings() : base() { - this.m_subType = subType; } - private readonly Guid m_subType; + protected Guid m_subType; protected bool m_vbr = true; public IWMWriter GetWriter() @@ -146,7 +145,7 @@ namespace CUETools.Codecs.WMA subType = pMediaType.subType, pcm = new AudioPCMConfig(pWfx.wBitsPerSample, pWfx.nChannels, pWfx.nSamplesPerSec) }; - if (PCM == null || (pWfx.nChannels == PCM.ChannelCount && pWfx.wBitsPerSample == PCM.BitsPerSample && pWfx.nSamplesPerSec == PCM.SampleRate)) + if (PCM == null || (pWfx.nChannels == PCM.ChannelCount && pWfx.wBitsPerSample >= PCM.BitsPerSample && pWfx.nSamplesPerSec == PCM.SampleRate)) yield return info; } } @@ -172,6 +171,7 @@ namespace CUETools.Codecs.WMA internal List GetFormats(IWMProfileManager pProfileManager) { var formats = new List(this.EnumerateFormatInfo(pProfileManager)); + formats.RemoveAll(fmt => formats.Exists(fmt2 => fmt2.pcm.BitsPerSample < fmt.pcm.BitsPerSample && fmt2.pcm.ChannelCount == fmt.pcm.ChannelCount && fmt2.pcm.SampleRate == fmt.pcm.SampleRate)); if (formats.Count < 2) return formats; int prefixLen = 0, suffixLen = 0; while (formats.TrueForAll(s => s.formatName.Length > prefixLen && @@ -210,49 +210,85 @@ namespace CUETools.Codecs.WMA public class WMALWriterSettings : WMAWriterSettings { public WMALWriterSettings() - : base(MediaSubType.WMAudio_Lossless) + : base() { + this.m_subType = MediaSubType.WMAudio_Lossless; } } - public class WMAV8VBRWriterSettings : WMAWriterSettings + public class WMALossyWriterSettings : WMAWriterSettings { - public WMAV8VBRWriterSettings() - : base(MediaSubType.WMAudioV8) + public WMALossyWriterSettings() + : base() { - this.m_vbr = true; + this.m_subType = MediaSubType.WMAudioV9; } - } - public class WMAV8CBRWriterSettings : WMAWriterSettings - { - public WMAV8CBRWriterSettings() - : base(MediaSubType.WMAudioV8) + public enum Codec { - this.m_vbr = false; + WMA9, + WMA10Pro } - } - public class WMAV9CBRWriterSettings : WMAWriterSettings - { - public WMAV9CBRWriterSettings() - : base(MediaSubType.WMAudioV9) + [DefaultValue(Codec.WMA10Pro)] + public Codec Version { - this.m_vbr = false; + get + { + return this.m_subType == MediaSubType.WMAudioV9 ? Codec.WMA10Pro : Codec.WMA9; + } + set + { + this.m_subType = value == Codec.WMA10Pro ? MediaSubType.WMAudioV9 : MediaSubType.WMAudioV8; + } + } + + [DefaultValue(true)] + public bool VBR + { + get + { + return this.m_vbr; + } + set + { + this.m_vbr = value; + } } } [AudioEncoderClass("wma lossless", "wma", true, 1, typeof(WMALWriterSettings))] - [AudioEncoderClass("wma v8 vbr", "wma", false, 3, typeof(WMAV8VBRWriterSettings))] - [AudioEncoderClass("wma v9 cbr", "wma", false, 2, typeof(WMAV9CBRWriterSettings))] - [AudioEncoderClass("wma v8 cbr", "wma", false, 1, typeof(WMAV8CBRWriterSettings))] + [AudioEncoderClass("wma lossy", "wma", false, 1, typeof(WMALossyWriterSettings))] public class WMAWriter : IAudioDest { IWMWriter m_pWriter; private string outputPath; private bool closed = false; + private bool fileCreated = false; + private bool writingBegan = false; private long sampleCount, finalSampleCount; + const ushort WAVE_FORMAT_EXTENSIBLE = 0xFFFE; + const ushort WAVE_FORMAT_PCM = 1; + + /// + /// From WAVEFORMATEXTENSIBLE + /// + [StructLayout(LayoutKind.Sequential, Pack = 2)] + public struct WaveFormatExtensible + { + public short wFormatTag; /* format type */ + public short nChannels; /* number of channels (i.e. mono, stereo, etc.) */ + public int nSamplesPerSec; /* sample rate */ + public int nAvgBytesPerSec; /* for buffer estimation */ + public short nBlockAlign; /* block size of data */ + public short wBitsPerSample; + public short cbSize; + public short wValidBitsPerSample; + public int dwChannelMask; + public Guid SubFormat; + } + public long FinalSampleCount { set @@ -286,22 +322,58 @@ namespace CUETools.Codecs.WMA m_pWriter = settings.GetWriter(); int cInputs; m_pWriter.GetInputCount(out cInputs); - //for (int iInput = 0; iInput < cInputs; iInput++) - //{ - //} - //IWMInputMediaProps pInput; - //pWriter.GetInputProps(0, out pInput); - //pInput.GetMediaType(pType, ref cbType); - // fill (WAVEFORMATEX*)pType->pbFormat - // WAVEFORMATEXTENSIBLE if needed (dwChannelMask, wValidBitsPerSample) - // if (chg) - //pInput.SetMediaType(pType); - //pWriter.SetInputProps(0, pInput); - - //{ DWORD dwFormatCount = 0; hr = pWriter->GetInputFormatCount(0, &dwFormatCount); TEST(hr); TESTB(dwFormatCount > 0); } - //// GetInputFormatCount failed previously for multichannel formats, before ...mask = guessChannelMask() added. Leave this check j.i.c. - m_pWriter.SetOutputFilename(outputPath); - m_pWriter.BeginWriting(); + if (cInputs < 1) throw new InvalidOperationException(); + IWMInputMediaProps pInput; + m_pWriter.GetInputProps(0, out pInput); + try + { + int cbType = 0; + AMMediaType pMediaType = null; + pInput.GetMediaType(pMediaType, ref cbType); + pMediaType = new AMMediaType(); + pMediaType.formatSize = cbType - Marshal.SizeOf(typeof(AMMediaType)); + pInput.GetMediaType(pMediaType, ref cbType); + try + { + var wfe = new WaveFormatExtensible(); + wfe.nChannels = (short)m_settings.PCM.ChannelCount; + wfe.nSamplesPerSec = m_settings.PCM.SampleRate; + wfe.nBlockAlign = (short)m_settings.PCM.BlockAlign; + wfe.wBitsPerSample = (short)m_settings.PCM.BitsPerSample; + wfe.nAvgBytesPerSec = wfe.nSamplesPerSec * wfe.nBlockAlign; + if ((m_settings.PCM.BitsPerSample == 8 || m_settings.PCM.BitsPerSample == 16 || m_settings.PCM.BitsPerSample == 24) && + (m_settings.PCM.ChannelCount == 1 || m_settings.PCM.ChannelCount == 2)) + { + wfe.wFormatTag = unchecked((short)WAVE_FORMAT_PCM); + wfe.cbSize = 0; + } + else + { + wfe.wFormatTag = unchecked((short)WAVE_FORMAT_EXTENSIBLE); + wfe.cbSize = 22; + wfe.wValidBitsPerSample = wfe.wBitsPerSample; + wfe.nBlockAlign = (short)((wfe.wBitsPerSample / 8) * wfe.nChannels); + wfe.dwChannelMask = (int)m_settings.PCM.ChannelMask; + wfe.SubFormat = MediaSubType.PCM; + } + Marshal.FreeCoTaskMem(pMediaType.formatPtr); + pMediaType.formatPtr = IntPtr.Zero; + pMediaType.formatSize = 0; + pMediaType.formatPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(wfe)); + pMediaType.formatSize = Marshal.SizeOf(wfe); + Marshal.StructureToPtr(wfe, pMediaType.formatPtr, false); + pInput.SetMediaType(pMediaType); + m_pWriter.SetInputProps(0, pInput); + } + finally + { + WMUtils.FreeWMMediaType(pMediaType); + } + } + finally + { + Marshal.ReleaseComObject(pInput); + } } catch (Exception ex) { @@ -320,7 +392,11 @@ namespace CUETools.Codecs.WMA { try { - m_pWriter.EndWriting(); + if (this.writingBegan) + { + m_pWriter.EndWriting(); + this.writingBegan = false; + } } finally { @@ -338,22 +414,34 @@ namespace CUETools.Codecs.WMA public void Delete() { if (this.outputPath == null) - { throw new InvalidOperationException("This writer was not created from file."); - } - if (!closed) + if (!this.closed) { this.Close(); - File.Delete(this.outputPath); + + if (this.fileCreated) + { + File.Delete(this.outputPath); + this.fileCreated = false; + } } } public void Write(AudioBuffer buffer) { if (this.closed) - { throw new InvalidOperationException("Writer already closed."); + + if (!this.fileCreated) + { + this.m_pWriter.SetOutputFilename(outputPath); + this.fileCreated = true; + } + if (!this.writingBegan) + { + this.m_pWriter.BeginWriting(); + this.writingBegan = true; } buffer.Prepare(this); diff --git a/CUETools.Codecs/AudioPCMConfig.cs b/CUETools.Codecs/AudioPCMConfig.cs index b7791cc..d2a1d04 100644 --- a/CUETools.Codecs/AudioPCMConfig.cs +++ b/CUETools.Codecs/AudioPCMConfig.cs @@ -3,22 +3,80 @@ public class AudioPCMConfig { public static readonly AudioPCMConfig RedBook = new AudioPCMConfig(16, 2, 44100); + public enum SpeakerConfig + { + SPEAKER_FRONT_LEFT = 0x1, + SPEAKER_FRONT_RIGHT = 0x2, + SPEAKER_FRONT_CENTER = 0x4, + SPEAKER_LOW_FREQUENCY = 0x8, + SPEAKER_BACK_LEFT = 0x10, + SPEAKER_BACK_RIGHT = 0x20, + SPEAKER_FRONT_LEFT_OF_CENTER = 0x40, + SPEAKER_FRONT_RIGHT_OF_CENTER = 0x80, + SPEAKER_BACK_CENTER = 0x100, + SPEAKER_SIDE_LEFT = 0x200, + SPEAKER_SIDE_RIGHT = 0x400, + SPEAKER_TOP_CENTER = 0x800, + SPEAKER_TOP_FRONT_LEFT = 0x1000, + SPEAKER_TOP_FRONT_CENTER = 0x2000, + SPEAKER_TOP_FRONT_RIGHT = 0x4000, + SPEAKER_TOP_BACK_LEFT = 0x8000, + SPEAKER_TOP_BACK_CENTER = 0x10000, + SPEAKER_TOP_BACK_RIGHT = 0x20000, + + KSAUDIO_SPEAKER_MONO = (SPEAKER_FRONT_CENTER), + KSAUDIO_SPEAKER_STEREO = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT), + KSAUDIO_SPEAKER_QUAD = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT), + KSAUDIO_SPEAKER_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER), + KSAUDIO_SPEAKER_5POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT), + KSAUDIO_SPEAKER_5POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT), + KSAUDIO_SPEAKER_7POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER), + KSAUDIO_SPEAKER_7POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) + } private int _bitsPerSample; private int _channelCount; private int _sampleRate; + private SpeakerConfig _channelMask; public int BitsPerSample { get { return _bitsPerSample; } } public int ChannelCount { get { return _channelCount; } } public int SampleRate { get { return _sampleRate; } } public int BlockAlign { get { return _channelCount * ((_bitsPerSample + 7) / 8); } } + public SpeakerConfig ChannelMask { get { return _channelMask; } } public bool IsRedBook { get { return _bitsPerSample == 16 && _channelCount == 2 && _sampleRate == 44100; } } + public SpeakerConfig GetDefaultChannelMask() + { + switch (_channelCount) + { + case 1: + return SpeakerConfig.KSAUDIO_SPEAKER_MONO; + case 2: + return SpeakerConfig.KSAUDIO_SPEAKER_STEREO; + case 3: + return SpeakerConfig.KSAUDIO_SPEAKER_STEREO | SpeakerConfig.SPEAKER_LOW_FREQUENCY; + case 4: + return SpeakerConfig.KSAUDIO_SPEAKER_QUAD; + case 5: + //return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1 & ~SpeakerConfig.SPEAKER_LOW_FREQUENCY; + return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1_SURROUND & ~SpeakerConfig.SPEAKER_LOW_FREQUENCY; + case 6: + //return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1; + return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1_SURROUND; + case 7: + return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1_SURROUND | SpeakerConfig.SPEAKER_BACK_CENTER; + case 8: + return SpeakerConfig.KSAUDIO_SPEAKER_7POINT1_SURROUND; + } + return (SpeakerConfig)((1 << _channelCount) - 1); + } public AudioPCMConfig(int bitsPerSample, int channelCount, int sampleRate) { _bitsPerSample = bitsPerSample; _channelCount = channelCount; _sampleRate = sampleRate; + _channelMask = GetDefaultChannelMask(); } } }