Code cleanup; Reader classes renamed to Decoders, Writers to Encoders, every Decoder must have a corresponding Settings class now just like Encoders. UserDefinedEncoders renamed to CommandLineEncoders, etc.

This commit is contained in:
Grigory Chudov
2018-03-24 12:15:49 -04:00
parent ca8bb2fff6
commit e1f8906170
65 changed files with 1713 additions and 1798 deletions

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net40;net20;netstandard2.0</TargetFrameworks>
<Version>2.1.7.0</Version>
<AssemblyName>CUETools.Codecs.libmp3lame</AssemblyName>
<RootNamespace>CUETools.Codecs.libmp3lame</RootNamespace>
<Product>CUETools</Product>
<Description>A library for encoding mp3 using LAME encoder.</Description>
<Copyright>Copyright (c) 2008-2018 Grigory Chudov</Copyright>
<Authors>Grigory Chudov</Authors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputPath>..\bin\$(Configuration)\plugins</OutputPath>
<RepositoryUrl>https://github.com/gchudov/cuetools.net</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Company />
</PropertyGroup>
<ItemDefinitionGroup>
<ProjectReference>
<Private>False</Private>
</ProjectReference>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectReference Include="..\CUETools.Codecs\CUETools.Codecs.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CUETools.Codecs.libmp3lame
{
public class LameException : Exception
{
public LameException(string message)
: base(message)
{
}
}
}

View File

@@ -0,0 +1,259 @@
using System;
using System.Runtime.InteropServices;
using CUETools.Codecs;
using System.IO;
namespace CUETools.Codecs.libmp3lame
{
public class AudioEncoder : IAudioDest
{
private string m_outputPath;
private Stream m_outputStream;
private bool m_closed = false, m_initialized = false;
private IntPtr m_handle;
private uint m_finalSampleCount;
private byte[] m_outputBuffer;
private long m_bytesWritten;
public long FinalSampleCount
{
set
{
if (value > uint.MaxValue)
{
throw new ArgumentException("Input file too big.");
}
this.m_finalSampleCount = (uint)value;
}
}
public string Path
{
get { return this.m_outputPath; }
}
private LameEncoderSettings m_settings;
public virtual AudioEncoderSettings Settings
{
get
{
return m_settings;
}
}
public long BytesWritten => m_bytesWritten;
public AudioEncoder(LameEncoderSettings settings, string path, Stream output = null)
{
this.CheckPCMConfig(settings.PCM);
this.m_settings = settings;
this.m_outputPath = path;
this.m_outputStream = output != null ? output : File.Create(path);
this.m_bytesWritten = 0;
}
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 = m_outputBuffer)
{
flushResult = libmp3lamedll.lame_encode_flush(m_handle, (IntPtr)outputBufferPtr, m_outputBuffer.Length);
}
}
if (flushResult < 0)
{
throw new LameException("Unknown flush error");
}
if (flushResult > 0)
{
this.m_outputStream.Write(this.m_outputBuffer, 0, flushResult);
m_bytesWritten += flushResult;
}
int lametagFrameSize = this.GetLametagFrame();
this.m_outputStream.Seek(0, SeekOrigin.Begin);
this.m_outputStream.Write(this.m_outputBuffer, 0, lametagFrameSize);
}
public void Close()
{
if (!this.m_closed)
{
if (this.m_initialized)
{
try
{
try
{
this.FinalizeEncoding();
}
finally
{
libmp3lamedll.lame_close(m_handle);
}
}
finally
{
m_handle = IntPtr.Zero;
if (this.m_outputPath != null)
{
this.m_outputStream.Close();
}
}
}
this.m_closed = true;
}
}
private int GetLametagFrame()
{
while (true)
{
uint lametagFrameResult;
unsafe
{
fixed (byte* outputBufferPtr = m_outputBuffer)
{
lametagFrameResult = libmp3lamedll.lame_get_lametag_frame(this.m_handle, (IntPtr)outputBufferPtr, (uint)m_outputBuffer.Length);
}
}
if (lametagFrameResult < 0)
{
throw new LameException("Error getting lametag frame.");
}
if (lametagFrameResult <= m_outputBuffer.Length)
{
return (int)lametagFrameResult;
}
this.EnsureOutputBufferSize((int)lametagFrameResult);
}
}
public uint GetLametagFrame(byte[] outputBuffer)
{
unsafe
{
fixed (byte* outputBufferPtr = outputBuffer)
{
return libmp3lamedll.lame_get_lametag_frame(m_handle, (IntPtr)outputBufferPtr, (uint)outputBuffer.Length);
}
}
}
private void EnsureInitialized()
{
if (!this.m_initialized)
{
m_handle = libmp3lamedll.lame_init();
libmp3lamedll.lame_set_bWriteVbrTag(m_handle, 1);
libmp3lamedll.lame_set_write_id3tag_automatic(m_handle, 0);
libmp3lamedll.lame_set_num_channels(m_handle, this.Settings.PCM.ChannelCount);
libmp3lamedll.lame_set_in_samplerate(m_handle, this.Settings.PCM.SampleRate);
if (this.m_finalSampleCount != 0)
{
libmp3lamedll.lame_set_num_samples(m_handle, this.m_finalSampleCount);
}
m_settings.Apply(m_handle);
if (libmp3lamedll.lame_init_params(m_handle) != 0)
{
throw new LameException("lame_init_params failed");
}
this.m_initialized = true;
}
}
public void Delete()
{
if (this.m_outputPath == null)
{
throw new InvalidOperationException("This writer was not created from file.");
}
if (!m_closed)
{
this.Close();
File.Delete(this.m_outputPath);
}
}
private void EnsureOutputBufferSize(int requiredSize)
{
if (this.m_outputBuffer == null || this.m_outputBuffer.Length < requiredSize)
{
this.m_outputBuffer = new byte[requiredSize];
}
}
public void Write(AudioBuffer buffer)
{
if (this.m_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.m_outputBuffer)
{
result = libmp3lamedll.lame_encode_buffer_interleaved(m_handle, (IntPtr)bytesPtr, buffer.Length, (IntPtr)outputBufferPtr, m_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.m_outputStream.Write(this.m_outputBuffer, 0, result);
m_bytesWritten += result;
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using Newtonsoft.Json;
namespace CUETools.Codecs.libmp3lame
{
[JsonObject(MemberSerialization.OptIn)]
public class CBREncoderSettings : LameEncoderSettings
{
public override string Extension => "mp3";
public override string Name => "libmp3lame-CBR";
public override int Priority => 1;
public static readonly int[] bps_table = new int[] { 96, 128, 192, 256, 320 };
[JsonProperty]
[DefaultValue(LameQuality.High)]
public LameQuality Quality { get; set; }
public CBREncoderSettings()
: base("96 128 192 256 320", "256")
{
}
public override void Apply(IntPtr lame)
{
libmp3lamedll.lame_set_VBR(lame, (int)LameVbrMode.Off);
libmp3lamedll.lame_set_brate(lame, CBREncoderSettings.bps_table[this.EncoderModeIndex]);
libmp3lamedll.lame_set_quality(lame, (int)this.Quality);
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CUETools.Codecs.libmp3lame
{
public enum LameQuality
{
High = 2,
Normal = 5,
Fast = 7,
}
public enum LameVbrMode
{
Off = 0,
Mt = 1,
Rh = 2,
Abr = 3,
Default = 4,
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CUETools.Codecs.libmp3lame
{
public class LameEncoderSettings : AudioEncoderSettings
{
public override Type EncoderType => typeof(AudioEncoder);
public LameEncoderSettings(string modes, string defaultMode)
: base(modes, defaultMode)
{
}
public virtual void Apply(IntPtr lame)
{
throw new MethodAccessException();
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using Newtonsoft.Json;
namespace CUETools.Codecs.libmp3lame
{
[JsonObject(MemberSerialization.OptIn)]
public class VBREncoderSettings : LameEncoderSettings
{
public override string Extension => "mp3";
public override string Name => "libmp3lame-VBR";
public override int Priority => 2;
[JsonProperty]
[DefaultValue(LameQuality.High)]
public LameQuality Quality { get; set; }
public VBREncoderSettings()
: base("V9 V8 V7 V6 V5 V4 V3 V2 V1 V0", "V2")
{
}
public override void Apply(IntPtr lame)
{
libmp3lamedll.lame_set_VBR(lame, (int)LameVbrMode.Default);
libmp3lamedll.lame_set_VBR_quality(lame, 9 - this.EncoderModeIndex);
libmp3lamedll.lame_set_quality(lame, (int)this.Quality);
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace CUETools.Codecs.libmp3lame
{
class libmp3lamedll
{
private const string DllName = "libmp3lame";
private const CallingConvention LameCallingConvention = CallingConvention.Cdecl;
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern IntPtr lame_init();
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_close(IntPtr handle);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_num_channels(IntPtr handle, int channels);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_in_samplerate(IntPtr handle, int sampleRate);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_quality(IntPtr handle, int quality);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_VBR(IntPtr handle, int vbrMode);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_VBR_mean_bitrate_kbps(IntPtr handle, int meanBitrate);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_init_params(IntPtr handle);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_num_samples(IntPtr handle, uint numSamples);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_encode_buffer_interleaved(IntPtr handle, IntPtr pcm, int num_samples, IntPtr mp3buf, int mp3buf_size);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_encode_flush(IntPtr handle, IntPtr mp3buf, int size);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern uint lame_get_lametag_frame(IntPtr handle, IntPtr buffer, uint size);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_VBR_quality(IntPtr handle, float vbrQuality);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_brate(IntPtr handle, int bitrate);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_bWriteVbrTag(IntPtr handle, int writeVbrTag);
[DllImport(DllName, CallingConvention = LameCallingConvention)]
internal static extern int lame_set_write_id3tag_automatic(IntPtr handle, int automaticWriteId3Tag);
static libmp3lamedll()
{
var myPath = new Uri(typeof(libmp3lamedll).Assembly.CodeBase).LocalPath;
var myFolder = System.IO.Path.GetDirectoryName(myPath);
var is64 = IntPtr.Size == 8;
var subfolder = is64 ? "x64" : "win32";
#if NET40
IntPtr Dll = LoadLibrary(System.IO.Path.Combine(myFolder, subfolder, DllName + ".dll"));
#else
IntPtr Dll = LoadLibrary(System.IO.Path.Combine(System.IO.Path.Combine(myFolder, subfolder), DllName + ".dll"));
#endif
if (Dll == IntPtr.Zero)
Dll = LoadLibrary(DllName + ".dll");
if (Dll == IntPtr.Zero)
throw new DllNotFoundException();
}
}
}