mirror of
https://github.com/claunia/cuetools.net.git
synced 2025-12-16 18:14:25 +00:00
266 lines
8.1 KiB
C#
266 lines
8.1 KiB
C#
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;
|
|
private long m_streamStart;
|
|
|
|
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 IAudioEncoderSettings 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(m_streamStart, 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()
|
|
{
|
|
this.EnsureOutputBufferSize(1024);
|
|
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);
|
|
}
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
byte[] id3v2 = { 0x49, 0x44, 0x33, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
int padding = m_settings.Padding;
|
|
int id3v2sz = padding;
|
|
int i = 9;
|
|
do
|
|
{
|
|
id3v2[i--] = (byte)(id3v2sz & 0x7f);
|
|
id3v2sz >>= 7;
|
|
} while (id3v2sz != 0);
|
|
m_outputStream.Write(id3v2, 0, id3v2.Length);
|
|
m_bytesWritten += id3v2.Length;
|
|
m_outputStream.Write(new byte[padding], 0, padding);
|
|
m_bytesWritten += padding;
|
|
m_streamStart = this.m_outputStream.Position;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|