mirror of
https://github.com/claunia/cuetools.net.git
synced 2025-12-16 18:14:25 +00:00
378 lines
14 KiB
C#
378 lines
14 KiB
C#
|
|
/**
|
||
|
|
* CUETools.WMA: WMA audio decoder
|
||
|
|
* Copyright (c) 20139 Grigory Chudov
|
||
|
|
*
|
||
|
|
* This library is free software; you can redistribute it and/or
|
||
|
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
|
* License as published by the Free Software Foundation; either
|
||
|
|
* version 2.1 of the License, or (at your option) any later version.
|
||
|
|
*
|
||
|
|
* This library is distributed in the hope that it will be useful,
|
||
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
|
* Lesser General Public License for more details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU Lesser General Public
|
||
|
|
* License along with this library; if not, write to the Free Software
|
||
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
|
*/
|
||
|
|
|
||
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Text;
|
||
|
|
using System.IO;
|
||
|
|
using WindowsMediaLib;
|
||
|
|
using WindowsMediaLib.Defs;
|
||
|
|
using System.Runtime.InteropServices;
|
||
|
|
|
||
|
|
namespace CUETools.Codecs.WMA
|
||
|
|
{
|
||
|
|
[AudioDecoderClass("builtin wma", "wma")]
|
||
|
|
public class WMAReader : IAudioSource
|
||
|
|
{
|
||
|
|
IWMSyncReader m_syncReader;
|
||
|
|
short m_wStreamNum = -1;
|
||
|
|
int m_dwAudioOutputNum = -1;
|
||
|
|
|
||
|
|
AudioPCMConfig pcm;
|
||
|
|
|
||
|
|
byte[] samplesBuffer;
|
||
|
|
int _samplesInBuffer, _samplesBufferOffset;
|
||
|
|
long _sampleCount = -1, _sampleOffset = 0;
|
||
|
|
|
||
|
|
string _path;
|
||
|
|
//Stream _IO;
|
||
|
|
|
||
|
|
public WMAReader(string path, Stream IO)
|
||
|
|
{
|
||
|
|
isValid(path);
|
||
|
|
bool pfIsProtected;
|
||
|
|
WMUtils.WMIsContentProtected(path, out pfIsProtected);
|
||
|
|
if (pfIsProtected)
|
||
|
|
throw new Exception("DRM present");
|
||
|
|
WMUtils.WMCreateSyncReader(IntPtr.Zero, Rights.None, out m_syncReader);
|
||
|
|
|
||
|
|
//m_syncReader.OpenStream()
|
||
|
|
m_syncReader.Open(path);
|
||
|
|
var pProfile = (m_syncReader as IWMProfile);
|
||
|
|
int dwStreamCount;
|
||
|
|
pProfile.GetStreamCount(out dwStreamCount);
|
||
|
|
for (int dwIndex = 0; dwIndex < dwStreamCount; dwIndex++)
|
||
|
|
{
|
||
|
|
IWMStreamConfig pConfig = null;
|
||
|
|
pProfile.GetStream(0, out pConfig);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
Guid guid;
|
||
|
|
pConfig.GetStreamType(out guid);
|
||
|
|
if (MediaType.Audio != guid)
|
||
|
|
continue;
|
||
|
|
short wStreamNum;
|
||
|
|
pConfig.GetStreamNumber(out wStreamNum);
|
||
|
|
int dwBitrate = -1;
|
||
|
|
pConfig.GetBitrate(out dwBitrate);
|
||
|
|
var pIWMMediaProps = pConfig as IWMMediaProps;
|
||
|
|
int cbType = 0;
|
||
|
|
pIWMMediaProps.GetMediaType(null, ref cbType);
|
||
|
|
var mt = new AMMediaType();
|
||
|
|
mt.formatSize = cbType;
|
||
|
|
pIWMMediaProps.GetMediaType(mt, ref cbType);
|
||
|
|
if (mt.formatType != FormatType.WaveEx)
|
||
|
|
continue;
|
||
|
|
if (mt.subType != MediaSubType.WMAudio_Lossless)
|
||
|
|
continue;
|
||
|
|
m_wStreamNum = wStreamNum;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
Marshal.ReleaseComObject(pConfig);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (m_wStreamNum == -1)
|
||
|
|
throw new Exception("No WMA lossless streams found");
|
||
|
|
|
||
|
|
m_syncReader.SetReadStreamSamples(m_wStreamNum, false);
|
||
|
|
bool pfCompressed;
|
||
|
|
m_syncReader.GetReadStreamSamples(m_wStreamNum, out pfCompressed);
|
||
|
|
if (pfCompressed)
|
||
|
|
throw new Exception("doesn't decompress");
|
||
|
|
m_syncReader.GetOutputNumberForStream(m_wStreamNum, out m_dwAudioOutputNum);
|
||
|
|
IWMOutputMediaProps pProps;
|
||
|
|
m_syncReader.GetOutputProps(m_dwAudioOutputNum, out pProps);
|
||
|
|
var m_pWfx = new WaveFormatEx();
|
||
|
|
try
|
||
|
|
{
|
||
|
|
StringBuilder sName = null;
|
||
|
|
AMMediaType pMediaType = null;
|
||
|
|
int cbType = 0;
|
||
|
|
|
||
|
|
cbType = 0;
|
||
|
|
pMediaType = null;
|
||
|
|
pProps.GetMediaType(pMediaType, ref cbType);
|
||
|
|
|
||
|
|
// Get the name of the output we'll be using
|
||
|
|
sName = null;
|
||
|
|
short iName = 0;
|
||
|
|
pProps.GetConnectionName(sName, ref iName);
|
||
|
|
|
||
|
|
sName = new StringBuilder(iName);
|
||
|
|
pProps.GetConnectionName(sName, ref iName);
|
||
|
|
|
||
|
|
pMediaType = new AMMediaType();
|
||
|
|
pMediaType.formatSize = cbType - Marshal.SizeOf(typeof(AMMediaType));
|
||
|
|
|
||
|
|
//
|
||
|
|
// Get the value for MediaType
|
||
|
|
//
|
||
|
|
pProps.GetMediaType(pMediaType, ref cbType);
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
if (MediaType.Audio != pMediaType.majorType)
|
||
|
|
throw new Exception("not Audio");
|
||
|
|
if (FormatType.WaveEx != pMediaType.formatType)
|
||
|
|
throw new Exception("not WaveEx");
|
||
|
|
Marshal.PtrToStructure(pMediaType.formatPtr, m_pWfx);
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
WMUtils.FreeWMMediaType(pMediaType);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
Marshal.ReleaseComObject(pProps);
|
||
|
|
}
|
||
|
|
|
||
|
|
//try
|
||
|
|
//{
|
||
|
|
// AttrDataType wmtType;
|
||
|
|
// short cbLength = 0;
|
||
|
|
// short wAnyStream = 0;
|
||
|
|
// var pHeaderInfo = m_syncReader as IWMHeaderInfo;
|
||
|
|
// pHeaderInfo.GetAttributeByName(ref wAnyStream, Constants.g_wszWMDuration, out wmtType, null, ref cbLength);
|
||
|
|
// var pbValue = new byte[cbLength];
|
||
|
|
// pHeaderInfo.GetAttributeByName(ref wAnyStream, Constants.g_wszWMDuration, out wmtType, pbValue, ref cbLength);
|
||
|
|
// var m_cnsFileDuration = BitConverter.ToInt64(pbValue, 0);
|
||
|
|
// _sampleCount = m_cnsFileDuration * m_pWfx.nSamplesPerSec / 10000000;
|
||
|
|
// // NOT ACCURATE ENOUGH (~1ms precision observed)
|
||
|
|
//}
|
||
|
|
//catch (COMException)
|
||
|
|
//{
|
||
|
|
//}
|
||
|
|
|
||
|
|
//try
|
||
|
|
//{
|
||
|
|
// var pHeaderInfo = m_syncReader as IWMHeaderInfo2;
|
||
|
|
// int nCodec;
|
||
|
|
// pHeaderInfo.GetCodecInfoCount(out nCodec);
|
||
|
|
// for (int wIndex = 0; wIndex < nCodec; wIndex++)
|
||
|
|
// {
|
||
|
|
// CodecInfoType enumCodecType;
|
||
|
|
// short cchName = 0;
|
||
|
|
// short cchDescription = 0;
|
||
|
|
// short cbCodecInfo = 0;
|
||
|
|
// pHeaderInfo.GetCodecInfo(wIndex, ref cchName, null,
|
||
|
|
// ref cchDescription, null, out enumCodecType,
|
||
|
|
// ref cbCodecInfo, null);
|
||
|
|
// var pwszName = new StringBuilder(cchName);
|
||
|
|
// var pwszDescription = new StringBuilder(cchDescription);
|
||
|
|
// var pbCodecInfo = new byte[cbCodecInfo];
|
||
|
|
// pHeaderInfo.GetCodecInfo(wIndex, ref cchName, pwszName,
|
||
|
|
// ref cchDescription, pwszDescription, out enumCodecType,
|
||
|
|
// ref cbCodecInfo, pbCodecInfo);
|
||
|
|
// if (enumCodecType == CodecInfoType.Audio)
|
||
|
|
// {
|
||
|
|
// // pbCodecInfo = {99,1} ??/
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
//}
|
||
|
|
//catch (COMException)
|
||
|
|
//{
|
||
|
|
//}
|
||
|
|
|
||
|
|
pcm = new AudioPCMConfig(m_pWfx.wBitsPerSample, m_pWfx.nChannels, m_pWfx.nSamplesPerSec);
|
||
|
|
_path = path;
|
||
|
|
|
||
|
|
int cbMax;
|
||
|
|
m_syncReader.GetMaxOutputSampleSize(m_dwAudioOutputNum, out cbMax);
|
||
|
|
//m_syncReader.GetMaxStreamSampleSize(m_wStreamNum, out cbMax);
|
||
|
|
//var ra = m_syncReader as IWMReaderAdvanced;
|
||
|
|
//ra.SetAllocateForOutput(m_dwAudioOutputNum, true);
|
||
|
|
|
||
|
|
samplesBuffer = new byte[cbMax];
|
||
|
|
}
|
||
|
|
|
||
|
|
public void isValid(string filename)
|
||
|
|
{
|
||
|
|
int pdwDataSize = 0;
|
||
|
|
WMUtils.WMValidateData(null, ref pdwDataSize);
|
||
|
|
byte[] data = new byte[pdwDataSize];
|
||
|
|
using (FileStream s = new FileStream(filename, FileMode.Open, FileAccess.Read))
|
||
|
|
{
|
||
|
|
if (s.Read(data, 0, pdwDataSize) < pdwDataSize)
|
||
|
|
throw new Exception("partial read"); // TODO
|
||
|
|
}
|
||
|
|
WMUtils.WMValidateData(data, ref pdwDataSize);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Close()
|
||
|
|
{
|
||
|
|
//_IO.Close();
|
||
|
|
m_syncReader.Close();
|
||
|
|
Marshal.ReleaseComObject(m_syncReader);
|
||
|
|
}
|
||
|
|
|
||
|
|
public long Length
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return _sampleCount;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public long Remaining
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return Length - Position;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public long Position
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return _sampleOffset - _samplesInBuffer;
|
||
|
|
}
|
||
|
|
set
|
||
|
|
{
|
||
|
|
if (_sampleCount < 0 || value > _sampleCount)
|
||
|
|
throw new Exception("seeking past end of stream");
|
||
|
|
throw new NotSupportedException();
|
||
|
|
//if (value < Position || value > _sampleOffset)
|
||
|
|
//{
|
||
|
|
// if (seek_table != null && _IO.CanSeek)
|
||
|
|
// {
|
||
|
|
// int best_st = -1;
|
||
|
|
// for (int st = 0; st < seek_table.Length; st++)
|
||
|
|
// {
|
||
|
|
// if (seek_table[st].number <= value &&
|
||
|
|
// (best_st == -1 || seek_table[st].number > seek_table[best_st].number))
|
||
|
|
// best_st = st;
|
||
|
|
// }
|
||
|
|
// if (best_st != -1)
|
||
|
|
// {
|
||
|
|
// _framesBufferLength = 0;
|
||
|
|
// _samplesInBuffer = 0;
|
||
|
|
// _samplesBufferOffset = 0;
|
||
|
|
// _IO.Position = (long)seek_table[best_st].offset + first_frame_offset;
|
||
|
|
// _sampleOffset = seek_table[best_st].number;
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
// if (value < Position)
|
||
|
|
// throw new Exception("cannot seek backwards without seek table");
|
||
|
|
//}
|
||
|
|
//while (value > _sampleOffset)
|
||
|
|
//{
|
||
|
|
// _samplesInBuffer = 0;
|
||
|
|
// _samplesBufferOffset = 0;
|
||
|
|
|
||
|
|
// fill_frames_buffer();
|
||
|
|
// if (_framesBufferLength == 0)
|
||
|
|
// throw new Exception("seek failed");
|
||
|
|
|
||
|
|
// int bytesDecoded = DecodeFrame(_framesBuffer, _framesBufferOffset, _framesBufferLength);
|
||
|
|
// _framesBufferLength -= bytesDecoded;
|
||
|
|
// _framesBufferOffset += bytesDecoded;
|
||
|
|
|
||
|
|
// _sampleOffset += _samplesInBuffer;
|
||
|
|
//};
|
||
|
|
//int diff = _samplesInBuffer - (int)(_sampleOffset - value);
|
||
|
|
//_samplesInBuffer -= diff;
|
||
|
|
//_samplesBufferOffset += diff;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public AudioPCMConfig PCM
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return pcm;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public string Path
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return _path;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const int BS = 4;
|
||
|
|
|
||
|
|
public int Read(AudioBuffer buff, int maxLength)
|
||
|
|
{
|
||
|
|
buff.Prepare(this, maxLength);
|
||
|
|
|
||
|
|
int offset = 0;
|
||
|
|
int sampleCount = buff.Length;
|
||
|
|
|
||
|
|
while (_samplesInBuffer < sampleCount)
|
||
|
|
{
|
||
|
|
if (_samplesInBuffer > 0)
|
||
|
|
{
|
||
|
|
Array.Copy(samplesBuffer, _samplesBufferOffset * BS, buff.Bytes, offset * BS, _samplesInBuffer * BS);
|
||
|
|
sampleCount -= _samplesInBuffer;
|
||
|
|
offset += _samplesInBuffer;
|
||
|
|
_samplesInBuffer = 0;
|
||
|
|
_samplesBufferOffset = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
INSSBuffer pSample;
|
||
|
|
long cnsSampleTime;
|
||
|
|
long cnsDuration;
|
||
|
|
SampleFlag flags;
|
||
|
|
int dwOutputNum;
|
||
|
|
short wStreamNum;
|
||
|
|
int dwLength;
|
||
|
|
IntPtr pdwBuffer;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
m_syncReader.GetNextSample(m_wStreamNum, out pSample, out cnsSampleTime, out cnsDuration, out flags, out dwOutputNum, out wStreamNum);
|
||
|
|
}
|
||
|
|
catch (COMException ex)
|
||
|
|
{
|
||
|
|
// EOF
|
||
|
|
if (ex.ErrorCode == NSResults.E_NO_MORE_SAMPLES)
|
||
|
|
{
|
||
|
|
_sampleCount = _sampleOffset;
|
||
|
|
return offset;
|
||
|
|
}
|
||
|
|
throw ex;
|
||
|
|
}
|
||
|
|
//if (dwOutputNum != m_dwAudioOutputNum || wStreamNum != m_wStreamNum)
|
||
|
|
//{
|
||
|
|
//}
|
||
|
|
pSample.GetBufferAndLength(out pdwBuffer, out dwLength);
|
||
|
|
Marshal.Copy(pdwBuffer, samplesBuffer, 0, dwLength);
|
||
|
|
Marshal.ReleaseComObject(pSample);
|
||
|
|
_samplesInBuffer = dwLength / BS;
|
||
|
|
_sampleOffset += _samplesInBuffer;
|
||
|
|
|
||
|
|
//_samplesInBuffer -= _samplesBufferOffset; // can be set by Seek, otherwise zero
|
||
|
|
//_sampleOffset += _samplesInBuffer;
|
||
|
|
}
|
||
|
|
|
||
|
|
Array.Copy(samplesBuffer, _samplesBufferOffset * BS, buff.Bytes, offset * BS, sampleCount * BS);
|
||
|
|
_samplesInBuffer -= sampleCount;
|
||
|
|
_samplesBufferOffset += sampleCount;
|
||
|
|
if (_samplesInBuffer == 0)
|
||
|
|
_samplesBufferOffset = 0;
|
||
|
|
return offset + sampleCount;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|