2011-07-11 07:35:14 +00:00
using System ;
2011-07-13 04:04:58 +00:00
using System.Collections ;
2011-07-11 07:35:14 +00:00
using System.Collections.Generic ;
using System.IO ;
using System.Drawing ;
using System.Runtime.InteropServices ;
using BizHawk ;
//some helpful p/invoke from http://www.codeproject.com/KB/audio-video/Motion_Detection.aspx?msg=1142967
namespace BizHawk.MultiClient
{
2012-05-06 22:18:16 +00:00
class AviWriter : IVideoWriter
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
CodecToken currVideoCodecToken = null ;
AviWriterSegment currSegment ;
IEnumerator < string > nameProvider ;
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
bool IsOpen { get { return nameProvider ! = null ; } }
2011-07-11 07:35:14 +00:00
2011-07-11 07:53:48 +00:00
public void Dispose ( )
{
2011-07-13 04:04:58 +00:00
if ( currSegment ! = null )
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-05-06 22:08:47 +00:00
if ( token is CodecToken )
currVideoCodecToken = ( CodecToken ) token ;
else
throw new ArgumentException ( "AviWriter only takes its own Codec Tokens!" ) ;
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 ;
for ( ; ; )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
yield return Path . Combine ( dir , baseName ) + "_" + counter + ext ;
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>
public void OpenFile ( string baseName ) { OpenFile ( CreateBasicNameProvider ( baseName ) ) ; }
2011-07-11 07:35:14 +00:00
2012-05-05 14:52:23 +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
System . Collections . Concurrent . BlockingCollection < Object > threadQ ;
System . Threading . Thread workerT ;
void threadproc ( )
{
try
{
while ( true )
{
Object o = threadQ . Take ( ) ;
if ( o is IVideoProvider )
AddFrameEx ( ( IVideoProvider ) o ) ;
else if ( o is short [ ] )
AddSamplesEx ( ( short [ ] ) o ) ;
else
// anything else is assumed to be quit time
return ;
}
}
2012-05-09 01:00:16 +00:00
catch ( Exception e )
2012-05-05 14:52:23 +00:00
{
2012-05-09 01:00:16 +00:00
System . Windows . Forms . MessageBox . Show ( "AVIFIL32 Thread died:\n\n" + e . ToString ( ) ) ;
2012-05-05 14:52:23 +00:00
return ;
}
}
// 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
class VideoCopy : IVideoProvider
{
int [ ] vb ;
int bw , bh , bc ;
2012-06-25 02:50:34 +00:00
public int VirtualWidth { get { return bw ; } }
2012-05-05 14:52:23 +00:00
public int BufferWidth { get { return bw ; } }
public int BufferHeight { get { return bh ; } }
public int BackgroundColor { get { return bc ; } }
public VideoCopy ( IVideoProvider c )
{
vb = ( int [ ] ) c . GetVideoBuffer ( ) . Clone ( ) ;
bw = c . BufferWidth ;
bh = c . BufferHeight ;
bc = c . BackgroundColor ;
}
public int [ ] GetVideoBuffer ( )
{
return vb ;
}
}
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>
/// <param name="nameProvider"></param>
public void OpenFile ( IEnumerator < string > nameProvider )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
this . nameProvider = nameProvider ;
if ( currVideoCodecToken = = null )
throw new InvalidOperationException ( "Tried to start recording an AVI with no video codec token set" ) ;
2012-05-05 14:52:23 +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
{
2012-05-05 14:52:23 +00:00
threadQ . Add ( new Object ( ) ) ; // acts as stop message
workerT . Join ( ) ;
2011-07-13 04:04:58 +00:00
if ( currSegment ! = null )
currSegment . Dispose ( ) ;
currSegment = null ;
2011-07-11 07:35:14 +00:00
}
2012-05-05 14:52:23 +00:00
public void AddFrame ( IVideoProvider source )
{
if ( ! workerT . IsAlive )
// signal some sort of error?
return ;
threadQ . Add ( new VideoCopy ( source ) ) ;
}
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 ( ) ;
2011-07-13 04:04:58 +00:00
if ( currSegment = = null ) Segment ( ) ;
currSegment . AddFrame ( source ) ;
}
2011-07-11 07:35:14 +00:00
2012-05-05 14:52:23 +00:00
public void AddSamples ( short [ ] samples )
{
if ( ! workerT . IsAlive )
// signal some sort of error?
return ;
// 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
threadQ . Add ( ( short [ ] ) samples . Clone ( ) ) ;
}
void AddSamplesEx ( short [ ] samples )
2011-07-13 04:04:58 +00:00
{
2011-07-13 04:24:55 +00:00
ConsiderLengthSegment ( ) ;
2011-07-13 04:04:58 +00:00
if ( currSegment = = null ) Segment ( ) ;
currSegment . AddSamples ( samples ) ;
}
2011-07-11 07:35:14 +00:00
2011-07-13 04:24:55 +00:00
void ConsiderLengthSegment ( )
{
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:04:58 +00:00
void StartRecording ( )
{
//i guess theres nothing to do here
}
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
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 ( ) ;
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
/// <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>
2012-06-16 16:51:47 +00:00
public IDisposable AcquireVideoCodecToken ( System . Windows . Forms . IWin32Window hwnd ) //, CodecToken lastToken)
2011-07-13 04:04:58 +00:00
{
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" ) ;
2012-05-06 22:08:47 +00:00
temp . OpenFile ( tempfile , temp_params , null ) ; //lastToken);
2012-06-16 16:51:47 +00:00
CodecToken token = ( CodecToken ) temp . AcquireVideoCodecToken ( hwnd . Handle ) ;
2011-07-13 04:04:58 +00:00
temp . CloseFile ( ) ;
File . Delete ( tempfile ) ;
return token ;
}
2011-07-11 07:35:14 +00:00
class Parameters
{
public int width , height ;
public void PopulateBITMAPINFOHEADER ( ref Win32 . BITMAPINFOHEADER bmih )
{
bmih . Init ( ) ;
bmih . biPlanes = 1 ;
bmih . biBitCount = 24 ;
2011-07-11 07:53:48 +00:00
bmih . biHeight = height ;
2011-07-11 07:35:14 +00:00
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 ;
2011-07-13 04:04:58 +00:00
if ( a_bits = = 16 ) bytes = 2 ;
else if ( a_bits = = 8 ) bytes = 1 ;
2011-07-11 07:35:14 +00:00
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 ) ;
2011-07-13 04:04:58 +00:00
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 ;
}
Parameters parameters = new Parameters ( ) ;
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 ;
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
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
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
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
{
2011-07-13 04:04:58 +00:00
~ CodecToken ( )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
Dispose ( ) ;
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
public static CodecToken TakePossession ( Win32 . AVICOMPRESSOPTIONS comprOptions )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
CodecToken ret = new CodecToken ( ) ;
ret . allocated = true ;
ret . comprOptions = comprOptions ;
ret . codec = Win32 . decode_mmioFOURCC ( comprOptions . fccHandler ) ;
return ret ;
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
private CodecToken ( ) { }
public Win32 . AVICOMPRESSOPTIONS comprOptions ;
public string codec ;
bool allocated = false ;
public void Dispose ( )
{
if ( ! allocated ) return ;
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
IntPtr [ ] infPtrs = new IntPtr [ 1 ] ;
IntPtr mem ;
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
// alloc unmanaged memory
mem = Marshal . AllocHGlobal ( Marshal . SizeOf ( typeof ( Win32 . AVICOMPRESSOPTIONS ) ) ) ;
infPtrs [ 0 ] = mem ;
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
// 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 ;
2011-07-11 07:35:14 +00:00
}
}
2012-05-07 21:45:25 +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 )
{
}
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
}
2011-07-13 04:04:58 +00:00
CodecToken currVideoCodecToken = null ;
bool IsOpen ;
IntPtr pAviFile , pAviRawVideoStream , pAviRawAudioStream , pAviCompressedVideoStream ;
IntPtr pGlobalBuf ;
int pGlobalBuf_size ;
/// <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>
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 )
Marshal . FreeHGlobal ( pGlobalBuf ) ;
pGlobalBuf_size = amount ;
pGlobalBuf = Marshal . AllocHGlobal ( pGlobalBuf_size ) ;
}
return pGlobalBuf ;
2011-07-11 07:35:14 +00:00
}
2011-07-13 04:04:58 +00:00
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
OutputStatus outStatus ;
2011-07-13 04:24:55 +00:00
public long GetLengthApproximation ( ) { return outStatus . video_bytes + outStatus . audio_bytes ; }
2011-07-13 04:04:58 +00:00
static int AVISaveOptions ( IntPtr stream , ref Win32 . AVICOMPRESSOPTIONS opts , IntPtr owner )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
IntPtr mem = Marshal . AllocHGlobal ( Marshal . SizeOf ( typeof ( Win32 . AVICOMPRESSOPTIONS ) ) ) ;
Marshal . StructureToPtr ( opts , mem , false ) ;
IntPtr [ ] streams = new [ ] { stream } ;
IntPtr [ ] infPtrs = new [ ] { mem } ;
int ret = Win32 . AVISaveOptions ( owner , 0 , 1 , streams , infPtrs ) ;
opts = ( Win32 . AVICOMPRESSOPTIONS ) Marshal . PtrToStructure ( mem , typeof ( Win32 . AVICOMPRESSOPTIONS ) ) ;
Marshal . FreeHGlobal ( mem ) ;
return ret ;
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 ;
//TODO - try creating the file once first before we let vfw botch it up?
//open the avi output file handle
if ( File . Exists ( destPath ) )
File . Delete ( destPath ) ;
if ( Win32 . FAILED ( Win32 . AVIFileOpenW ( ref pAviFile , destPath , Win32 . OpenFileStyle . OF_CREATE | Win32 . OpenFileStyle . OF_WRITE , 0 ) ) )
throw new InvalidOperationException ( "Couldnt open dest path for avi file: " + destPath ) ;
//initialize the video stream
Win32 . AVISTREAMINFOW vidstream_header = new Win32 . AVISTREAMINFOW ( ) ;
Win32 . BITMAPINFOHEADER bmih = new Win32 . BITMAPINFOHEADER ( ) ;
parameters . PopulateBITMAPINFOHEADER ( ref bmih ) ;
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" ) ;
}
//initialize audio stream
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
/// <summary>
/// Acquires a video codec configuration from the user
/// </summary>
2012-05-06 22:08:47 +00:00
public IDisposable AcquireVideoCodecToken ( IntPtr hwnd )
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
if ( ! IsOpen ) throw new InvalidOperationException ( "File must be opened before acquiring a codec token (or else the stream formats wouldnt be known)" ) ;
//encoder params
Win32 . AVICOMPRESSOPTIONS comprOptions = new Win32 . AVICOMPRESSOPTIONS ( ) ;
if ( currVideoCodecToken ! = null )
{
comprOptions = currVideoCodecToken . comprOptions ;
}
if ( AVISaveOptions ( pAviRawVideoStream , ref comprOptions , hwnd ) ! = 0 )
return CodecToken . TakePossession ( comprOptions ) ;
else 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 )
throw new InvalidOperationException ( "set a video codec token before opening the streams!" ) ;
//open compressed video stream
if ( Win32 . FAILED ( Win32 . AVIMakeCompressedStream ( out pAviCompressedVideoStream , pAviRawVideoStream , ref currVideoCodecToken . comprOptions , IntPtr . Zero ) ) )
{
CloseStreams ( ) ;
throw new InvalidOperationException ( "Failed making compressed video stream" ) ;
}
//set the compressed video stream input format
Win32 . BITMAPINFOHEADER bmih = new Win32 . BITMAPINFOHEADER ( ) ;
parameters . PopulateBITMAPINFOHEADER ( ref bmih ) ;
if ( Win32 . FAILED ( Win32 . AVIStreamSetFormat ( pAviCompressedVideoStream , 0 , ref bmih , Marshal . SizeOf ( bmih ) ) ) )
{
CloseStreams ( ) ;
throw new InvalidOperationException ( "Failed setting compressed video stream input format" ) ;
}
//set audio stream input format
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>
/// wrap up the avi writing
/// </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 ;
}
if ( pAviRawVideoStream ! = IntPtr . Zero )
{
Win32 . AVIStreamRelease ( pAviRawVideoStream ) ;
pAviRawVideoStream = IntPtr . Zero ;
}
if ( pAviFile ! = IntPtr . Zero )
{
Win32 . AVIFileRelease ( pAviFile ) ;
pAviFile = IntPtr . Zero ;
}
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 ( )
{
FlushBufferedAudio ( ) ;
if ( pAviCompressedVideoStream ! = IntPtr . Zero )
{
Win32 . AVIStreamRelease ( pAviCompressedVideoStream ) ;
pAviCompressedVideoStream = IntPtr . Zero ;
}
}
2011-07-11 07:35:14 +00:00
2011-07-13 04:04:58 +00:00
//todo - why couldnt this take an ISoundProvider? it could do the timekeeping as well.. hmm
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 + + )
outStatus . BufferedShorts [ outStatus . audio_buffered_shorts + + ] = samples [ idx + + ] ;
todo - = chunk ;
if ( outStatus . audio_buffered_shorts = = OutputStatus . AUDIO_SEGMENT_SIZE )
FlushBufferedAudio ( ) ;
}
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 ] ;
}
//(TODO - inefficient- build directly in a buffer)
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
2011-07-13 04:04:58 +00:00
int todo = source . BufferHeight * source . BufferWidth ;
int w = source . BufferWidth ;
int h = source . BufferHeight ;
IntPtr buf = GetStaticGlobalBuf ( todo * 3 ) ;
int [ ] buffer = source . GetVideoBuffer ( ) ;
fixed ( int * buffer_ptr = & buffer [ 0 ] )
{
byte * bytes_ptr = ( byte * ) buf . ToPointer ( ) ;
2011-07-11 07:35:14 +00:00
{
2011-07-13 04:04:58 +00:00
byte * bp = bytes_ptr ;
for ( int idx = w * h - w , y = 0 ; y < h ; y + + )
2011-07-11 07:53:48 +00:00
{
2011-07-13 04:04:58 +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 ;
2011-07-11 07:53:48 +00:00
}
2011-07-13 04:04:58 +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
public override string ToString ( )
{
return "avi writer" ;
}
public string WriterDescription ( )
{
return "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." ;
}
public string DesiredExtension ( )
{
return "avi" ;
}
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);
// }
// //bmp.Save(string.Format("c:\\dump\\{0}.bmp", i), ImageFormat.Bmp);
// 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();
////-----