Files
cuetools.net/CUETools.Codecs.LAME/LameWriter.cs

301 lines
11 KiB
C#
Raw Normal View History

2011-10-23 23:05:45 +00:00
using System;
using System.Runtime.InteropServices;
using CUETools.Codecs;
2011-10-25 01:16:11 +00:00
using System.IO;
2011-10-23 23:05:45 +00:00
namespace CUETools.Codecs.LAME
{
2013-04-02 19:56:58 -04:00
[AudioEncoderClass("CBR (libmp3lame)", "mp3", false, 1, typeof(LameWriterCBRSettings))]
[AudioEncoderClass("VBR (libmp3lame)", "mp3", false, 2, typeof(LameWriterVBRSettings))]
2011-10-23 23:05:45 +00:00
public class LameWriter : IAudioDest
{
#region Unmanaged Functions
private const string LameDll = "libmp3lame";
2011-10-23 23:05:45 +00:00
private const CallingConvention LameCallingConvention = CallingConvention.Cdecl;
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern IntPtr lame_init();
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_close(IntPtr handle);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_num_channels(IntPtr handle, int channels);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_in_samplerate(IntPtr handle, int sampleRate);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_quality(IntPtr handle, int quality);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_VBR(IntPtr handle, int vbrMode);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_VBR_mean_bitrate_kbps(IntPtr handle, int meanBitrate);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_init_params(IntPtr handle);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_num_samples(IntPtr handle, uint numSamples);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_encode_buffer_interleaved(IntPtr handle, IntPtr pcm, int num_samples, IntPtr mp3buf, int mp3buf_size);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_encode_flush(IntPtr handle, IntPtr mp3buf, int size);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern uint lame_get_lametag_frame(IntPtr handle, IntPtr buffer, uint size);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_VBR_quality(IntPtr handle, float vbrQuality);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_brate(IntPtr handle, int bitrate);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_bWriteVbrTag(IntPtr handle, int writeVbrTag);
2011-10-23 23:05:45 +00:00
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
2013-04-02 19:56:58 -04:00
internal static extern int lame_set_write_id3tag_automatic(IntPtr handle, int automaticWriteId3Tag);
2011-10-23 23:05:45 +00:00
#endregion
2013-04-02 19:56:58 -04:00
private string m_outputPath;
private Stream m_outputStream;
2011-10-23 23:05:45 +00:00
2013-04-02 19:56:58 -04:00
private bool m_closed = false, m_initialized = false;
private IntPtr m_handle;
private uint m_finalSampleCount;
private byte[] m_outputBuffer;
2011-10-23 23:05:45 +00:00
public long FinalSampleCount
{
set
{
if (value > uint.MaxValue)
{
throw new ArgumentException("Input file too big.");
}
2013-04-02 19:56:58 -04:00
this.m_finalSampleCount = (uint)value;
2011-10-23 23:05:45 +00:00
}
}
public string Path
{
2013-04-02 19:56:58 -04:00
get { return this.m_outputPath; }
2011-10-23 23:05:45 +00:00
}
private LameWriterSettings m_settings;
2013-04-02 19:56:58 -04:00
public virtual AudioEncoderSettings Settings
2011-10-23 23:05:45 +00:00
{
get
{
2013-04-02 19:56:58 -04:00
return m_settings;
2011-10-23 23:05:45 +00:00
}
}
public LameWriter(string path, Stream output, LameWriterSettings settings)
2011-10-23 23:05:45 +00:00
{
this.CheckPCMConfig(settings.PCM);
this.m_settings = settings;
2013-04-02 19:56:58 -04:00
this.m_outputPath = path;
this.m_outputStream = output != null ? output : File.Create(path);
2011-10-23 23:05:45 +00:00
}
public LameWriter(string path, LameWriterSettings settings)
: this(path, null, settings)
2011-10-23 23:05:45 +00:00
{
}
private void CheckPCMConfig(AudioPCMConfig pcm)
{
if (pcm.BitsPerSample != 16)
{
throw new ArgumentException("LAME only supports 16 bits/sample.");
}
}
private void FinalizeEncoding()
{
this.EnsureOutputBufferSize(7200);
int flushResult;
unsafe
{
2013-04-02 19:56:58 -04:00
fixed (byte* outputBufferPtr = m_outputBuffer)
2011-10-23 23:05:45 +00:00
{
2013-04-02 19:56:58 -04:00
flushResult = lame_encode_flush(m_handle, (IntPtr)outputBufferPtr, m_outputBuffer.Length);
2011-10-23 23:05:45 +00:00
}
}
if (flushResult < 0)
{
throw new LameException("Unknown flush error");
}
if (flushResult > 0)
{
2013-04-02 19:56:58 -04:00
this.m_outputStream.Write(this.m_outputBuffer, 0, flushResult);
2011-10-23 23:05:45 +00:00
}
int lametagFrameSize = this.GetLametagFrame();
2013-04-02 19:56:58 -04:00
this.m_outputStream.Seek(0, SeekOrigin.Begin);
this.m_outputStream.Write(this.m_outputBuffer, 0, lametagFrameSize);
2011-10-23 23:05:45 +00:00
}
public void Close()
{
2013-04-02 19:56:58 -04:00
if (!this.m_closed)
2011-10-23 23:05:45 +00:00
{
2013-04-02 19:56:58 -04:00
if (this.m_initialized)
2011-10-23 23:05:45 +00:00
{
try
{
try
{
this.FinalizeEncoding();
}
finally
{
2013-04-02 19:56:58 -04:00
lame_close(m_handle);
2011-10-23 23:05:45 +00:00
}
}
finally
{
2013-04-02 19:56:58 -04:00
m_handle = IntPtr.Zero;
if (this.m_outputPath != null)
2011-10-23 23:05:45 +00:00
{
2013-04-02 19:56:58 -04:00
this.m_outputStream.Close();
2011-10-23 23:05:45 +00:00
}
}
}
2013-04-02 19:56:58 -04:00
this.m_closed = true;
2011-10-23 23:05:45 +00:00
}
}
private int GetLametagFrame()
{
while (true)
{
uint lametagFrameResult;
unsafe
{
2013-04-02 19:56:58 -04:00
fixed (byte* outputBufferPtr = m_outputBuffer)
2011-10-23 23:05:45 +00:00
{
2013-04-02 19:56:58 -04:00
lametagFrameResult = lame_get_lametag_frame(this.m_handle, (IntPtr)outputBufferPtr, (uint)m_outputBuffer.Length);
2011-10-23 23:05:45 +00:00
}
}
if (lametagFrameResult < 0)
{
throw new LameException("Error getting lametag frame.");
}
2013-04-02 19:56:58 -04:00
if (lametagFrameResult <= m_outputBuffer.Length)
2011-10-23 23:05:45 +00:00
{
return (int)lametagFrameResult;
}
this.EnsureOutputBufferSize((int)lametagFrameResult);
}
}
public uint GetLametagFrame(byte[] outputBuffer)
{
unsafe
{
fixed (byte* outputBufferPtr = outputBuffer)
{
2013-04-02 19:56:58 -04:00
return lame_get_lametag_frame(m_handle, (IntPtr)outputBufferPtr, (uint)outputBuffer.Length);
2011-10-23 23:05:45 +00:00
}
}
}
private void EnsureInitialized()
{
2013-04-02 19:56:58 -04:00
if (!this.m_initialized)
2011-10-23 23:05:45 +00:00
{
2013-04-02 19:56:58 -04:00
m_handle = lame_init();
2011-10-23 23:05:45 +00:00
2013-04-02 19:56:58 -04:00
lame_set_bWriteVbrTag(m_handle, 1);
lame_set_write_id3tag_automatic(m_handle, 0);
2011-10-23 23:05:45 +00:00
lame_set_num_channels(m_handle, this.Settings.PCM.ChannelCount);
lame_set_in_samplerate(m_handle, this.Settings.PCM.SampleRate);
2011-10-23 23:05:45 +00:00
2013-04-02 19:56:58 -04:00
if (this.m_finalSampleCount != 0)
2011-10-23 23:05:45 +00:00
{
2013-04-02 19:56:58 -04:00
lame_set_num_samples(m_handle, this.m_finalSampleCount);
2011-10-23 23:05:45 +00:00
}
2013-04-02 19:56:58 -04:00
m_settings.Apply(m_handle);
2011-10-23 23:05:45 +00:00
2013-04-02 19:56:58 -04:00
if (lame_init_params(m_handle) != 0)
2011-10-23 23:05:45 +00:00
{
throw new LameException("lame_init_params failed");
}
2013-04-02 19:56:58 -04:00
this.m_initialized = true;
2011-10-23 23:05:45 +00:00
}
}
public void Delete()
{
2013-04-02 19:56:58 -04:00
if (this.m_outputPath == null)
2011-10-23 23:05:45 +00:00
{
throw new InvalidOperationException("This writer was not created from file.");
}
2013-04-02 19:56:58 -04:00
if (!m_closed)
2011-10-23 23:05:45 +00:00
{
this.Close();
2013-04-02 19:56:58 -04:00
File.Delete(this.m_outputPath);
2011-10-23 23:05:45 +00:00
}
}
private void EnsureOutputBufferSize(int requiredSize)
{
2013-04-02 19:56:58 -04:00
if (this.m_outputBuffer == null || this.m_outputBuffer.Length < requiredSize)
2011-10-23 23:05:45 +00:00
{
2013-04-02 19:56:58 -04:00
this.m_outputBuffer = new byte[requiredSize];
2011-10-23 23:05:45 +00:00
}
}
public void Write(AudioBuffer buffer)
{
2013-04-02 19:56:58 -04:00
if (this.m_closed)
2011-10-23 23:05:45 +00:00
{
throw new InvalidOperationException("Writer already closed.");
}
buffer.Prepare(this);
this.EnsureInitialized();
this.EnsureOutputBufferSize(buffer.Length * 5 / 4 + 7200);
byte[] bytes = buffer.Bytes;
int result;
unsafe
{
fixed (byte* bytesPtr = bytes)
{
2013-04-02 19:56:58 -04:00
fixed (byte* outputBufferPtr = this.m_outputBuffer)
2011-10-23 23:05:45 +00:00
{
2013-04-02 19:56:58 -04:00
result = lame_encode_buffer_interleaved(m_handle, (IntPtr)bytesPtr, buffer.Length, (IntPtr)outputBufferPtr, m_outputBuffer.Length);
2011-10-23 23:05:45 +00:00
}
}
}
if (result < 0)
{
switch (result)
{
case -1:
throw new LameException("Output buffer is too small");
case -2:
throw new LameException("malloc problem");
case -3:
throw new LameException("lame_init_params was not called");
case -4:
throw new LameException("Psycho acoustic problems");
default:
throw new LameException("Unknown error");
}
}
if (result > 0)
{
2013-04-02 19:56:58 -04:00
this.m_outputStream.Write(this.m_outputBuffer, 0, result);
2011-10-23 23:05:45 +00:00
}
}
}
}