winamp/Src/Plugins/Input/in_flv/PlayThread.cpp

703 lines
16 KiB
C++

#include "main.h"
#include <windows.h>
#include "api__in_flv.h"
#include "../Winamp/wa_ipc.h"
#include "FLVHeader.h"
#include "FLVStreamHeader.h"
#include "FLVAudioHeader.h"
#include "FLVVideoHeader.h"
#include "FLVMetadata.h"
#include <malloc.h>
#include <stdio.h>
#include <shlwapi.h>
#include "VideoThread.h"
#include "FLVReader.h"
#include "resource.h"
#include "FLVCOM.h"
#include <api/service/waservicefactory.h>
#include "ifc_flvaudiodecoder.h"
#include "svc_flvdecoder.h"
#include "../nu/AudioOutput.h"
#define PRE_BUFFER_MS 5000.0
#define PRE_BUFFER_MAX (1024*1024)
uint32_t last_timestamp=0;
static bool audioOpened;
int bufferCount;
static int bits, channels, sampleRate;
static double dataRate_audio, dataRate_video;
static double dataRate;
static uint64_t prebuffer;
bool mute=false;
uint32_t first_timestamp;
static size_t audio_buffered=0;
void VideoStop();
static ifc_flvaudiodecoder *audioDecoder=0;
static bool checked_in_swf=false;
extern bool video_only;
class FLVWait
{
public:
int WaitOrAbort(int len)
{
if (WaitForSingleObject(killswitch, len) == WAIT_OBJECT_0)
return 1;
return 0;
}
};
static nu::AudioOutput<FLVWait> outputter(&plugin);
static void Buffering(int bufStatus, const wchar_t *displayString)
{
if (bufStatus < 0 || bufStatus > 100)
return;
char tempdata[75*2] = {0, };
int csa = plugin.SAGetMode();
if (csa & 1)
{
for (int x = 0; x < bufStatus*75 / 100; x ++)
tempdata[x] = x * 16 / 75;
}
else if (csa&2)
{
int offs = (csa & 1) ? 75 : 0;
int x = 0;
while (x < bufStatus*75 / 100)
{
tempdata[offs + x++] = -6 + x * 14 / 75;
}
while (x < 75)
{
tempdata[offs + x++] = 0;
}
}
else if (csa == 4)
{
tempdata[0] = tempdata[1] = (bufStatus * 127 / 100);
}
if (csa) plugin.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa);
/*
TODO
wchar_t temp[64] = {0};
StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus);
SetStatus(temp);
*/
//SetVideoStatusText(temp); // TODO: find a way to set the old status back
videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f));
}
static bool Audio_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->HandlesAudio(type);
factory->releaseInterface(creator);
if (supported == svc_flvdecoder::CREATEDECODER_SUCCESS)
return true;
}
}
return false;
}
static ifc_flvaudiodecoder *CreateAudioDecoder(const FLVAudioHeader &header)
{
ifc_flvaudiodecoder *audio_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->CreateAudioDecoder(header.stereo, header.bits, header.sampleRate, header.format, &audio_decoder) == FLV_AUDIO_SUCCESS)
return audio_decoder;
factory->releaseInterface(creator);
}
}
return 0;
}
void OnStart()
{
Video_Init();
audioOpened = false;
audioDecoder = 0;
bufferCount=0;
mute = false;
audio_buffered=0;
if (!videoOutput)
videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT);
}
void Audio_Close()
{
if (audioDecoder)
audioDecoder->Close();
audioDecoder=0;
}
static bool OpenAudio(unsigned int sampleRate, unsigned int channels, unsigned int bits)
{
if (!outputter.Open(0, channels, sampleRate, bits, -666))
return false;
audioOpened = true;
return true;
}
static void DoBuffer(FLVReader &reader)
{
// TODO: we should pre-buffer after getting the first audio + video frames
// so we can estimate bitrate
for (;;)
{
uint64_t processedPosition = reader.GetProcessedPosition();
if (processedPosition < prebuffer && !reader.IsEOF())
{
Buffering((int)((100ULL * processedPosition) / prebuffer), WASABI_API_LNGSTRINGW(IDS_BUFFERING));
if (WaitForSingleObject(killswitch, 100) == WAIT_OBJECT_0)
break;
else
continue;
}
else
break;
}
}
char pcmdata[65536] = {0};
size_t outlen = 32768;
static uint32_t audio_type;
static void OnAudio(FLVReader &reader, void *data, size_t length, const FLVAudioHeader &header)
{
if (!audioDecoder)
{
audioDecoder = CreateAudioDecoder(header);
audio_type = header.format;
video_only=false;
}
if (audioDecoder)
{
if (!audioDecoder->Ready())
{
//first_timestamp = -1;
}
outlen = sizeof(pcmdata)/2 - audio_buffered;
double bitrate = 0;
int ret = audioDecoder->DecodeSample(data, length, pcmdata+audio_buffered, &outlen, &bitrate);
if (ret == FLV_AUDIO_SUCCESS)
{
outlen+=audio_buffered;
audio_buffered=0;
if (bitrate && dataRate_audio != bitrate)
{
dataRate_audio = bitrate;
dataRate = dataRate_audio + dataRate_video;
plugin.SetInfo((int)dataRate, -1, -1, 1);
}
if (!audioOpened)
{
// pre-populate values for decoders that use the header info (e.g. ADPCM)
sampleRate=header.sampleRate;
channels = header.stereo?2:1;
bits = header.bits;
if (audioDecoder->GetOutputFormat((unsigned int *)&sampleRate, (unsigned int *)&channels, (unsigned int *)&bits) == FLV_AUDIO_SUCCESS)
{
// buffer (if needed)
prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0);
if (prebuffer > PRE_BUFFER_MAX)
prebuffer=PRE_BUFFER_MAX;
DoBuffer(reader); // benski> admittedly a crappy place to call this
if (WaitForSingleObject(killswitch, 0) == WAIT_OBJECT_0)
return;
OpenAudio(sampleRate, channels, bits);
}
}
if (mute)
{
if (bits == 8) // 8 bit is signed so 128 is zero voltage
memset(pcmdata, 0x80, outlen);
else
memset(pcmdata, 0, outlen);
}
if (audioOpened && outlen)
{
outputter.Write(pcmdata, outlen);
}
else
{
audio_buffered=outlen;
}
}
else if (ret == FLV_AUDIO_NEEDS_MORE_INPUT)
{
plugin.SetInfo(-1, -1, -1, 0);
}
}
}
#define PREBUFFER_BYTES 2048ULL
enum
{
CODEC_CHECK_NONE=0,
CODEC_CHECK_AUDIO=1,
CODEC_CHECK_VIDEO=2,
CODEC_CHECK_UNSURE = -1,
};
static bool CheckSWF()
{
if (!checked_in_swf)
{
const wchar_t *pluginsDir = (const wchar_t *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW);
wchar_t in_swf_path[MAX_PATH] = {0};
PathCombineW(in_swf_path, pluginsDir, L"in_swf.dll");
in_swf = LoadLibraryW(in_swf_path);
checked_in_swf = true;
}
return !!in_swf;
}
static void CALLBACK SWFAPC(ULONG_PTR param)
{
if (in_swf)
{
typedef In_Module *(*MODULEGETTER)();
MODULEGETTER moduleGetter=0;
moduleGetter = (MODULEGETTER)GetProcAddress(in_swf, "winampGetInModule2");
if (moduleGetter)
swf_mod = moduleGetter();
}
if (swf_mod)
{
if (swf_mod->Play(playFile))
swf_mod=0;
}
if (!swf_mod)
{
if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
}
}
static bool Audio_DecoderReady()
{
if (!audioDecoder)
return true;
return !!audioDecoder->Ready();
}
static bool DecodersReady()
{
return Audio_DecoderReady() && Video_DecoderReady();
}
DWORD CALLBACK PlayProcedure(LPVOID param)
{
int needCodecCheck=CODEC_CHECK_UNSURE;
int missingCodecs=CODEC_CHECK_NONE;
size_t codecCheckFrame=0;
dataRate_audio=0;
dataRate_video=0;
dataRate=0;
first_timestamp=-1;
outputter.Init(plugin.outMod);
FLVReader reader(playFile);
size_t i=0;
bool hasDuration=false;
OnStart();
plugin.is_seekable=0;
prebuffer = PREBUFFER_BYTES;
bool first_frame_parsed=false;
for (;;)
{
DoBuffer(reader);
if (WaitForSingleObject(killswitch, paused?100:0) == WAIT_OBJECT_0)
break;
if (paused)
continue;
if (m_need_seek != -1 && DecodersReady() && first_frame_parsed)
{
if (reader.GetPosition(m_need_seek, &i, video_opened))
{
VideoFlush();
if (audioDecoder)
audioDecoder->Flush();
FrameData frameData;
reader.GetFrame(i, frameData);
outputter.Flush(frameData.header.timestamp);
if (video_only)
{
video_clock.Seek(frameData.header.timestamp);
}
}
uint32_t first_timestamp = 0;
m_need_seek=-1;
}
// update the movie length
if (!hasDuration)
{
if (reader.IsStreaming())
{
hasDuration=true;
g_length = -1000;
plugin.is_seekable=0;
}
else
{
g_length=reader.GetMaxTimestamp();
if (g_length != -1000)
plugin.is_seekable=1;
}
PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
}
/** if it's a local file or an HTTP asset with content-length
** we verify that the FLV has codecs we can decode
** depending on settings, we will do one of the following with unsupport assets
** 1) Play anyway (e.g. an H.264 video might play audio only)
** 2) Use in_swf to play back
** 3) skip
**/
if (needCodecCheck == CODEC_CHECK_UNSURE)
{
FLVHeader *header = reader.GetHeader();
if (header)
{
needCodecCheck=CODEC_CHECK_NONE;
if (!reader.IsStreaming())
{
if (header->hasVideo)
needCodecCheck |= CODEC_CHECK_VIDEO;
if (header->hasAudio)
needCodecCheck |= CODEC_CHECK_AUDIO;
}
if (!header->hasAudio)
{
video_only=true;
video_clock.Start();
}
}
}
if (needCodecCheck)
{
FrameData frameData;
if (reader.GetFrame(codecCheckFrame, frameData))
{
FLVStreamHeader &frameHeader = frameData.header;
if ((needCodecCheck & CODEC_CHECK_AUDIO) && frameHeader.type == FLV::FRAME_TYPE_AUDIO)
{
reader.Seek(frameData.location+15); // TODO: check for -1 return value
uint8_t data[1] = {0};
FLVAudioHeader audioHeader;
size_t bytesRead = reader.Read(data, 1);
if (audioHeader.Read(data, bytesRead))
{
if (Audio_IsSupported(audioHeader.format))
{
needCodecCheck &= ~CODEC_CHECK_AUDIO;
}
else
{
needCodecCheck &= ~CODEC_CHECK_AUDIO;
missingCodecs|=CODEC_CHECK_AUDIO;
video_only=true;
}
}
}
if ((needCodecCheck & CODEC_CHECK_VIDEO) && frameHeader.type == FLV::FRAME_TYPE_VIDEO)
{
reader.Seek(frameData.location+15); // TODO: check for -1 return value
uint8_t data[1] = {0};
FLVVideoHeader videoHeader;
size_t bytesRead = reader.Read(data, 1);
if (videoHeader.Read(data, bytesRead))
{
if (Video_IsSupported(videoHeader.format))
{
needCodecCheck &= ~CODEC_CHECK_VIDEO;
}
else
{
needCodecCheck &= ~CODEC_CHECK_VIDEO;
missingCodecs|=CODEC_CHECK_VIDEO;
}
}
}
codecCheckFrame++;
}
else if (reader.IsEOF())
break;
else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0)
break;
}
if (needCodecCheck)
continue; // don't start decoding until we've done our codec check
if (missingCodecs)
{
// use in_swf to play this one
if (CheckSWF())
{
HANDLE mainThread = WASABI_API_APP->main_getMainThreadHandle();
if (mainThread)
{
reader.Kill();
Audio_Close();
Video_Stop();
Video_Close();
QueueUserAPC(SWFAPC, mainThread,0);
CloseHandle(mainThread);
return 0;
}
}
else
{
FLVHeader *header = reader.GetHeader();
if (header)
{
bool can_play_something = false;
if (header->hasVideo && !(missingCodecs & CODEC_CHECK_VIDEO))
can_play_something = true; // we can play video
else if (header->hasAudio && !(missingCodecs & CODEC_CHECK_AUDIO))
can_play_something = true; // we can play audio
if (can_play_something)
{
missingCodecs=false;
continue;
}
}
break; // no header or no codecs at all, bail out
}
}
/* --- End Codec Check --- */
FrameData frameData;
if (reader.GetFrame(i, frameData))
{
i++;
uint8_t data[2] = {0};
FLVStreamHeader &frameHeader = frameData.header;
reader.Seek(frameData.location+15); // TODO: check for -1 return value
switch (frameHeader.type)
{
default:
#ifdef _DEBUG
DebugBreak();
#endif
break;
case FLV::FRAME_TYPE_AUDIO: // audio
first_frame_parsed=true;
if (m_need_seek == -1 || !Audio_DecoderReady())
{
FLVAudioHeader audioHeader;
size_t bytesRead = reader.Read(data, 1);
if (audioHeader.Read(data, bytesRead))
{
size_t dataSize = frameHeader.dataSize - 1;
uint8_t *audiodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t));
if (audiodata)
{
bytesRead = reader.Read(audiodata, dataSize);
if (bytesRead != dataSize)
break;
if (!reader.IsStreaming())
{
if (first_timestamp == -1)
first_timestamp = frameHeader.timestamp;
last_timestamp = frameHeader.timestamp;
last_timestamp = plugin.outMod->GetWrittenTime();
}
OnAudio(reader, audiodata, dataSize, audioHeader);
free(audiodata);
}
}
}
break;
case FLV::FRAME_TYPE_VIDEO: // video
first_frame_parsed=true;
if (m_need_seek == -1 || !Video_DecoderReady())
{
FLVVideoHeader videoHeader;
size_t bytesRead = reader.Read(data, 1);
if (videoHeader.Read(data, bytesRead))
{
size_t dataSize = frameHeader.dataSize - 1;
uint8_t *videodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t));
if (videodata)
{
bytesRead = reader.Read(videodata, dataSize);
if (bytesRead != dataSize)
{
free(videodata);
break;
}
if (!OnVideo(videodata, dataSize, videoHeader.format, frameHeader.timestamp))
free(videodata);
}
}
}
break;
case FLV::FRAME_TYPE_METADATA: // metadata
{
first_frame_parsed=true;
size_t dataSize = frameHeader.dataSize;
uint8_t *metadatadata= (uint8_t *)calloc(dataSize, sizeof(uint8_t));
if (metadatadata)
{
size_t bytesRead = reader.Read(metadatadata, dataSize);
if (bytesRead != dataSize)
{
free(metadatadata);
break;
}
FLVMetadata metadata;
metadata.Read(metadatadata, dataSize);
for ( FLVMetadata::Tag *tag : metadata.tags )
{
if (!_wcsicmp(tag->name.str, L"onMetaData"))
{
AMFType *amf_stream_title;
amf_stream_title = tag->parameters->array[L"streamTitle"];
if (amf_stream_title && amf_stream_title->type == AMFType::TYPE_STRING)
{
AMFString *stream_title_string = (AMFString *)amf_stream_title;
Nullsoft::Utility::AutoLock stream_lock(stream_title_guard);
free(stream_title);
stream_title = _wcsdup(stream_title_string->str);
PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
}
AMFType *w, *h;
w = tag->parameters->array[L"width"];
h = tag->parameters->array[L"height"];
if (w && h)
{
width = (int)AMFGetDouble(w);
height = (int)AMFGetDouble(h);
}
AMFType *duration;
duration=tag->parameters->array[L"duration"];
if (duration)
{
hasDuration=true;
plugin.is_seekable=1;
g_length = (int)(AMFGetDouble(duration)*1000.0);
}
// grab the data rate. we'll need this to determine a good pre-buffer.
AMFType *videoDataRate, *audioDataRate;
videoDataRate=tag->parameters->array[L"videodatarate"];
audioDataRate=tag->parameters->array[L"audiodatarate"];
if (videoDataRate || audioDataRate)
{
dataRate_audio = audioDataRate?AMFGetDouble(audioDataRate):0.0;
dataRate_video = videoDataRate?AMFGetDouble(videoDataRate):0.0;
dataRate = dataRate_audio + dataRate_video;
if (dataRate < 1.0f)
dataRate = 720.0f;
plugin.SetInfo((int)dataRate, -1, -1, 1);
prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0);
if (prebuffer > PRE_BUFFER_MAX)
prebuffer=PRE_BUFFER_MAX;
PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
}
}
flvCOM.MetadataCallback(tag);
}
free(metadatadata);
}
}
break;
}
}
else if (reader.IsEOF())
break;
else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0)
break;
}
reader.SignalKill();
if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
{
outputter.Write(0,0);
outputter.WaitWhilePlaying();
if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
}
SetEvent(killswitch);
Video_Stop();
Video_Close();
reader.Kill();
Audio_Close();
return 0;
}