diff options
Diffstat (limited to 'Src/Plugins/Input/in_flv/PlayThread.cpp')
-rw-r--r-- | Src/Plugins/Input/in_flv/PlayThread.cpp | 703 |
1 files changed, 703 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_flv/PlayThread.cpp b/Src/Plugins/Input/in_flv/PlayThread.cpp new file mode 100644 index 00000000..33fa735e --- /dev/null +++ b/Src/Plugins/Input/in_flv/PlayThread.cpp @@ -0,0 +1,703 @@ +#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; +}
\ No newline at end of file |