Skip to content

Commit

Permalink
First stage of channnelMask support.
Browse files Browse the repository at this point in the history
WMA encoder now supports multichannel audio and WMA 10 codec in VBR mode.
  • Loading branch information
gchudov committed Apr 14, 2013
1 parent dd4a2b6 commit 1a96b0e
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 46 deletions.
180 changes: 134 additions & 46 deletions CUETools.Codecs.WMA/WMAWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -146,7 +145,7 @@ internal IEnumerable<WMAFormatInfo> EnumerateFormatInfo(IWMProfileManager pProfi
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;
}
}
Expand All @@ -172,6 +171,7 @@ internal IEnumerable<WMAFormatInfo> EnumerateFormatInfo(IWMProfileManager pProfi
internal List<WMAFormatInfo> GetFormats(IWMProfileManager pProfileManager)
{
var formats = new List<WMAFormatInfo>(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 &&
Expand Down Expand Up @@ -210,49 +210,85 @@ internal class WMAFormatInfo
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
{
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
{
this.m_vbr = false;
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;

/// <summary>
/// From WAVEFORMATEXTENSIBLE
/// </summary>
[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
Expand Down Expand Up @@ -286,22 +322,58 @@ public WMAWriter(string path, WMAWriterSettings settings)
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)
{
Expand All @@ -320,7 +392,11 @@ public void Close()
{
try
{
m_pWriter.EndWriting();
if (this.writingBegan)
{
m_pWriter.EndWriting();
this.writingBegan = false;
}
}
finally
{
Expand All @@ -338,22 +414,34 @@ public void Close()
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);
Expand Down
58 changes: 58 additions & 0 deletions CUETools.Codecs/AudioPCMConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}

0 comments on commit 1a96b0e

Please sign in to comment.