303 lines
7.6 KiB
C#
303 lines
7.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.IO;
|
|
|
|
using BizHawk.Common.IOExtensions;
|
|
using BizHawk.Emulation.Common;
|
|
|
|
namespace BizHawk.Client.EmuHawk
|
|
{
|
|
/// <summary>
|
|
/// writes MS standard riff files containing uncompressed PCM wav data
|
|
/// supports 16 bit signed data only
|
|
/// </summary>
|
|
public class WavWriter : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// underlying file being written to
|
|
/// </summary>
|
|
BinaryWriter file;
|
|
/// <summary>
|
|
/// sequence of files to write to (split on 32 bit limit)
|
|
/// </summary>
|
|
IEnumerator<Stream> filechain;
|
|
|
|
/// <summary>
|
|
/// samplerate in HZ
|
|
/// </summary>
|
|
int samplerate;
|
|
/// <summary>
|
|
/// number of audio channels
|
|
/// </summary>
|
|
int numchannels;
|
|
|
|
/// <summary>
|
|
/// number of bytes of PCM data written to current file
|
|
/// </summary>
|
|
UInt64 numbytes;
|
|
|
|
/// <summary>
|
|
/// number of bytes after which a file split should be made
|
|
/// </summary>
|
|
const UInt64 splitpoint = 2 * 1000 * 1000 * 1000;
|
|
|
|
/// <summary>
|
|
/// write riff headers to current file
|
|
/// </summary>
|
|
void writeheaders()
|
|
{
|
|
file.Write(Encoding.ASCII.GetBytes("RIFF")); // ChunkID
|
|
file.Write((uint)0); // ChunkSize
|
|
file.Write(Encoding.ASCII.GetBytes("WAVE")); // Format
|
|
|
|
file.Write(Encoding.ASCII.GetBytes("fmt ")); // SubchunkID
|
|
file.Write((uint)16); // SubchunkSize
|
|
file.Write((ushort)1); // AudioFormat (PCM)
|
|
file.Write((ushort)numchannels); // NumChannels
|
|
file.Write((uint)samplerate); // SampleRate
|
|
file.Write((uint)(samplerate * numchannels * 2)); // ByteRate
|
|
file.Write((ushort)(numchannels * 2)); // BlockAlign
|
|
file.Write((ushort)16); // BitsPerSample
|
|
|
|
file.Write(Encoding.ASCII.GetBytes("data")); // SubchunkID
|
|
file.Write((uint)0); // SubchunkSize
|
|
}
|
|
|
|
/// <summary>
|
|
/// seek back to beginning of file and fix header sizes (if possible)
|
|
/// </summary>
|
|
void finalizeheaders()
|
|
{
|
|
if (numbytes + 36 >= 0x100000000)
|
|
// passed 4G limit, nothing to be done
|
|
return;
|
|
try
|
|
{
|
|
file.Seek(4, SeekOrigin.Begin);
|
|
file.Write((uint)(36 + numbytes));
|
|
file.Seek(40, SeekOrigin.Begin);
|
|
file.Write((uint)(numbytes));
|
|
}
|
|
catch (NotSupportedException)
|
|
{ // unseekable; oh well
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// close current underlying stream
|
|
/// </summary>
|
|
void closecurrent()
|
|
{
|
|
if (file != null)
|
|
{
|
|
finalizeheaders();
|
|
file.Close();
|
|
file.Dispose();
|
|
}
|
|
file = null;
|
|
}
|
|
/// <summary>
|
|
/// open a new underlying stream
|
|
/// </summary>
|
|
/// <param name="next"></param>
|
|
void opencurrent(Stream next)
|
|
{
|
|
file = new BinaryWriter(next, Encoding.ASCII);
|
|
numbytes = 0;
|
|
writeheaders();
|
|
}
|
|
|
|
/// <summary>
|
|
/// write samples to file
|
|
/// </summary>
|
|
/// <param name="samples">samples to write; should contain one for each channel</param>
|
|
public void writesamples(short[] samples)
|
|
{
|
|
file.Write(samples);
|
|
numbytes += (ulong)(samples.Length * sizeof(short));
|
|
|
|
// try splitting if we can
|
|
if (numbytes >= splitpoint && filechain != null)
|
|
{
|
|
if (!filechain.MoveNext())
|
|
{ // out of files, just keep on writing to this one
|
|
filechain = null;
|
|
}
|
|
else
|
|
{
|
|
Stream next = filechain.Current;
|
|
closecurrent();
|
|
opencurrent(next);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
/// <summary>
|
|
/// finishes writing
|
|
/// </summary>
|
|
public void Close()
|
|
{
|
|
closecurrent();
|
|
}
|
|
|
|
/// <summary>
|
|
/// checks sampling rate, number of channels for validity
|
|
/// </summary>
|
|
void checkargs()
|
|
{
|
|
if (samplerate < 1 || numchannels < 1)
|
|
throw new ArgumentException("Bad samplerate/numchannels");
|
|
}
|
|
|
|
/// <summary>
|
|
/// initializes WavWriter with a single output stream
|
|
/// no attempt is made to split
|
|
/// </summary>
|
|
/// <param name="s">WavWriter now owns this stream</param>
|
|
/// <param name="samplerate">sampling rate in HZ</param>
|
|
/// <param name="numchannels">number of audio channels</param>
|
|
public WavWriter(Stream s, int samplerate, int numchannels)
|
|
{
|
|
this.samplerate = samplerate;
|
|
this.numchannels = numchannels;
|
|
filechain = null;
|
|
checkargs();
|
|
opencurrent(s);
|
|
}
|
|
|
|
/// <summary>
|
|
/// initializes WavWriter with an enumeration of Streams
|
|
/// one is consumed every time 2G is hit
|
|
/// if the enumerator runs out before the audio stream does, the last file could be >2G
|
|
/// </summary>
|
|
/// <param name="ss">WavWriter now owns any of these streams that it enumerates</param>
|
|
/// <param name="samplerate">sampling rate in HZ</param>
|
|
/// <param name="numchannels">number of audio channels</param>
|
|
public WavWriter(IEnumerator<Stream> ss, int samplerate, int numchannels)
|
|
{
|
|
this.samplerate = samplerate;
|
|
this.numchannels = numchannels;
|
|
checkargs();
|
|
filechain = ss;
|
|
// advance to first
|
|
if (!filechain.MoveNext())
|
|
throw new ArgumentException("Iterator was empty!");
|
|
opencurrent(ss.Current);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// slim wrapper on WavWriter that implements IVideoWriter (discards all video!)
|
|
/// </summary>
|
|
public class WavWriterV : IVideoWriter
|
|
{
|
|
public void SetVideoCodecToken(IDisposable token) { }
|
|
public void AddFrame(IVideoProvider source) { }
|
|
public void SetMovieParameters(int fpsnum, int fpsden) { }
|
|
public void SetVideoParameters(int width, int height) { }
|
|
public void SetFrame(int frame) { }
|
|
|
|
class WavWriterVToken : IDisposable
|
|
{
|
|
public void Dispose() { }
|
|
}
|
|
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
|
|
{
|
|
// don't care
|
|
return new WavWriterVToken();
|
|
}
|
|
|
|
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
|
{
|
|
this.sampleRate = sampleRate;
|
|
this.channels = channels;
|
|
if (bits != 16)
|
|
throw new ArgumentException("Only support 16bit audio!");
|
|
}
|
|
|
|
public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords)
|
|
{
|
|
// not implemented
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (wavwriter != null)
|
|
wavwriter.Dispose();
|
|
}
|
|
|
|
WavWriter wavwriter = null;
|
|
int sampleRate = 0;
|
|
int channels = 0;
|
|
|
|
/// <summary>
|
|
/// create a simple wav stream iterator
|
|
/// </summary>
|
|
/// <param name="template"></param>
|
|
/// <returns></returns>
|
|
static IEnumerator<Stream> CreateStreamIterator(string template)
|
|
{
|
|
string dir = Path.GetDirectoryName(template);
|
|
string baseName = Path.GetFileNameWithoutExtension(template);
|
|
string ext = Path.GetExtension(template);
|
|
yield return new FileStream(template, FileMode.Create);
|
|
int counter = 1;
|
|
while (true)
|
|
{
|
|
yield return new FileStream(Path.Combine(dir, baseName) + "_" + counter + ext, FileMode.Create);
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
public void OpenFile(string baseName)
|
|
{
|
|
wavwriter = new WavWriter(CreateStreamIterator(baseName), sampleRate, channels);
|
|
}
|
|
|
|
public void CloseFile()
|
|
{
|
|
wavwriter.Close();
|
|
wavwriter.Dispose();
|
|
wavwriter = null;
|
|
}
|
|
|
|
public void AddSamples(short[] samples)
|
|
{
|
|
wavwriter.writesamples(samples);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "WAV writer";
|
|
}
|
|
|
|
public string WriterDescription()
|
|
{
|
|
return "Writes a series of standard RIFF wav files containing uncompressed audio. Does not write video. Splits every 2G.";
|
|
}
|
|
|
|
public string DesiredExtension()
|
|
{
|
|
return "wav";
|
|
}
|
|
|
|
|
|
public void SetDefaultVideoCodecToken()
|
|
{
|
|
// don't use codec tokens, so don't care
|
|
}
|
|
|
|
public string ShortName()
|
|
{
|
|
return "wave";
|
|
}
|
|
}
|
|
}
|