292 lines
6.2 KiB
C++
292 lines
6.2 KiB
C++
|
#include "main.h"
|
||
|
#include "VideoThread.h"
|
||
|
#include "api__in_flv.h"
|
||
|
#include "FLVVideoHeader.h"
|
||
|
#include <shlwapi.h>
|
||
|
#include <windows.h>
|
||
|
#include "../nu/threadname.h"
|
||
|
#include <api/service/waservicefactory.h>
|
||
|
#include "../nu/AutoLock.h"
|
||
|
#include "../nu/SampleQueue.h"
|
||
|
|
||
|
int width, height;
|
||
|
IVideoOutput *videoOutput=0;
|
||
|
static HANDLE videoThread=0;
|
||
|
static volatile LONG video_flush=0;
|
||
|
static ifc_flvvideodecoder *videoDecoder=0;
|
||
|
bool video_opened=false;
|
||
|
static HANDLE coded_frames_event=0;
|
||
|
static HANDLE video_flush_event=0;
|
||
|
static Nullsoft::Utility::LockGuard coded_frames_guard;
|
||
|
extern bool video_only;
|
||
|
|
||
|
void Video_Init()
|
||
|
{
|
||
|
video_opened=false;
|
||
|
videoDecoder=0;
|
||
|
videoThread=0;
|
||
|
width=0;
|
||
|
height=0;
|
||
|
|
||
|
if (coded_frames_event == 0)
|
||
|
coded_frames_event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||
|
if (video_flush_event == 0)
|
||
|
video_flush_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||
|
video_flush=0;
|
||
|
}
|
||
|
|
||
|
struct FRAMEDATA
|
||
|
{
|
||
|
FRAMEDATA()
|
||
|
{
|
||
|
data=0;
|
||
|
length=0;
|
||
|
timestamp=0;
|
||
|
}
|
||
|
|
||
|
~FRAMEDATA()
|
||
|
{
|
||
|
free(data);
|
||
|
}
|
||
|
void Reset()
|
||
|
{
|
||
|
free(data);
|
||
|
data=0;
|
||
|
length=0;
|
||
|
timestamp=0;
|
||
|
}
|
||
|
void Set(void *_data, size_t _length, uint32_t ts)
|
||
|
{
|
||
|
data = _data;
|
||
|
length = _length;
|
||
|
timestamp = ts;
|
||
|
}
|
||
|
void *data;
|
||
|
size_t length;
|
||
|
uint32_t timestamp;
|
||
|
};
|
||
|
|
||
|
static SampleQueue<FRAMEDATA> coded_frames;
|
||
|
|
||
|
extern int GetOutputTime();
|
||
|
static void DecodeVideo(FRAMEDATA *framedata)
|
||
|
{
|
||
|
if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
|
||
|
{
|
||
|
int decodeResult = videoDecoder->DecodeSample(framedata->data, framedata->length, framedata->timestamp);
|
||
|
|
||
|
if (decodeResult == FLV_VIDEO_SUCCESS)
|
||
|
{
|
||
|
void *data, *decoder_data;
|
||
|
uint64_t timestamp=framedata->timestamp;
|
||
|
while (videoDecoder->GetPicture(&data, &decoder_data, ×tamp) == FLV_VIDEO_SUCCESS)
|
||
|
{
|
||
|
if (!video_opened)
|
||
|
{
|
||
|
int color_format;
|
||
|
if (videoDecoder->GetOutputFormat(&width, &height, &color_format) == FLV_VIDEO_SUCCESS)
|
||
|
{
|
||
|
videoOutput->extended(VIDUSER_SET_THREAD_SAFE, 1, 0);
|
||
|
videoOutput->open(width, height, 0, 1.0, color_format);
|
||
|
video_opened=true;
|
||
|
}
|
||
|
}
|
||
|
if (video_opened)
|
||
|
{
|
||
|
again:
|
||
|
int realTime =(int)GetOutputTime();
|
||
|
if (timestamp > (realTime+5))
|
||
|
{
|
||
|
HANDLE handles[] = {killswitch, video_flush_event};
|
||
|
int ret=WaitForMultipleObjects(2, handles, FALSE, (DWORD)(timestamp-realTime));
|
||
|
if (ret != WAIT_TIMEOUT)
|
||
|
{
|
||
|
videoDecoder->FreePicture(data, decoder_data);
|
||
|
framedata->Reset();
|
||
|
return ;
|
||
|
}
|
||
|
goto again; // TODO: handle paused state a little better than this
|
||
|
}
|
||
|
videoOutput->draw(data);
|
||
|
}
|
||
|
videoDecoder->FreePicture(data, decoder_data);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
framedata->Reset();
|
||
|
}
|
||
|
|
||
|
DWORD CALLBACK VideoProcedure(LPVOID param)
|
||
|
{
|
||
|
SetThreadName(-1,"FLV_VideoProcedure");
|
||
|
HANDLE wait_handles[] = { killswitch, video_flush_event, coded_frames_event};
|
||
|
int ret;
|
||
|
do
|
||
|
{
|
||
|
ret = WaitForMultipleObjects(3, wait_handles, FALSE, INFINITE);
|
||
|
if (ret == WAIT_OBJECT_0 + 1)
|
||
|
{
|
||
|
if (video_flush)
|
||
|
{
|
||
|
InterlockedDecrement(&video_flush);
|
||
|
if (videoDecoder)
|
||
|
videoDecoder->Flush();
|
||
|
}
|
||
|
ResetEvent(video_flush_event);
|
||
|
}
|
||
|
else if (ret == WAIT_OBJECT_0 + 2)
|
||
|
{
|
||
|
FRAMEDATA *frame_data = 0;
|
||
|
while (frame_data = coded_frames.PopProcessed())
|
||
|
{
|
||
|
DecodeVideo(frame_data);
|
||
|
frame_data->Reset();
|
||
|
coded_frames.PushFree(frame_data);
|
||
|
}
|
||
|
}
|
||
|
} while (ret != WAIT_OBJECT_0);
|
||
|
|
||
|
if (video_opened && videoOutput)
|
||
|
videoOutput->close();
|
||
|
video_opened=false;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
bool Video_IsSupported(int type)
|
||
|
{
|
||
|
size_t n = 0;
|
||
|
waServiceFactory *factory = NULL;
|
||
|
while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
|
||
|
{
|
||
|
svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
|
||
|
if (creator)
|
||
|
{
|
||
|
int supported = creator->HandlesVideo(type);
|
||
|
factory->releaseInterface(creator);
|
||
|
if (supported == svc_flvdecoder::CREATEDECODER_SUCCESS)
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static ifc_flvvideodecoder *CreateVideoDecoder(int type)
|
||
|
{
|
||
|
ifc_flvvideodecoder *video_decoder = 0;
|
||
|
size_t n = 0;
|
||
|
waServiceFactory *factory = NULL;
|
||
|
while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
|
||
|
{
|
||
|
svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
|
||
|
if (creator)
|
||
|
{
|
||
|
if (creator->CreateVideoDecoder(type, width, height, &video_decoder) == FLV_VIDEO_SUCCESS)
|
||
|
return video_decoder;
|
||
|
|
||
|
factory->releaseInterface(creator);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F}
|
||
|
static const GUID playbackConfigGroupGUID =
|
||
|
{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } };
|
||
|
bool OnVideo(void *data, size_t length, int type, unsigned __int32 timestamp)
|
||
|
{
|
||
|
if (!videoDecoder)
|
||
|
{
|
||
|
videoDecoder = CreateVideoDecoder(type);
|
||
|
}
|
||
|
|
||
|
if (videoDecoder)
|
||
|
{
|
||
|
if (!video_only && !videoThread)
|
||
|
{
|
||
|
DWORD threadId;
|
||
|
videoThread = CreateThread(0, 0, VideoProcedure, 0, 0, &threadId);
|
||
|
SetThreadPriority(videoThread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST));
|
||
|
}
|
||
|
|
||
|
FRAMEDATA *new_frame = coded_frames.PopFree();
|
||
|
if (new_frame)
|
||
|
{
|
||
|
new_frame->Set(data, length, timestamp);
|
||
|
if (video_only)
|
||
|
{
|
||
|
DecodeVideo(new_frame);
|
||
|
new_frame->Reset();
|
||
|
coded_frames.PushFree(new_frame);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
coded_frames.PushProcessed(new_frame);
|
||
|
SetEvent(coded_frames_event);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void Video_Stop()
|
||
|
{
|
||
|
if (video_only)
|
||
|
{
|
||
|
ResetEvent(coded_frames_event);
|
||
|
Nullsoft::Utility::AutoLock l(coded_frames_guard);
|
||
|
coded_frames.Trim();
|
||
|
if (video_opened && videoOutput)
|
||
|
videoOutput->close();
|
||
|
video_opened=false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (videoThread)
|
||
|
WaitForSingleObject(videoThread, INFINITE);
|
||
|
videoThread=0;
|
||
|
|
||
|
InterlockedIncrement(&video_flush);
|
||
|
ResetEvent(coded_frames_event);
|
||
|
Nullsoft::Utility::AutoLock l(coded_frames_guard);
|
||
|
coded_frames.Trim();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Video_Close()
|
||
|
{
|
||
|
video_opened=false;
|
||
|
|
||
|
if (videoDecoder)
|
||
|
{
|
||
|
videoDecoder->Close();
|
||
|
videoDecoder=0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VideoFlush()
|
||
|
{
|
||
|
if (video_only)
|
||
|
{
|
||
|
if (videoDecoder)
|
||
|
videoDecoder->Flush();
|
||
|
}
|
||
|
else if (videoThread)
|
||
|
{
|
||
|
InterlockedIncrement(&video_flush);
|
||
|
ResetEvent(coded_frames_event);
|
||
|
coded_frames.Trim();
|
||
|
SetEvent(video_flush_event);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool Video_DecoderReady()
|
||
|
{
|
||
|
if (!videoDecoder)
|
||
|
return true;
|
||
|
|
||
|
return !!videoDecoder->Ready();
|
||
|
}
|