aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Input/in_flv/PlayThread.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Input/in_flv/PlayThread.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/Plugins/Input/in_flv/PlayThread.cpp')
-rw-r--r--Src/Plugins/Input/in_flv/PlayThread.cpp703
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