Windows Port: When recording AVIs, video framebuffer conversions and file write operations are performed asynchronously with the main thread. This should greatly increase AVI recording performance.

This commit is contained in:
rogerman 2018-02-25 16:00:31 -08:00
parent c7ca122d95
commit cbe4717d2f
3 changed files with 291 additions and 171 deletions

427
desmume/src/frontend/windows/aviout.cpp Normal file → Executable file
View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2006-2017 DeSmuME team Copyright (C) 2006-2018 DeSmuME team
This file is free software: you can redistribute it and/or modify This file is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -48,24 +48,33 @@ static void EMU_PrintMessage(const char* msg) {
//extern WAVEFORMATEX wf; //extern WAVEFORMATEX wf;
//extern int soundo; //extern int soundo;
#define VIDEO_STREAM 0 #define VIDEO_STREAM 0
#define AUDIO_STREAM 1 #define AUDIO_STREAM 1
#define MAX_CONVERT_THREADS 16 #define MAX_CONVERT_THREADS 32
#define AUDIO_STREAM_BUFFER_SIZE (DESMUME_SAMPLE_RATE * sizeof(u16) * 2) // 16-bit samples, 2 channels, 1 second duration
struct AVIFile; struct AVIFile;
struct AVIConversionParam struct AVIConversionParam
{ {
AVIFile *avi; AVIFile *avi;
size_t bufferIndex;
const void *src; const void *src;
size_t firstLineIndex; size_t firstLineIndex;
size_t lastLineIndex; size_t lastLineIndex;
}; };
typedef struct AVIConversionParam AVIConversionParam; typedef struct AVIConversionParam AVIConversionParam;
struct AVIFileWriteParam
{
AVIFile *avi;
size_t bufferIndex;
};
typedef struct AVIFileWriteParam AVIFileWriteParam;
struct AVIFile struct AVIFile
{ {
int valid;
int fps; int fps;
int fps_scale; int fps_scale;
@ -88,6 +97,7 @@ struct AVIFile
int sound_samples; int sound_samples;
u8 *convert_buffer; u8 *convert_buffer;
size_t videoStreamBufferSize;
int prescaleLevel; int prescaleLevel;
size_t frameWidth; size_t frameWidth;
size_t frameHeight; size_t frameHeight;
@ -96,12 +106,16 @@ struct AVIFile
long tBytes, ByteBuffer; long tBytes, ByteBuffer;
u8 audio_buffer[DESMUME_SAMPLE_RATE*2*2]; // 1 second buffer u8 audio_buffer[AUDIO_STREAM_BUFFER_SIZE * 2];
int audio_buffer_pos; int audio_buffer_pos[2];
size_t currentBufferIndex;
size_t numThreads; size_t numThreads;
Task *fileWriteThread;
Task *convertThread[MAX_CONVERT_THREADS]; Task *convertThread[MAX_CONVERT_THREADS];
AVIConversionParam convertParam[MAX_CONVERT_THREADS]; AVIConversionParam convertParam[MAX_CONVERT_THREADS];
AVIFileWriteParam fileWriteParam;
}; };
typedef AVIFile AVIFile; typedef AVIFile AVIFile;
@ -141,39 +155,36 @@ static bool truncate_existing(const char* filename)
static int avi_audiosegment_size(struct AVIFile* avi_out) static int avi_audiosegment_size(struct AVIFile* avi_out)
{ {
if(!avi_out || !avi_out->valid || !avi_out->sound_added) if (!AVI_IsRecording() || !avi_out->sound_added)
return 0; return 0;
assert(avi_out->wave_format.nAvgBytesPerSec <= sizeof(avi_out->audio_buffer)); assert(avi_out->wave_format.nAvgBytesPerSec <= sizeof(avi_out->audio_buffer));
return avi_out->wave_format.nAvgBytesPerSec; return avi_out->wave_format.nAvgBytesPerSec;
} }
static void avi_create(struct AVIFile** avi_out)
{
*avi_out = (struct AVIFile*)malloc(sizeof(struct AVIFile));
memset(*avi_out, 0, sizeof(struct AVIFile));
AVIFileInit();
}
static void avi_destroy(struct AVIFile** avi_out) static void avi_destroy(struct AVIFile** avi_out)
{ {
if(!(*avi_out)) if (!(*avi_out))
return; return;
if((*avi_out)->sound_added) const size_t bufferIndex = (*avi_out)->currentBufferIndex;
HRESULT error = S_OK;
if ((*avi_out)->sound_added)
{ {
if((*avi_out)->compressed_streams[AUDIO_STREAM]) if ((*avi_out)->compressed_streams[AUDIO_STREAM])
{ {
if ((*avi_out)->audio_buffer_pos > 0) { if ((*avi_out)->audio_buffer_pos[bufferIndex] > 0)
if(FAILED(AVIStreamWrite(avi_file->compressed_streams[AUDIO_STREAM], {
avi_file->sound_samples, (*avi_out)->audio_buffer_pos / (*avi_out)->wave_format.nBlockAlign, const int frameSampleCount = (*avi_out)->audio_buffer_pos[bufferIndex] / (*avi_out)->wave_format.nBlockAlign;
(*avi_out)->audio_buffer, (*avi_out)->audio_buffer_pos, 0, NULL, &avi_file->ByteBuffer)))
{ error = AVIStreamWrite(avi_file->compressed_streams[AUDIO_STREAM],
avi_file->valid = 0; avi_file->sound_samples, frameSampleCount,
} (*avi_out)->audio_buffer + (AUDIO_STREAM_BUFFER_SIZE * bufferIndex), (*avi_out)->audio_buffer_pos[bufferIndex], 0, NULL, &avi_file->ByteBuffer);
(*avi_out)->sound_samples += (*avi_out)->audio_buffer_pos / (*avi_out)->wave_format.nBlockAlign;
(*avi_out)->sound_samples += frameSampleCount;
(*avi_out)->tBytes += avi_file->ByteBuffer; (*avi_out)->tBytes += avi_file->ByteBuffer;
(*avi_out)->audio_buffer_pos = 0; (*avi_out)->audio_buffer_pos[bufferIndex] = 0;
} }
LONG test = AVIStreamClose((*avi_out)->compressed_streams[AUDIO_STREAM]); LONG test = AVIStreamClose((*avi_out)->compressed_streams[AUDIO_STREAM]);
@ -182,7 +193,7 @@ static void avi_destroy(struct AVIFile** avi_out)
} }
} }
if((*avi_out)->video_added) if ((*avi_out)->video_added)
{ {
if((*avi_out)->compressed_streams[VIDEO_STREAM]) if((*avi_out)->compressed_streams[VIDEO_STREAM])
{ {
@ -197,15 +208,11 @@ static void avi_destroy(struct AVIFile** avi_out)
} }
} }
if((*avi_out)->avi_file) if ((*avi_out)->avi_file)
{ {
AVIFileClose((*avi_out)->avi_file); AVIFileClose((*avi_out)->avi_file);
(*avi_out)->avi_file = NULL; (*avi_out)->avi_file = NULL;
} }
free_aligned((*avi_out)->convert_buffer);
free(*avi_out);
*avi_out = NULL;
} }
static void set_video_format(const BITMAPINFOHEADER* bitmap_format, struct AVIFile* avi_out) static void set_video_format(const BITMAPINFOHEADER* bitmap_format, struct AVIFile* avi_out)
@ -220,46 +227,68 @@ static void set_sound_format(const WAVEFORMATEX* wave_format, struct AVIFile* av
(*avi_out).sound_added = 1; (*avi_out).sound_added = 1;
} }
static int avi_open(const char* filename, const BITMAPINFOHEADER* pbmih, const WAVEFORMATEX* pwfex) static bool avi_open(const char* filename, const BITMAPINFOHEADER* pbmih, const WAVEFORMATEX* pwfex, bool isNewSegment)
{ {
int error = 1; bool isErrorInFileWrite = false;
int result = 0; bool result = false;
do do
{ {
// close existing first if (!truncate_existing(filename))
DRV_AviEnd(false);
if(!truncate_existing(filename))
break; break;
if(!pbmih) if (!pbmih)
break; break;
// create the object if (!isNewSegment)
avi_create(&avi_file); {
avi_file = (struct AVIFile*)malloc(sizeof(struct AVIFile));
memset(avi_file, 0, sizeof(struct AVIFile));
avi_file->prescaleLevel = video.prescaleHD;
avi_file->frameWidth = GPU_FRAMEBUFFER_NATIVE_WIDTH * video.prescaleHD;
avi_file->frameHeight = GPU_FRAMEBUFFER_NATIVE_HEIGHT * video.prescaleHD * 2;
avi_file->videoStreamBufferSize = avi_file->frameWidth * avi_file->frameHeight * 3;
avi_file->convert_buffer = (u8*)malloc_alignedCacheLine(avi_file->videoStreamBufferSize * 2);
// create the video stream
set_video_format(pbmih, avi_file);
memset(&avi_file->avi_video_header, 0, sizeof(AVISTREAMINFO));
avi_file->avi_video_header.fccType = streamtypeVIDEO;
avi_file->avi_video_header.dwScale = 6 * 355 * 263;
avi_file->avi_video_header.dwRate = 33513982;
avi_file->avi_video_header.dwSuggestedBufferSize = avi_file->bitmap_format.biSizeImage;
// add audio format
if (pwfex)
{
set_sound_format(pwfex, avi_file);
memset(&avi_file->avi_sound_header, 0, sizeof(AVISTREAMINFO));
avi_file->avi_sound_header.fccType = streamtypeAUDIO;
avi_file->avi_sound_header.dwQuality = (DWORD)-1;
avi_file->avi_sound_header.dwScale = avi_file->wave_format.nBlockAlign;
avi_file->avi_sound_header.dwRate = avi_file->wave_format.nAvgBytesPerSec;
avi_file->avi_sound_header.dwSampleSize = avi_file->wave_format.nBlockAlign;
avi_file->avi_sound_header.dwInitialFrames = 1;
}
avi_file->currentBufferIndex = 0;
avi_file->audio_buffer_pos[0] = 0;
avi_file->audio_buffer_pos[1] = 0;
}
AVIFileInit();
// open the file // open the file
if(FAILED(AVIFileOpen(&avi_file->avi_file, filename, OF_CREATE | OF_WRITE, NULL))) if (FAILED(AVIFileOpen(&avi_file->avi_file, filename, OF_CREATE | OF_WRITE, NULL)))
break;
if (FAILED(AVIFileCreateStream(avi_file->avi_file, &avi_file->streams[VIDEO_STREAM], &avi_file->avi_video_header)))
break; break;
// create the video stream if (use_prev_options)
set_video_format(pbmih, avi_file);
memset(&avi_file->avi_video_header, 0, sizeof(AVISTREAMINFO));
avi_file->prescaleLevel = video.prescaleHD;
avi_file->frameWidth = GPU_FRAMEBUFFER_NATIVE_WIDTH * video.prescaleHD;
avi_file->frameHeight = GPU_FRAMEBUFFER_NATIVE_HEIGHT * video.prescaleHD * 2;
avi_file->convert_buffer = (u8*)malloc_alignedCacheLine(avi_file->frameWidth * avi_file->frameHeight * 3);
avi_file->avi_video_header.fccType = streamtypeVIDEO;
avi_file->avi_video_header.dwScale = 6*355*263;
avi_file->avi_video_header.dwRate = 33513982;
avi_file->avi_video_header.dwSuggestedBufferSize = avi_file->bitmap_format.biSizeImage;
if(FAILED(AVIFileCreateStream(avi_file->avi_file, &avi_file->streams[VIDEO_STREAM], &avi_file->avi_video_header)))
break;
if(use_prev_options)
{ {
avi_file->compress_options[VIDEO_STREAM] = saved_avi_info.compress_options[VIDEO_STREAM]; avi_file->compress_options[VIDEO_STREAM] = saved_avi_info.compress_options[VIDEO_STREAM];
avi_file->compress_options_ptr[VIDEO_STREAM] = &avi_file->compress_options[0]; avi_file->compress_options_ptr[VIDEO_STREAM] = &avi_file->compress_options[0];
@ -270,35 +299,24 @@ static int avi_open(const char* filename, const BITMAPINFOHEADER* pbmih, const W
memset(&avi_file->compress_options[VIDEO_STREAM], 0, sizeof(AVICOMPRESSOPTIONS)); memset(&avi_file->compress_options[VIDEO_STREAM], 0, sizeof(AVICOMPRESSOPTIONS));
avi_file->compress_options_ptr[VIDEO_STREAM] = &avi_file->compress_options[0]; avi_file->compress_options_ptr[VIDEO_STREAM] = &avi_file->compress_options[0];
//retryAviSaveOptions: //mbg merge 7/17/06 removed //retryAviSaveOptions: //mbg merge 7/17/06 removed
error = 0; if (!AVISaveOptions(MainWindow->getHWnd(), 0, 1, &avi_file->streams[VIDEO_STREAM], &avi_file->compress_options_ptr[VIDEO_STREAM]))
if(!AVISaveOptions(MainWindow->getHWnd(), 0, 1, &avi_file->streams[VIDEO_STREAM], &avi_file->compress_options_ptr[VIDEO_STREAM]))
break; break;
error = 1; isErrorInFileWrite = true;
} }
// create compressed stream // create compressed stream
if(FAILED(AVIMakeCompressedStream(&avi_file->compressed_streams[VIDEO_STREAM], avi_file->streams[VIDEO_STREAM], &avi_file->compress_options[VIDEO_STREAM], NULL))) if (FAILED(AVIMakeCompressedStream(&avi_file->compressed_streams[VIDEO_STREAM], avi_file->streams[VIDEO_STREAM], &avi_file->compress_options[VIDEO_STREAM], NULL)))
break; break;
// set the stream format // set the stream format
if(FAILED(AVIStreamSetFormat(avi_file->compressed_streams[VIDEO_STREAM], 0, (void*)&avi_file->bitmap_format, avi_file->bitmap_format.biSize))) if (FAILED(AVIStreamSetFormat(avi_file->compressed_streams[VIDEO_STREAM], 0, (void*)&avi_file->bitmap_format, avi_file->bitmap_format.biSize)))
break; break;
// add sound (if requested) // add sound (if requested)
if(pwfex) if (pwfex)
{ {
// add audio format
set_sound_format(pwfex, avi_file);
// create the audio stream // create the audio stream
memset(&avi_file->avi_sound_header, 0, sizeof(AVISTREAMINFO)); if (FAILED(AVIFileCreateStream(avi_file->avi_file, &avi_file->streams[AUDIO_STREAM], &avi_file->avi_sound_header)))
avi_file->avi_sound_header.fccType = streamtypeAUDIO;
avi_file->avi_sound_header.dwQuality = (DWORD)-1;
avi_file->avi_sound_header.dwScale = avi_file->wave_format.nBlockAlign;
avi_file->avi_sound_header.dwRate = avi_file->wave_format.nAvgBytesPerSec;
avi_file->avi_sound_header.dwSampleSize = avi_file->wave_format.nBlockAlign;
avi_file->avi_sound_header.dwInitialFrames = 1;
if(FAILED(AVIFileCreateStream(avi_file->avi_file, &avi_file->streams[AUDIO_STREAM], &avi_file->avi_sound_header)))
break; break;
// AVISaveOptions doesn't seem to work for audio streams // AVISaveOptions doesn't seem to work for audio streams
@ -306,7 +324,7 @@ static int avi_open(const char* filename, const BITMAPINFOHEADER* pbmih, const W
avi_file->compressed_streams[AUDIO_STREAM] = avi_file->streams[AUDIO_STREAM]; avi_file->compressed_streams[AUDIO_STREAM] = avi_file->streams[AUDIO_STREAM];
// set the stream format // set the stream format
if(FAILED(AVIStreamSetFormat(avi_file->compressed_streams[AUDIO_STREAM], 0, (void*)&avi_file->wave_format, sizeof(WAVEFORMATEX)))) if (FAILED(AVIStreamSetFormat(avi_file->compressed_streams[AUDIO_STREAM], 0, (void*)&avi_file->wave_format, sizeof(WAVEFORMATEX))))
break; break;
} }
@ -315,30 +333,47 @@ static int avi_open(const char* filename, const BITMAPINFOHEADER* pbmih, const W
avi_file->sound_samples = 0; avi_file->sound_samples = 0;
avi_file->tBytes = 0; avi_file->tBytes = 0;
avi_file->ByteBuffer = 0; avi_file->ByteBuffer = 0;
avi_file->audio_buffer_pos = 0;
// success // success
error = 0; result = true;
result = 1;
avi_file->valid = 1;
} while(0); } while(0);
if(!result) if (!result)
{ {
avi_destroy(&avi_file); avi_destroy(&avi_file);
if(error)
free_aligned(avi_file->convert_buffer);
free(avi_file);
avi_file = NULL;
if (isErrorInFileWrite)
EMU_PrintError("Error writing AVI file"); EMU_PrintError("Error writing AVI file");
} }
return result; return result;
} }
static bool AviNextSegment()
{
char avi_fname[MAX_PATH];
strcpy(avi_fname, saved_avi_fname);
char avi_fname_temp[MAX_PATH];
sprintf(avi_fname_temp, "%s_part%d%s", avi_fname, avi_segnum + 2, saved_avi_ext);
saved_avi_info = *avi_file;
use_prev_options = 1;
avi_segnum++;
bool ret = DRV_AviBegin(avi_fname_temp, true);
use_prev_options = 0;
strcpy(saved_avi_fname, avi_fname);
return ret;
}
//converts 16bpp to 24bpp and flips //converts 16bpp to 24bpp and flips
static void do_video_conversion555(AVIFile* avi, const u16* srcHead, const size_t firstLineIndex, const size_t lastLineIndex) static void do_video_conversion555(AVIFile* avi, const size_t bufferIndex, const u16* srcHead, const size_t firstLineIndex, const size_t lastLineIndex)
{ {
const u16* src = srcHead + (avi->frameWidth * firstLineIndex); const u16* src = srcHead + (avi->frameWidth * firstLineIndex);
u8* dst = avi->convert_buffer + (avi->frameWidth * (avi->frameHeight - (firstLineIndex + 1)) * 3); u8* dst = avi->convert_buffer + (avi->videoStreamBufferSize * bufferIndex) + (avi->frameWidth * (avi->frameHeight - (firstLineIndex + 1)) * 3);
for (size_t y = firstLineIndex; y <= lastLineIndex; y++) for (size_t y = firstLineIndex; y <= lastLineIndex; y++)
{ {
@ -349,10 +384,10 @@ static void do_video_conversion555(AVIFile* avi, const u16* srcHead, const size_
} }
//converts 32bpp to 24bpp and flips //converts 32bpp to 24bpp and flips
static void do_video_conversion(AVIFile* avi, const u32* srcHead, const size_t firstLineIndex, const size_t lastLineIndex) static void do_video_conversion(AVIFile* avi, const size_t bufferIndex, const u32* srcHead, const size_t firstLineIndex, const size_t lastLineIndex)
{ {
const u32* src = srcHead + (avi->frameWidth * firstLineIndex); const u32* src = srcHead + (avi->frameWidth * firstLineIndex);
u8* dst = avi->convert_buffer + (avi->frameWidth * (avi->frameHeight - (firstLineIndex + 1)) * 3); u8* dst = avi->convert_buffer + (avi->videoStreamBufferSize * bufferIndex) + (avi->frameWidth * (avi->frameHeight - (firstLineIndex + 1)) * 3);
for (size_t y = firstLineIndex; y <= lastLineIndex; y++) for (size_t y = firstLineIndex; y <= lastLineIndex; y++)
{ {
@ -362,10 +397,71 @@ static void do_video_conversion(AVIFile* avi, const u32* srcHead, const size_t f
} }
} }
void DRV_AviFileWriteExecute(AVIFile *theFile, const size_t bufferIndex)
{
HRESULT error = S_OK;
// Write the video stream to file.
if (avi_file->video_added)
{
error = AVIStreamWrite(avi_file->compressed_streams[VIDEO_STREAM],
avi_file->video_frames, 1,
avi_file->convert_buffer + (avi_file->videoStreamBufferSize * bufferIndex), avi_file->bitmap_format.biSizeImage,
AVIIF_KEYFRAME, NULL, &avi_file->ByteBuffer);
if (FAILED(error))
{
DRV_AviEnd(false);
return;
}
avi_file->video_frames++;
avi_file->tBytes += avi_file->ByteBuffer;
}
// Write the audio stream to file.
if (avi_file->sound_added)
{
const int frameSampleCount = avi_file->audio_buffer_pos[bufferIndex] / avi_file->wave_format.nBlockAlign;
error = AVIStreamWrite(avi_file->compressed_streams[AUDIO_STREAM],
avi_file->sound_samples, frameSampleCount,
avi_file->audio_buffer + (AUDIO_STREAM_BUFFER_SIZE * bufferIndex), avi_file->audio_buffer_pos[bufferIndex],
0, NULL, &avi_file->ByteBuffer);
if (FAILED(error))
{
DRV_AviEnd(false);
return;
}
avi_file->sound_samples += frameSampleCount;
avi_file->tBytes += avi_file->ByteBuffer;
avi_file->audio_buffer_pos[bufferIndex] = 0;
}
// segment / split AVI when it's almost 2 GB (2000MB, to be precise)
//if(!(avi_file->video_frames % 60) && avi_file->tBytes > 2097152000) AviNextSegment();
//NOPE: why does it have to break at 1 second?
//we need to support dumping HD stuff here; that means 100s of MBs per second
//let's roll this back a bit to 1800MB to give us a nice huge 256MB wiggle room, and get rid of the 1 second check
if (avi_file->tBytes > (1800 * 1024 * 1024)) AviNextSegment();
}
void DRV_AviFileWriteFinish()
{
if (!AVI_IsRecording())
{
return;
}
avi_file->fileWriteThread->finish();
}
static void* RunConvertBuffer555XTo888(void *arg) static void* RunConvertBuffer555XTo888(void *arg)
{ {
AVIConversionParam *convertParam = (AVIConversionParam *)arg; AVIConversionParam *convertParam = (AVIConversionParam *)arg;
do_video_conversion555(convertParam->avi, (u16 *)convertParam->src, convertParam->firstLineIndex, convertParam->lastLineIndex); do_video_conversion555(convertParam->avi, convertParam->bufferIndex, (u16 *)convertParam->src, convertParam->firstLineIndex, convertParam->lastLineIndex);
return NULL; return NULL;
} }
@ -373,24 +469,17 @@ static void* RunConvertBuffer555XTo888(void *arg)
static void* RunConvertBuffer888XTo888(void *arg) static void* RunConvertBuffer888XTo888(void *arg)
{ {
AVIConversionParam *convertParam = (AVIConversionParam *)arg; AVIConversionParam *convertParam = (AVIConversionParam *)arg;
do_video_conversion(convertParam->avi, (u32 *)convertParam->src, convertParam->firstLineIndex, convertParam->lastLineIndex); do_video_conversion(convertParam->avi, convertParam->bufferIndex, (u32 *)convertParam->src, convertParam->firstLineIndex, convertParam->lastLineIndex);
return NULL; return NULL;
} }
static bool AviNextSegment() static void* RunAviFileWrite(void *arg)
{ {
char avi_fname[MAX_PATH]; AVIFileWriteParam *fileWriteParam = (AVIFileWriteParam *)arg;
strcpy(avi_fname,saved_avi_fname); DRV_AviFileWriteExecute(fileWriteParam->avi, fileWriteParam->bufferIndex);
char avi_fname_temp[MAX_PATH];
sprintf(avi_fname_temp, "%s_part%d%s", avi_fname, avi_segnum+2, saved_avi_ext); return NULL;
saved_avi_info=*avi_file;
use_prev_options=1;
avi_segnum++;
bool ret = DRV_AviBegin(avi_fname_temp,true);
use_prev_options=0;
strcpy(saved_avi_fname,avi_fname);
return ret;
} }
@ -398,7 +487,7 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
{ {
DRV_AviEnd(newsegment); DRV_AviEnd(newsegment);
if(!newsegment) if (!newsegment)
avi_segnum = 0; avi_segnum = 0;
BITMAPINFOHEADER bi; BITMAPINFOHEADER bi;
@ -412,7 +501,7 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
WAVEFORMATEX wf; WAVEFORMATEX wf;
wf.cbSize = sizeof(WAVEFORMATEX); wf.cbSize = sizeof(WAVEFORMATEX);
wf.nAvgBytesPerSec = DESMUME_SAMPLE_RATE * 4; wf.nAvgBytesPerSec = DESMUME_SAMPLE_RATE * sizeof(u16) * 2;
wf.nBlockAlign = 4; wf.nBlockAlign = 4;
wf.nChannels = 2; wf.nChannels = 2;
wf.nSamplesPerSec = DESMUME_SAMPLE_RATE; wf.nSamplesPerSec = DESMUME_SAMPLE_RATE;
@ -436,13 +525,13 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
// pwf = 0; // pwf = 0;
if(!avi_open(fname, &bi, pwf)) if (!avi_open(fname, &bi, pwf, newsegment))
{ {
saved_avi_fname[0]='\0'; saved_avi_fname[0]='\0';
return 0; return 0;
} }
if(!avi_segnum) if (avi_segnum == 0)
{ {
avi_file->numThreads = CommonSettings.num_cores; avi_file->numThreads = CommonSettings.num_cores;
@ -476,12 +565,19 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
} }
avi_file->convertParam[i].avi = avi_file; avi_file->convertParam[i].avi = avi_file;
avi_file->convertParam[i].bufferIndex = avi_file->currentBufferIndex;
avi_file->convertParam[i].src = NULL; avi_file->convertParam[i].src = NULL;
avi_file->convertThread[i] = new Task(); avi_file->convertThread[i] = new Task();
avi_file->convertThread[i]->start(false); avi_file->convertThread[i]->start(false);
} }
avi_file->fileWriteParam.avi = avi_file;
avi_file->fileWriteParam.bufferIndex = avi_file->currentBufferIndex;
avi_file->fileWriteThread = new Task();
avi_file->fileWriteThread->start(false);
// Don't display at file splits // Don't display at file splits
EMU_PrintMessage("AVI recording started."); EMU_PrintMessage("AVI recording started.");
driver->AddLine("AVI recording started."); driver->AddLine("AVI recording started.");
@ -490,7 +586,7 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
strncpy(saved_cur_avi_fnameandext,fname,MAX_PATH); strncpy(saved_cur_avi_fnameandext,fname,MAX_PATH);
strncpy(saved_avi_fname,fname,MAX_PATH); strncpy(saved_avi_fname,fname,MAX_PATH);
char* dot = strrchr(saved_avi_fname, '.'); char* dot = strrchr(saved_avi_fname, '.');
if(dot && dot > strrchr(saved_avi_fname, '/') && dot > strrchr(saved_avi_fname, '\\')) if (dot && dot > strrchr(saved_avi_fname, '/') && dot > strrchr(saved_avi_fname, '\\'))
{ {
strcpy(saved_avi_ext,dot); strcpy(saved_avi_ext,dot);
dot[0]='\0'; dot[0]='\0';
@ -499,29 +595,39 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
return 1; return 1;
} }
void DRV_AviFrameStart()
{
if (!AVI_IsRecording())
{
return;
}
avi_file->currentBufferIndex = ((avi_file->currentBufferIndex + 1) % 2);
}
void DRV_AviVideoUpdate() void DRV_AviVideoUpdate()
{ {
if (!avi_file || !avi_file->valid)
return;
const NDSDisplayInfo& dispInfo = GPU->GetDisplayInfo();
const void* buffer = dispInfo.masterCustomBuffer;
//dont do anything if prescale has changed, it's just going to be garbage //dont do anything if prescale has changed, it's just going to be garbage
if (video.prescaleHD != avi_file->prescaleLevel) if (!AVI_IsRecording() || !avi_file->video_added || (video.prescaleHD != avi_file->prescaleLevel))
{
return; return;
}
const NDSDisplayInfo &dispInfo = GPU->GetDisplayInfo();
const void *buffer = dispInfo.masterCustomBuffer;
if (gpu_bpp == 15) if (gpu_bpp == 15)
{ {
if (avi_file->numThreads == 0) if (avi_file->numThreads == 0)
{ {
do_video_conversion555(avi_file, (u16 *)buffer, 0, avi_file->frameHeight - 1); do_video_conversion555(avi_file, avi_file->currentBufferIndex, (u16 *)buffer, 0, avi_file->frameHeight - 1);
} }
else else
{ {
for (size_t i = 0; i < avi_file->numThreads; i++) for (size_t i = 0; i < avi_file->numThreads; i++)
{ {
avi_file->convertParam[i].src = buffer; avi_file->convertParam[i].src = buffer;
avi_file->convertParam[i].bufferIndex = avi_file->currentBufferIndex;
avi_file->convertThread[i]->execute(&RunConvertBuffer555XTo888, &avi_file->convertParam[i]); avi_file->convertThread[i]->execute(&RunConvertBuffer555XTo888, &avi_file->convertParam[i]);
} }
} }
@ -530,93 +636,80 @@ void DRV_AviVideoUpdate()
{ {
if (avi_file->numThreads == 0) if (avi_file->numThreads == 0)
{ {
do_video_conversion(avi_file, (u32 *)buffer, 0, avi_file->frameHeight - 1); do_video_conversion(avi_file, avi_file->currentBufferIndex, (u32 *)buffer, 0, avi_file->frameHeight - 1);
} }
else else
{ {
for (size_t i = 0; i < avi_file->numThreads; i++) for (size_t i = 0; i < avi_file->numThreads; i++)
{ {
avi_file->convertParam[i].src = buffer; avi_file->convertParam[i].src = buffer;
avi_file->convertParam[i].bufferIndex = avi_file->currentBufferIndex;
avi_file->convertThread[i]->execute(&RunConvertBuffer888XTo888, &avi_file->convertParam[i]); avi_file->convertThread[i]->execute(&RunConvertBuffer888XTo888, &avi_file->convertParam[i]);
} }
} }
} }
}
for (size_t i = 0; i < avi_file->numThreads; i++) void DRV_AviSoundUpdate(void *soundData, int soundLen)
{ {
avi_file->convertThread[i]->finish(); if (!AVI_IsRecording() || !avi_file->sound_added)
} return;
if(FAILED(AVIStreamWrite(avi_file->compressed_streams[VIDEO_STREAM], const int soundSize = soundLen * avi_file->wave_format.nBlockAlign;
avi_file->video_frames, 1, avi_file->convert_buffer, memcpy(avi_file->audio_buffer + (AUDIO_STREAM_BUFFER_SIZE * avi_file->currentBufferIndex) + avi_file->audio_buffer_pos[avi_file->currentBufferIndex], soundData, soundSize);
avi_file->bitmap_format.biSizeImage, AVIIF_KEYFRAME, avi_file->audio_buffer_pos[avi_file->currentBufferIndex] += soundSize;
NULL, &avi_file->ByteBuffer))) }
void DRV_AviFileWrite()
{
if (!AVI_IsRecording())
{ {
DRV_AviEnd(false);
return; return;
} }
avi_file->video_frames++; if (avi_file->video_added)
avi_file->tBytes += avi_file->ByteBuffer; {
for (size_t i = 0; i < avi_file->numThreads; i++)
{
avi_file->convertThread[i]->finish();
}
}
// segment / split AVI when it's almost 2 GB (2000MB, to be precise) DRV_AviFileWriteFinish();
//if(!(avi_file->video_frames % 60) && avi_file->tBytes > 2097152000) AviNextSegment();
//NOPE: why does it have to break at 1 second? avi_file->fileWriteParam.bufferIndex = avi_file->currentBufferIndex;
//we need to support dumping HD stuff here; that means 100s of MBs per second avi_file->fileWriteThread->execute(&RunAviFileWrite, &avi_file->fileWriteParam);
//let's roll this back a bit to 1800MB to give us a nice huge 256MB wiggle room, and get rid of the 1 second check
if(avi_file->tBytes > (1800*1024*1024)) AviNextSegment();
} }
bool AVI_IsRecording() bool AVI_IsRecording()
{ {
return avi_file && avi_file->valid; return (avi_file != NULL);
}
void DRV_AviSoundUpdate(void* soundData, int soundLen)
{
if(!AVI_IsRecording() || !avi_file->sound_added)
return;
const int audioSegmentSize = avi_audiosegment_size(avi_file);
const int samplesPerSegment = audioSegmentSize / avi_file->wave_format.nBlockAlign;
const int soundSize = soundLen * avi_file->wave_format.nBlockAlign;
int nBytes = soundSize;
while (avi_file->audio_buffer_pos + nBytes > audioSegmentSize) {
const int bytesToTransfer = audioSegmentSize - avi_file->audio_buffer_pos;
memcpy(&avi_file->audio_buffer[avi_file->audio_buffer_pos], &((u8*)soundData)[soundSize - nBytes], bytesToTransfer);
nBytes -= bytesToTransfer;
if(FAILED(AVIStreamWrite(avi_file->compressed_streams[AUDIO_STREAM],
avi_file->sound_samples, samplesPerSegment,
avi_file->audio_buffer, audioSegmentSize, 0, NULL, &avi_file->ByteBuffer)))
{
DRV_AviEnd(false);
return;
}
avi_file->sound_samples += samplesPerSegment;
avi_file->tBytes += avi_file->ByteBuffer;
avi_file->audio_buffer_pos = 0;
}
memcpy(&avi_file->audio_buffer[avi_file->audio_buffer_pos], &((u8*)soundData)[soundSize - nBytes], nBytes);
avi_file->audio_buffer_pos += nBytes;
} }
void DRV_AviEnd(bool newsegment) void DRV_AviEnd(bool newsegment)
{ {
if(!avi_file) if (!AVI_IsRecording())
return; return;
if(!newsegment) avi_destroy(&avi_file);
if (!newsegment)
{ {
EMU_PrintMessage("AVI recording ended."); EMU_PrintMessage("AVI recording ended.");
driver->AddLine("AVI recording ended."); driver->AddLine("AVI recording ended.");
delete avi_file->fileWriteThread;
avi_file->fileWriteThread = NULL;
for (size_t i = 0; i < avi_file->numThreads; i++) for (size_t i = 0; i < avi_file->numThreads; i++)
{ {
delete avi_file->convertThread[i]; delete avi_file->convertThread[i];
avi_file->convertThread[i] = NULL; avi_file->convertThread[i] = NULL;
avi_file->numThreads = 0; avi_file->numThreads = 0;
} }
}
avi_destroy(&avi_file); free_aligned(avi_file->convert_buffer);
free(avi_file);
avi_file = NULL;
}
} }

8
desmume/src/frontend/windows/aviout.h Normal file → Executable file
View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2009-2015 DeSmuME team Copyright (C) 2009-2018 DeSmuME team
This file is free software: you can redistribute it and/or modify This file is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -22,8 +22,12 @@
bool DRV_AviBegin(const char* fname, bool newsegment = false); bool DRV_AviBegin(const char* fname, bool newsegment = false);
void DRV_AviEnd(bool newsegment = false); void DRV_AviEnd(bool newsegment = false);
void DRV_AviSoundUpdate(void* soundData, int soundLen);
bool AVI_IsRecording(); bool AVI_IsRecording();
void DRV_AviFrameStart();
void DRV_AviVideoUpdate(); void DRV_AviVideoUpdate();
void DRV_AviSoundUpdate(void* soundData, int soundLen);
void DRV_AviFileWrite();
void DRV_AviFileWriteFinish();
#endif #endif

View File

@ -1,6 +1,6 @@
/* /*
Copyright (C) 2006 Theo Berkau Copyright (C) 2006 Theo Berkau
Copyright (C) 2006-2017 DeSmuME team Copyright (C) 2006-2018 DeSmuME team
This file is free software: you can redistribute it and/or modify This file is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -538,6 +538,21 @@ unsigned short windowSize = 0;
Color::Fuchsia Color::Fuchsia
};*/ };*/
class GPUEventHandlerWindows : public GPUEventHandlerDefault
{
public:
virtual void DidFrameEnd(bool isFrameSkipped, const NDSDisplayInfo &latestDisplayInfo)
{
if (isFrameSkipped)
{
return;
}
DRV_AviVideoUpdate();
}
};
GPUEventHandlerWindows *WinGPUEvent = NULL;
LRESULT CALLBACK HUDFontSettingsDlgProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp); LRESULT CALLBACK HUDFontSettingsDlgProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp);
LRESULT CALLBACK GFX3DSettingsDlgProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp); LRESULT CALLBACK GFX3DSettingsDlgProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp);
@ -2209,6 +2224,8 @@ static void StepRunLoop_Core()
NDS_endProcessingInput(); NDS_endProcessingInput();
FCEUMOV_HandleRecording(); FCEUMOV_HandleRecording();
DRV_AviFrameStart();
inFrameBoundary = false; inFrameBoundary = false;
{ {
Lock lock; Lock lock;
@ -2217,7 +2234,8 @@ static void StepRunLoop_Core()
win_sound_samplecounter = DESMUME_SAMPLE_RATE/60; win_sound_samplecounter = DESMUME_SAMPLE_RATE/60;
} }
inFrameBoundary = true; inFrameBoundary = true;
DRV_AviVideoUpdate();
DRV_AviFileWrite();
CallRegisteredLuaFunctions(LUACALL_AFTEREMULATION); CallRegisteredLuaFunctions(LUACALL_AFTEREMULATION);
ServiceDisplayThreadInvocations(); ServiceDisplayThreadInvocations();
@ -2939,6 +2957,7 @@ int _main()
driver = new WinDriver(); driver = new WinDriver();
CurrentWifiHandler = new WinWifiHandler(); CurrentWifiHandler = new WinWifiHandler();
WinGPUEvent = new GPUEventHandlerWindows;
InitializeCriticalSection(&win_execute_sync); InitializeCriticalSection(&win_execute_sync);
InitializeCriticalSection(&win_backbuffer_sync); InitializeCriticalSection(&win_backbuffer_sync);
@ -3272,6 +3291,8 @@ int _main()
NDS_Init(); NDS_Init();
GPU->SetEventHandler(WinGPUEvent);
CommonSettings.GFX3D_Renderer_TextureScalingFactor = (cmdline.texture_upscale != -1) ? cmdline.texture_upscale : GetPrivateProfileInt("3D", "TextureScalingFactor ", 1, IniName); CommonSettings.GFX3D_Renderer_TextureScalingFactor = (cmdline.texture_upscale != -1) ? cmdline.texture_upscale : GetPrivateProfileInt("3D", "TextureScalingFactor ", 1, IniName);
int newPrescaleHD = (cmdline.gpu_resolution_multiplier != -1) ? cmdline.gpu_resolution_multiplier : GetPrivateProfileInt("3D", "PrescaleHD", 1, IniName); int newPrescaleHD = (cmdline.gpu_resolution_multiplier != -1) ? cmdline.gpu_resolution_multiplier : GetPrivateProfileInt("3D", "PrescaleHD", 1, IniName);
@ -3494,6 +3515,7 @@ int _main()
KillDisplay(); KillDisplay();
DRV_AviFileWriteFinish();
DRV_AviEnd(); DRV_AviEnd();
WAV_End(); WAV_End();
@ -3785,6 +3807,7 @@ void SetRotate(HWND hwnd, int rot, bool user)
void AviEnd() void AviEnd()
{ {
NDS_Pause(); NDS_Pause();
DRV_AviFileWriteFinish();
DRV_AviEnd(); DRV_AviEnd();
NDS_UnPause(); NDS_UnPause();
} }