diff options
Diffstat (limited to 'Src/Plugins/Input/in_avi')
25 files changed, 3820 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_avi/ExtendedFileInfo.cpp b/Src/Plugins/Input/in_avi/ExtendedFileInfo.cpp new file mode 100644 index 00000000..23262e66 --- /dev/null +++ b/Src/Plugins/Input/in_avi/ExtendedFileInfo.cpp @@ -0,0 +1,138 @@ +#include <bfc/platform/types.h> +#include <windows.h> +#include "api__in_avi.h" +#include "win32_avi_reader.h" +#include "../nsavi/metadata.h" +#include "../nu/ns_wc.h" +#include <strsafe.h> +#include "resource.h" + +static void ReadMetadata(nsavi::Metadata &metadata, uint32_t id, wchar_t *dest, size_t destlen) +{ + nsavi::Info *info=0; + const char *str = 0; + if (metadata.GetInfo(&info) == nsavi::READ_OK && (str = info->GetMetadata(id))) + { + MultiByteToWideCharSZ(CP_ACP/*UTF8*/, 0, str, -1, dest, (int)destlen); + } + else + dest[0]=0; +} + +extern "C" __declspec(dllexport) +int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, size_t destlen) +{ + if (!_stricmp(data, "type")) + { + dest[0]='1'; + dest[1]=0; + return 1; + } + else if (!_stricmp(data, "family")) + { + int len; + const wchar_t *p; + if (!fn || !fn[0]) return 0; + len = lstrlenW(fn); + if (len < 4 || L'.' != fn[len - 4]) return 0; + p = &fn[len - 3]; + if (!_wcsicmp(p, L"AVI") && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(IDS_FAMILY_STRING))) return 1; + return 0; + } + else + { + AVIReaderWin32 reader; + if (reader.Open(fn) == nsavi::READ_OK) + { + nsavi::Metadata metadata(&reader); + uint32_t riff_type; + metadata.GetRIFFType(&riff_type); // need to call this to get the party started + // TODO: cache metadata object + + if (!_stricmp(data, "length")) + { + int time_ms; + if (metadata.GetDuration(&time_ms) == nsavi::READ_OK) + StringCchPrintf(dest, destlen, L"%d", time_ms); + else + dest[0]=0; + } + else if (!_stricmp(data, "height")) + { + nsavi::HeaderList header_list; + if (metadata.GetHeaderList(&header_list) == nsavi::READ_OK && header_list.avi_header && header_list.avi_header->height) + StringCchPrintf(dest, destlen, L"%d", header_list.avi_header->height); + else + dest[0]=0; + } + else if (!_stricmp(data, "width")) + { + nsavi::HeaderList header_list; + if (metadata.GetHeaderList(&header_list) == nsavi::READ_OK && header_list.avi_header && header_list.avi_header->width) + StringCchPrintf(dest, destlen, L"%d", header_list.avi_header->width); + else + dest[0]=0; + } + else if (!_stricmp(data, "bitrate")) + { + int time_ms = 0; + uint64_t file_length = 0; + if (metadata.GetDuration(&time_ms) == nsavi::READ_OK + && (file_length = reader.GetContentLength()) + && time_ms > 0 && file_length > 0) + { + uint64_t bitrate = 8ULL * file_length / (uint64_t)time_ms; + StringCchPrintf(dest, destlen, L"%I64u", bitrate); + } + else + dest[0]=0; + } + else if (!_stricmp(data, "artist")) + { + ReadMetadata(metadata, nsaviFOURCC('I','A','R','T'), dest, destlen); + } + else if (!_stricmp(data, "publisher")) + { + ReadMetadata(metadata, nsaviFOURCC('I','P','U','B'), dest, destlen); + } + else if (!_stricmp(data, "album")) + { + ReadMetadata(metadata, nsaviFOURCC('I','A','L','B'), dest, destlen); + } + else if (!_stricmp(data, "composer")) + { + ReadMetadata(metadata, nsaviFOURCC('I','C','O','M'), dest, destlen); + } + else if (!_stricmp(data, "genre")) + { + ReadMetadata(metadata, nsaviFOURCC('I','G','N','R'), dest, destlen); + } + else if (!_stricmp(data, "comment")) + { + ReadMetadata(metadata, nsaviFOURCC('I','C','M','T'), dest, destlen); + } + else if (!_stricmp(data, "title")) + { + ReadMetadata(metadata, nsaviFOURCC('I','N','A','M'), dest, destlen); + } + else if (!_stricmp(data, "tool")) + { + ReadMetadata(metadata, nsaviFOURCC('I','S','F','T'), dest, destlen); + } + else if (!_stricmp(data, "copyright")) + { + ReadMetadata(metadata, nsaviFOURCC('I','C','O','P'), dest, destlen); + } + else + { + reader.Close(); + return 0; + } + + reader.Close(); + return 1; + } + } + + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/InfoDialog.cpp b/Src/Plugins/Input/in_avi/InfoDialog.cpp new file mode 100644 index 00000000..ea31e660 --- /dev/null +++ b/Src/Plugins/Input/in_avi/InfoDialog.cpp @@ -0,0 +1,390 @@ +#include "../nsavi/nsavi.h" +#include "api__in_avi.h" +#include "../nu/ListView.h" +#include "../nu/AutoWide.h" +#include "resource.h" +#include "main.h" +#include <strsafe.h> + +struct KnownField +{ + uint32_t field; + wchar_t name[256]; // TODO: change to resource ID +}; + +static KnownField known_fields[] = +{ + {nsaviFOURCC('I','S','F','T'), L"Tool"}, // IDS_FIELD_TOOL + {nsaviFOURCC('I','A','R','T'), L"Artist"}, // IDS_FIELD_ARTIST + {nsaviFOURCC('I','P','U','B'), L"Publisher"}, // IDS_FIELD_PUBLISHER + {nsaviFOURCC('I','A','L','B'), L"Album"}, // IDS_FIELD_ALBUM + {nsaviFOURCC('I','C','O','M'), L"Composer"}, // IDS_FIELD_COMPOSER + {nsaviFOURCC('I','G','N','R'), L"Genre"}, // IDS_FIELD_GENRE + {nsaviFOURCC('I','C','M','T'), L"Comment"}, // IDS_FIELD_COMMENT + {nsaviFOURCC('I','N','A','M'), L"Title"}, // IDS_FIELD_TITLE + {nsaviFOURCC('I','C','O','P'), L"Copyright"}, // IDS_FIELD_COPYRIGHT +}; + +static KnownField known_video_codecs[] = +{ + {nsaviFOURCC('V','P','6','0'), L"On2 VP6"}, + {nsaviFOURCC('V','P','6','1'), L"On2 VP6"}, + {nsaviFOURCC('V','P','6','2'), L"On2 VP6"}, + + {nsaviFOURCC('X','V','I','D'), L"MPEG-4 Part 2"}, + {nsaviFOURCC('x','v','i','d'), L"MPEG-4 Part 2"}, + + {nsaviFOURCC('d','i','v','x'), L"MPEG-4 Part 2"}, + {nsaviFOURCC('D','I','V','X'), L"MPEG-4 Part 2"}, + {nsaviFOURCC('D','X','5','0'), L"MPEG-4 Part 2"}, + + {nsaviFOURCC('m','p','4','v'), L"MPEG-4 Part 2"}, + + {nsaviFOURCC('S','E','D','G'), L"MPEG-4 Part 2"}, + + {nsaviFOURCC('H','2','6','4'), L"H.264"}, + + {nsaviFOURCC('M','J','P','G'), L"Motion JPEG"}, + + {nsaviFOURCC('t','s','c','c'), L"TechSmith"}, + + {nsaviFOURCC('c','v','i','d'), L"Cinepack"}, + + {nsaviFOURCC('M','P','G','4'), L"MS-MPEG-4 v1"}, + {nsaviFOURCC('M','P','4','1'), L"MS-MPEG-4 v1"}, + {nsaviFOURCC('M','P','4','2'), L"MS-MPEG-4 v2"}, + {nsaviFOURCC('M','P','4','3'), L"MS-MPEG-4 v3"}, + + {nsavi::video_format_rgb, L"RGB"}, + {nsavi::video_format_rle8, L"8bpp RLE"}, + {nsavi::video_format_rle4, L"4bpp RLE"}, +}; + +static KnownField known_audio_codecs[] = +{ + {nsavi::audio_format_pcm, L"Wave"}, + {nsavi::audio_format_ms_adpcm, L"Microsoft ADPCM"}, + {nsavi::audio_format_alaw, L"A-law"}, + {nsavi::audio_format_ulaw, L"μ-law"}, + {nsavi::audio_format_ima_adpcm, L"IMA ADPCM"}, + {nsavi::audio_format_truespeech, L"DSP Truespeech"}, + {nsavi::audio_format_mp2, L"MPEG Layer 2"}, + {nsavi::audio_format_mp3, L"MPEG Layer 3"}, + {nsavi::audio_format_a52, L"ATSC A/52 (AC3)"}, + {nsavi::audio_format_aac, L"AAC"}, + {nsavi::audio_format_vorbis, L"Vorbis"}, + {nsavi::audio_format_speex, L"Speex"}, + {nsavi::audio_format_extensible, L"Extensible Wave"}, + {nsavi::audio_format_dts, L"DTS"}, + +}; + +enum +{ + COLUMN_TRACK_TYPE = 0, + COLUMN_CODEC_NAME = 1, + COLUMN_CODEC_ID = 2, + COLUMN_DESCRIPTION = 3, + COLUMN_STREAM_NAME = 4, +}; + +static const wchar_t *FindKnownName(const KnownField *fields, size_t num_fields, uint32_t value) +{ + for (size_t i=0;i!=num_fields;i++) + { + if (fields[i].field == value) + { + return fields[i].name; + } + } + return 0; +} + +static void MakeStringFromFOURCC(wchar_t *str, uint32_t fourcc) +{ + const uint8_t *characters = (const uint8_t *)&(fourcc); + if (fourcc < 65536) + { + StringCchPrintfW(str, 5, L"%X", fourcc); + } + + else + { + str[0] = characters[0]; + str[1] = characters[1]; + str[2] = characters[2]; + str[3] = characters[3]; + str[4] = 0; + } +} + + +static INT_PTR CALLBACK InfoDialog_Metadata(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + nsavi::Metadata *metadata = (nsavi::Metadata *)lParam; + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam); + + W_ListView list_view(hwndDlg, IDC_TRACKLIST); + + list_view.AddCol(WASABI_API_LNGSTRINGW(IDS_COLUMN_FIELD), 100); + list_view.AddCol(WASABI_API_LNGSTRINGW(IDS_COLUMN_FOURCC), 75); + list_view.AddCol(WASABI_API_LNGSTRINGW(IDS_COLUMN_VALUE), 250); + + nsavi::Info *info; + if (metadata->GetInfo(&info) == nsavi::READ_OK) + { + int n=0; + for (nsavi::Info::const_iterator itr = info->begin();itr!=info->end();itr++) + { + const wchar_t *field_name = FindKnownName(known_fields, sizeof(known_fields)/sizeof(known_fields[0]), itr->first); + + wchar_t fourcc[5] = {0}; + MakeStringFromFOURCC(fourcc, itr->first); + + if (field_name) + n= list_view.AppendItem(field_name, 0); + else + n= list_view.AppendItem(fourcc, 0); + + list_view.SetItemText(n, 1, fourcc); + list_view.SetItemText(n, 2, AutoWide(itr->second, CP_ACP/*UTF8*/)); + } + } + } + return 1; + case WM_SIZE: + { + RECT r; + GetClientRect(hwndDlg, &r); + SetWindowPos(GetDlgItem(hwndDlg, IDC_TRACKLIST), HWND_TOP, r.left, r.top, r.right, r.bottom, SWP_NOACTIVATE); + } + break; + } + return 0; +} + +void GetVideoCodecName(wchar_t *str, size_t str_cch, nsavi::STRF *stream_format) +{ + nsavi::video_format *format = (nsavi::video_format *)stream_format; + const wchar_t *codec_name = FindKnownName(known_video_codecs, sizeof(known_video_codecs)/sizeof(known_video_codecs[0]), format->compression); + if (codec_name) + StringCchCopy(str, str_cch, codec_name); + else + MakeStringFromFOURCC(str, format->compression); +} + +void GetVideoCodecDescription(wchar_t *str, size_t str_cch, nsavi::STRF *stream_format) +{ + nsavi::video_format *format = (nsavi::video_format *)stream_format; + StringCchPrintf(str, str_cch, L"%ux%u", format->width, format->height); +} + +void GetAudioCodecName(wchar_t *str, size_t str_cch, nsavi::STRF *stream_format) +{ + nsavi::audio_format *format = (nsavi::audio_format *)stream_format; + const wchar_t *codec_name = FindKnownName(known_audio_codecs, sizeof(known_audio_codecs)/sizeof(known_audio_codecs[0]), format->format); + if (codec_name) + StringCchCopy(str, str_cch, codec_name); + else + MakeStringFromFOURCC(str, format->format); +} + +void GetAudioCodecDescription(wchar_t *str, size_t str_cch, nsavi::STRF *stream_format) +{ + nsavi::audio_format *format = (nsavi::audio_format *)stream_format; + if (format->average_bytes_per_second) + { + StringCchPrintf(str, str_cch, L"%u %s", format->average_bytes_per_second / 125UL, WASABI_API_LNGSTRINGW(IDS_KBPS)); + } + else + str[0]=0; +} + +static INT_PTR CALLBACK InfoDialog_Tracks(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + nsavi::Metadata *metadata = (nsavi::Metadata *)lParam; + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam); + + W_ListView list_view(hwndDlg, IDC_TRACKLIST); + + list_view.AddCol(WASABI_API_LNGSTRINGW(IDS_COLUMN_TRACK_TYPE), 100); + list_view.AddCol(WASABI_API_LNGSTRINGW(IDS_COLUMN_CODEC_NAME), 100); + list_view.AddCol(WASABI_API_LNGSTRINGW(IDS_COLUMN_CODEC_ID), 75); + list_view.AddCol(WASABI_API_LNGSTRINGW(IDS_COLUMN_DESCRIPTION), 100); + list_view.AddCol(WASABI_API_LNGSTRINGW(IDS_COLUMN_STREAM_NAME), 100); + + nsavi::HeaderList header_list; + if (metadata->GetHeaderList(&header_list) == nsavi::READ_OK) + { + for (size_t i=0;i!=header_list.stream_list_size;i++) + { + int n; + const nsavi::STRL &stream = header_list.stream_list[i]; + switch(stream.stream_header->stream_type) + { + case nsavi::stream_type_audio: + { + n = list_view.AppendItem(WASABI_API_LNGSTRINGW(IDS_TYPE_AUDIO), 0); + nsavi::audio_format *format = (nsavi::audio_format *)stream.stream_format; + wchar_t codec_id[5] = {0}; + MakeStringFromFOURCC(codec_id, format->format); + list_view.SetItemText(n, COLUMN_CODEC_ID, codec_id); + const wchar_t *codec_name = FindKnownName(known_audio_codecs, sizeof(known_audio_codecs)/sizeof(known_audio_codecs[0]), format->format); + if (codec_name) + list_view.SetItemText(n, COLUMN_CODEC_NAME, codec_name); + else + list_view.SetItemText(n, COLUMN_CODEC_NAME, codec_id); + + wchar_t description[256] = {0}; + GetAudioCodecDescription(description, 256, stream.stream_format); + list_view.SetItemText(n, COLUMN_DESCRIPTION, description); + } + break; + case nsavi::stream_type_video: + { + n = list_view.AppendItem(WASABI_API_LNGSTRINGW(IDS_TYPE_VIDEO), 0); + nsavi::video_format *format = (nsavi::video_format *)stream.stream_format; + wchar_t fourcc[5] = {0}; + MakeStringFromFOURCC(fourcc, format->compression); + list_view.SetItemText(n, COLUMN_CODEC_ID, fourcc); + const wchar_t *codec_name = FindKnownName(known_video_codecs, sizeof(known_video_codecs)/sizeof(known_video_codecs[0]), format->compression); + if (codec_name) + list_view.SetItemText(n, COLUMN_CODEC_NAME, codec_name); + else + list_view.SetItemText(n, COLUMN_CODEC_NAME, fourcc); + wchar_t description[256] = {0}; + GetVideoCodecDescription(description, 256, stream.stream_format); + list_view.SetItemText(n, COLUMN_DESCRIPTION, description); + } + break; + default: + { + wchar_t fourcc[5] = {0}; + MakeStringFromFOURCC(fourcc, stream.stream_header->stream_type); + n = list_view.AppendItem(fourcc, 0); + } + break; + } + if (stream.stream_name) + { + //const char *name = (const char *) (((const uint8_t *)stream.stream_name) + 4); + // TODO: need AutoWideN before this is safe + // list_view.SetItemText(n, COLUMN_STREAM_NAME, AutoWide(name, CP_UTF8)); + } + } + } + } + return 1; + case WM_SIZE: + { + RECT r; + GetClientRect(hwndDlg, &r); + SetWindowPos(GetDlgItem(hwndDlg, IDC_TRACKLIST), HWND_TOP, r.left, r.top, r.right, r.bottom, SWP_NOACTIVATE); + } + break; + } + return 0; +} + + +struct InfoDialogContext +{ + nsavi::Metadata *metadata; + HWND active_tab; +}; + +static VOID WINAPI OnSelChanged(HWND hwndDlg, HWND hwndTab, InfoDialogContext *context) +{ + if (context->active_tab) + { + DestroyWindow(context->active_tab); + } + int selection = TabCtrl_GetCurSel(hwndTab); + switch(selection) + { + case 0: + context->active_tab = WASABI_API_CREATEDIALOGPARAMW(IDD_TRACKS, hwndDlg, InfoDialog_Metadata, (LPARAM)context->metadata); + break; + case 1: + context->active_tab = WASABI_API_CREATEDIALOGPARAMW(IDD_TRACKS, hwndDlg, InfoDialog_Tracks, (LPARAM)context->metadata); + break; + } + + RECT r; + GetWindowRect(hwndTab,&r); + TabCtrl_AdjustRect(hwndTab,FALSE,&r); + MapWindowPoints(NULL,hwndDlg,(LPPOINT)&r,2); + + SetWindowPos(context->active_tab,HWND_TOP,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOACTIVATE); + ShowWindow(context->active_tab, SW_SHOWNA); + + if (GetFocus() != hwndTab) + { + SetFocus(context->active_tab); + } +} + +INT_PTR CALLBACK InfoDialog(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND hwndTab = GetDlgItem(hwndDlg,IDC_TAB1); + InfoDialogContext *context = (InfoDialogContext *)calloc(1, sizeof(InfoDialogContext)); + context->metadata = (nsavi::Metadata *)lParam; + context->active_tab = 0; + SetWindowLongPtr(hwndDlg,GWLP_USERDATA, (LPARAM)context); + TCITEMW tie = {0}; + tie.mask = TCIF_TEXT; + tie.pszText = WASABI_API_LNGSTRINGW(IDS_TAB_METADATA); + SendMessageW(hwndTab, TCM_INSERTITEMW, 0, (LPARAM)&tie); + tie.pszText = WASABI_API_LNGSTRINGW(IDS_TAB_TRACKS); + SendMessageW(hwndTab, TCM_INSERTITEMW, 1, (LPARAM)&tie); + OnSelChanged(hwndDlg, hwndTab, context); + } + return 1; + + case WM_DESTROY: + { + InfoDialogContext *context = (InfoDialogContext *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + free(context); + } + break; + case WM_NOTIFY: + { + LPNMHDR lpn = (LPNMHDR) lParam; + if (lpn && lpn->code==TCN_SELCHANGE) + { + InfoDialogContext *context = (InfoDialogContext *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + OnSelChanged(hwndDlg,GetDlgItem(hwndDlg,IDC_TAB1),context); + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + EndDialog(hwndDlg,0); + } + break; + case IDCANCEL: + { + EndDialog(hwndDlg,1); + } + break; + } + break; + } + + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/PlayThread.cpp b/Src/Plugins/Input/in_avi/PlayThread.cpp new file mode 100644 index 00000000..8ed3b658 --- /dev/null +++ b/Src/Plugins/Input/in_avi/PlayThread.cpp @@ -0,0 +1,964 @@ +#include "main.h" +#include "api__in_avi.h" +#include "../nsavi/nsavi.h" +#include "interfaces.h" +#include "../nu/AudioOutput.h" +#include "../Winamp/wa_ipc.h" +#include <api/service/waservicefactory.h> +#include "VideoThread.h" +#include "win32_avi_reader.h" +#include "http_avi_reader.h" +#include "StreamSelector.h" +#include <shlwapi.h> +#include <strsafe.h> +#include <map> + +nsavi::HeaderList header_list; +int video_stream_num, audio_stream_num; +ifc_avivideodecoder *video_decoder=0; +IVideoOutput *video_output=0; +HANDLE audio_break=0, audio_resume=0, audio_break_done=0; +static Streams streams; +static bool checked_in_dshow=false; +extern int GetOutputTime(); + +class StatsFOURCC +{ +public: + uint32_t GetAudioStat() + { + uint32_t fourcc=0; + uint32_t max=0; + for (Stats::iterator itr = audio_types.begin();itr!=audio_types.end();itr++) + { + if (itr->second > max) + { + max = itr->second; + fourcc = itr->first; + } + } + return fourcc; + } + + uint32_t GetVideoStat() + { + uint32_t fourcc=0; + uint32_t max=0; + for (Stats::iterator itr = video_fourccs.begin();itr!=video_fourccs.end();itr++) + { + if (itr->second > max) + { + max = itr->second; + fourcc = itr->first; + } + } + return fourcc; + } + + typedef std::map<uint32_t, uint32_t> Stats; + Stats audio_types; + Stats video_fourccs; +}; + +static StatsFOURCC stats; +class AVIWait +{ +public: + int WaitOrAbort(int time_in_ms) + { + HANDLE events[] = {killswitch, seek_event}; + int ret = WaitForMultipleObjects(2, events, FALSE, time_in_ms); + if (ret == WAIT_TIMEOUT) + return 0; + else if (ret == WAIT_OBJECT_0) + return 1; + else if (ret == WAIT_OBJECT_0+1) + return 2; + + return -1; + } +}; + +static bool audio_opened=false; +static ifc_aviaudiodecoder *audio_decoder=0; +static char audio_output[65536]; +static nu::AudioOutput<AVIWait> out(&plugin); + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } +}; + +static int GetStreamNumber( uint32_t id ) +{ + char *stream_data = (char *)( &id ); + if ( !isxdigit( stream_data[ 0 ] ) || !isxdigit( stream_data[ 1 ] ) ) + return -1; + + stream_data[ 2 ] = 0; + int stream_number = strtoul( stream_data, 0, 16 ); + + return stream_number; +} + +static ifc_aviaudiodecoder *FindAudioDecoder( const nsavi::AVIH *avi_header, const nsavi::STRL &stream ) +{ + unsigned int bits_per_sample = (unsigned int)AGAVE_API_CONFIG->GetUnsigned( playbackConfigGroupGUID, L"bits", 16 ); + if ( bits_per_sample >= 24 ) bits_per_sample = 24; + else bits_per_sample = 16; + + unsigned int max_channels; + // get max channels + if ( AGAVE_API_CONFIG->GetBool( playbackConfigGroupGUID, L"surround", true ) ) + max_channels = 6; + else if ( AGAVE_API_CONFIG->GetBool( playbackConfigGroupGUID, L"mono", false ) ) + max_channels = 1; + else + max_channels = 2; + + size_t n = 0; + waServiceFactory *sf = 0; + while ( sf = plugin.service->service_enumService( WaSvc::AVIDECODER, n++ ) ) + { + svc_avidecoder *dec = static_cast<svc_avidecoder *>( sf->getInterface() ); + if ( dec ) + { + ifc_aviaudiodecoder *decoder = 0; + if ( dec->CreateAudioDecoder( avi_header, stream.stream_header, stream.stream_format, stream.stream_data, + bits_per_sample, max_channels, false, + &decoder ) == svc_avidecoder::CREATEDECODER_SUCCESS ) + { + sf->releaseInterface( dec ); + return decoder; + } + + sf->releaseInterface( dec ); + } + } + + return 0; +} + + +static ifc_avivideodecoder *FindVideoDecoder(const nsavi::AVIH *avi_header, const nsavi::STRL &stream) +{ + size_t n = 0; + waServiceFactory *sf = 0; + while (sf = plugin.service->service_enumService(WaSvc::AVIDECODER, n++)) + { + svc_avidecoder *dec = static_cast<svc_avidecoder *>(sf->getInterface()); + if (dec) + { + ifc_avivideodecoder *decoder=0; + if (dec->CreateVideoDecoder(avi_header, stream.stream_header, stream.stream_format, stream.stream_data, &decoder) == svc_avidecoder::CREATEDECODER_SUCCESS) + { + sf->releaseInterface(dec); + return decoder; + } + + sf->releaseInterface(dec); + } + } + + return 0; +} + + +static bool OnAudio( uint16_t type, const void **input_buffer, uint32_t *input_buffer_bytes ) +{ + uint32_t output_len = sizeof( audio_output ); + int ret = audio_decoder->DecodeChunk( type, input_buffer, input_buffer_bytes, audio_output, &output_len ); + //if (*input_buffer_bytes != 0) + //DebugBreak(); + if ( ( ret == ifc_aviaudiodecoder::AVI_SUCCESS || ret == ifc_aviaudiodecoder::AVI_NEED_MORE_INPUT ) && output_len ) + { + if ( !audio_opened ) + { + unsigned int sample_rate, channels, bps; + bool is_float; + if ( audio_decoder->GetOutputProperties( &sample_rate, &channels, &bps, &is_float ) == ifc_aviaudiodecoder::AVI_SUCCESS ) + { + audio_opened = out.Open( 0, channels, sample_rate, bps ); + if ( !audio_opened ) + return false; + } + else + { + // TODO: buffer audio. can nu::AudioOutput handle this for us? + } + } + + if ( audio_opened ) + out.Write( audio_output, output_len ); + } + + return true; +} + +static bool CheckDSHOW() +{ + if (!checked_in_dshow) + { + LPCWSTR pluginsDir = (LPCWSTR)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW); + wchar_t in_dshow_path[MAX_PATH] = {0}; + PathCombine(in_dshow_path, pluginsDir, L"in_dshow.dll"); + in_dshow = LoadLibrary(in_dshow_path); + checked_in_dshow = true; + } + + return !!in_dshow; +} + +static void CALLBACK DSHOWAPC( ULONG_PTR param ) +{ + In_Module *dshow_mod_local = 0; + wchar_t *playFile = (wchar_t *)param; + + if ( in_dshow ) + { + typedef In_Module *( *MODULEGETTER )( ); + + MODULEGETTER moduleGetter = (MODULEGETTER)GetProcAddress( in_dshow, "winampGetInModule2" ); + if ( moduleGetter ) + dshow_mod_local = moduleGetter(); + } + + if ( dshow_mod_local ) + { + dshow_mod_local->outMod = plugin.outMod; + if ( dshow_mod_local->Play( playFile ) ) + dshow_mod_local = 0; + } + + free( playFile ); + + if ( !dshow_mod_local ) + { + if ( WaitForSingleObject( killswitch, 200 ) != WAIT_OBJECT_0 ) + PostMessage( plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0 ); + } + else + dshow_mod = dshow_mod_local; +} + +/* --- Video Window text info --- */ +void SetVideoInfoText() +{ + wchar_t audio_name[128] = {0}, audio_properties[256] = {0}; + wchar_t video_name[128] = {0}, video_properties[256] = {0}; + wchar_t video_info[512] = {0}; + if (audio_decoder && video_decoder) + { + GetAudioCodecName(audio_name, sizeof(audio_name)/sizeof(*audio_name), header_list.stream_list[audio_stream_num].stream_format); + GetAudioCodecDescription(audio_properties, sizeof(audio_properties)/sizeof(*audio_properties), header_list.stream_list[audio_stream_num].stream_format); + GetVideoCodecName(video_name, sizeof(video_name)/sizeof(*video_name), header_list.stream_list[video_stream_num].stream_format); + GetVideoCodecDescription(video_properties, sizeof(video_properties)/sizeof(*video_properties), header_list.stream_list[video_stream_num].stream_format); + StringCbPrintf(video_info, sizeof(video_info), L"AVI: %s (%s), %s (%s)", audio_name, audio_properties, video_name, video_properties); + video_output->extended(VIDUSER_SET_INFOSTRINGW,(INT_PTR)video_info,0); + } + else if (audio_decoder) + { + GetAudioCodecName(audio_name, sizeof(audio_name)/sizeof(*audio_name), header_list.stream_list[audio_stream_num].stream_format); + GetAudioCodecDescription(audio_properties, sizeof(audio_properties)/sizeof(*audio_properties), header_list.stream_list[audio_stream_num].stream_format); + StringCbPrintf(video_info, sizeof(video_info), L"AVI: %s (%s)", audio_name, audio_properties); + video_output->extended(VIDUSER_SET_INFOSTRINGW,(INT_PTR)video_info,0); + } + else if (video_decoder) + { + GetVideoCodecName(video_name, sizeof(video_name)/sizeof(*video_name), header_list.stream_list[video_stream_num].stream_format); + GetVideoCodecDescription(video_properties, sizeof(video_properties)/sizeof(*video_properties), header_list.stream_list[video_stream_num].stream_format); + StringCbPrintf(video_info, sizeof(video_info), L"AVI: %s (%s)", video_name, video_properties); + video_output->extended(VIDUSER_SET_INFOSTRINGW,(INT_PTR)video_info,0); + } +} +void Streams::Reset() +{ + num_audio_streams = 0; + num_video_streams = 0; + + current_audio_stream = 0; +} + +void Streams::AddAudioStream(int stream_num) +{ + audio_streams[num_audio_streams++]=stream_num; +} + +void Streams::AddVideoStream(int stream_num) +{ + video_streams[num_video_streams++]=stream_num; +} + +void Streams::SetAudioStream(int stream_num) +{ + for (int i=0;i<num_audio_streams;i++) + { + if (audio_streams[i] == stream_num) + current_audio_stream=i; + } +} + +void Streams::SetVideoStream(int stream_num) +{ + for (int i=0;i<num_video_streams;i++) + { + if (video_streams[i] == stream_num) + current_video_stream=i; + } +} + +int Streams::getNumAudioTracks() +{ + return num_audio_streams; +} + +void Streams::enumAudioTrackName(int n, char *buf, int size) +{ + StringCchPrintfA(buf, size, "Audio Stream %d", n); +} + +int Streams::getCurAudioTrack() +{ + return current_audio_stream; +} + +int Streams::getNumVideoTracks() +{ + return num_video_streams; +} + +void Streams::enumVideoTrackName(int n, char *buf, int size) +{ + StringCchPrintfA(buf, size, "Video Stream %d", n); +} + +int Streams::getCurVideoTrack() +{ + return current_video_stream; +} + +void Streams::setAudioTrack(int n) +{ + SetEvent(audio_break); + WaitForSingleObject(audio_break_done, INFINITE); + + int i = audio_streams[n]; + const nsavi::STRL &stream = header_list.stream_list[i]; + if (audio_decoder) + { + audio_decoder->Close(); + audio_decoder=0; + } + + audio_decoder = FindAudioDecoder(header_list.avi_header, stream); + if (audio_decoder) + { + current_audio_stream = n; + audio_stream_num = i; + video_only=0; // TODO! need to do more to get this to work if we are switching FROM video_only + } + else + { + video_only; // TODO! need to do more to get this to work here + } + + SetEvent(audio_resume); + WaitForSingleObject(audio_break_done, INFINITE); + + SetVideoInfoText(); +} + +void Streams::setVideoTrack(int n) +{ + // TODO: need to VideoBreak, destroy decoder, create new one and update video_stream_num +} + + +bool SingleReaderLoop(nsavi::Demuxer &demuxer, nsavi::avi_reader *reader, nsavi::SeekTable *&audio_seek_table, nsavi::SeekTable *&video_seek_table) +{ + const void *input_buffer = 0; + uint16_t type = 0; + uint32_t input_buffer_bytes = 0; + bool idx1_searched=false; + + HANDLE events[] = { killswitch, seek_event, audio_break, audio_resume }; + void *data; + uint32_t data_size; + uint32_t data_type; + int waitTime = 0; + for (;;) + { + int ret = WaitForMultipleObjects(4, events, FALSE, waitTime); + if (ret == WAIT_OBJECT_0) + { + break; + } + else if (ret == WAIT_OBJECT_0+1) + { + volatile LONG _this_seek_position; + do + { + InterlockedExchange(&_this_seek_position, seek_position); + if (_this_seek_position != -1) + { + int this_seek_position = _this_seek_position; + ResetEvent(seek_event); // reset this first so nothing aborts on it + if (!idx1_searched) + { + nsavi::IDX1 *index; + ret = demuxer.GetSeekTable(&index); + if (ret == nsavi::READ_OK) + { + if (video_seek_table) + video_seek_table->AddIndex(index); + if (audio_seek_table) + audio_seek_table->AddIndex(index); + } + idx1_searched=true; + } + + uint64_t index_position, start_time; + + while (video_seek_table && video_seek_table->GetIndexLocation(this_seek_position, &index_position, &start_time)) + { + nsavi::INDX *next_index=0; + if (demuxer.GetIndexChunk(&next_index, index_position) == 0) + { + video_seek_table->AddIndex(next_index, start_time); // seek table takes ownership + free(next_index); + } + } + + while (audio_seek_table && audio_seek_table->GetIndexLocation(this_seek_position, &index_position, &start_time)) + { + nsavi::INDX *next_index=0; + if (demuxer.GetIndexChunk(&next_index, index_position) == 0) + { + audio_seek_table->AddIndex(next_index, start_time); // seek table takes ownership + free(next_index); + } + } + + if (video_seek_table) + { + int curr_time = GetOutputTime(); + int direction = (curr_time < this_seek_position)?nsavi::SeekTable::SEEK_FORWARD:nsavi::SeekTable::SEEK_BACKWARD; + const nsavi::SeekEntry *video_seek_entry=video_seek_table->GetSeekPoint(this_seek_position, curr_time, direction); + if (video_seek_entry) + { + Video_Break(); + if (video_only) + { + demuxer.Seek(video_seek_entry->file_position, video_seek_entry->absolute, reader); + video_clock.Seek(this_seek_position); + } + else if (audio_seek_table) + { + const nsavi::SeekEntry *audio_seek_entry=audio_seek_table->GetSeekPoint(this_seek_position); + if (audio_seek_entry) + { + if (audio_seek_entry->file_position < video_seek_entry->file_position) + demuxer.Seek(audio_seek_entry->file_position, audio_seek_entry->absolute, reader); + else + demuxer.Seek(video_seek_entry->file_position, video_seek_entry->absolute, reader); + audio_decoder->Flush(); + out.Flush(this_seek_position); + } + } + video_total_time = video_seek_entry->stream_time; + Video_Flush(); + } + } + else if (audio_seek_table) + { + int curr_time = GetOutputTime(); + int direction = (curr_time < this_seek_position)?nsavi::SeekTable::SEEK_FORWARD:nsavi::SeekTable::SEEK_BACKWARD; + const nsavi::SeekEntry *audio_seek_entry=audio_seek_table->GetSeekPoint(this_seek_position, curr_time, direction); + if (audio_seek_entry) + { + demuxer.Seek(audio_seek_entry->file_position, audio_seek_entry->absolute, reader); + audio_decoder->Flush(); + out.Flush(this_seek_position); + } + } + } + } while (InterlockedCompareExchange(&seek_position, -1, _this_seek_position) != _this_seek_position); // loop again if seek point changed + } + else if (ret == WAIT_OBJECT_0+2) + { // audio break + ResetEvent(audio_break); + SetEvent(audio_break_done); + waitTime = INFINITE; + continue; + } + else if (ret == WAIT_OBJECT_0+3) + { // audio resume + ResetEvent(audio_resume); + SetEvent(audio_break_done); + waitTime = 0; + continue; + } + else if (ret != WAIT_TIMEOUT) + { + break; + } + + if (input_buffer_bytes) // TODO: read ahead in situation where there is one giant audio chunk for the entire movie + { + if (!OnAudio(type, &input_buffer, &input_buffer_bytes)) + { + return false; + } + if (input_buffer_bytes == 0) + { + free(data); + data = NULL; + } + } + else + { + ret = demuxer.GetNextMovieChunk(reader, &data, &data_size, &data_type); + if (ret != nsavi::READ_OK) + { + break; + } + + int stream_number = GetStreamNumber(data_type); + type = (data_type>>16); + if (stream_number == audio_stream_num) + { + input_buffer = (const void *)data; + input_buffer_bytes = data_size; + if (!OnAudio(type, &input_buffer, &input_buffer_bytes)) + { + return false; + } + if (input_buffer_bytes == 0) + { + free(data); + data = NULL; + } + } + else if (stream_number == video_stream_num) + { + OnVideo(type, data, data_size); + data = NULL; + } + else + { + free(data); + data = NULL; + } + } + } + return true; +} + +bool MultiReaderLoop(nsavi::Demuxer &demuxer, nsavi::avi_reader *reader, nsavi::avi_reader *video_reader, nsavi::SeekTable *&audio_seek_table, nsavi::SeekTable *&video_seek_table) +{ + demuxer.SeekToMovieChunk(video_reader); + + CreateVideoReaderThread(&demuxer, video_reader); + + const void *input_buffer = 0; + uint16_t type = 0; + uint32_t input_buffer_bytes = 0; + bool idx1_searched=false; + + HANDLE events[] = { killswitch, seek_event, audio_break, audio_resume}; + void *data; + uint32_t data_size; + uint32_t data_type; + int waitTime=0; + for (;;) + { + int ret = WaitForMultipleObjects(4, events, FALSE, waitTime); + if (ret == WAIT_OBJECT_0) + { + break; + } + else if (ret == WAIT_OBJECT_0+1) + { + volatile LONG _this_seek_position; + do + { + InterlockedExchange(&_this_seek_position, seek_position); + if (_this_seek_position != -1) + { + int this_seek_position = _this_seek_position; + ResetEvent(seek_event); // reset this first so nothing aborts on it + if (!idx1_searched) + { + nsavi::IDX1 *index; + ret = demuxer.GetSeekTable(&index); + if (ret == nsavi::READ_OK) + { + video_seek_table->AddIndex(index); + audio_seek_table->AddIndex(index); + } + idx1_searched=true; + } + + uint64_t index_position, start_time; + while (video_seek_table->GetIndexLocation(this_seek_position, &index_position, &start_time)) + { + nsavi::INDX *next_index=0; + if (demuxer.GetIndexChunk(&next_index, index_position) == 0) + { + video_seek_table->AddIndex(next_index, start_time); // seek table takes ownership + free(next_index); + } + } + + while (audio_seek_table->GetIndexLocation(this_seek_position, &index_position, &start_time)) + { + nsavi::INDX *next_index=0; + if (demuxer.GetIndexChunk(&next_index, index_position) == 0) + { + audio_seek_table->AddIndex(next_index, start_time); // seek table takes ownership + free(next_index); + } + } + + int curr_time = GetOutputTime(); + int direction = (curr_time < this_seek_position)?nsavi::SeekTable::SEEK_FORWARD:nsavi::SeekTable::SEEK_BACKWARD; + const nsavi::SeekEntry *video_seek_entry=video_seek_table->GetSeekPoint(this_seek_position, curr_time, direction); + if (video_seek_entry) + { + Video_Break(); + demuxer.Seek(video_seek_entry->file_position, video_seek_entry->absolute, video_reader); + const nsavi::SeekEntry *audio_seek_entry=audio_seek_table->GetSeekPoint(this_seek_position); + if (audio_seek_entry) + { + demuxer.Seek(audio_seek_entry->file_position, audio_seek_entry->absolute, reader); + audio_decoder->Flush(); + out.Flush(this_seek_position); + } + video_total_time = video_seek_entry->stream_time; + Video_Flush(); + } + } + } while (InterlockedCompareExchange(&seek_position, -1, _this_seek_position) != _this_seek_position); // loop again if seek point changed + } + else if (ret == WAIT_OBJECT_0+2) + { // audio break + ResetEvent(audio_break); + SetEvent(audio_break_done); + waitTime = INFINITE; + continue; + } + else if (ret == WAIT_OBJECT_0+3) + { // audio resume + ResetEvent(audio_resume); + SetEvent(audio_break_done); + waitTime = 0; + continue; + } + else if (ret != WAIT_TIMEOUT) + { + break; + } + + if (input_buffer_bytes) // TODO: read ahead in situation where there is one giant audio chunk for the entire movie + { + if (!OnAudio(type, &input_buffer, &input_buffer_bytes)) + { + return false; + } + if (input_buffer_bytes == 0) + { + free(data); + data = NULL; + } + } + else + { + ret = demuxer.GetNextMovieChunk(reader, &data, &data_size, &data_type, audio_stream_num); + if (ret != nsavi::READ_OK) + { + break; + } + + int stream_number = GetStreamNumber(data_type); + type = (data_type>>16); + + if (stream_number == audio_stream_num && type != 0x7869) // ignore 'ix' + { + input_buffer = (const void *)data; + input_buffer_bytes = data_size; + if (!OnAudio(type, &input_buffer, &input_buffer_bytes)) + { + return false; + } + + if (input_buffer_bytes == 0) + { + free(data); + data = NULL; + } + } + else + { + free(data); + data = NULL; + } + } + } + + return true; +} + +void PlayLoop(nsavi::avi_reader *reader, bool multiple_readers) +{ + AVIReaderWin32 video_reader; + uint32_t riff_type; + + audio_decoder=0; + video_decoder=0; + nsavi::SeekTable *video_seek_table = 0, *audio_seek_table = 0; + nsavi::Demuxer demuxer(reader); + audio_opened=false; + int audio_bitrate=0; + streams.Reset(); + + out.Init(plugin.outMod); + if (!video_output) + video_output = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT); + audio_stream_num = 65536; + video_stream_num=65536; // purposefully too big value + Video_Init(); + + if (demuxer.GetRIFFType(&riff_type) == nsavi::READ_OK) + { + bool audio_no_decoder=false; + bool video_no_decoder=false; + if (demuxer.GetHeaderList(&header_list) == nsavi::READ_OK) + { + // find available codecs + for (uint32_t i=0;i!=header_list.stream_list_size;i++) + { + const nsavi::STRL &stream = header_list.stream_list[i]; + if (stream.stream_header) + { + if (stream.stream_header->stream_type == nsavi::stream_type_audio) + { + nsavi::audio_format *f = (nsavi::audio_format *)stream.stream_format; + if (f) + { + stats.audio_types[f->format]++; + + streams.AddAudioStream(i); + if (!audio_decoder) + { // TODO: check priority + audio_decoder = FindAudioDecoder(header_list.avi_header, stream); + if (audio_decoder) + { + streams.SetAudioStream(i); + audio_stream_num = i; + video_only=0; + } + else + audio_no_decoder = true; + + if (stream.stream_header->length && !stream.stream_header->sample_size && stream.stream_header->rate) + g_duration = (uint64_t)stream.stream_header->length * (uint64_t)stream.stream_header->scale * 1000ULL / (uint64_t)stream.stream_header->rate; + audio_bitrate = MulDiv(f->average_bytes_per_second, 8, 1000); + plugin.SetInfo(audio_bitrate, -1, -1, -1); + } + } + } + else if (stream.stream_header->stream_type == nsavi::stream_type_video) + { + nsavi::video_format *f = (nsavi::video_format *)stream.stream_format; + if (f) + { + stats.video_fourccs[f->compression]++; + + streams.AddVideoStream(i); + if (!video_decoder) + { // TODO: check priority + video_decoder = FindVideoDecoder(header_list.avi_header, stream); + if (video_decoder) + { + video_stream_num = i; + streams.SetVideoStream(i); + } + else + video_no_decoder = true; + if (g_duration == -1 && stream.stream_header->rate) + g_duration = (uint64_t)stream.stream_header->length * (uint64_t)stream.stream_header->scale * 1000ULL / (uint64_t)stream.stream_header->rate; + } + } + } + } + } + } + + if (AGAVE_API_STATS) + { + uint32_t audio_format = stats.GetAudioStat(); + uint32_t video_format = stats.GetVideoStat(); + AGAVE_API_STATS->SetStat(api_stats::AVI_AUDIO_FORMAT, audio_format); + AGAVE_API_STATS->SetStat(api_stats::AVI_VIDEO_FOURCC, video_format); + } + + if ((audio_no_decoder || video_no_decoder) && CheckDSHOW()) + { + // use in_dshow to play this one + HANDLE mainThread = WASABI_API_APP->main_getMainThreadHandle(); + if (mainThread) + { + Video_Stop(); + if (audio_decoder) + { + audio_decoder->Close(); + audio_decoder=0; + } + + Video_Close(); + delete video_seek_table; + delete audio_seek_table; + wchar_t *fn = (wchar_t *)calloc(1024, sizeof(wchar_t *)); + reader->GetFilename(fn, 1024); + QueueUserAPC(DSHOWAPC, mainThread, (ULONG_PTR)fn); + CloseHandle(mainThread); + return ; + } + } + + if (!audio_decoder && !video_decoder) + { + goto btfo; + } + + if (!audio_decoder) + { + video_only=1; + video_clock.Start(); + } + } + + else + { + goto btfo; + } + SetVideoInfoText(); + + + video_output->extended(VIDUSER_SET_TRACKSELINTERFACE, (INT_PTR)&streams, 0); + + if (video_stream_num != 65536) + video_seek_table = new nsavi::SeekTable(video_stream_num, !!video_decoder, &header_list); + if (audio_stream_num != 65536) + audio_seek_table = new nsavi::SeekTable(audio_stream_num, false, &header_list); + + uint64_t content_length = reader->GetContentLength(); + if (content_length && g_duration) + { + int total_bitrate = (int)(8ULL * content_length / (uint64_t)g_duration); + plugin.SetInfo(total_bitrate, -1, -1, -1); + } + else if (header_list.avi_header->max_bytes_per_second) + { + int total_bitrate = MulDiv(header_list.avi_header->max_bytes_per_second, 8, 1000); + plugin.SetInfo(total_bitrate, -1, -1, -1); + } + else + { + // use seek table for bitrate? + } + + if (demuxer.FindMovieChunk() != nsavi::READ_OK) + { + goto btfo; + } + + if (multiple_readers && video_decoder && !video_only) + { + wchar_t fn[MAX_PATH] = {0}; + reader->GetFilename(fn, MAX_PATH); + if (video_reader.Open(fn) == nsavi::READ_OK) + { + MultiReaderLoop(demuxer, reader, &video_reader, audio_seek_table, video_seek_table); + } + else + SingleReaderLoop(demuxer, reader, audio_seek_table, video_seek_table); + } + else + SingleReaderLoop(demuxer, reader, audio_seek_table, video_seek_table); + + if (audio_opened && WaitForSingleObject(killswitch, 0) == WAIT_TIMEOUT) + { + out.Write(0, 0); + out.WaitWhilePlaying(); + } +btfo: + if (WaitForSingleObject(killswitch, 0) == WAIT_TIMEOUT) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + Video_Stop(); + if (audio_decoder) + { + audio_decoder->Close(); + audio_decoder=0; + if (audio_opened) + out.Close(); + } + + Video_Close(); + video_reader.Close(); + delete video_seek_table; + delete audio_seek_table; +} + +DWORD CALLBACK AVIPlayThread(LPVOID param) +{ + if (!audio_break) + audio_break = CreateEvent(0, TRUE, FALSE, 0); + + if (!audio_resume) + audio_resume = CreateEvent(0, TRUE, FALSE, 0); + + if (!audio_break_done) + audio_break_done = CreateEvent(0, FALSE, FALSE, 0); + + wchar_t *filename = (wchar_t *)param; + if (PathIsURLW(filename)) + { + AVIReaderHTTP reader(killswitch, seek_event); + if (reader.Open(filename) != nsavi::READ_OK || reader.Connect() != nsavi::READ_OK) + { + if (WaitForSingleObject(killswitch, 200) == WAIT_TIMEOUT) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + else + { + PlayLoop(&reader, false); + reader.Close(); + } + } + else + { + AVIReaderWin32 reader; + if (reader.Open(filename) != nsavi::READ_OK) + { + if (WaitForSingleObject(killswitch, 200) == WAIT_TIMEOUT) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + } + else + { + wchar_t root[4] = {0}; + StringCchCopy(root, 4, filename); + UINT drive_type = GetDriveType(root); + if (drive_type == DRIVE_CDROM) + PlayLoop(&reader, false); + else + PlayLoop(&reader, true); + reader.Close(); + } + + } + free(filename); + return 0; +} diff --git a/Src/Plugins/Input/in_avi/StreamSelector.h b/Src/Plugins/Input/in_avi/StreamSelector.h new file mode 100644 index 00000000..a81743d2 --- /dev/null +++ b/Src/Plugins/Input/in_avi/StreamSelector.h @@ -0,0 +1,32 @@ +#pragma once +#include "../Winamp/wa_ipc.h" +#include <bfc/platform/types.h> +class Streams : public ITrackSelector +{ +public: + void Reset(); + void AddAudioStream(int stream_num); + void AddVideoStream(int stream_num); + + void SetAudioStream(int stream_num); + void SetVideoStream(int stream_num); + + uint16_t audio_streams[256]; + int num_audio_streams; + int current_audio_stream; + uint16_t video_streams[256]; + int num_video_streams; + int current_video_stream; + + /* ITrackSelector interface */ + int getNumAudioTracks(); + void enumAudioTrackName(int n, char *buf, int size); + int getCurAudioTrack(); + + int getNumVideoTracks(); + void enumVideoTrackName(int n, char *buf, int size); + int getCurVideoTrack(); + + void setAudioTrack(int n); + void setVideoTrack(int n); +}; diff --git a/Src/Plugins/Input/in_avi/VideoThread.cpp b/Src/Plugins/Input/in_avi/VideoThread.cpp new file mode 100644 index 00000000..4dcdea3c --- /dev/null +++ b/Src/Plugins/Input/in_avi/VideoThread.cpp @@ -0,0 +1,428 @@ +#include "main.h" +#include "api__in_avi.h" +#include "../nu/AutoLock.h" +#include "../nu/SampleQueue.h" +#include "../Winamp/wa_ipc.h" +#include "interfaces.h" +#include "../nsavi/nsavi.h" +#include "../nu/ThreadName.h" +#include "player.h" + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +extern nsavi::HeaderList header_list; +extern int video_stream_num; + +int width, height; +extern IVideoOutput *video_output; +static HANDLE video_thread=0; +extern ifc_avivideodecoder *video_decoder; +bool video_opened=false; +static HANDLE coded_frames_event=0; +static Nullsoft::Utility::LockGuard coded_frames_guard; +uint64_t video_total_time=0; +HANDLE video_break=0, video_flush=0, video_flush_done=0, video_resume=0, video_ready=0; + +static int GetStreamNumber(uint32_t id) +{ + char *stream_data = (char *)(&id); + if (!isxdigit(stream_data[0]) || !isxdigit(stream_data[1])) + return -1; + + stream_data[2] = 0; + int stream_number = strtoul(stream_data, 0, 16); + return stream_number; +} + +void Video_Init() +{ + video_opened=false; + video_decoder=0; + video_thread=0; + width=0; + height=0; + video_total_time=0; + + if (coded_frames_event == 0) + coded_frames_event = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* video events */ + if (!video_break) + video_break = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (!video_flush_done) + video_flush_done = CreateEvent(NULL, FALSE, FALSE, NULL); + + if (!video_flush) + video_flush = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (!video_resume) + video_resume = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (!video_ready) + video_ready = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +struct FRAMEDATA +{ + FRAMEDATA() + { + type = 0; + data=0; + length=0; + } + + ~FRAMEDATA() + { + free(data); + } + void Reset() + { + free(data); + data=0; + length=0; + type = 0; + } + void Set(uint16_t _type, void *_data, size_t _length) + { + type = _type; + data = _data; + length = _length; + } + void *data; + size_t length; + uint16_t type; +}; + +static SampleQueue<FRAMEDATA> coded_frames; + +extern int GetOutputTime(); +struct VideoContext +{ + nsavi::avi_reader *reader; + nsavi::Demuxer *demuxer; + int video_stream_num; +}; + +static void DecodeVideo(FRAMEDATA *frame_data) +{ + HANDLE handles[] = {killswitch, video_break}; + if (WaitForMultipleObjects(2, handles, FALSE, 0) == WAIT_TIMEOUT) + { + int decodeResult = video_decoder->DecodeChunk(frame_data->type, frame_data->data, frame_data->length); + + if (decodeResult == ifc_avivideodecoder::AVI_SUCCESS) + { + void *data, *decoder_data; + while (video_decoder->GetPicture(&data, &decoder_data) == ifc_avivideodecoder::AVI_SUCCESS) + { + if (!video_opened) + { + int color_format; + double aspect_ratio=1.0; + int flip=0; + if (video_decoder->GetOutputProperties(&width, &height, &color_format, &aspect_ratio, &flip) == ifc_avivideodecoder::AVI_SUCCESS) + { + nsavi::VPRP *vprp = header_list.stream_list[video_stream_num].video_properties; + if (vprp) + { + uint32_t asp = vprp->aspect_ratio; + uint32_t aspect_x = HIWORD(asp); + uint32_t aspect_y = LOWORD(asp); + + aspect_ratio = (double)vprp->frame_width / (double)vprp->frame_height / ((double)aspect_x / (double)aspect_y); + } + else + aspect_ratio = 1.0/aspect_ratio; + + video_output->extended(VIDUSER_SET_THREAD_SAFE, 1, 0); + video_output->open(width, height, flip, aspect_ratio, color_format); + video_opened=true; + } + } + if (video_opened) + { + int timestamp=(int)(video_total_time*1000ULL/(uint64_t) header_list.stream_list[video_stream_num].stream_header->rate); +again: + int real_time =(int)GetOutputTime(); + if (timestamp > (real_time+5)) + { + HANDLE handles[] = {killswitch, video_break}; + int ret=WaitForMultipleObjects(2, handles, FALSE, timestamp-real_time); + if (ret != WAIT_TIMEOUT) + { + video_decoder->FreePicture(data, decoder_data); + frame_data->Reset(); + return ; + } + goto again; // TODO: handle paused state a little better than this + } + RGB32 *palette=0; + if (video_decoder->GetPalette(&palette) == ifc_avivideodecoder::AVI_SUCCESS) + { + video_output->extended(VIDUSER_SET_PALETTE, (INT_PTR)palette, 0); + } + video_output->draw(data); + } + video_total_time += header_list.stream_list[video_stream_num].stream_header->scale; + video_decoder->FreePicture(data, decoder_data); + } + } + + } + + // frame_data->Reset(); +} + +static DWORD CALLBACK VideoProcedure(LPVOID param) +{ + SetThreadName(-1,"AVI_VideoProcedure"); + HANDLE wait_handles[] = { killswitch, video_break, video_flush, video_resume, coded_frames_event }; + while (1) + { + int ret = WaitForMultipleObjects(5, wait_handles, FALSE, INFINITE); + + if (ret == WAIT_OBJECT_0) + { + break; + } + if (ret == WAIT_OBJECT_0 + 1) // video break + { + ResetEvent(coded_frames_event); + ResetEvent(video_break); + SetEvent(video_flush_done); + } + else if (ret == WAIT_OBJECT_0 + 2) // video flush + { + if (video_decoder) + video_decoder->Flush(); + ResetEvent(video_flush); + coded_frames.Trim(); + SetEvent(video_flush_done); + } + else if (ret == WAIT_OBJECT_0 + 3) // video resume + { + ResetEvent(video_resume); + SetEvent(coded_frames_event); + SetEvent(video_flush_done); + } + else if (ret == WAIT_OBJECT_0 + 4) // data from demuxer + { + FRAMEDATA *frame_data = 0; + while (frame_data = coded_frames.PopProcessed()) + { + DecodeVideo(frame_data); + frame_data->Reset(); + coded_frames.PushFree(frame_data); + } + } + }; + + if (video_opened && video_output) + video_output->close(); + video_opened=false; + + return 0; +} + +static DWORD CALLBACK VideoReaderProcedure(LPVOID param) +{ + VideoContext *ctx = (VideoContext *)param; + nsavi::avi_reader *reader = ctx->reader; + nsavi::Demuxer *demuxer = ctx->demuxer; + SetThreadName(-1,"AVI_VideoProcedure"); + HANDLE wait_handles[] = { killswitch, video_break, video_flush, video_resume }; + int waitTime = 0; + while (1) + { + int ret = WaitForMultipleObjects(4, wait_handles, FALSE, waitTime); + + if (ret == WAIT_OBJECT_0) + { + break; + } + if (ret == WAIT_OBJECT_0 + 1) // video break + { + ResetEvent(coded_frames_event); + ResetEvent(video_break); + SetEvent(video_flush_done); + waitTime = INFINITE; + } + else if (ret == WAIT_OBJECT_0 + 2) // video flush + { + if (video_decoder) + video_decoder->Flush(); + ResetEvent(video_flush); + coded_frames.Trim(); + SetEvent(video_flush_done); + waitTime = 0; + } + else if (ret == WAIT_OBJECT_0 + 3) // video resume + { + ResetEvent(video_resume); + SetEvent(coded_frames_event); + SetEvent(video_flush_done); + waitTime = 0; + } + else if (ret == WAIT_TIMEOUT) + { + void *data=0; + uint32_t data_size = 0; + uint32_t data_type = 0; + int ret = demuxer->GetNextMovieChunk(reader, &data, &data_size, &data_type, video_stream_num); + if (ret != nsavi::READ_OK) + { + break; + } + int stream_number = GetStreamNumber(data_type); + uint16_t type = (data_type>>16); + + if (stream_number == video_stream_num) + { + FRAMEDATA frame_data; + frame_data.data = data; + frame_data.length = data_size; + frame_data.type = type; + DecodeVideo(&frame_data); + frame_data.Reset(); + } + else + free(data); + } + }; + + if (video_opened && video_output) + video_output->close(); + video_opened=false; + free(ctx); + return 0; +} + +bool CreateVideoReaderThread(nsavi::Demuxer *demuxer, nsavi::avi_reader *video_reader) +{ + if (!video_decoder || video_only) + return false; + + VideoContext *context = (VideoContext *)calloc(1, sizeof(VideoContext)); + + context->reader = video_reader; + context->demuxer = demuxer; + DWORD threadId = 0; + video_thread = CreateThread(0, 0, VideoReaderProcedure, context, 0, &threadId); + SetThreadPriority(video_thread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + return true; +} + +bool OnVideo(uint16_t type, void *data, size_t length) +{ + if (!video_decoder) + return false; + + if (!video_only && !video_thread) + { + DWORD threadId; + video_thread = CreateThread(0, 0, VideoProcedure, 0, 0, &threadId); + SetThreadPriority(video_thread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + } + + if (video_thread) + { + FRAMEDATA *new_frame = coded_frames.PopFree(); + if (new_frame) + { + new_frame->Set(type, data, length); + coded_frames.PushProcessed(new_frame); + SetEvent(coded_frames_event); + } + return true; + } + else if (video_only) + { + FRAMEDATA *new_frame = coded_frames.PopFree(); + if (new_frame) + { + new_frame->Set(type, data, length); + DecodeVideo(new_frame); + new_frame->Reset(); + } + return true; + } + + return false; +} + +void Video_Stop() +{ + SetEvent(killswitch); + if (video_only) + { + video_thread=0; + + ResetEvent(coded_frames_event); + Nullsoft::Utility::AutoLock l(coded_frames_guard); + coded_frames.Trim(); + if (video_opened && video_output) + video_output->close(); + video_opened=false; + } + else + { + if (video_thread) + WaitForSingleObject(video_thread, INFINITE); + video_thread=0; + + ResetEvent(coded_frames_event); + Nullsoft::Utility::AutoLock l(coded_frames_guard); + coded_frames.Trim(); + } +} + +void Video_Close() +{ + video_opened=false; + + if (video_decoder) + { + video_decoder->Close(); + video_decoder=0; + } +} + +void Video_Break() +{ + if (!video_only && video_decoder) + { + SetEvent(video_break); + HANDLE events[2] = {video_flush_done, video_thread}; + WaitForMultipleObjects(2, events, FALSE, INFINITE); + } +} + +void Video_Flush() +{ + if (video_decoder) + { + if (video_only) + { + video_decoder->Flush(); + } + else + { + SetEvent(video_flush); + HANDLE events[2] = {video_flush_done, video_thread}; + WaitForMultipleObjects(2, events, FALSE, INFINITE); + } + } +} + +/* +bool Video_DecoderReady() +{ +if (!video_decoder) +return true; + +return !!video_decoder->Ready(); +} +*/ diff --git a/Src/Plugins/Input/in_avi/VideoThread.h b/Src/Plugins/Input/in_avi/VideoThread.h new file mode 100644 index 00000000..c8a134f1 --- /dev/null +++ b/Src/Plugins/Input/in_avi/VideoThread.h @@ -0,0 +1,12 @@ +#pragma once +#include <bfc/platform/types.h> +#include "../nsavi/demuxer.h" +extern uint64_t video_total_time; + +bool OnVideo(uint16_t type, void *data, size_t length); +void Video_Stop(); +void Video_Close(); +void Video_Flush(); +void Video_Init(); +void Video_Break(); +bool CreateVideoReaderThread(nsavi::Demuxer *demuxer, nsavi::avi_reader *video_reader);
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/api.cpp b/Src/Plugins/Input/in_avi/api.cpp new file mode 100644 index 00000000..545c00ac --- /dev/null +++ b/Src/Plugins/Input/in_avi/api.cpp @@ -0,0 +1,50 @@ +#include "api__in_avi.h" +#include "main.h" +#include <api/service/waservicefactory.h> + +api_config *AGAVE_API_CONFIG = 0; +api_application *WASABI_API_APP = 0; +api_stats *AGAVE_API_STATS = 0; +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +template <class api_T> +void ServiceBuild(api_T *&api_t, GUID factoryGUID_t) +{ + if (plugin.service) + { + waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + api_t = reinterpret_cast<api_T *>( factory->getInterface() ); + } +} + +template <class api_T> +void ServiceRelease(api_T *api_t, GUID factoryGUID_t) +{ + if (plugin.service && api_t) + { + waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + factory->releaseInterface(api_t); + } + api_t = NULL; +} + +void WasabiInit() +{ + ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); + ServiceBuild(AGAVE_API_STATS, AnonymousStatsGUID); + ServiceBuild(WASABI_API_LNG, languageApiGUID); + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(plugin.hDllInstance,InAviLangGUID); +} + +void WasabiQuit() +{ + ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(WASABI_API_APP, applicationApiServiceGuid); + ServiceRelease(AGAVE_API_STATS, AnonymousStatsGUID); + ServiceRelease(WASABI_API_LNG, languageApiGUID); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/api__in_avi.h b/Src/Plugins/Input/in_avi/api__in_avi.h new file mode 100644 index 00000000..06d7dd13 --- /dev/null +++ b/Src/Plugins/Input/in_avi/api__in_avi.h @@ -0,0 +1,15 @@ +#pragma once + +#include "../Agave/Config/api_config.h" +#include "../Agave/Language/api_language.h" + +#include <api/application/api_application.h> +extern api_application *applicationApi; +#define WASABI_API_APP applicationApi + +#include "../Winamp/api_stats.h" +extern api_stats *statsApi; +#define AGAVE_API_STATS statsApi + +void WasabiInit(); +void WasabiQuit(); diff --git a/Src/Plugins/Input/in_avi/http_avi_reader.cpp b/Src/Plugins/Input/in_avi/http_avi_reader.cpp new file mode 100644 index 00000000..abe9cf53 --- /dev/null +++ b/Src/Plugins/Input/in_avi/http_avi_reader.cpp @@ -0,0 +1,255 @@ +#include "main.h" +#include "http_avi_reader.h" +#include "../nu/AutoChar.h" +#include "api__in_avi.h" +#include <api/service/waservicefactory.h> +#include "../nu/ns_wc.h" +#include <strsafe.h> + +static const int http_buffer_size=128*1024; +// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C} +static const GUID internetConfigGroupGUID = +{ + 0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c } +}; + +AVIReaderHTTP::AVIReaderHTTP(HANDLE killswitch, HANDLE seek_event) +{ + seekable = 0; + http = 0; + position = 0; + handles[0]=killswitch; + handles[1]=seek_event; +} + +static void SetUserAgent(api_httpreceiver *http) +{ + char agent[256] = {0}; + StringCchPrintfA(agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString()); + http->addheader(agent); +} + +void AVIReaderHTTP::GetFilename(wchar_t *fn, size_t len) +{ + const char *url = http->get_url(); + MultiByteToWideCharSZ(CP_ACP, 0, url, -1, fn, (int)len); +} + +int AVIReaderHTTP::Open(const wchar_t *url) +{ + int use_proxy = 1; + + const wchar_t *proxy = AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0); + bool proxy80 = AGAVE_API_CONFIG->GetBool(internetConfigGroupGUID, L"proxy80", false); + if (proxy80 && wcsstr(url, L":") && (!wcsstr(url, L":80/") && wcsstr(url, L":80") != (url + wcslen(url) - 3))) + use_proxy = 0; + + waServiceFactory *sf = plugin.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->getInterface(); + + http->open(API_DNS_AUTODNS, http_buffer_size, (use_proxy && proxy && proxy[0]) ? (char *)AutoChar(proxy) : NULL); + http->addheader("Accept:*/*"); + SetUserAgent(http); + + http->connect(AutoChar(url, CP_UTF8), 0); + + return nsavi::READ_OK; +} + + +int AVIReaderHTTP::Open(const char *url, uint64_t start_offset) +{ + int use_proxy = 1; + + const wchar_t *proxy = AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0); + bool proxy80 = AGAVE_API_CONFIG->GetBool(internetConfigGroupGUID, L"proxy80", false); + if (proxy80 && strstr(url, ":") && (!strstr(url, ":80/") && strstr(url, ":80") != (url + strlen(url) - 3))) + use_proxy = 0; + + waServiceFactory *sf = plugin.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->getInterface(); + + http->open(API_DNS_AUTODNS, http_buffer_size, (use_proxy && proxy && proxy[0]) ? (char *)AutoChar(proxy) : NULL); + http->addheader("Accept:*/*"); + SetUserAgent(http); + + if (start_offset) + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "Range: bytes=%I64u-", start_offset); + http->addheader(temp); + position = start_offset; + } + + http->connect(url, !!start_offset); + + return nsavi::READ_OK; +} + +void AVIReaderHTTP::Close() +{ + if (http) + { + waServiceFactory *sf = plugin.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) sf->releaseInterface(http); + } + http = 0; +} + + +int AVIReaderHTTP::Connect() +{ + http->run(); + int status = http->get_status(); + while (status == HTTPRECEIVER_STATUS_CONNECTING || status == HTTPRECEIVER_STATUS_READING_HEADERS) + { + int ret = WaitForMultipleObjects(2, handles, FALSE, 50); + if (ret == WAIT_OBJECT_0+1) + return -2; + else if (ret != WAIT_TIMEOUT) + return -1; + http->run(); + status = http->get_status(); + } + + if (status == HTTPRECEIVER_STATUS_ERROR) + { + return nsavi::READ_FAILED; + } + const char *headers = http->getallheaders(); + const char *ranges = http->getheader("accept-ranges"); + seekable = (ranges && _stricmp(ranges, "bytes")); + + return nsavi::READ_OK; +} + +int AVIReaderHTTP::Buffer() +{ + while (http->bytes_available() < http_buffer_size && http->run() == HTTPRECEIVER_RUN_OK) + { + int ret = WaitForMultipleObjects(2, handles, FALSE, 50); + if (ret == WAIT_OBJECT_0+1) + return -2; + else if (ret != WAIT_TIMEOUT) + return -1; + } + + return nsavi::READ_OK; +} + +uint64_t AVIReaderHTTP::GetContentLength() +{ + const char *content_length = http->getheader("content-length"); + if (content_length) + return _atoi64(content_length); + else + return 0; +} + +int AVIReaderHTTP::Read(void *buffer, uint32_t read_length, uint32_t *bytes_read) +{ + uint32_t total_bytes_read=0; + for(;;) + { + int ret = http->run(); + int http_bytes_read = http->get_bytes(buffer, read_length); + + read_length -= http_bytes_read; + buffer = (uint8_t *)buffer + http_bytes_read; + total_bytes_read+=http_bytes_read; + position += http_bytes_read; + + if (!read_length) + { + *bytes_read = total_bytes_read; + return nsavi::READ_OK; + } + + if (http->bytes_available() == 0) + { + if (ret == HTTPRECEIVER_RUN_CONNECTION_CLOSED) + { + if (position == http->content_length()) + return nsavi::READ_EOF; + else + return nsavi::READ_DISCONNECT; + } + else if (ret == HTTPRECEIVER_RUN_ERROR) + { + return nsavi::READ_DISCONNECT; + } + } + + ret = WaitForMultipleObjects(2, handles, FALSE, 50); + if (ret == WAIT_OBJECT_0+1) + return -2; + else if (ret != WAIT_TIMEOUT) + return -1; + } + + return nsavi::READ_OK; +} + +int AVIReaderHTTP::Peek(void *buffer, uint32_t read_length, uint32_t *bytes_read) +{ + uint32_t total_bytes_read=0; + + while (http->bytes_available() < (int)read_length && http->run() == HTTPRECEIVER_RUN_OK) + { + int ret = WaitForMultipleObjects(2, handles, FALSE, 50); + if (ret == WAIT_OBJECT_0+1) + return -2; + else if (ret != WAIT_TIMEOUT) + return -1; + } + + *bytes_read = http->peek_bytes(buffer, read_length); + return nsavi::READ_OK; +} + +int AVIReaderHTTP::Seek(uint64_t seek_position) +{ + + // if position is forward of our current position, see if we have enough in the buffer to just advance the buffer + if (seek_position > position + && seek_position - position <= http->bytes_available()) + { + int bytes_read = http->get_bytes(0, (int)(seek_position - position)); + position += bytes_read; + return nsavi::READ_OK; + } + else + { + // otherwise, close connection and re-open with a start position + char *url = _strdup(http->get_url()); + Close(); + Open(url, seek_position); + free(url); + return Connect(); + } +} + +uint64_t AVIReaderHTTP::Tell() +{ + return position; +} + +int AVIReaderHTTP::Skip(uint32_t skip_bytes) +{ + // see if we have enough room in our buffer + if (http->bytes_available() >= (int)skip_bytes) + { + int bytes_read = http->get_bytes(0, skip_bytes); + position += bytes_read; + return nsavi::READ_OK; + } + else + { + // close connection and re-open with a start position + char *url = _strdup(http->get_url()); + Close(); + Open(url, position+skip_bytes); + free(url); + return Connect(); + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/http_avi_reader.h b/Src/Plugins/Input/in_avi/http_avi_reader.h new file mode 100644 index 00000000..66eb6d49 --- /dev/null +++ b/Src/Plugins/Input/in_avi/http_avi_reader.h @@ -0,0 +1,31 @@ +#pragma once +#include "../nsavi/avi_reader.h" +#include "../../..\Components\wac_network\wac_network_http_receiver_api.h" + +class AVIReaderHTTP : public nsavi::avi_reader +{ +public: + AVIReaderHTTP(HANDLE killswitch, HANDLE seek_event); + int Open(const wchar_t *url); + void Close(); + int Connect(); + int Buffer(); + + /* avi_reader implementation */ + int Read(void *buffer, uint32_t read_length, uint32_t *bytes_read); + int Peek(void *buffer, uint32_t read_length, uint32_t *bytes_read); + //void OverlappedHint(uint32_t read_length); + int Seek(uint64_t position); + uint64_t Tell(); + int Skip(uint32_t skip_bytes); + uint64_t GetContentLength(); + void GetFilename(wchar_t *fn, size_t len); +private: + /* internal methods */ + int Open(const char *url, uint64_t start_offset=0); +private: + api_httpreceiver *http; + uint64_t position; + bool seekable; + HANDLE handles[2]; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/ifc_aviaudiodecoder.h b/Src/Plugins/Input/in_avi/ifc_aviaudiodecoder.h new file mode 100644 index 00000000..00994bb8 --- /dev/null +++ b/Src/Plugins/Input/in_avi/ifc_aviaudiodecoder.h @@ -0,0 +1,67 @@ +#pragma once +#include <bfc/dispatch.h> + +class NOVTABLE ifc_aviaudiodecoder : public Dispatchable +{ +protected: + ifc_aviaudiodecoder() {} + ~ifc_aviaudiodecoder() {} +public: + enum + { + AVI_SUCCESS = 0, + AVI_NEED_MORE_INPUT = -1, + AVI_FAILURE=1, + AVI_RESYNC=2, + }; + int OutputFrameSize(uint32_t *frame_size); + int GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat); // can return an error code for "havn't locked to stream yet" + // many AVI files arbitrarily divide the data stream (e.g. an MP3 frame might span two chunks). Others put ALL the audio into ONE chunk. awesome. + // for this reason, you are given double pointers to the data buffer and a pointer to the data size + // and you are expected to update it on return + // if inputBufferBytes != 0, you will be called again with the same data (return AVI_SUCCESS, though) + // if you were unable to decode because of bitstream errors, return AVI_RESYNC so in_avi can try to correct timing + int DecodeChunk(uint16_t type, const void **inputBuffer, uint32_t *inputBufferBytes, void *outputBuffer, uint32_t *outputBufferBytes); + void Flush(); + void Close(); // self-destructs + void EndOfStream(); // no more input, output anything you have buffered + DISPATCH_CODES + { + OUTPUT_FRAME_SIZE = 0, + GET_OUTPUT_PROPERTIES = 1, + DECODE_CHUNK = 2, + FLUSH = 3, + CLOSE = 4, + END_OF_STREAM = 5, + }; +}; + +inline int ifc_aviaudiodecoder::OutputFrameSize(uint32_t *frame_size) +{ + return _call(OUTPUT_FRAME_SIZE, (int)AVI_FAILURE, frame_size); +} + +inline int ifc_aviaudiodecoder::GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat) +{ + return _call(GET_OUTPUT_PROPERTIES, (int)AVI_FAILURE, sampleRate, channels, bitsPerSample, isFloat); +} + +inline int ifc_aviaudiodecoder::DecodeChunk(uint16_t type, const void **inputBuffer, uint32_t *inputBufferBytes, void *outputBuffer, uint32_t *outputBufferBytes) +{ + return _call(DECODE_CHUNK, (int)AVI_FAILURE, type, inputBuffer, inputBufferBytes, outputBuffer, outputBufferBytes); +} + +inline void ifc_aviaudiodecoder::Flush() +{ + _voidcall(FLUSH); +} + +inline void ifc_aviaudiodecoder::Close() +{ + _voidcall(CLOSE); +} + +inline void ifc_aviaudiodecoder::EndOfStream() +{ + _voidcall(END_OF_STREAM); +} diff --git a/Src/Plugins/Input/in_avi/ifc_avivideodecoder.h b/Src/Plugins/Input/in_avi/ifc_avivideodecoder.h new file mode 100644 index 00000000..27b158f9 --- /dev/null +++ b/Src/Plugins/Input/in_avi/ifc_avivideodecoder.h @@ -0,0 +1,77 @@ +#pragma once +#include <bfc/dispatch.h> +#include <bfc/platform/types.h> + +class NOVTABLE ifc_avivideodecoder : public Dispatchable +{ +protected: + ifc_avivideodecoder() {} + ~ifc_avivideodecoder() {} +public: + enum + { + AVI_SUCCESS = 0, + AVI_NEED_MORE_INPUT = -1, + AVI_FAILURE=1, + }; + int GetOutputProperties(int *x, int *y, int *color_format, double *aspect_ratio, int *flip); + int DecodeChunk(uint16_t type, const void *inputBuffer, size_t inputBufferBytes); + void Flush(); + void Close(); + int GetPicture(void **data, void **decoder_data); + void FreePicture(void *data, void *decoder_data); + void EndOfStream(); // signal to the decoder that the video bitstream is over - flush any buffered frames + void HurryUp(int state); // 1 = hurry up (drop unnecessary frames), 0 = revert to normal + int GetPalette(RGB32 **palette); + DISPATCH_CODES + { + GET_OUTPUT_PROPERTIES = 0, + DECODE_CHUNK = 1, + FLUSH = 2, + CLOSE = 3, + GET_PICTURE = 4, + FREE_PICTURE = 5, + END_OF_STREAM = 6, + HURRY_UP = 7, + GET_PALETTE = 8, + }; +}; + +inline int ifc_avivideodecoder::GetOutputProperties(int *x, int *y, int *color_format, double *aspect_ratio, int *flip) +{ + return _call(GET_OUTPUT_PROPERTIES, (int)AVI_FAILURE, x, y, color_format, aspect_ratio, flip); +} +inline int ifc_avivideodecoder::DecodeChunk(uint16_t type, const void *inputBuffer, size_t inputBufferBytes) +{ + return _call(DECODE_CHUNK, (int)AVI_FAILURE, type, inputBuffer, inputBufferBytes); +} +inline void ifc_avivideodecoder::Flush() +{ + _voidcall(FLUSH); +} +inline void ifc_avivideodecoder::Close() +{ + _voidcall(CLOSE); +} +inline int ifc_avivideodecoder::GetPicture(void **data, void **decoder_data) +{ + return _call(GET_PICTURE, (int)AVI_FAILURE, data, decoder_data); +} +inline void ifc_avivideodecoder::FreePicture(void *data, void *decoder_data) +{ + _voidcall(FREE_PICTURE, data, decoder_data); +} +inline void ifc_avivideodecoder::EndOfStream() +{ + _voidcall(END_OF_STREAM); +} + +inline void ifc_avivideodecoder::HurryUp(int state) +{ + _voidcall(HURRY_UP, state); +} + +inline int ifc_avivideodecoder::GetPalette(RGB32 **palette) +{ + return _call(GET_PALETTE, (int)AVI_FAILURE, palette); +} diff --git a/Src/Plugins/Input/in_avi/in_avi.rc b/Src/Plugins/Input/in_avi/in_avi.rc new file mode 100644 index 00000000..0191bd2c --- /dev/null +++ b/Src/Plugins/Input/in_avi/in_avi.rc @@ -0,0 +1,159 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#include ""version.rc2""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_INFODIALOG DIALOGEX 0, 0, 339, 266 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "AVI File Properties" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Close",IDOK,282,245,50,14 + CONTROL "",IDC_TAB1,"SysTabControl32",0x0,7,7,325,235 +END + +IDD_TRACKS DIALOGEX 0, 0, 316, 183 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_TRACKLIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_TABSTOP,7,7,302,169 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_INFODIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 332 + TOPMARGIN, 7 + BOTTOMMARGIN, 259 + END + + IDD_TRACKS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 309 + TOPMARGIN, 7 + BOTTOMMARGIN, 176 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_AVI "Nullsoft AVI Demuxer v%s" + 65535 "{CA36E14A-3742-4edc-A40F-2BC87F26B347}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_AVI_OLD "Nullsoft AVI Demuxer" + IDS_BUFFERING "Buffering" + IDS_FAMILY_STRING "AVI Video" + IDS_TAB_TRACKS "Tracks" + IDS_TAB_METADATA "Metadata" + IDS_COLUMN_TRACK_TYPE "Type" + IDS_COLUMN_CODEC_NAME "Codec Name" + IDS_COLUMN_CODEC_ID "Codec ID" + IDS_COLUMN_DESCRIPTION "Description" + IDS_COLUMN_STREAM_NAME "Stream Name" + IDS_COLUMN_FIELD "Field" + IDS_COLUMN_FOURCC "FOURCC" + IDS_COLUMN_VALUE "Value" + IDS_TYPE_VIDEO "Video" +END + +STRINGTABLE +BEGIN + IDS_TYPE_AUDIO "Audio" + IDS_FIELD_TOOL "Tool" + IDS_FIELD_ARTIST "Artist" + IDS_FIELD_PUBLISHER "Publisher" + IDS_FIELD_ALBUM "Album" + IDS_FIELD_COMPOSER "Composer" + IDS_FIELD_GENRE "Genre" + IDS_FIELD_COMMENT "Comment" + IDS_FIELD_TITLE "Title" + IDS_FIELD_COPYRIGHT "Copyright" + IDS_KBPS "kbps" + IDS_AVI_DESC "Audio/Video Interleave (AVI)" + IDS_ABOUT_TEXT "%s\n© 2009-2023 Winamp SA\nWritten by: Ben Allison\nBuild date: %s" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#include "version.rc2" + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Src/Plugins/Input/in_avi/in_avi.sln b/Src/Plugins/Input/in_avi/in_avi.sln new file mode 100644 index 00000000..0f8142f8 --- /dev/null +++ b/Src/Plugins/Input/in_avi/in_avi.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29509.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_avi", "in_avi.vcxproj", "{FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}.Debug|Win32.ActiveCfg = Debug|Win32 + {FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}.Debug|Win32.Build.0 = Debug|Win32 + {FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}.Debug|x64.ActiveCfg = Debug|x64 + {FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}.Debug|x64.Build.0 = Debug|x64 + {FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}.Release|Win32.ActiveCfg = Release|Win32 + {FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}.Release|Win32.Build.0 = Release|Win32 + {FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}.Release|x64.ActiveCfg = Release|x64 + {FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {81F95D61-31E0-4423-B565-752889A9A18B} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_avi/in_avi.vcxproj b/Src/Plugins/Input/in_avi/in_avi.vcxproj new file mode 100644 index 00000000..e2688993 --- /dev/null +++ b/Src/Plugins/Input/in_avi/in_avi.vcxproj @@ -0,0 +1,284 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{FE01A3D7-A137-4AEB-9061-20BE2ACF2D5F}</ProjectGuid> + <RootNamespace>in_avi</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + <IncludePath>$(IncludePath)</IncludePath> + <LibraryPath>$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + <IncludePath>$(IncludePath)</IncludePath> + <LibraryPath>$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Label="Vcpkg"> + <VcpkgEnabled>false</VcpkgEnabled> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;IN_AVI_EXPORTS;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ +xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;IN_AVI_EXPORTS;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ +xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_AVI_EXPORTS;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;4018;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_AVI_EXPORTS;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;4018;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\..\..\nsavi\demuxer.cpp" /> + <ClCompile Include="..\..\..\nsavi\duration.cpp" /> + <ClCompile Include="..\..\..\nsavi\info.cpp" /> + <ClCompile Include="..\..\..\nsavi\metadata.cpp" /> + <ClCompile Include="..\..\..\nsavi\ParserBase.cpp" /> + <ClCompile Include="..\..\..\nsavi\read.cpp" /> + <ClCompile Include="..\..\..\nsavi\seektable.cpp" /> + <ClCompile Include="..\..\..\nu\listview.cpp" /> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp" /> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp" /> + <ClCompile Include="api.cpp" /> + <ClCompile Include="ExtendedFileInfo.cpp" /> + <ClCompile Include="http_avi_reader.cpp" /> + <ClCompile Include="InfoDialog.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="PlayThread.cpp" /> + <ClCompile Include="VideoThread.cpp" /> + <ClCompile Include="win32_avi_reader.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\nsavi\avi_header.h" /> + <ClInclude Include="..\..\..\nsavi\avi_reader.h" /> + <ClInclude Include="..\..\..\nsavi\demuxer.h" /> + <ClInclude Include="..\..\..\nsavi\duration.h" /> + <ClInclude Include="..\..\..\nsavi\info.h" /> + <ClInclude Include="..\..\..\nsavi\metadata.h" /> + <ClInclude Include="..\..\..\nsavi\nsavi.h" /> + <ClInclude Include="..\..\..\nsavi\ParserBase.h" /> + <ClInclude Include="..\..\..\nsavi\read.h" /> + <ClInclude Include="..\..\..\nsavi\seektable.h" /> + <ClInclude Include="..\..\..\nu\AudioOutput.h" /> + <ClInclude Include="..\..\..\nu\listview.h" /> + <ClInclude Include="..\..\..\nu\RingBuffer.h" /> + <ClInclude Include="..\..\..\nu\SpillBuffer.h" /> + <ClInclude Include="..\..\..\nu\VideoClock.h" /> + <ClInclude Include="api__in_avi.h" /> + <ClInclude Include="http_avi_reader.h" /> + <ClInclude Include="ifc_aviaudiodecoder.h" /> + <ClInclude Include="ifc_avivideodecoder.h" /> + <ClInclude Include="interfaces.h" /> + <ClInclude Include="main.h" /> + <ClInclude Include="player.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="StreamSelector.h" /> + <ClInclude Include="svc_avidecoder.h" /> + <ClInclude Include="VideoThread.h" /> + <ClInclude Include="win32_avi_reader.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_avi.rc" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/in_avi.vcxproj.filters b/Src/Plugins/Input/in_avi/in_avi.vcxproj.filters new file mode 100644 index 00000000..e6323f44 --- /dev/null +++ b/Src/Plugins/Input/in_avi/in_avi.vcxproj.filters @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="win32_avi_reader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="api.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedFileInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="http_avi_reader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="InfoDialog.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="PlayThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VideoThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsavi\demuxer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsavi\duration.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsavi\info.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\listview.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsavi\metadata.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsavi\ParserBase.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsavi\read.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nsavi\seektable.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api__in_avi.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="http_avi_reader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ifc_aviaudiodecoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ifc_avivideodecoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="interfaces.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="player.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="StreamSelector.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="svc_avidecoder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VideoThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="win32_avi_reader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\AudioOutput.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\avi_header.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\avi_reader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\demuxer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\duration.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\info.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\listview.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\metadata.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\nsavi.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\ParserBase.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\read.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\RingBuffer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nsavi\seektable.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\SpillBuffer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\VideoClock.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{e77a3e6b-2a9d-49ed-9c56-aae5b1e712ec}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{799c4db0-c7b2-4460-ac6a-f399052539a6}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{386d35de-b42e-4963-8764-99635e1efddb}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_avi.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/interfaces.h b/Src/Plugins/Input/in_avi/interfaces.h new file mode 100644 index 00000000..e2fb2f0a --- /dev/null +++ b/Src/Plugins/Input/in_avi/interfaces.h @@ -0,0 +1,4 @@ +#pragma once +#include "svc_avidecoder.h" +#include "ifc_aviaudiodecoder.h" +#include "ifc_avivideodecoder.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/main.cpp b/Src/Plugins/Input/in_avi/main.cpp new file mode 100644 index 00000000..844da8fc --- /dev/null +++ b/Src/Plugins/Input/in_avi/main.cpp @@ -0,0 +1,330 @@ +#include "main.h" +#include "../winamp/wa_ipc.h" +#include "api__in_avi.h" +#include "../nsavi/nsavi.h" +#include "win32_avi_reader.h" +#include "resource.h" +#include <strsafe.h> + +#define AVI_PLUGIN_VERSION L"0.78" + +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = +{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +void SetFileExtensions(void) +{ + static char fileExtensionsString[256] = {0}; // "AVI\0Audio/Video Interleave (AVI)\0" + char* end = 0; + size_t remaining = 0; + StringCchCopyExA(fileExtensionsString, 256, "AVI", &end, &remaining, 0); + StringCchCopyExA(end+1, remaining-1, WASABI_API_LNGSTRING(IDS_AVI_DESC), 0, 0, 0); + plugin.FileExtensions = fileExtensionsString; +} + +HANDLE killswitch = 0, seek_event = 0; +volatile LONG seek_position=-1; +int g_duration = -1; +nu::VideoClock video_clock; +int paused = 0; +static HANDLE play_thread = 0; +int video_only=0; +In_Module *dshow_mod = 0; +HMODULE in_dshow=0; +wchar_t pluginName[256] = {0}; + +static int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) +{ + MSGBOXPARAMS msgbx = {sizeof(MSGBOXPARAMS),0}; + msgbx.lpszText = message; + msgbx.lpszCaption = title; + msgbx.lpszIcon = MAKEINTRESOURCE(102); + msgbx.hInstance = GetModuleHandle(0); + msgbx.dwStyle = MB_USERICON; + msgbx.hwndOwner = parent; + return MessageBoxIndirect(&msgbx); +} + +void About(HWND hwndParent) +{ + wchar_t message[1024] = {0}, text[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_AVI_OLD,text,1024); + StringCchPrintf(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + plugin.description, TEXT(__DATE__)); + DoAboutMessageBox(hwndParent,text,message); +} + +int Init() +{ + if (!IsWindow(plugin.hMainWindow)) + return IN_INIT_FAILURE; + + WasabiInit(); + + StringCchPrintfW(pluginName, ARRAYSIZE(pluginName), + WASABI_API_LNGSTRINGW(IDS_NULLSOFT_AVI), AVI_PLUGIN_VERSION); + plugin.description = (char*)pluginName; + SetFileExtensions(); + return IN_INIT_SUCCESS; +} + +void Quit() +{ + WasabiQuit(); + if (in_dshow) + { + FreeLibrary(in_dshow); + in_dshow = NULL; + } +} + +void GetFileInfo(const wchar_t *file, wchar_t *title, int *length_in_ms) +{ + if (title) + *title=0; + if (length_in_ms) + { + if (file && *file) + { + *length_in_ms = -1000; // fallback if anything fails + AVIReaderWin32 reader; + if (reader.Open(file) == nsavi::READ_OK) + { + nsavi::Duration duration(&reader); + duration.GetDuration(length_in_ms); + reader.Close(); + } + } + else + { + if (dshow_mod) + { + dshow_mod->GetFileInfo(file, title, length_in_ms); + } + else + { + + *length_in_ms=g_duration; + } + } + } + + +} + +int InfoBox(const wchar_t *file, HWND hwndParent) +{ + AVIReaderWin32 reader; + if (reader.Open(file) == nsavi::READ_OK) + { + nsavi::Metadata metadata(&reader); + uint32_t type; + if (metadata.GetRIFFType(&type) == nsavi::READ_OK && type == ' IVA') + { + WASABI_API_DIALOGBOXPARAMW(IDD_INFODIALOG, hwndParent, InfoDialog, (LPARAM)&metadata); + } + reader.Close(); + } + + return INFOBOX_UNCHANGED; +} + +int IsOurFile(const wchar_t *fn) +{ + return 0; +} + + +int Play(const wchar_t *fn) // return zero on success, -1 on file-not-found, some other value on other (stopping winamp) error +{ + g_duration = -1; + seek_position = -1; + video_clock.Reset(); + video_only=false; + paused=0; + if (!killswitch) + killswitch = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!seek_event) + seek_event = CreateEvent(NULL, TRUE, FALSE, NULL); + + ResetEvent(killswitch); + ResetEvent(seek_event); + + play_thread = CreateThread(0, 0, AVIPlayThread, _wcsdup(fn), 0, 0); + SetThreadPriority(play_thread, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + return 0; // success +} + + +void Pause() +{ + paused = 1; + + if (dshow_mod) + { + dshow_mod->Pause(); + } + else if (video_only) + { + video_clock.Pause(); + } + else + { + plugin.outMod->Pause(1); + } +} + +void UnPause() +{ + paused = 0; + if (dshow_mod) + { + dshow_mod->UnPause(); + } + else if (video_only) + { + video_clock.Unpause(); + } + else + { + plugin.outMod->Pause(0); + } +} + +int IsPaused() +{ + if (dshow_mod) + return dshow_mod->IsPaused(); + + return paused; +} + +void Stop() +{ + if (dshow_mod) + { + dshow_mod->Stop(); + dshow_mod=0; + } + else if (play_thread) + { + SetEvent(killswitch); + WaitForSingleObject(play_thread, INFINITE); + ResetEvent(killswitch); + play_thread=0; + } +} + +// time stuff +int GetLength() +{ + if (dshow_mod) + return dshow_mod->GetLength(); + else + return g_duration; +} + +int GetOutputTime() +{ + if (dshow_mod) + return dshow_mod->GetOutputTime(); + else if (video_only) + return video_clock.GetOutputTime(); + if (plugin.outMod) + return plugin.outMod->GetOutputTime(); + else + return 0; +} + +void SetOutputTime(int time_in_ms) +{ + if (dshow_mod) + { + dshow_mod->SetOutputTime(time_in_ms); + } + else if (seek_event) + { + InterlockedExchange(&seek_position, time_in_ms); + SetEvent(seek_event); + } +} + +void SetVolume(int volume) +{ + if (dshow_mod) + { + dshow_mod->SetVolume(volume); + } + else + { + plugin.outMod->SetVolume(volume); + } +} + +void SetPan(int pan) +{ + if (dshow_mod) + { + dshow_mod->SetPan(pan); + } + else + { + plugin.outMod->SetPan(pan); + } +} + +void EQSet(int on, char data[10], int preamp) +{ + if (dshow_mod) + { + dshow_mod->EQSet(on, data, preamp); + return; + } +} + +In_Module plugin = +{ + IN_VER_RET, + "nullsoft(in_avi.dll)", + NULL, // hMainWindow + NULL, // hDllInstance + 0 /*"AVI\0Audio/Video Interleave (AVI)\0"*/, + 1, // is seekable + IN_MODULE_FLAG_USES_OUTPUT_PLUGIN, //UsesOutputPlug + About, + About, + Init, + Quit, + GetFileInfo, + InfoBox, + IsOurFile, + Play, + Pause, + UnPause, + IsPaused, + Stop, + GetLength, + GetOutputTime, + SetOutputTime, + SetVolume, + SetPan, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + EQSet, + 0, + 0 +}; + +extern "C" __declspec(dllexport) In_Module * winampGetInModule2() +{ + return &plugin; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/main.h b/Src/Plugins/Input/in_avi/main.h new file mode 100644 index 00000000..fb905612 --- /dev/null +++ b/Src/Plugins/Input/in_avi/main.h @@ -0,0 +1,19 @@ +#pragma once +#include "../winamp/in2.h" +#include "../nu/VideoClock.h" +#include "../nsavi/nsavi.h" +extern In_Module plugin, *dshow_mod; +DWORD CALLBACK AVIPlayThread(LPVOID param); +extern HANDLE killswitch, seek_event; +extern volatile LONG seek_position; +extern int g_duration; +extern nu::VideoClock video_clock; +extern int video_only; +extern HMODULE in_dshow; + +/* InfoDialog.cpp */ +INT_PTR CALLBACK InfoDialog(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +void GetVideoCodecName(wchar_t *str, size_t str_cch, nsavi::STRF *stream_format); +void GetVideoCodecDescription(wchar_t *str, size_t str_cch, nsavi::STRF *stream_format); +void GetAudioCodecName(wchar_t *str, size_t str_cch, nsavi::STRF *stream_format); +void GetAudioCodecDescription(wchar_t *str, size_t str_cch, nsavi::STRF *stream_format);
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/player.h b/Src/Plugins/Input/in_avi/player.h new file mode 100644 index 00000000..f7699309 --- /dev/null +++ b/Src/Plugins/Input/in_avi/player.h @@ -0,0 +1,3 @@ +#pragma once + +extern int audio_stream_num, video_stream_num;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_avi/resource.h b/Src/Plugins/Input/in_avi/resource.h new file mode 100644 index 00000000..990d046b --- /dev/null +++ b/Src/Plugins/Input/in_avi/resource.h @@ -0,0 +1,50 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_avi.rc +// +#define IDS_NULLSOFT_AVI_OLD 0 +#define IDS_BUFFERING 2 +#define IDS_AVI_VIDEO 3 +#define IDS_FAMILY_STRING 3 +#define IDS_STRING4 4 +#define IDS_TAB_TRACKS 5 +#define IDS_TAB_METADATA 6 +#define IDS_COLUMN_TRACK_TYPE 7 +#define IDS_COLUMN_CODEC_NAME 8 +#define IDS_COLUMN_CODEC_ID 9 +#define IDS_COLUMN_DESCRIPTION 10 +#define IDS_COLUMN_STREAM_NAME 11 +#define IDS_COLUMN_FIELD 12 +#define IDS_COLUMN_FOURCC 13 +#define IDS_COLUMN_VALUE 14 +#define IDS_TYPE_VIDEO 15 +#define IDS_TYPE_AUDIO 16 +#define IDS_FIELD_TOOL 17 +#define IDS_FIELD_ARTIST 18 +#define IDS_FIELD_PUBLISHER 19 +#define IDS_FIELD_ALBUM 20 +#define IDS_FIELD_COMPOSER 21 +#define IDS_FIELD_GENRE 22 +#define IDS_FIELD_COMMENT 23 +#define IDS_FIELD_TITLE 24 +#define IDS_FIELD_COPYRIGHT 25 +#define IDS_STRING105 26 +#define IDS_KBPS 26 +#define IDS_AVI_DESC 27 +#define IDS_ABOUT_TEXT 28 +#define IDD_INFODIALOG 103 +#define IDD_TRACKS 104 +#define IDC_TRACKLIST 1001 +#define IDC_TAB1 1002 +#define IDS_NULLSOFT_AVI 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 108 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_avi/svc_avidecoder.h b/Src/Plugins/Input/in_avi/svc_avidecoder.h new file mode 100644 index 00000000..28b7300b --- /dev/null +++ b/Src/Plugins/Input/in_avi/svc_avidecoder.h @@ -0,0 +1,74 @@ +#pragma once +#include <bfc/dispatch.h> +#include "../nsavi/avi_header.h" +#include <api/service/services.h> +class ifc_avivideodecoder; +class ifc_aviaudiodecoder; +class ifc_avitextdecoder; +class ifc_avimididecoder; + +class NOVTABLE svc_avidecoder : public Dispatchable +{ +protected: + svc_avidecoder() {} + ~svc_avidecoder() {} +public: + static FOURCC getServiceType() { return WaSvc::AVIDECODER; } + enum + { + CREATEDECODER_SUCCESS = 0, + CREATEDECODER_NOT_MINE = -1, // graceful failure + CREATEDECODER_FAILURE = 1, // generic failure - codec_id is ours but we weren't able to create the decoder (e.g. track_entry_data) + }; + int CreateAudioDecoder(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, + unsigned int preferred_bits, unsigned int max_channels, bool floating_point, + ifc_aviaudiodecoder **decoder); + int CreateVideoDecoder(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, ifc_avivideodecoder **decoder); + + // for retrieving short codec names, e.g. "MPEG Audio" or "H.264" + int GetAudioCodec(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, wchar_t *buf, size_t buf_cch); + int GetVideoCodec(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, wchar_t *buf, size_t buf_cch); + + // for longer description, e.g. "H.264 320x240" or "PCM 16bit stereo 44.1khz" + int GetAudioDescription(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, wchar_t *buf, size_t buf_cch); + int GetVideoDescription(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, wchar_t *buf, size_t buf_cch); + DISPATCH_CODES + { + CREATE_AUDIO_DECODER = 0, + CREATE_VIDEO_DECODER = 1, + GET_AUDIO_CODEC = 2, + GET_VIDEO_CODEC = 3, + GET_AUDIO_DESCRIPTION = 4, + GET_VIDEO_DESCRIPTION = 5, + }; +}; + +inline int svc_avidecoder::CreateAudioDecoder(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, unsigned int preferred_bits, unsigned int max_channels, bool floating_point, ifc_aviaudiodecoder **decoder) +{ + return _call(CREATE_AUDIO_DECODER, (int)CREATEDECODER_NOT_MINE, avi_header, stream_header, stream_format, stream_data, preferred_bits, max_channels, floating_point, decoder); +} + +inline int svc_avidecoder::CreateVideoDecoder(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, ifc_avivideodecoder **decoder) +{ + return _call(CREATE_VIDEO_DECODER, (int)CREATEDECODER_NOT_MINE, avi_header, stream_header, stream_format, stream_data, decoder); +} + +inline int svc_avidecoder::GetAudioCodec(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, wchar_t *buf, size_t buf_cch) +{ + return _call(GET_AUDIO_CODEC, (int)CREATEDECODER_NOT_MINE, avi_header, stream_header, stream_format, stream_data, buf, buf_cch); +} + +inline int svc_avidecoder::GetVideoCodec(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, wchar_t *buf, size_t buf_cch) +{ + return _call(GET_VIDEO_CODEC, (int)CREATEDECODER_NOT_MINE, avi_header, stream_header, stream_format, stream_data, buf, buf_cch); +} + +inline int svc_avidecoder::GetAudioDescription(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, wchar_t *buf, size_t buf_cch) +{ + return _call(GET_AUDIO_DESCRIPTION, (int)CREATEDECODER_NOT_MINE, avi_header, stream_header, stream_format, stream_data, buf, buf_cch); +} + +inline int svc_avidecoder::GetVideoDescription(const nsavi::AVIH *avi_header, const nsavi::STRH *stream_header, const nsavi::STRF *stream_format, const nsavi::STRD *stream_data, wchar_t *buf, size_t buf_cch) +{ + return _call(GET_VIDEO_DESCRIPTION, (int)CREATEDECODER_NOT_MINE, avi_header, stream_header, stream_format, stream_data, buf, buf_cch); +} diff --git a/Src/Plugins/Input/in_avi/version.rc2 b/Src/Plugins/Input/in_avi/version.rc2 new file mode 100644 index 00000000..eec411af --- /dev/null +++ b/Src/Plugins/Input/in_avi/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,78,0,0 + PRODUCTVERSION WINAMP_PRODUCTVER + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Winamp SA" + VALUE "FileDescription", "Winamp Input Plug-in" + VALUE "FileVersion", "0,78,0,0" + VALUE "InternalName", "Nullsoft AVI Demuxer" + VALUE "LegalCopyright", "Copyright © 2009-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_avi.dll" + VALUE "ProductName", "Winamp" + VALUE "ProductVersion", STR_WINAMP_PRODUCTVER + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/Src/Plugins/Input/in_avi/win32_avi_reader.cpp b/Src/Plugins/Input/in_avi/win32_avi_reader.cpp new file mode 100644 index 00000000..cc716d86 --- /dev/null +++ b/Src/Plugins/Input/in_avi/win32_avi_reader.cpp @@ -0,0 +1,174 @@ +#include "win32_avi_reader.h" +#include <strsafe.h> + +const int _BUFFER_SIZE_ = 16384; + +AVIReaderWin32::AVIReaderWin32() +{ + hFile = 0; + + _ring_buffer.reserve( _BUFFER_SIZE_ ); + + end_of_file = false; + + position.QuadPart = 0; + local_filename = 0; +} + +AVIReaderWin32::~AVIReaderWin32() +{ + free( local_filename ); +} + +void AVIReaderWin32::Close() +{ + if ( hFile && hFile != INVALID_HANDLE_VALUE ) + { + //CancelIo(hFile); + CloseHandle( hFile ); + } +} + +uint64_t AVIReaderWin32::GetContentLength() +{ + LARGE_INTEGER position; + position.QuadPart = 0; + position.LowPart = GetFileSize( hFile, (LPDWORD)&position.HighPart ); + + if ( position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR ) + return 0; + else + return position.QuadPart; +} + +void AVIReaderWin32::GetFilename( wchar_t *fn, size_t len ) +{ + StringCchCopyW( fn, len, local_filename ); +} + +int AVIReaderWin32::Open( const wchar_t *filename ) +{ + free( local_filename ); + local_filename = _wcsdup( filename ); + + hFile = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0 ); + if ( hFile == INVALID_HANDLE_VALUE ) + return nsavi::READ_NOT_FOUND; + + return nsavi::READ_OK; +} + +/* used by RingBuffer::fill() */ +size_t AVIReaderWin32::Read( void *dest, size_t len ) +{ + // TODO: use overlapped I/O so can we wait on the read simultaneously with the killswitch and seek_event + DWORD bytes_read = 0; + if ( ReadFile( hFile, dest, (DWORD)len, &bytes_read, NULL ) && bytes_read != len ) + end_of_file = true; + + return bytes_read; +} + +int AVIReaderWin32::Read( void *p_read_buffer, uint32_t read_length, uint32_t *bytes_read ) +{ + if ( end_of_file && _ring_buffer.empty() ) + return nsavi::READ_EOF; + + size_t total_bytes_read = 0; + + while ( read_length && !( end_of_file && _ring_buffer.empty() ) ) + { + // read what we can from the buffer + size_t bytes_read = _ring_buffer.read( p_read_buffer, read_length ); + p_read_buffer = (uint8_t *)p_read_buffer + bytes_read; + read_length -= (uint32_t)bytes_read; + total_bytes_read += bytes_read; + position.QuadPart += bytes_read; + + if ( read_length > _BUFFER_SIZE_ ) + { + // read directly from the file if we have a large read + bytes_read = Read( p_read_buffer, read_length ); + p_read_buffer = (uint8_t *)p_read_buffer + bytes_read; + read_length -= (uint32_t)bytes_read; + total_bytes_read += bytes_read; + position.QuadPart += bytes_read; + } + else + { + // refill buffer if necessary + _ring_buffer.fill( this, _BUFFER_SIZE_ ); + } + } + + *bytes_read = (uint32_t)total_bytes_read; + + return nsavi::READ_OK; +} + +int AVIReaderWin32::Peek( void *read_buffer, uint32_t read_length, uint32_t *bytes_read ) +{ + if ( end_of_file && _ring_buffer.empty() ) + return nsavi::READ_EOF; + + // refill buffer if necessary + if ( _ring_buffer.size() < read_length ) + _ring_buffer.fill( this, _BUFFER_SIZE_ ); + + *bytes_read = (uint32_t)_ring_buffer.peek( read_buffer, read_length ); + + return nsavi::READ_OK; +} + +static LONGLONG Seek64( HANDLE hf, __int64 distance, DWORD MoveMethod ) +{ + LARGE_INTEGER li; + li.QuadPart = distance; + li.LowPart = SetFilePointer( hf, li.LowPart, &li.HighPart, MoveMethod ); + if ( li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR ) + { + li.QuadPart = -1; + } + + return li.QuadPart; +} + +int AVIReaderWin32::Seek( uint64_t new_position ) +{ + _ring_buffer.clear(); + + position.QuadPart = Seek64( hFile, new_position, SEEK_SET ); + end_of_file = ( position.QuadPart != new_position ); + + return nsavi::READ_OK; +} + +uint64_t AVIReaderWin32::Tell() +{ + return position.QuadPart; +} + +int AVIReaderWin32::Skip( uint32_t skip_bytes ) +{ + if ( end_of_file && _ring_buffer.empty() ) + return nsavi::READ_EOF; + + if ( skip_bytes < _ring_buffer.size() ) + { + _ring_buffer.advance( skip_bytes ); + + position.QuadPart += skip_bytes; + + return nsavi::READ_OK; + } + else + { + return Seek( position.QuadPart + skip_bytes ); + } +} + +void AVIReaderWin32::OverlappedHint( uint32_t read_length ) +{ + if ( read_length > _ring_buffer.size() ) + _ring_buffer.fill( this, _BUFFER_SIZE_ ); +} diff --git a/Src/Plugins/Input/in_avi/win32_avi_reader.h b/Src/Plugins/Input/in_avi/win32_avi_reader.h new file mode 100644 index 00000000..f853fad8 --- /dev/null +++ b/Src/Plugins/Input/in_avi/win32_avi_reader.h @@ -0,0 +1,36 @@ +#pragma once +#include "../nsavi/avi_reader.h" +#include "../nu/ringbuffer.h" + +class AVIReaderWin32 : public nsavi::avi_reader, private Filler +{ +public: + AVIReaderWin32(); + ~AVIReaderWin32(); + int Open(const wchar_t *filename); + void Close(); + +/* avi_reader implementation */ + uint64_t GetContentLength(); + int Seek(uint64_t position); + +private: + int Read(void *p_read_buffer, uint32_t read_length, uint32_t *bytes_read); + int Peek(void *p_read_buffer, uint32_t read_length, uint32_t *bytes_read); + void OverlappedHint(uint32_t read_length); + uint64_t Tell(); + int Skip(uint32_t skip_bytes); + void GetFilename(wchar_t *fn, size_t len); + + /* internal helpers */ + void DoRead(); +/* RingBuffer Filler implementation */ + size_t Read(void *dest, size_t len); + + HANDLE hFile; // i hate hungarian notation, but calling this hFile is a force of habit :) + RingBuffer _ring_buffer; + bool end_of_file; + LARGE_INTEGER position; // since we read ahead, we need to keep track of this separately + wchar_t *local_filename; + +};
\ No newline at end of file |