diff --git a/desmume/src/frontend/windows/aviout.cpp b/desmume/src/frontend/windows/aviout.cpp old mode 100644 new mode 100755 index 1af4aeb71..98420481e --- a/desmume/src/frontend/windows/aviout.cpp +++ b/desmume/src/frontend/windows/aviout.cpp @@ -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 @@ -48,24 +48,33 @@ static void EMU_PrintMessage(const char* msg) { //extern WAVEFORMATEX wf; //extern int soundo; -#define VIDEO_STREAM 0 -#define AUDIO_STREAM 1 -#define MAX_CONVERT_THREADS 16 +#define VIDEO_STREAM 0 +#define AUDIO_STREAM 1 +#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))) - { - avi_file->valid = 0; - } - (*avi_out)->sound_samples += (*avi_out)->audio_buffer_pos / (*avi_out)->wave_format.nBlockAlign; + if ((*avi_out)->audio_buffer_pos[bufferIndex] > 0) + { + 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)); + + 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 - 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; - // 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.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) + 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]); } } } +} - for (size_t i = 0; i < avi_file->numThreads; i++) - { - avi_file->convertThread[i]->finish(); - } +void DRV_AviSoundUpdate(void *soundData, int soundLen) +{ + if (!AVI_IsRecording() || !avi_file->sound_added) + return; - 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))) + 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()) { - DRV_AviEnd(false); return; } - avi_file->video_frames++; - avi_file->tBytes += avi_file->ByteBuffer; + if (avi_file->video_added) + { + 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) - //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(); + DRV_AviFileWriteFinish(); + + 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; + } } diff --git a/desmume/src/frontend/windows/aviout.h b/desmume/src/frontend/windows/aviout.h old mode 100644 new mode 100755 index 9848f6685..57e7e8557 --- a/desmume/src/frontend/windows/aviout.h +++ b/desmume/src/frontend/windows/aviout.h @@ -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 diff --git a/desmume/src/frontend/windows/main.cpp b/desmume/src/frontend/windows/main.cpp index 5f3939f19..ef26aabb2 100755 --- a/desmume/src/frontend/windows/main.cpp +++ b/desmume/src/frontend/windows/main.cpp @@ -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(); }