aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Input/in_avi/VideoThread.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Input/in_avi/VideoThread.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/Plugins/Input/in_avi/VideoThread.cpp')
-rw-r--r--Src/Plugins/Input/in_avi/VideoThread.cpp428
1 files changed, 428 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_avi/VideoThread.cpp b/Src/Plugins/Input/in_avi/VideoThread.cpp
new file mode 100644
index 00000000..4dcdea3c
--- /dev/null
+++ b/Src/Plugins/Input/in_avi/VideoThread.cpp
@@ -0,0 +1,428 @@
+#include "main.h"
+#include "api__in_avi.h"
+#include "../nu/AutoLock.h"
+#include "../nu/SampleQueue.h"
+#include "../Winamp/wa_ipc.h"
+#include "interfaces.h"
+#include "../nsavi/nsavi.h"
+#include "../nu/ThreadName.h"
+#include "player.h"
+
+// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F}
+static const GUID playbackConfigGroupGUID =
+{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } };
+
+extern nsavi::HeaderList header_list;
+extern int video_stream_num;
+
+int width, height;
+extern IVideoOutput *video_output;
+static HANDLE video_thread=0;
+extern ifc_avivideodecoder *video_decoder;
+bool video_opened=false;
+static HANDLE coded_frames_event=0;
+static Nullsoft::Utility::LockGuard coded_frames_guard;
+uint64_t video_total_time=0;
+HANDLE video_break=0, video_flush=0, video_flush_done=0, video_resume=0, video_ready=0;
+
+static int GetStreamNumber(uint32_t id)
+{
+ char *stream_data = (char *)(&id);
+ if (!isxdigit(stream_data[0]) || !isxdigit(stream_data[1]))
+ return -1;
+
+ stream_data[2] = 0;
+ int stream_number = strtoul(stream_data, 0, 16);
+ return stream_number;
+}
+
+void Video_Init()
+{
+ video_opened=false;
+ video_decoder=0;
+ video_thread=0;
+ width=0;
+ height=0;
+ video_total_time=0;
+
+ if (coded_frames_event == 0)
+ coded_frames_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ /* video events */
+ if (!video_break)
+ video_break = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!video_flush_done)
+ video_flush_done = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ if (!video_flush)
+ video_flush = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!video_resume)
+ video_resume = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!video_ready)
+ video_ready = CreateEvent(NULL, TRUE, FALSE, NULL);
+}
+
+struct FRAMEDATA
+{
+ FRAMEDATA()
+ {
+ type = 0;
+ data=0;
+ length=0;
+ }
+
+ ~FRAMEDATA()
+ {
+ free(data);
+ }
+ void Reset()
+ {
+ free(data);
+ data=0;
+ length=0;
+ type = 0;
+ }
+ void Set(uint16_t _type, void *_data, size_t _length)
+ {
+ type = _type;
+ data = _data;
+ length = _length;
+ }
+ void *data;
+ size_t length;
+ uint16_t type;
+};
+
+static SampleQueue<FRAMEDATA> coded_frames;
+
+extern int GetOutputTime();
+struct VideoContext
+{
+ nsavi::avi_reader *reader;
+ nsavi::Demuxer *demuxer;
+ int video_stream_num;
+};
+
+static void DecodeVideo(FRAMEDATA *frame_data)
+{
+ HANDLE handles[] = {killswitch, video_break};
+ if (WaitForMultipleObjects(2, handles, FALSE, 0) == WAIT_TIMEOUT)
+ {
+ int decodeResult = video_decoder->DecodeChunk(frame_data->type, frame_data->data, frame_data->length);
+
+ if (decodeResult == ifc_avivideodecoder::AVI_SUCCESS)
+ {
+ void *data, *decoder_data;
+ while (video_decoder->GetPicture(&data, &decoder_data) == ifc_avivideodecoder::AVI_SUCCESS)
+ {
+ if (!video_opened)
+ {
+ int color_format;
+ double aspect_ratio=1.0;
+ int flip=0;
+ if (video_decoder->GetOutputProperties(&width, &height, &color_format, &aspect_ratio, &flip) == ifc_avivideodecoder::AVI_SUCCESS)
+ {
+ nsavi::VPRP *vprp = header_list.stream_list[video_stream_num].video_properties;
+ if (vprp)
+ {
+ uint32_t asp = vprp->aspect_ratio;
+ uint32_t aspect_x = HIWORD(asp);
+ uint32_t aspect_y = LOWORD(asp);
+
+ aspect_ratio = (double)vprp->frame_width / (double)vprp->frame_height / ((double)aspect_x / (double)aspect_y);
+ }
+ else
+ aspect_ratio = 1.0/aspect_ratio;
+
+ video_output->extended(VIDUSER_SET_THREAD_SAFE, 1, 0);
+ video_output->open(width, height, flip, aspect_ratio, color_format);
+ video_opened=true;
+ }
+ }
+ if (video_opened)
+ {
+ int timestamp=(int)(video_total_time*1000ULL/(uint64_t) header_list.stream_list[video_stream_num].stream_header->rate);
+again:
+ int real_time =(int)GetOutputTime();
+ if (timestamp > (real_time+5))
+ {
+ HANDLE handles[] = {killswitch, video_break};
+ int ret=WaitForMultipleObjects(2, handles, FALSE, timestamp-real_time);
+ if (ret != WAIT_TIMEOUT)
+ {
+ video_decoder->FreePicture(data, decoder_data);
+ frame_data->Reset();
+ return ;
+ }
+ goto again; // TODO: handle paused state a little better than this
+ }
+ RGB32 *palette=0;
+ if (video_decoder->GetPalette(&palette) == ifc_avivideodecoder::AVI_SUCCESS)
+ {
+ video_output->extended(VIDUSER_SET_PALETTE, (INT_PTR)palette, 0);
+ }
+ video_output->draw(data);
+ }
+ video_total_time += header_list.stream_list[video_stream_num].stream_header->scale;
+ video_decoder->FreePicture(data, decoder_data);
+ }
+ }
+
+ }
+
+ // frame_data->Reset();
+}
+
+static DWORD CALLBACK VideoProcedure(LPVOID param)
+{
+ SetThreadName(-1,"AVI_VideoProcedure");
+ HANDLE wait_handles[] = { killswitch, video_break, video_flush, video_resume, coded_frames_event };
+ while (1)
+ {
+ int ret = WaitForMultipleObjects(5, wait_handles, FALSE, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ break;
+ }
+ if (ret == WAIT_OBJECT_0 + 1) // video break
+ {
+ ResetEvent(coded_frames_event);
+ ResetEvent(video_break);
+ SetEvent(video_flush_done);
+ }
+ else if (ret == WAIT_OBJECT_0 + 2) // video flush
+ {
+ if (video_decoder)
+ video_decoder->Flush();
+ ResetEvent(video_flush);
+ coded_frames.Trim();
+ SetEvent(video_flush_done);
+ }
+ else if (ret == WAIT_OBJECT_0 + 3) // video resume
+ {
+ ResetEvent(video_resume);
+ SetEvent(coded_frames_event);
+ SetEvent(video_flush_done);
+ }
+ else if (ret == WAIT_OBJECT_0 + 4) // data from demuxer
+ {
+ FRAMEDATA *frame_data = 0;
+ while (frame_data = coded_frames.PopProcessed())
+ {
+ DecodeVideo(frame_data);
+ frame_data->Reset();
+ coded_frames.PushFree(frame_data);
+ }
+ }
+ };
+
+ if (video_opened && video_output)
+ video_output->close();
+ video_opened=false;
+
+ return 0;
+}
+
+static DWORD CALLBACK VideoReaderProcedure(LPVOID param)
+{
+ VideoContext *ctx = (VideoContext *)param;
+ nsavi::avi_reader *reader = ctx->reader;
+ nsavi::Demuxer *demuxer = ctx->demuxer;
+ SetThreadName(-1,"AVI_VideoProcedure");
+ HANDLE wait_handles[] = { killswitch, video_break, video_flush, video_resume };
+ int waitTime = 0;
+ while (1)
+ {
+ int ret = WaitForMultipleObjects(4, wait_handles, FALSE, waitTime);
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ break;
+ }
+ if (ret == WAIT_OBJECT_0 + 1) // video break
+ {
+ ResetEvent(coded_frames_event);
+ ResetEvent(video_break);
+ SetEvent(video_flush_done);
+ waitTime = INFINITE;
+ }
+ else if (ret == WAIT_OBJECT_0 + 2) // video flush
+ {
+ if (video_decoder)
+ video_decoder->Flush();
+ ResetEvent(video_flush);
+ coded_frames.Trim();
+ SetEvent(video_flush_done);
+ waitTime = 0;
+ }
+ else if (ret == WAIT_OBJECT_0 + 3) // video resume
+ {
+ ResetEvent(video_resume);
+ SetEvent(coded_frames_event);
+ SetEvent(video_flush_done);
+ waitTime = 0;
+ }
+ else if (ret == WAIT_TIMEOUT)
+ {
+ void *data=0;
+ uint32_t data_size = 0;
+ uint32_t data_type = 0;
+ int ret = demuxer->GetNextMovieChunk(reader, &data, &data_size, &data_type, video_stream_num);
+ if (ret != nsavi::READ_OK)
+ {
+ break;
+ }
+ int stream_number = GetStreamNumber(data_type);
+ uint16_t type = (data_type>>16);
+
+ if (stream_number == video_stream_num)
+ {
+ FRAMEDATA frame_data;
+ frame_data.data = data;
+ frame_data.length = data_size;
+ frame_data.type = type;
+ DecodeVideo(&frame_data);
+ frame_data.Reset();
+ }
+ else
+ free(data);
+ }
+ };
+
+ if (video_opened && video_output)
+ video_output->close();
+ video_opened=false;
+ free(ctx);
+ return 0;
+}
+
+bool CreateVideoReaderThread(nsavi::Demuxer *demuxer, nsavi::avi_reader *video_reader)
+{
+ if (!video_decoder || video_only)
+ return false;
+
+ VideoContext *context = (VideoContext *)calloc(1, sizeof(VideoContext));
+
+ context->reader = video_reader;
+ context->demuxer = demuxer;
+ DWORD threadId = 0;
+ video_thread = CreateThread(0, 0, VideoReaderProcedure, context, 0, &threadId);
+ SetThreadPriority(video_thread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST));
+ return true;
+}
+
+bool OnVideo(uint16_t type, void *data, size_t length)
+{
+ if (!video_decoder)
+ return false;
+
+ if (!video_only && !video_thread)
+ {
+ DWORD threadId;
+ video_thread = CreateThread(0, 0, VideoProcedure, 0, 0, &threadId);
+ SetThreadPriority(video_thread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST));
+ }
+
+ if (video_thread)
+ {
+ FRAMEDATA *new_frame = coded_frames.PopFree();
+ if (new_frame)
+ {
+ new_frame->Set(type, data, length);
+ coded_frames.PushProcessed(new_frame);
+ SetEvent(coded_frames_event);
+ }
+ return true;
+ }
+ else if (video_only)
+ {
+ FRAMEDATA *new_frame = coded_frames.PopFree();
+ if (new_frame)
+ {
+ new_frame->Set(type, data, length);
+ DecodeVideo(new_frame);
+ new_frame->Reset();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void Video_Stop()
+{
+ SetEvent(killswitch);
+ if (video_only)
+ {
+ video_thread=0;
+
+ ResetEvent(coded_frames_event);
+ Nullsoft::Utility::AutoLock l(coded_frames_guard);
+ coded_frames.Trim();
+ if (video_opened && video_output)
+ video_output->close();
+ video_opened=false;
+ }
+ else
+ {
+ if (video_thread)
+ WaitForSingleObject(video_thread, INFINITE);
+ video_thread=0;
+
+ ResetEvent(coded_frames_event);
+ Nullsoft::Utility::AutoLock l(coded_frames_guard);
+ coded_frames.Trim();
+ }
+}
+
+void Video_Close()
+{
+ video_opened=false;
+
+ if (video_decoder)
+ {
+ video_decoder->Close();
+ video_decoder=0;
+ }
+}
+
+void Video_Break()
+{
+ if (!video_only && video_decoder)
+ {
+ SetEvent(video_break);
+ HANDLE events[2] = {video_flush_done, video_thread};
+ WaitForMultipleObjects(2, events, FALSE, INFINITE);
+ }
+}
+
+void Video_Flush()
+{
+ if (video_decoder)
+ {
+ if (video_only)
+ {
+ video_decoder->Flush();
+ }
+ else
+ {
+ SetEvent(video_flush);
+ HANDLE events[2] = {video_flush_done, video_thread};
+ WaitForMultipleObjects(2, events, FALSE, INFINITE);
+ }
+ }
+}
+
+/*
+bool Video_DecoderReady()
+{
+if (!video_decoder)
+return true;
+
+return !!video_decoder->Ready();
+}
+*/