Add NutWriter, which writes uncompressed audio and video to the crappy Nut container. Will be used for piping audio and video at the same time to ffmpeg once it's been tested some more.
This commit is contained in:
parent
c823d6b46a
commit
c2e88829f6
|
@ -281,6 +281,8 @@
|
|||
<Compile Include="NEStools\SpriteViewer.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="NutMuxer.cs" />
|
||||
<Compile Include="NutWriter.cs" />
|
||||
<Compile Include="PCEtools\PCEBGCanvas.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
|
|
|
@ -2748,7 +2748,7 @@ namespace BizHawk.MultiClient
|
|||
sfd.FileName = "NULL";
|
||||
sfd.InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.AVIPath, "");
|
||||
}
|
||||
sfd.Filter = "AVI (*.avi)|*.avi|JMD (*.jmd)|*.jmd|WAV (*.wav)|*.wav|Matroska (*.mkv)|*.mkv|All Files|*.*";
|
||||
sfd.Filter = "AVI (*.avi)|*.avi|JMD (*.jmd)|*.jmd|WAV (*.wav)|*.wav|Matroska (*.mkv)|*.mkv|NUT (*.nut)|*.nut|All Files|*.*";
|
||||
Global.Sound.StopSound();
|
||||
var result = sfd.ShowDialog();
|
||||
Global.Sound.StartSound();
|
||||
|
@ -2770,6 +2770,8 @@ namespace BizHawk.MultiClient
|
|||
aw = new WavWriterV();
|
||||
else if (ext == ".mkv")
|
||||
aw = new FFmpegWriter();
|
||||
else if (ext == ".nut")
|
||||
aw = new NutWriter();
|
||||
else // hmm?
|
||||
aw = new AviWriter();
|
||||
try
|
||||
|
|
|
@ -0,0 +1,518 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
|
||||
namespace BizHawk.MultiClient
|
||||
{
|
||||
/// <summary>
|
||||
/// implements a simple muxer for the NUT media format
|
||||
/// http://ffmpeg.org/~michael/nut.txt
|
||||
/// </summary>
|
||||
class NutMuxer
|
||||
{
|
||||
/* TODO: timestamp sanitization (like JMDWriter) */
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// variable length value, unsigned
|
||||
/// </summary>
|
||||
static void WriteVarU(ulong v, Stream stream)
|
||||
{
|
||||
byte[] b = new byte[10];
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
if (i > 0)
|
||||
b[i++] = (byte)((v & 127) | 128);
|
||||
else
|
||||
b[i++] = (byte)(v & 127);
|
||||
v /= 128;
|
||||
} while (v > 0);
|
||||
for (; i > 0; i--)
|
||||
stream.WriteByte(b[i - 1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// variable length value, unsigned
|
||||
/// </summary>
|
||||
static void WriteVarU(int v, Stream stream)
|
||||
{
|
||||
if (v < 0)
|
||||
throw new ArgumentOutOfRangeException("unsigned must be non-negative");
|
||||
WriteVarU((ulong)v, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// variable length value, unsigned
|
||||
/// </summary>
|
||||
static void WriteVarU(long v, Stream stream)
|
||||
{
|
||||
if (v < 0)
|
||||
throw new ArgumentOutOfRangeException("unsigned must be non-negative");
|
||||
WriteVarU((ulong)v, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// variable length value, signed
|
||||
/// </summary>
|
||||
static void WriteVarS(long v, Stream stream)
|
||||
{
|
||||
ulong temp;
|
||||
if (v < 0)
|
||||
temp = 1 + 2 * (ulong)(-v);
|
||||
else
|
||||
temp = 2 * (ulong)(v);
|
||||
WriteVarU(temp - 1, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// utf-8 string with length prepended
|
||||
/// </summary>
|
||||
static void WriteString(string s, Stream stream)
|
||||
{
|
||||
WriteBytes(Encoding.UTF8.GetBytes(s), stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// arbitrary sequence of bytes with length prepended
|
||||
/// </summary>
|
||||
static void WriteBytes(byte[] b, Stream stream)
|
||||
{
|
||||
WriteVarU(b.Length, stream);
|
||||
stream.Write(b, 0, b.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// big endian 64 bit unsigned
|
||||
/// </summary>
|
||||
/// <param name="v"></param>
|
||||
/// <param name="stream"></param>
|
||||
static void WriteBE64(ulong v, Stream stream)
|
||||
{
|
||||
byte[] b = new byte[8];
|
||||
for (int i = 7; i >= 0; i--)
|
||||
{
|
||||
b[i] = (byte)(v & 255);
|
||||
v >>= 8;
|
||||
}
|
||||
stream.Write(b, 0, 8);
|
||||
}
|
||||
/// <summary>
|
||||
/// big endian 32 bit unsigned
|
||||
/// </summary>
|
||||
/// <param name="v"></param>
|
||||
/// <param name="stream"></param>
|
||||
static void WriteBE32(uint v, Stream stream)
|
||||
{
|
||||
byte[] b = new byte[4];
|
||||
for (int i = 3; i >= 0; i--)
|
||||
{
|
||||
b[i] = (byte)(v & 255);
|
||||
v >>= 8;
|
||||
}
|
||||
stream.Write(b, 0, 4);
|
||||
}
|
||||
/// <summary>
|
||||
/// big endian 32 bit unsigned
|
||||
/// </summary>
|
||||
/// <param name="v"></param>
|
||||
/// <param name="stream"></param>
|
||||
static void WriteBE32(int v, Stream stream)
|
||||
{
|
||||
byte[] b = new byte[4];
|
||||
for (int i = 3; i >= 0; i--)
|
||||
{
|
||||
b[i] = (byte)(v & 255);
|
||||
v >>= 8;
|
||||
}
|
||||
stream.Write(b, 0, 4);
|
||||
}
|
||||
|
||||
static readonly uint[] CRCtable = new uint[]
|
||||
{
|
||||
0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
|
||||
0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
|
||||
0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
|
||||
0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// seems to be different than standard CRC32?????
|
||||
/// </summary>
|
||||
/// <param name="buf"></param>
|
||||
/// <returns></returns>
|
||||
static uint NutCRC32(byte[] buf)
|
||||
{
|
||||
uint crc = 0;
|
||||
for (int i = 0; i < buf.Length; i++)
|
||||
{
|
||||
crc ^= (uint)buf[i] << 24;
|
||||
crc = (crc << 4) ^ CRCtable[crc >> 28];
|
||||
crc = (crc << 4) ^ CRCtable[crc >> 28];
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// writes a single packet out, including checksums
|
||||
/// </summary>
|
||||
class NutPacket : Stream
|
||||
{
|
||||
public enum StartCode : ulong
|
||||
{
|
||||
Main = 0x4e4d7a561f5f04ad,
|
||||
Stream = 0x4e5311405bf2f9db,
|
||||
Syncpoint = 0x4e4be4adeeca4569,
|
||||
Index = 0x4e58dd672f23e64e,
|
||||
Info = 0x4e49ab68b596ba78
|
||||
};
|
||||
|
||||
MemoryStream data;
|
||||
StartCode startcode;
|
||||
Stream underlying;
|
||||
|
||||
public NutPacket(StartCode startcode, Stream underlying)
|
||||
{
|
||||
data = new MemoryStream();
|
||||
this.startcode = startcode;
|
||||
this.underlying = underlying;
|
||||
}
|
||||
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write data out to underlying stream, including header, footer, checksums
|
||||
/// this cannot be done more than once!
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
// first, prep header
|
||||
var header = new MemoryStream();
|
||||
WriteBE64((ulong)startcode, header);
|
||||
WriteVarU(data.Length + 4, header); // +4 for checksum
|
||||
if (data.Length > 4092)
|
||||
WriteBE32(NutCRC32(header.ToArray()), header);
|
||||
var tmp = header.ToArray();
|
||||
underlying.Write(tmp, 0, tmp.Length);
|
||||
|
||||
tmp = data.ToArray();
|
||||
underlying.Write(tmp, 0, tmp.Length);
|
||||
WriteBE32(NutCRC32(tmp), underlying);
|
||||
|
||||
data = null;
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
data.Write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// stores basic AV parameters
|
||||
/// </summary>
|
||||
class AVParams
|
||||
{
|
||||
public int width, height, samplerate, fpsnum, fpsden, channels;
|
||||
/// <summary>
|
||||
/// puts fpsnum, fpsden in lowest terms
|
||||
/// </summary>
|
||||
public void Reduce()
|
||||
{
|
||||
int gcd = (int) BigInteger.GreatestCommonDivisor(new BigInteger(fpsnum), new BigInteger(fpsden));
|
||||
fpsnum /= gcd;
|
||||
fpsden /= gcd;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stores basic AV parameters
|
||||
/// </summary>
|
||||
AVParams avparams;
|
||||
|
||||
/// <summary>
|
||||
/// target output for nut stream
|
||||
/// </summary>
|
||||
Stream output;
|
||||
|
||||
/// <summary>
|
||||
/// PTS of video stream. timebase is 1/framerate, so this is equal to number of frames
|
||||
/// </summary>
|
||||
ulong videopts;
|
||||
|
||||
/// <summary>
|
||||
/// PTS of audio stream. timebase is 1/samplerate, so this is equal to number of samples
|
||||
/// </summary>
|
||||
ulong audiopts;
|
||||
|
||||
/// <summary>
|
||||
/// has EOR been writen on this stream?
|
||||
/// </summary>
|
||||
bool videodone;
|
||||
/// <summary>
|
||||
/// has EOR been written on this stream?
|
||||
/// </summary>
|
||||
bool audiodone;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// write out the main header
|
||||
/// </summary>
|
||||
void writemainheader()
|
||||
{
|
||||
// note: this tag not actually part of main headers
|
||||
var tmp = Encoding.ASCII.GetBytes("nut/multimedia container\0");
|
||||
output.Write(tmp, 0, tmp.Length);
|
||||
|
||||
var header = new NutPacket(NutPacket.StartCode.Main, output);
|
||||
|
||||
WriteVarU(3, header); // version
|
||||
WriteVarU(2, header); // stream_count
|
||||
WriteVarU(65536, header); // max_distance
|
||||
|
||||
WriteVarU(2, header); // time_base_count
|
||||
// timebase is length of single frame, so reversed num+den is intentional
|
||||
WriteVarU(avparams.fpsden, header); // time_base_num[0]
|
||||
WriteVarU(avparams.fpsnum, header); // time_base_den[0]
|
||||
WriteVarU(1, header); // time_base_num[1]
|
||||
WriteVarU(avparams.samplerate, header); // time_base_den[1]
|
||||
|
||||
// frame flag compression is ignored for simplicity
|
||||
for (int i = 0; i < 255; i++) // not 256 because entry 0x4e is skipped (as it would indicate a startcode)
|
||||
{
|
||||
WriteVarU((1 << 12), header); // tmp_flag = FLAG_CODED
|
||||
WriteVarU(0, header); // tmp_fields
|
||||
}
|
||||
|
||||
// header compression ignored because it's not useful to us
|
||||
WriteVarU(0, header); // header_count_minus1
|
||||
|
||||
// BROADCAST_MODE only useful for realtime transmission clock recovery
|
||||
WriteVarU(0, header); // main_flags
|
||||
|
||||
header.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write out the 0th stream header (video)
|
||||
/// </summary>
|
||||
void writevideoheader()
|
||||
{
|
||||
var header = new NutPacket(NutPacket.StartCode.Stream, output);
|
||||
WriteVarU(0, header); // stream_id
|
||||
WriteVarU(0, header); // stream_class = video
|
||||
WriteString("BGRA", header); // fourcc = "BGRA"
|
||||
WriteVarU(0, header); // time_base_id = 0
|
||||
WriteVarU(8, header); // msb_pts_shift
|
||||
WriteVarU(1, header); // max_pts_distance
|
||||
WriteVarU(0, header); // decode_delay
|
||||
WriteVarU(1, header); // stream_flags = FLAG_FIXED_FPS
|
||||
WriteBytes(new byte[0], header); // codec_specific_data
|
||||
|
||||
// stream_class = video
|
||||
WriteVarU(avparams.width, header); // width
|
||||
WriteVarU(avparams.height, header); // height
|
||||
WriteVarU(1, header); // sample_width
|
||||
WriteVarU(1, header); // sample_height
|
||||
WriteVarU(18, header); // colorspace_type = full range rec709 (avisynth's "PC.709")
|
||||
|
||||
header.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write out the 1st stream header (audio)
|
||||
/// </summary>
|
||||
void writeaudioheader()
|
||||
{
|
||||
var header = new NutPacket(NutPacket.StartCode.Stream, output);
|
||||
WriteVarU(1, header); // stream_id
|
||||
WriteVarU(1, header); // stream_class = audio
|
||||
WriteString("\x01\x00\x00\x00", header); // fourcc = 01 00 00 00
|
||||
WriteVarU(1, header); // time_base_id = 1
|
||||
WriteVarU(8, header); // msb_pts_shift
|
||||
WriteVarU(avparams.samplerate, header); // max_pts_distance
|
||||
WriteVarU(0, header); // decode_delay
|
||||
WriteVarU(0, header); // stream_flags = none; no FIXED_FPS because we aren't guaranteeing same-size audio chunks
|
||||
WriteBytes(new byte[0], header); // codec_specific_data
|
||||
|
||||
// stream_class = audio
|
||||
WriteVarU(avparams.samplerate, header); // samplerate_num
|
||||
WriteVarU(1, header); // samplerate_den
|
||||
WriteVarU(avparams.channels, header); // channel_count
|
||||
|
||||
header.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// writes a syncpoint header with already coded universal timestamp
|
||||
/// </summary>
|
||||
void writesyncpoint(ulong global_key_pts)
|
||||
{
|
||||
var header = new NutPacket(NutPacket.StartCode.Syncpoint, output);
|
||||
WriteVarU(global_key_pts, header); // global_key_pts; file starts at time 0
|
||||
WriteVarU(1, header); // back_ptr_div_16 ?????????????????????????????
|
||||
header.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write a video frame to the stream
|
||||
/// </summary>
|
||||
/// <param name="data">raw video data; if length 0, write EOR</param>
|
||||
public void writevideoframe(byte[] data)
|
||||
{
|
||||
if (videodone)
|
||||
throw new Exception("Can't write data after end of relevance!");
|
||||
if (data.Length == 0)
|
||||
videodone = true;
|
||||
writesyncpoint(videopts * 2 + 0);
|
||||
writeframe(data, 0, videopts);
|
||||
videopts++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void writeframe(byte[] data, int stream_id, ulong pts)
|
||||
{
|
||||
var frameheader = new MemoryStream();
|
||||
frameheader.WriteByte(0); // frame_code
|
||||
// frame_flags = FLAG_CODED, so:
|
||||
int flags = 0;
|
||||
flags |= 1 << 0; // FLAG_KEY
|
||||
if (data.Length == 0)
|
||||
flags |= 1 << 1; // FLAG_EOR
|
||||
flags |= 1 << 3; // FLAG_CODED_PTS
|
||||
flags |= 1 << 4; // FLAG_STREAM_ID
|
||||
flags |= 1 << 5; // FLAG_SIZE_MSB
|
||||
flags |= 1 << 6; // FLAG_CHECKSUM
|
||||
WriteVarU(flags, frameheader);
|
||||
WriteVarU(stream_id, frameheader); // stream_id
|
||||
WriteVarU(pts + 256, frameheader); // coded_pts = pts + 1 << msb_pts_shift
|
||||
WriteVarU(data.Length, frameheader); // data_size_msb
|
||||
|
||||
var frameheaderarr = frameheader.ToArray();
|
||||
output.Write(frameheaderarr, 0, frameheaderarr.Length);
|
||||
WriteBE32(NutCRC32(frameheaderarr), output); // checksum
|
||||
output.Write(data, 0, data.Length);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write an audio frame to the stream
|
||||
/// </summary>
|
||||
/// <param name="data">raw audio data; if length 0, write EOR</param>
|
||||
public void writeaudioframe(short[] samples)
|
||||
{
|
||||
if (audiodone)
|
||||
throw new Exception("Can't write audio after end of relevance!");
|
||||
byte[] data = new byte[samples.Length * sizeof (short)];
|
||||
Buffer.BlockCopy(samples, 0, data, 0, data.Length);
|
||||
if (data.Length == 0)
|
||||
audiodone = true;
|
||||
|
||||
writesyncpoint(audiopts * 2 + 1);
|
||||
writeframe(data, 1, audiopts);
|
||||
audiopts += (ulong)samples.Length / (ulong)avparams.channels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// create a new NutMuxer
|
||||
/// </summary>
|
||||
/// <param name="width">video width</param>
|
||||
/// <param name="height">video height</param>
|
||||
/// <param name="fpsnum">fps numerator</param>
|
||||
/// <param name="fpsden">fps denominator</param>
|
||||
/// <param name="samplerate">audio samplerate</param>
|
||||
/// <param name="channels">audio number of channels</param>
|
||||
/// <param name="underlying">Stream to write to</param>
|
||||
public NutMuxer(int width, int height, int fpsnum, int fpsden, int samplerate, int channels, Stream underlying)
|
||||
{
|
||||
avparams = new AVParams();
|
||||
avparams.width = width;
|
||||
avparams.height = height;
|
||||
avparams.fpsnum = fpsnum;
|
||||
avparams.fpsden = fpsden;
|
||||
avparams.Reduce(); // timebases in nut MUST be relatively prime
|
||||
avparams.samplerate = samplerate;
|
||||
avparams.channels = channels;
|
||||
output = underlying;
|
||||
|
||||
audiopts = 0;
|
||||
videopts = 0;
|
||||
|
||||
writemainheader();
|
||||
writevideoheader();
|
||||
writeaudioheader();
|
||||
|
||||
videodone = false;
|
||||
audiodone = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// finish and flush everything
|
||||
/// closes underlying stream!!
|
||||
/// </summary>
|
||||
public void Finish()
|
||||
{
|
||||
if (!videodone)
|
||||
writevideoframe(new byte[0]);
|
||||
if (!audiodone)
|
||||
writeaudioframe(new short[0]);
|
||||
|
||||
output.Close();
|
||||
output = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.MultiClient
|
||||
{
|
||||
/// <summary>
|
||||
/// dumps in the "nut" container format
|
||||
/// uncompressed video and audio
|
||||
/// </summary>
|
||||
class NutWriter : IVideoWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// dummy codec token class
|
||||
/// </summary>
|
||||
class NutWriterToken : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetVideoCodecToken(IDisposable token)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
public IDisposable AcquireVideoCodecToken(IntPtr hwnd)
|
||||
{
|
||||
return new NutWriterToken();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// avparams
|
||||
/// </summary>
|
||||
int fpsnum, fpsden, width, height, sampleRate, channels;
|
||||
|
||||
NutMuxer current = null;
|
||||
string baseName;
|
||||
int segment;
|
||||
|
||||
public void OpenFile(string baseName)
|
||||
{
|
||||
this.baseName = System.IO.Path.GetFileNameWithoutExtension(baseName);
|
||||
segment = 0;
|
||||
|
||||
|
||||
startsegment();
|
||||
}
|
||||
|
||||
void startsegment()
|
||||
{
|
||||
var currentfile = System.IO.File.Open(String.Format("{0}_{1,4:D4}.nut", baseName, segment), System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write);
|
||||
current = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, currentfile);
|
||||
}
|
||||
|
||||
void endsegment()
|
||||
{
|
||||
current.Finish();
|
||||
current = null;
|
||||
}
|
||||
|
||||
|
||||
public void CloseFile()
|
||||
{
|
||||
endsegment();
|
||||
}
|
||||
|
||||
public void AddFrame(IVideoProvider source)
|
||||
{
|
||||
if (source.BufferHeight != height || source.BufferWidth != width)
|
||||
SetVideoParameters(source.BufferWidth, source.BufferHeight);
|
||||
var a = source.GetVideoBuffer();
|
||||
var b = new byte[a.Length * sizeof(int)];
|
||||
Buffer.BlockCopy(a, 0, b, 0, b.Length);
|
||||
current.writevideoframe(b);
|
||||
}
|
||||
|
||||
public void AddSamples(short[] samples)
|
||||
{
|
||||
current.writeaudioframe(samples);
|
||||
}
|
||||
|
||||
|
||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||
{
|
||||
this.fpsnum = fpsnum;
|
||||
this.fpsden = fpsden;
|
||||
if (current != null)
|
||||
{
|
||||
endsegment();
|
||||
segment++;
|
||||
startsegment();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVideoParameters(int width, int height)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
if (current != null)
|
||||
{
|
||||
endsegment();
|
||||
segment++;
|
||||
startsegment();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
||||
{
|
||||
if (bits != 16)
|
||||
throw new ArgumentOutOfRangeException("audio depth must be 16 bit!");
|
||||
this.sampleRate = sampleRate;
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords)
|
||||
{
|
||||
// could be implemented?
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue