diff --git a/BizHawk.MultiClient/JMDWriter.cs b/BizHawk.MultiClient/JMDWriter.cs
index 6566f359d9..16d714f464 100644
--- a/BizHawk.MultiClient/JMDWriter.cs
+++ b/BizHawk.MultiClient/JMDWriter.cs
@@ -9,6 +9,12 @@ using ICSharpCode.SharpZipLib.Zip.Compression;
namespace BizHawk.MultiClient
{
+ ///
+ /// implements IVideoWriter, outputting to format "JMD"
+ /// this is the JPC-rr multidump format; there are no filesize limits, and resolution can switch dynamically
+ /// so each dump is always one file
+ /// they can be processed with JPC-rr streamtools or JMDSource (avisynth)
+ ///
class JMDWriter : IVideoWriter
{
///
@@ -20,12 +26,18 @@ namespace BizHawk.MultiClient
{
}
+ ///
+ /// how hard the zlib compressor works
+ ///
public int compressionlevel
{
get;
set;
}
+ ///
+ /// number of threads to be used for video compression (sort of)
+ ///
public int numthreads
{
get;
@@ -71,31 +83,424 @@ namespace BizHawk.MultiClient
///
/// actual disk file being written
///
- FileStream JMDfile;
+ JMDfile jmdfile;
///
- /// current timestamp offset in JMD
- /// ie, (number of ffffffffff appearances) * (ffffffff)
+ /// metadata for a movie
+ /// not needed if we aren't dumping something that's not a movie
///
- UInt64 timestampoff;
+ class MovieMetaData
+ {
+ ///
+ /// name of the game (rom)
+ ///
+ public string gamename;
+ ///
+ /// author(s) names
+ ///
+ public string authors;
+ ///
+ /// total length of the movie: ms
+ ///
+ public UInt64 lengthms;
+ ///
+ /// number of rerecords
+ ///
+ public UInt64 rerecords;
+ }
///
- /// total number of video frames, used to calculate timestamps
+ /// represents the metadata for the active movie (if applicable)
///
- UInt64 totalframes;
+ MovieMetaData moviemetadata;
///
- /// total number of audio samples, used to calculate timestamps
+ /// represents a JMD file packet ready to be written except for sorting and timestamp offset
///
- UInt64 totalsamples;
+ class JMDPacket
+ {
+ public UInt16 stream;
+ public UInt64 timestamp; // final muxed timestamp will be relative to previous
+ public byte subtype;
+ public byte[] data;
+ }
- // movie metadata
+ ///
+ /// writes JMDfile packets to an underlying bytestream
+ /// handles one video, one pcm audio, and one metadata track
+ ///
+ class JMDfile
+ {
+ ///
+ /// current timestamp position
+ ///
+ UInt64 timestampoff;
+ ///
+ /// total number of video frames written
+ ///
+ UInt64 totalframes;
+ ///
+ /// total number of sample pairs written
+ ///
+ UInt64 totalsamples;
- string gamename;
- string authors;
- UInt64 lengthms;
- UInt64 rerecords;
+ ///
+ /// fps of the video stream is fpsnum/fpsden
+ ///
+ int fpsnum;
+ ///
+ /// fps of the video stream is fpsnum/fpsden
+ ///
+ int fpsden;
+ ///
+ /// audio samplerate in hz
+ ///
+ int audiosamplerate;
+ ///
+ /// true if input will be stereo; mono otherwise
+ /// output stream is always stereo
+ ///
+ bool stereo;
+ ///
+ /// underlying bytestream that is being written to
+ ///
+ Stream f;
+ public JMDfile(Stream f, int fpsnum, int fpsden, int audiosamplerate, bool stereo)
+ {
+ if (!f.CanWrite)
+ throw new ArgumentException("Stream must be writable!");
+ this.f = f;
+ this.fpsnum = fpsnum;
+ this.fpsden = fpsden;
+ this.audiosamplerate = audiosamplerate;
+ this.stereo = stereo;
+
+ timestampoff = 0;
+ totalframes = 0;
+ totalsamples = 0;
+
+ astorage = new Queue();
+ vstorage = new Queue();
+
+ writeheader();
+ }
+
+ ///
+ /// write header to the JPC file
+ /// assumes one video, one audio, and one metadata stream, with hardcoded IDs
+ ///
+ void writeheader()
+ {
+ // write JPC MAGIC
+ writeBE16(0xffff);
+ f.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
+ }
+
+ ///
+ /// write metadata for a movie file
+ /// can be called at any time
+ ///
+ /// metadata to write
+ public void writemetadata(MovieMetaData mmd)
+ {
+ byte[] temp;
+ // write metadatas
+ writeBE16(2); // data channel
+ writeBE32(0); // timestamp (same time as previous packet)
+ f.WriteByte(71); // gamename
+ temp = System.Text.Encoding.UTF8.GetBytes(mmd.gamename);
+ writeVar(temp.Length);
+ f.Write(temp, 0, temp.Length);
+
+ writeBE16(2);
+ writeBE32(0);
+ f.WriteByte(65); // authors
+ temp = System.Text.Encoding.UTF8.GetBytes(mmd.authors);
+ writeVar(temp.Length);
+ f.Write(temp, 0, temp.Length);
+
+ writeBE16(2);
+ writeBE32(0);
+ f.WriteByte(76); // length
+ writeVar(8);
+ writeBE64(mmd.lengthms * 1000000);
+
+ writeBE16(2);
+ writeBE32(0);
+ f.WriteByte(82); // rerecords
+ writeVar(8);
+ writeBE64(mmd.rerecords);
+ }
+
+ ///
+ /// write big endian 16 bit unsigned
+ ///
+ ///
+ void writeBE16(UInt16 v)
+ {
+ byte[] b = new byte[2];
+ b[0] = (byte)(v >> 8);
+ b[1] = (byte)(v & 255);
+ f.Write(b, 0, 2);
+ }
+
+ ///
+ /// write big endian 32 bit unsigned
+ ///
+ ///
+ void writeBE32(UInt32 v)
+ {
+ byte[] b = new byte[4];
+ b[0] = (byte)(v >> 24);
+ b[1] = (byte)(v >> 16);
+ b[2] = (byte)(v >> 8);
+ b[3] = (byte)(v & 255);
+ f.Write(b, 0, 4);
+ }
+
+ ///
+ /// write big endian 64 bit unsigned
+ ///
+ ///
+ void writeBE64(UInt64 v)
+ {
+ byte[] b = new byte[8];
+ for (int i = 7; i >= 0; i--)
+ {
+ b[i] = (byte)(v & 255);
+ v >>= 8;
+ }
+ f.Write(b, 0, 8);
+ }
+
+ ///
+ /// write variable length value
+ /// encoding is similar to MIDI
+ ///
+ ///
+ void writeVar(UInt64 v)
+ {
+ byte[] b = new byte[10];
+ int i = 0;
+ while (v > 0)
+ {
+ if (i > 0)
+ b[i++] = (byte)((v & 127) | 128);
+ else
+ b[i++] = (byte)(v & 127);
+ v /= 128;
+ }
+ if (i == 0)
+ f.WriteByte(0);
+ else
+ for (; i > 0; i--)
+ f.WriteByte(b[i - 1]);
+ }
+
+ ///
+ /// write variable length value
+ /// encoding is similar to MIDI
+ ///
+ ///
+ void writeVar(int v)
+ {
+ if (v < 0)
+ throw new ArgumentException("length cannot be less than 0!");
+ writeVar((UInt64)v);
+ }
+
+ ///
+ /// creates a timestamp out of fps value
+ ///
+ /// fpsnum
+ /// fpsden
+ /// frame position
+ /// timestamp in nanoseconds
+ static UInt64 timestampcalc(int rate, int scale, UInt64 pos)
+ {
+ // rate/scale events per second
+ // timestamp is in nanoseconds
+ // round down, consistent with JPC-rr apparently?
+ var b = new System.Numerics.BigInteger(pos) * scale * 1000000000 / rate;
+
+ return (UInt64)b;
+ }
+
+ ///
+ /// actually write a packet to file
+ /// timestamp sequence must be nondecreasing
+ ///
+ ///
+ void writeActual(JMDPacket j)
+ {
+ if (j.timestamp < timestampoff)
+ throw new ArithmeticException("JMD Timestamp problem?");
+ UInt64 timestampout = j.timestamp - timestampoff;
+ while (timestampout > 0xffffffff)
+ {
+ timestampout -= 0xffffffff;
+ // write timestamp skipper
+ for (int i = 0; i < 6; i++)
+ f.WriteByte(0xff);
+ }
+ timestampoff = j.timestamp;
+ writeBE16(j.stream);
+ writeBE32((UInt32)timestampout);
+ f.WriteByte(j.subtype);
+ writeVar((UInt64)j.data.LongLength);
+ f.Write(j.data, 0, j.data.Length);
+ }
+
+ ///
+ /// assemble JMDPacket and send to packetqueue
+ ///
+ /// zlibed frame with width and height prepended
+ public void AddVideo(byte[] source)
+ {
+ var j = new JMDPacket();
+ j.stream = 0;
+ j.subtype = 1; // zlib compressed, other possibility is 0 = uncompressed
+ j.data = source;
+ j.timestamp = timestampcalc(fpsnum, fpsden, (UInt64)totalframes);
+ totalframes++;
+ writevideo(j);
+ }
+
+ ///
+ /// assemble JMDPacket and send to packetqueue
+ /// one audio packet is split up into many many JMD packets, since JMD requires only 2 samples (1 left, 1 right) per packet
+ ///
+ ///
+ public void AddSamples(short[] samples)
+ {
+ if (!stereo)
+ for (int i = 0; i < samples.Length; i++)
+ doaudiopacket(samples[i], samples[i]);
+ else
+ for (int i = 0; i < samples.Length / 2; i++)
+ doaudiopacket(samples[2 * i], samples[2 * i + 1]);
+ }
+
+ ///
+ /// helper function makes a JMDPacket out of one sample pair and adds it to the order queue
+ ///
+ /// left sample
+ /// right sample
+ void doaudiopacket(short l, short r)
+ {
+ var j = new JMDPacket();
+ j.stream = 1;
+ j.subtype = 1; // raw PCM audio
+ j.data = new byte[4];
+ j.data[0] = (byte)(l >> 8);
+ j.data[1] = (byte)(l & 255);
+ j.data[2] = (byte)(r >> 8);
+ j.data[3] = (byte)(r & 255);
+
+ j.timestamp = timestampcalc(audiosamplerate, 1, totalsamples);
+ totalsamples++;
+ writesound(j);
+ }
+
+ // ensure outputs are in order
+ // JMD packets must be in nondecreasing timestamp order, but there's no obligation
+ // for us to get handed that. this code is a bit overcomplex to handle edge cases
+ // that may not be a problem with the current system?
+
+ ///
+ /// collection of JMDpackets yet to be written (audio)
+ ///
+ Queue astorage;
+ ///
+ /// collection of JMDpackets yet to be written (video)
+ ///
+ Queue vstorage;
+
+ ///
+ /// add a sound packet to the file write queue
+ /// will be written when order-appropriate wrt video
+ /// the sound packets added must be internally ordered (but need not match video order)
+ ///
+ ///
+ void writesound(JMDPacket j)
+ {
+ while (vstorage.Count > 0)
+ {
+ var p = vstorage.Peek();
+ if (p.timestamp <= j.timestamp)
+ writeActual(vstorage.Dequeue());
+ else
+ break;
+ }
+ astorage.Enqueue(j);
+ }
+
+ ///
+ /// add a video packet to the file write queue
+ /// will be written when order-appropriate wrt audio
+ /// the video packets added must be internally ordered (but need not match audio order)
+ ///
+ ///
+ void writevideo(JMDPacket j)
+ {
+ while (astorage.Count > 0)
+ {
+ var p = astorage.Peek();
+ if (p.timestamp <= j.timestamp)
+ writeActual(astorage.Dequeue());
+ else
+ break;
+ }
+ vstorage.Enqueue(j);
+ }
+
+ ///
+ /// flush all remaining JMDPackets to file
+ /// call before closing the file
+ ///
+ void flushpackets()
+ {
+ while (astorage.Count > 0 && vstorage.Count > 0)
+ {
+ var ap = astorage.Peek();
+ var av = vstorage.Peek();
+ if (ap.timestamp <= av.timestamp)
+ writeActual(astorage.Dequeue());
+ else
+ writeActual(vstorage.Dequeue());
+ }
+ while (astorage.Count > 0)
+ writeActual(astorage.Dequeue());
+ while (vstorage.Count > 0)
+ writeActual(vstorage.Dequeue());
+ }
+
+ ///
+ /// flush any remaining packets and close underlying stream
+ ///
+ public void Close()
+ {
+ flushpackets();
+ f.Close();
+ }
+ }
///
/// sets default (probably wrong) parameters
@@ -109,10 +514,7 @@ namespace BizHawk.MultiClient
audiobits = 8;
token = null;
- gamename = "";
- authors = "";
- lengthms = 0;
- rerecords = 0;
+ moviemetadata = null;
}
public void Dispose()
@@ -120,7 +522,6 @@ namespace BizHawk.MultiClient
// we have no unmanaged resources
}
-
///
/// sets the codec token to be used for video compression
///
@@ -191,7 +592,6 @@ namespace BizHawk.MultiClient
audiobits = bits;
}
-
///
/// opens a recording stream
/// set a video codec token first.
@@ -202,61 +602,11 @@ namespace BizHawk.MultiClient
if (ext == null || ext.ToLower() != ".jmd")
baseName = baseName + ".jmd";
- JMDfile = File.Open(baseName, FileMode.OpenOrCreate);
- timestampoff = 0;
- totalframes = 0;
- totalsamples = 0;
+ jmdfile = new JMDfile(File.Open(baseName, FileMode.OpenOrCreate), fpsnum, fpsden, audiosamplerate, audiochannels == 2);
- // 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);
- }
+ if (moviemetadata != null)
+ jmdfile.writemetadata(moviemetadata);
// start up thread
// problem: since audio chunks and video frames both go through here, exactly how many zlib workers
@@ -266,8 +616,6 @@ namespace BizHawk.MultiClient
workerT = new System.Threading.Thread(new System.Threading.ThreadStart(threadproc));
workerT.Start();
GzipFrameDelegate = new GzipFrameD(GzipFrame);
- astorage = new Queue();
- vstorage = new Queue();
}
// some of this code is copied from AviWriter... not sure how if at all it should be abstracted
@@ -291,9 +639,9 @@ namespace BizHawk.MultiClient
{
Object o = threadQ.Take();
if (o is IAsyncResult)
- AddFrameEx(GzipFrameDelegate.EndInvoke((IAsyncResult)o));
+ jmdfile.AddVideo(GzipFrameDelegate.EndInvoke((IAsyncResult)o));
else if (o is short[])
- AddSamplesEx((short[])o);
+ jmdfile.AddSamples((short[])o);
else
// anything else is assumed to be quit time
return;
@@ -306,180 +654,6 @@ namespace BizHawk.MultiClient
}
}
- ///
- /// write big endian 16 bit unsigned to JMDfile
- ///
- ///
- void writeBE16(UInt16 v)
- {
- byte[] b = new byte[2];
- b[0] = (byte)(v >> 8);
- b[1] = (byte)(v & 255);
- JMDfile.Write(b, 0, 2);
- }
-
- ///
- /// write big endian 32 bit unsigned to JMDfile
- ///
- ///
- void writeBE32(UInt32 v)
- {
- byte[] b = new byte[4];
- b[0] = (byte)(v >> 24);
- b[1] = (byte)(v >> 16);
- b[2] = (byte)(v >> 8);
- b[3] = (byte)(v & 255);
- JMDfile.Write(b, 0, 4);
- }
-
- ///
- /// write big endian 64 bit unsigned to JMDfile
- ///
- ///
- void writeBE64(UInt64 v)
- {
- byte[] b = new byte[8];
- for (int i = 7; i >= 0; i--)
- {
- b[i] = (byte)(v & 255);
- v >>= 8;
- }
- JMDfile.Write(b, 0, 8);
- }
-
- ///
- /// write variable length number to file
- /// encoding is similar to MIDI
- ///
- ///
- void writeVar(UInt64 v)
- {
- byte[] b = new byte[10];
- int i = 0;
- while (v > 0)
- {
- if (i > 0)
- b[i++] = (byte)((v & 127) | 128);
- else
- b[i++] = (byte)(v & 127);
- v /= 128;
- }
- if (i == 0)
- JMDfile.WriteByte(0);
- else
- for (; i > 0; i--)
- JMDfile.WriteByte(b[i - 1]);
- }
-
- ///
- /// write variable length number to file
- /// encoding is similar to MIDI
- ///
- ///
- void writeVar(int v)
- {
- if (v < 0)
- throw new ArgumentException("length cannot be less than 0!");
- writeVar((UInt64)v);
- }
-
- ///
- /// write packet, but they have to be in order!
- ///
- ///
- void writeActual(JMDPacket j)
- {
- if (j.timestamp < timestampoff)
- throw new ArithmeticException("JMD Timestamp problem?");
- UInt64 timestampout = j.timestamp - timestampoff;
- while (timestampout > 0xffffffff)
- {
- timestampout -= 0xffffffff;
- // write timestamp skipper
- for (int i = 0; i < 6; i++)
- JMDfile.WriteByte(0xff);
- }
- timestampoff = j.timestamp;
- writeBE16(j.stream);
- writeBE32((UInt32)timestampout);
- JMDfile.WriteByte(j.subtype);
- writeVar((UInt64)j.data.LongLength);
- JMDfile.Write(j.data, 0, j.data.Length);
- }
-
-
- // ensure outputs are in order
- // JMD packets must be in nondecreasing timestamp order, but there's no obligation
- // for us to get handed that. this code is a bit overcomplex to handle edge cases
- // that may not be a problem with the current system?
-
- ///
- /// collection of JMDpackets yet to be written (audio)
- /// assumed to be in order
- ///
- Queue astorage;
- ///
- /// collection of JMDpackets yet to be written (video)
- /// assumed to be in order
- ///
- Queue vstorage;
-
- ///
- /// add a sound packet to the file write queue
- /// will be written when order-appropriate wrt video
- ///
- ///
- void writesound(JMDPacket j)
- {
- while (vstorage.Count > 0)
- {
- var p = vstorage.Peek();
- if (p.timestamp <= j.timestamp)
- writeActual(vstorage.Dequeue());
- else
- break;
- }
- astorage.Enqueue(j);
- }
-
- ///
- /// add a video packet to the file write queue
- /// will be written when order-appropriate wrt audio
- ///
- ///
- void writevideo(JMDPacket j)
- {
- while (astorage.Count > 0)
- {
- var p = astorage.Peek();
- if (p.timestamp <= j.timestamp)
- writeActual(astorage.Dequeue());
- else
- break;
- }
- vstorage.Enqueue(j);
- }
- ///
- /// flush all remaining JMDPackets to file
- /// call before closing the file
- ///
- void flushpackets()
- {
- while (astorage.Count > 0 && vstorage.Count > 0)
- {
- var ap = astorage.Peek();
- var av = vstorage.Peek();
- if (ap.timestamp <= av.timestamp)
- writeActual(astorage.Dequeue());
- else
- writeActual(vstorage.Dequeue());
- }
- while (astorage.Count > 0)
- writeActual(astorage.Dequeue());
- while (vstorage.Count > 0)
- writeActual(vstorage.Dequeue());
- }
-
///
/// close recording stream
///
@@ -488,9 +662,7 @@ namespace BizHawk.MultiClient
threadQ.Add(new Object()); // acts as stop message
workerT.Join();
- flushpackets();
-
- JMDfile.Close();
+ jmdfile.Close();
}
///
@@ -503,7 +675,6 @@ namespace BizHawk.MultiClient
public int BufferWidth;
public int BufferHeight;
- public int BackgroundColor;
public VideoCopy(IVideoProvider c)
{
int[] vb = c.GetVideoBuffer();
@@ -519,7 +690,6 @@ namespace BizHawk.MultiClient
//Buffer.BlockCopy(vb, 0, VideoBuffer, 0, VideoBuffer.Length);
BufferWidth = c.BufferWidth;
BufferHeight = c.BufferHeight;
- BackgroundColor = c.BackgroundColor;
}
}
@@ -528,8 +698,8 @@ namespace BizHawk.MultiClient
/// the byte array includes width and height dimensions at the beginning
/// this is run asynchronously for speedup, as compressing can be slow
///
- ///
- ///
+ /// video frame to compress
+ /// zlib compressed frame, with width and height prepended
byte[] GzipFrame(VideoCopy v)
{
MemoryStream m = new MemoryStream();
@@ -555,6 +725,9 @@ namespace BizHawk.MultiClient
/// VideoCopy to compress
/// gzipped stream with width and height prepended
delegate byte[] GzipFrameD(VideoCopy v);
+ ///
+ /// delegate for GzipFrame
+ ///
GzipFrameD GzipFrameDelegate;
///
@@ -580,89 +753,16 @@ namespace BizHawk.MultiClient
threadQ.Add((short[])samples.Clone());
}
- ///
- /// assemble JMDPacket and send to packetqueue
- ///
- ///
- void AddFrameEx(byte[] source)
- {
- // at this point, VideoCopy contains a zlib compressed bytestream
- var j = new JMDPacket();
- j.stream = 0;
- j.subtype = 1; // zlib compressed, other possibility is 0 = uncompressed
- j.data = source;
- j.timestamp = timestampcalc(fpsnum, fpsden, (UInt64)totalframes);
- totalframes++;
- writevideo(j);
- }
-
- ///
- /// assemble JMDPacket and send to packetqueue
- /// one audio packet is split up into many many JMD packets, since JMD requires only 2 samples (1 left, 1 right) per packet
- ///
- ///
- void AddSamplesEx(short[] samples)
- {
- if (audiochannels == 1)
- for (int i = 0; i < samples.Length; i++)
- doaudiopacket(samples[i], samples[i]);
- else
- for (int i = 0; i < samples.Length / 2; i++)
- doaudiopacket(samples[2 * i], samples[2 * i + 1]);
- }
- void doaudiopacket(short l, short r)
- {
- var j = new JMDPacket();
- j.stream = 1;
- j.subtype = 1; // raw PCM audio
- j.data = new byte[4];
- j.data[0] = (byte)(l >> 8);
- j.data[1] = (byte)(l & 255);
- j.data[2] = (byte)(r >> 8);
- j.data[3] = (byte)(r & 255);
-
- j.timestamp = timestampcalc(audiosamplerate, 1, totalsamples);
- totalsamples++;
- writesound(j);
- }
-
- ///
- /// represents a JMD file packet ready to be written except for sorting and timestamp offset
- ///
- class JMDPacket
- {
- public UInt16 stream;
- public UInt64 timestamp; // haven't subtracted timestampoffs yet
- public byte subtype;
- public byte[] data;
- }
-
- ///
- /// creates a timestamp out of fps value
- ///
- /// fpsnum
- /// fpsden
- /// frame position
- ///
- static UInt64 timestampcalc(int rate, int scale, UInt64 pos)
- {
- // rate/scale events per second
- // timestamp is in nanoseconds
- // round down, consistent with JPC-rr apparently?
- var b = new System.Numerics.BigInteger(pos) * scale * 1000000000 / rate;
-
- return (UInt64)b;
- }
///
/// set metadata parameters; should be called before opening file
- /// NYI
///
public void SetMetaData(string gameName, string authors, UInt64 lengthMS, UInt64 rerecords)
{
- this.gamename = gameName;
- this.authors = authors;
- this.lengthms = lengthMS;
- this.rerecords = rerecords;
+ moviemetadata = new MovieMetaData();
+ moviemetadata.gamename = gameName;
+ moviemetadata.authors = authors;
+ moviemetadata.lengthms = lengthMS;
+ moviemetadata.rerecords = rerecords;
}
}
}