Windows Port: AVI recording now maintains many more frames in memory. Also, the framebuffer color conversion threads and file writing thread are more free running threads.
- These changes help to stabilize the performance of AVI recording, making it less sensitive to sudden changes in disk writing speed. - The maximum amount of frames maintained in memory will either be 1.5 GB worth or 180 frames (or 3 seconds) worth, whichever is less.
This commit is contained in:
parent
db1a19ad86
commit
8763a6169a
|
@ -61,8 +61,8 @@ static void* RunConvertVideoSlice888XTo888(void *arg)
|
||||||
|
|
||||||
static void* RunAviFileWrite(void *arg)
|
static void* RunAviFileWrite(void *arg)
|
||||||
{
|
{
|
||||||
AVIFileWriteParam *fileWriteParam = (AVIFileWriteParam *)arg;
|
AVIFileStream *fs = (AVIFileStream *)arg;
|
||||||
fileWriteParam->fs->WriteOneFrame(fileWriteParam->srcVideo, fileWriteParam->videoBufferSize, fileWriteParam->srcAudio, fileWriteParam->audioBufferSize);
|
fs->WriteAllFrames();
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -98,14 +98,39 @@ AVIFileStream::AVIFileStream()
|
||||||
_writtenVideoFrameCount = 0;
|
_writtenVideoFrameCount = 0;
|
||||||
_writtenAudioSampleCount = 0;
|
_writtenAudioSampleCount = 0;
|
||||||
_writtenBytes = 0;
|
_writtenBytes = 0;
|
||||||
|
|
||||||
|
_semQueue = NULL;
|
||||||
|
_mutexQueue = slock_new();
|
||||||
}
|
}
|
||||||
|
|
||||||
AVIFileStream::~AVIFileStream()
|
AVIFileStream::~AVIFileStream()
|
||||||
{
|
{
|
||||||
this->Close();
|
this->Close(FSCA_WriteRemainingInQueue);
|
||||||
|
|
||||||
|
slock_free(this->_mutexQueue);
|
||||||
|
ssem_free(this->_semQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT AVIFileStream::Open(const char *fileName, BITMAPINFOHEADER *bmpFormat, WAVEFORMATEX *wavFormat)
|
size_t AVIFileStream::GetExpectedFrameSize(const BITMAPINFOHEADER *bmpFormat, const WAVEFORMATEX *wavFormat)
|
||||||
|
{
|
||||||
|
size_t expectedFrameSize = 0; // The expected frame size is the video frame size plus the sum of the audio samples.
|
||||||
|
|
||||||
|
if (bmpFormat != NULL)
|
||||||
|
{
|
||||||
|
expectedFrameSize += bmpFormat->biSizeImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wavFormat != NULL)
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
expectedFrameSize += ((wavFormat->nAvgBytesPerSec / 60) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expectedFrameSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT AVIFileStream::Open(const char *fileName, BITMAPINFOHEADER *bmpFormat, WAVEFORMATEX *wavFormat, size_t pendingFrameCount)
|
||||||
{
|
{
|
||||||
HRESULT error = S_OK;
|
HRESULT error = S_OK;
|
||||||
|
|
||||||
|
@ -154,10 +179,9 @@ HRESULT AVIFileStream::Open(const char *fileName, BITMAPINFOHEADER *bmpFormat, W
|
||||||
this->_streamInfo[AUDIO_STREAM].dwSampleSize = this->_wavFormat.nBlockAlign;
|
this->_streamInfo[AUDIO_STREAM].dwSampleSize = this->_wavFormat.nBlockAlign;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The expected frame size is the video frame size plus the sum of the audio samples.
|
this->_expectedFrameSize = AVIFileStream::GetExpectedFrameSize(bmpFormat, wavFormat);
|
||||||
// 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.
|
_semQueue = ssem_new(pendingFrameCount);
|
||||||
this->_expectedFrameSize = this->_bmpFormat.biSizeImage + ((this->_wavFormat.nAvgBytesPerSec / 60) * 2);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -244,13 +268,58 @@ HRESULT AVIFileStream::Open(const char *fileName, BITMAPINFOHEADER *bmpFormat, W
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AVIFileStream::Close()
|
void AVIFileStream::Close(FileStreamCloseAction theAction)
|
||||||
{
|
{
|
||||||
if (this->_file == NULL)
|
if (this->_file == NULL)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (theAction)
|
||||||
|
{
|
||||||
|
case FSCA_PurgeQueue:
|
||||||
|
{
|
||||||
|
AVIWriteQueue clearQueue;
|
||||||
|
|
||||||
|
slock_lock(this->_mutexQueue);
|
||||||
|
this->_writeQueue.swap(clearQueue);
|
||||||
|
slock_unlock(this->_mutexQueue);
|
||||||
|
|
||||||
|
const size_t remainingFrameCount = (size_t)ssem_get(this->_semQueue);
|
||||||
|
for (size_t i = 0; i < remainingFrameCount; i++)
|
||||||
|
{
|
||||||
|
ssem_signal(this->_semQueue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FSCA_WriteRemainingInQueue:
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
slock_lock(this->_mutexQueue);
|
||||||
|
if (this->_writeQueue.empty())
|
||||||
|
{
|
||||||
|
slock_unlock(this->_mutexQueue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVIFileWriteParam param = this->_writeQueue.front();
|
||||||
|
slock_unlock(this->_mutexQueue);
|
||||||
|
|
||||||
|
this->WriteOneFrame(param);
|
||||||
|
|
||||||
|
slock_lock(this->_mutexQueue);
|
||||||
|
this->_writeQueue.pop();
|
||||||
|
slock_unlock(this->_mutexQueue);
|
||||||
|
|
||||||
|
ssem_signal(this->_semQueue);
|
||||||
|
|
||||||
|
} while (true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// _compressedStream[AUDIO_STREAM] is just a copy of _stream[AUDIO_STREAM]
|
// _compressedStream[AUDIO_STREAM] is just a copy of _stream[AUDIO_STREAM]
|
||||||
if (this->_compressedStream[AUDIO_STREAM])
|
if (this->_compressedStream[AUDIO_STREAM])
|
||||||
{
|
{
|
||||||
|
@ -284,6 +353,38 @@ bool AVIFileStream::IsValid()
|
||||||
return (this->_file != NULL);
|
return (this->_file != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AVIFileStream::QueueAdd(u8 *srcVideo, const size_t videoBufferSize, u8 *srcAudio, const size_t audioBufferSize)
|
||||||
|
{
|
||||||
|
AVIFileWriteParam newParam;
|
||||||
|
newParam.fs = this;
|
||||||
|
newParam.srcVideo = srcVideo;
|
||||||
|
newParam.videoBufferSize = videoBufferSize;
|
||||||
|
newParam.srcAudio = srcAudio;
|
||||||
|
newParam.audioBufferSize = audioBufferSize;
|
||||||
|
|
||||||
|
ssem_wait(this->_semQueue);
|
||||||
|
|
||||||
|
slock_lock(this->_mutexQueue);
|
||||||
|
this->_writeQueue.push(newParam);
|
||||||
|
slock_unlock(this->_mutexQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AVIFileStream::QueueWait()
|
||||||
|
{
|
||||||
|
// If the queue if full, the ssem_wait() will force a wait until the current frame is finished writing.
|
||||||
|
ssem_wait(this->_semQueue);
|
||||||
|
ssem_signal(this->_semQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AVIFileStream::GetQueueSize()
|
||||||
|
{
|
||||||
|
slock_lock(this->_mutexQueue);
|
||||||
|
const size_t queueSize = this->_writeQueue.size();
|
||||||
|
slock_unlock(this->_mutexQueue);
|
||||||
|
|
||||||
|
return queueSize;
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT AVIFileStream::FlushVideo(u8 *srcBuffer, const LONG bufferSize)
|
HRESULT AVIFileStream::FlushVideo(u8 *srcBuffer, const LONG bufferSize)
|
||||||
{
|
{
|
||||||
HRESULT error = S_OK;
|
HRESULT error = S_OK;
|
||||||
|
@ -341,21 +442,21 @@ HRESULT AVIFileStream::FlushAudio(u8 *srcBuffer, const LONG bufferSize)
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT AVIFileStream::WriteOneFrame(u8 *srcVideo, const LONG videoBufferSize, u8 *srcAudio, const LONG audioBufferSize)
|
HRESULT AVIFileStream::WriteOneFrame(const AVIFileWriteParam ¶m)
|
||||||
{
|
{
|
||||||
HRESULT error = S_OK;
|
HRESULT error = S_OK;
|
||||||
|
|
||||||
error = this->FlushVideo(srcVideo, videoBufferSize);
|
error = this->FlushVideo(param.srcVideo, param.videoBufferSize);
|
||||||
if (FAILED(error))
|
if (FAILED(error))
|
||||||
{
|
{
|
||||||
this->Close();
|
this->Close(FSCA_PurgeQueue);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = this->FlushAudio(srcAudio, audioBufferSize);
|
error = this->FlushAudio(param.srcAudio, param.audioBufferSize);
|
||||||
if (FAILED(error))
|
if (FAILED(error))
|
||||||
{
|
{
|
||||||
this->Close();
|
this->Close(FSCA_PurgeQueue);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,14 +464,14 @@ HRESULT AVIFileStream::WriteOneFrame(u8 *srcVideo, const LONG videoBufferSize, u
|
||||||
const size_t futureFrameSize = this->_writtenBytes + this->_expectedFrameSize;
|
const size_t futureFrameSize = this->_writtenBytes + this->_expectedFrameSize;
|
||||||
if (futureFrameSize >= MAX_AVI_FILE_SIZE)
|
if (futureFrameSize >= MAX_AVI_FILE_SIZE)
|
||||||
{
|
{
|
||||||
this->Close();
|
this->Close(FSCA_DoNothing);
|
||||||
this->_segmentNumber++;
|
this->_segmentNumber++;
|
||||||
|
|
||||||
error = this->Open(NULL, NULL, NULL);
|
error = this->Open(NULL, NULL, NULL, 0);
|
||||||
if (FAILED(error))
|
if (FAILED(error))
|
||||||
{
|
{
|
||||||
EMU_PrintError("Error creating new AVI segment.");
|
EMU_PrintError("Error creating new AVI segment.");
|
||||||
this->Close();
|
this->Close(FSCA_PurgeQueue);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,22 +479,55 @@ HRESULT AVIFileStream::WriteOneFrame(u8 *srcVideo, const LONG videoBufferSize, u
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HRESULT AVIFileStream::WriteAllFrames()
|
||||||
|
{
|
||||||
|
HRESULT error = S_OK;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
slock_lock(this->_mutexQueue);
|
||||||
|
if (this->_writeQueue.empty())
|
||||||
|
{
|
||||||
|
slock_unlock(this->_mutexQueue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVIFileWriteParam param = this->_writeQueue.front();
|
||||||
|
slock_unlock(this->_mutexQueue);
|
||||||
|
|
||||||
|
error = this->WriteOneFrame(param);
|
||||||
|
|
||||||
|
slock_lock(this->_mutexQueue);
|
||||||
|
this->_writeQueue.pop();
|
||||||
|
slock_unlock(this->_mutexQueue);
|
||||||
|
|
||||||
|
ssem_signal(this->_semQueue);
|
||||||
|
|
||||||
|
if (FAILED(error))
|
||||||
|
{
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
NDSCaptureObject::NDSCaptureObject()
|
NDSCaptureObject::NDSCaptureObject()
|
||||||
{
|
{
|
||||||
// create the video stream
|
// Create the format structs.
|
||||||
memset(&_bmpFormat, 0, sizeof(BITMAPINFOHEADER));
|
memset(&_bmpFormat, 0, sizeof(BITMAPINFOHEADER));
|
||||||
_bmpFormat.biSize = 0x28;
|
_bmpFormat.biSize = 0x28;
|
||||||
_bmpFormat.biPlanes = 1;
|
_bmpFormat.biPlanes = 1;
|
||||||
_bmpFormat.biBitCount = 24;
|
_bmpFormat.biBitCount = 24;
|
||||||
|
|
||||||
_pendingVideoBuffer = NULL;
|
|
||||||
|
|
||||||
memset(_pendingAudioBuffer, 0, AUDIO_STREAM_BUFFER_SIZE * PENDING_BUFFER_COUNT * sizeof(u8));
|
|
||||||
memset(&_wavFormat, 0, sizeof(WAVEFORMATEX));
|
memset(&_wavFormat, 0, sizeof(WAVEFORMATEX));
|
||||||
|
|
||||||
|
_pendingVideoBuffer = NULL;
|
||||||
|
_pendingAudioBuffer = NULL;
|
||||||
|
_pendingAudioWriteSize = NULL;
|
||||||
|
_pendingBufferCount = 0;
|
||||||
_currentBufferIndex = 0;
|
_currentBufferIndex = 0;
|
||||||
_pendingAudioWriteSize[0] = 0;
|
|
||||||
_pendingAudioWriteSize[1] = 0;
|
|
||||||
|
|
||||||
// Create the colorspace conversion threads.
|
// Create the colorspace conversion threads.
|
||||||
_numThreads = CommonSettings.num_cores;
|
_numThreads = CommonSettings.num_cores;
|
||||||
|
@ -419,12 +553,6 @@ NDSCaptureObject::NDSCaptureObject()
|
||||||
// Generate the AVI file streams.
|
// Generate the AVI file streams.
|
||||||
_fs = new AVIFileStream;
|
_fs = new AVIFileStream;
|
||||||
|
|
||||||
_fileWriteParam.fs = _fs;
|
|
||||||
_fileWriteParam.srcVideo = NULL;
|
|
||||||
_fileWriteParam.videoBufferSize = 0;
|
|
||||||
_fileWriteParam.srcAudio = this->_pendingAudioBuffer;
|
|
||||||
_fileWriteParam.audioBufferSize = 0;
|
|
||||||
|
|
||||||
_fileWriteThread = new Task();
|
_fileWriteThread = new Task();
|
||||||
_fileWriteThread->start(false);
|
_fileWriteThread->start(false);
|
||||||
}
|
}
|
||||||
|
@ -437,13 +565,23 @@ NDSCaptureObject::NDSCaptureObject(size_t frameWidth, size_t frameHeight, const
|
||||||
_bmpFormat.biHeight = frameHeight * 2;
|
_bmpFormat.biHeight = frameHeight * 2;
|
||||||
_bmpFormat.biSizeImage = _bmpFormat.biWidth * _bmpFormat.biHeight * 3;
|
_bmpFormat.biSizeImage = _bmpFormat.biWidth * _bmpFormat.biHeight * 3;
|
||||||
|
|
||||||
_pendingVideoBuffer = (u8*)malloc_alignedCacheLine(_bmpFormat.biSizeImage * PENDING_BUFFER_COUNT);
|
|
||||||
|
|
||||||
if (wfex != NULL)
|
if (wfex != NULL)
|
||||||
{
|
{
|
||||||
_wavFormat = *wfex;
|
_wavFormat = *wfex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const size_t expectedFrameSize = AVIFileStream::GetExpectedFrameSize(&_bmpFormat, wfex);
|
||||||
|
_pendingBufferCount = MAX_PENDING_BUFFER_SIZE / expectedFrameSize;
|
||||||
|
|
||||||
|
if (_pendingBufferCount > MAX_PENDING_FRAME_COUNT)
|
||||||
|
{
|
||||||
|
_pendingBufferCount = MAX_PENDING_FRAME_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingVideoBuffer = (u8 *)malloc_alignedCacheLine(_bmpFormat.biSizeImage * _pendingBufferCount * sizeof(u8));
|
||||||
|
_pendingAudioBuffer = (u8 *)malloc_alignedCacheLine(AUDIO_STREAM_BUFFER_SIZE * _pendingBufferCount * sizeof(u8));
|
||||||
|
_pendingAudioWriteSize = (size_t *)calloc(_pendingBufferCount, sizeof(size_t));
|
||||||
|
|
||||||
const size_t linesPerThread = (_numThreads > 0) ? _bmpFormat.biHeight / _numThreads : _bmpFormat.biHeight;
|
const size_t linesPerThread = (_numThreads > 0) ? _bmpFormat.biHeight / _numThreads : _bmpFormat.biHeight;
|
||||||
|
|
||||||
if (_numThreads == 0)
|
if (_numThreads == 0)
|
||||||
|
@ -482,9 +620,6 @@ NDSCaptureObject::NDSCaptureObject(size_t frameWidth, size_t frameHeight, const
|
||||||
_convertParam[i].frameWidth = _bmpFormat.biWidth;
|
_convertParam[i].frameWidth = _bmpFormat.biWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileWriteParam.srcVideo = _pendingVideoBuffer;
|
|
||||||
_fileWriteParam.videoBufferSize = _bmpFormat.biSizeImage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NDSCaptureObject::~NDSCaptureObject()
|
NDSCaptureObject::~NDSCaptureObject()
|
||||||
|
@ -501,16 +636,18 @@ NDSCaptureObject::~NDSCaptureObject()
|
||||||
delete this->_fs;
|
delete this->_fs;
|
||||||
|
|
||||||
free_aligned(this->_pendingVideoBuffer);
|
free_aligned(this->_pendingVideoBuffer);
|
||||||
|
free_aligned(this->_pendingAudioBuffer);
|
||||||
|
free(this->_pendingAudioWriteSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT NDSCaptureObject::OpenFileStream(const char *fileName)
|
HRESULT NDSCaptureObject::OpenFileStream(const char *fileName)
|
||||||
{
|
{
|
||||||
return this->_fs->Open(fileName, &this->_bmpFormat, &this->_wavFormat);
|
return this->_fs->Open(fileName, &this->_bmpFormat, &this->_wavFormat, this->_pendingBufferCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NDSCaptureObject::CloseFileStream()
|
void NDSCaptureObject::CloseFileStream()
|
||||||
{
|
{
|
||||||
this->_fs->Close();
|
this->_fs->Close(FSCA_WriteRemainingInQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NDSCaptureObject::IsFileStreamValid()
|
bool NDSCaptureObject::IsFileStreamValid()
|
||||||
|
@ -520,34 +657,41 @@ bool NDSCaptureObject::IsFileStreamValid()
|
||||||
|
|
||||||
void NDSCaptureObject::StartFrame()
|
void NDSCaptureObject::StartFrame()
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < this->_numThreads; i++)
|
// If the queue is full, then we need to wait for some frames to finish writing
|
||||||
{
|
// before we continue adding new frames to the queue.
|
||||||
this->_convertThread[i]->finish();
|
this->_fs->QueueWait();
|
||||||
}
|
|
||||||
|
|
||||||
this->_currentBufferIndex = ((this->_currentBufferIndex + 1) % PENDING_BUFFER_COUNT);
|
this->_currentBufferIndex = ((this->_currentBufferIndex + 1) % this->_pendingBufferCount);
|
||||||
this->_pendingAudioWriteSize[this->_currentBufferIndex] = 0;
|
this->_pendingAudioWriteSize[this->_currentBufferIndex] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NDSCaptureObject::StreamWriteStart()
|
void NDSCaptureObject::StreamWriteStart()
|
||||||
{
|
{
|
||||||
const size_t bufferIndex = this->_currentBufferIndex;
|
const size_t bufferIndex = this->_currentBufferIndex;
|
||||||
|
const size_t queueSize = this->_fs->GetQueueSize();
|
||||||
|
const bool isQueueEmpty = (queueSize == 0);
|
||||||
|
|
||||||
if (this->_bmpFormat.biSizeImage > 0)
|
// If there are no frames in the current write queue, then we know that the current
|
||||||
|
// pending video frame will be written immediately. If this is the case, then we
|
||||||
|
// need to force the video conversion to finish so that we can write out the frame.
|
||||||
|
if (isQueueEmpty)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < this->_numThreads; i++)
|
if (this->_bmpFormat.biSizeImage > 0)
|
||||||
{
|
{
|
||||||
this->_convertThread[i]->finish();
|
for (size_t i = 0; i < this->_numThreads; i++)
|
||||||
|
{
|
||||||
|
this->_convertThread[i]->finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->_fileWriteThread->finish();
|
this->_fs->QueueAdd(this->_pendingVideoBuffer + (this->_bmpFormat.biSizeImage * bufferIndex), this->_bmpFormat.biSizeImage,
|
||||||
|
this->_pendingAudioBuffer + (AUDIO_STREAM_BUFFER_SIZE * bufferIndex), this->_pendingAudioWriteSize[bufferIndex]);
|
||||||
|
|
||||||
this->_fileWriteParam.srcVideo = this->_pendingVideoBuffer + (this->_bmpFormat.biSizeImage * bufferIndex);
|
if (isQueueEmpty)
|
||||||
this->_fileWriteParam.srcAudio = this->_pendingAudioBuffer + (AUDIO_STREAM_BUFFER_SIZE * bufferIndex);
|
{
|
||||||
this->_fileWriteParam.audioBufferSize = this->_pendingAudioWriteSize[bufferIndex];
|
this->_fileWriteThread->execute(&RunAviFileWrite, this->_fs);
|
||||||
|
}
|
||||||
this->_fileWriteThread->execute(&RunAviFileWrite, &this->_fileWriteParam);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NDSCaptureObject::StreamWriteFinish()
|
void NDSCaptureObject::StreamWriteFinish()
|
||||||
|
@ -608,6 +752,7 @@ void NDSCaptureObject::ReadVideoFrame(const void *srcVideoFrame, const size_t in
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < this->_numThreads; i++)
|
for (size_t i = 0; i < this->_numThreads; i++)
|
||||||
{
|
{
|
||||||
|
this->_convertThread[i]->finish();
|
||||||
this->_convertParam[i].src = (u16 *)srcVideoFrame + this->_convertParam[i].srcOffset;
|
this->_convertParam[i].src = (u16 *)srcVideoFrame + this->_convertParam[i].srcOffset;
|
||||||
this->_convertParam[i].dst = convertBufferHead + this->_convertParam[i].dstOffset;
|
this->_convertParam[i].dst = convertBufferHead + this->_convertParam[i].dstOffset;
|
||||||
this->_convertThread[i]->execute(&RunConvertVideoSlice555XTo888, &this->_convertParam[i]);
|
this->_convertThread[i]->execute(&RunConvertVideoSlice555XTo888, &this->_convertParam[i]);
|
||||||
|
@ -626,6 +771,7 @@ void NDSCaptureObject::ReadVideoFrame(const void *srcVideoFrame, const size_t in
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < this->_numThreads; i++)
|
for (size_t i = 0; i < this->_numThreads; i++)
|
||||||
{
|
{
|
||||||
|
this->_convertThread[i]->finish();
|
||||||
this->_convertParam[i].src = (u32 *)srcVideoFrame + this->_convertParam[i].srcOffset;
|
this->_convertParam[i].src = (u32 *)srcVideoFrame + this->_convertParam[i].srcOffset;
|
||||||
this->_convertParam[i].dst = convertBufferHead + this->_convertParam[i].dstOffset;
|
this->_convertParam[i].dst = convertBufferHead + this->_convertParam[i].dstOffset;
|
||||||
this->_convertThread[i]->execute(&RunConvertVideoSlice888XTo888, &this->_convertParam[i]);
|
this->_convertThread[i]->execute(&RunConvertVideoSlice888XTo888, &this->_convertParam[i]);
|
||||||
|
|
|
@ -21,6 +21,11 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <vfw.h>
|
#include <vfw.h>
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include <rthreads/rthreads.h>
|
||||||
|
#include <rthreads/rsemaphore.h>
|
||||||
|
|
||||||
#include "GPU.h"
|
#include "GPU.h"
|
||||||
#include "SPU.h"
|
#include "SPU.h"
|
||||||
#include "utils/task.h"
|
#include "utils/task.h"
|
||||||
|
@ -31,7 +36,16 @@
|
||||||
|
|
||||||
#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 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 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.
|
#define MAX_PENDING_BUFFER_SIZE (1536ULL * 1024ULL * 1024ULL) // Max pending buffer size should not exceed 1.5 GB.
|
||||||
|
#define MAX_PENDING_FRAME_COUNT 180 // Maintain up to 180 frames in memory for current and future file writes. This is equivalent to 3 seconds worth of frames.
|
||||||
|
|
||||||
|
|
||||||
|
enum FileStreamCloseAction
|
||||||
|
{
|
||||||
|
FSCA_DoNothing = 0,
|
||||||
|
FSCA_PurgeQueue = 1,
|
||||||
|
FSCA_WriteRemainingInQueue = 2
|
||||||
|
};
|
||||||
|
|
||||||
class NDSCaptureObject;
|
class NDSCaptureObject;
|
||||||
class AVIFileStream;
|
class AVIFileStream;
|
||||||
|
@ -62,6 +76,8 @@ struct AVIFileWriteParam
|
||||||
};
|
};
|
||||||
typedef struct AVIFileWriteParam AVIFileWriteParam;
|
typedef struct AVIFileWriteParam AVIFileWriteParam;
|
||||||
|
|
||||||
|
typedef std::queue<AVIFileWriteParam> AVIWriteQueue;
|
||||||
|
|
||||||
class AVIFileStream
|
class AVIFileStream
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -86,17 +102,28 @@ protected:
|
||||||
LONG _writtenVideoFrameCount;
|
LONG _writtenVideoFrameCount;
|
||||||
LONG _writtenAudioSampleCount;
|
LONG _writtenAudioSampleCount;
|
||||||
|
|
||||||
|
ssem_t *_semQueue;
|
||||||
|
slock_t *_mutexQueue;
|
||||||
|
AVIWriteQueue _writeQueue;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AVIFileStream();
|
AVIFileStream();
|
||||||
~AVIFileStream();
|
~AVIFileStream();
|
||||||
|
|
||||||
HRESULT Open(const char *fileName, BITMAPINFOHEADER *bmpFormat, WAVEFORMATEX *wavFormat);
|
static size_t GetExpectedFrameSize(const BITMAPINFOHEADER *bmpFormat, const WAVEFORMATEX *wavFormat);
|
||||||
void Close();
|
|
||||||
|
HRESULT Open(const char *fileName, BITMAPINFOHEADER *bmpFormat, WAVEFORMATEX *wavFormat, size_t pendingFrameCount);
|
||||||
|
void Close(FileStreamCloseAction theAction);
|
||||||
bool IsValid();
|
bool IsValid();
|
||||||
|
|
||||||
|
void QueueAdd(u8 *srcVideo, const size_t videoBufferSize, u8 *srcAudio, const size_t audioBufferSize);
|
||||||
|
void QueueWait();
|
||||||
|
size_t GetQueueSize();
|
||||||
|
|
||||||
HRESULT FlushVideo(u8 *srcBuffer, const LONG bufferSize);
|
HRESULT FlushVideo(u8 *srcBuffer, const LONG bufferSize);
|
||||||
HRESULT FlushAudio(u8 *srcBuffer, const LONG bufferSize);
|
HRESULT FlushAudio(u8 *srcBuffer, const LONG bufferSize);
|
||||||
HRESULT WriteOneFrame(u8 *srcVideo, const LONG videoBufferSize, u8 *srcAudio, const LONG audioBufferSize);
|
HRESULT WriteOneFrame(const AVIFileWriteParam ¶m);
|
||||||
|
HRESULT WriteAllFrames();
|
||||||
};
|
};
|
||||||
|
|
||||||
class NDSCaptureObject
|
class NDSCaptureObject
|
||||||
|
@ -108,15 +135,15 @@ protected:
|
||||||
WAVEFORMATEX _wavFormat;
|
WAVEFORMATEX _wavFormat;
|
||||||
|
|
||||||
u8 *_pendingVideoBuffer;
|
u8 *_pendingVideoBuffer;
|
||||||
u8 _pendingAudioBuffer[AUDIO_STREAM_BUFFER_SIZE * PENDING_BUFFER_COUNT];
|
u8 *_pendingAudioBuffer;
|
||||||
size_t _pendingAudioWriteSize[2];
|
size_t *_pendingAudioWriteSize;
|
||||||
|
size_t _pendingBufferCount;
|
||||||
size_t _currentBufferIndex;
|
size_t _currentBufferIndex;
|
||||||
|
|
||||||
size_t _numThreads;
|
size_t _numThreads;
|
||||||
Task *_fileWriteThread;
|
Task *_fileWriteThread;
|
||||||
Task *_convertThread[MAX_CONVERT_THREADS];
|
Task *_convertThread[MAX_CONVERT_THREADS];
|
||||||
VideoConvertParam _convertParam[MAX_CONVERT_THREADS];
|
VideoConvertParam _convertParam[MAX_CONVERT_THREADS];
|
||||||
AVIFileWriteParam _fileWriteParam;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NDSCaptureObject();
|
NDSCaptureObject();
|
||||||
|
|
Loading…
Reference in New Issue