#include "main.h" #include #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 #include #include #include "VideoThread.h" #include "FLVReader.h" #include "resource.h" #include "FLVCOM.h" #include #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 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(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; }