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

405
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
it under the terms of the GNU General Public License as published by
@ -50,22 +50,31 @@ static void EMU_PrintMessage(const char* msg) {
#define VIDEO_STREAM 0
#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 AVIConversionParam
{
AVIFile *avi;
size_t bufferIndex;
const void *src;
size_t firstLineIndex;
size_t lastLineIndex;
};
typedef struct AVIConversionParam AVIConversionParam;
struct AVIFileWriteParam
{
AVIFile *avi;
size_t bufferIndex;
};
typedef struct AVIFileWriteParam AVIFileWriteParam;
struct AVIFile
{
int valid;
int fps;
int fps_scale;
@ -88,6 +97,7 @@ struct AVIFile
int sound_samples;
u8 *convert_buffer;
size_t videoStreamBufferSize;
int prescaleLevel;
size_t frameWidth;
size_t frameHeight;
@ -96,12 +106,16 @@ struct AVIFile
long tBytes, ByteBuffer;
u8 audio_buffer[DESMUME_SAMPLE_RATE*2*2]; // 1 second buffer
int audio_buffer_pos;
u8 audio_buffer[AUDIO_STREAM_BUFFER_SIZE * 2];
int audio_buffer_pos[2];
size_t currentBufferIndex;
size_t numThreads;
Task *fileWriteThread;
Task *convertThread[MAX_CONVERT_THREADS];
AVIConversionParam convertParam[MAX_CONVERT_THREADS];
AVIFileWriteParam fileWriteParam;
};
typedef AVIFile AVIFile;
@ -141,39 +155,36 @@ static bool truncate_existing(const char* filename)
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;
assert(avi_out->wave_format.nAvgBytesPerSec <= sizeof(avi_out->audio_buffer));
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)
{
if(!(*avi_out))
if (!(*avi_out))
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(FAILED(AVIStreamWrite(avi_file->compressed_streams[AUDIO_STREAM],
avi_file->sound_samples, (*avi_out)->audio_buffer_pos / (*avi_out)->wave_format.nBlockAlign,
(*avi_out)->audio_buffer, (*avi_out)->audio_buffer_pos, 0, NULL, &avi_file->ByteBuffer)))
if ((*avi_out)->audio_buffer_pos[bufferIndex] > 0)
{
avi_file->valid = 0;
}
(*avi_out)->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;
error = AVIStreamWrite(avi_file->compressed_streams[AUDIO_STREAM],
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 += frameSampleCount;
(*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]);
@ -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])
{
@ -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);
(*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)
@ -220,46 +227,68 @@ static void set_sound_format(const WAVEFORMATEX* wave_format, struct AVIFile* av
(*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;
int result = 0;
bool isErrorInFileWrite = false;
bool result = false;
do
{
// close existing first
DRV_AviEnd(false);
if(!truncate_existing(filename))
if (!truncate_existing(filename))
break;
if(!pbmih)
if (!pbmih)
break;
// create the object
avi_create(&avi_file);
if (!isNewSegment)
{
avi_file = (struct AVIFile*)malloc(sizeof(struct AVIFile));
memset(avi_file, 0, sizeof(struct AVIFile));
// open the file
if(FAILED(AVIFileOpen(&avi_file->avi_file, filename, OF_CREATE | OF_WRITE, NULL)))
break;
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->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.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)))
// 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
if (FAILED(AVIFileOpen(&avi_file->avi_file, filename, OF_CREATE | OF_WRITE, NULL)))
break;
if(use_prev_options)
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_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));
avi_file->compress_options_ptr[VIDEO_STREAM] = &avi_file->compress_options[0];
//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;
error = 1;
isErrorInFileWrite = true;
}
// 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;
// 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;
// add sound (if requested)
if(pwfex)
if (pwfex)
{
// add audio format
set_sound_format(pwfex, avi_file);
// create the audio stream
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;
if(FAILED(AVIFileCreateStream(avi_file->avi_file, &avi_file->streams[AUDIO_STREAM], &avi_file->avi_sound_header)))
if (FAILED(AVIFileCreateStream(avi_file->avi_file, &avi_file->streams[AUDIO_STREAM], &avi_file->avi_sound_header)))
break;
// 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];
// 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;
}
@ -315,30 +333,47 @@ static int avi_open(const char* filename, const BITMAPINFOHEADER* pbmih, const W
avi_file->sound_samples = 0;
avi_file->tBytes = 0;
avi_file->ByteBuffer = 0;
avi_file->audio_buffer_pos = 0;
// success
error = 0;
result = 1;
avi_file->valid = 1;
result = true;
} while(0);
if(!result)
if (!result)
{
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");
}
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
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);
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++)
{
@ -349,10 +384,10 @@ static void do_video_conversion555(AVIFile* avi, const u16* srcHead, const size_
}
//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);
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++)
{
@ -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)
{
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;
}
@ -373,24 +469,17 @@ static void* RunConvertBuffer555XTo888(void *arg)
static void* RunConvertBuffer888XTo888(void *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;
}
static bool AviNextSegment()
static void* RunAviFileWrite(void *arg)
{
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;
AVIFileWriteParam *fileWriteParam = (AVIFileWriteParam *)arg;
DRV_AviFileWriteExecute(fileWriteParam->avi, fileWriteParam->bufferIndex);
return NULL;
}
@ -398,7 +487,7 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
{
DRV_AviEnd(newsegment);
if(!newsegment)
if (!newsegment)
avi_segnum = 0;
BITMAPINFOHEADER bi;
@ -412,7 +501,7 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
WAVEFORMATEX wf;
wf.cbSize = sizeof(WAVEFORMATEX);
wf.nAvgBytesPerSec = DESMUME_SAMPLE_RATE * 4;
wf.nAvgBytesPerSec = DESMUME_SAMPLE_RATE * sizeof(u16) * 2;
wf.nBlockAlign = 4;
wf.nChannels = 2;
wf.nSamplesPerSec = DESMUME_SAMPLE_RATE;
@ -436,13 +525,13 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
// pwf = 0;
if(!avi_open(fname, &bi, pwf))
if (!avi_open(fname, &bi, pwf, newsegment))
{
saved_avi_fname[0]='\0';
return 0;
}
if(!avi_segnum)
if (avi_segnum == 0)
{
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].bufferIndex = avi_file->currentBufferIndex;
avi_file->convertParam[i].src = NULL;
avi_file->convertThread[i] = new Task();
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
EMU_PrintMessage("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_avi_fname,fname,MAX_PATH);
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);
dot[0]='\0';
@ -499,29 +595,39 @@ bool DRV_AviBegin(const char* fname, bool newsegment)
return 1;
}
void DRV_AviFrameStart()
{
if (!AVI_IsRecording())
{
return;
}
avi_file->currentBufferIndex = ((avi_file->currentBufferIndex + 1) % 2);
}
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
if (video.prescaleHD != avi_file->prescaleLevel)
if (!AVI_IsRecording() || !avi_file->video_added || (video.prescaleHD != avi_file->prescaleLevel))
{
return;
}
const NDSDisplayInfo &dispInfo = GPU->GetDisplayInfo();
const void *buffer = dispInfo.masterCustomBuffer;
if (gpu_bpp == 15)
{
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
{
for (size_t i = 0; i < avi_file->numThreads; i++)
{
avi_file->convertParam[i].src = buffer;
avi_file->convertParam[i].bufferIndex = avi_file->currentBufferIndex;
avi_file->convertThread[i]->execute(&RunConvertBuffer555XTo888, &avi_file->convertParam[i]);
}
}
@ -530,93 +636,80 @@ void DRV_AviVideoUpdate()
{
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
{
for (size_t i = 0; i < avi_file->numThreads; i++)
{
avi_file->convertParam[i].src = buffer;
avi_file->convertParam[i].bufferIndex = avi_file->currentBufferIndex;
avi_file->convertThread[i]->execute(&RunConvertBuffer888XTo888, &avi_file->convertParam[i]);
}
}
}
}
void DRV_AviSoundUpdate(void *soundData, int soundLen)
{
if (!AVI_IsRecording() || !avi_file->sound_added)
return;
const int soundSize = soundLen * avi_file->wave_format.nBlockAlign;
memcpy(avi_file->audio_buffer + (AUDIO_STREAM_BUFFER_SIZE * avi_file->currentBufferIndex) + avi_file->audio_buffer_pos[avi_file->currentBufferIndex], soundData, soundSize);
avi_file->audio_buffer_pos[avi_file->currentBufferIndex] += soundSize;
}
void DRV_AviFileWrite()
{
if (!AVI_IsRecording())
{
return;
}
if (avi_file->video_added)
{
for (size_t i = 0; i < avi_file->numThreads; i++)
{
avi_file->convertThread[i]->finish();
}
if(FAILED(AVIStreamWrite(avi_file->compressed_streams[VIDEO_STREAM],
avi_file->video_frames, 1, avi_file->convert_buffer,
avi_file->bitmap_format.biSizeImage, AVIIF_KEYFRAME,
NULL, &avi_file->ByteBuffer)))
{
DRV_AviEnd(false);
return;
}
avi_file->video_frames++;
avi_file->tBytes += avi_file->ByteBuffer;
DRV_AviFileWriteFinish();
// 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();
avi_file->fileWriteParam.bufferIndex = avi_file->currentBufferIndex;
avi_file->fileWriteThread->execute(&RunAviFileWrite, &avi_file->fileWriteParam);
}
bool AVI_IsRecording()
{
return avi_file && avi_file->valid;
}
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;
return (avi_file != NULL);
}
void DRV_AviEnd(bool newsegment)
{
if(!avi_file)
if (!AVI_IsRecording())
return;
if(!newsegment)
avi_destroy(&avi_file);
if (!newsegment)
{
EMU_PrintMessage("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++)
{
delete avi_file->convertThread[i];
avi_file->convertThread[i] = NULL;
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
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);
void DRV_AviEnd(bool newsegment = false);
void DRV_AviSoundUpdate(void* soundData, int soundLen);
bool AVI_IsRecording();
void DRV_AviFrameStart();
void DRV_AviVideoUpdate();
void DRV_AviSoundUpdate(void* soundData, int soundLen);
void DRV_AviFileWrite();
void DRV_AviFileWriteFinish();
#endif

View File

@ -1,6 +1,6 @@
/*
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
it under the terms of the GNU General Public License as published by
@ -538,6 +538,21 @@ unsigned short windowSize = 0;
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 GFX3DSettingsDlgProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp);
@ -2209,6 +2224,8 @@ static void StepRunLoop_Core()
NDS_endProcessingInput();
FCEUMOV_HandleRecording();
DRV_AviFrameStart();
inFrameBoundary = false;
{
Lock lock;
@ -2217,7 +2234,8 @@ static void StepRunLoop_Core()
win_sound_samplecounter = DESMUME_SAMPLE_RATE/60;
}
inFrameBoundary = true;
DRV_AviVideoUpdate();
DRV_AviFileWrite();
CallRegisteredLuaFunctions(LUACALL_AFTEREMULATION);
ServiceDisplayThreadInvocations();
@ -2939,6 +2957,7 @@ int _main()
driver = new WinDriver();
CurrentWifiHandler = new WinWifiHandler();
WinGPUEvent = new GPUEventHandlerWindows;
InitializeCriticalSection(&win_execute_sync);
InitializeCriticalSection(&win_backbuffer_sync);
@ -3272,6 +3291,8 @@ int _main()
NDS_Init();
GPU->SetEventHandler(WinGPUEvent);
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);
@ -3494,6 +3515,7 @@ int _main()
KillDisplay();
DRV_AviFileWriteFinish();
DRV_AviEnd();
WAV_End();
@ -3785,6 +3807,7 @@ void SetRotate(HWND hwnd, int rot, bool user)
void AviEnd()
{
NDS_Pause();
DRV_AviFileWriteFinish();
DRV_AviEnd();
NDS_UnPause();
}