mirror of
https://github.com/claunia/cuetools.net.git
synced 2025-12-16 18:14:25 +00:00
Initial checkin (from Availo)
This commit is contained in:
348
CUETools.Codecs.LAME/LameWriter.cs
Normal file
348
CUETools.Codecs.LAME/LameWriter.cs
Normal file
@@ -0,0 +1,348 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using CUETools.Codecs;
|
||||
|
||||
namespace CUETools.Codecs.LAME
|
||||
{
|
||||
public class LameWriter : IAudioDest
|
||||
{
|
||||
#region Unmanaged Functions
|
||||
|
||||
private const string LameDll = "lame_enc";
|
||||
private const CallingConvention LameCallingConvention = CallingConvention.Cdecl;
|
||||
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern IntPtr lame_init();
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_close(IntPtr handle);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_num_channels(IntPtr handle, int channels);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_in_samplerate(IntPtr handle, int sampleRate);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_quality(IntPtr handle, int quality);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_VBR(IntPtr handle, int vbrMode);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_VBR_mean_bitrate_kbps(IntPtr handle, int meanBitrate);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_init_params(IntPtr handle);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_num_samples(IntPtr handle, uint numSamples);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_encode_buffer_interleaved(IntPtr handle, IntPtr pcm, int num_samples, IntPtr mp3buf, int mp3buf_size);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_encode_flush(IntPtr handle, IntPtr mp3buf, int size);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern uint lame_get_lametag_frame(IntPtr handle, IntPtr buffer, uint size);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_VBR_quality(IntPtr handle, float vbrQuality);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_brate(IntPtr handle, int bitrate);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_bWriteVbrTag(IntPtr handle, int writeVbrTag);
|
||||
[DllImport(LameDll, CallingConvention = LameCallingConvention)]
|
||||
private static extern int lame_set_write_id3tag_automatic(IntPtr handle, int automaticWriteId3Tag);
|
||||
|
||||
#endregion
|
||||
|
||||
private string outputPath;
|
||||
private Stream outputStream;
|
||||
|
||||
private LameWriterSettings settings;
|
||||
private bool closed = false, initialized = false;
|
||||
private IntPtr handle;
|
||||
private AudioPCMConfig pcm;
|
||||
private uint finalSampleCount;
|
||||
private byte[] outputBuffer;
|
||||
|
||||
public long BlockSize
|
||||
{
|
||||
set { }
|
||||
}
|
||||
|
||||
public virtual int CompressionLevel
|
||||
{
|
||||
get { return 0; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public long FinalSampleCount
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value > uint.MaxValue)
|
||||
{
|
||||
throw new ArgumentException("Input file too big.");
|
||||
}
|
||||
this.finalSampleCount = (uint)value;
|
||||
}
|
||||
}
|
||||
|
||||
public AudioPCMConfig PCM
|
||||
{
|
||||
get { return this.pcm; }
|
||||
}
|
||||
|
||||
public long Padding
|
||||
{
|
||||
set { }
|
||||
}
|
||||
|
||||
public string Path
|
||||
{
|
||||
get { return this.outputPath; }
|
||||
}
|
||||
|
||||
public virtual object Settings
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.settings;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!(value is LameWriterSettings))
|
||||
{
|
||||
throw new ArgumentException("Settings must be of type LameWriterSettings");
|
||||
}
|
||||
this.settings = (LameWriterSettings)value;
|
||||
}
|
||||
}
|
||||
|
||||
public LameWriter(string path, AudioPCMConfig pcm)
|
||||
{
|
||||
this.CheckPCMConfig(pcm);
|
||||
|
||||
this.pcm = pcm;
|
||||
this.outputPath = path;
|
||||
this.outputStream = File.Create(path);
|
||||
}
|
||||
|
||||
public LameWriter(Stream output, AudioPCMConfig pcm)
|
||||
{
|
||||
this.CheckPCMConfig(pcm);
|
||||
|
||||
this.outputStream = output;
|
||||
this.pcm = pcm;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
fixed (byte* outputBufferPtr = outputBuffer)
|
||||
{
|
||||
flushResult = lame_encode_flush(handle, (IntPtr)outputBufferPtr, outputBuffer.Length);
|
||||
}
|
||||
}
|
||||
if (flushResult < 0)
|
||||
{
|
||||
throw new LameException("Unknown flush error");
|
||||
}
|
||||
if (flushResult > 0)
|
||||
{
|
||||
this.outputStream.Write(this.outputBuffer, 0, flushResult);
|
||||
}
|
||||
|
||||
int lametagFrameSize = this.GetLametagFrame();
|
||||
this.outputStream.Seek(0, SeekOrigin.Begin);
|
||||
this.outputStream.Write(this.outputBuffer, 0, lametagFrameSize);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!this.closed)
|
||||
{
|
||||
if (this.initialized)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
this.FinalizeEncoding();
|
||||
}
|
||||
finally
|
||||
{
|
||||
lame_close(handle);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle = IntPtr.Zero;
|
||||
if (this.outputPath != null)
|
||||
{
|
||||
this.outputStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetLametagFrame()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
uint lametagFrameResult;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* outputBufferPtr = outputBuffer)
|
||||
{
|
||||
lametagFrameResult = lame_get_lametag_frame(this.handle, (IntPtr)outputBufferPtr, (uint)outputBuffer.Length);
|
||||
}
|
||||
}
|
||||
if (lametagFrameResult < 0)
|
||||
{
|
||||
throw new LameException("Error getting lametag frame.");
|
||||
}
|
||||
if (lametagFrameResult <= outputBuffer.Length)
|
||||
{
|
||||
return (int)lametagFrameResult;
|
||||
}
|
||||
this.EnsureOutputBufferSize((int)lametagFrameResult);
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetLametagFrame(byte[] outputBuffer)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* outputBufferPtr = outputBuffer)
|
||||
{
|
||||
return lame_get_lametag_frame(handle, (IntPtr)outputBufferPtr, (uint)outputBuffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (!this.initialized)
|
||||
{
|
||||
handle = lame_init();
|
||||
|
||||
lame_set_bWriteVbrTag(handle, 1);
|
||||
lame_set_write_id3tag_automatic(handle, 0);
|
||||
|
||||
lame_set_num_channels(handle, this.pcm.ChannelCount);
|
||||
lame_set_in_samplerate(handle, this.pcm.SampleRate);
|
||||
|
||||
lame_set_quality(handle, (int)this.settings.Quality);
|
||||
|
||||
if (this.finalSampleCount != 0)
|
||||
{
|
||||
lame_set_num_samples(handle, this.finalSampleCount);
|
||||
}
|
||||
|
||||
lame_set_VBR(this.handle, (int)this.settings.VbrMode);
|
||||
|
||||
switch (this.settings.VbrMode)
|
||||
{
|
||||
case LameVbrMode.Abr:
|
||||
lame_set_VBR_mean_bitrate_kbps(handle, this.settings.Bitrate);
|
||||
break;
|
||||
case LameVbrMode.Default:
|
||||
lame_set_VBR_quality(handle, this.settings.VbrQuality);
|
||||
break;
|
||||
case LameVbrMode.Off:
|
||||
lame_set_brate(handle, this.settings.Bitrate);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Only ABR, Default and Off VBR modes are supported.");
|
||||
}
|
||||
|
||||
if (lame_init_params(handle) != 0)
|
||||
{
|
||||
throw new LameException("lame_init_params failed");
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if (this.outputPath != null)
|
||||
{
|
||||
throw new InvalidOperationException("This writer was not created from file.");
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
{
|
||||
this.Close();
|
||||
File.Delete(this.outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureOutputBufferSize(int requiredSize)
|
||||
{
|
||||
if (this.outputBuffer == null || this.outputBuffer.Length < requiredSize)
|
||||
{
|
||||
this.outputBuffer = new byte[requiredSize];
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(AudioBuffer buffer)
|
||||
{
|
||||
if (this.closed)
|
||||
{
|
||||
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)
|
||||
{
|
||||
fixed (byte* outputBufferPtr = this.outputBuffer)
|
||||
{
|
||||
result = lame_encode_buffer_interleaved(handle, (IntPtr)bytesPtr, buffer.Length, (IntPtr)outputBufferPtr, outputBuffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
this.outputStream.Write(this.outputBuffer, 0, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
CUETools.Codecs.LAME/LameWriterSettings.cs
Normal file
54
CUETools.Codecs.LAME/LameWriterSettings.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
|
||||
namespace CUETools.Codecs.LAME
|
||||
{
|
||||
public class LameException: Exception
|
||||
{
|
||||
public LameException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public enum LameQuality
|
||||
{
|
||||
High,
|
||||
}
|
||||
|
||||
public enum LameVbrMode
|
||||
{
|
||||
Off,
|
||||
Default,
|
||||
Abr,
|
||||
}
|
||||
|
||||
public class LameWriterSettings
|
||||
{
|
||||
public LameQuality Quality { get; set; }
|
||||
public LameVbrMode VbrMode { get; set; }
|
||||
public int VbrQuality { get; set; }
|
||||
public int Bitrate { get; set; }
|
||||
|
||||
public LameWriterSettings()
|
||||
{
|
||||
Quality = LameQuality.High;
|
||||
VbrMode = LameVbrMode.Default;
|
||||
VbrQuality = 5;
|
||||
}
|
||||
|
||||
public static LameWriterSettings CreateCbr(int bitrate, LameQuality encodeQuality = LameQuality.High)
|
||||
{
|
||||
return new LameWriterSettings() { VbrMode = LameVbrMode.Off, Bitrate = bitrate, Quality = encodeQuality };
|
||||
}
|
||||
|
||||
public static LameWriterSettings CreateAbr(int bitrate, LameQuality encodeQuality = LameQuality.High)
|
||||
{
|
||||
return new LameWriterSettings() { VbrMode = LameVbrMode.Abr, Bitrate = bitrate, Quality = encodeQuality };
|
||||
}
|
||||
|
||||
public static LameWriterSettings CreateVbr(int vbrQuality, LameQuality encodeQuality = LameQuality.High)
|
||||
{
|
||||
return new LameWriterSettings() { VbrMode = LameVbrMode.Default, VbrQuality = vbrQuality, Quality = encodeQuality };
|
||||
}
|
||||
}
|
||||
}
|
||||
54
CUETools.Codecs.LAME/LameWriterVBR.cs
Normal file
54
CUETools.Codecs.LAME/LameWriterVBR.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using CUETools.Codecs;
|
||||
|
||||
namespace CUETools.Codecs.LAME
|
||||
{
|
||||
[AudioEncoderClass("lame2 VBR", "mp3", false, "V9 V8 V7 V6 V5 V4 V3 V2 V1 V0", "V2", 2, typeof(LAMEEncoderVBRSettings))]
|
||||
public class LameWriterVBR : LameWriter
|
||||
{
|
||||
private int quality = 0;
|
||||
|
||||
public LameWriterVBR(string path, Stream IO, AudioPCMConfig pcm)
|
||||
: base(IO, pcm)
|
||||
{
|
||||
}
|
||||
|
||||
public LameWriterVBR(string path, AudioPCMConfig pcm)
|
||||
: base(path, pcm)
|
||||
{
|
||||
}
|
||||
|
||||
public override int CompressionLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
return 9 - quality;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value < 0 || value > 9)
|
||||
throw new Exception("unsupported compression level");
|
||||
quality = 9 - value;
|
||||
}
|
||||
}
|
||||
|
||||
LAMEEncoderVBRSettings _settings = new LAMEEncoderVBRSettings();
|
||||
|
||||
public override object Settings
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value as LAMEEncoderVBRSettings == null)
|
||||
throw new Exception("Unsupported options " + value);
|
||||
_settings = value as LAMEEncoderVBRSettings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user