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();