301 lines
7.5 KiB
C++
301 lines
7.5 KiB
C++
#include "Main.h"
|
|
#include "VideoLayer.h"
|
|
#include <initguid.h>
|
|
#include <wmsdkidl.h>
|
|
#include <cassert>
|
|
#include "util.h"
|
|
#include "resource.h"
|
|
#include <strsafe.h>
|
|
|
|
#include "config.h"
|
|
#define VIDEO_ACCEPTABLE_DROP (config_video_drop_threshold*10000)
|
|
|
|
VideoLayer::VideoLayer(IWMReader *_reader)
|
|
: reader(_reader), videoOutputNum(-1),
|
|
reader2(0), offset(0), nextRest(0),
|
|
converter(NULL), videoOpened(false),
|
|
video_output_opened(false),
|
|
killSwitch(0), aspect(0),
|
|
earlyDelivery(0), fourcc(0),
|
|
drmProtected(false),
|
|
videoStream(0), flip(false),
|
|
videoWidth(0), videoHeight(0)
|
|
{
|
|
reader->AddRef();
|
|
if (FAILED(reader->QueryInterface(&reader2)))
|
|
reader2 = 0;
|
|
|
|
if (FAILED(reader->QueryInterface(&header)))
|
|
header = 0;
|
|
|
|
killSwitch = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
}
|
|
|
|
VideoLayer::~VideoLayer()
|
|
{
|
|
videoThread.Kill();
|
|
if (reader2)
|
|
reader2->Release();
|
|
if (header)
|
|
header->Release();
|
|
reader->Release();
|
|
CloseHandle(killSwitch);
|
|
}
|
|
|
|
bool AcceptableFormat(GUID &subtype)
|
|
{
|
|
if (subtype == WMMEDIASUBTYPE_YV12
|
|
|| subtype == WMMEDIASUBTYPE_YUY2
|
|
|| subtype == WMMEDIASUBTYPE_UYVY
|
|
//|| subtype == WMMEDIASUBTYPE_YVYU
|
|
|| subtype == WMMEDIASUBTYPE_RGB24
|
|
|| subtype == WMMEDIASUBTYPE_RGB32
|
|
|| subtype == WMMEDIASUBTYPE_I420
|
|
|| subtype == WMMEDIASUBTYPE_IYUV
|
|
|| subtype == WMMEDIASUBTYPE_RGB1
|
|
|| subtype == WMMEDIASUBTYPE_RGB4
|
|
|| subtype == WMMEDIASUBTYPE_RGB8
|
|
|| subtype == WMMEDIASUBTYPE_RGB565
|
|
|| subtype == WMMEDIASUBTYPE_RGB555
|
|
)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool VideoLayer::AttemptOpenVideo(VideoOutputStream *attempt)
|
|
{
|
|
videoWidth = attempt->DestinationWidth();
|
|
videoHeight = attempt->DestinationHeight();
|
|
flip = attempt->Flipped();
|
|
fourcc = attempt->FourCC();
|
|
if (!fourcc)
|
|
return false;
|
|
|
|
aspect = 1.0;
|
|
return true;
|
|
|
|
}
|
|
|
|
bool VideoLayer::OpenVideo()
|
|
{
|
|
videoOutputNum = -1;
|
|
DWORD numOutputs, numFormats;
|
|
IWMOutputMediaProps *formatProperties;
|
|
VideoOutputStream *stream;
|
|
GUID mediaType;
|
|
|
|
reader->GetOutputCount(&numOutputs);
|
|
|
|
for (DWORD output = 0;output < numOutputs;output++)
|
|
{
|
|
// test the default format first, and if that fails, iterate through the rest
|
|
const int defaultFormat = -1;
|
|
HRESULT hr;
|
|
if (FAILED(hr = reader->GetOutputFormatCount(output, &numFormats)))
|
|
continue;
|
|
|
|
for (int format = 0/*defaultFormat*/;format != numFormats;format++)
|
|
{
|
|
if (format == defaultFormat)
|
|
reader->GetOutputProps(output, &formatProperties);
|
|
else
|
|
reader->GetOutputFormat(output, format, &formatProperties);
|
|
|
|
formatProperties->GetType(&mediaType);
|
|
|
|
if (mediaType == WMMEDIATYPE_Video)
|
|
{
|
|
stream = new VideoOutputStream(formatProperties);
|
|
|
|
if (stream->IsVideo() // if it's video
|
|
&& AcceptableFormat(stream->GetSubType()) // and a video format we like
|
|
&& AttemptOpenVideo(stream)) // and winamp was able to open it
|
|
{
|
|
videoOpened = true;
|
|
int fourcc = stream->FourCC();
|
|
if (fourcc == '8BGR')
|
|
{
|
|
RGBQUAD *palette = stream->CreatePalette();
|
|
winamp.SetVideoPalette(palette);
|
|
|
|
// TODO: don't leak the palette
|
|
}
|
|
char *cc = (char *) & fourcc;
|
|
char status[512] = {0};
|
|
StringCchPrintfA(status, 512, WASABI_API_LNGSTRING(IDS_WINDOWS_MEDIA_XXX),
|
|
stream->DestinationWidth(), stream->DestinationHeight(), cc[0], cc[1], cc[2], cc[3]);
|
|
winamp.SetVideoStatusText(status);
|
|
converter = MakeConverter(stream);
|
|
videoOutputNum = output;
|
|
videoStream = stream;
|
|
reader->SetOutputProps(output, formatProperties);
|
|
formatProperties->Release();
|
|
return true;
|
|
}
|
|
|
|
delete stream;
|
|
stream = 0;
|
|
|
|
}
|
|
formatProperties->Release();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void VideoLayer::SampleReceived(QWORD &timeStamp, QWORD &duration, unsigned long &outputNum, unsigned long &flags, INSSBuffer *&sample)
|
|
{
|
|
if (outputNum == videoOutputNum)
|
|
{
|
|
if (WaitForSingleObject(killSwitch, 0) == WAIT_OBJECT_0)
|
|
return ;
|
|
|
|
INSSBuffer3 *buff3;
|
|
if (SUCCEEDED(sample->QueryInterface(&buff3)))
|
|
{
|
|
short aspectHex = 0;
|
|
DWORD size = 2;
|
|
buff3->GetProperty(WM_SampleExtensionGUID_PixelAspectRatio, &aspectHex, &size);
|
|
if (aspectHex)
|
|
{
|
|
double newAspect = (double)((aspectHex & 0xFF00) >> 8) / (double)(aspectHex & 0xFF) ;
|
|
|
|
if (newAspect != aspect)
|
|
{
|
|
aspect = newAspect;
|
|
videoThread.OpenVideo(drmProtected, videoWidth, videoHeight, flip, aspect, fourcc);
|
|
video_output_opened=true;
|
|
}
|
|
}
|
|
buff3->Release();
|
|
}
|
|
|
|
if (!video_output_opened)
|
|
{
|
|
videoThread.OpenVideo(drmProtected, videoWidth, videoHeight, flip, aspect, fourcc);
|
|
video_output_opened=true;
|
|
}
|
|
|
|
__int64 timeDiff;
|
|
First().TimeToSync(timeStamp, timeDiff);
|
|
|
|
if (timeDiff < -VIDEO_ACCEPTABLE_DROP) // late
|
|
{
|
|
timeDiff = -timeDiff;
|
|
if (config_video_catchup) First().VideoCatchup(timeDiff);
|
|
if (config_video_framedropoffset) this->VideoFrameDrop((DWORD)(timeDiff / 10000));
|
|
if (config_video_notifylate) reader2->NotifyLateDelivery(timeDiff);
|
|
|
|
// drop the frame
|
|
}
|
|
else // early
|
|
{
|
|
while (!videoThread.AddBuffer(sample, timeStamp, flags, drmProtected))
|
|
{
|
|
if (WaitForSingleObject(killSwitch, VIDEO_ACCEPTABLE_JITTER_MS) == WAIT_OBJECT_0)
|
|
return ;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
WMHandler::SampleReceived(timeStamp, duration, outputNum, flags, sample);
|
|
}
|
|
|
|
void VideoLayer::Opened()
|
|
{
|
|
WORD stream = 0;
|
|
WMT_ATTR_DATATYPE type = WMT_TYPE_BOOL;
|
|
BOOL value;
|
|
WORD valueLen = sizeof(value);
|
|
header->GetAttributeByName(&stream, g_wszWMProtected, &type, (BYTE *)&value, &valueLen);
|
|
drmProtected = !!value;
|
|
|
|
ResetEvent(killSwitch);
|
|
if (OpenVideo())
|
|
{
|
|
ResetEvent(killSwitch);
|
|
HRESULT hr;
|
|
|
|
BOOL dedicatedThread = config_video_dedicated_thread ? TRUE : FALSE;
|
|
hr = reader2->SetOutputSetting(videoOutputNum, g_wszDedicatedDeliveryThread, WMT_TYPE_BOOL, (BYTE *) & dedicatedThread, sizeof(dedicatedThread));
|
|
assert(hr == S_OK);
|
|
|
|
earlyDelivery = config_video_early ? config_video_early_pad : 0;
|
|
hr = reader2->SetOutputSetting(videoOutputNum, g_wszEarlyDataDelivery, WMT_TYPE_DWORD, (BYTE *) & earlyDelivery , sizeof(earlyDelivery));
|
|
assert(hr == S_OK);
|
|
|
|
BOOL outOfOrder = config_video_outoforder ? TRUE : FALSE;
|
|
hr = reader2->SetOutputSetting(videoOutputNum, g_wszDeliverOnReceive, WMT_TYPE_BOOL, (BYTE *) & outOfOrder, sizeof(outOfOrder));
|
|
assert(hr == S_OK);
|
|
|
|
BOOL justInTime = config_lowmemory ? TRUE : FALSE;
|
|
hr = reader2->SetOutputSetting(videoOutputNum, g_wszJustInTimeDecode, WMT_TYPE_BOOL, (BYTE *) & justInTime, sizeof(justInTime));
|
|
assert(hr == S_OK);
|
|
}
|
|
else
|
|
{
|
|
videoOpened = false;
|
|
}
|
|
|
|
WMHandler::Opened();
|
|
}
|
|
|
|
void VideoLayer::VideoFrameDrop(DWORD lateness)
|
|
{
|
|
//earlyDelivery+=lateness;
|
|
lateness += earlyDelivery;
|
|
HRESULT hr = reader2->SetOutputSetting(videoOutputNum, g_wszEarlyDataDelivery, WMT_TYPE_DWORD, (BYTE *) & lateness, sizeof(lateness));
|
|
assert(hr == S_OK);
|
|
}
|
|
|
|
void VideoLayer::Closed()
|
|
{
|
|
if (video_output_opened)
|
|
{
|
|
videoThread.CloseVideo(drmProtected);
|
|
video_output_opened = false;
|
|
}
|
|
videoOpened = false;
|
|
delete videoStream;
|
|
videoStream=0;
|
|
WMHandler::Closed();
|
|
}
|
|
|
|
|
|
bool VideoLayer::IsOpen()
|
|
{
|
|
return videoOpened;
|
|
}
|
|
|
|
void VideoLayer::HasVideo(bool &video)
|
|
{
|
|
video=videoOpened;
|
|
}
|
|
|
|
void VideoLayer::Kill()
|
|
{
|
|
SetEvent(killSwitch);
|
|
if (videoOpened)
|
|
videoThread.SignalStop();//SignalStop();
|
|
|
|
WMHandler::Kill();
|
|
if (videoOpened)
|
|
videoThread.WaitForStop();
|
|
}
|
|
|
|
void VideoLayer::Started()
|
|
{
|
|
ResetEvent(killSwitch);
|
|
if (videoOpened)
|
|
videoThread.Start(converter, &First());
|
|
WMHandler::Started();
|
|
}
|
|
|
|
void VideoLayer::Stopped()
|
|
{
|
|
if (videoOpened)
|
|
videoThread.Stop();
|
|
WMHandler::Stopped();
|
|
}
|