using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using ICSharpCode.SharpZipLib.Zip.Compression;
namespace BizHawk.MultiClient
{
class JMDWriter : IVideoWriter
{
///
/// carries private compression information data
///
class CodecToken : IDisposable
{
public void Dispose()
{
}
public int compressionlevel
{
get;
set;
}
public int numthreads
{
get;
set;
}
///
/// instantiates a CodecToken with default parameters
///
public CodecToken()
{
compressionlevel = Deflater.DEFAULT_COMPRESSION;
numthreads = 3;
}
}
///
/// stores compression parameters
///
CodecToken token;
///
/// fps numerator, constant
///
int fpsnum;
///
/// fps denominator, constant
///
int fpsden;
///
/// audio samplerate, constant
///
int audiosamplerate;
///
/// audio number of channels, constant; 1 or 2 only
///
int audiochannels;
///
/// audio bits per sample, constant; only 16 supported
///
int audiobits;
///
/// actual disk file being written
///
FileStream JMDfile;
///
/// current timestamp offset in JMD
/// ie, (number of ffffffffff appearances) * (ffffffff)
///
UInt64 timestampoff;
///
/// total number of video frames, used to calculate timestamps
///
UInt64 totalframes;
///
/// total number of audio samples, used to calculate timestamps
///
UInt64 totalsamples;
// movie metadata
string gamename;
string authors;
UInt64 lengthms;
UInt64 rerecords;
///
/// sets default (probably wrong) parameters
///
public JMDWriter()
{
fpsnum = 25;
fpsden = 1;
audiosamplerate = 22050;
audiochannels = 1;
audiobits = 8;
token = null;
gamename = "";
authors = "";
lengthms = 0;
rerecords = 0;
}
public void Dispose()
{
// we have no unmanaged resources
}
///
/// sets the codec token to be used for video compression
///
public void SetVideoCodecToken(IDisposable token)
{
if (token is CodecToken)
this.token = (CodecToken)token;
else
throw new ArgumentException("codec token must be of right type");
}
///
/// obtain a set of recording compression parameters
///
/// hwnd to attach to if the user is shown config dialog
/// codec token, dispose of it when you're done with it
public IDisposable AcquireVideoCodecToken(IntPtr hwnd)
{
CodecToken ret = new CodecToken();
int t = ret.numthreads;
// Deflater.DEFAULT_COMPRESSION is actually a magic value and is not in the range NO_COMPRESSION..BEST_COMPRESSION
// so our default is just a guesstimate in the middle
int c = (Deflater.BEST_COMPRESSION + Deflater.NO_COMPRESSION) / 2;
if (!JMDForm.DoCompressionDlg(ref t, ref c, 1, 6, Deflater.NO_COMPRESSION, Deflater.BEST_COMPRESSION, hwnd))
return null;
ret.numthreads = t;
ret.compressionlevel = c;
return ret;
}
///
/// set framerate to fpsnum/fpsden (assumed to be unchanging over the life of the stream)
///
public void SetMovieParameters(int fpsnum, int fpsden)
{
this.fpsnum = fpsnum;
this.fpsden = fpsden;
}
///
/// set resolution parameters (width x height)
/// must be set before file is opened
/// can be changed in future
/// should always match IVideoProvider
///
///
///
public void SetVideoParameters(int width, int height)
{
// each frame is dumped independently with its own resolution tag, so we don't care to store this
}
///
/// set audio parameters. cannot change later
///
public void SetAudioParameters(int sampleRate, int channels, int bits)
{
// the sampleRate limits are arbitrary, just to catch things which are probably silly-wrong
// if a larger range of sampling rates is needed, it should be supported
if (sampleRate < 8000 || sampleRate > 96000 || channels < 1 || channels > 2 || bits != 16)
throw new ArgumentException("Audio parameters out of range!");
audiosamplerate = sampleRate;
audiochannels = channels;
audiobits = bits;
}
///
/// opens a recording stream
/// set a video codec token first.
///
public void OpenFile(string baseName)
{
string ext = Path.GetExtension(baseName);
if (ext == null || ext.ToLower() != ".jmd")
baseName = baseName + ".jmd";
JMDfile = File.Open(baseName, FileMode.OpenOrCreate);
timestampoff = 0;
totalframes = 0;
totalsamples = 0;
// write JPC MAGIC
writeBE16(0xffff);
JMDfile.Write(Encoding.ASCII.GetBytes("JPCRRMULTIDUMP"), 0, 14);
// write channel table
writeBE16(3); // number of streams
// for each stream
writeBE16(0); // channel 0
writeBE16(0); // video
writeBE16(0); // no name
writeBE16(1); // channel 1
writeBE16(1); // pcm audio
writeBE16(0); // no name
writeBE16(2); // channel 2
writeBE16(5); // metadata
writeBE16(0); // no name
if (gamename != null && gamename != String.Empty)
{
byte[] temp;
// write metadatas
writeBE16(2); // data channel
writeBE32(0); // timestamp 0;
JMDfile.WriteByte(71); // gamename
temp = System.Text.Encoding.UTF8.GetBytes(gamename);
writeVar(temp.Length);
JMDfile.Write(temp, 0, temp.Length);
writeBE16(2);
writeBE32(0);
JMDfile.WriteByte(65); // authors
temp = System.Text.Encoding.UTF8.GetBytes(authors);
writeVar(temp.Length);
JMDfile.Write(temp, 0, temp.Length);
writeBE16(2);
writeBE32(0);
JMDfile.WriteByte(76); // length
writeVar(8);
writeBE64(lengthms * 1000000);
writeBE16(2);
writeBE32(0);
JMDfile.WriteByte(82); // rerecords
writeVar(8);
writeBE64(rerecords);
}
// start up thread
// problem: since audio chunks and video frames both go through here, exactly how many zlib workers
// gives is not known without knowing how the emulator will chunk audio packets
// this shouldn't affect results though, just performance
threadQ = new System.Collections.Concurrent.BlockingCollection