avi rez-change segmentation

This commit is contained in:
zeromus 2011-07-13 04:04:58 +00:00
parent a8c66418e7
commit bff3957c7e
2 changed files with 463 additions and 329 deletions

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Drawing; using System.Drawing;
@ -9,14 +10,266 @@ using BizHawk;
namespace BizHawk.MultiClient namespace BizHawk.MultiClient
{ {
unsafe class AviWriter : IDisposable class AviWriter : IDisposable
{ {
static AviWriter() CodecToken currVideoCodecToken = null;
AviWriterSegment currSegment;
IEnumerator<string> nameProvider;
bool IsOpen { get { return nameProvider != null; } }
public void Dispose()
{
if (currSegment != null)
currSegment.Dispose();
}
/// <summary>
/// sets the codec token to be used for video compression
/// </summary>
public void SetVideoCodecToken(CodecToken token)
{
currVideoCodecToken = token;
}
public static IEnumerator<string> CreateBasicNameProvider(string template)
{
string dir = Path.GetDirectoryName(template);
string baseName = Path.GetFileNameWithoutExtension(template);
string ext = Path.GetExtension(template);
yield return template;
int counter = 1;
for (; ; )
{
yield return Path.Combine(dir, baseName) + "_" + counter + ext;
counter++;
}
}
/// <summary>
/// opens an avi file for recording with names based on the supplied template.
/// set a video codec token first.
/// </summary>
public void OpenFile(string baseName) { OpenFile(CreateBasicNameProvider(baseName)); }
/// <summary>
/// opens an avi file for recording with the supplied enumerator used to name files.
/// set a video codec token first.
/// </summary>
/// <param name="nameProvider"></param>
public void OpenFile(IEnumerator<string> nameProvider)
{
this.nameProvider = nameProvider;
if (currVideoCodecToken == null)
throw new InvalidOperationException("Tried to start recording an AVI with no video codec token set");
}
public void CloseFile()
{
if (currSegment != null)
currSegment.Dispose();
currSegment = null;
}
public void AddFrame(IVideoProvider source)
{
SetVideoParameters(source.BufferWidth, source.BufferHeight);
if (currSegment == null) Segment();
currSegment.AddFrame(source);
}
public void AddSamples(short[] samples)
{
if (currSegment == null) Segment();
currSegment.AddSamples(samples);
}
void StartRecording()
{
//i guess theres nothing to do here
}
void Segment()
{
if (!IsOpen) return;
if (currSegment == null)
StartRecording();
else
currSegment.Dispose();
currSegment = new AviWriterSegment();
nameProvider.MoveNext();
currSegment.OpenFile(nameProvider.Current, parameters, currVideoCodecToken);
currSegment.OpenStreams();
}
/// <summary>
/// Acquires a video codec configuration from the user. you may save it for future use, but you must dispose of it when youre done with it.
/// returns null if the user canceled the dialog
/// </summary>
public static CodecToken AcquireVideoCodecToken(IntPtr hwnd, CodecToken lastToken)
{
var temp_params = new Parameters();
temp_params.height = 256;
temp_params.width = 256;
temp_params.fps = 60;
temp_params.fps_scale = 1;
temp_params.a_bits = 16;
temp_params.a_samplerate = 44100;
temp_params.a_channels = 2;
var temp = new AviWriterSegment();
string tempfile = Path.GetTempFileName();
File.Delete(tempfile);
tempfile = Path.ChangeExtension(tempfile, "avi");
temp.OpenFile(tempfile, temp_params, lastToken);
CodecToken token = temp.AcquireVideoCodecToken(hwnd);
temp.CloseFile();
File.Delete(tempfile);
return token;
}
class Parameters
{
public int width, height;
public void PopulateBITMAPINFOHEADER(ref Win32.BITMAPINFOHEADER bmih)
{
bmih.Init();
bmih.biPlanes = 1;
bmih.biBitCount = 24;
bmih.biHeight = height;
bmih.biWidth = width;
bmih.biSizeImage = (uint)(3 * width * height);
}
public bool has_audio;
public int a_samplerate, a_channels, a_bits;
public void PopulateWAVEFORMATEX(ref Win32.WAVEFORMATEX wfex)
{
int bytes = 0;
if (a_bits == 16) bytes = 2;
else if (a_bits == 8) bytes = 1;
else throw new InvalidOperationException("only 8/16 bits audio are supported by AviWriter and you chose: " + a_bits);
if (a_channels == 1) { }
else if (a_channels == 2) { }
else throw new InvalidOperationException("only 1/2 channels audio are supported by AviWriter and you chose: " + a_channels);
wfex.Init();
wfex.nBlockAlign = (ushort)(bytes * a_channels);
wfex.nChannels = (ushort)a_channels;
wfex.wBitsPerSample = (ushort)a_bits;
wfex.wFormatTag = Win32.WAVE_FORMAT_PCM;
wfex.nSamplesPerSec = (uint)a_samplerate;
wfex.nAvgBytesPerSec = (uint)(wfex.nBlockAlign * a_samplerate);
}
public int fps, fps_scale;
}
Parameters parameters = new Parameters();
/// <summary>
/// set basic movie timing parameters for the avi file. must be set before the file is opened.
/// </summary>
public void SetMovieParameters(int fps, int fps_scale)
{
bool change = false;
change |= fps != parameters.fps;
parameters.fps = fps;
change |= parameters.fps_scale != fps_scale;
parameters.fps_scale = fps_scale;
if (change) Segment();
}
/// <summary>
/// set basic video parameters for the avi file. must be set before the file is opened.
/// </summary>
public void SetVideoParameters(int width, int height)
{
bool change = false;
change |= parameters.width != width;
parameters.width = width;
change |= parameters.height != height;
parameters.height = height;
if (change) Segment();
}
/// <summary>
/// set basic audio parameters for the avi file. must be set before the file isopened.
/// </summary>
public void SetAudioParameters(int sampleRate, int channels, int bits)
{
bool change = false;
change |= parameters.a_samplerate != sampleRate;
parameters.a_samplerate = sampleRate;
change |= parameters.a_channels != channels;
parameters.a_channels = channels;
change |= parameters.a_bits != bits;
parameters.a_bits = bits;
change |= parameters.has_audio != true;
parameters.has_audio = true;
if (change) Segment();
}
public class CodecToken : IDisposable
{
~CodecToken()
{
Dispose();
}
public static CodecToken TakePossession(Win32.AVICOMPRESSOPTIONS comprOptions)
{
CodecToken ret = new CodecToken();
ret.allocated = true;
ret.comprOptions = comprOptions;
ret.codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler);
return ret;
}
private CodecToken() { }
public Win32.AVICOMPRESSOPTIONS comprOptions;
public string codec;
bool allocated = false;
public void Dispose()
{
if (!allocated) return;
IntPtr[] infPtrs = new IntPtr[1];
IntPtr mem;
// alloc unmanaged memory
mem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.AVICOMPRESSOPTIONS)));
infPtrs[0] = mem;
// copy from managed structure to unmanaged memory
Marshal.StructureToPtr(comprOptions, mem, false);
Win32.AVISaveOptionsFree(1, infPtrs);
Marshal.FreeHGlobal(mem);
codec = null;
comprOptions = new Win32.AVICOMPRESSOPTIONS();
allocated = false;
}
}
unsafe class AviWriterSegment : IDisposable
{
static AviWriterSegment()
{ {
Win32.AVIFileInit(); Win32.AVIFileInit();
} }
public AviWriter() public AviWriterSegment()
{ {
} }
@ -77,120 +330,12 @@ namespace BizHawk.MultiClient
return ret; return ret;
} }
Parameters parameters;
public class CodecToken : IDisposable public void OpenFile(string destPath, Parameters parameters, CodecToken videoCodecToken)
{ {
public static CodecToken TakePossession(Win32.AVICOMPRESSOPTIONS comprOptions) this.parameters = parameters;
{ this.currVideoCodecToken = videoCodecToken;
CodecToken ret = new CodecToken();
ret.allocated = true;
ret.comprOptions = comprOptions;
ret.codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler);
return ret;
}
private CodecToken() { }
public Win32.AVICOMPRESSOPTIONS comprOptions;
public string codec;
bool allocated = false;
public void Dispose()
{
if (!allocated) return;
IntPtr[] infPtrs = new IntPtr[1];
IntPtr mem;
// alloc unmanaged memory
mem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.AVICOMPRESSOPTIONS)));
infPtrs[0] = mem;
// copy from managed structure to unmanaged memory
Marshal.StructureToPtr(comprOptions, mem, false);
Win32.AVISaveOptionsFree(1, infPtrs);
Marshal.FreeHGlobal(mem);
codec = null;
comprOptions = new Win32.AVICOMPRESSOPTIONS();
allocated = false;
}
}
class Parameters
{
public int width, height;
public void PopulateBITMAPINFOHEADER(ref Win32.BITMAPINFOHEADER bmih)
{
bmih.Init();
bmih.biPlanes = 1;
bmih.biBitCount = 24;
bmih.biHeight = height;
bmih.biWidth = width;
bmih.biSizeImage = (uint)(3 * width * height);
}
public bool has_audio;
public int a_samplerate, a_channels, a_bits;
public void PopulateWAVEFORMATEX(ref Win32.WAVEFORMATEX wfex)
{
int bytes = 0;
if(a_bits == 16) bytes=2;
else if(a_bits==8) bytes=1;
else throw new InvalidOperationException("only 8/16 bits audio are supported by AviWriter and you chose: " + a_bits);
if (a_channels == 1) { }
else if (a_channels == 2) { }
else throw new InvalidOperationException("only 1/2 channels audio are supported by AviWriter and you chose: " + a_channels);
wfex.Init();
wfex.nBlockAlign = (ushort)(bytes * a_channels);
wfex.nChannels = (ushort)a_channels;
wfex.wBitsPerSample = (ushort)a_bits;
wfex.wFormatTag = Win32.WAVE_FORMAT_PCM;
wfex.nSamplesPerSec = (uint)a_samplerate;
wfex.nAvgBytesPerSec = (uint)(wfex.nBlockAlign * a_samplerate);
}
public int fps, fps_scale;
}
Parameters parameters = new Parameters();
/// <summary>
/// set basic movie timing parameters for the avi file. must be set before the file is opened.
/// </summary>
public void SetMovieParameters(int fps, int fps_scale)
{
if (IsOpen) throw new InvalidOperationException("Can't set avi parameters while it is open");
parameters.fps = fps;
parameters.fps_scale = fps_scale;
}
/// <summary>
/// set basic video parameters for the avi file. must be set before the file is opened.
/// </summary>
public void SetVideoParameters(int width, int height)
{
if (IsOpen) throw new InvalidOperationException("Can't set avi parameters while it is open");
parameters.width = width;
parameters.height = height;
}
/// <summary>
/// set basic audio parameters for the avi file. must be set before the file isopened.
/// </summary>
public void SetAudioParameters(int sampleRate, int channels, int bits)
{
if (IsOpen) throw new InvalidOperationException("Can't set avi parameters while it is open");
parameters.a_samplerate = sampleRate;
parameters.a_channels = channels;
parameters.a_bits = bits;
parameters.has_audio = true;
}
/// <summary>
/// commits the movie parameters and opens the output file
/// </summary>
public void OpenFile(string destPath)
{
//TODO - try creating the file once first before we let vfw botch it up? //TODO - try creating the file once first before we let vfw botch it up?
//open the avi output file handle //open the avi output file handle
@ -279,7 +424,7 @@ namespace BizHawk.MultiClient
//set audio stream input format //set audio stream input format
Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX(); Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX();
parameters.PopulateWAVEFORMATEX(ref wfex); parameters.PopulateWAVEFORMATEX(ref wfex);
if(Win32.FAILED(Win32.AVIStreamSetFormat(pAviRawAudioStream, 0, ref wfex, Marshal.SizeOf(wfex)))) if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviRawAudioStream, 0, ref wfex, Marshal.SizeOf(wfex))))
{ {
CloseStreams(); CloseStreams();
throw new InvalidOperationException("Failed setting raw audio stream input format"); throw new InvalidOperationException("Failed setting raw audio stream input format");
@ -341,7 +486,7 @@ namespace BizHawk.MultiClient
outStatus.BufferedShorts[outStatus.audio_buffered_shorts++] = samples[idx++]; outStatus.BufferedShorts[outStatus.audio_buffered_shorts++] = samples[idx++];
todo -= chunk; todo -= chunk;
if(outStatus.audio_buffered_shorts == OutputStatus.AUDIO_SEGMENT_SIZE) if (outStatus.audio_buffered_shorts == OutputStatus.AUDIO_SEGMENT_SIZE)
FlushBufferedAudio(); FlushBufferedAudio();
} }
} }
@ -350,7 +495,7 @@ namespace BizHawk.MultiClient
{ {
int todo = outStatus.audio_buffered_shorts; int todo = outStatus.audio_buffered_shorts;
int todo_realsamples = todo / 2; int todo_realsamples = todo / 2;
IntPtr buf = GetStaticGlobalBuf(todo*2); IntPtr buf = GetStaticGlobalBuf(todo * 2);
short* sptr = (short*)buf.ToPointer(); short* sptr = (short*)buf.ToPointer();
for (int i = 0; i < todo; i++) for (int i = 0; i < todo; i++)
@ -366,7 +511,7 @@ namespace BizHawk.MultiClient
public unsafe void AddFrame(IVideoProvider source) public unsafe void AddFrame(IVideoProvider source)
{ {
if(parameters.width != source.BufferWidth if (parameters.width != source.BufferWidth
|| parameters.height != source.BufferHeight) || parameters.height != source.BufferHeight)
throw new InvalidOperationException("video buffer changed between start and now"); throw new InvalidOperationException("video buffer changed between start and now");
@ -406,17 +551,7 @@ namespace BizHawk.MultiClient
} }
} }
/// <summary>
/// sets the codec token to be used for video compression
/// </summary>
public void SetVideoCodecToken(CodecToken token)
{
currVideoCodecToken = token;
} }
} }
} }

View File

@ -2597,10 +2597,9 @@ namespace BizHawk.MultiClient
aw.SetMovieParameters(fps, 0x01000000); aw.SetMovieParameters(fps, 0x01000000);
aw.SetVideoParameters(Global.Emulator.VideoProvider.BufferWidth, Global.Emulator.VideoProvider.BufferHeight); aw.SetVideoParameters(Global.Emulator.VideoProvider.BufferWidth, Global.Emulator.VideoProvider.BufferHeight);
aw.SetAudioParameters(44100, 2, 16); aw.SetAudioParameters(44100, 2, 16);
aw.OpenFile(sfd.FileName); var token = AviWriter.AcquireVideoCodecToken(Global.MainForm.Handle, null);
var token = aw.AcquireVideoCodecToken(Global.MainForm.Handle);
aw.SetVideoCodecToken(token); aw.SetVideoCodecToken(token);
aw.OpenStreams(); aw.OpenFile(sfd.FileName);
//commit the avi writing last, in case there were any errors earlier //commit the avi writing last, in case there were any errors earlier
CurrAviWriter = aw; CurrAviWriter = aw;