diff --git a/desmume/src/frontend/windows/aviout.cpp b/desmume/src/frontend/windows/aviout.cpp index 98420481e..64aa8088c 100755 --- a/desmume/src/frontend/windows/aviout.cpp +++ b/desmume/src/frontend/windows/aviout.cpp @@ -1,715 +1,765 @@ -/* - 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 - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - This file is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with the this software. If not, see . -*/ - -#include "aviout.h" - -#include -#include -#include -#include - -#include "debug.h" -#include "console.h" -#include "gfx3d.h" -#include "SPU.h" - -#include "video.h" -#include "windriver.h" -#include "main.h" -#include "driver.h" -#include "NDSSystem.h" -#include "utils/task.h" - -extern VideoInfo video; - -static void EMU_PrintError(const char* msg) { - LOG(msg); -} - -static void EMU_PrintMessage(const char* msg) { - LOG(msg); -} - -//extern PALETTEENTRY *color_palette; -//extern WAVEFORMATEX wf; -//extern int soundo; - -#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 fps; - int fps_scale; - - int video_added; - BITMAPINFOHEADER bitmap_format; - - int sound_added; - WAVEFORMATEX wave_format; - - AVISTREAMINFO avi_video_header; - AVISTREAMINFO avi_sound_header; - PAVIFILE avi_file; - PAVISTREAM streams[2]; - PAVISTREAM compressed_streams[2]; - - AVICOMPRESSOPTIONS compress_options[2]; - AVICOMPRESSOPTIONS* compress_options_ptr[2]; - - int video_frames; - int sound_samples; - - u8 *convert_buffer; - size_t videoStreamBufferSize; - int prescaleLevel; - size_t frameWidth; - size_t frameHeight; - int start_scanline; - int end_scanline; - - long tBytes, ByteBuffer; - - 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; - -AVIFile *avi_file = NULL; - -struct VideoSystemInfo -{ - int start_scanline; - int end_scanline; - int fps; -}; - - -static char saved_cur_avi_fnameandext[MAX_PATH]; -static char saved_avi_fname[MAX_PATH]; -static char saved_avi_ext[MAX_PATH]; -static int avi_segnum=0; -//static FILE* avi_check_file=0; -static struct AVIFile saved_avi_info; -static int use_prev_options=0; -static bool use_sound=false; - - - -static bool truncate_existing(const char* filename) -{ - // this is only here because AVIFileOpen doesn't seem to do it for us - FILE* fd = fopen(filename, "wb"); - if(fd) - { - fclose(fd); - return 1; - } - - return 0; -} - -static int avi_audiosegment_size(struct AVIFile* avi_out) -{ - 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_destroy(struct AVIFile** avi_out) -{ - if (!(*avi_out)) - return; - - 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)->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[bufferIndex] = 0; - } - - LONG test = AVIStreamClose((*avi_out)->compressed_streams[AUDIO_STREAM]); - (*avi_out)->compressed_streams[AUDIO_STREAM] = NULL; - (*avi_out)->streams[AUDIO_STREAM] = NULL; // compressed_streams[AUDIO_STREAM] is just a copy of streams[AUDIO_STREAM] - } - } - - if ((*avi_out)->video_added) - { - if((*avi_out)->compressed_streams[VIDEO_STREAM]) - { - AVIStreamClose((*avi_out)->compressed_streams[VIDEO_STREAM]); - (*avi_out)->compressed_streams[VIDEO_STREAM] = NULL; - } - - if((*avi_out)->streams[VIDEO_STREAM]) - { - AVIStreamClose((*avi_out)->streams[VIDEO_STREAM]); - (*avi_out)->streams[VIDEO_STREAM] = NULL; - } - } - - if ((*avi_out)->avi_file) - { - AVIFileClose((*avi_out)->avi_file); - (*avi_out)->avi_file = NULL; - } -} - -static void set_video_format(const BITMAPINFOHEADER* bitmap_format, struct AVIFile* avi_out) -{ - memcpy(&((*avi_out).bitmap_format), bitmap_format, sizeof(BITMAPINFOHEADER)); - (*avi_out).video_added = 1; -} - -static void set_sound_format(const WAVEFORMATEX* wave_format, struct AVIFile* avi_out) -{ - memcpy(&((*avi_out).wave_format), wave_format, sizeof(WAVEFORMATEX)); - (*avi_out).sound_added = 1; -} - -static bool avi_open(const char* filename, const BITMAPINFOHEADER* pbmih, const WAVEFORMATEX* pwfex, bool isNewSegment) -{ - bool isErrorInFileWrite = false; - bool result = false; - - do - { - if (!truncate_existing(filename)) - break; - - if (!pbmih) - break; - - 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))) - break; - - 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]; - } - else - { - // get compression options - 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 - if (!AVISaveOptions(MainWindow->getHWnd(), 0, 1, &avi_file->streams[VIDEO_STREAM], &avi_file->compress_options_ptr[VIDEO_STREAM])) - break; - 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))) - 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))) - break; - - // add sound (if requested) - if (pwfex) - { - // create the audio stream - 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 - // so here we just copy the pointer for the compressed stream - 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)))) - break; - } - - // initialize counters - avi_file->video_frames = 0; - avi_file->sound_samples = 0; - avi_file->tBytes = 0; - avi_file->ByteBuffer = 0; - - // success - result = true; - - } while(0); - - if (!result) - { - avi_destroy(&avi_file); - - 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 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->videoStreamBufferSize * bufferIndex) + (avi->frameWidth * (avi->frameHeight - (firstLineIndex + 1)) * 3); +/* + Copyright (C) 2006-2018 DeSmuME team - for (size_t y = firstLineIndex; y <= lastLineIndex; y++) + 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 + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the this software. If not, see . +*/ + +#include "aviout.h" + +#include +#include + +#include + +#include "debug.h" +#include "common.h" + +#include "windriver.h" +#include "driver.h" +#include "NDSSystem.h" + + +NDSCaptureObject *captureObject = NULL; +bool AVIFileStream::__needAviLibraryInit = true; + +static void EMU_PrintError(const char* msg) +{ + LOG(msg); +} + +static void EMU_PrintMessage(const char* msg) +{ + LOG(msg); +} + +static void* RunConvertVideoSlice555XTo888(void *arg) +{ + VideoConvertParam *convertParam = (VideoConvertParam *)arg; + convertParam->captureObj->ConvertVideoSlice555Xto888(*convertParam); + + return NULL; +} + +static void* RunConvertVideoSlice888XTo888(void *arg) +{ + VideoConvertParam *convertParam = (VideoConvertParam *)arg; + convertParam->captureObj->ConvertVideoSlice888Xto888(*convertParam); + + return NULL; +} + +static void* RunAviFileWrite(void *arg) +{ + AVIFileWriteParam *fileWriteParam = (AVIFileWriteParam *)arg; + fileWriteParam->fs->WriteOneFrame(fileWriteParam->srcVideo, fileWriteParam->videoBufferSize, fileWriteParam->srcAudio, fileWriteParam->audioBufferSize); + + return NULL; +} + +AVIFileStream::AVIFileStream() +{ + _file = NULL; + _stream[0] = NULL; + _stream[1] = NULL; + _compressedStream[0] = NULL; + _compressedStream[1] = NULL; + + memset(&_compressionOptions[0], 0, sizeof(AVICOMPRESSOPTIONS)); + memset(&_compressionOptions[1], 0, sizeof(AVICOMPRESSOPTIONS)); + memset(&_bmpFormat, 0, sizeof(BITMAPINFOHEADER)); + memset(&_wavFormat, 0, sizeof(WAVEFORMATEX)); + + memset(_baseFileName, '\0', MAX_PATH * sizeof(char)); + memset(_baseFileNameExt, '\0', MAX_PATH * sizeof(char)); + _segmentNumber = 0; + + memset(&_streamInfo[VIDEO_STREAM], 0, sizeof(AVISTREAMINFO)); + _streamInfo[VIDEO_STREAM].fccType = streamtypeVIDEO; + _streamInfo[VIDEO_STREAM].dwScale = 6 * 355 * 263; + _streamInfo[VIDEO_STREAM].dwRate = 33513982; + + memset(&_streamInfo[AUDIO_STREAM], 0, sizeof(AVISTREAMINFO)); + _streamInfo[AUDIO_STREAM].fccType = streamtypeAUDIO; + _streamInfo[AUDIO_STREAM].dwQuality = (DWORD)-1; + _streamInfo[AUDIO_STREAM].dwInitialFrames = 1; + + _expectedFrameSize = 0; + _writtenVideoFrameCount = 0; + _writtenAudioSampleCount = 0; + _writtenBytes = 0; +} + +AVIFileStream::~AVIFileStream() +{ + this->Close(); +} + +HRESULT AVIFileStream::Open(const char *fileName, BITMAPINFOHEADER *bmpFormat, WAVEFORMATEX *wavFormat) +{ + HRESULT error = S_OK; + + // Initialize the AVI library if it hasn't already been initialized. + if (AVIFileStream::__needAviLibraryInit) { - ColorspaceConvertBuffer555XTo888(src, dst, avi->frameWidth); - src += avi->frameWidth; - dst -= avi->frameWidth * 3; + AVIFileInit(); + AVIFileStream::__needAviLibraryInit = false; } -} - -//converts 32bpp to 24bpp and flips -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->videoStreamBufferSize * bufferIndex) + (avi->frameWidth * (avi->frameHeight - (firstLineIndex + 1)) * 3); - - for (size_t y = firstLineIndex; y <= lastLineIndex; y++) - { - ColorspaceConvertBuffer888XTo888(src, dst, avi->frameWidth); - src += avi->frameWidth; - dst -= avi->frameWidth * 3; - } -} - -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, convertParam->bufferIndex, (u16 *)convertParam->src, convertParam->firstLineIndex, convertParam->lastLineIndex); - - return NULL; -} - -static void* RunConvertBuffer888XTo888(void *arg) -{ - AVIConversionParam *convertParam = (AVIConversionParam *)arg; - do_video_conversion(convertParam->avi, convertParam->bufferIndex, (u32 *)convertParam->src, convertParam->firstLineIndex, convertParam->lastLineIndex); - - return NULL; -} - -static void* RunAviFileWrite(void *arg) -{ - AVIFileWriteParam *fileWriteParam = (AVIFileWriteParam *)arg; - DRV_AviFileWriteExecute(fileWriteParam->avi, fileWriteParam->bufferIndex); - - return NULL; -} - - -bool DRV_AviBegin(const char* fname, bool newsegment) -{ - DRV_AviEnd(newsegment); - - if (!newsegment) - avi_segnum = 0; - - BITMAPINFOHEADER bi; - memset(&bi, 0, sizeof(bi)); - bi.biSize = 0x28; - bi.biPlanes = 1; - bi.biBitCount = 24; - bi.biWidth = 256 * video.prescaleHD; - bi.biHeight = 384 * video.prescaleHD; - bi.biSizeImage = 3 * bi.biWidth * bi.biHeight; - - WAVEFORMATEX wf; - wf.cbSize = sizeof(WAVEFORMATEX); - wf.nAvgBytesPerSec = DESMUME_SAMPLE_RATE * sizeof(u16) * 2; - wf.nBlockAlign = 4; - wf.nChannels = 2; - wf.nSamplesPerSec = DESMUME_SAMPLE_RATE; - wf.wBitsPerSample = 16; - wf.wFormatTag = WAVE_FORMAT_PCM; - - - saved_avi_ext[0]='\0'; - - //mbg 8/10/08 - decide whether there will be sound in this movie - //if this is a new movie.. - /*if(!avi_file) { - if(FSettings.SndRate) - use_sound = true; - else use_sound = false; - }*/ - - //mbg 8/10/08 - if there is no sound in this movie, then dont open the audio stream - WAVEFORMATEX* pwf = &wf; - //if(!use_sound) - // pwf = 0; - - - if (!avi_open(fname, &bi, pwf, newsegment)) - { - saved_avi_fname[0]='\0'; - return 0; - } - - if (avi_segnum == 0) - { - avi_file->numThreads = CommonSettings.num_cores; - - if (avi_file->numThreads > MAX_CONVERT_THREADS) - { - avi_file->numThreads = MAX_CONVERT_THREADS; - } - else if (avi_file->numThreads < 2) - { - avi_file->numThreads = 0; - } - - size_t linesPerThread = (avi_file->numThreads > 0) ? avi_file->frameHeight / avi_file->numThreads : avi_file->frameHeight; - - for (size_t i = 0; i < avi_file->numThreads; i++) - { - if (i == 0) - { - avi_file->convertParam[i].firstLineIndex = 0; - avi_file->convertParam[i].lastLineIndex = linesPerThread - 1; - } - else if (i == (avi_file->numThreads - 1)) - { - avi_file->convertParam[i].firstLineIndex = avi_file->convertParam[i - 1].lastLineIndex + 1; - avi_file->convertParam[i].lastLineIndex = avi_file->frameHeight - 1; - } - else - { - avi_file->convertParam[i].firstLineIndex = avi_file->convertParam[i - 1].lastLineIndex + 1; - avi_file->convertParam[i].lastLineIndex = avi_file->convertParam[i].firstLineIndex + linesPerThread - 1; - } - - 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."); - } - - 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, '\\')) - { - strcpy(saved_avi_ext,dot); - dot[0]='\0'; - } - - return 1; -} - -void DRV_AviFrameStart() -{ - if (!AVI_IsRecording()) - { - return; - } - - avi_file->currentBufferIndex = ((avi_file->currentBufferIndex + 1) % 2); -} - -void DRV_AviVideoUpdate() -{ - //dont do anything if prescale has changed, it's just going to be garbage - 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, 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]); - } - } - } - else - { - if (avi_file->numThreads == 0) - { - 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(); - } - } - - DRV_AviFileWriteFinish(); - - avi_file->fileWriteParam.bufferIndex = avi_file->currentBufferIndex; - avi_file->fileWriteThread->execute(&RunAviFileWrite, &avi_file->fileWriteParam); -} - -bool AVI_IsRecording() -{ - return (avi_file != NULL); -} - -void DRV_AviEnd(bool newsegment) -{ - if (!AVI_IsRecording()) - return; - - 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; - } - - free_aligned(avi_file->convert_buffer); - free(avi_file); - avi_file = NULL; - } -} + + char workingFileName[MAX_PATH]; + memset(workingFileName, '\0', sizeof(workingFileName)); + + if (this->_segmentNumber == 0) + { + // Generate the base file name. This will be used for the first segment. + const char *dot = strrchr(fileName, '.'); + if (dot && dot > strrchr(fileName, '/') && dot > strrchr(fileName, '\\')) + { + const size_t baseNameSize = dot - fileName; + + strcpy(this->_baseFileNameExt, dot); + strncpy(this->_baseFileName, fileName, baseNameSize); + this->_baseFileName[baseNameSize] = '\0'; // Even though the string should already be filled with \0, manually terminate again for safety's sake. + + sprintf(workingFileName, "%s%s", this->_baseFileName, this->_baseFileNameExt); + } + else + { + error = E_INVALIDARG; + return error; + } + + // Set up the stream formats that will be used for all AVI segments. + if (bmpFormat != NULL) + { + this->_bmpFormat = *bmpFormat; + this->_streamInfo[VIDEO_STREAM].dwSuggestedBufferSize = this->_bmpFormat.biSizeImage; + } + + if (wavFormat != NULL) + { + this->_wavFormat = *wavFormat; + this->_streamInfo[AUDIO_STREAM].dwScale = this->_wavFormat.nBlockAlign; + this->_streamInfo[AUDIO_STREAM].dwRate = this->_wavFormat.nAvgBytesPerSec; + this->_streamInfo[AUDIO_STREAM].dwSampleSize = this->_wavFormat.nBlockAlign; + } + + // The expected frame size is the video frame size plus the sum of the audio samples. + // Since the number of audio samples may not be exactly the same for each video frame, + // we double the expected size of the audio buffer for safety. + this->_expectedFrameSize = this->_bmpFormat.biSizeImage + ((this->_wavFormat.nAvgBytesPerSec / 60) * 2); + } + else + { + // Tack on a suffix to the base file name in order to generate a new file name. + sprintf(workingFileName, "%s_part%d%s", this->_baseFileName, (int)(this->_segmentNumber + 1), this->_baseFileNameExt); + } + + // this is only here because AVIFileOpen doesn't seem to do it for us + FILE *fd = fopen(workingFileName, "wb"); + if (fd != NULL) + { + fclose(fd); + } + else + { + error = E_ACCESSDENIED; + return error; + } + + // open the file + error = AVIFileOpen(&this->_file, workingFileName, OF_CREATE | OF_WRITE, NULL); + if (FAILED(error)) + { + return error; + } + + if (this->_streamInfo[VIDEO_STREAM].dwSuggestedBufferSize > 0) + { + error = AVIFileCreateStream(this->_file, &this->_stream[VIDEO_STREAM], &this->_streamInfo[VIDEO_STREAM]); + if (FAILED(error)) + { + return error; + } + + if (this->_segmentNumber == 0) + { + AVICOMPRESSOPTIONS *videoCompressionOptionsPtr = &this->_compressionOptions[VIDEO_STREAM]; + + // get compression options + INT_PTR optionsID = AVISaveOptions(MainWindow->getHWnd(), 0, 1, &this->_stream[VIDEO_STREAM], &videoCompressionOptionsPtr); + if (optionsID == 0) // User clicked "Cancel" + { + error = E_ABORT; + return error; + } + } + + // create compressed stream + error = AVIMakeCompressedStream(&this->_compressedStream[VIDEO_STREAM], this->_stream[VIDEO_STREAM], &this->_compressionOptions[VIDEO_STREAM], NULL); + if (FAILED(error)) + { + return error; + } + + // set the stream format + error = AVIStreamSetFormat(this->_compressedStream[VIDEO_STREAM], 0, &this->_bmpFormat, this->_bmpFormat.biSize); + if (FAILED(error)) + { + return error; + } + } + + if (this->_streamInfo[AUDIO_STREAM].dwSampleSize > 0) + { + // create the audio stream + error = AVIFileCreateStream(this->_file, &this->_stream[AUDIO_STREAM], &this->_streamInfo[AUDIO_STREAM]); + if (FAILED(error)) + { + return error; + } + + // AVISaveOptions doesn't seem to work for audio streams + // so here we just copy the pointer for the compressed stream + this->_compressedStream[AUDIO_STREAM] = this->_stream[AUDIO_STREAM]; + + // set the stream format + error = AVIStreamSetFormat(this->_compressedStream[AUDIO_STREAM], 0, &this->_wavFormat, sizeof(WAVEFORMATEX)); + if (FAILED(error)) + { + return error; + } + } + + return error; +} + +void AVIFileStream::Close() +{ + if (this->_file == NULL) + { + return; + } + + // _compressedStream[AUDIO_STREAM] is just a copy of _stream[AUDIO_STREAM] + if (this->_compressedStream[AUDIO_STREAM]) + { + AVIStreamClose(this->_compressedStream[AUDIO_STREAM]); + this->_compressedStream[AUDIO_STREAM] = NULL; + this->_stream[AUDIO_STREAM] = NULL; + } + + if (this->_compressedStream[VIDEO_STREAM]) + { + AVIStreamClose(this->_compressedStream[VIDEO_STREAM]); + this->_compressedStream[VIDEO_STREAM] = NULL; + } + + if (this->_stream[VIDEO_STREAM]) + { + AVIStreamClose(this->_stream[VIDEO_STREAM]); + this->_stream[VIDEO_STREAM] = NULL; + } + + AVIFileClose(this->_file); + this->_file = NULL; + + this->_writtenVideoFrameCount = 0; + this->_writtenAudioSampleCount = 0; + this->_writtenBytes = 0; +} + +bool AVIFileStream::IsValid() +{ + return (this->_file != NULL); +} + +HRESULT AVIFileStream::FlushVideo(u8 *srcBuffer, const LONG bufferSize) +{ + HRESULT error = S_OK; + + if (this->_compressedStream[VIDEO_STREAM] == NULL) + { + return error; + } + + LONG writtenBytesOut = 0; + + error = AVIStreamWrite(this->_compressedStream[VIDEO_STREAM], + this->_writtenVideoFrameCount, 1, + srcBuffer, bufferSize, + AVIIF_KEYFRAME, + NULL, &writtenBytesOut); + + if (FAILED(error)) + { + return error; + } + + this->_writtenVideoFrameCount++; + this->_writtenBytes += writtenBytesOut; + + return error; +} + +HRESULT AVIFileStream::FlushAudio(u8 *srcBuffer, const LONG bufferSize) +{ + HRESULT error = S_OK; + + if ( (this->_compressedStream[AUDIO_STREAM] == NULL) || (bufferSize == 0) ) + { + return error; + } + + const LONG inSampleCount = bufferSize / this->_streamInfo[AUDIO_STREAM].dwSampleSize; + LONG writtenBytesOut = 0; + + error = AVIStreamWrite(this->_compressedStream[AUDIO_STREAM], + this->_writtenAudioSampleCount, inSampleCount, + srcBuffer, bufferSize, + 0, + NULL, &writtenBytesOut); + + if (FAILED(error)) + { + return error; + } + + this->_writtenAudioSampleCount += inSampleCount; + this->_writtenBytes += writtenBytesOut; + + return error; +} + +HRESULT AVIFileStream::WriteOneFrame(u8 *srcVideo, const LONG videoBufferSize, u8 *srcAudio, const LONG audioBufferSize) +{ + HRESULT error = S_OK; + + error = this->FlushVideo(srcVideo, videoBufferSize); + if (FAILED(error)) + { + this->Close(); + return error; + } + + error = this->FlushAudio(srcAudio, audioBufferSize); + if (FAILED(error)) + { + this->Close(); + return error; + } + + // Create a new AVI segment if the next written frame would exceed the maximum AVI file size. + const size_t futureFrameSize = this->_writtenBytes + this->_expectedFrameSize; + if (futureFrameSize >= MAX_AVI_FILE_SIZE) + { + this->Close(); + this->_segmentNumber++; + + error = this->Open(NULL, NULL, NULL); + if (FAILED(error)) + { + EMU_PrintError("Error creating new AVI segment."); + this->Close(); + return error; + } + } + + return error; +} + +NDSCaptureObject::NDSCaptureObject() +{ + // create the video stream + memset(&_bmpFormat, 0, sizeof(BITMAPINFOHEADER)); + _bmpFormat.biSize = 0x28; + _bmpFormat.biPlanes = 1; + _bmpFormat.biBitCount = 24; + + _pendingVideoBuffer = NULL; + + memset(_pendingAudioBuffer, 0, AUDIO_STREAM_BUFFER_SIZE * PENDING_BUFFER_COUNT * sizeof(u8)); + memset(&_wavFormat, 0, sizeof(WAVEFORMATEX)); + + _currentBufferIndex = 0; + _pendingAudioWriteSize[0] = 0; + _pendingAudioWriteSize[1] = 0; + + // Create the colorspace conversion threads. + _numThreads = CommonSettings.num_cores; + + if (_numThreads > MAX_CONVERT_THREADS) + { + _numThreads = MAX_CONVERT_THREADS; + } + else if (_numThreads < 2) + { + _numThreads = 0; + } + + for (size_t i = 0; i < _numThreads; i++) + { + memset(&_convertParam[i], 0, sizeof(VideoConvertParam)); + _convertParam[i].captureObj = this; + + _convertThread[i] = new Task(); + _convertThread[i]->start(false); + } + + // Generate the AVI file streams. + _fs = new AVIFileStream; + + _fileWriteParam.fs = _fs; + _fileWriteParam.srcVideo = NULL; + _fileWriteParam.videoBufferSize = 0; + _fileWriteParam.srcAudio = this->_pendingAudioBuffer; + _fileWriteParam.audioBufferSize = 0; + + _fileWriteThread = new Task(); + _fileWriteThread->start(false); +} + +NDSCaptureObject::NDSCaptureObject(size_t frameWidth, size_t frameHeight, const WAVEFORMATEX *wfex) +{ + this->NDSCaptureObject::NDSCaptureObject(); + + _bmpFormat.biWidth = frameWidth; + _bmpFormat.biHeight = frameHeight * 2; + _bmpFormat.biSizeImage = _bmpFormat.biWidth * _bmpFormat.biHeight * 3; + + _pendingVideoBuffer = (u8*)malloc_alignedCacheLine(_bmpFormat.biSizeImage * PENDING_BUFFER_COUNT); + + if (wfex != NULL) + { + _wavFormat = *wfex; + } + + const size_t linesPerThread = (_numThreads > 0) ? _bmpFormat.biHeight / _numThreads : _bmpFormat.biHeight; + + if (_numThreads == 0) + { + for (size_t i = 0; i < MAX_CONVERT_THREADS; i++) + { + _convertParam[i].firstLineIndex = 0; + _convertParam[i].lastLineIndex = linesPerThread - 1; + _convertParam[i].srcOffset = 0; + _convertParam[i].dstOffset = (_bmpFormat.biWidth * (_bmpFormat.biHeight - 1) * 3); + _convertParam[i].frameWidth = _bmpFormat.biWidth; + } + } + else + { + for (size_t i = 0; i < _numThreads; i++) + { + if (i == 0) + { + _convertParam[i].firstLineIndex = 0; + _convertParam[i].lastLineIndex = linesPerThread - 1; + } + else if (i == (_numThreads - 1)) + { + _convertParam[i].firstLineIndex = _convertParam[i - 1].lastLineIndex + 1; + _convertParam[i].lastLineIndex = _bmpFormat.biHeight - 1; + } + else + { + _convertParam[i].firstLineIndex = _convertParam[i - 1].lastLineIndex + 1; + _convertParam[i].lastLineIndex = _convertParam[i].firstLineIndex + linesPerThread - 1; + } + + _convertParam[i].srcOffset = (_bmpFormat.biWidth * _convertParam[i].firstLineIndex); + _convertParam[i].dstOffset = (_bmpFormat.biWidth * (_bmpFormat.biHeight - (_convertParam[i].firstLineIndex + 1)) * 3); + _convertParam[i].frameWidth = _bmpFormat.biWidth; + } + } + + _fileWriteParam.srcVideo = _pendingVideoBuffer; + _fileWriteParam.videoBufferSize = _bmpFormat.biSizeImage; +} + +NDSCaptureObject::~NDSCaptureObject() +{ + this->_fileWriteThread->finish(); + delete this->_fileWriteThread; + + for (size_t i = 0; i < this->_numThreads; i++) + { + this->_convertThread[i]->finish(); + delete this->_convertThread[i]; + } + + delete this->_fs; + + free_aligned(this->_pendingVideoBuffer); +} + +HRESULT NDSCaptureObject::OpenFileStream(const char *fileName) +{ + return this->_fs->Open(fileName, &this->_bmpFormat, &this->_wavFormat); +} + +void NDSCaptureObject::CloseFileStream() +{ + this->_fs->Close(); +} + +bool NDSCaptureObject::IsFileStreamValid() +{ + return this->_fs->IsValid(); +} + +void NDSCaptureObject::StartFrame() +{ + for (size_t i = 0; i < this->_numThreads; i++) + { + this->_convertThread[i]->finish(); + } + + this->_currentBufferIndex = ((this->_currentBufferIndex + 1) % PENDING_BUFFER_COUNT); + this->_pendingAudioWriteSize[this->_currentBufferIndex] = 0; +} + +void NDSCaptureObject::StreamWriteStart() +{ + const size_t bufferIndex = this->_currentBufferIndex; + + if (this->_bmpFormat.biSizeImage > 0) + { + for (size_t i = 0; i < this->_numThreads; i++) + { + this->_convertThread[i]->finish(); + } + } + + this->_fileWriteThread->finish(); + + this->_fileWriteParam.srcVideo = this->_pendingVideoBuffer + (this->_bmpFormat.biSizeImage * bufferIndex); + this->_fileWriteParam.srcAudio = this->_pendingAudioBuffer + (AUDIO_STREAM_BUFFER_SIZE * bufferIndex); + this->_fileWriteParam.audioBufferSize = this->_pendingAudioWriteSize[bufferIndex]; + + this->_fileWriteThread->execute(&RunAviFileWrite, &this->_fileWriteParam); +} + +void NDSCaptureObject::StreamWriteFinish() +{ + this->_fileWriteThread->finish(); +} + +//converts 16bpp to 24bpp and flips +void NDSCaptureObject::ConvertVideoSlice555Xto888(const VideoConvertParam ¶m) +{ + const u16 *__restrict src = (const u16 *__restrict)param.src; + u8 *__restrict dst = param.dst; + + for (size_t y = param.firstLineIndex; y <= param.lastLineIndex; y++) + { + ColorspaceConvertBuffer555XTo888(src, dst, param.frameWidth); + src += param.frameWidth; + dst -= param.frameWidth * 3; + } +} + +//converts 32bpp to 24bpp and flips +void NDSCaptureObject::ConvertVideoSlice888Xto888(const VideoConvertParam ¶m) +{ + const u32 *__restrict src = (const u32 *__restrict)param.src; + u8 *__restrict dst = param.dst; + + for (size_t y = param.firstLineIndex; y <= param.lastLineIndex; y++) + { + ColorspaceConvertBuffer888XTo888(src, dst, param.frameWidth); + src += param.frameWidth; + dst -= param.frameWidth * 3; + } +} + +void NDSCaptureObject::ReadVideoFrame(const void *srcVideoFrame, const size_t inFrameWidth, const size_t inFrameHeight, const NDSColorFormat colorFormat) +{ + //dont do anything if prescale has changed, it's just going to be garbage + if ((this->_bmpFormat.biSizeImage == 0) || + (this->_bmpFormat.biWidth != inFrameWidth) || + (this->_bmpFormat.biHeight != (inFrameHeight * 2))) + { + return; + } + + const size_t bufferIndex = this->_currentBufferIndex; + u8 *convertBufferHead = this->_pendingVideoBuffer + (this->_bmpFormat.biSizeImage * bufferIndex); + + if (colorFormat == NDSColorFormat_BGR555_Rev) + { + if (this->_numThreads == 0) + { + this->_convertParam[0].src = (u16 *)srcVideoFrame; + this->_convertParam[0].dst = convertBufferHead + this->_convertParam[0].dstOffset; + this->ConvertVideoSlice555Xto888(this->_convertParam[0]); + } + else + { + for (size_t i = 0; i < this->_numThreads; i++) + { + this->_convertParam[i].src = (u16 *)srcVideoFrame + this->_convertParam[i].srcOffset; + this->_convertParam[i].dst = convertBufferHead + this->_convertParam[i].dstOffset; + this->_convertThread[i]->execute(&RunConvertVideoSlice555XTo888, &this->_convertParam[i]); + } + } + } + else + { + if (this->_numThreads == 0) + { + this->_convertParam[0].src = (u32 *)srcVideoFrame; + this->_convertParam[0].dst = convertBufferHead + this->_convertParam[0].dstOffset; + this->ConvertVideoSlice888Xto888(this->_convertParam[0]); + } + else + { + for (size_t i = 0; i < this->_numThreads; i++) + { + this->_convertParam[i].src = (u32 *)srcVideoFrame + this->_convertParam[i].srcOffset; + this->_convertParam[i].dst = convertBufferHead + this->_convertParam[i].dstOffset; + this->_convertThread[i]->execute(&RunConvertVideoSlice888XTo888, &this->_convertParam[i]); + } + } + } +} + +void NDSCaptureObject::ReadAudioFrames(const void *srcAudioBuffer, const size_t inSampleCount) +{ + if (this->_wavFormat.nBlockAlign == 0) + { + return; + } + + const size_t bufferIndex = this->_currentBufferIndex; + const size_t soundSize = inSampleCount * this->_wavFormat.nBlockAlign; + + memcpy(this->_pendingAudioBuffer + (AUDIO_STREAM_BUFFER_SIZE * bufferIndex) + this->_pendingAudioWriteSize[bufferIndex], srcAudioBuffer, soundSize); + this->_pendingAudioWriteSize[bufferIndex] += soundSize; +} + +bool DRV_AviBegin(const char *fileName) +{ + HRESULT error = S_OK; + bool result = true; + + WAVEFORMATEX wf; + wf.cbSize = sizeof(WAVEFORMATEX); + wf.nAvgBytesPerSec = DESMUME_SAMPLE_RATE * sizeof(u16) * 2; + wf.nBlockAlign = 4; + wf.nChannels = 2; + wf.nSamplesPerSec = DESMUME_SAMPLE_RATE; + wf.wBitsPerSample = 16; + wf.wFormatTag = WAVE_FORMAT_PCM; + + const NDSDisplayInfo &dispInfo = GPU->GetDisplayInfo(); + NDSCaptureObject *newCaptureObject = new NDSCaptureObject(dispInfo.customWidth, dispInfo.customHeight, &wf); + + error = newCaptureObject->OpenFileStream(fileName); + if (FAILED(error)) + { + if (error != E_ABORT) + { + EMU_PrintError("Error starting AVI file."); + driver->AddLine("Error starting AVI file."); + } + + delete newCaptureObject; + + result = false; + return result; + } + + captureObject = newCaptureObject; + return result; +} + +void DRV_AviFrameStart() +{ + if (!AVI_IsRecording()) + { + return; + } + + captureObject->StartFrame(); +} + +void DRV_AviVideoUpdate(const NDSDisplayInfo &displayInfo) +{ + if (!AVI_IsRecording()) + { + return; + } + + captureObject->ReadVideoFrame(displayInfo.masterCustomBuffer, displayInfo.customWidth, displayInfo.customHeight, displayInfo.colorFormat); +} + +void DRV_AviSoundUpdate(void *soundData, const int soundLen) +{ + if (!AVI_IsRecording()) + { + return; + } + + captureObject->ReadAudioFrames(soundData, soundLen); +} + +void DRV_AviFileWriteStart() +{ + if (!AVI_IsRecording()) + { + return; + } + + // Clean up stale capture objects at this time if the underlying file stream is invalid. + // An invalid file stream may occur if there was an error in the file stream creation or + // write. + if (!captureObject->IsFileStreamValid()) + { + delete captureObject; + captureObject = NULL; + + EMU_PrintMessage("AVI recording ended."); + driver->AddLine("AVI recording ended."); + + return; + } + + captureObject->StreamWriteStart(); +} + +void DRV_AviFileWriteFinish() +{ + if (!AVI_IsRecording()) + { + return; + } + + captureObject->StreamWriteFinish(); +} + +bool AVI_IsRecording() +{ + return (captureObject != NULL); +} + +void DRV_AviEnd() +{ + if (!AVI_IsRecording()) + { + return; + } + + delete captureObject; + captureObject = NULL; +} diff --git a/desmume/src/frontend/windows/aviout.h b/desmume/src/frontend/windows/aviout.h index 57e7e8557..b2595f58b 100755 --- a/desmume/src/frontend/windows/aviout.h +++ b/desmume/src/frontend/windows/aviout.h @@ -18,16 +18,134 @@ #ifndef _AVIOUT_H_ #define _AVIOUT_H_ -#include "types.h" +#include +#include -bool DRV_AviBegin(const char* fname, bool newsegment = false); -void DRV_AviEnd(bool newsegment = false); +#include "GPU.h" +#include "SPU.h" +#include "utils/task.h" + +#define VIDEO_STREAM 0 +#define AUDIO_STREAM 1 +#define MAX_CONVERT_THREADS 32 + +#define AUDIO_STREAM_BUFFER_SIZE ((DESMUME_SAMPLE_RATE * sizeof(u16) * 2) / 30) // 16-bit samples, 2 channels, 2 frames (need only 1 frame's worth, but have 2 frame's worth for safety) +#define MAX_AVI_FILE_SIZE (2ULL * 1024ULL * 1024ULL * 1024ULL) // Max file size should be 2 GB due to the Video for Windows AVI limit. +#define PENDING_BUFFER_COUNT 2 // Number of pending buffers to maintain for file writes. Each pending buffer will store 1 frame's worth of the audio/video streams. + +class NDSCaptureObject; +class AVIFileStream; + +struct VideoConvertParam +{ + NDSCaptureObject *captureObj; + + const void *src; + u8 *dst; + + size_t srcOffset; + size_t dstOffset; + + size_t firstLineIndex; + size_t lastLineIndex; + size_t frameWidth; +}; +typedef struct VideoConvertParam VideoConvertParam; + +struct AVIFileWriteParam +{ + AVIFileStream *fs; + u8 *srcVideo; + u8 *srcAudio; + size_t videoBufferSize; + size_t audioBufferSize; +}; +typedef struct AVIFileWriteParam AVIFileWriteParam; + +class AVIFileStream +{ +private: + static bool __needAviLibraryInit; + +protected: + PAVIFILE _file; + PAVISTREAM _stream[2]; + PAVISTREAM _compressedStream[2]; + AVISTREAMINFO _streamInfo[2]; + + AVICOMPRESSOPTIONS _compressionOptions[2]; + BITMAPINFOHEADER _bmpFormat; + WAVEFORMATEX _wavFormat; + + char _baseFileName[MAX_PATH]; + char _baseFileNameExt[MAX_PATH]; + size_t _segmentNumber; + + size_t _expectedFrameSize; + size_t _writtenBytes; + LONG _writtenVideoFrameCount; + LONG _writtenAudioSampleCount; + +public: + AVIFileStream(); + ~AVIFileStream(); + + HRESULT Open(const char *fileName, BITMAPINFOHEADER *bmpFormat, WAVEFORMATEX *wavFormat); + void Close(); + bool IsValid(); + + HRESULT FlushVideo(u8 *srcBuffer, const LONG bufferSize); + HRESULT FlushAudio(u8 *srcBuffer, const LONG bufferSize); + HRESULT WriteOneFrame(u8 *srcVideo, const LONG videoBufferSize, u8 *srcAudio, const LONG audioBufferSize); +}; + +class NDSCaptureObject +{ +protected: + AVIFileStream *_fs; + + BITMAPINFOHEADER _bmpFormat; + WAVEFORMATEX _wavFormat; + + u8 *_pendingVideoBuffer; + u8 _pendingAudioBuffer[AUDIO_STREAM_BUFFER_SIZE * PENDING_BUFFER_COUNT]; + size_t _pendingAudioWriteSize[2]; + size_t _currentBufferIndex; + + size_t _numThreads; + Task *_fileWriteThread; + Task *_convertThread[MAX_CONVERT_THREADS]; + VideoConvertParam _convertParam[MAX_CONVERT_THREADS]; + AVIFileWriteParam _fileWriteParam; + +public: + NDSCaptureObject(); + NDSCaptureObject(size_t videoWidth, size_t videoHeight, const WAVEFORMATEX *wfex); + ~NDSCaptureObject(); + + HRESULT OpenFileStream(const char *fileName); + void CloseFileStream(); + bool IsFileStreamValid(); + + void StartFrame(); + void StreamWriteStart(); + void StreamWriteFinish(); + + void ConvertVideoSlice555Xto888(const VideoConvertParam ¶m); + void ConvertVideoSlice888Xto888(const VideoConvertParam ¶m); + + void ReadVideoFrame(const void *srcVideoFrame, const size_t inFrameWidth, const size_t inFrameHeight, const NDSColorFormat colorFormat); + void ReadAudioFrames(const void *srcAudioBuffer, const size_t inSampleCount); +}; + +bool DRV_AviBegin(const char *fileName); +void DRV_AviEnd(); bool AVI_IsRecording(); void DRV_AviFrameStart(); -void DRV_AviVideoUpdate(); -void DRV_AviSoundUpdate(void* soundData, int soundLen); -void DRV_AviFileWrite(); +void DRV_AviVideoUpdate(const NDSDisplayInfo &displayInfo); +void DRV_AviSoundUpdate(void *soundData, const int soundLen); +void DRV_AviFileWriteStart(); void DRV_AviFileWriteFinish(); #endif diff --git a/desmume/src/frontend/windows/main.cpp b/desmume/src/frontend/windows/main.cpp index ef26aabb2..4b13012cc 100755 --- a/desmume/src/frontend/windows/main.cpp +++ b/desmume/src/frontend/windows/main.cpp @@ -548,7 +548,7 @@ public: return; } - DRV_AviVideoUpdate(); + DRV_AviVideoUpdate(latestDisplayInfo); } }; @@ -2235,7 +2235,7 @@ static void StepRunLoop_Core() } inFrameBoundary = true; - DRV_AviFileWrite(); + DRV_AviFileWriteStart(); CallRegisteredLuaFunctions(LUACALL_AFTEREMULATION); ServiceDisplayThreadInvocations(); @@ -3515,7 +3515,6 @@ int _main() KillDisplay(); - DRV_AviFileWriteFinish(); DRV_AviEnd(); WAV_End(); @@ -3807,8 +3806,11 @@ void SetRotate(HWND hwnd, int rot, bool user) void AviEnd() { NDS_Pause(); - DRV_AviFileWriteFinish(); + DRV_AviEnd(); + LOG("AVI recording ended."); + driver->AddLine("AVI recording ended."); + NDS_UnPause(); } @@ -3868,9 +3870,21 @@ void AviRecordTo() ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN | OFN_PATHMUSTEXIST; - if(GetSaveFileName(&ofn)) + if (GetSaveFileName(&ofn)) { - DRV_AviBegin(folder); + if (AVI_IsRecording()) + { + DRV_AviEnd(); + LOG("AVI recording ended."); + driver->AddLine("AVI recording ended."); + } + + bool result = DRV_AviBegin(folder); + if (result) + { + LOG("AVI recording started."); + driver->AddLine("AVI recording started."); + } } NDS_UnPause();