From 78a0494708755d4cc820fb3b43a23f407a2bb151 Mon Sep 17 00:00:00 2001 From: goyuken Date: Fri, 11 May 2012 17:00:44 +0000 Subject: [PATCH] add WavWriter, writes standard WAV files for capture. For the moment, only available as its own IVideoWriter (that discards all video frames). Choose by selecting .wav format in the start avi capture file select dialog. --- .../BizHawk.MultiClient.csproj | 1 + BizHawk.MultiClient/MainForm.cs | 16 +- BizHawk.MultiClient/WavWriter.cs | 272 ++++++++++++++++++ 3 files changed, 282 insertions(+), 7 deletions(-) create mode 100644 BizHawk.MultiClient/WavWriter.cs diff --git a/BizHawk.MultiClient/BizHawk.MultiClient.csproj b/BizHawk.MultiClient/BizHawk.MultiClient.csproj index dd99f68b99..e9a8a41aa9 100644 --- a/BizHawk.MultiClient/BizHawk.MultiClient.csproj +++ b/BizHawk.MultiClient/BizHawk.MultiClient.csproj @@ -304,6 +304,7 @@ + GifAnimator.cs diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index 0e46b545d2..6730d01b06 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -2668,7 +2668,7 @@ namespace BizHawk.MultiClient sfd.FileName = "NULL"; sfd.InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.AVIPath, ""); } - sfd.Filter = "AVI (*.avi)|*.avi|JMD (*.jmd)|*.jmd|All Files|*.*"; + sfd.Filter = "AVI (*.avi)|*.avi|JMD (*.jmd)|*.jmd|WAV (*.wav)|*.wav|All Files|*.*"; Global.Sound.StopSound(); var result = sfd.ShowDialog(); Global.Sound.StartSound(); @@ -2682,12 +2682,14 @@ namespace BizHawk.MultiClient IVideoWriter aw; string ext = Path.GetExtension(sfd.FileName).ToLower(); - if (ext == ".jmd") - aw = new JMDWriter(); - else if (ext == ".avi") - aw = new AviWriter(); - else // hmm? - aw = new AviWriter(); + if (ext == ".jmd") + aw = new JMDWriter(); + else if (ext == ".avi") + aw = new AviWriter(); + else if (ext == ".wav") + aw = new WavWriterV(); + else // hmm? + aw = new AviWriter(); try { aw.SetMovieParameters(fps, 0x01000000); diff --git a/BizHawk.MultiClient/WavWriter.cs b/BizHawk.MultiClient/WavWriter.cs new file mode 100644 index 0000000000..22ff286ec2 --- /dev/null +++ b/BizHawk.MultiClient/WavWriter.cs @@ -0,0 +1,272 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace BizHawk.MultiClient +{ + /// + /// writes MS standard riff files containing uncompressed PCM wav data + /// supports 16 bit signed data only + /// + public class WavWriter : IDisposable + { + /// + /// underlying file being written to + /// + BinaryWriter file; + /// + /// sequence of files to write to (split on 32 bit limit) + /// + IEnumerator filechain; + + /// + /// samplerate in HZ + /// + int samplerate; + /// + /// number of audio channels + /// + int numchannels; + + /// + /// number of bytes of PCM data written to current file + /// + UInt64 numbytes; + + /// + /// number of bytes after which a file split should be made + /// + const UInt64 splitpoint = 2 * 1000 * 1000 * 1000; + + /// + /// write riff headers to current file + /// + void writeheaders() + { + file.Write(Encoding.ASCII.GetBytes("RIFF")); // ChunkID + file.Write((UInt32)0); // ChunkSize + file.Write(Encoding.ASCII.GetBytes("WAVE")); // Format + + file.Write(Encoding.ASCII.GetBytes("fmt ")); // SubchunkID + file.Write((UInt32)16); // SubchunkSize + file.Write((UInt16)1); // AudioFormat (PCM) + file.Write((UInt16)numchannels); // NumChannels + file.Write((UInt32)samplerate); // SampleRate + file.Write((UInt32)(samplerate * numchannels * 2)); // ByteRate + file.Write((UInt16)(numchannels * 2)); // BlockAlign + file.Write((UInt16)16); // BitsPerSample + + file.Write(Encoding.ASCII.GetBytes("data")); // SubchunkID + file.Write((UInt32)0); // SubchunkSize + } + + /// + /// seek back to beginning of file and fix header sizes (if possible) + /// + void finalizeheaders() + { + if (numbytes + 36 >= 0x100000000) + // passed 4G limit, nothing to be done + return; + try + { + file.Seek(4, SeekOrigin.Begin); + file.Write((UInt32)(36 + numbytes)); + file.Seek(40, SeekOrigin.Begin); + file.Write((UInt32)(numbytes)); + } + catch (NotSupportedException) + { // unseekable; oh well + } + } + + /// + /// close current underlying stream + /// + void closecurrent () + { + if (file != null) + { + finalizeheaders(); + file.Close(); + file.Dispose(); + } + file = null; + } + /// + /// open a new underlying stream + /// + /// + void opencurrent (Stream next) + { + file = new BinaryWriter(next, Encoding.ASCII); + numbytes = 0; + writeheaders(); + } + + /// + /// write samples to file + /// + /// samples to write; should contain one for each channel + public void writesamples(short[] samples) + { + file.Write(samples); + numbytes += (UInt64)(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(); + } + + /// + /// finishes writing + /// + public void Close() + { + closecurrent(); + } + + /// + /// checks sampling rate, number of channels for validity + /// + void checkargs() + { + if (samplerate < 1 || numchannels < 1) + throw new ArgumentException("Bad samplerate/numchannels"); + } + + /// + /// initializes WavWriter with a single output stream + /// no attempt is made to split + /// + /// WavWriter now owns this stream + /// sampling rate in HZ + /// number of audio channels + public WavWriter(Stream s, int samplerate, int numchannels) + { + this.samplerate = samplerate; + this.numchannels = numchannels; + filechain = null; + checkargs(); + opencurrent(s); + } + + /// + /// 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 + /// + /// WavWriter now owns any of these streams that it enumerates + /// sampling rate in HZ + /// number of audio channels + public WavWriter(IEnumerator 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); + } + } + + /// + /// slim wrapper on WavWriter that implements IVideoWriter (discards all video!) + /// + 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) { } + + class WavWriterVToken : IDisposable + { + public void Dispose() { } + } + public IDisposable AcquireVideoCodecToken(IntPtr 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; + + /// + /// create a simple wav stream iterator + /// + /// + /// + static IEnumerator 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); + } + } +}