WinUAE/od-win32/avioutput.cpp
2023-05-16 20:02:04 +03:00

2024 lines
55 KiB
C++

/*
UAE - The Ultimate Amiga Emulator
avioutput.c
Copyright(c) 2001 - 2002; §ane
2005-2006; Toni Wilen
*/
#include <windows.h>
#include <mmsystem.h>
#include <vfw.h>
#include <msacm.h>
#include <ks.h>
#include <ksmedia.h>
#include "sysconfig.h"
#include "sysdeps.h"
#include "resource.h"
#include "options.h"
#include "audio.h"
#include "custom.h"
#include "memory.h"
#include "newcpu.h"
#include "picasso96.h"
#include "render.h"
#include "win32.h"
#include "win32gfx.h"
#include "direct3d.h"
#include "sound.h"
#include "gfxfilter.h"
#include "xwin.h"
#include "avioutput.h"
#include "registry.h"
#include "fsdb.h"
#include "threaddep/thread.h"
#include "zfile.h"
#include "savestate.h"
#include "gfxboard.h"
#define MAX_AVI_SIZE (0x80000000 - 0x1000000)
static smp_comm_pipe workindex;
static smp_comm_pipe queuefull;
static volatile int alive;
static volatile int avioutput_failed;
static int avioutput_init = 0;
static int avioutput_needs_restart;
static int frame_start; // start frame
static int frame_count; // current frame
static int frame_skip;
static uae_u64 total_avi_size;
static int partcnt;
static int first_frame;
static int aviout_monid;
static int fps_in_use;
int avioutput_audio, avioutput_video, avioutput_enabled, avioutput_requested;
static int videoallocated;
static int aviout_width_out, aviout_height_out;
static int aviout_xoffset_out, aviout_yoffset_out;
int avioutput_width, avioutput_height, avioutput_bits;
static int avioutput_bitmode = 24;
static int avioutput_fps = VBLANK_HZ_PAL;
int avioutput_framelimiter = 0, avioutput_nosoundoutput = 0;
int avioutput_nosoundsync = 1, avioutput_originalsize = 0;
int avioutput_split_files = 1;
TCHAR avioutput_filename_gui[MAX_DPATH];
TCHAR avioutput_filename_auto[MAX_DPATH];
TCHAR avioutput_filename_inuse[MAX_DPATH];
static TCHAR avioutput_filename_tmp[MAX_DPATH];
extern struct uae_prefs workprefs;
extern TCHAR config_filename[256];
static CRITICAL_SECTION AVIOutput_CriticalSection;
static int cs_allocated;
static PAVIFILE pfile = NULL; // handle of our AVI file
static PAVISTREAM AVIStreamInterface = NULL; // Address of stream interface
static struct zfile *FileStream;
struct avientry {
uae_u8 *lpVideo;
LPBITMAPINFOHEADER lpbi;
uae_u8 *lpAudio;
int sndsize;
int expectedsize;
};
#define AVIENTRY_MAX 10
static int avientryindex;
static struct avientry *avientries[AVIENTRY_MAX + 1];
/* audio */
static int FirstAudio;
static bool audio_validated;
static DWORD dwAudioInputRemaining;
static unsigned int StreamSizeAudio; // audio write position
static float StreamSizeAudioExpected, StreamSizeAudioGot;
static PAVISTREAM AVIAudioStream = NULL; // compressed stream pointer
static HACMSTREAM has = NULL; // stream handle that can be used to perform conversions
static ACMSTREAMHEADER ash;
static ACMFORMATCHOOSE acmopt;
static WAVEFORMATEXTENSIBLE wfxSrc; // source audio format
static LPWAVEFORMATEX pwfxDst = NULL; // pointer to destination audio format
static DWORD wfxMaxFmtSize;
static FILE *wavfile;
static uae_u8 *lpAudioDst, *lpAudioSrc;
static DWORD dwAudioOutputBytes, dwAudioInputBytes;
static uae_u8 *avi_sndbuffer;
static int avi_sndbufsize;
static int avi_sndbuffered;
/* video */
static PAVISTREAM AVIVideoStream = NULL; // compressed stream pointer
static AVICOMPRESSOPTIONS videoOptions;
static AVICOMPRESSOPTIONS FAR * aOptions[] = { &videoOptions }; // array of pointers to AVICOMPRESSOPTIONS structures
static LPBITMAPINFOHEADER lpbi;
static PCOMPVARS pcompvars;
extern bool need_genlock_data;
extern uae_u8 **row_map_genlock;
static bool usealpha(void)
{
return need_genlock_data != 0 && row_map_genlock && currprefs.genlock_image && currprefs.genlock_alpha;
}
void avi_message (const TCHAR *format,...)
{
TCHAR msg[MAX_DPATH];
va_list parms;
DWORD flags = MB_OK | MB_TASKMODAL;
va_start (parms, format);
_vsntprintf (msg, sizeof msg / sizeof (TCHAR), format, parms);
va_end (parms);
write_log (msg);
if (msg[_tcslen (msg) - 1] != '\n')
write_log (_T("\n"));
if (!MessageBox (NULL, msg, _T("AVI"), flags))
write_log (_T("MessageBox(%s) failed, err=%d\n"), msg, GetLastError ());
}
static int lpbisize (void)
{
return sizeof (BITMAPINFOHEADER) + (((avioutput_bits <= 8) ? 1 << avioutput_bits : 0) * sizeof (RGBQUAD));
}
static void freeavientry (struct avientry *ae)
{
if (!ae)
return;
xfree (ae->lpAudio);
xfree (ae->lpVideo);
xfree (ae->lpbi);
xfree (ae);
}
static struct avientry *allocavientry_audio (uae_u8 *snd, int size, int expectedsize)
{
struct avientry *ae = xcalloc (struct avientry, 1);
ae->lpAudio = xmalloc (uae_u8, size);
memcpy (ae->lpAudio, snd, size);
ae->sndsize = size;
ae->expectedsize = expectedsize;
return ae;
}
static struct avientry *allocavientry_video(void)
{
struct avientry *ae = xcalloc (struct avientry, 1);
ae->lpbi = (LPBITMAPINFOHEADER)xmalloc (uae_u8, lpbisize ());
memcpy (ae->lpbi, lpbi, lpbisize ());
ae->lpVideo = xcalloc (uae_u8, lpbi->biSizeImage);
return ae;
}
static void queueavientry (struct avientry *ae)
{
EnterCriticalSection (&AVIOutput_CriticalSection);
avientries[++avientryindex] = ae;
LeaveCriticalSection (&AVIOutput_CriticalSection);
write_comm_pipe_u32 (&workindex, 0, 1);
}
static struct avientry *getavientry (void)
{
int i;
struct avientry *ae;
if (avientryindex < 0)
return NULL;
ae = avientries[0];
for (i = 0; i < avientryindex; i++)
avientries[i] = avientries[i + 1];
avientryindex--;
return ae;
}
static void freequeue (void)
{
struct avientry *ae;
while ((ae = getavientry())) {
freeavientry(ae);
}
}
static void waitqueuefull (void)
{
for (;;) {
EnterCriticalSection (&AVIOutput_CriticalSection);
if (avientryindex < AVIENTRY_MAX) {
LeaveCriticalSection (&AVIOutput_CriticalSection);
while (comm_pipe_has_data (&queuefull))
read_comm_pipe_u32_blocking (&queuefull);
return;
}
LeaveCriticalSection (&AVIOutput_CriticalSection);
read_comm_pipe_u32_blocking (&queuefull);
}
}
static UAEREG *openavikey (void)
{
return regcreatetree (NULL, _T("AVConfiguration"));
}
static void storesettings (UAEREG *avikey)
{
regsetint (avikey, _T("FrameLimiter"), avioutput_framelimiter);
regsetint (avikey, _T("NoSoundOutput"), avioutput_nosoundoutput);
regsetint (avikey, _T("NoSoundSync"), avioutput_nosoundsync);
regsetint (avikey, _T("Original"), avioutput_originalsize);
regsetint(avikey, _T("FPS"), avioutput_fps);
regsetint(avikey, _T("RecordMode"), avioutput_audio);
regsetstr(avikey, _T("FileName"), avioutput_filename_gui);
}
static void getsettings (UAEREG *avikey, bool allowaudio)
{
int val;
if (regqueryint (avikey, _T("NoSoundOutput"), &val))
avioutput_nosoundoutput = val;
if (regqueryint (avikey, _T("NoSoundSync"), &val))
avioutput_nosoundsync = val;
if (regqueryint (avikey, _T("FrameLimiter"), &val))
avioutput_framelimiter = val;
if (regqueryint (avikey, _T("Original"), &val))
avioutput_originalsize = val;
if (!avioutput_framelimiter)
avioutput_nosoundoutput = 1;
if (regqueryint (avikey, _T("FPS"), &val))
avioutput_fps = val;
if (allowaudio) {
if (regqueryint(avikey, _T("RecordMode"), &val))
avioutput_audio = val;
}
val = sizeof avioutput_filename_gui / sizeof(TCHAR);
regquerystr(avikey, _T("FileName"), avioutput_filename_gui, &val);
}
void AVIOutput_GetSettings (void)
{
UAEREG *avikey = openavikey ();
if (avikey)
getsettings (avikey, true);
regclosetree (avikey);
}
void AVIOutput_SetSettings (void)
{
UAEREG *avikey = openavikey ();
if (avikey)
storesettings (avikey);
regclosetree (avikey);
}
void AVIOutput_ReleaseAudio (void)
{
audio_validated = false;
}
static void AVIOutput_FreeAudioDstFormat ()
{
xfree (pwfxDst);
pwfxDst = NULL;
}
static int AVIOutput_AudioAllocated (void)
{
return pwfxDst && audio_validated;
}
static int AVIOutput_AllocateAudio (bool immediate)
{
struct uae_prefs *p = immediate ? &workprefs : &currprefs;
AVIOutput_ReleaseAudio ();
// set the source format
memset (&wfxSrc, 0, sizeof (wfxSrc));
wfxSrc.Format.wFormatTag = WAVE_FORMAT_PCM;
wfxSrc.Format.nChannels = get_audio_nativechannels (p->sound_stereo) ? get_audio_nativechannels (p->sound_stereo) : 2;
wfxSrc.Format.nSamplesPerSec = p->sound_freq ? p->sound_freq : 44100;
wfxSrc.Format.nBlockAlign = wfxSrc.Format.nChannels * 16 / 8;
wfxSrc.Format.nAvgBytesPerSec = wfxSrc.Format.nBlockAlign * wfxSrc.Format.nSamplesPerSec;
wfxSrc.Format.wBitsPerSample = 16;
wfxSrc.Format.cbSize = 0;
if (wfxSrc.Format.nChannels > 2) {
wfxSrc.Format.cbSize = sizeof (WAVEFORMATEXTENSIBLE) - sizeof (WAVEFORMATEX);
wfxSrc.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
wfxSrc.Samples.wValidBitsPerSample = 16;
switch (wfxSrc.Format.nChannels)
{
case 4:
wfxSrc.dwChannelMask = KSAUDIO_SPEAKER_SURROUND;
break;
case 6:
wfxSrc.dwChannelMask = KSAUDIO_SPEAKER_5POINT1_SURROUND;
break;
case 8:
wfxSrc.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
break;
}
}
if (!pwfxDst) {
MMRESULT err;
if ((err = acmMetrics (NULL, ACM_METRIC_MAX_SIZE_FORMAT, &wfxMaxFmtSize))) {
gui_message (_T("acmMetrics() FAILED (%X)\n"), err);
return 0;
}
if (wfxMaxFmtSize < sizeof (WAVEFORMATEX))
return 0;
pwfxDst = (LPWAVEFORMATEX)xmalloc (uae_u8, wfxMaxFmtSize);
memcpy(pwfxDst, &wfxSrc.Format, sizeof WAVEFORMATEX);
pwfxDst->cbSize = (WORD) (wfxMaxFmtSize - sizeof (WAVEFORMATEX)); // shrugs
}
memset(&acmopt, 0, sizeof (ACMFORMATCHOOSE));
acmopt.cbStruct = sizeof (ACMFORMATCHOOSE);
acmopt.fdwStyle = ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT;
acmopt.pwfx = pwfxDst;
acmopt.cbwfx = wfxMaxFmtSize;
acmopt.pszTitle = _T("Choose Audio Codec");
//acmopt.szFormatTag =; // not valid until the format is chosen
//acmopt.szFormat =; // not valid until the format is chosen
//acmopt.pszName =; // can use later in config saving loading
//acmopt.cchName =; // size of pszName, as pszName can be non-null-terminated
acmopt.fdwEnum = ACM_FORMATENUMF_INPUT | ACM_FORMATENUMF_NCHANNELS |
ACM_FORMATENUMF_NSAMPLESPERSEC;
//ACM_FORMATENUMF_CONVERT // renders WinUAE unstable for some unknown reason
//ACM_FORMATENUMF_WBITSPERSAMPLE // MP3 doesn't apply so it will be removed from codec selection
//ACM_FORMATENUMF_SUGGEST // with this flag set, only MP3 320kbps is displayed, which is closest to the source format
acmopt.pwfxEnum = &wfxSrc.Format;
FirstAudio = 1;
dwAudioInputRemaining = 0;
return 1;
}
static int AVIOutput_ValidateAudio (WAVEFORMATEX *wft, TCHAR *name, int len)
{
DWORD ret;
ACMFORMATTAGDETAILS aftd;
ACMFORMATDETAILS afd;
audio_validated = false;
memset(&aftd, 0, sizeof (ACMFORMATTAGDETAILS));
aftd.cbStruct = sizeof (ACMFORMATTAGDETAILS);
aftd.dwFormatTag = wft->wFormatTag;
ret = acmFormatTagDetails (NULL, &aftd, ACM_FORMATTAGDETAILSF_FORMATTAG);
if (ret) {
write_log(_T("acmFormatTagDetails %08x failed\n"), wft->wFormatTag);
return 0;
}
memset (&afd, 0, sizeof (ACMFORMATDETAILS));
afd.cbStruct = sizeof (ACMFORMATDETAILS);
afd.dwFormatTag = wft->wFormatTag;
afd.pwfx = wft;
afd.cbwfx = sizeof (WAVEFORMATEX) + wft->cbSize;
ret = acmFormatDetails (NULL, &afd, ACM_FORMATDETAILSF_FORMAT);
if (ret) {
write_log(_T("acmFormatDetails %08x failed\n"), wft->wFormatTag);
return 0;
}
if (name)
_stprintf (name, _T("%s %s"), aftd.szFormatTag, afd.szFormat);
audio_validated = true;
return 1;
}
static int AVIOutput_GetAudioFromRegistry (WAVEFORMATEX *wft)
{
int ss;
int ok = 0;
UAEREG *avikey;
avikey = openavikey ();
if (!avikey)
return 0;
getsettings (avikey, true);
if (wft) {
ok = -1;
ss = wfxMaxFmtSize;
if (regquerydata (avikey, _T("AudioConfigurationVars"), wft, &ss)) {
if (AVIOutput_ValidateAudio (wft, NULL, 0))
ok = 1;
}
}
if (ok < 0 || !wft)
regdelete (avikey, _T("AudioConfigurationVars"));
regclosetree (avikey);
return ok;
}
static int AVIOutput_GetAudioCodecName (WAVEFORMATEX *wft, TCHAR *name, int len)
{
return AVIOutput_ValidateAudio (wft, name, len);
}
int AVIOutput_GetAudioCodec (TCHAR *name, int len)
{
AVIOutput_Initialize ();
if (AVIOutput_AudioAllocated ())
return AVIOutput_GetAudioCodecName (pwfxDst, name, len);
if (!AVIOutput_AllocateAudio (true))
return 0;
if (AVIOutput_GetAudioFromRegistry (pwfxDst) > 0) {
if (AVIOutput_GetAudioCodecName (pwfxDst, name, len)) {
write_log(_T("Audio: %s\n"), name);
return 1;
}
write_log(_T("Failed to open audio %08x\n"), pwfxDst->wFormatTag);
}
AVIOutput_ReleaseAudio ();
return 0;
}
int AVIOutput_ChooseAudioCodec (HWND hwnd, TCHAR *s, int len)
{
AVIOutput_Initialize ();
AVIOutput_End();
if (!AVIOutput_AllocateAudio (true))
return 0;
acmopt.hwndOwner = hwnd;
switch (acmFormatChoose (&acmopt))
{
case MMSYSERR_NOERROR:
{
UAEREG *avikey;
_tcscpy (s, acmopt.szFormatTag);
avikey = openavikey ();
if (avikey) {
regsetdata (avikey, _T("AudioConfigurationVars"), pwfxDst, pwfxDst->cbSize + sizeof (WAVEFORMATEX));
storesettings (avikey);
regclosetree (avikey);
}
return 1;
}
case ACMERR_CANCELED:
AVIOutput_GetAudioFromRegistry (NULL);
AVIOutput_ReleaseAudio();
break;
case ACMERR_NOTPOSSIBLE:
MessageBox (hwnd, _T("The buffer identified by the pwfx member of the ACMFORMATCHOOSE structure is too small to contain the selected format."), VersionStr, MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
break;
case MMSYSERR_INVALFLAG:
MessageBox (hwnd, _T("At least one flag is invalid."), VersionStr, MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
break;
case MMSYSERR_INVALHANDLE:
MessageBox (hwnd, _T("The specified handle is invalid."), VersionStr, MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
break;
case MMSYSERR_INVALPARAM:
MessageBox (hwnd, _T("At least one parameter is invalid."), VersionStr, MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
break;
case MMSYSERR_NODRIVER:
MessageBox (hwnd, _T("A suitable driver is not available to provide valid format selections.\n(Unsupported channel-mode selected in Sound-panel?)"), VersionStr, MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
break;
default:
MessageBox (hwnd, _T("acmFormatChoose() FAILED"), VersionStr, MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
break;
}
return 0;
}
static int AVIOutput_VideoAllocated(void)
{
return videoallocated ? 1 : 0;
}
void AVIOutput_ReleaseVideo(void)
{
videoallocated = 0;
freequeue();
xfree(lpbi);
lpbi = NULL;
}
static int AVIOutput_AllocateVideo(void)
{
struct AmigaMonitor *mon = &AMonitors[aviout_monid];
avioutput_width = avioutput_height = avioutput_bits = 0;
aviout_width_out = aviout_height_out = 0;
aviout_xoffset_out = aviout_yoffset_out = 0;
avioutput_fps = (int)(vblank_hz + 0.5);
if (!avioutput_fps)
avioutput_fps = ispal(NULL) ? 50 : 60;
if (avioutput_originalsize || WIN32GFX_IsPicassoScreen(mon)) {
int pitch;
if (!gfxboard_isgfxboardscreen(0)) {
getfilterbuffer(0, &avioutput_width, &avioutput_height, &pitch, &avioutput_bits);
} else {
gfxboard_freertgbuffer(0, gfxboard_getrtgbuffer(0, &avioutput_width, &avioutput_height, &pitch, &avioutput_bits, NULL));
}
}
if (avioutput_width == 0 || avioutput_height == 0 || avioutput_bits == 0) {
avioutput_width = WIN32GFX_GetWidth(mon);
avioutput_height = WIN32GFX_GetHeight(mon);
avioutput_bits = WIN32GFX_GetDepth(mon, 0);
if (WIN32GFX_IsPicassoScreen(mon) && avioutput_originalsize) {
struct picasso96_state_struct *state = &picasso96_state[aviout_monid];
avioutput_width = state->Width;
avioutput_height = state->Height;
}
}
aviout_width_out = avioutput_width + 15;
aviout_width_out &= ~15;
aviout_height_out = avioutput_height + 1;
aviout_height_out &= ~1;
AVIOutput_Initialize();
AVIOutput_ReleaseVideo();
if (avioutput_width == 0 || avioutput_height == 0) {
avioutput_width = workprefs.gfx_monitor[0].gfx_size.width;
avioutput_height = workprefs.gfx_monitor[0].gfx_size.height;
avioutput_bits = WIN32GFX_GetDepth(mon, 0);
}
if (!aviout_height_out)
aviout_height_out = avioutput_height;
if (!aviout_width_out)
aviout_width_out = avioutput_width;
if (avioutput_originalsize) {
if (currprefs.aviout_width > 0)
aviout_width_out = currprefs.aviout_width;
if (currprefs.aviout_height > 0)
aviout_height_out = currprefs.aviout_height;
if (currprefs.aviout_xoffset >= 0)
aviout_xoffset_out = currprefs.aviout_xoffset;
if (currprefs.aviout_yoffset >= 0)
aviout_yoffset_out = currprefs.aviout_yoffset;
}
if (avioutput_bits == 0)
avioutput_bits = 24;
if (avioutput_bits > 24)
avioutput_bits = 24;
if (avioutput_bitmode && avioutput_bits >= 24)
avioutput_bits = avioutput_bitmode;
lpbi = (LPBITMAPINFOHEADER)xcalloc(uae_u8, lpbisize());
lpbi->biSize = sizeof(BITMAPINFOHEADER);
lpbi->biWidth = aviout_width_out;
lpbi->biHeight = aviout_height_out;
lpbi->biPlanes = 1;
lpbi->biBitCount = avioutput_bits;
lpbi->biCompression = BI_RGB; // uncompressed format
lpbi->biSizeImage = (((lpbi->biWidth * lpbi->biBitCount + 31) & ~31) / 8) * lpbi->biHeight;
lpbi->biXPelsPerMeter = 0; // ??
lpbi->biYPelsPerMeter = 0; // ??
lpbi->biClrUsed = (lpbi->biBitCount <= 8) ? 1 << lpbi->biBitCount : 0;
lpbi->biClrImportant = 0;
videoallocated = 1;
return 1;
}
static int compressorallocated;
static void AVIOutput_FreeVideoDstFormat(void)
{
if (!pcompvars)
return;
ICClose(pcompvars->hic);
if (compressorallocated)
ICCompressorFree(pcompvars);
compressorallocated = FALSE;
pcompvars->hic = NULL;
}
static int AVIOutput_GetCOMPVARSFromRegistry(COMPVARS *pcv)
{
UAEREG *avikey;
int ss;
int ok = 0;
avikey = openavikey ();
if (!avikey)
return 0;
getsettings (avikey, false);
if (pcv) {
ok = -1;
ss = pcv->cbSize;
pcv->hic = 0;
if (regquerydata (avikey, _T("VideoConfigurationVars"), pcv, &ss)) {
pcv->hic = 0;
pcv->lpbiIn = pcv->lpbiOut = 0;
pcv->cbState = 0;
if (regquerydatasize (avikey, _T("VideoConfigurationState"), &ss)) {
LPBYTE state = NULL;
if (ss > 0) {
state = xmalloc (BYTE, ss);
regquerydata(avikey, _T("VideoConfigurationState"), state, &ss);
}
pcv->hic = ICOpen (pcv->fccType, pcv->fccHandler, ICMODE_COMPRESS);
if (pcv->hic || (!pcv->hic && pcv->fccHandler == mmioFOURCC('D', 'I', 'B', ' '))) { // DIB = uncompressed
ok = 1;
if (pcv->hic)
ICSetState (pcv->hic, state, ss);
}
xfree (state);
}
}
}
if (ok < 0 || !pcv) {
regdelete (avikey, _T("VideoConfigurationVars"));
regdelete (avikey, _T("VideoConfigurationState"));
}
regclosetree (avikey);
return ok;
}
static int AVIOutput_GetVideoCodecName (COMPVARS *pcv, TCHAR *name, int len)
{
ICINFO icinfo = { 0 };
name[0] = 0;
if (pcv->fccHandler == mmioFOURCC ('D','I','B',' ')) {
_tcscpy (name, _T("Full Frames (Uncompressed)"));
return 1;
}
if (ICGetInfo (pcv->hic, &icinfo, sizeof (ICINFO)) != 0) {
_tcsncpy (name, icinfo.szDescription, len);
return 1;
}
return 0;
}
int AVIOutput_GetVideoCodec(TCHAR *name, int len)
{
AVIOutput_Initialize();
if (AVIOutput_VideoAllocated())
return AVIOutput_GetVideoCodecName(pcompvars, name, len);
if (!AVIOutput_AllocateVideo())
return 0;
AVIOutput_FreeVideoDstFormat();
if (AVIOutput_GetCOMPVARSFromRegistry(pcompvars) > 0) {
if (AVIOutput_GetVideoCodecName(pcompvars, name, len)) {
write_log(_T("Video %s\n"), name);
return 1;
}
write_log(_T("Failed to open video %08x\n"), pcompvars->fccType);
}
AVIOutput_ReleaseVideo();
return 0;
}
int AVIOutput_ChooseVideoCodec (HWND hwnd, TCHAR *s, int len)
{
AVIOutput_Initialize();
AVIOutput_End();
if (!AVIOutput_AllocateVideo())
return 0;
AVIOutput_FreeVideoDstFormat();
// we really should check first to see if the user has a particular compressor installed before we set one
// we could set one but we will leave it up to the operating system and the set priority levels for the compressors
// default
//pcompvars->fccHandler = mmioFOURCC('C','V','I','D'); // "Cinepak Codec by Radius"
//pcompvars->fccHandler = mmioFOURCC('M','R','L','E'); // "Microsoft RLE"
//pcompvars->fccHandler = mmioFOURCC('D','I','B',' '); // "Full Frames (Uncompressed)"
pcompvars->lQ = 10000; // 10000 is maximum quality setting or ICQUALITY_DEFAULT for default
pcompvars->lKey = avioutput_fps; // default to one key frame per second, every (FPS) frames
pcompvars->dwFlags = 0;
if (ICCompressorChoose (hwnd, ICMF_CHOOSE_DATARATE | ICMF_CHOOSE_KEYFRAME, lpbi, NULL, pcompvars, "Choose Video Codec") == TRUE) {
UAEREG *avikey;
LRESULT ss;
uae_u8 *state;
compressorallocated = TRUE;
ss = ICGetState (pcompvars->hic, NULL, 0);
if (ss > 0) {
LRESULT err;
state = xmalloc (uae_u8, ss);
err = ICGetState (pcompvars->hic, state, ss);
if (err < 0) {
ss = 0;
xfree (state);
}
} else {
ss = 0;
}
if (ss == 0)
state = xcalloc (uae_u8, 1);
avikey = openavikey ();
if (avikey) {
regsetdata (avikey, _T("VideoConfigurationState"), state, (DWORD)ss);
regsetdata (avikey, _T("VideoConfigurationVars"), pcompvars, pcompvars->cbSize);
storesettings (avikey);
regclosetree (avikey);
}
xfree (state);
return AVIOutput_GetVideoCodecName (pcompvars, s, len);
} else {
AVIOutput_GetCOMPVARSFromRegistry (NULL);
AVIOutput_ReleaseVideo ();
return 0;
}
}
static void AVIOutput_Begin2(bool fullstart, bool immediate);
static void AVIOutput_End2(bool);
uae_s64 max_avi_size = MAX_AVI_SIZE;
static void checkAVIsize(bool force, bool split)
{
int tmp_avioutput_video = avioutput_video;
int tmp_avioutput_audio = avioutput_audio;
TCHAR fn[MAX_DPATH];
if (!force && total_avi_size < max_avi_size)
return;
if (total_avi_size == 0)
return;
if (split) {
if (!avioutput_split_files)
return;
partcnt++;
_tcscpy(fn, avioutput_filename_tmp);
_stprintf(avioutput_filename_inuse, _T("%s_%d.avi"), fn, partcnt);
write_log(_T("AVI split %d at %lld bytes, %d frames\n"), partcnt, total_avi_size, frame_count);
} else {
write_log(_T("AVI restart at %lld bytes, %d frames\n"), total_avi_size, frame_count);
}
AVIOutput_End2(false);
total_avi_size = 0;
avioutput_video = tmp_avioutput_video;
avioutput_audio = tmp_avioutput_audio;
AVIOutput_Begin2(false, false);
_tcscpy (avioutput_filename_tmp, fn);
}
static void dorestart(void)
{
write_log (_T("AVIOutput: parameters changed, restarting..\n"));
checkAVIsize(true, avioutput_needs_restart > 0);
avioutput_needs_restart = 0;
}
static void AVIOuput_AVIWriteAudio (uae_u8 *sndbuffer, int sndbufsize, int expectedsize)
{
struct avientry *ae;
if (avioutput_failed) {
AVIOutput_End ();
return;
}
if (!sndbufsize)
return;
checkAVIsize(false, true);
if (avioutput_needs_restart)
dorestart ();
waitqueuefull ();
ae = allocavientry_audio (sndbuffer, sndbufsize, expectedsize);
queueavientry (ae);
}
typedef short HWORD;
typedef unsigned short UHWORD;
typedef int LWORD;
typedef unsigned int ULWORD;
static int resampleFast(float factor, HWORD *in, HWORD *out, int inCount, int outCount, int nChans);
static uae_u8 *hack_resample(uae_u8 *srcbuffer, int gotSize, int wantedSize)
{
uae_u8 *outbuf;
int ch = wfxSrc.Format.nChannels;
int bytesperframe = ch * 2;
int gotSamples = gotSize / bytesperframe;
int wantedSamples = wantedSize / bytesperframe;
float factor = (float)wantedSamples / gotSamples;
outbuf = xmalloc(uae_u8, wantedSize + bytesperframe);
resampleFast(factor, (HWORD*)srcbuffer, (HWORD*)outbuf, gotSamples, wantedSamples, ch);
return outbuf;
}
static int AVIOutput_AVIWriteAudio_Thread (struct avientry *ae)
{
DWORD flags;
LONG swritten = 0, written = 0;
unsigned int err;
uae_u8 *srcbuffer = NULL;
uae_u8 *freebuffer = NULL;
int srcbuffersize = 0;
if (!avioutput_audio)
return 1;
if (!avioutput_init)
goto error;
if (ae) {
srcbuffer = ae->lpAudio;
srcbuffersize = ae->sndsize;
// size didn't match, resample it to keep AV sync.
if (ae->expectedsize && ae->expectedsize != ae->sndsize) {
srcbuffer = hack_resample(srcbuffer, ae->sndsize, ae->expectedsize);
srcbuffersize = ae->expectedsize;
freebuffer = srcbuffer;
}
}
if (FirstAudio) {
if ((err = acmStreamSize(has, wfxSrc.Format.nSamplesPerSec, &dwAudioOutputBytes, ACM_STREAMSIZEF_SOURCE) != 0)) {
gui_message (_T("acmStreamSize() FAILED (%X)\n"), err);
goto error;
}
dwAudioInputBytes = wfxSrc.Format.nSamplesPerSec;
if (!(lpAudioSrc = xcalloc (uae_u8, dwAudioInputBytes)))
goto error;
if (!(lpAudioDst = xcalloc (uae_u8, dwAudioOutputBytes)))
goto error;
memset(&ash, 0, sizeof ash);
ash.cbStruct = sizeof (ACMSTREAMHEADER);
// source
ash.pbSrc = lpAudioSrc;
ash.cbSrcLength = dwAudioInputBytes;
// destination
ash.pbDst = lpAudioDst;
ash.cbDstLength = dwAudioOutputBytes;
if ((err = acmStreamPrepareHeader (has, &ash, 0))) {
avi_message (_T("acmStreamPrepareHeader() FAILED (%X)\n"), err);
goto error;
}
}
ash.cbSrcLength = 0;
if (srcbuffer) {
if (srcbuffersize + dwAudioInputRemaining > dwAudioInputBytes) {
avi_message(_T("AVIOutput audio buffer overflow!?"));
goto error;
}
ash.cbSrcLength = srcbuffersize;
memcpy(ash.pbSrc + dwAudioInputRemaining, srcbuffer, srcbuffersize);
}
ash.cbSrcLength += dwAudioInputRemaining;
ash.cbSrcLengthUsed = 0;
flags = ACM_STREAMCONVERTF_BLOCKALIGN;
if (FirstAudio)
flags |= ACM_STREAMCONVERTF_START;
if (!ae)
flags |= ACM_STREAMCONVERTF_END;
if ((err = acmStreamConvert (has, &ash, flags))) {
avi_message (_T("acmStreamConvert() FAILED (%X)\n"), err);
goto error;
}
dwAudioInputRemaining = ash.cbSrcLength - ash.cbSrcLengthUsed;
if (dwAudioInputRemaining)
memmove(ash.pbSrc, ash.pbSrc + ash.cbSrcLengthUsed, dwAudioInputRemaining);
if (ash.cbDstLengthUsed) {
if (FileStream) {
zfile_fwrite(lpAudioDst, 1, ash.cbDstLengthUsed, FileStream);
} else {
if ((err = AVIStreamWrite (AVIAudioStream, StreamSizeAudio, ash.cbDstLengthUsed / pwfxDst->nBlockAlign, lpAudioDst, ash.cbDstLengthUsed, 0, &swritten, &written)) != 0) {
avi_message (_T("AVIStreamWrite() FAILED (%X)\n"), err);
goto error;
}
}
}
StreamSizeAudio += swritten;
total_avi_size += written;
FirstAudio = 0;
xfree(freebuffer);
return 1;
error:
xfree(freebuffer);
return 0;
}
static void AVIOutput_AVIWriteAudio_Thread_End(void)
{
if (!FirstAudio && !avioutput_failed) {
AVIOutput_AVIWriteAudio_Thread(NULL);
}
ash.cbSrcLength = avioutput_failed ? 0 : dwAudioInputBytes;
acmStreamUnprepareHeader(has, &ash, 0);
xfree(lpAudioDst);
lpAudioDst = NULL;
xfree(lpAudioSrc);
lpAudioSrc = NULL;
FirstAudio = 1;
dwAudioInputRemaining = 0;
}
static void AVIOuput_WAVWriteAudio(uae_u8 *sndbuffer, int sndbufsize)
{
fwrite(sndbuffer, 1, sndbufsize, wavfile);
}
static int getFromRenderTarget11(struct avientry *avie, bool renderTarget)
{
int ok = 0;
int w, h, pitch, bits = 32, d;
void *data;
bool got = D3D11_capture(aviout_monid, &data, &w, &h, &d, &pitch, renderTarget);
if (got) {
int dpitch = ((aviout_width_out * avioutput_bits + 31) & ~31) / 8;
for (int y = 0; y < h && y < aviout_height_out; y++) {
uae_u8 *d = (uae_u8*)avie->lpVideo + (aviout_height_out - y - 1) * dpitch;
uae_u32 *s = (uae_u32*)((uae_u8*)data + y * pitch);
for (int x = 0; x < w && x < aviout_width_out; x++) {
uae_u32 v = *s++;
d[0] = v >> 0;
d[1] = v >> 8;
d[2] = v >> 16;
if (avioutput_bits == 32) {
d[3] = v >> 24;
d += 4;
} else {
d += 3;
}
}
}
D3D11_capture(aviout_monid, NULL, NULL, NULL, NULL, NULL, renderTarget);
ok = 1;
}
return ok;
}
static int getFromRenderTarget(struct avientry *avie, bool renderTarget)
{
int ok = 0;
int w, h, bits;
HRESULT hr;
D3DLOCKED_RECT l;
LPDIRECT3DSURFACE9 s = D3D_capture(aviout_monid, &w, &h, &bits, renderTarget);
if (s) {
hr = s->LockRect(&l, NULL, D3DLOCK_READONLY);
if (SUCCEEDED(hr)) {
int dpitch = ((aviout_width_out * avioutput_bits + 31) & ~31) / 8;
if (bits == 32) {
for (int y = 0; y < h && y < aviout_height_out; y++) {
uae_u8 *d = (uae_u8*)avie->lpVideo + (aviout_height_out - y - 1) * dpitch;
uae_u32 *s = (uae_u32*)((uae_u8*)l.pBits + y * l.Pitch);
for (int x = 0; x < w && x < aviout_width_out; x++) {
uae_u32 v = *s++;
d[0] = v >> 0;
d[1] = v >> 8;
d[2] = v >> 16;
if (avioutput_bits == 32) {
d[3] = v >> 24;
d += 4;
} else {
d += 3;
}
}
}
} else if (bits == 16 || bits == 15) {
for (int y = 0; y < h && y < aviout_height_out; y++) {
uae_u8 *d = (uae_u8*)avie->lpVideo + (aviout_height_out - y - 1) * dpitch;
uae_u16 *s = (uae_u16*)((uae_u8*)l.pBits + y * l.Pitch);
for (int x = 0; x < w && x < aviout_width_out; x++) {
uae_u16 v = s[x];
uae_u16 v2 = v;
if (bits == 16) {
v2 = v & 31;
v2 |= ((v & (31 << 5)) << 1) | (((v >> 5) & 1) << 5);
v2 |= (v & (31 << 10)) << 1;
} else {
v2 = v & 31;
v2 |= (v >> 1) & (31 << 5);
v2 |= (v >> 1) & (31 << 10);
}
((uae_u16*)d)[0] = v2;
d += 2;
}
}
}
s->UnlockRect();
}
ok = 1;
}
return ok;
}
static int getFromDC(struct avientry *avie)
{
HDC hdc;
HBITMAP hbitmap = NULL;
HBITMAP hbitmapOld = NULL;
HDC hdcMem = NULL;
int ok = 1;
hdc = gethdc(aviout_monid);
if (!hdc)
return 0;
// create a memory device context compatible with the application's current screen
hdcMem = CreateCompatibleDC(hdc);
hbitmap = CreateCompatibleBitmap(hdc, avioutput_width, avioutput_height);
hbitmapOld = (HBITMAP)SelectObject(hdcMem, hbitmap);
// probably not the best idea to use slow GDI functions for this,
// locking the surfaces and copying them by hand would be more efficient perhaps
// draw centered in frame
BitBlt(hdcMem, 0, 0, avioutput_width, avioutput_height, hdc, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbitmapOld);
if (GetDIBits(hdc, hbitmap, 0, avioutput_height, avie->lpVideo, (LPBITMAPINFO)lpbi, DIB_RGB_COLORS) == 0) {
gui_message(_T("GetDIBits() FAILED (%X)\n"), GetLastError());
ok = 0;
}
DeleteObject(hbitmap);
DeleteDC(hdcMem);
releasehdc(aviout_monid, hdc);
return ok;
}
static int rgb_type;
void AVIOutput_RGBinfo (int rb, int gb, int bb, int ab, int rs, int gs, int bs, int as)
{
if (bs == 0 && gs == 5 && rs == 11)
rgb_type = 1;
else if (bs == 0 && gs == 8 && rs == 16)
rgb_type = 2;
else if (bs == 0 && gs == 5 && rs == 10)
rgb_type = 3;
else
rgb_type = 0;
}
#if defined (GFXFILTER)
extern uae_u8 *bufmem_ptr;
static int getFromBuffer(struct avientry *ae, int original)
{
struct AmigaMonitor *mon = &AMonitors[aviout_monid];
struct vidbuf_description *vidinfo = &adisplays[aviout_monid].gfxvidinfo;
int x, y, w, h, d;
uae_u8 *src, *mem;
uae_u8 *dst = ae->lpVideo;
int spitch, dpitch;
int maxw, maxh;
mem = NULL;
dpitch = ((aviout_width_out * avioutput_bits + 31) & ~31) / 8;
if (original || WIN32GFX_IsPicassoScreen(mon)) {
if (!gfxboard_isgfxboardscreen(aviout_monid)) {
src = getfilterbuffer(aviout_monid, &w, &h, &spitch, &d);
maxw = vidinfo->outbuffer->outwidth;
maxh = vidinfo->outbuffer->outheight;
} else {
src = mem = gfxboard_getrtgbuffer(aviout_monid, &w, &h, &spitch, &d, NULL);
maxw = w;
maxh = h;
}
} else {
spitch = vidinfo->outbuffer->rowbytes;
src = bufmem_ptr;
maxw = vidinfo->outbuffer->outwidth;
maxh = vidinfo->outbuffer->outheight;
}
if (!src)
return 0;
int xoffset = currprefs.aviout_xoffset < 0 ? (aviout_width_out - avioutput_width) / 2 : -currprefs.aviout_xoffset;
int yoffset = currprefs.aviout_yoffset < 0 ? (aviout_height_out - avioutput_height) / 2 : -currprefs.aviout_yoffset;
dst += dpitch * aviout_height_out;
if (yoffset > 0) {
if (yoffset >= aviout_height_out - avioutput_height)
yoffset = aviout_height_out - avioutput_height;
dst -= dpitch * yoffset;
} else if (yoffset < 0) {
yoffset = -yoffset;
if (yoffset >= avioutput_height - aviout_height_out)
yoffset = avioutput_height - aviout_height_out;
src += spitch * yoffset;
}
int xoffset2 = 0;
if (xoffset < 0) {
xoffset2 = -xoffset;
xoffset = 0;
}
int dbpx = avioutput_bits / 8;
int sbpx = avioutput_bits / 8;
if (sbpx == 3)
sbpx = 4;
for (y = 0; y < avioutput_height && y < maxh && y < aviout_height_out; y++) {
uae_u8 *s, *d;
dst -= dpitch;
d = dst;
s = src;
if (xoffset > 0) {
d += xoffset * dbpx;
} else if (xoffset2 > 0) {
s += xoffset2 * sbpx;
}
for (x = 0; x < avioutput_width && x < maxw && x < aviout_width_out; x++) {
if (avioutput_bits == 8) {
*d++ = s[x];
} else if (avioutput_bits == 16) {
uae_u16 v = ((uae_u16*)s)[x];
uae_u16 v2 = v;
if (rgb_type == 3) {
v2 = v & 31;
v2 |= ((v & (31 << 5)) << 1) | (((v >> 5) & 1) << 5);
v2 |= (v & (31 << 10)) << 1;
} else if (rgb_type) {
v2 = v & 31;
v2 |= (v >> 1) & (31 << 5);
v2 |= (v >> 1) & (31 << 10);
}
((uae_u16*)d)[0] = v2;
d += 2;
} else if (avioutput_bits == 32) {
uae_u32 v = ((uae_u32*)s)[x];
((uae_u32*)d)[0] = v;
d += 4;
} else if (avioutput_bits == 24) {
uae_u32 v = ((uae_u32*)s)[x];
*d++ = v;
*d++ = v >> 8;
*d++ = v >> 16;
}
}
src += spitch;
}
if (mem)
gfxboard_freertgbuffer(aviout_monid, mem);
return 1;
}
#endif
static void AVIOutput_WriteVideo(void)
{
struct AmigaMonitor *mon = &AMonitors[aviout_monid];
struct avientry *ae;
int v;
if (avioutput_failed) {
AVIOutput_End ();
return;
}
checkAVIsize(false, true);
if (avioutput_needs_restart)
dorestart ();
waitqueuefull ();
ae = allocavientry_video();
lockrtg();
if (avioutput_originalsize) {
v = getFromBuffer (ae, 1);
if (!v) {
if (D3D_isenabled(0) == 2) {
v = getFromRenderTarget11(ae, false);
} else if (D3D_isenabled(0) == 1) {
v = getFromRenderTarget(ae, false);
}
}
} else {
if (D3D_isenabled(0) == 2) {
v = getFromRenderTarget11(ae, true);
} else if (D3D_isenabled(0) == 1) {
v = getFromRenderTarget(ae, true);
} else {
v = getFromDC (ae);
}
}
unlockrtg();
if (v)
queueavientry (ae);
else
AVIOutput_End ();
}
static int AVIOutput_AVIWriteVideo_Thread (struct avientry *ae)
{
LONG written = 0;
unsigned int err;
if (avioutput_video) {
if (!avioutput_init)
goto error;
// GetDIBits tends to change this and ruins palettized output
ae->lpbi->biClrUsed = (avioutput_bits <= 8) ? 1 << avioutput_bits : 0;
if (!frame_count) {
if ((err = AVIStreamSetFormat (AVIVideoStream, frame_count, ae->lpbi, ae->lpbi->biSize + (ae->lpbi->biClrUsed * sizeof (RGBQUAD)))) != 0) {
avi_message (_T("AVIStreamSetFormat() FAILED (%X)\n"), err);
goto error;
}
}
if ((err = AVIStreamWrite (AVIVideoStream, frame_count, 1, ae->lpVideo, ae->lpbi->biSizeImage, 0, NULL, &written)) != 0) {
avi_message (_T("AVIStreamWrite() FAILED (%X)\n"), err);
goto error;
}
frame_count++;
total_avi_size += written;
} else {
avi_message (_T("DirectDraw_GetDC() FAILED\n"));
goto error;
}
if ((frame_count % (fps_in_use * 10)) == 0)
write_log (_T("AVIOutput: %d frames, (%d fps)\n"), frame_count, fps_in_use);
return 1;
error:
return 0;
}
static void writewavheader (uae_u32 size)
{
uae_u16 tw;
uae_u32 tl;
int bits = 16;
int channels = get_audio_nativechannels (currprefs.sound_stereo);
fseek (wavfile, 0, SEEK_SET);
fwrite ("RIFF", 1, 4, wavfile);
tl = 0;
if (size)
tl = size - 8;
fwrite (&tl, 1, 4, wavfile);
fwrite ("WAVEfmt ", 1, 8, wavfile);
tl = 16;
fwrite (&tl, 1, 4, wavfile);
tw = 1;
fwrite (&tw, 1, 2, wavfile);
tw = channels;
fwrite (&tw, 1, 2, wavfile);
tl = currprefs.sound_freq;
fwrite (&tl, 1, 4, wavfile);
tl = currprefs.sound_freq * channels * bits / 8;
fwrite (&tl, 1, 4, wavfile);
tw = channels * bits / 8;
fwrite (&tw, 1, 2, wavfile);
tw = bits;
fwrite (&tw, 1, 2, wavfile);
fwrite ("data", 1, 4, wavfile);
tl = 0;
if (size)
tl = size - 44;
fwrite (&tl, 1, 4, wavfile);
}
void AVIOutput_Restart(bool split)
{
if (first_frame)
return;
avioutput_needs_restart = split ? 1 : -1;
}
static void AVIOutput_End2(bool fullrestart)
{
if (alive) {
write_log (_T("killing worker thread\n"));
write_comm_pipe_u32 (&workindex, 0xfffffffe, 1);
while (alive) {
while (comm_pipe_has_data (&queuefull))
read_comm_pipe_u32_blocking (&queuefull);
Sleep (10);
}
}
first_frame = 0;
avioutput_enabled = 0;
avioutput_failed = 0;
freequeue ();
destroy_comm_pipe (&workindex);
destroy_comm_pipe (&queuefull);
if (has) {
acmStreamClose (has, 0);
has = NULL;
}
if (AVIAudioStream) {
AVIStreamRelease (AVIAudioStream);
AVIAudioStream = NULL;
}
if (AVIVideoStream) {
AVIStreamRelease (AVIVideoStream);
AVIVideoStream = NULL;
}
if (AVIStreamInterface) {
AVIStreamRelease (AVIStreamInterface);
AVIStreamInterface = NULL;
}
if (pfile) {
AVIFileRelease (pfile);
pfile = NULL;
}
StreamSizeAudio = frame_count = 0;
StreamSizeAudioExpected = 0;
StreamSizeAudioGot = 0;
if (fullrestart) {
partcnt = 0;
avi_sndbuffered = 0;
xfree(avi_sndbuffer);
avi_sndbuffer = NULL;
avi_sndbufsize = 0;
}
if (wavfile) {
writewavheader (ftell (wavfile));
fclose (wavfile);
wavfile = 0;
}
}
void AVIOutput_End(void)
{
AVIOutput_End2(true);
}
static void AVIOutput_worker (void *arg);
static void AVIOutput_Begin2(bool fullstart, bool immediate)
{
AVISTREAMINFO avistreaminfo; // Structure containing information about the stream, including the stream type and its sample rate
int i, err;
TCHAR *ext1, *ext2;
struct avientry *ae = NULL;
AVIOutput_Initialize ();
if (avioutput_enabled) {
if (!avioutput_requested)
AVIOutput_End ();
return;
}
if (!avioutput_requested)
return;
aviout_monid = getfocusedmonitor();
if (avioutput_audio == AVIAUDIO_WAV) {
ext1 = _T(".wav"); ext2 = _T(".avi");
} else {
ext1 = _T(".avi"); ext2 = _T(".wav");
}
if (uaetcslen(avioutput_filename_inuse) >= 4 && !_tcsicmp (avioutput_filename_inuse + uaetcslen(avioutput_filename_inuse) - 4, ext2))
avioutput_filename_inuse[uaetcslen(avioutput_filename_inuse) - 4] = 0;
if (uaetcslen(avioutput_filename_inuse) >= 4 && _tcsicmp (avioutput_filename_inuse + uaetcslen(avioutput_filename_inuse) - 4, ext1))
_tcscat (avioutput_filename_inuse, ext1);
_tcscpy (avioutput_filename_tmp, avioutput_filename_inuse);
i = uaetcslen(avioutput_filename_tmp) - 1;
while (i > 0 && avioutput_filename_tmp[i] != '.') i--;
if (i > 0)
avioutput_filename_tmp[i] = 0;
avioutput_needs_restart = 0;
avioutput_enabled = avioutput_audio || avioutput_video;
if (!avioutput_init || !avioutput_enabled) {
write_log (_T("No video or audio enabled\n"));
goto error;
}
if (fullstart) {
reset_sound();
first_frame = 0;
if (savestate_state && !avioutput_originalsize)
first_frame = 1;
} else {
first_frame = 0;
}
// delete any existing file before writing AVI
SetFileAttributes (avioutput_filename_inuse, FILE_ATTRIBUTE_ARCHIVE);
DeleteFile (avioutput_filename_inuse);
if (avioutput_audio == AVIAUDIO_WAV) {
wavfile = _tfopen (avioutput_filename_inuse, _T("wb"));
if (!wavfile) {
gui_message (_T("Failed to open wave-file\n\nThis can happen if the path and or file name was entered incorrectly.\n"));
goto error;
}
writewavheader (0);
write_log (_T("wave-output to '%s' started\n"), avioutput_filename_inuse);
return;
}
if (!FileStream) {
if (((err = AVIFileOpen (&pfile, avioutput_filename_inuse, OF_CREATE | OF_WRITE, NULL)) != 0)) {
gui_message (_T("AVIFileOpen() FAILED (Error %X)\n\nThis can happen if the path and or file name was entered incorrectly.\nRequired *.avi extension.\n"), err);
goto error;
}
}
if (avioutput_audio) {
if (!AVIOutput_AllocateAudio (immediate))
goto error;
memset (&avistreaminfo, 0, sizeof (AVISTREAMINFO));
avistreaminfo.fccType = streamtypeAUDIO;
avistreaminfo.fccHandler = 0; // This member is not used for audio streams.
avistreaminfo.dwFlags = 0;
//avistreaminfo.dwCaps =; // Capability flags; currently unused.
//avistreaminfo.wPriority =;
//avistreaminfo.wLanguage =;
avistreaminfo.dwScale = pwfxDst->nBlockAlign;
avistreaminfo.dwRate = pwfxDst->nAvgBytesPerSec;
avistreaminfo.dwStart = 0;
avistreaminfo.dwLength = -1;
avistreaminfo.dwInitialFrames = 0;
avistreaminfo.dwSuggestedBufferSize = 0; // Use zero if you do not know the correct buffer size.
avistreaminfo.dwQuality = -1; // -1 default quality value
avistreaminfo.dwSampleSize = pwfxDst->nBlockAlign;
//avistreaminfo.rcFrame; // doesn't apply to audio
//avistreaminfo.dwEditCount =; // Number of times the stream has been edited. The stream handler maintains this count.
//avistreaminfo.dwFormatChangeCount =; // Number of times the stream format has changed. The stream handler maintains this count.
_tcscpy (avistreaminfo.szName, _T("Audiostream")); // description of the stream.
// create the audio stream
if ((err = AVIFileCreateStream (pfile, &AVIAudioStream, &avistreaminfo)) != 0) {
gui_message (_T("AVIFileCreateStream() FAILED (%X)\n"), err);
goto error;
}
if ((err = AVIStreamSetFormat (AVIAudioStream, 0, pwfxDst, sizeof (WAVEFORMATEX) + pwfxDst->cbSize)) != 0) {
gui_message (_T("AVIStreamSetFormat() FAILED (%X)\n"), err);
goto error;
}
if ((err = acmStreamOpen(&has, NULL, &wfxSrc.Format, pwfxDst, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME)) != 0) {
gui_message (_T("acmStreamOpen() FAILED (%X)\n"), err);
goto error;
}
audio_validated = true;
}
if (avioutput_video) {
int retrycnt = 0;
while (retrycnt < 2) {
avioutput_bitmode = retrycnt ? 32 : 24;
if (usealpha()) {
avioutput_bitmode = avioutput_bitmode == 32 ? 24 : 32;
}
retrycnt++;
ae = allocavientry_video();
if (!AVIOutput_AllocateVideo())
goto error;
// fill in the header for the video stream
memset(&avistreaminfo, 0, sizeof(AVISTREAMINFO));
avistreaminfo.fccType = streamtypeVIDEO; // stream type
// unsure about this, as this is the uncompressed stream, not the compressed stream
//avistreaminfo.fccHandler = 0;
// incase the amiga changes palette
if (ae->lpbi->biBitCount < 24)
avistreaminfo.dwFlags = AVISTREAMINFO_FORMATCHANGES;
//avistreaminfo.dwCaps =; // Capability flags; currently unused
//avistreaminfo.wPriority =; // Priority of the stream
//avistreaminfo.wLanguage =; // Language of the stream
avistreaminfo.dwScale = 1;
avistreaminfo.dwRate = avioutput_fps; // our playback speed default (PAL 50fps), (NTSC 60fps)
avistreaminfo.dwStart = 0; // no delay
avistreaminfo.dwLength = 1; // initial length
//avistreaminfo.dwInitialFrames =; // audio only
avistreaminfo.dwSuggestedBufferSize = ae->lpbi->biSizeImage;
avistreaminfo.dwQuality = -1; // drivers will use the default quality setting
avistreaminfo.dwSampleSize = 0; // variable video data samples
SetRect(&avistreaminfo.rcFrame, 0, 0, ae->lpbi->biWidth, ae->lpbi->biHeight); // rectangle for stream
//avistreaminfo.dwEditCount =; // Number of times the stream has been edited. The stream handler maintains this count.
//avistreaminfo.dwFormatChangeCount =; // Number of times the stream format has changed. The stream handler maintains this count.
_tcscpy(avistreaminfo.szName, _T("Videostream")); // description of the stream.
// create the stream
if ((err = AVIFileCreateStream(pfile, &AVIStreamInterface, &avistreaminfo)) != 0) {
if (retrycnt < 2) {
freeavientry(ae);
continue;
}
gui_message(_T("AVIFileCreateStream() FAILED (%08X)\n"), err);
goto error;
}
break;
}
videoOptions.fccType = streamtypeVIDEO;
videoOptions.fccHandler = pcompvars->fccHandler;
videoOptions.dwKeyFrameEvery = pcompvars->lKey;
videoOptions.dwQuality = pcompvars->lQ;
videoOptions.dwBytesPerSecond = pcompvars->lDataRate * 1024;
videoOptions.dwFlags = AVICOMPRESSF_VALID | AVICOMPRESSF_KEYFRAMES | AVICOMPRESSF_INTERLEAVE | AVICOMPRESSF_DATARATE;
videoOptions.dwInterleaveEvery = 1;
videoOptions.cbFormat = sizeof(BITMAPINFOHEADER);
videoOptions.lpFormat = lpbi;
videoOptions.cbParms = pcompvars->cbState;
videoOptions.lpParms = pcompvars->lpState;
// create a compressed stream from our uncompressed stream and a compression filter
if ((err = AVIMakeCompressedStream(&AVIVideoStream, AVIStreamInterface, &videoOptions, NULL)) != AVIERR_OK) {
gui_message(_T("AVIMakeCompressedStream() FAILED (%X)\n"), err);
goto error;
}
}
freeavientry(ae);
init_comm_pipe(&workindex, 20, 1);
init_comm_pipe(&queuefull, 20, 1);
avientryindex = -1;
alive = -1;
fps_in_use = avioutput_fps;
uae_start_thread(_T("aviworker"), AVIOutput_worker, NULL, NULL);
write_log(_T("AVIOutput enabled: monitor=%d video=%d audio=%d path='%s'\n"), aviout_monid, avioutput_video, avioutput_audio, avioutput_filename_inuse);
return;
error:
freeavientry(ae);
AVIOutput_End();
}
void AVIOutput_Begin(bool immediate)
{
AVIOutput_Begin2(true, immediate);
}
void AVIOutput_Release (void)
{
AVIOutput_End ();
AVIOutput_ReleaseAudio ();
AVIOutput_ReleaseVideo ();
if (avioutput_init) {
AVIFileExit ();
avioutput_init = 0;
}
AVIOutput_FreeAudioDstFormat();
AVIOutput_FreeVideoDstFormat();
xfree (pcompvars);
pcompvars = NULL;
if (cs_allocated) {
DeleteCriticalSection (&AVIOutput_CriticalSection);
cs_allocated = 0;
}
}
void AVIOutput_Initialize (void)
{
if (avioutput_init)
return;
InitializeCriticalSection (&AVIOutput_CriticalSection);
cs_allocated = 1;
pcompvars = xcalloc (COMPVARS, 1);
if (!pcompvars)
return;
pcompvars->cbSize = sizeof (COMPVARS);
AVIFileInit ();
avioutput_init = 1;
avientryindex = -1;
}
static void AVIOutput_worker (void *arg)
{
bool quit = false;
write_log (_T("AVIOutput worker thread started\n"));
alive = 1;
for (;;) {
uae_u32 idx = read_comm_pipe_u32_blocking (&workindex);
struct avientry *ae;
int r1 = 1;
int r2 = 1;
if (idx == 0xffffffff)
break;
if (idx == 0xfffffffe)
quit = true;
for (;;) {
EnterCriticalSection (&AVIOutput_CriticalSection);
ae = getavientry ();
LeaveCriticalSection (&AVIOutput_CriticalSection);
if (ae == NULL)
break;
if (!quit) {
write_comm_pipe_u32(&queuefull, 0, 1);
}
if (!avioutput_failed) {
if (ae->lpAudio)
r1 = AVIOutput_AVIWriteAudio_Thread (ae);
if (ae->lpVideo)
r2 = AVIOutput_AVIWriteVideo_Thread (ae);
if (r1 == 0 || r2 == 0)
avioutput_failed = 1;
}
freeavientry (ae);
}
if (idx == 0xffffffff || quit || avioutput_failed)
break;
}
AVIOutput_AVIWriteAudio_Thread_End();
write_log (_T("AVIOutput worker thread killed. quit=%d\n"), quit);
alive = 0;
}
void AVIOutput_Toggle (int mode, bool immediate)
{
if (mode < 0) {
avioutput_requested = !avioutput_requested;
} else {
if (mode == avioutput_requested)
return;
avioutput_requested = mode;
}
if (!avioutput_requested)
AVIOutput_End ();
if (immediate && avioutput_requested) {
TCHAR tmp[MAX_DPATH];
_tcscpy (avioutput_filename_inuse, avioutput_filename_auto);
if (!avioutput_filename_inuse[0]) {
fetch_path (_T("VideoPath"), avioutput_filename_inuse, sizeof (avioutput_filename_inuse) / sizeof (TCHAR));
_tcscat (avioutput_filename_inuse, _T("output.avi"));
}
for (;;) {
int num = 0;
TCHAR *ext = NULL;
TCHAR *p = _tcsrchr (avioutput_filename_inuse, '.');
if (p)
ext = my_strdup (p);
else
p = avioutput_filename_inuse + _tcslen (avioutput_filename_inuse);
p[0] = 0;
if (p > avioutput_filename_inuse && p[-1] >= '0' && p[-1] <= '9') {
p--;
while (p > avioutput_filename_inuse && p[-1] >= '0' && p[-1] <= '9')
p--;
num = _tstol (p);
} else {
*p++ = '_';
*p = 0;
}
num++;
_stprintf (p, _T("%u"), num);
if (ext)
_tcscat (avioutput_filename_inuse, ext);
xfree (ext);
if (!my_existsfile (avioutput_filename_inuse))
break;
}
if (avioutput_audio != AVIAUDIO_WAV) {
avioutput_audio = AVIOutput_GetAudioCodec (tmp, sizeof tmp / sizeof (TCHAR));
avioutput_video = AVIOutput_GetVideoCodec (tmp, sizeof tmp / sizeof (TCHAR));
}
AVIOutput_Begin (immediate);
} else if (!immediate && avioutput_requested) {
_tcscpy (avioutput_filename_inuse, avioutput_filename_gui);
}
}
static void start_if_requested(void)
{
if (!avioutput_requested)
return;
AVIOutput_Begin(true);
}
static bool can_record(void)
{
if (savestate_state == STATE_DORESTORE || savestate_state == STATE_RESTORE)
return false;
return true;
}
bool AVIOutput_WriteAudio(uae_u8 *sndbuffer, int sndbufsize)
{
if (!can_record())
return false;
start_if_requested();
if (!avioutput_audio || !avioutput_enabled)
return false;
if (!sndbufsize)
return false;
if (avioutput_failed)
return false;
if (!avi_sndbuffer) {
avi_sndbufsize = sndbufsize * 2;
avi_sndbuffer = xcalloc(uae_u8, avi_sndbufsize);
avi_sndbuffered = 0;
}
while (avi_sndbuffered + sndbufsize > avi_sndbufsize) {
avi_sndbufsize *= 2;
avi_sndbuffer = xrealloc(uae_u8, avi_sndbuffer, avi_sndbufsize);
}
memcpy(avi_sndbuffer + avi_sndbuffered, sndbuffer, sndbufsize);
avi_sndbuffered += sndbufsize;
return true;
}
bool frame_drawn(int monid)
{
if (monid != aviout_monid)
return false;
if (!can_record())
return false;
start_if_requested();
if (screenshot_multi) {
screenshot(monid, 1, 1);
if (screenshot_multi > 0)
screenshot_multi--;
}
if (!avioutput_enabled)
return false;
if (avioutput_failed)
return false;
if (avioutput_audio == AVIAUDIO_WAV) {
finish_sound_buffer();
if (!first_frame) {
AVIOuput_WAVWriteAudio(avi_sndbuffer, avi_sndbuffered);
}
first_frame = 0;
avi_sndbuffered = 0;
return true;
}
if (avioutput_audio) {
finish_sound_buffer();
if (first_frame == 0 || first_frame == -1) {
int bytesperframe;
bytesperframe = wfxSrc.Format.nChannels * 2;
StreamSizeAudioGot += avi_sndbuffered / bytesperframe;
unsigned int lastexpected = (unsigned int)StreamSizeAudioExpected;
StreamSizeAudioExpected += ((float)wfxSrc.Format.nSamplesPerSec) / fps_in_use;
if (avioutput_video) {
int idiff = (int)(StreamSizeAudioGot - StreamSizeAudioExpected);
if ((vsync_counter % 5) == 0)
write_log(_T("%.1f %.1f %d\n"), StreamSizeAudioExpected, StreamSizeAudioGot, idiff);
if (idiff) {
StreamSizeAudioGot = StreamSizeAudioExpected;
AVIOuput_AVIWriteAudio(avi_sndbuffer, avi_sndbuffered, (int)(StreamSizeAudioExpected - lastexpected) * bytesperframe);
} else {
AVIOuput_AVIWriteAudio(avi_sndbuffer, avi_sndbuffered, 0);
}
} else {
AVIOuput_AVIWriteAudio(avi_sndbuffer, avi_sndbuffered, 0);
}
}
avi_sndbuffered = 0;
}
if (first_frame) {
if (first_frame > 0)
first_frame--;
else
first_frame++;
return true;
}
if (!avioutput_video)
return false;
AVIOutput_WriteVideo();
return true;
}
/* Resampler from:
* Digital Audio Resampling Home Page located at
* http://ccrma.stanford.edu/~jos/resample/.
*/
#define Nhc 8
#define Na 7
#define Np (Nhc+Na)
#define Npc (1<<Nhc)
#define Amask ((1<<Na)-1)
#define Pmask ((1<<Np)-1)
#define Nh 16
#define Nb 16
#define Nhxn 14
#define Nhg (Nh-Nhxn)
#define NLpScl 13
#define MAX_HWORD (32767)
#define MIN_HWORD (-32768)
STATIC_INLINE HWORD WordToHword(LWORD v, int scl)
{
HWORD out;
LWORD llsb = (1 << (scl - 1));
v += llsb; /* round */
v >>= scl;
if (v > MAX_HWORD) {
v = MAX_HWORD;
} else if (v < MIN_HWORD) {
v = MIN_HWORD;
}
out = (HWORD)v;
return out;
}
static int SrcLinear(HWORD X[], HWORD Y[], float factor, ULWORD *Time, UHWORD Nx)
{
HWORD iconst;
HWORD *Xp, *Ystart;
LWORD v, x1, x2;
float dt; /* Step through input signal */
ULWORD dtb; /* Fixed-point version of Dt */
ULWORD endTime; /* When Time reaches EndTime, return to user */
dt = 1.0f / factor; /* Output sampling period */
dtb = (ULWORD)(dt*(1 << Np) + 0.5); /* Fixed-point representation */
Ystart = Y;
endTime = *Time + (1 << Np)*(LWORD)Nx;
while (*Time < endTime) {
iconst = (*Time) & Pmask;
Xp = &X[(*Time) >> Np]; /* Ptr to current input sample */
x1 = *Xp++;
x2 = *Xp;
x1 *= ((1 << Np) - iconst);
x2 *= iconst;
v = x1 + x2;
*Y++ = WordToHword(v, Np); /* Deposit output */
*Time += dtb; /* Move to next sample by time increment */
}
return (int)(Y - Ystart); /* Return number of output samples */
}
#define IBUFFSIZE 4096 /* Input buffer size */
#define OBUFFSIZE 8192 /* Output buffer size */
static int resampleFast( /* number of output samples returned */
float factor, /* factor = Sndout/Sndin */
HWORD *in, /* input and output file descriptors */
HWORD *out,
int inCount, /* number of input samples to convert */
int outCount, /* number of output samples to compute */
int nChans) /* number of sound channels (1 or 2) */
{
ULWORD Time, Time2; /* Current time/pos in input sample */
UHWORD Xp, Ncreep, Xoff, Xread;
HWORD X1[IBUFFSIZE], Y1[OBUFFSIZE]; /* I/O buffers */
HWORD X2[IBUFFSIZE], Y2[OBUFFSIZE]; /* I/O buffers */
UHWORD Nout, Nx;
int i, Ycount, last;
int inseekpos = 0;
int outseekpos = 0;
Xoff = 10;
Nx = IBUFFSIZE - 2 * Xoff; /* # of samples to process each iteration */
last = 0; /* Have not read last input sample yet */
Ycount = 0; /* Current sample and length of output file */
Xp = Xoff; /* Current "now"-sample pointer for input */
Xread = Xoff; /* Position in input array to read into */
Time = (Xoff << Np); /* Current-time pointer for converter */
for (i = 0; i<Xoff; X1[i++] = 0); /* Need Xoff zeros at begining of sample */
for (i = 0; i<Xoff; X2[i++] = 0); /* Need Xoff zeros at begining of sample */
do {
if (!last) /* If haven't read last sample yet */
{
int idx = Xread;
while (inCount > 0 && idx < IBUFFSIZE) {
if (nChans == 2) {
X1[idx] = in[inseekpos * 2 + 0];
X2[idx] = in[inseekpos * 2 + 1];
} else {
X1[idx] = in[inseekpos];
}
inCount--;
idx++;
inseekpos++;
}
if (inCount == 0)
last = idx;
if (last && (last - Xoff<Nx)) { /* If last sample has been read... */
Nx = last - Xoff; /* ...calc last sample affected by filter */
if (Nx <= 0)
break;
}
}
/* Resample stuff in input buffer */
Time2 = Time;
Nout = SrcLinear(X1, Y1, factor, &Time, Nx);
if (nChans == 2)
Nout = SrcLinear(X2, Y2, factor, &Time2, Nx);
Time -= (Nx << Np); /* Move converter Nx samples back in time */
Xp += Nx; /* Advance by number of samples processed */
Ncreep = (Time >> Np) - Xoff; /* Calc time accumulation in Time */
if (Ncreep) {
Time -= (Ncreep << Np); /* Remove time accumulation */
Xp += Ncreep; /* and add it to read pointer */
}
for (i = 0; i<IBUFFSIZE - Xp + Xoff; i++) { /* Copy part of input signal */
X1[i] = X1[i + Xp - Xoff]; /* that must be re-used */
if (nChans == 2)
X2[i] = X2[i + Xp - Xoff]; /* that must be re-used */
}
if (last) { /* If near end of sample... */
last -= Xp; /* ...keep track were it ends */
if (!last) /* Lengthen input by 1 sample if... */
last++; /* ...needed to keep flag TRUE */
}
Xread = i; /* Pos in input buff to read new data into */
Xp = Xoff;
Ycount += Nout;
if (Ycount>outCount) {
Nout -= (Ycount - outCount);
Ycount = outCount;
}
if (Nout > OBUFFSIZE) /* Check to see if output buff overflowed */
return -1;
if (nChans == 1) {
for (i = 0; i < Nout; i++) {
out[outseekpos] = Y1[i];
outseekpos++;
}
} else {
for (i = 0; i < Nout; i++) {
out[outseekpos * 2 + 0] = Y1[i];
out[outseekpos * 2 + 1] = Y2[i];
outseekpos++;
}
}
} while (Ycount<outCount); /* Continue until done */
return(Ycount); /* Return # of samples in output file */
}