2011-07-11 07:35:14 +00:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Runtime.InteropServices ;
2017-04-18 17:27:44 +00:00
using System.Windows.Forms ;
2011-07-11 07:35:14 +00:00
2013-10-25 00:57:23 +00:00
using BizHawk.Client.Common ;
2013-11-04 01:39:19 +00:00
using BizHawk.Emulation.Common ;
2013-10-25 00:57:23 +00:00
2017-04-18 17:27:44 +00:00
// some helpful p/invoke from http://www.codeproject.com/KB/audio-video/Motion_Detection.aspx?msg=1142967
2013-11-03 03:54:37 +00:00
namespace BizHawk.Client.EmuHawk
2011-07-11 07:35:14 +00:00
{
2014-10-10 18:09:00 +00:00
[ VideoWriter ( "vfwavi" , "AVI writer" ,
"Uses the Microsoft AVIFIL32 system to write .avi files. Audio is uncompressed; Video can be compressed with any installed VCM codec. Splits on 2G and resolution change." ) ]
2017-04-18 17:27:44 +00:00
internal class AviWriter : IVideoWriter
2011-07-11 07:35:14 +00:00
{
2017-04-18 17:27:44 +00:00
private CodecToken _currVideoCodecToken = null ;
private AviWriterSegment _currSegment ;
private IEnumerator < string > _nameProvider ;
2011-07-11 07:35:14 +00:00
2017-04-18 17:27:44 +00:00
public void SetFrame ( int frame )
{
}
2014-06-18 02:28:07 +00:00
2017-04-18 17:27:44 +00:00
private bool IsOpen = > _nameProvider ! = null ;
2011-07-11 07:35:14 +00:00
2011-07-11 07:53:48 +00:00
public void Dispose ( )
{
2017-04-18 17:27:44 +00:00
_currSegment ? . Dispose ( ) ;
2011-07-11 07:53:48 +00:00
}
2011-07-11 07:35:14 +00:00
/// <summary>
2011-07-13 04:04:58 +00:00
/// sets the codec token to be used for video compression
2011-07-11 07:35:14 +00:00
/// </summary>
2012-05-06 22:08:47 +00:00
public void SetVideoCodecToken ( IDisposable token )
2011-07-11 07:35:14 +00:00
{
2012-07-23 00:33:30 +00:00
if ( token is CodecToken )
2017-04-18 17:27:44 +00:00
{
_currVideoCodecToken = ( CodecToken ) token ;
}
2012-07-23 00:33:30 +00:00
else
2017-04-18 17:27:44 +00:00
{
2019-03-28 03:17:14 +00:00
throw new ArgumentException ( $"{nameof(AviWriter)} only takes its own {nameof(CodecToken)}s!" ) ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
}
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 ;
2017-04-18 17:27:44 +00:00
for ( ; ; )
2011-07-11 07:35:14 +00:00
{
2019-03-18 14:06:37 +00:00
yield return Path . Combine ( dir , $"{baseName}_{counter}{ext}" ) ;
2011-07-13 04:04:58 +00:00
counter + + ;
2011-07-11 07:35:14 +00:00
}
}
2011-07-13 04:04:58 +00:00
/// <summary>
/// opens an avi file for recording with names based on the supplied template.
/// set a video codec token first.
/// </summary>
2017-04-18 17:27:44 +00:00
public void OpenFile ( string baseName )
{
OpenFile ( CreateBasicNameProvider ( baseName ) ) ;
}
2011-07-11 07:35:14 +00:00
2012-07-23 00:33:30 +00:00
// thread communication
// synchronized queue with custom messages
// it seems like there are 99999 ways to do everything in C#, so i'm sure this is not the best
2017-04-18 17:27:44 +00:00
System . Collections . Concurrent . BlockingCollection < object > threadQ ;
2012-07-23 00:33:30 +00:00
System . Threading . Thread workerT ;
2017-04-18 17:27:44 +00:00
private void threadproc ( )
2012-07-23 00:33:30 +00:00
{
try
{
while ( true )
{
2017-04-18 17:27:44 +00:00
object o = threadQ . Take ( ) ;
2012-07-23 00:33:30 +00:00
if ( o is IVideoProvider )
2017-04-18 17:27:44 +00:00
{
2012-07-23 00:33:30 +00:00
AddFrameEx ( ( IVideoProvider ) o ) ;
2017-04-18 17:27:44 +00:00
}
2012-07-23 00:33:30 +00:00
else if ( o is short [ ] )
2017-04-18 17:27:44 +00:00
{
2012-07-23 00:33:30 +00:00
AddSamplesEx ( ( short [ ] ) o ) ;
2017-04-18 17:27:44 +00:00
}
2012-07-23 00:33:30 +00:00
else
2017-04-18 17:27:44 +00:00
{
2012-07-23 00:33:30 +00:00
// anything else is assumed to be quit time
return ;
2017-04-18 17:27:44 +00:00
}
2012-07-23 00:33:30 +00:00
}
}
catch ( Exception e )
{
2019-03-18 14:06:37 +00:00
MessageBox . Show ( $"AVIFIL32 Thread died:\n\n{e}" ) ;
2012-07-23 00:33:30 +00:00
}
}
// we can't pass the IVideoProvider we get to another thread, because it doesn't actually keep a local copy of its data,
// instead grabbing it from the emu as needed. this causes frame loss/dupping as a race condition
// instead we pass this
2017-04-18 17:27:44 +00:00
private class VideoCopy : IVideoProvider
2012-07-23 00:33:30 +00:00
{
2017-04-18 17:27:44 +00:00
private readonly int [ ] _vb ;
public int VirtualWidth { get ; }
public int VirtualHeight { get ; }
public int BufferWidth { get ; }
public int BufferHeight { get ; }
public int BackgroundColor { get ; }
2017-05-05 16:25:38 +00:00
public int VsyncNumerator { get ; }
public int VsyncDenominator { get ; }
2012-07-23 00:33:30 +00:00
public VideoCopy ( IVideoProvider c )
{
2017-04-18 17:27:44 +00:00
_vb = ( int [ ] ) c . GetVideoBuffer ( ) . Clone ( ) ;
2014-04-30 23:48:37 +00:00
BufferWidth = c . BufferWidth ;
2017-04-18 17:27:44 +00:00
BufferHeight = c . BufferHeight ;
2014-04-30 23:48:37 +00:00
BackgroundColor = c . BackgroundColor ;
VirtualWidth = c . VirtualWidth ;
VirtualHeight = c . VirtualHeight ;
2017-05-05 16:25:38 +00:00
VsyncNumerator = c . VsyncNumerator ;
VsyncDenominator = c . VsyncDenominator ;
2012-07-23 00:33:30 +00:00
}
2017-04-18 17:27:44 +00:00
2012-07-23 00:33:30 +00:00
public int [ ] GetVideoBuffer ( )
{
2017-04-18 17:27:44 +00:00
return _vb ;
2012-07-23 00:33:30 +00:00
}
}
2012-05-05 14:52:23 +00:00
2011-07-13 04:04:58 +00:00
/// <summary>
/// opens an avi file for recording with the supplied enumerator used to name files.
/// set a video codec token first.
/// </summary>
public void OpenFile ( IEnumerator < string > nameProvider )
2011-07-11 07:35:14 +00:00
{
2017-04-18 17:27:44 +00:00
_nameProvider = nameProvider ;
if ( _currVideoCodecToken = = null )
{
2011-07-13 04:04:58 +00:00
throw new InvalidOperationException ( "Tried to start recording an AVI with no video codec token set" ) ;
2017-04-18 17:27:44 +00:00
}
2012-05-05 14:52:23 +00:00
2012-07-23 00:33:30 +00:00
threadQ = new System . Collections . Concurrent . BlockingCollection < Object > ( 30 ) ;
workerT = new System . Threading . Thread ( new System . Threading . ThreadStart ( threadproc ) ) ;
workerT . Start ( ) ;
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
public void CloseFile ( )
2011-07-11 07:35:14 +00:00
{
2017-04-18 17:27:44 +00:00
threadQ . Add ( new object ( ) ) ; // acts as stop message
2012-07-23 00:33:30 +00:00
workerT . Join ( ) ;
2017-04-18 17:27:44 +00:00
_currSegment ? . Dispose ( ) ;
_currSegment = null ;
2011-07-11 07:35:14 +00:00
}
2012-07-23 00:33:30 +00:00
public void AddFrame ( IVideoProvider source )
{
2012-10-18 20:57:53 +00:00
while ( ! threadQ . TryAdd ( new VideoCopy ( source ) , 1000 ) )
{
if ( ! workerT . IsAlive )
2017-04-18 17:27:44 +00:00
{
2012-10-18 20:57:53 +00:00
throw new Exception ( "AVI Worker thread died!" ) ;
2017-04-18 17:27:44 +00:00
}
2012-10-18 20:57:53 +00:00
}
2012-07-23 00:33:30 +00:00
}
2017-04-18 17:27:44 +00:00
private void AddFrameEx ( IVideoProvider source )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
SetVideoParameters ( source . BufferWidth , source . BufferHeight ) ;
2011-07-13 04:24:55 +00:00
ConsiderLengthSegment ( ) ;
2017-04-18 17:27:44 +00:00
if ( _currSegment = = null )
{
Segment ( ) ;
}
_currSegment . AddFrame ( source ) ;
2011-07-13 04:04:58 +00:00
}
2011-07-11 07:35:14 +00:00
2012-07-23 00:33:30 +00:00
public void AddSamples ( short [ ] samples )
{
// as MainForm.cs is written now, samples is all ours (nothing else will use it for anything)
// but that's a bad assumption to make and could change in the future, so copy it since we're passing to another thread
2012-10-18 20:57:53 +00:00
while ( ! threadQ . TryAdd ( ( short [ ] ) samples . Clone ( ) , 1000 ) )
{
if ( ! workerT . IsAlive )
2017-04-18 17:27:44 +00:00
{
2012-10-18 20:57:53 +00:00
throw new Exception ( "AVI Worker thread died!" ) ;
2017-04-18 17:27:44 +00:00
}
2012-10-18 20:57:53 +00:00
}
2012-07-23 00:33:30 +00:00
}
2012-10-18 20:57:53 +00:00
2017-04-18 17:27:44 +00:00
private void AddSamplesEx ( short [ ] samples )
2011-07-13 04:04:58 +00:00
{
2011-07-13 04:24:55 +00:00
ConsiderLengthSegment ( ) ;
2017-04-18 17:27:44 +00:00
if ( _currSegment = = null )
{
Segment ( ) ;
}
_currSegment . AddSamples ( samples ) ;
2011-07-13 04:04:58 +00:00
}
2011-07-11 07:35:14 +00:00
2017-04-18 17:27:44 +00:00
private void ConsiderLengthSegment ( )
2011-07-13 04:24:55 +00:00
{
2017-04-18 17:27:44 +00:00
if ( _currSegment = = null )
{
return ;
}
long len = _currSegment . GetLengthApproximation ( ) ;
const long segment_length_limit = 2 * 1000 * 1000 * 1000 ; // 2GB
// const long segment_length_limit = 10 * 1000 * 1000; //for testing
if ( len > segment_length_limit )
{
Segment ( ) ;
}
2011-07-13 04:24:55 +00:00
}
2017-04-18 17:27:44 +00:00
private void StartRecording ( )
2011-07-13 04:04:58 +00:00
{
2017-04-18 17:27:44 +00:00
// i guess theres nothing to do here
2011-07-13 04:04:58 +00:00
}
2011-07-11 07:35:14 +00:00
2017-04-18 17:27:44 +00:00
private void Segment ( )
2011-07-13 04:04:58 +00:00
{
2017-04-18 17:27:44 +00:00
if ( ! IsOpen )
{
return ;
}
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
if ( _currSegment = = null )
{
2011-07-13 04:04:58 +00:00
StartRecording ( ) ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
else
2017-04-18 17:27:44 +00:00
{
_currSegment . Dispose ( ) ;
}
_currSegment = new AviWriterSegment ( ) ;
_nameProvider . MoveNext ( ) ;
_currSegment . OpenFile ( _nameProvider . Current , parameters , _currVideoCodecToken ) ;
2012-10-18 21:39:42 +00:00
try
{
2017-04-18 17:27:44 +00:00
_currSegment . OpenStreams ( ) ;
2012-10-18 21:39:42 +00:00
}
catch // will automatically try again with 32 bit
{
2017-04-18 17:27:44 +00:00
_currSegment . OpenStreams ( ) ;
2012-10-18 21:39:42 +00:00
}
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
/// <summary>
2017-04-18 17:27:44 +00:00
/// Acquires a video codec configuration from the user. you may save it for future use, but you must dispose of it when you're done with it.
2011-07-13 04:04:58 +00:00
/// returns null if the user canceled the dialog
/// </summary>
2017-04-18 17:27:44 +00:00
public IDisposable AcquireVideoCodecToken ( IWin32Window hwnd )
2011-07-13 04:04:58 +00:00
{
2017-04-18 17:27:44 +00:00
var tempParams = new Parameters
{
height = 256 ,
width = 256 ,
fps = 60 ,
fps_scale = 1 ,
a_bits = 16 ,
a_samplerate = 44100 ,
a_channels = 2
} ;
2011-07-13 04:04:58 +00:00
var temp = new AviWriterSegment ( ) ;
string tempfile = Path . GetTempFileName ( ) ;
File . Delete ( tempfile ) ;
tempfile = Path . ChangeExtension ( tempfile , "avi" ) ;
2017-04-18 17:27:44 +00:00
temp . OpenFile ( tempfile , tempParams , null ) ;
CodecToken token = ( CodecToken ) temp . AcquireVideoCodecToken ( hwnd . Handle , _currVideoCodecToken ) ;
2011-07-13 04:04:58 +00:00
temp . CloseFile ( ) ;
File . Delete ( tempfile ) ;
return token ;
}
2011-07-11 07:35:14 +00:00
2017-04-18 17:27:44 +00:00
private class Parameters
2011-07-11 07:35:14 +00:00
{
public int width , height ;
2014-12-21 05:27:42 +00:00
public int pitch ; //in bytes
public int pitch_add ;
2012-10-18 21:39:42 +00:00
public void PopulateBITMAPINFOHEADER24 ( ref Win32 . BITMAPINFOHEADER bmih )
2011-07-11 07:35:14 +00:00
{
bmih . Init ( ) ;
bmih . biPlanes = 1 ;
bmih . biBitCount = 24 ;
2011-07-11 07:53:48 +00:00
bmih . biHeight = height ;
2017-04-18 17:27:44 +00:00
// pad up width so that we end up with multiple of 4 bytes
2014-12-21 05:27:42 +00:00
pitch = width * 3 ;
pitch = ( pitch + 3 ) & ~ 3 ;
2017-04-18 17:27:44 +00:00
pitch_add = pitch - ( width * 3 ) ;
2011-07-11 07:35:14 +00:00
bmih . biWidth = width ;
2014-12-21 05:27:42 +00:00
bmih . biSizeImage = ( uint ) ( pitch * height ) ;
2011-07-11 07:35:14 +00:00
}
2012-10-18 21:39:42 +00:00
public void PopulateBITMAPINFOHEADER32 ( ref Win32 . BITMAPINFOHEADER bmih )
{
bmih . Init ( ) ;
bmih . biPlanes = 1 ;
bmih . biBitCount = 32 ;
2014-12-21 05:27:42 +00:00
pitch = width * 4 ;
2012-10-18 21:39:42 +00:00
bmih . biHeight = height ;
bmih . biWidth = width ;
2014-12-21 05:27:42 +00:00
bmih . biSizeImage = ( uint ) ( pitch * height ) ;
2012-10-18 21:39:42 +00:00
}
2011-07-11 07:35:14 +00:00
public bool has_audio ;
public int a_samplerate , a_channels , a_bits ;
public void PopulateWAVEFORMATEX ( ref Win32 . WAVEFORMATEX wfex )
{
int bytes = 0 ;
2011-07-13 04:04:58 +00:00
if ( a_bits = = 16 ) bytes = 2 ;
else if ( a_bits = = 8 ) bytes = 1 ;
2019-03-28 03:17:14 +00:00
else throw new InvalidOperationException ( $"only 8/16 bits audio are supported by {nameof(AviWriter)} and you chose: {a_bits}" ) ;
2011-07-11 07:35:14 +00:00
if ( a_channels = = 1 ) { }
else if ( a_channels = = 2 ) { }
2019-03-28 03:17:14 +00:00
else throw new InvalidOperationException ( $"only 1/2 channels audio are supported by {nameof(AviWriter)} and you chose: {a_channels}" ) ;
2011-07-11 07:35:14 +00:00
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 ;
}
2017-04-18 17:27:44 +00:00
private readonly Parameters parameters = new Parameters ( ) ;
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
2011-07-11 07:35:14 +00:00
/// <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 )
{
2011-07-13 04:04:58 +00:00
bool change = false ;
2012-07-23 00:33:30 +00:00
2011-07-13 04:04:58 +00:00
change | = fps ! = parameters . fps ;
2011-07-11 07:35:14 +00:00
parameters . fps = fps ;
2011-07-13 04:04:58 +00:00
change | = parameters . fps_scale ! = fps_scale ;
2011-07-11 07:35:14 +00:00
parameters . fps_scale = fps_scale ;
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
if ( change )
{
Segment ( ) ;
}
2011-07-11 07:35:14 +00:00
}
/// <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 )
{
2011-07-13 04:04:58 +00:00
bool change = false ;
change | = parameters . width ! = width ;
2011-07-11 07:35:14 +00:00
parameters . width = width ;
2011-07-13 04:04:58 +00:00
change | = parameters . height ! = height ;
2011-07-11 07:35:14 +00:00
parameters . height = height ;
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
if ( change )
{
Segment ( ) ;
}
2011-07-11 07:35:14 +00:00
}
/// <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 )
{
2011-07-13 04:04:58 +00:00
bool change = false ;
change | = parameters . a_samplerate ! = sampleRate ;
2011-07-11 07:35:14 +00:00
parameters . a_samplerate = sampleRate ;
2011-07-13 04:04:58 +00:00
change | = parameters . a_channels ! = channels ;
2011-07-11 07:35:14 +00:00
parameters . a_channels = channels ;
2011-07-13 04:04:58 +00:00
change | = parameters . a_bits ! = bits ;
2011-07-11 07:35:14 +00:00
parameters . a_bits = bits ;
2011-07-13 04:04:58 +00:00
change | = parameters . has_audio ! = true ;
2011-07-11 07:35:14 +00:00
parameters . has_audio = true ;
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
if ( change )
{
Segment ( ) ;
}
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
public class CodecToken : IDisposable
2011-07-11 07:35:14 +00:00
{
2015-12-19 11:12:22 +00:00
public void Dispose ( ) { }
private CodecToken ( ) { }
private Win32 . AVICOMPRESSOPTIONS comprOptions ;
public string codec ;
public byte [ ] Format = new byte [ 0 ] ;
public byte [ ] Parms = new byte [ 0 ] ;
public static CodecToken CreateFromAVICOMPRESSOPTIONS ( ref Win32 . AVICOMPRESSOPTIONS opts )
2011-07-11 07:35:14 +00:00
{
2017-04-18 17:27:44 +00:00
var ret = new CodecToken
{
comprOptions = opts ,
codec = Win32 . decode_mmioFOURCC ( opts . fccHandler ) ,
Format = new byte [ opts . cbFormat ] ,
Parms = new byte [ opts . cbParms ]
} ;
if ( opts . lpFormat ! = IntPtr . Zero )
{
Marshal . Copy ( opts . lpFormat , ret . Format , 0 , opts . cbFormat ) ;
}
if ( opts . lpParms ! = IntPtr . Zero )
{
Marshal . Copy ( opts . lpParms , ret . Parms , 0 , opts . cbParms ) ;
}
2011-07-13 04:04:58 +00:00
return ret ;
2011-07-11 07:35:14 +00:00
}
2015-12-19 11:12:22 +00:00
[DllImport("kernel32.dll", SetLastError = true)]
2017-04-08 22:36:29 +00:00
public static extern bool HeapFree ( IntPtr hHeap , uint dwFlags , IntPtr lpMem ) ;
2017-04-18 17:27:44 +00:00
2015-12-19 11:12:22 +00:00
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcessHeap ( ) ;
2017-04-18 17:27:44 +00:00
2015-12-19 11:12:22 +00:00
[DllImport("kernel32.dll", SetLastError = false)]
public static extern IntPtr HeapAlloc ( IntPtr hHeap , uint dwFlags , int dwBytes ) ;
public static void DeallocateAVICOMPRESSOPTIONS ( ref Win32 . AVICOMPRESSOPTIONS opts )
{
2017-04-18 17:27:44 +00:00
// test: increase stability by never freeing anything, ever
// if (opts.lpParms != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpParms);
// if (opts.lpFormat != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpFormat);
2017-04-08 22:36:29 +00:00
opts . lpParms = IntPtr . Zero ;
opts . lpFormat = IntPtr . Zero ;
2015-12-19 11:12:22 +00:00
}
2011-07-13 04:04:58 +00:00
2015-12-19 11:12:22 +00:00
public void AllocateToAVICOMPRESSOPTIONS ( ref Win32 . AVICOMPRESSOPTIONS opts )
{
opts = comprOptions ;
if ( opts . cbParms ! = 0 )
{
2017-04-08 22:36:29 +00:00
opts . lpParms = HeapAlloc ( GetProcessHeap ( ) , 0 , opts . cbParms ) ;
Marshal . Copy ( Parms , 0 , opts . lpParms , opts . cbParms ) ;
2012-08-03 22:02:04 +00:00
}
2015-12-19 11:12:22 +00:00
if ( opts . cbFormat ! = 0 )
2012-08-03 22:02:04 +00:00
{
2017-04-08 22:36:29 +00:00
opts . lpFormat = HeapAlloc ( GetProcessHeap ( ) , 0 , opts . cbFormat ) ;
Marshal . Copy ( Format , 0 , opts . lpFormat , opts . cbFormat ) ;
2012-08-03 22:02:04 +00:00
}
2011-07-11 07:35:14 +00:00
}
2015-12-19 11:12:22 +00:00
2017-04-18 17:27:44 +00:00
private byte [ ] SerializeToByteArray ( )
2012-08-03 17:43:17 +00:00
{
var m = new MemoryStream ( ) ;
var b = new BinaryWriter ( m ) ;
b . Write ( comprOptions . fccType ) ;
b . Write ( comprOptions . fccHandler ) ;
b . Write ( comprOptions . dwKeyFrameEvery ) ;
b . Write ( comprOptions . dwQuality ) ;
b . Write ( comprOptions . dwBytesPerSecond ) ;
b . Write ( comprOptions . dwFlags ) ;
//b.Write(comprOptions.lpFormat);
b . Write ( comprOptions . cbFormat ) ;
//b.Write(comprOptions.lpParms);
b . Write ( comprOptions . cbParms ) ;
b . Write ( comprOptions . dwInterleaveEvery ) ;
b . Write ( Format ) ;
2015-12-19 11:12:22 +00:00
b . Write ( Parms ) ;
2012-08-03 17:43:17 +00:00
b . Close ( ) ;
return m . ToArray ( ) ;
}
2017-04-18 17:27:44 +00:00
private static CodecToken DeSerializeFromByteArray ( byte [ ] data )
2012-08-03 17:43:17 +00:00
{
var m = new MemoryStream ( data , false ) ;
var b = new BinaryReader ( m ) ;
Win32 . AVICOMPRESSOPTIONS comprOptions = new Win32 . AVICOMPRESSOPTIONS ( ) ;
byte [ ] Format ;
2015-12-19 11:12:22 +00:00
byte [ ] Parms ;
2012-08-03 17:43:17 +00:00
try
{
comprOptions . fccType = b . ReadInt32 ( ) ;
comprOptions . fccHandler = b . ReadInt32 ( ) ;
comprOptions . dwKeyFrameEvery = b . ReadInt32 ( ) ;
comprOptions . dwQuality = b . ReadInt32 ( ) ;
comprOptions . dwBytesPerSecond = b . ReadInt32 ( ) ;
comprOptions . dwFlags = b . ReadInt32 ( ) ;
//comprOptions.lpFormat = b.ReadInt32();
comprOptions . cbFormat = b . ReadInt32 ( ) ;
//comprOptions.lpParms = b.ReadInt32();
comprOptions . cbParms = b . ReadInt32 ( ) ;
comprOptions . dwInterleaveEvery = b . ReadInt32 ( ) ;
Format = b . ReadBytes ( comprOptions . cbFormat ) ;
2015-12-19 11:12:22 +00:00
Parms = b . ReadBytes ( comprOptions . cbParms ) ;
2012-08-03 17:43:17 +00:00
}
catch ( IOException )
{
// ran off end of array most likely
return null ;
}
finally
{
b . Close ( ) ;
}
2017-04-18 17:27:44 +00:00
var ret = new CodecToken
{
comprOptions = comprOptions ,
Format = Format ,
Parms = Parms ,
codec = Win32 . decode_mmioFOURCC ( comprOptions . fccHandler )
} ;
2012-08-03 22:02:04 +00:00
return ret ;
2012-08-03 17:43:17 +00:00
}
public string Serialize ( )
{
2014-06-29 02:28:48 +00:00
return Convert . ToBase64String ( SerializeToByteArray ( ) ) ;
2012-08-03 17:43:17 +00:00
}
public static CodecToken DeSerialize ( string s )
{
2014-06-29 02:28:48 +00:00
return DeSerializeFromByteArray ( Convert . FromBase64String ( s ) ) ;
2012-08-03 17:43:17 +00:00
}
2011-07-11 07:35:14 +00:00
}
2012-07-23 00:33:30 +00:00
/// <summary>
/// set metadata parameters; should be called before opening file
/// NYI
/// </summary>
public void SetMetaData ( string gameName , string authors , UInt64 lengthMS , UInt64 rerecords )
{
}
2012-05-07 21:45:25 +00:00
2011-07-13 04:04:58 +00:00
unsafe class AviWriterSegment : IDisposable
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
static AviWriterSegment ( )
{
Win32 . AVIFileInit ( ) ;
}
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
public AviWriterSegment ( )
2011-07-11 07:35:14 +00:00
{
}
2011-07-13 04:04:58 +00:00
public void Dispose ( )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
CloseFile ( ) ;
2011-07-11 07:35:14 +00:00
}
2017-04-18 17:27:44 +00:00
private CodecToken currVideoCodecToken = null ;
private bool IsOpen ;
private IntPtr pAviFile , pAviRawVideoStream , pAviRawAudioStream , pAviCompressedVideoStream ;
private IntPtr pGlobalBuf ;
private int pGlobalBuf_size ;
// are we sending 32 bit RGB to avi or 24?
private bool bit32 = false ;
2011-07-13 04:04:58 +00:00
/// <summary>
/// there is just ony global buf. this gets it and makes sure its big enough. don't get all re-entrant on it!
/// </summary>
2017-04-18 17:27:44 +00:00
private IntPtr GetStaticGlobalBuf ( int amount )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
if ( amount > pGlobalBuf_size )
{
if ( pGlobalBuf ! = IntPtr . Zero )
2017-04-18 17:27:44 +00:00
{
2011-07-13 04:04:58 +00:00
Marshal . FreeHGlobal ( pGlobalBuf ) ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
pGlobalBuf_size = amount ;
pGlobalBuf = Marshal . AllocHGlobal ( pGlobalBuf_size ) ;
}
2017-04-18 17:27:44 +00:00
2011-07-13 04:04:58 +00:00
return pGlobalBuf ;
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
private class OutputStatus
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
public int video_frames ;
public int video_bytes ;
2011-07-13 04:24:55 +00:00
public int audio_bytes ;
2011-07-13 04:04:58 +00:00
public int audio_samples ;
public int audio_buffered_shorts ;
public const int AUDIO_SEGMENT_SIZE = 44100 * 2 ;
public short [ ] BufferedShorts = new short [ AUDIO_SEGMENT_SIZE ] ;
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
private OutputStatus outStatus ;
public long GetLengthApproximation ( )
{
return outStatus . video_bytes + outStatus . audio_bytes ;
}
2011-07-13 04:04:58 +00:00
2015-12-19 11:12:22 +00:00
static unsafe int AVISaveOptions ( IntPtr stream , ref Win32 . AVICOMPRESSOPTIONS opts , IntPtr owner )
2011-07-11 07:35:14 +00:00
{
2015-12-19 11:12:22 +00:00
fixed ( Win32 . AVICOMPRESSOPTIONS * _popts = & opts )
{
IntPtr * pStream = & stream ;
Win32 . AVICOMPRESSOPTIONS * popts = _popts ;
Win32 . AVICOMPRESSOPTIONS * * ppopts = & popts ;
return Win32 . AVISaveOptions ( owner , 0 , 1 , ( void * ) pStream , ( void * ) ppopts ) ;
}
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
Parameters parameters ;
public void OpenFile ( string destPath , Parameters parameters , CodecToken videoCodecToken )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
this . parameters = parameters ;
this . currVideoCodecToken = videoCodecToken ;
2017-04-18 17:27:44 +00:00
// TODO - try creating the file once first before we let vfw botch it up?
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
// open the avi output file handle
2011-07-13 04:04:58 +00:00
if ( File . Exists ( destPath ) )
2017-04-18 17:27:44 +00:00
{
2011-07-13 04:04:58 +00:00
File . Delete ( destPath ) ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
if ( Win32 . FAILED ( Win32 . AVIFileOpenW ( ref pAviFile , destPath , Win32 . OpenFileStyle . OF_CREATE | Win32 . OpenFileStyle . OF_WRITE , 0 ) ) )
2017-04-18 17:27:44 +00:00
{
2019-03-18 14:06:37 +00:00
throw new InvalidOperationException ( $"Couldnt open dest path for avi file: {destPath}" ) ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
// initialize the video stream
2011-07-13 04:04:58 +00:00
Win32 . AVISTREAMINFOW vidstream_header = new Win32 . AVISTREAMINFOW ( ) ;
Win32 . BITMAPINFOHEADER bmih = new Win32 . BITMAPINFOHEADER ( ) ;
2012-10-18 21:39:42 +00:00
parameters . PopulateBITMAPINFOHEADER24 ( ref bmih ) ;
2011-07-13 04:04:58 +00:00
vidstream_header . fccType = Win32 . mmioFOURCC ( "vids" ) ;
vidstream_header . dwRate = parameters . fps ;
vidstream_header . dwScale = parameters . fps_scale ;
vidstream_header . dwSuggestedBufferSize = ( int ) bmih . biSizeImage ;
if ( Win32 . FAILED ( Win32 . AVIFileCreateStreamW ( pAviFile , out pAviRawVideoStream , ref vidstream_header ) ) )
{
CloseFile ( ) ;
throw new InvalidOperationException ( "Failed opening raw video stream. Not sure how this could happen" ) ;
}
2017-04-18 17:27:44 +00:00
// initialize audio stream
2011-07-13 04:04:58 +00:00
Win32 . AVISTREAMINFOW audstream_header = new Win32 . AVISTREAMINFOW ( ) ;
Win32 . WAVEFORMATEX wfex = new Win32 . WAVEFORMATEX ( ) ;
parameters . PopulateWAVEFORMATEX ( ref wfex ) ;
audstream_header . fccType = Win32 . mmioFOURCC ( "auds" ) ;
audstream_header . dwQuality = - 1 ;
audstream_header . dwScale = wfex . nBlockAlign ;
audstream_header . dwRate = ( int ) wfex . nAvgBytesPerSec ;
audstream_header . dwSampleSize = wfex . nBlockAlign ;
audstream_header . dwInitialFrames = 1 ; // ??? optimal value?
if ( Win32 . FAILED ( Win32 . AVIFileCreateStreamW ( pAviFile , out pAviRawAudioStream , ref audstream_header ) ) )
{
CloseFile ( ) ;
throw new InvalidOperationException ( "Failed opening raw audio stream. Not sure how this could happen" ) ;
}
outStatus = new OutputStatus ( ) ;
IsOpen = true ;
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
2015-12-19 11:12:22 +00:00
2011-07-13 04:04:58 +00:00
/// <summary>
/// Acquires a video codec configuration from the user
/// </summary>
2015-12-19 11:12:22 +00:00
public IDisposable AcquireVideoCodecToken ( IntPtr hwnd , CodecToken lastCodecToken )
2011-07-11 07:35:14 +00:00
{
2017-04-18 17:27:44 +00:00
if ( ! IsOpen )
{
throw new InvalidOperationException ( "File must be opened before acquiring a codec token (or else the stream formats wouldnt be known)" ) ;
}
2011-07-13 04:04:58 +00:00
2015-12-19 11:12:22 +00:00
if ( lastCodecToken ! = null )
2017-04-18 17:27:44 +00:00
{
2015-12-19 11:12:22 +00:00
currVideoCodecToken = lastCodecToken ;
2017-04-18 17:27:44 +00:00
}
2015-12-19 11:12:22 +00:00
2017-04-18 17:27:44 +00:00
// encoder params
2011-07-13 04:04:58 +00:00
Win32 . AVICOMPRESSOPTIONS comprOptions = new Win32 . AVICOMPRESSOPTIONS ( ) ;
2017-04-18 17:27:44 +00:00
currVideoCodecToken ? . AllocateToAVICOMPRESSOPTIONS ( ref comprOptions ) ;
2015-12-16 21:23:52 +00:00
2015-12-19 11:12:22 +00:00
bool result = AVISaveOptions ( pAviRawVideoStream , ref comprOptions , hwnd ) ! = 0 ;
CodecToken ret = CodecToken . CreateFromAVICOMPRESSOPTIONS ( ref comprOptions ) ;
2017-04-18 17:27:44 +00:00
// so, AVISaveOptions may have changed some of the pointers
// if it changed the pointers, did it it free the old ones? we don't know
// let's assume it frees them. if we're wrong, we leak. if we assume otherwise and we're wrong, we may crash.
// so that means any pointers that come in here are either
// 1. ones we allocated a minute ago
// 2. ones VFW allocated
// guess what? doesn't matter. We'll free them all ourselves.
2015-12-19 11:12:22 +00:00
CodecToken . DeallocateAVICOMPRESSOPTIONS ( ref comprOptions ) ;
2017-04-18 17:27:44 +00:00
if ( result )
2012-08-03 17:43:17 +00:00
{
2015-12-19 11:12:22 +00:00
// save to config and return it
2012-08-03 17:43:17 +00:00
Global . Config . AVICodecToken = ret . Serialize ( ) ;
return ret ;
}
2017-04-18 17:27:44 +00:00
return null ;
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
/// <summary>
/// begin recording
/// </summary>
public void OpenStreams ( )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
if ( currVideoCodecToken = = null )
2017-04-18 17:27:44 +00:00
{
2011-07-13 04:04:58 +00:00
throw new InvalidOperationException ( "set a video codec token before opening the streams!" ) ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
// open compressed video stream
2015-12-19 11:12:22 +00:00
Win32 . AVICOMPRESSOPTIONS opts = new Win32 . AVICOMPRESSOPTIONS ( ) ;
currVideoCodecToken . AllocateToAVICOMPRESSOPTIONS ( ref opts ) ;
bool failed = Win32 . FAILED ( Win32 . AVIMakeCompressedStream ( out pAviCompressedVideoStream , pAviRawVideoStream , ref opts , IntPtr . Zero ) ) ;
CodecToken . DeallocateAVICOMPRESSOPTIONS ( ref opts ) ;
2017-04-18 17:27:44 +00:00
if ( failed )
2011-07-13 04:04:58 +00:00
{
CloseStreams ( ) ;
throw new InvalidOperationException ( "Failed making compressed video stream" ) ;
}
2017-04-18 17:27:44 +00:00
// set the compressed video stream input format
2011-07-13 04:04:58 +00:00
Win32 . BITMAPINFOHEADER bmih = new Win32 . BITMAPINFOHEADER ( ) ;
2012-10-18 21:39:42 +00:00
if ( bit32 )
2017-04-18 17:27:44 +00:00
{
2012-10-18 21:39:42 +00:00
parameters . PopulateBITMAPINFOHEADER32 ( ref bmih ) ;
2017-04-18 17:27:44 +00:00
}
2012-10-18 21:39:42 +00:00
else
2017-04-18 17:27:44 +00:00
{
2012-10-18 21:39:42 +00:00
parameters . PopulateBITMAPINFOHEADER24 ( ref bmih ) ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
if ( Win32 . FAILED ( Win32 . AVIStreamSetFormat ( pAviCompressedVideoStream , 0 , ref bmih , Marshal . SizeOf ( bmih ) ) ) )
{
2012-10-18 21:39:42 +00:00
bit32 = true ; // we'll try again
2011-07-13 04:04:58 +00:00
CloseStreams ( ) ;
throw new InvalidOperationException ( "Failed setting compressed video stream input format" ) ;
}
2017-04-18 17:27:44 +00:00
// set audio stream input format
2011-07-13 04:04:58 +00:00
Win32 . WAVEFORMATEX wfex = new Win32 . WAVEFORMATEX ( ) ;
parameters . PopulateWAVEFORMATEX ( ref wfex ) ;
if ( Win32 . FAILED ( Win32 . AVIStreamSetFormat ( pAviRawAudioStream , 0 , ref wfex , Marshal . SizeOf ( wfex ) ) ) )
{
CloseStreams ( ) ;
throw new InvalidOperationException ( "Failed setting raw audio stream input format" ) ;
}
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
/// <summary>
2017-04-18 17:27:44 +00:00
/// wrap up the AVI writing
2011-07-13 04:04:58 +00:00
/// </summary>
public void CloseFile ( )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
CloseStreams ( ) ;
if ( pAviRawAudioStream ! = IntPtr . Zero )
{
Win32 . AVIStreamRelease ( pAviRawAudioStream ) ;
pAviRawAudioStream = IntPtr . Zero ;
}
2017-04-18 17:27:44 +00:00
2011-07-13 04:04:58 +00:00
if ( pAviRawVideoStream ! = IntPtr . Zero )
{
Win32 . AVIStreamRelease ( pAviRawVideoStream ) ;
pAviRawVideoStream = IntPtr . Zero ;
}
2017-04-18 17:27:44 +00:00
2011-07-13 04:04:58 +00:00
if ( pAviFile ! = IntPtr . Zero )
{
Win32 . AVIFileRelease ( pAviFile ) ;
pAviFile = IntPtr . Zero ;
}
2017-04-18 17:27:44 +00:00
2011-07-13 04:04:58 +00:00
if ( pGlobalBuf ! = IntPtr . Zero )
{
Marshal . FreeHGlobal ( pGlobalBuf ) ;
pGlobalBuf = IntPtr . Zero ;
pGlobalBuf_size = 0 ;
}
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
/// <summary>
/// end recording
/// </summary>
public void CloseStreams ( )
{
2012-10-18 21:39:42 +00:00
if ( pAviRawAudioStream ! = IntPtr . Zero )
2017-04-18 17:27:44 +00:00
{
2012-10-18 21:39:42 +00:00
FlushBufferedAudio ( ) ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
if ( pAviCompressedVideoStream ! = IntPtr . Zero )
{
Win32 . AVIStreamRelease ( pAviCompressedVideoStream ) ;
pAviCompressedVideoStream = IntPtr . Zero ;
}
}
2011-07-11 07:35:14 +00:00
2017-04-18 17:27:44 +00:00
// todo - why couldnt this take an ISoundProvider? it could do the timekeeping as well.. hmm
2011-07-13 04:04:58 +00:00
public unsafe void AddSamples ( short [ ] samples )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
int todo = samples . Length ;
int idx = 0 ;
while ( todo > 0 )
{
int remain = OutputStatus . AUDIO_SEGMENT_SIZE - outStatus . audio_buffered_shorts ;
int chunk = Math . Min ( remain , todo ) ;
for ( int i = 0 ; i < chunk ; i + + )
2017-04-18 17:27:44 +00:00
{
2011-07-13 04:04:58 +00:00
outStatus . BufferedShorts [ outStatus . audio_buffered_shorts + + ] = samples [ idx + + ] ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
todo - = chunk ;
if ( outStatus . audio_buffered_shorts = = OutputStatus . AUDIO_SEGMENT_SIZE )
2017-04-18 17:27:44 +00:00
{
2011-07-13 04:04:58 +00:00
FlushBufferedAudio ( ) ;
2017-04-18 17:27:44 +00:00
}
2011-07-13 04:04:58 +00:00
}
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
unsafe void FlushBufferedAudio ( )
{
int todo = outStatus . audio_buffered_shorts ;
int todo_realsamples = todo / 2 ;
IntPtr buf = GetStaticGlobalBuf ( todo * 2 ) ;
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
short * sptr = ( short * ) buf . ToPointer ( ) ;
for ( int i = 0 ; i < todo ; i + + )
{
sptr [ i ] = outStatus . BufferedShorts [ i ] ;
}
2017-04-18 17:27:44 +00:00
// (TODO - inefficient- build directly in a buffer)
2011-07-13 04:04:58 +00:00
int bytes_written ;
Win32 . AVIStreamWrite ( pAviRawAudioStream , outStatus . audio_samples , todo_realsamples , buf , todo_realsamples * 4 , 0 , IntPtr . Zero , out bytes_written ) ;
outStatus . audio_samples + = todo_realsamples ;
2011-07-13 04:24:55 +00:00
outStatus . audio_bytes + = bytes_written ;
2011-07-13 04:04:58 +00:00
outStatus . audio_buffered_shorts = 0 ;
}
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
public unsafe void AddFrame ( IVideoProvider source )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
if ( parameters . width ! = source . BufferWidth
| | parameters . height ! = source . BufferHeight )
throw new InvalidOperationException ( "video buffer changed between start and now" ) ;
2011-07-11 07:35:14 +00:00
2014-12-21 05:27:42 +00:00
int pitch_add = parameters . pitch_add ;
int todo = parameters . pitch * parameters . height ;
2011-07-13 04:04:58 +00:00
int w = source . BufferWidth ;
int h = source . BufferHeight ;
2012-10-18 21:39:42 +00:00
if ( ! bit32 )
2011-07-13 04:04:58 +00:00
{
2014-12-21 05:27:42 +00:00
IntPtr buf = GetStaticGlobalBuf ( todo ) ;
2011-07-13 04:04:58 +00:00
2017-04-18 17:27:44 +00:00
// TODO - would using a byte* be faster?
2012-10-18 21:39:42 +00:00
int [ ] buffer = source . GetVideoBuffer ( ) ;
fixed ( int * buffer_ptr = & buffer [ 0 ] )
{
byte * bytes_ptr = ( byte * ) buf . ToPointer ( ) ;
2011-07-11 07:53:48 +00:00
{
2012-10-18 21:39:42 +00:00
byte * bp = bytes_ptr ;
for ( int idx = w * h - w , y = 0 ; y < h ; y + + )
2011-07-13 04:04:58 +00:00
{
2012-10-18 21:39:42 +00:00
for ( int x = 0 ; x < w ; x + + , idx + + )
{
int r = ( buffer [ idx ] > > 0 ) & 0xFF ;
int g = ( buffer [ idx ] > > 8 ) & 0xFF ;
int b = ( buffer [ idx ] > > 16 ) & 0xFF ;
* bp + + = ( byte ) r ;
* bp + + = ( byte ) g ;
* bp + + = ( byte ) b ;
}
idx - = w * 2 ;
2014-12-21 05:27:42 +00:00
bp + = pitch_add ;
2011-07-13 04:04:58 +00:00
}
2012-10-18 21:39:42 +00:00
int bytes_written ;
2014-12-21 05:27:42 +00:00
int ret = Win32 . AVIStreamWrite ( pAviCompressedVideoStream , outStatus . video_frames , 1 , new IntPtr ( bytes_ptr ) , todo , Win32 . AVIIF_KEYFRAME , IntPtr . Zero , out bytes_written ) ;
2012-10-18 21:39:42 +00:00
outStatus . video_bytes + = bytes_written ;
outStatus . video_frames + + ;
}
2011-07-11 07:35:14 +00:00
}
}
2012-10-18 21:39:42 +00:00
else // 32 bit
{
IntPtr buf = GetStaticGlobalBuf ( todo * 4 ) ;
int [ ] buffer = source . GetVideoBuffer ( ) ;
fixed ( int * buffer_ptr = & buffer [ 0 ] )
{
byte * bytes_ptr = ( byte * ) buf . ToPointer ( ) ;
{
byte * bp = bytes_ptr ;
for ( int idx = w * h - w , y = 0 ; y < h ; y + + )
{
for ( int x = 0 ; x < w ; x + + , idx + + )
{
int r = ( buffer [ idx ] > > 0 ) & 0xFF ;
int g = ( buffer [ idx ] > > 8 ) & 0xFF ;
int b = ( buffer [ idx ] > > 16 ) & 0xFF ;
* bp + + = ( byte ) r ;
* bp + + = ( byte ) g ;
* bp + + = ( byte ) b ;
* bp + + = 0 ;
}
idx - = w * 2 ;
}
2011-07-11 07:35:14 +00:00
2012-10-18 21:39:42 +00:00
int bytes_written ;
int ret = Win32 . AVIStreamWrite ( pAviCompressedVideoStream , outStatus . video_frames , 1 , new IntPtr ( bytes_ptr ) , todo * 3 , Win32 . AVIIF_KEYFRAME , IntPtr . Zero , out bytes_written ) ;
outStatus . video_bytes + = bytes_written ;
outStatus . video_frames + + ;
}
}
}
2011-07-11 07:35:14 +00:00
}
}
2012-06-13 19:50:50 +00:00
2012-07-23 00:33:30 +00:00
public void SetDefaultVideoCodecToken ( )
{
2012-08-03 17:43:17 +00:00
CodecToken ct = CodecToken . DeSerialize ( Global . Config . AVICodecToken ) ;
if ( ct = = null )
2017-04-18 17:27:44 +00:00
{
2019-03-28 03:17:14 +00:00
throw new Exception ( $"No default {nameof(Global.Config.AVICodecToken)} in config!" ) ;
2017-04-18 17:27:44 +00:00
}
_currVideoCodecToken = ct ;
2012-07-23 00:33:30 +00:00
}
2014-10-10 18:09:00 +00:00
public string DesiredExtension ( )
2012-07-23 00:33:30 +00:00
{
2014-10-10 18:09:00 +00:00
return "avi" ;
2012-07-23 00:33:30 +00:00
}
2016-03-05 23:19:12 +00:00
2017-04-18 17:27:44 +00:00
public bool UsesAudio = > parameters . has_audio ;
public bool UsesVideo = > true ;
2011-07-11 07:35:14 +00:00
}
}
////TEST AVI
//AviWriter aw = new AviWriter();
//aw.SetVideoParameters(256, 256);
//aw.SetMovieParameters(60, 1);
//aw.OpenFile("d:\\bizhawk.avi");
//CreateHandle();
//var token = aw.AcquireVideoCodecToken(Handle);
//aw.SetVideoCodecToken(token);
//aw.OpenStreams();
//for (int i = 0; i < 100; i++)
//{
// TestVideoProvider video = new TestVideoProvider();
// Bitmap bmp = new Bitmap(256, 256, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
// using (Graphics g = Graphics.FromImage(bmp))
// {
// g.Clear(Color.Red);
// using (Font f = new Font(FontFamily.GenericMonospace, 10))
// g.DrawString(i.ToString(), f, Brushes.Black, 0, 0);
// }
2019-03-18 14:06:37 +00:00
// //bmp.Save($"c:\\dump\\{i}.bmp", ImageFormat.Bmp);
2011-07-11 07:35:14 +00:00
// for (int y = 0, idx = 0; y < 256; y++)
// for (int x = 0; x < 256; x++)
// video.buffer[idx++] = bmp.GetPixel(x, y).ToArgb();
// aw.AddFrame(video);
//}
//aw.CloseStreams();
//aw.CloseFile();
////-----