AVOUT: FFMPEG and Nut writers: reduce memory thrashing issues substantially. should have no problem keeping up with extraordinarily large sizes now.

This commit is contained in:
goyuken 2014-07-30 03:12:21 +00:00
parent 11d2216c98
commit fcab1873b7
3 changed files with 107 additions and 28 deletions

View File

@ -182,13 +182,11 @@ namespace BizHawk.Client.EmuHawk
if (ffmpeg.HasExited) if (ffmpeg.HasExited)
throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror()); throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror());
var a = source.GetVideoBuffer();
var b = new byte[a.Length * sizeof (int)];
Buffer.BlockCopy(a, 0, b, 0, b.Length);
var video = source.GetVideoBuffer();
try try
{ {
muxer.writevideoframe(b); muxer.WriteVideoFrame(video);
} }
catch catch
{ {
@ -260,7 +258,7 @@ namespace BizHawk.Client.EmuHawk
throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror()); throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror());
try try
{ {
muxer.writeaudioframe(samples); muxer.WriteAudioFrame(samples);
} }
catch catch
{ {

View File

@ -15,6 +15,67 @@ namespace BizHawk.Client.EmuHawk
{ {
// this code isn't really any good for general purpose nut creation // this code isn't really any good for general purpose nut creation
#region simple buffer reuser
public class ReusableBufferPool<T>
{
private List<T[]> _available = new List<T[]>();
private List<T[]> _inuse = new List<T[]>();
private readonly int maxstored;
public ReusableBufferPool(int maxstored)
{
this.maxstored = maxstored;
}
private T[] GetBufferInternal(int length, bool zerofill, Func<T[], bool> criteria)
{
if (_inuse.Count == maxstored)
throw new InvalidOperationException();
T[] candidate = _available.FirstOrDefault(criteria);
if (candidate == null)
{
if (_available.Count + _inuse.Count == maxstored)
{
// out of space! should not happen often
Console.WriteLine("Purging");
_available.Clear();
}
candidate = new T[length];
}
else
{
if (zerofill)
Array.Clear(candidate, 0, candidate.Length);
_available.Remove(candidate);
}
_inuse.Add(candidate);
return candidate;
}
public T[] GetBuffer(int length, bool zerofill = false)
{
return GetBufferInternal(length, zerofill, a => a.Length == length);
}
public T[] GetBufferAtLeast(int length, bool zerofill = false)
{
return GetBufferInternal(length, zerofill, a => a.Length >= length && a.Length / (float)length <= 2.0f);
}
public void ReleaseBuffer(T[] buffer)
{
if (!_inuse.Contains(buffer))
throw new ArgumentException();
_inuse.Remove(buffer);
_available.Add(buffer);
}
}
#endregion
#region binary write helpers #region binary write helpers
/// <summary> /// <summary>
@ -328,6 +389,8 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
Queue<NutFrame> audioqueue; Queue<NutFrame> audioqueue;
ReusableBufferPool<byte> _bufferpool = new ReusableBufferPool<byte>(12);
#endregion #endregion
#region header writers #region header writers
@ -433,6 +496,11 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
byte[] data; byte[] data;
/// <summary>
/// valid length of the data
/// </summary>
int actual_length;
/// <summary> /// <summary>
/// presentation timestamp /// presentation timestamp
/// </summary> /// </summary>
@ -443,21 +511,26 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
ulong ptsnum, ptsden; ulong ptsnum, ptsden;
ReusableBufferPool<byte> _pool;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="payload">frame data</param> /// <param name="payload">frame data</param>
/// <param name="payloadlen">actual length of frame data</param>
/// <param name="pts">presentation timestamp</param> /// <param name="pts">presentation timestamp</param>
/// <param name="ptsnum">numerator of timebase</param> /// <param name="ptsnum">numerator of timebase</param>
/// <param name="ptsden">denominator of timebase</param> /// <param name="ptsden">denominator of timebase</param>
/// <param name="ptsindex">which timestamp base is used, assumed to be also stream number</param> /// <param name="ptsindex">which timestamp base is used, assumed to be also stream number</param>
public NutFrame(byte[] payload, ulong pts, ulong ptsnum, ulong ptsden, int ptsindex) public NutFrame(byte[] payload, int payloadlen, ulong pts, ulong ptsnum, ulong ptsden, int ptsindex, ReusableBufferPool<byte> pool)
{ {
this.pts = pts; this.pts = pts;
this.ptsnum = ptsnum; this.ptsnum = ptsnum;
this.ptsden = ptsden; this.ptsden = ptsden;
var frame = new MemoryStream(); this._pool = pool;
data = pool.GetBufferAtLeast(payloadlen + 2048);
var frame = new MemoryStream(data);
// create syncpoint // create syncpoint
var sync = new NutPacket(NutPacket.StartCode.Syncpoint, frame); var sync = new NutPacket(NutPacket.StartCode.Syncpoint, frame);
@ -471,7 +544,7 @@ namespace BizHawk.Client.EmuHawk
// frame_flags = FLAG_CODED, so: // frame_flags = FLAG_CODED, so:
int flags = 0; int flags = 0;
flags |= 1 << 0; // FLAG_KEY flags |= 1 << 0; // FLAG_KEY
if (payload.Length == 0) if (payloadlen == 0)
flags |= 1 << 1; // FLAG_EOR flags |= 1 << 1; // FLAG_EOR
flags |= 1 << 3; // FLAG_CODED_PTS flags |= 1 << 3; // FLAG_CODED_PTS
flags |= 1 << 4; // FLAG_STREAM_ID flags |= 1 << 4; // FLAG_STREAM_ID
@ -480,14 +553,14 @@ namespace BizHawk.Client.EmuHawk
WriteVarU(flags, frameheader); WriteVarU(flags, frameheader);
WriteVarU(ptsindex, frameheader); // stream_id WriteVarU(ptsindex, frameheader); // stream_id
WriteVarU(pts + 256, frameheader); // coded_pts = pts + 1 << msb_pts_shift WriteVarU(pts + 256, frameheader); // coded_pts = pts + 1 << msb_pts_shift
WriteVarU(payload.Length, frameheader); // data_size_msb WriteVarU(payloadlen, frameheader); // data_size_msb
var frameheaderarr = frameheader.ToArray(); var frameheaderarr = frameheader.ToArray();
frame.Write(frameheaderarr, 0, frameheaderarr.Length); frame.Write(frameheaderarr, 0, frameheaderarr.Length);
WriteBE32(NutCRC32(frameheaderarr), frame); // checksum WriteBE32(NutCRC32(frameheaderarr), frame); // checksum
frame.Write(payload, 0, payload.Length); frame.Write(payload, 0, payloadlen);
data = frame.ToArray(); actual_length = (int)frame.Position;
} }
/// <summary> /// <summary>
@ -521,7 +594,8 @@ namespace BizHawk.Client.EmuHawk
/// <param name="dest"></param> /// <param name="dest"></param>
public void WriteData(Stream dest) public void WriteData(Stream dest)
{ {
dest.Write(data, 0, data.Length); dest.Write(data, 0, actual_length);
_pool.ReleaseBuffer(data);
//dbg.WriteLine(string.Format("{0},{1},{2}", pts, ptsnum, ptsden)); //dbg.WriteLine(string.Format("{0},{1},{2}", pts, ptsnum, ptsden));
} }
} }
@ -530,13 +604,19 @@ namespace BizHawk.Client.EmuHawk
/// write a video frame to the stream /// write a video frame to the stream
/// </summary> /// </summary>
/// <param name="data">raw video data; if length 0, write EOR</param> /// <param name="data">raw video data; if length 0, write EOR</param>
public void writevideoframe(byte[] data) public void WriteVideoFrame(int[] video)
{ {
if (videodone) if (videodone)
throw new Exception("Can't write data after end of relevance!"); throw new InvalidOperationException("Can't write data after end of relevance!");
if (data.Length == 0) if (audioqueue.Count > 5)
throw new Exception("A\\V Desync?");
int datalen = video.Length * sizeof(int);
byte[] data = _bufferpool.GetBufferAtLeast(datalen);
Buffer.BlockCopy(video, 0, data, 0, datalen);
if (datalen == 0)
videodone = true; videodone = true;
var f = new NutFrame(data, videopts, (ulong) avparams.fpsden, (ulong) avparams.fpsnum, 0); var f = new NutFrame(data, datalen, videopts, (ulong) avparams.fpsden, (ulong) avparams.fpsnum, 0, _bufferpool);
_bufferpool.ReleaseBuffer(data);
videopts++; videopts++;
videoqueue.Enqueue(f); videoqueue.Enqueue(f);
while (audioqueue.Count > 0 && f >= audioqueue.Peek()) while (audioqueue.Count > 0 && f >= audioqueue.Peek())
@ -547,16 +627,20 @@ namespace BizHawk.Client.EmuHawk
/// write an audio frame to the stream /// write an audio frame to the stream
/// </summary> /// </summary>
/// <param name="data">raw audio data; if length 0, write EOR</param> /// <param name="data">raw audio data; if length 0, write EOR</param>
public void writeaudioframe(short[] samples) public void WriteAudioFrame(short[] samples)
{ {
if (audiodone) if (audiodone)
throw new Exception("Can't write audio after end of relevance!"); throw new Exception("Can't write audio after end of relevance!");
byte[] data = new byte[samples.Length * sizeof (short)]; if (videoqueue.Count > 5)
Buffer.BlockCopy(samples, 0, data, 0, data.Length); throw new Exception("A\\V Desync?");
if (data.Length == 0) int datalen = samples.Length * sizeof(short);
byte[] data = _bufferpool.GetBufferAtLeast(datalen);
Buffer.BlockCopy(samples, 0, data, 0, datalen);
if (datalen == 0)
audiodone = true; audiodone = true;
var f = new NutFrame(data, audiopts, 1, (ulong)avparams.samplerate, 1); var f = new NutFrame(data, datalen, audiopts, 1, (ulong)avparams.samplerate, 1, _bufferpool);
_bufferpool.ReleaseBuffer(data);
audiopts += (ulong)samples.Length / (ulong)avparams.channels; audiopts += (ulong)samples.Length / (ulong)avparams.channels;
audioqueue.Enqueue(f); audioqueue.Enqueue(f);
while (videoqueue.Count > 0 && f >= videoqueue.Peek()) while (videoqueue.Count > 0 && f >= videoqueue.Peek())
@ -606,9 +690,9 @@ namespace BizHawk.Client.EmuHawk
public void Finish() public void Finish()
{ {
if (!videodone) if (!videodone)
writevideoframe(new byte[0]); WriteVideoFrame(new int[0]);
if (!audiodone) if (!audiodone)
writeaudioframe(new short[0]); WriteAudioFrame(new short[0]);
// flush any remaining queued packets // flush any remaining queued packets

View File

@ -75,15 +75,12 @@ namespace BizHawk.Client.EmuHawk
{ {
if (source.BufferHeight != height || source.BufferWidth != width) if (source.BufferHeight != height || source.BufferWidth != width)
SetVideoParameters(source.BufferWidth, source.BufferHeight); SetVideoParameters(source.BufferWidth, source.BufferHeight);
var a = source.GetVideoBuffer(); current.WriteVideoFrame(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) public void AddSamples(short[] samples)
{ {
current.writeaudioframe(samples); current.WriteAudioFrame(samples);
} }